From c32163a9be9dae2b2f050dae88cccf8a3e479446 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 21 Mar 2024 18:26:35 +0000 Subject: [PATCH] Initial fix for defaulting parameters, switch to null rather than strings, this is important for prepared statements/SQL queries. --- .../server/src/api/controllers/query/index.ts | 55 +++++++++++-------- packages/server/src/threads/definitions.ts | 13 ++--- packages/types/src/documents/app/query.ts | 2 + 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/packages/server/src/api/controllers/query/index.ts b/packages/server/src/api/controllers/query/index.ts index 0dba20dacd..b05b4b1222 100644 --- a/packages/server/src/api/controllers/query/index.ts +++ b/packages/server/src/api/controllers/query/index.ts @@ -6,7 +6,7 @@ import { invalidateDynamicVariables } from "../../../threads/utils" import env from "../../../environment" import { events, context, utils, constants } from "@budibase/backend-core" import sdk from "../../../sdk" -import { QueryEvent } from "../../../threads/definitions" +import { QueryEvent, QueryEventParameters } from "../../../threads/definitions" import { ConfigType, Query, @@ -18,7 +18,6 @@ import { FieldType, ExecuteQueryRequest, ExecuteQueryResponse, - QueryParameter, PreviewQueryRequest, PreviewQueryResponse, } from "@budibase/types" @@ -29,7 +28,7 @@ const Runner = new Thread(ThreadType.QUERY, { timeoutMs: env.QUERY_THREAD_TIMEOUT, }) -function validateQueryInputs(parameters: Record) { +function validateQueryInputs(parameters: QueryEventParameters) { for (let entry of Object.entries(parameters)) { const [key, value] = entry if (typeof value !== "string") { @@ -100,8 +99,10 @@ export async function save(ctx: UserCtx) { const datasource = await sdk.datasources.get(query.datasourceId) let eventFn - if (!query._id) { + if (!query._id && !query._rev) { query._id = generateQueryID(query.datasourceId) + // flag to state whether the default bindings are empty strings (old behaviour) or null + query.nullDefaultSupport = true eventFn = () => events.query.created(datasource, query) } else { eventFn = () => events.query.updated(datasource, query) @@ -135,16 +136,22 @@ function getAuthConfig(ctx: UserCtx) { } function enrichParameters( - queryParameters: QueryParameter[], - requestParameters: Record = {} -): Record { + query: Query, + requestParameters: QueryEventParameters = {} +): QueryEventParameters { + const paramNotSet = (val: unknown) => val === "" || val == undefined // first check parameters are all valid validateQueryInputs(requestParameters) // make sure parameters are fully enriched with defaults - for (let parameter of queryParameters) { - if (!requestParameters[parameter.name]) { - requestParameters[parameter.name] = parameter.default + for (let parameter of query.parameters) { + let value: string | null = requestParameters[parameter.name] + if (!value) { + value = parameter.default } + if (query.nullDefaultSupport && paramNotSet(value)) { + value = null + } + requestParameters[parameter.name] = value } return requestParameters } @@ -157,10 +164,15 @@ export async function preview( ) // preview may not have a queryId as it hasn't been saved, but if it does // this stops dynamic variables from calling the same query - const { fields, parameters, queryVerb, transformer, queryId, schema } = - ctx.request.body + const queryId = ctx.request.body.queryId + // the body contains the makings of a query, which has not been saved yet + const query: Query = ctx.request.body + // hasn't been saved, new query + if (!queryId && !query._id) { + query.nullDefaultSupport = true + } - let existingSchema = schema + let existingSchema = query.schema if (queryId && !existingSchema) { try { const db = context.getAppDB() @@ -268,13 +280,13 @@ export async function preview( try { const inputs: QueryEvent = { appId: ctx.appId, - datasource, - queryVerb, - fields, - parameters: enrichParameters(parameters), - transformer, + queryVerb: query.queryVerb, + fields: query.fields, + parameters: enrichParameters(query), + transformer: query.transformer, + schema: query.schema, queryId, - schema, + datasource, // have to pass down to the thread runner - can't put into context now environmentVariables: envVars, ctx: { @@ -336,10 +348,7 @@ async function execute( queryVerb: query.queryVerb, fields: query.fields, pagination: ctx.request.body.pagination, - parameters: enrichParameters( - query.parameters, - ctx.request.body.parameters - ), + parameters: enrichParameters(query, ctx.request.body.parameters), transformer: query.transformer, queryId: ctx.params.queryId, // have to pass down to the thread runner - can't put into context now diff --git a/packages/server/src/threads/definitions.ts b/packages/server/src/threads/definitions.ts index 14b97c57b1..85e546280d 100644 --- a/packages/server/src/threads/definitions.ts +++ b/packages/server/src/threads/definitions.ts @@ -1,21 +1,20 @@ -import { Datasource, QuerySchema, Row } from "@budibase/types" +import { Datasource, Row, Query } from "@budibase/types" export type WorkerCallback = (error: any, response?: any) => void -export interface QueryEvent { +export interface QueryEvent + extends Omit { appId?: string datasource: Datasource - queryVerb: string - fields: { [key: string]: any } - parameters: { [key: string]: unknown } pagination?: any - transformer: any queryId?: string environmentVariables?: Record + parameters: QueryEventParameters ctx?: any - schema?: Record } +export type QueryEventParameters = Record + export interface QueryResponse { rows: Row[] keys: string[] diff --git a/packages/types/src/documents/app/query.ts b/packages/types/src/documents/app/query.ts index 535c5dab3b..baba4def95 100644 --- a/packages/types/src/documents/app/query.ts +++ b/packages/types/src/documents/app/query.ts @@ -15,6 +15,8 @@ export interface Query extends Document { schema: Record readable: boolean queryVerb: string + // flag to state whether the default bindings are empty strings (old behaviour) or null + nullDefaultSupport?: boolean } export interface QueryPreview extends Omit {