From 69a80f27000d856618a5cf248f9c234677f8cd1d Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 21 Mar 2025 16:34:04 +0000 Subject: [PATCH 1/4] Fixing issue with JSON columns in MS-SQL. --- packages/backend-core/src/sql/sql.ts | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/backend-core/src/sql/sql.ts b/packages/backend-core/src/sql/sql.ts index 7791ecb28b..fee0bdb0c0 100644 --- a/packages/backend-core/src/sql/sql.ts +++ b/packages/backend-core/src/sql/sql.ts @@ -116,6 +116,15 @@ function stringifyArray(value: any[], quoteStyle = '"'): string { return `[${value.join(",")}]` } +function isJsonColumn( + field: FieldSchema +): field is JsonFieldMetadata | BBReferenceFieldMetadata { + return ( + JsonTypes.includes(field.type) && + !helpers.schema.isDeprecatedSingleUserColumn(field) + ) +} + const allowEmptyRelationships: Record = { [BasicOperator.EQUAL]: false, [BasicOperator.NOT_EQUAL]: true, @@ -372,6 +381,11 @@ class InternalBuilder { return null } + // MS-SQL doesn't allow an object to be passed in + if (this.client === SqlClient.MS_SQL && isJsonColumn(schema)) { + return JSON.stringify(input) + } + if ( this.client === SqlClient.ORACLE && schema.type === FieldType.DATETIME && @@ -1869,7 +1883,7 @@ class SqlQueryBuilder extends SqlTableQueryBuilder { ): T[] { const tableName = this.getTableName(table, aliases) for (const [name, field] of Object.entries(table.schema)) { - if (!this._isJsonColumn(field)) { + if (!isJsonColumn(field)) { continue } const fullName = `${tableName}.${name}` as keyof T @@ -1885,15 +1899,6 @@ class SqlQueryBuilder extends SqlTableQueryBuilder { return results } - _isJsonColumn( - field: FieldSchema - ): field is JsonFieldMetadata | BBReferenceFieldMetadata { - return ( - JsonTypes.includes(field.type) && - !helpers.schema.isDeprecatedSingleUserColumn(field) - ) - } - log(query: string, values?: SqlQueryBinding) { sqlLog(this.getSqlClient(), query, values) } From 7ca206717a05a1919a4a8daa3815780eed9149cc Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 21 Mar 2025 17:03:59 +0000 Subject: [PATCH 2/4] Adding test case for updating an attachment. --- .../server/src/api/routes/tests/row.spec.ts | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index e910e5ab55..cf24430f82 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -2196,6 +2196,13 @@ if (descriptions.length) { }) describe("attachments and signatures", () => { + function generateAttachment(value: string) { + return { + key: `${config.getAppId()}/attachments/${value}`, + } + } + const newCsv = () => `${uuid.v4()}.csv` + const coreAttachmentEnrichment = async ( schema: TableSchema, field: string, @@ -2206,18 +2213,13 @@ if (descriptions.length) { schema, }) ) - const attachmentToStoreKey = (attachmentId: string) => { - return { - key: `${config.getAppId()}/attachments/${attachmentId}`, - } - } const draftRow = { name: "test", description: "test", [field]: typeof attachmentCfg === "string" - ? attachmentToStoreKey(attachmentCfg) - : attachmentCfg.map(attachmentToStoreKey), + ? generateAttachment(attachmentCfg) + : attachmentCfg.map(generateAttachment), tableId: testTable._id, } const row = await config.api.row.save(testTable._id!, draftRow) @@ -2238,6 +2240,7 @@ if (descriptions.length) { } }) }) + return { row, table: testTable } } it("should allow enriching single attachment rows", async () => { @@ -2250,10 +2253,29 @@ if (descriptions.length) { }, }, "attachment", - `${uuid.v4()}.csv` + newCsv() ) }) + it("should allow updating single attachment row", async () => { + const { row, table } = await coreAttachmentEnrichment( + { + attachment: { + type: FieldType.ATTACHMENT_SINGLE, + name: "attachment", + constraints: { presence: false }, + }, + }, + "attachment", + newCsv() + ) + + const newAttachment = generateAttachment(newCsv()) + row["attachment"] = newAttachment + const updated = await config.api.row.save(table._id!, row) + expect(updated.attachment.key).toBe(newAttachment.key) + }) + it("should allow enriching attachment list rows", async () => { await coreAttachmentEnrichment( { @@ -2264,7 +2286,7 @@ if (descriptions.length) { }, }, "attachments", - [`${uuid.v4()}.csv`] + [newCsv()] ) }) From 17a80d044f53b644661c1d9176315b089375a0a0 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 21 Mar 2025 17:35:52 +0000 Subject: [PATCH 3/4] Handling for MariaDB, MySQL and Oracle. --- packages/backend-core/src/sql/sql.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/backend-core/src/sql/sql.ts b/packages/backend-core/src/sql/sql.ts index fee0bdb0c0..bb4348254b 100644 --- a/packages/backend-core/src/sql/sql.ts +++ b/packages/backend-core/src/sql/sql.ts @@ -188,6 +188,16 @@ class InternalBuilder { return this.table.schema[column] } + private requiresJsonAsStringClient(): boolean { + const requiresJsonAsString = [ + SqlClient.MS_SQL, + SqlClient.MY_SQL, + SqlClient.MARIADB, + SqlClient.ORACLE, + ] + return requiresJsonAsString.includes(this.client) + } + private quoteChars(): [string, string] { const wrapped = this.knexClient.wrapIdentifier("foo", {}) return [wrapped[0], wrapped[wrapped.length - 1]] @@ -382,7 +392,7 @@ class InternalBuilder { } // MS-SQL doesn't allow an object to be passed in - if (this.client === SqlClient.MS_SQL && isJsonColumn(schema)) { + if (this.requiresJsonAsStringClient() && isJsonColumn(schema)) { return JSON.stringify(input) } From e5ffb074bca9d59368bc718209809c80948fdefe Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 21 Mar 2025 20:08:04 +0000 Subject: [PATCH 4/4] Only convert to a string if it is an object. --- packages/backend-core/src/sql/sql.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/backend-core/src/sql/sql.ts b/packages/backend-core/src/sql/sql.ts index bb4348254b..937f88c846 100644 --- a/packages/backend-core/src/sql/sql.ts +++ b/packages/backend-core/src/sql/sql.ts @@ -391,8 +391,12 @@ class InternalBuilder { return null } - // MS-SQL doesn't allow an object to be passed in - if (this.requiresJsonAsStringClient() && isJsonColumn(schema)) { + // some database don't allow an object to be passed in + if ( + this.requiresJsonAsStringClient() && + isJsonColumn(schema) && + typeof input === "object" + ) { return JSON.stringify(input) }