Initial fix for defaulting parameters, switch to null rather than strings, this is important for prepared statements/SQL queries.

This commit is contained in:
mike12345567 2024-03-21 18:26:35 +00:00
parent ed94459fd8
commit c32163a9be
3 changed files with 40 additions and 30 deletions

View File

@ -6,7 +6,7 @@ import { invalidateDynamicVariables } from "../../../threads/utils"
import env from "../../../environment" import env from "../../../environment"
import { events, context, utils, constants } from "@budibase/backend-core" import { events, context, utils, constants } from "@budibase/backend-core"
import sdk from "../../../sdk" import sdk from "../../../sdk"
import { QueryEvent } from "../../../threads/definitions" import { QueryEvent, QueryEventParameters } from "../../../threads/definitions"
import { import {
ConfigType, ConfigType,
Query, Query,
@ -18,7 +18,6 @@ import {
FieldType, FieldType,
ExecuteQueryRequest, ExecuteQueryRequest,
ExecuteQueryResponse, ExecuteQueryResponse,
QueryParameter,
PreviewQueryRequest, PreviewQueryRequest,
PreviewQueryResponse, PreviewQueryResponse,
} from "@budibase/types" } from "@budibase/types"
@ -29,7 +28,7 @@ const Runner = new Thread(ThreadType.QUERY, {
timeoutMs: env.QUERY_THREAD_TIMEOUT, timeoutMs: env.QUERY_THREAD_TIMEOUT,
}) })
function validateQueryInputs(parameters: Record<string, string>) { function validateQueryInputs(parameters: QueryEventParameters) {
for (let entry of Object.entries(parameters)) { for (let entry of Object.entries(parameters)) {
const [key, value] = entry const [key, value] = entry
if (typeof value !== "string") { if (typeof value !== "string") {
@ -100,8 +99,10 @@ export async function save(ctx: UserCtx<Query, Query>) {
const datasource = await sdk.datasources.get(query.datasourceId) const datasource = await sdk.datasources.get(query.datasourceId)
let eventFn let eventFn
if (!query._id) { if (!query._id && !query._rev) {
query._id = generateQueryID(query.datasourceId) 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) eventFn = () => events.query.created(datasource, query)
} else { } else {
eventFn = () => events.query.updated(datasource, query) eventFn = () => events.query.updated(datasource, query)
@ -135,16 +136,22 @@ function getAuthConfig(ctx: UserCtx) {
} }
function enrichParameters( function enrichParameters(
queryParameters: QueryParameter[], query: Query,
requestParameters: Record<string, string> = {} requestParameters: QueryEventParameters = {}
): Record<string, string> { ): QueryEventParameters {
const paramNotSet = (val: unknown) => val === "" || val == undefined
// first check parameters are all valid // first check parameters are all valid
validateQueryInputs(requestParameters) validateQueryInputs(requestParameters)
// make sure parameters are fully enriched with defaults // make sure parameters are fully enriched with defaults
for (let parameter of queryParameters) { for (let parameter of query.parameters) {
if (!requestParameters[parameter.name]) { let value: string | null = requestParameters[parameter.name]
requestParameters[parameter.name] = parameter.default if (!value) {
value = parameter.default
} }
if (query.nullDefaultSupport && paramNotSet(value)) {
value = null
}
requestParameters[parameter.name] = value
} }
return requestParameters 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 // 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 // this stops dynamic variables from calling the same query
const { fields, parameters, queryVerb, transformer, queryId, schema } = const queryId = ctx.request.body.queryId
ctx.request.body // 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) { if (queryId && !existingSchema) {
try { try {
const db = context.getAppDB() const db = context.getAppDB()
@ -268,13 +280,13 @@ export async function preview(
try { try {
const inputs: QueryEvent = { const inputs: QueryEvent = {
appId: ctx.appId, appId: ctx.appId,
datasource, queryVerb: query.queryVerb,
queryVerb, fields: query.fields,
fields, parameters: enrichParameters(query),
parameters: enrichParameters(parameters), transformer: query.transformer,
transformer, schema: query.schema,
queryId, queryId,
schema, datasource,
// have to pass down to the thread runner - can't put into context now // have to pass down to the thread runner - can't put into context now
environmentVariables: envVars, environmentVariables: envVars,
ctx: { ctx: {
@ -336,10 +348,7 @@ async function execute(
queryVerb: query.queryVerb, queryVerb: query.queryVerb,
fields: query.fields, fields: query.fields,
pagination: ctx.request.body.pagination, pagination: ctx.request.body.pagination,
parameters: enrichParameters( parameters: enrichParameters(query, ctx.request.body.parameters),
query.parameters,
ctx.request.body.parameters
),
transformer: query.transformer, transformer: query.transformer,
queryId: ctx.params.queryId, queryId: ctx.params.queryId,
// have to pass down to the thread runner - can't put into context now // have to pass down to the thread runner - can't put into context now

View File

@ -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 type WorkerCallback = (error: any, response?: any) => void
export interface QueryEvent { export interface QueryEvent
extends Omit<Query, "datasourceId" | "name" | "parameters" | "readable"> {
appId?: string appId?: string
datasource: Datasource datasource: Datasource
queryVerb: string
fields: { [key: string]: any }
parameters: { [key: string]: unknown }
pagination?: any pagination?: any
transformer: any
queryId?: string queryId?: string
environmentVariables?: Record<string, string> environmentVariables?: Record<string, string>
parameters: QueryEventParameters
ctx?: any ctx?: any
schema?: Record<string, QuerySchema | string>
} }
export type QueryEventParameters = Record<string, string | null>
export interface QueryResponse { export interface QueryResponse {
rows: Row[] rows: Row[]
keys: string[] keys: string[]

View File

@ -15,6 +15,8 @@ export interface Query extends Document {
schema: Record<string, QuerySchema | string> schema: Record<string, QuerySchema | string>
readable: boolean readable: boolean
queryVerb: string queryVerb: string
// flag to state whether the default bindings are empty strings (old behaviour) or null
nullDefaultSupport?: boolean
} }
export interface QueryPreview extends Omit<Query, "_id"> { export interface QueryPreview extends Omit<Query, "_id"> {