Handle composite keys on exports

This commit is contained in:
Adria Navarro 2024-07-09 10:43:45 +02:00
parent 85827bbf93
commit 5be8882122
4 changed files with 35 additions and 27 deletions

View File

@ -172,6 +172,8 @@ function convertBooleans(query: SqlQuery | SqlQuery[]): SqlQuery | SqlQuery[] {
return query return query
} }
const COMPLEX_ID_OPERATOR = "_complexIdOperator"
class InternalBuilder { class InternalBuilder {
private readonly client: string private readonly client: string
@ -214,7 +216,14 @@ class InternalBuilder {
for (let [key, value] of Object.entries(structure)) { for (let [key, value] of Object.entries(structure)) {
const updatedKey = dbCore.removeKeyNumbering(key) const updatedKey = dbCore.removeKeyNumbering(key)
const isRelationshipField = updatedKey.includes(".") 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) const alias = getTableAlias(tableName)
fn(alias ? `${alias}.${updatedKey}` : updatedKey, value) fn(alias ? `${alias}.${updatedKey}` : updatedKey, value)
} else if (opts.relationship && isRelationshipField) { } else if (opts.relationship && isRelationshipField) {
@ -745,6 +754,9 @@ class InternalBuilder {
class SqlQueryBuilder extends SqlTableQueryBuilder { class SqlQueryBuilder extends SqlTableQueryBuilder {
private readonly limit: number private readonly limit: number
public static COMPLEX_ID_OPERATOR = COMPLEX_ID_OPERATOR
// pass through client to get flavour of SQL // pass through client to get flavour of SQL
constructor(client: string, limit: number = BASE_LIMIT) { constructor(client: string, limit: number = BASE_LIMIT) {
super(client) super(client)

View File

@ -40,7 +40,7 @@ import {
} from "../../../sdk/app/rows/utils" } from "../../../sdk/app/rows/utils"
import { processObjectSync } from "@budibase/string-templates" import { processObjectSync } from "@budibase/string-templates"
import { cloneDeep } from "lodash/fp" 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 sdk from "../../../sdk"
import env from "../../../environment" import env from "../../../environment"
import { makeExternalQuery } from "../../../integrations/base/query" import { makeExternalQuery } from "../../../integrations/base/query"
@ -193,11 +193,26 @@ export class ExternalRequest<T extends Operation> {
for (let field of Object.keys(operator || {})) { for (let field of Object.keys(operator || {})) {
if (dbCore.removeKeyNumbering(field) === "_id") { if (dbCore.removeKeyNumbering(field) === "_id") {
if (primary) { if (primary) {
const parts = breakRowIdField(operator[field]) let idField = operator[field]
for (let field of primary) { try {
operator[`${prefix}:${field}`] = parts.shift() // 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 // make sure this field doesn't exist on any filter
delete operator[field] delete operator[field]

View File

@ -1428,22 +1428,6 @@ describe.each([
expect(row._id).toEqual(existing._id) 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 () => { it("should return an error if no table is found", async () => {
const existing = await config.api.row.save(table._id!, {}) const existing = await config.api.row.save(table._id!, {})
await config.api.row.exportRows( 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({ const tableRequest = saveTableRequest({
primary: ["number", "string"], primary: ["number", "string"],
schema: { schema: {

View File

@ -158,10 +158,7 @@ export async function exportRows(
_id: rowIds.map((row: string) => { _id: rowIds.map((row: string) => {
const ids = breakRowIdField(row) const ids = breakRowIdField(row)
if (ids.length > 1) { if (ids.length > 1) {
throw new HTTPError( return ids
"Export data does not support composite keys.",
400
)
} }
return ids[0] return ids[0]
}), }),