budibase/packages/server/src/sdk/app/tables/external/utils.ts

167 lines
5.3 KiB
TypeScript

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<string, Table>,
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
}