From c22356fb4d3378a2225dc6e1ab491401a3ed2f5a Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 5 Nov 2021 18:55:36 +0000 Subject: [PATCH] Fixing an issue with relationship modal breaking when multiple data sources available to relate to, also fixing an pile of issues with creating and reading rows from SQL server plus. --- .../DataTable/modals/CreateEditColumn.svelte | 13 ++- packages/server/src/definitions/datasource.ts | 2 +- packages/server/src/integrations/base/sql.ts | 83 +++++++++++++++++++ .../src/integrations/microsoftSqlServer.ts | 35 +++++--- packages/server/src/integrations/mysql.ts | 61 +------------- packages/server/src/integrations/utils.ts | 2 +- 6 files changed, 121 insertions(+), 75 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index ebfea9cee6..6ee0c48d2a 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -59,9 +59,6 @@ let deletion $: checkConstraints(field) - $: tableOptions = $tables.list.filter( - opt => opt._id !== $tables.draft._id && opt.type === table.type - ) $: required = !!field?.constraints?.presence || primaryDisplay $: uneditable = $tables.selected?._id === TableNames.USERS && @@ -88,6 +85,14 @@ field.type !== LINK_TYPE && !uneditable && field.type !== AUTO_TYPE $: relationshipOptions = getRelationshipOptions(field) $: external = table.type === "external" + // in the case of internal tables the sourceId will just be undefined + $: tableOptions = $tables.list.filter( + opt => + opt._id !== $tables.draft._id && + opt.type === table.type && + table.sourceId === opt.sourceId + ) + $: console.log(tableOptions) async function saveColumn() { if (field.type === AUTO_TYPE) { @@ -174,7 +179,7 @@ if (!field || !field.tableId) { return null } - const linkTable = tableOptions.find(table => table._id === field.tableId) + const linkTable = tableOptions?.find(table => table._id === field.tableId) if (!linkTable) { return null } diff --git a/packages/server/src/definitions/datasource.ts b/packages/server/src/definitions/datasource.ts index a82e50b140..6e6cb02f4f 100644 --- a/packages/server/src/definitions/datasource.ts +++ b/packages/server/src/definitions/datasource.ts @@ -119,7 +119,7 @@ export interface SortJson { export interface PaginationJson { limit: number - page: string | number + page?: string | number } export interface RelationshipsJson { diff --git a/packages/server/src/integrations/base/sql.ts b/packages/server/src/integrations/base/sql.ts index 06a9d0aa10..d72f87958b 100644 --- a/packages/server/src/integrations/base/sql.ts +++ b/packages/server/src/integrations/base/sql.ts @@ -216,6 +216,10 @@ class InternalBuilder { query = query.orderBy(key, direction) } } + if (this.client === "mssql" && !sort && paginate?.limit) { + // @ts-ignore + query = query.orderBy(json.meta?.table?.primary[0]) + } query = this.addFilters(tableName, query, filters) // @ts-ignore let preQuery: KnexQuery = knex({ @@ -301,6 +305,85 @@ class SqlQueryBuilder extends SqlTableQueryBuilder { // @ts-ignore return query.toSQL().toNative() } + + async getReturningRow(queryFn: Function, json: QueryJson) { + if (!json.extra || !json.extra.idFilter) { + return {} + } + const input = this._query({ + endpoint: { + ...json.endpoint, + operation: Operation.READ, + }, + resource: { + fields: [], + }, + filters: json.extra.idFilter, + paginate: { + limit: 1, + }, + meta: json.meta, + }) + return queryFn(input, Operation.READ) + } + + // when creating if an ID has been inserted need to make sure + // the id filter is enriched with it before trying to retrieve the row + checkLookupKeys(id: any, json: QueryJson) { + if (!id || !json.meta?.table || !json.meta.table.primary) { + return json + } + const primaryKey = json.meta.table.primary?.[0] + json.extra = { + idFilter: { + equal: { + [primaryKey]: id, + }, + }, + } + return json + } + + // this function recreates the returning functionality of postgres + async queryWithReturning( + json: QueryJson, + queryFn: Function, + processFn: Function = (result: any) => result + ) { + const sqlClient = this.getSqlClient() + const operation = this._operation(json) + const input = this._query(json, { disableReturning: true }) + if (Array.isArray(input)) { + const responses = [] + for (let query of input) { + responses.push(await queryFn(query, operation)) + } + return responses + } + let row + // need to manage returning, a feature mySQL can't do + if (operation === Operation.DELETE) { + row = processFn(await this.getReturningRow(queryFn, json)) + } + const response = await queryFn(input, operation) + const results = processFn(response) + // same as delete, manage returning + if (operation === Operation.CREATE || operation === Operation.UPDATE) { + let id + if (sqlClient === "mssql") { + id = results?.[0].id + } else if (sqlClient === "mysql") { + id = results?.insertId + } + row = processFn( + await this.getReturningRow(queryFn, this.checkLookupKeys(id, json)) + ) + } + if (operation !== Operation.READ) { + return row + } + return results.length ? results : [{ [operation.toLowerCase()]: true }] + } } module.exports = SqlQueryBuilder diff --git a/packages/server/src/integrations/microsoftSqlServer.ts b/packages/server/src/integrations/microsoftSqlServer.ts index bcb22f50a9..1ee64759ef 100644 --- a/packages/server/src/integrations/microsoftSqlServer.ts +++ b/packages/server/src/integrations/microsoftSqlServer.ts @@ -1,8 +1,9 @@ import { - Integration, DatasourceFieldTypes, - QueryTypes, + Integration, + Operation, QueryJson, + QueryTypes, SqlQuery, } from "../definitions/datasource" import { getSqlQuery } from "./utils" @@ -103,15 +104,26 @@ module MSSQLModule { json: DatasourceFieldTypes.JSON, } - async function internalQuery(client: any, query: SqlQuery) { + async function internalQuery( + client: any, + query: SqlQuery, + operation: string | undefined = undefined + ) { + const request = client.request() try { if (Array.isArray(query.bindings)) { let count = 0 for (let binding of query.bindings) { - client.input(`p${count++}`, binding) + request.input(`p${count++}`, binding) } } - return await client.query(query.sql) + // this is a hack to get the inserted ID back, + // no way to do this with Knex nicely + const sql = + operation === Operation.CREATE + ? `${query.sql}; SELECT SCOPE_IDENTITY() AS id;` + : query.sql + return await request.query(sql) } catch (err) { // @ts-ignore throw new Error(err) @@ -180,8 +192,7 @@ module MSSQLModule { async connect() { try { - const client = await this.pool.connect() - this.client = client.request() + this.client = await this.pool.connect() } catch (err) { // @ts-ignore throw new Error(err) @@ -276,10 +287,12 @@ module MSSQLModule { async query(json: QueryJson) { await this.connect() - const operation = this._operation(json).toLowerCase() - const input = this._query(json) - const response = await internalQuery(this.client, input) - return response.recordset ? response.recordset : [{ [operation]: true }] + const operation = this._operation(json) + const queryFn = (query: any, op: string) => + internalQuery(this.client, query, op) + const processFn = (result: any) => + result.recordset ? result.recordset : [{ [operation]: true }] + return this.queryWithReturning(json, queryFn, processFn) } } diff --git a/packages/server/src/integrations/mysql.ts b/packages/server/src/integrations/mysql.ts index 30cbd836b5..ad313d9302 100644 --- a/packages/server/src/integrations/mysql.ts +++ b/packages/server/src/integrations/mysql.ts @@ -223,67 +223,12 @@ module MySQLModule { return results.length ? results : [{ deleted: true }] } - async getReturningRow(json: QueryJson) { - if (!json.extra || !json.extra.idFilter) { - return {} - } - const input = this._query({ - endpoint: { - ...json.endpoint, - operation: Operation.READ, - }, - fields: [], - filters: json.extra.idFilter, - paginate: { - limit: 1, - }, - }) - return internalQuery(this.client, input, false) - } - - // when creating if an ID has been inserted need to make sure - // the id filter is enriched with it before trying to retrieve the row - checkLookupKeys(results: any, json: QueryJson) { - if (!results?.insertId || !json.meta?.table || !json.meta.table.primary) { - return json - } - const primaryKey = json.meta.table.primary?.[0] - json.extra = { - idFilter: { - equal: { - [primaryKey]: results.insertId, - }, - }, - } - return json - } - async query(json: QueryJson) { - const operation = this._operation(json) this.client.connect() - const input = this._query(json, { disableReturning: true }) - if (Array.isArray(input)) { - const responses = [] - for (let query of input) { - responses.push(await internalQuery(this.client, query, false)) - } - return responses - } - let row - // need to manage returning, a feature mySQL can't do - if (operation === operation.DELETE) { - row = this.getReturningRow(json) - } - const results = await internalQuery(this.client, input, false) - // same as delete, manage returning - if (operation === Operation.CREATE || operation === Operation.UPDATE) { - row = this.getReturningRow(this.checkLookupKeys(results, json)) - } + const queryFn = (query: any) => internalQuery(this.client, query, false) + const output = await this.queryWithReturning(json, queryFn) this.client.end() - if (operation !== Operation.READ) { - return row - } - return results.length ? results : [{ [operation.toLowerCase()]: true }] + return output } } diff --git a/packages/server/src/integrations/utils.ts b/packages/server/src/integrations/utils.ts index f91d25423c..55ec086b7e 100644 --- a/packages/server/src/integrations/utils.ts +++ b/packages/server/src/integrations/utils.ts @@ -1,4 +1,4 @@ -import { SqlQuery } from "../definitions/datasource" +import { Operation, SqlQuery } from "../definitions/datasource" import { Datasource, Table } from "../definitions/common" import { SourceNames } from "../definitions/datasource" const { DocumentTypes, SEPARATOR } = require("../db/utils")