1
0
Fork 0
mirror of synced 2024-09-08 13:41:09 +12:00

Handling deletion of rows that violate constraints, this has been an issue in Budibase for some time and causes some confusion, attempting to resolve this when deleting rows.

This commit is contained in:
mike12345567 2024-02-05 18:57:16 +00:00
parent e8e7eea47a
commit 9a8c31a2a4

View file

@ -7,6 +7,7 @@ import {
FilterType,
IncludeRelationship,
ManyToManyRelationshipFieldMetadata,
ManyToOneRelationshipFieldMetadata,
OneToManyRelationshipFieldMetadata,
Operation,
PaginationJson,
@ -102,6 +103,26 @@ function buildFilters(
}
}
function removeRelationships(
rowId: string,
table: Table,
isManyToMany: boolean,
colName?: string
) {
const tableId = table._id!
const filters = buildFilters(rowId, {}, table)
// safety check, if there are no filters on deletion bad things happen
if (Object.keys(filters).length !== 0) {
const op = isManyToMany ? Operation.DELETE : Operation.UPDATE
const body = colName && !isManyToMany ? { [colName]: null } : undefined
return getDatasourceAndQuery({
endpoint: getEndpoint(tableId, op),
body,
filters,
})
}
}
/**
* This function checks the incoming parameters to make sure all the inputs are
* valid based on on the table schema. The main thing this is looking for is when a
@ -305,6 +326,18 @@ export class ExternalRequest<T extends Operation> {
}
}
async getRow(table: Table, rowId: string): Promise<Row> {
const response = await getDatasourceAndQuery({
endpoint: getEndpoint(table._id!, Operation.READ),
filters: buildFilters(rowId, {}, table),
})
if (response.length > 0) {
return response[0]
} else {
throw new Error(`Cannot fetch row by ID "${rowId}"`)
}
}
inputProcessing(row: Row | undefined, table: Table) {
if (!row) {
return { row, manyRelationships: [] }
@ -572,7 +605,9 @@ export class ExternalRequest<T extends Operation> {
* information.
*/
async lookupRelations(tableId: string, row: Row) {
const related: { [key: string]: any } = {}
const related: {
[key: string]: { rows: Row[]; isMany: boolean; tableId: string }
} = {}
const { tableName } = breakExternalTableId(tableId)
if (!tableName) {
return related
@ -591,7 +626,7 @@ export class ExternalRequest<T extends Operation> {
continue
}
const isMany = field.relationshipType === RelationshipType.MANY_TO_MANY
const tableId = isMany ? field.through : field.tableId
const tableId = isMany ? field.through! : field.tableId!
const { tableName: relatedTableName } = breakExternalTableId(tableId)
// @ts-ignore
const linkPrimaryKey = this.tables[relatedTableName].primary[0]
@ -610,7 +645,7 @@ export class ExternalRequest<T extends Operation> {
},
})
// this is the response from knex if no rows found
const rows = !response[0].read ? response : []
const rows: Row[] = !response[0].read ? response : []
const storeTo = isMany ? field.throughFrom || linkPrimaryKey : fieldName
related[storeTo] = { rows, isMany, tableId }
}
@ -698,24 +733,46 @@ export class ExternalRequest<T extends Operation> {
continue
}
for (let row of rows) {
const filters = buildFilters(generateIdForRow(row, table), {}, table)
// safety check, if there are no filters on deletion bad things happen
if (Object.keys(filters).length !== 0) {
const op = isMany ? Operation.DELETE : Operation.UPDATE
const body = isMany ? undefined : { [colName]: null }
promises.push(
getDatasourceAndQuery({
endpoint: getEndpoint(tableId, op),
body,
filters,
})
)
const promise = removeRelationships(
generateIdForRow(row, table),
table,
isMany,
colName
)
if (promise) {
promises.push(promise)
}
}
}
await Promise.all(promises)
}
async removeRelationshipsToRow(table: Table, rowId: string) {
const row = await this.getRow(table, rowId)
const related = await this.lookupRelations(table._id!, row)
for (let column of Object.values(table.schema)) {
if (
column.type !== FieldType.LINK ||
column.relationshipType === RelationshipType.ONE_TO_MANY
) {
continue
}
const relationshipColumn = column as ManyToOneRelationshipFieldMetadata
const { rows, isMany, tableId } = related[relationshipColumn.fieldName]
const table = this.getTable(tableId)!
await Promise.all(
rows.map(row =>
removeRelationships(
generateIdForRow(row, table),
table,
isMany,
relationshipColumn.fieldName
)
)
)
}
}
/**
* This function is a bit crazy, but the exact purpose of it is to protect against the scenario in which
* you have column overlap in relationships, e.g. we join a few different tables and they all have the
@ -828,6 +885,10 @@ export class ExternalRequest<T extends Operation> {
}
const aliasing = new AliasTables(Object.keys(this.tables))
// remove any relationships that could block deletion
if (operation === Operation.DELETE && id) {
await this.removeRelationshipsToRow(table, generateRowIdField(id))
}
const response = await aliasing.queryWithAliasing(json)
// handle many-to-many relationships now if we know the ID (could be auto increment)
if (operation !== Operation.READ) {