import { Datasource, ManyToManyRelationshipFieldMetadata, ManyToOneRelationshipFieldMetadata, OneToManyRelationshipFieldMetadata, RelationshipFieldMetadata, RelationshipType, Table, TableSourceType, } from "@budibase/types" import { FieldTypes } from "../../../../constants" import { foreignKeyStructure, generateForeignKey, generateJunctionTableName, } from "../../../../api/controllers/table/utils" import { buildExternalTableId } from "../../../../integrations/utils" import { cloneDeep } from "lodash/fp" export function cleanupRelationships( table: Table, tables: Record, oldTable?: Table ) { const tableToIterate = oldTable ? oldTable : table // clean up relationships in couch table schemas for (let [key, schema] of Object.entries(tableToIterate.schema)) { if ( schema.type === FieldTypes.LINK && (!oldTable || table.schema[key] == null) ) { const schemaTableId = schema.tableId const relatedTable = Object.values(tables).find( table => table._id === schemaTableId ) const foreignKey = schema.relationshipType !== RelationshipType.MANY_TO_MANY && schema.foreignKey if (!relatedTable || !foreignKey) { continue } for (let [relatedKey, relatedSchema] of Object.entries( relatedTable.schema )) { if ( relatedSchema.type === FieldTypes.LINK && relatedSchema.fieldName === foreignKey ) { delete relatedTable.schema[relatedKey] } } } } } export function otherRelationshipType(type: RelationshipType) { if (type === RelationshipType.MANY_TO_MANY) { return RelationshipType.MANY_TO_MANY } return type === RelationshipType.ONE_TO_MANY ? RelationshipType.MANY_TO_ONE : RelationshipType.ONE_TO_MANY } export function generateManyLinkSchema( datasource: Datasource, column: ManyToManyRelationshipFieldMetadata, table: Table, relatedTable: Table ): Table { if (!table.primary || !relatedTable.primary) { const noPrimaryName = !table.primary ? table.name : relatedTable.name throw new Error( `Unable to generate many link schema, "${noPrimaryName}" does not have a primary key` ) } const primary = table.name + table.primary[0] const relatedPrimary = relatedTable.name + relatedTable.primary[0] const jcTblName = generateJunctionTableName(column, table, relatedTable) const datasourceId = datasource._id! // first create the new table const junctionTable: Table = { type: "table", _id: buildExternalTableId(datasourceId, jcTblName), name: jcTblName, primary: [primary, relatedPrimary], constrained: [primary, relatedPrimary], sourceId: datasourceId, sourceType: TableSourceType.EXTERNAL, schema: { [primary]: foreignKeyStructure(primary, { toTable: table.name, toKey: table.primary[0], }), [relatedPrimary]: foreignKeyStructure(relatedPrimary, { toTable: relatedTable.name, toKey: relatedTable.primary[0], }), }, } column.through = junctionTable._id column.throughFrom = relatedPrimary column.throughTo = primary column.fieldName = relatedPrimary return junctionTable } export function generateLinkSchema( column: | OneToManyRelationshipFieldMetadata | ManyToOneRelationshipFieldMetadata, table: Table, relatedTable: Table, type: RelationshipType.ONE_TO_MANY | RelationshipType.MANY_TO_ONE ) { if (!table.primary || !relatedTable.primary) { throw new Error("Unable to generate link schema, no primary keys") } const isOneSide = type === RelationshipType.ONE_TO_MANY const primary = isOneSide ? relatedTable.primary[0] : table.primary[0] // generate a foreign key const foreignKey = generateForeignKey(column, relatedTable) column.relationshipType = type column.foreignKey = isOneSide ? foreignKey : primary column.fieldName = isOneSide ? primary : foreignKey return foreignKey } export function generateRelatedSchema( linkColumn: RelationshipFieldMetadata, table: Table, relatedTable: Table, columnName: string ) { // generate column for other table let relatedSchema: RelationshipFieldMetadata const isMany2Many = linkColumn.relationshipType === RelationshipType.MANY_TO_MANY // swap them from the main link if (!isMany2Many && linkColumn.foreignKey) { relatedSchema = cloneDeep(linkColumn) as | OneToManyRelationshipFieldMetadata | ManyToOneRelationshipFieldMetadata relatedSchema.fieldName = linkColumn.foreignKey relatedSchema.foreignKey = linkColumn.fieldName } // is many to many else { const manyToManyCol = linkColumn as ManyToManyRelationshipFieldMetadata relatedSchema = cloneDeep(linkColumn) as ManyToManyRelationshipFieldMetadata // don't need to copy through, already got it relatedSchema.fieldName = manyToManyCol.throughTo! relatedSchema.throughTo = manyToManyCol.throughFrom relatedSchema.throughFrom = manyToManyCol.throughTo } relatedSchema.relationshipType = otherRelationshipType( linkColumn.relationshipType ) relatedSchema.tableId = relatedTable._id! relatedSchema.name = columnName table.schema[columnName] = relatedSchema } export function isRelationshipSetup(column: RelationshipFieldMetadata) { return (column as any).foreignKey || (column as any).through }