From 5be8882122fec80594a80bc1c63217c86e34a6a2 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 9 Jul 2024 10:43:45 +0200 Subject: [PATCH] Handle composite keys on exports --- packages/backend-core/src/sql/sql.ts | 14 ++++++++++- .../api/controllers/row/ExternalRequest.ts | 25 +++++++++++++++---- .../server/src/api/routes/tests/row.spec.ts | 18 +------------ .../src/sdk/app/rows/search/external.ts | 5 +--- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/packages/backend-core/src/sql/sql.ts b/packages/backend-core/src/sql/sql.ts index c17cbae86f..7410ca5dbf 100644 --- a/packages/backend-core/src/sql/sql.ts +++ b/packages/backend-core/src/sql/sql.ts @@ -172,6 +172,8 @@ function convertBooleans(query: SqlQuery | SqlQuery[]): SqlQuery | SqlQuery[] { return query } +const COMPLEX_ID_OPERATOR = "_complexIdOperator" + class InternalBuilder { private readonly client: string @@ -214,7 +216,14 @@ class InternalBuilder { for (let [key, value] of Object.entries(structure)) { const updatedKey = dbCore.removeKeyNumbering(key) const isRelationshipField = updatedKey.includes(".") - if (!opts.relationship && !isRelationshipField) { + + if (updatedKey === COMPLEX_ID_OPERATOR) { + const alias = getTableAlias(tableName) + fn( + value.id.map((x: string) => (alias ? `${alias}.${x}` : x)), + value.values + ) + } else if (!opts.relationship && !isRelationshipField) { const alias = getTableAlias(tableName) fn(alias ? `${alias}.${updatedKey}` : updatedKey, value) } else if (opts.relationship && isRelationshipField) { @@ -745,6 +754,9 @@ class InternalBuilder { class SqlQueryBuilder extends SqlTableQueryBuilder { private readonly limit: number + + public static COMPLEX_ID_OPERATOR = COMPLEX_ID_OPERATOR + // pass through client to get flavour of SQL constructor(client: string, limit: number = BASE_LIMIT) { super(client) diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts index b51de46e99..a50297f7b0 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -40,7 +40,7 @@ import { } from "../../../sdk/app/rows/utils" import { processObjectSync } from "@budibase/string-templates" import { cloneDeep } from "lodash/fp" -import { db as dbCore } from "@budibase/backend-core" +import { db as dbCore, sql } from "@budibase/backend-core" import sdk from "../../../sdk" import env from "../../../environment" import { makeExternalQuery } from "../../../integrations/base/query" @@ -193,11 +193,26 @@ export class ExternalRequest { for (let field of Object.keys(operator || {})) { if (dbCore.removeKeyNumbering(field) === "_id") { if (primary) { - const parts = breakRowIdField(operator[field]) - for (let field of primary) { - operator[`${prefix}:${field}`] = parts.shift() + let idField = operator[field] + try { + // Make sure _id queries decode the Row IDs + idField = JSON.parse(idField) + } catch { + // It is not a JSON value + } + + const parts = breakRowIdField(idField) + if (primary.length > 1) { + operator[sql.Sql.COMPLEX_ID_OPERATOR] = { + id: primary, + values: parts, + } + } else { + for (let field of primary) { + operator[`${prefix}:${field}`] = parts.shift() + } + prefix++ } - prefix++ } // make sure this field doesn't exist on any filter delete operator[field] diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index d92438a310..9628757d16 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -1428,22 +1428,6 @@ describe.each([ expect(row._id).toEqual(existing._id) }) - it("should return an error on composite keys", async () => { - const existing = await config.api.row.save(table._id!, {}) - await config.api.row.exportRows( - table._id!, - { - rows: [`['${existing._id!}']`, "['d001', '10111']"], - }, - { - status: 400, - body: { - message: "Export data does not support composite keys.", - }, - } - ) - }) - it("should return an error if no table is found", async () => { const existing = await config.api.row.save(table._id!, {}) await config.api.row.exportRows( @@ -1453,7 +1437,7 @@ describe.each([ ) }) - it("can export rows with composite primary keys", async () => { + it("should handle filtering by composite primary keys", async () => { const tableRequest = saveTableRequest({ primary: ["number", "string"], schema: { diff --git a/packages/server/src/sdk/app/rows/search/external.ts b/packages/server/src/sdk/app/rows/search/external.ts index 93c46d8cc3..84306f572f 100644 --- a/packages/server/src/sdk/app/rows/search/external.ts +++ b/packages/server/src/sdk/app/rows/search/external.ts @@ -158,10 +158,7 @@ export async function exportRows( _id: rowIds.map((row: string) => { const ids = breakRowIdField(row) if (ids.length > 1) { - throw new HTTPError( - "Export data does not support composite keys.", - 400 - ) + return ids } return ids[0] }),