From 8d7fe780288f7b6ce6914c102af883499803e425 Mon Sep 17 00:00:00 2001 From: Mel O'Hagan Date: Wed, 27 Jul 2022 11:40:46 +0100 Subject: [PATCH] Not Contains filter for MySQL --- packages/server/src/definitions/datasource.ts | 5 +- packages/server/src/integrations/base/sql.ts | 96 ++++++++++--------- .../server/src/integrations/tests/sql.spec.js | 45 +++++++++ packages/server/yarn.lock | 35 ++++--- packages/worker/yarn.lock | 35 ++++--- 5 files changed, 141 insertions(+), 75 deletions(-) diff --git a/packages/server/src/definitions/datasource.ts b/packages/server/src/definitions/datasource.ts index 9752fc947a..0033d35550 100644 --- a/packages/server/src/definitions/datasource.ts +++ b/packages/server/src/definitions/datasource.ts @@ -132,7 +132,10 @@ export interface SearchFilters { [key: string]: any[] } contains?: { - [key: string]: any + [key: string]: any[] + } + notContains?: { + [key: string]: any[] } } diff --git a/packages/server/src/integrations/base/sql.ts b/packages/server/src/integrations/base/sql.ts index ebbc2f0d8e..bbb84b49cb 100644 --- a/packages/server/src/integrations/base/sql.ts +++ b/packages/server/src/integrations/base/sql.ts @@ -157,6 +157,54 @@ class InternalBuilder { } } + const contains = (mode: object) => { + const fnc = allOr ? "orWhere" : "where" + const rawFnc = `${fnc}Raw` + const not = mode === filters?.notContains ? "NOT " : "" + function stringifyArray(value: Array): string { + for (let i in value) { + if (typeof value[i] === "string") { + value[i] = `"${value[i]}"` + } + } + return `'[${value.join(",")}]'` + } + if (this.client === SqlClients.POSTGRES) { + iterate(mode, (key: string, value: Array) => { + const fieldNames = key.split(/\./g) + const tableName = fieldNames[0] + const columnName = fieldNames[1] + // @ts-ignore + query = query[rawFnc]( + `"${tableName}"."${columnName}"::jsonb @> ${stringifyArray(value)}` + ) + }) + } else if (this.client === SqlClients.MY_SQL) { + iterate(mode, (key: string, value: Array) => { + // @ts-ignore + query = query[rawFnc]( + `${not}JSON_CONTAINS(${key}, ${stringifyArray(value)})` + ) + }) + } else { + iterate(mode, (key: string, value: Array) => { + let andStatement = "" + for (let i in value) { + if (typeof value[i] === "string") { + value[i] = `%"${value[i]}"%` + } else { + value[i] = `%${value[i]}%` + } + andStatement += + (andStatement ? " AND " : "") + + `LOWER(${likeKey(this.client, key)}) LIKE ?` + } + // @ts-ignore + query = query[rawFnc](andStatement, value) + }) + } + } + if (!filters) { return query } @@ -227,50 +275,10 @@ class InternalBuilder { }) } if (filters.contains) { - const fnc = allOr ? "orWhere" : "where" - const rawFnc = `${fnc}Raw` - function stringifyArray(value: Array): string { - for (let i in value) { - if (typeof value[i] === "string") { - value[i] = `"${value[i]}"` - } - } - return `'[${value.join(",")}]'` - } - if (this.client === SqlClients.POSTGRES) { - iterate(filters.contains, (key: string, value: Array) => { - const fieldNames = key.split(/\./g) - const tableName = fieldNames[0] - const columnName = fieldNames[1] - // @ts-ignore - query = query[rawFnc]( - `"${tableName}"."${columnName}"::jsonb @> ${stringifyArray(value)}` - ) - }) - } else if (this.client === SqlClients.MY_SQL) { - iterate(filters.contains, (key: string, value: Array) => { - // @ts-ignore - query = query[rawFnc]( - `JSON_CONTAINS(${key}, ${stringifyArray(value)})` - ) - }) - } else { - iterate(filters.contains, (key: string, value: Array) => { - let andStatement = "" - for (let i in value) { - if (typeof value[i] === "string") { - value[i] = `%"${value[i]}"%` - } else { - value[i] = `%${value[i]}%` - } - andStatement += - (andStatement ? " AND " : "") + - `LOWER(${likeKey(this.client, key)}) LIKE ?` - } - // @ts-ignore - query = query[rawFnc](andStatement, value) - }) - } + contains(filters.contains) + } + if (filters.notContains) { + contains(filters.notContains) } return query } diff --git a/packages/server/src/integrations/tests/sql.spec.js b/packages/server/src/integrations/tests/sql.spec.js index 47d553321f..865cf76e64 100644 --- a/packages/server/src/integrations/tests/sql.spec.js +++ b/packages/server/src/integrations/tests/sql.spec.js @@ -284,4 +284,49 @@ describe("SQL query builder", () => { sql: `select * from (select * from \"${TABLE_NAME}\" where \"${TABLE_NAME}\".\"age\"::jsonb @> '[20]' and \"${TABLE_NAME}\".\"name\"::jsonb @> '["John"]' limit $1) as \"${TABLE_NAME}\"` }) }) + + it("should use like expression for MS-SQL when filter is notContains", () => { + const query = new Sql(SqlClients.MS_SQL, 10)._query(generateReadJson({ + filters: { + notContains: { + age: [20], + name: ["John"] + } + } + })) + expect(query).toEqual({ + bindings: [10, "%20%", `%"John"%`], + sql: `select * from (select top (@p0) * from [${TABLE_NAME}] where LOWER(${TABLE_NAME}.age) LIKE @p1 and LOWER(${TABLE_NAME}.name) LIKE @p2) as [${TABLE_NAME}]` + }) + }) + + it("should use NOT JSON_CONTAINS expression for MySQL when filter is notContains", () => { + const query = new Sql(SqlClients.MY_SQL, 10)._query(generateReadJson({ + filters: { + notContains: { + age: [20], + name: ["John"] + } + } + })) + expect(query).toEqual({ + bindings: [10], + sql: `select * from (select * from \`${TABLE_NAME}\` where NOT JSON_CONTAINS(${TABLE_NAME}.age, '[20]') and NOT JSON_CONTAINS(${TABLE_NAME}.name, '["John"]') limit ?) as \`${TABLE_NAME}\`` + }) + }) + + it("should use jsonb operator expression for PostgreSQL when filter is notContains", () => { + const query = new Sql(SqlClients.POSTGRES, 10)._query(generateReadJson({ + filters: { + notContains: { + age: [20], + name: ["John"] + } + } + })) + expect(query).toEqual({ + bindings: [10], + sql: `select * from (select * from \"${TABLE_NAME}\" where \"${TABLE_NAME}\".\"age\"::jsonb @> '[20]' and \"${TABLE_NAME}\".\"name\"::jsonb @> '["John"]' limit $1) as \"${TABLE_NAME}\"` + }) + }) }) diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 6d285201db..58b9033823 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -1094,12 +1094,12 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@1.1.24": - version "1.1.24" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.1.24.tgz#35b15c4a2ff3eaed0a612ff04095017250a30e6d" - integrity sha512-Ab5Ju+2Cggfkz14+BasVgHSrxz73l6U6EtcF2Lb8EwkoLHYUT8llhW4hgrO6fXt5QaQ7mhsoiTUZesyn8Xy/Bg== +"@budibase/backend-core@1.1.25-alpha.1": + version "1.1.25-alpha.1" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.1.25-alpha.1.tgz#5a3953ddf84bf9db4c4943c86405a72835989ee9" + integrity sha512-C9D9QMXle99vfqXQdsUW/M7SjUpIsYP1ooKamy2+z/qBO3uBpBg6BkASpfNn0iMXZoz0DvQrmsOAuCoMxPvx2A== dependencies: - "@budibase/types" "^1.1.24" + "@budibase/types" "^1.1.25-alpha.1" "@techpass/passport-openidconnect" "0.3.2" aws-sdk "2.1030.0" bcrypt "5.0.1" @@ -1177,13 +1177,13 @@ svelte-flatpickr "^3.2.3" svelte-portal "^1.0.0" -"@budibase/pro@1.1.24": - version "1.1.24" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.1.24.tgz#4fd5d1b162308cb52d2cebbe9ede05b1f952b727" - integrity sha512-YGloe5IhBVRuGrGgRF1/qvmTICkxmwWAg7cxYPxBszI8IT7Ilisvsm2aBaDFfRYIe9bjzV2PzziiIkwA5qXlXw== +"@budibase/pro@1.1.25-alpha.1": + version "1.1.25-alpha.1" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.1.25-alpha.1.tgz#ae9527d193832e87f767c7b92c3460fa7cec4cec" + integrity sha512-ALyBFVdMhbat+HZB+DSRHcW2WG4POXdPAOcppreAEgAjUornSdhu9OV1yZOSe8G6DI+VycVP+DHpqVZdwbIJeQ== dependencies: - "@budibase/backend-core" "1.1.24" - "@budibase/types" "1.1.24" + "@budibase/backend-core" "1.1.25-alpha.1" + "@budibase/types" "1.1.25-alpha.1" node-fetch "^2.6.1" "@budibase/standard-components@^0.9.139": @@ -1204,10 +1204,15 @@ svelte-apexcharts "^1.0.2" svelte-flatpickr "^3.1.0" -"@budibase/types@1.1.24", "@budibase/types@^1.1.24": - version "1.1.24" - resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.1.24.tgz#87aaab20dc2093b88e036ccb5385ee8a39167cf6" - integrity sha512-ZlzDDBN3uWHCy80lyIZt3IBZB2AQBDgFt9G8b6r5S2rNlPZJQ5u1m58bIcan3NbDNLQOjATZ9opkejIB2qcZnw== +"@budibase/types@1.1.25-alpha.1": + version "1.1.25-alpha.1" + resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.1.25-alpha.1.tgz#cea7dae315fa5a1981090720068f51e5d72dd427" + integrity sha512-XyRdR03nUJYUJ7bEbthUovJFUsv7TGp39s4ch2O11FrZppRGWeT5anTKyTUoLbZ6Trtkx0iEFf9ubvt3Dk9ctw== + +"@budibase/types@^1.1.25-alpha.1": + version "1.1.25" + resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.1.25.tgz#4d52ac31368de37500a2ae8f8dc02a662d58d49a" + integrity sha512-K74BqAZiM+4URVvGPXhAVE3r+lLQoQ/LOFY30fAvAOv6WMJsw5r7NpF4m1l7bevPxZ6+ku1q/RnoI9aRGqdLlg== "@bull-board/api@3.7.0": version "3.7.0" diff --git a/packages/worker/yarn.lock b/packages/worker/yarn.lock index 41b6835785..06dd229e1e 100644 --- a/packages/worker/yarn.lock +++ b/packages/worker/yarn.lock @@ -291,12 +291,12 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@1.1.24": - version "1.1.24" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.1.24.tgz#35b15c4a2ff3eaed0a612ff04095017250a30e6d" - integrity sha512-Ab5Ju+2Cggfkz14+BasVgHSrxz73l6U6EtcF2Lb8EwkoLHYUT8llhW4hgrO6fXt5QaQ7mhsoiTUZesyn8Xy/Bg== +"@budibase/backend-core@1.1.25-alpha.1": + version "1.1.25-alpha.1" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.1.25-alpha.1.tgz#5a3953ddf84bf9db4c4943c86405a72835989ee9" + integrity sha512-C9D9QMXle99vfqXQdsUW/M7SjUpIsYP1ooKamy2+z/qBO3uBpBg6BkASpfNn0iMXZoz0DvQrmsOAuCoMxPvx2A== dependencies: - "@budibase/types" "^1.1.24" + "@budibase/types" "^1.1.25-alpha.1" "@techpass/passport-openidconnect" "0.3.2" aws-sdk "2.1030.0" bcrypt "5.0.1" @@ -324,19 +324,24 @@ uuid "8.3.2" zlib "1.0.5" -"@budibase/pro@1.1.24": - version "1.1.24" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.1.24.tgz#4fd5d1b162308cb52d2cebbe9ede05b1f952b727" - integrity sha512-YGloe5IhBVRuGrGgRF1/qvmTICkxmwWAg7cxYPxBszI8IT7Ilisvsm2aBaDFfRYIe9bjzV2PzziiIkwA5qXlXw== +"@budibase/pro@1.1.25-alpha.1": + version "1.1.25-alpha.1" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.1.25-alpha.1.tgz#ae9527d193832e87f767c7b92c3460fa7cec4cec" + integrity sha512-ALyBFVdMhbat+HZB+DSRHcW2WG4POXdPAOcppreAEgAjUornSdhu9OV1yZOSe8G6DI+VycVP+DHpqVZdwbIJeQ== dependencies: - "@budibase/backend-core" "1.1.24" - "@budibase/types" "1.1.24" + "@budibase/backend-core" "1.1.25-alpha.1" + "@budibase/types" "1.1.25-alpha.1" node-fetch "^2.6.1" -"@budibase/types@1.1.24", "@budibase/types@^1.1.24": - version "1.1.24" - resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.1.24.tgz#87aaab20dc2093b88e036ccb5385ee8a39167cf6" - integrity sha512-ZlzDDBN3uWHCy80lyIZt3IBZB2AQBDgFt9G8b6r5S2rNlPZJQ5u1m58bIcan3NbDNLQOjATZ9opkejIB2qcZnw== +"@budibase/types@1.1.25-alpha.1": + version "1.1.25-alpha.1" + resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.1.25-alpha.1.tgz#cea7dae315fa5a1981090720068f51e5d72dd427" + integrity sha512-XyRdR03nUJYUJ7bEbthUovJFUsv7TGp39s4ch2O11FrZppRGWeT5anTKyTUoLbZ6Trtkx0iEFf9ubvt3Dk9ctw== + +"@budibase/types@^1.1.25-alpha.1": + version "1.1.25" + resolved "https://registry.yarnpkg.com/@budibase/types/-/types-1.1.25.tgz#4d52ac31368de37500a2ae8f8dc02a662d58d49a" + integrity sha512-K74BqAZiM+4URVvGPXhAVE3r+lLQoQ/LOFY30fAvAOv6WMJsw5r7NpF4m1l7bevPxZ6+ku1q/RnoI9aRGqdLlg== "@cspotcode/source-map-consumer@0.8.0": version "0.8.0"