From c0b85c63797d7bacb59f75f458eef5f3db090871 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 8 Jul 2024 18:42:11 +0100 Subject: [PATCH] Initial implementation - needs testing. --- .../src/api/controllers/row/utils/sqlUtils.ts | 16 +++++- .../server/src/sdk/app/rows/search/sqs.ts | 55 ++++++++++++++----- packages/server/src/sdk/app/rows/sqlAlias.ts | 3 +- 3 files changed, 59 insertions(+), 15 deletions(-) diff --git a/packages/server/src/api/controllers/row/utils/sqlUtils.ts b/packages/server/src/api/controllers/row/utils/sqlUtils.ts index 767916616c..76cf4d01ff 100644 --- a/packages/server/src/api/controllers/row/utils/sqlUtils.ts +++ b/packages/server/src/api/controllers/row/utils/sqlUtils.ts @@ -22,6 +22,18 @@ export function isManyToMany( return !!(field as ManyToManyRelationshipFieldMetadata).through } +function isCorrectRelationship( + relationship: RelationshipsJson, + row: Row +): boolean { + const junctionTableId = relationship.through! + const possibleColumns = [ + `${junctionTableId}.doc1.fieldName`, + `${junctionTableId}.doc2.fieldName`, + ] + return !!possibleColumns.find(col => row[col] === relationship.column) +} + /** * This iterates through the returned rows and works out what elements of the rows * actually match up to another row (based on primary keys) - this is pretty specific @@ -64,7 +76,9 @@ export async function updateRelationshipColumns( if (!linked._id) { continue } - columns[relationship.column] = linked + if (opts?.sqs && isCorrectRelationship(relationship, row)) { + columns[relationship.column] = linked + } } for (let [column, related] of Object.entries(columns)) { if (!row._id) { diff --git a/packages/server/src/sdk/app/rows/search/sqs.ts b/packages/server/src/sdk/app/rows/search/sqs.ts index 72a1557cc9..b7af8705a2 100644 --- a/packages/server/src/sdk/app/rows/search/sqs.ts +++ b/packages/server/src/sdk/app/rows/search/sqs.ts @@ -5,6 +5,7 @@ import { Operation, QueryJson, RelationshipFieldMetadata, + RelationshipsJson, Row, RowSearchParams, SearchFilters, @@ -30,7 +31,10 @@ import { SQLITE_DESIGN_DOC_ID, SQS_DATASOURCE_INTERNAL, } from "@budibase/backend-core" -import { CONSTANT_INTERNAL_ROW_COLS } from "../../../../db/utils" +import { + CONSTANT_INTERNAL_ROW_COLS, + generateJunctionTableID, +} from "../../../../db/utils" import AliasTables from "../sqlAlias" import { outputProcessing } from "../../../../utilities/rowProcessor" import pick from "lodash/pick" @@ -52,7 +56,7 @@ const USER_COLUMN_PREFIX_REGEX = new RegExp( function buildInternalFieldList( table: Table, tables: Table[], - opts: { relationships: boolean } = { relationships: true } + opts?: { relationships?: RelationshipsJson[] } ) { let fieldList: string[] = [] fieldList = fieldList.concat( @@ -60,20 +64,31 @@ function buildInternalFieldList( ) for (let col of Object.values(table.schema)) { const isRelationship = col.type === FieldType.LINK - if (!opts.relationships && isRelationship) { + if (!opts?.relationships && isRelationship) { continue } if (isRelationship) { const linkCol = col as RelationshipFieldMetadata const relatedTable = tables.find(table => table._id === linkCol.tableId)! - fieldList = fieldList.concat( - buildInternalFieldList(relatedTable, tables, { relationships: false }) + // no relationships provided, don't go more than a layer deep + fieldList = fieldList.concat(buildInternalFieldList(relatedTable, tables)) + fieldList.push( + `${generateJunctionTableID( + table._id!, + relatedTable._id! + )}.doc1.fieldName` + ) + fieldList.push( + `${generateJunctionTableID( + table._id!, + relatedTable._id! + )}.doc2.fieldName` ) } else { fieldList.push(`${table._id}.${mapToUserColumn(col.name)}`) } } - return fieldList + return [...new Set(fieldList)] } function cleanupFilters( @@ -165,18 +180,27 @@ function reverseUserColumnMapping(rows: Row[]) { }) } -function runSqlQuery(json: QueryJson, tables: Table[]): Promise function runSqlQuery( json: QueryJson, tables: Table[], + relationships: RelationshipsJson[] +): Promise +function runSqlQuery( + json: QueryJson, + tables: Table[], + relationships: RelationshipsJson[], opts: { countTotalRows: true } ): Promise async function runSqlQuery( json: QueryJson, tables: Table[], + relationships: RelationshipsJson[], opts?: { countTotalRows?: boolean } ) { - const alias = new AliasTables(tables.map(table => table.name)) + const relationshipJunctionTableIds = relationships.map(rel => rel.through!) + const alias = new AliasTables( + tables.map(table => table.name).concat(relationshipJunctionTableIds) + ) if (opts?.countTotalRows) { json.endpoint.operation = Operation.COUNT } @@ -193,8 +217,13 @@ async function runSqlQuery( let bindings = query.bindings // quick hack for docIds - sql = sql.replace(/`doc1`.`rowId`/g, "`doc1.rowId`") - sql = sql.replace(/`doc2`.`rowId`/g, "`doc2.rowId`") + + const fixJunctionDocs = (field: string) => + ["doc1", "doc2"].forEach(doc => { + sql = sql.replaceAll(`\`${doc}\`.\`${field}\``, `\`${doc}.${field}\``) + }) + fixJunctionDocs("rowId") + fixJunctionDocs("fieldName") if (Array.isArray(query)) { throw new Error("SQS cannot currently handle multiple queries") @@ -260,7 +289,7 @@ export async function search( columnPrefix: USER_COLUMN_PREFIX, }, resource: { - fields: buildInternalFieldList(table, allTables), + fields: buildInternalFieldList(table, allTables, { relationships }), }, relationships, } @@ -292,11 +321,11 @@ export async function search( try { const queries: Promise[] = [] - queries.push(runSqlQuery(request, allTables)) + queries.push(runSqlQuery(request, allTables, relationships)) if (options.countRows) { // get the total count of rows queries.push( - runSqlQuery(request, allTables, { + runSqlQuery(request, allTables, relationships, { countTotalRows: true, }) ) diff --git a/packages/server/src/sdk/app/rows/sqlAlias.ts b/packages/server/src/sdk/app/rows/sqlAlias.ts index bc8fc56d5e..664e64057b 100644 --- a/packages/server/src/sdk/app/rows/sqlAlias.ts +++ b/packages/server/src/sdk/app/rows/sqlAlias.ts @@ -111,7 +111,8 @@ export default class AliasTables { aliasField(field: string) { const tableNames = this.tableNames if (field.includes(".")) { - const [tableName, column] = field.split(".") + const [tableName, ...rest] = field.split(".") + const column = rest.join(".") const foundTableName = tableNames.find(name => { const idx = tableName.indexOf(name) if (idx === -1 || idx > 1) {