From a332c058ce8f1fb371c84c7beb2aff44c1590354 Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Tue, 5 Mar 2024 16:19:21 +0000 Subject: [PATCH] Disabling aliasing on writes (create, update, delete) for MySQL/MS-SQL datasources. --- .../server/src/api/controllers/row/alias.ts | 48 +++++++++++++++---- packages/server/src/integrations/base/sql.ts | 10 ++-- packages/server/src/integrations/index.ts | 11 +++-- packages/server/src/sdk/app/rows/utils.ts | 20 ++++++++ 4 files changed, 74 insertions(+), 15 deletions(-) diff --git a/packages/server/src/api/controllers/row/alias.ts b/packages/server/src/api/controllers/row/alias.ts index 46b090bb97..0adcfaa582 100644 --- a/packages/server/src/api/controllers/row/alias.ts +++ b/packages/server/src/api/controllers/row/alias.ts @@ -1,12 +1,16 @@ import { - QueryJson, - SearchFilters, - Table, - Row, + Datasource, DatasourcePlusQueryResponse, + Operation, + QueryJson, + Row, + SearchFilters, } from "@budibase/types" -import { getDatasourceAndQuery } from "../../../sdk/app/rows/utils" +import { getSQLClient } from "../../../sdk/app/rows/utils" import { cloneDeep } from "lodash" +import sdk from "../../../sdk" +import { makeExternalQuery } from "../../../integrations/base/query" +import { SqlClient } from "../../../integrations/utils" class CharSequence { static alphabet = "abcdefghijklmnopqrstuvwxyz" @@ -43,6 +47,32 @@ export default class AliasTables { this.charSeq = new CharSequence() } + isAliasingEnabled(json: QueryJson, datasource: Datasource) { + const fieldLength = json.resource?.fields?.length + if (!fieldLength || fieldLength <= 0) { + return false + } + const writeOperations = [ + Operation.CREATE, + Operation.UPDATE, + Operation.DELETE, + ] + try { + const sqlClient = getSQLClient(datasource) + const isWrite = writeOperations.includes(json.endpoint.operation) + if ( + isWrite && + (sqlClient === SqlClient.MY_SQL || sqlClient === SqlClient.MS_SQL) + ) { + return false + } + } catch (err) { + // if we can't get an SQL client, we can't alias + return false + } + return true + } + getAlias(tableName: string) { if (this.aliases[tableName]) { return this.aliases[tableName] @@ -111,8 +141,10 @@ export default class AliasTables { } async queryWithAliasing(json: QueryJson): DatasourcePlusQueryResponse { - const fieldLength = json.resource?.fields?.length - const aliasingEnabled = fieldLength && fieldLength > 0 + const datasourceId = json.endpoint.datasourceId + const datasource = await sdk.datasources.get(datasourceId) + + const aliasingEnabled = this.isAliasingEnabled(json, datasource) if (aliasingEnabled) { json = cloneDeep(json) // run through the query json to update anywhere a table may be used @@ -158,7 +190,7 @@ export default class AliasTables { } json.tableAliases = invertedTableAliases } - const response = await getDatasourceAndQuery(json) + const response = await makeExternalQuery(datasource, json) if (Array.isArray(response) && aliasingEnabled) { return this.reverse(response) } else { diff --git a/packages/server/src/integrations/base/sql.ts b/packages/server/src/integrations/base/sql.ts index c8acb606b3..be1883c8ec 100644 --- a/packages/server/src/integrations/base/sql.ts +++ b/packages/server/src/integrations/base/sql.ts @@ -435,10 +435,12 @@ class InternalBuilder { aliases?: QueryJson["tableAliases"] ): Knex.QueryBuilder { const tableName = endpoint.entityId - const tableAliased = aliases?.[tableName] - ? `${tableName} as ${aliases?.[tableName]}` - : tableName - let query = knex(tableAliased) + const tableAlias = aliases?.[tableName] + let table: string | Record = tableName + if (tableAlias) { + table = { [tableAlias]: tableName } + } + let query = knex(table) if (endpoint.schema) { query = query.withSchema(endpoint.schema) } diff --git a/packages/server/src/integrations/index.ts b/packages/server/src/integrations/index.ts index ee2bb23f23..18c46b9260 100644 --- a/packages/server/src/integrations/index.ts +++ b/packages/server/src/integrations/index.ts @@ -14,13 +14,18 @@ import firebase from "./firebase" import redis from "./redis" import snowflake from "./snowflake" import oracle from "./oracle" -import { SourceName, Integration, PluginType } from "@budibase/types" +import { + SourceName, + Integration, + PluginType, + IntegrationBase, +} from "@budibase/types" import { getDatasourcePlugin } from "../utilities/fileSystem" import env from "../environment" import cloneDeep from "lodash/cloneDeep" import sdk from "../sdk" -const DEFINITIONS: Record = { +const DEFINITIONS: { [key: SourceName]: Integration | undefined } = { [SourceName.POSTGRES]: postgres.schema, [SourceName.DYNAMODB]: dynamodb.schema, [SourceName.MONGODB]: mongodb.schema, @@ -40,7 +45,7 @@ const DEFINITIONS: Record = { [SourceName.BUDIBASE]: undefined, } -const INTEGRATIONS: Record = { +const INTEGRATIONS: { [key: SourceName]: IntegrationBase | undefined } = { [SourceName.POSTGRES]: postgres.integration, [SourceName.DYNAMODB]: dynamodb.integration, [SourceName.MONGODB]: mongodb.integration, diff --git a/packages/server/src/sdk/app/rows/utils.ts b/packages/server/src/sdk/app/rows/utils.ts index a8052462a9..e090045925 100644 --- a/packages/server/src/sdk/app/rows/utils.ts +++ b/packages/server/src/sdk/app/rows/utils.ts @@ -7,11 +7,31 @@ import { Table, TableSchema, DatasourcePlusQueryResponse, + Datasource, + SourceName, } from "@budibase/types" import { makeExternalQuery } from "../../../integrations/base/query" import { Format } from "../../../api/controllers/view/exporters" import sdk from "../.." import { isRelationshipColumn } from "../../../db/utils" +import { SqlClient } from "../../../integrations/utils" + +export function getSQLClient(datasource: Datasource): SqlClient { + if (!datasource.isSQL) { + throw new Error("Cannot get SQL Client for non-SQL datasource") + } + switch (datasource.source) { + case SourceName.POSTGRES: + return SqlClient.POSTGRES + case SourceName.MYSQL: + return SqlClient.MY_SQL + case SourceName.ORACLE: + return SqlClient.ORACLE + case SourceName.SQL_SERVER: + return SqlClient.MS_SQL + } + throw new Error("Unable to find a valid SQL client") +} export async function getDatasourceAndQuery( json: QueryJson