From cb19e1f24c0ab72989cf7058525492f1fe2a84c1 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 26 Feb 2024 17:56:28 +0000 Subject: [PATCH] Fixing response types of DS+ query function. --- .../api/controllers/row/ExternalRequest.ts | 9 ++++---- .../server/src/api/controllers/row/alias.ts | 21 +++++++++++++------ .../server/src/integrations/base/query.ts | 8 +++++-- .../server/src/integrations/googlesheets.ts | 7 ++++--- .../src/integrations/microsoftSqlServer.ts | 3 ++- packages/server/src/integrations/mysql.ts | 4 ++-- packages/server/src/integrations/oracle.ts | 8 ++++--- packages/server/src/integrations/postgres.ts | 3 ++- packages/server/src/sdk/app/rows/utils.ts | 13 ++++++++++-- packages/types/src/sdk/datasources.ts | 16 +++++++++++++- 10 files changed, 67 insertions(+), 25 deletions(-) diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts index 0070e0bf24..0dd17a86e6 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -332,7 +332,7 @@ export class ExternalRequest { endpoint: getEndpoint(table._id!, Operation.READ), filters: buildFilters(rowId, {}, table), }) - if (response.length > 0) { + if (Array.isArray(response)) { return response[0] } else { throw new Error(`Cannot fetch row by ID "${rowId}"`) @@ -646,7 +646,7 @@ export class ExternalRequest { }, }) // this is the response from knex if no rows found - const rows: Row[] = !response[0].read ? response : [] + const rows: Row[] = response?.[0].read ? [] : (response as Row[]) const storeTo = isMany ? field.throughFrom || linkPrimaryKey : fieldName related[storeTo] = { rows, isMany, tableId } } @@ -899,15 +899,16 @@ export class ExternalRequest { response = await getDatasourceAndQuery(json) } + const responseRows = Array.isArray(response) ? response : [] // handle many-to-many relationships now if we know the ID (could be auto increment) if (operation !== Operation.READ) { await this.handleManyRelationships( table._id || "", - response[0], + responseRows[0], processed.manyRelationships ) } - const output = this.outputProcessing(response, table, relationships) + const output = this.outputProcessing(responseRows, table, relationships) // if reading it'll just be an array of rows, return whole thing if (operation === Operation.READ) { return ( diff --git a/packages/server/src/api/controllers/row/alias.ts b/packages/server/src/api/controllers/row/alias.ts index 6533e51728..ca144ee518 100644 --- a/packages/server/src/api/controllers/row/alias.ts +++ b/packages/server/src/api/controllers/row/alias.ts @@ -1,4 +1,10 @@ -import { QueryJson, SearchFilters, Table, Row } from "@budibase/types" +import { + QueryJson, + SearchFilters, + Table, + Row, + DatasourcePlusQueryResponse, +} from "@budibase/types" import { getDatasourceAndQuery } from "../../../sdk/app/rows/utils" import { cloneDeep } from "lodash" @@ -68,9 +74,8 @@ export default class AliasTables { return map } - async queryWithAliasing(json: QueryJson) { + async queryWithAliasing(json: QueryJson): DatasourcePlusQueryResponse { json = cloneDeep(json) - const aliasField = (field: string) => this.aliasField(field) const aliasTable = (table: Table) => ({ ...table, name: this.getAlias(table.name), @@ -78,7 +83,7 @@ export default class AliasTables { // run through the query json to update anywhere a table may be used if (json.resource?.fields) { json.resource.fields = json.resource.fields.map(field => - aliasField(field) + this.aliasField(field) ) } if (json.filters) { @@ -88,7 +93,7 @@ export default class AliasTables { } const aliasedFilters: typeof filter = {} for (let key of Object.keys(filter)) { - aliasedFilters[aliasField(key)] = filter[key] + aliasedFilters[this.aliasField(key)] = filter[key] } json.filters[filterKey as keyof SearchFilters] = aliasedFilters } @@ -120,6 +125,10 @@ export default class AliasTables { } json.tableAliases = invertedTableAliases const response = await getDatasourceAndQuery(json) - return this.reverse(response) + if (Array.isArray(response)) { + return this.reverse(response) + } else { + return response + } } } diff --git a/packages/server/src/integrations/base/query.ts b/packages/server/src/integrations/base/query.ts index 4f31e37744..b906ecbb1b 100644 --- a/packages/server/src/integrations/base/query.ts +++ b/packages/server/src/integrations/base/query.ts @@ -1,11 +1,15 @@ -import { QueryJson, Datasource } from "@budibase/types" +import { + QueryJson, + Datasource, + DatasourcePlusQueryResponse, +} from "@budibase/types" import { getIntegration } from "../index" import sdk from "../../sdk" export async function makeExternalQuery( datasource: Datasource, json: QueryJson -) { +): DatasourcePlusQueryResponse { datasource = await sdk.datasources.enrich(datasource) const Integration = await getIntegration(datasource.source) // query is the opinionated function diff --git a/packages/server/src/integrations/googlesheets.ts b/packages/server/src/integrations/googlesheets.ts index 58c867ea0b..32398bde41 100644 --- a/packages/server/src/integrations/googlesheets.ts +++ b/packages/server/src/integrations/googlesheets.ts @@ -16,6 +16,7 @@ import { Table, TableRequest, TableSourceType, + DatasourcePlusQueryResponse, } from "@budibase/types" import { OAuth2Client } from "google-auth-library" import { @@ -334,7 +335,7 @@ class GoogleSheetsIntegration implements DatasourcePlus { return { tables: externalTables, errors } } - async query(json: QueryJson) { + async query(json: QueryJson): DatasourcePlusQueryResponse { const sheet = json.endpoint.entityId switch (json.endpoint.operation) { case Operation.CREATE: @@ -384,7 +385,7 @@ class GoogleSheetsIntegration implements DatasourcePlus { } try { await this.connect() - return await this.client.addSheet({ title: name, headerValues: [name] }) + await this.client.addSheet({ title: name, headerValues: [name] }) } catch (err) { console.error("Error creating new table in google sheets", err) throw err @@ -450,7 +451,7 @@ class GoogleSheetsIntegration implements DatasourcePlus { try { await this.connect() const sheetToDelete = this.client.sheetsByTitle[sheet] - return await sheetToDelete.delete() + await sheetToDelete.delete() } catch (err) { console.error("Error deleting table in google sheets", err) throw err diff --git a/packages/server/src/integrations/microsoftSqlServer.ts b/packages/server/src/integrations/microsoftSqlServer.ts index e063933503..f87e248ac0 100644 --- a/packages/server/src/integrations/microsoftSqlServer.ts +++ b/packages/server/src/integrations/microsoftSqlServer.ts @@ -13,6 +13,7 @@ import { SourceName, Schema, TableSourceType, + DatasourcePlusQueryResponse, } from "@budibase/types" import { getSqlQuery, @@ -493,7 +494,7 @@ class SqlServerIntegration extends Sql implements DatasourcePlus { return response.recordset || [{ deleted: true }] } - async query(json: QueryJson) { + async query(json: QueryJson): DatasourcePlusQueryResponse { const schema = this.config.schema await this.connect() if (schema && schema !== DEFAULT_SCHEMA && json?.endpoint) { diff --git a/packages/server/src/integrations/mysql.ts b/packages/server/src/integrations/mysql.ts index 6eebda8df5..f629381807 100644 --- a/packages/server/src/integrations/mysql.ts +++ b/packages/server/src/integrations/mysql.ts @@ -12,7 +12,7 @@ import { SourceName, Schema, TableSourceType, - FieldType, + DatasourcePlusQueryResponse, } from "@budibase/types" import { getSqlQuery, @@ -381,7 +381,7 @@ class MySQLIntegration extends Sql implements DatasourcePlus { return results.length ? results : [{ deleted: true }] } - async query(json: QueryJson) { + async query(json: QueryJson): DatasourcePlusQueryResponse { await this.connect() try { const queryFn = (query: any) => diff --git a/packages/server/src/integrations/oracle.ts b/packages/server/src/integrations/oracle.ts index cdf37a9c83..86c4bf519d 100644 --- a/packages/server/src/integrations/oracle.ts +++ b/packages/server/src/integrations/oracle.ts @@ -12,6 +12,8 @@ import { ConnectionInfo, Schema, TableSourceType, + Row, + DatasourcePlusQueryResponse, } from "@budibase/types" import { buildExternalTableId, @@ -420,7 +422,7 @@ class OracleIntegration extends Sql implements DatasourcePlus { : [{ deleted: true }] } - async query(json: QueryJson) { + async query(json: QueryJson): DatasourcePlusQueryResponse { const operation = this._operation(json) const input = this._query(json, { disableReturning: true }) if (Array.isArray(input)) { @@ -444,7 +446,7 @@ class OracleIntegration extends Sql implements DatasourcePlus { if (deletedRows?.rows?.length) { return deletedRows.rows } else if (response.rows?.length) { - return response.rows + return response.rows as Row[] } else { // get the last row that was updated if ( @@ -455,7 +457,7 @@ class OracleIntegration extends Sql implements DatasourcePlus { const lastRow = await this.internalQuery({ sql: `SELECT * FROM \"${json.endpoint.entityId}\" WHERE ROWID = '${response.lastRowid}'`, }) - return lastRow.rows + return lastRow.rows as Row[] } else { return [{ [operation.toLowerCase()]: true }] } diff --git a/packages/server/src/integrations/postgres.ts b/packages/server/src/integrations/postgres.ts index 7fb75f5d9f..6e87f296e1 100644 --- a/packages/server/src/integrations/postgres.ts +++ b/packages/server/src/integrations/postgres.ts @@ -12,6 +12,7 @@ import { SourceName, Schema, TableSourceType, + DatasourcePlusQueryResponse, } from "@budibase/types" import { getSqlQuery, @@ -419,7 +420,7 @@ class PostgresIntegration extends Sql implements DatasourcePlus { return response.rows.length ? response.rows : [{ deleted: true }] } - async query(json: QueryJson) { + async query(json: QueryJson): DatasourcePlusQueryResponse { const operation = this._operation(json).toLowerCase() const input = this._query(json) if (Array.isArray(input)) { diff --git a/packages/server/src/sdk/app/rows/utils.ts b/packages/server/src/sdk/app/rows/utils.ts index 2320820b3e..75f980d9f9 100644 --- a/packages/server/src/sdk/app/rows/utils.ts +++ b/packages/server/src/sdk/app/rows/utils.ts @@ -1,12 +1,21 @@ import cloneDeep from "lodash/cloneDeep" import validateJs from "validate.js" -import { FieldType, QueryJson, Row, Table, TableSchema } from "@budibase/types" +import { + FieldType, + QueryJson, + Row, + Table, + TableSchema, + DatasourcePlusQueryResponse, +} from "@budibase/types" import { makeExternalQuery } from "../../../integrations/base/query" import { Format } from "../../../api/controllers/view/exporters" import sdk from "../.." import { isRelationshipColumn } from "../../../db/utils" -export async function getDatasourceAndQuery(json: QueryJson) { +export async function getDatasourceAndQuery( + json: QueryJson +): DatasourcePlusQueryResponse { const datasourceId = json.endpoint.datasourceId const datasource = await sdk.datasources.get(datasourceId) return makeExternalQuery(datasource, json) diff --git a/packages/types/src/sdk/datasources.ts b/packages/types/src/sdk/datasources.ts index 7a335eb3b9..4cddb0c09e 100644 --- a/packages/types/src/sdk/datasources.ts +++ b/packages/types/src/sdk/datasources.ts @@ -1,4 +1,5 @@ -import { Table } from "../documents" +import { Table, Row } from "../documents" +import { QueryJson } from "./search" export const PASSWORD_REPLACEMENT = "--secret-value--" @@ -180,11 +181,24 @@ export interface Schema { errors: Record } +// return these when an operation occurred but we got no response +enum DSPlusOperation { + CREATE = "create", + READ = "read", + UPDATE = "update", + DELETE = "delete", +} + +export type DatasourcePlusQueryResponse = Promise< + Row[] | Record[] | void +> + export interface DatasourcePlus extends IntegrationBase { // if the datasource supports the use of bindings directly (to protect against SQL injection) // this returns the format of the identifier getBindingIdentifier(): string getStringConcat(parts: string[]): string + query(json: QueryJson): DatasourcePlusQueryResponse buildSchema( datasourceId: string, entities: Record