@budibase/backend-core 2.29.21 → 2.29.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +68 -40
- package/dist/index.js.map +3 -3
- package/dist/index.js.meta.json +1 -1
- package/dist/package.json +4 -4
- package/dist/src/sql/sql.d.ts +2 -2
- package/dist/src/sql/sql.js +68 -42
- package/dist/src/sql/sql.js.map +1 -1
- package/dist/src/sql/sqlTable.d.ts +3 -3
- package/dist/src/sql/sqlTable.js.map +1 -1
- package/package.json +4 -4
- package/src/sql/sql.ts +103 -44
- package/src/sql/sqlTable.ts +3 -3
package/src/sql/sql.ts
CHANGED
|
@@ -42,27 +42,28 @@ const envLimit = environment.SQL_MAX_ROWS
|
|
|
42
42
|
: null
|
|
43
43
|
const BASE_LIMIT = envLimit || 5000
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
// Takes a string like foo and returns a quoted string like [foo] for SQL Server
|
|
46
|
+
// and "foo" for Postgres.
|
|
47
|
+
function quote(client: SqlClient, str: string): string {
|
|
47
48
|
switch (client) {
|
|
48
|
-
case SqlClient.MY_SQL:
|
|
49
|
-
start = end = "`"
|
|
50
|
-
break
|
|
51
49
|
case SqlClient.SQL_LITE:
|
|
52
50
|
case SqlClient.ORACLE:
|
|
53
51
|
case SqlClient.POSTGRES:
|
|
54
|
-
|
|
55
|
-
break
|
|
52
|
+
return `"${str}"`
|
|
56
53
|
case SqlClient.MS_SQL:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
return `[${str}]`
|
|
55
|
+
case SqlClient.MY_SQL:
|
|
56
|
+
return `\`${str}\``
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Takes a string like a.b.c and returns a quoted identifier like [a].[b].[c]
|
|
61
|
+
// for SQL Server and `a`.`b`.`c` for MySQL.
|
|
62
|
+
function quotedIdentifier(client: SqlClient, key: string): string {
|
|
65
63
|
return key
|
|
64
|
+
.split(".")
|
|
65
|
+
.map(part => quote(client, part))
|
|
66
|
+
.join(".")
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
function parse(input: any) {
|
|
@@ -113,34 +114,81 @@ function generateSelectStatement(
|
|
|
113
114
|
knex: Knex
|
|
114
115
|
): (string | Knex.Raw)[] | "*" {
|
|
115
116
|
const { resource, meta } = json
|
|
117
|
+
const client = knex.client.config.client as SqlClient
|
|
116
118
|
|
|
117
119
|
if (!resource || !resource.fields || resource.fields.length === 0) {
|
|
118
120
|
return "*"
|
|
119
121
|
}
|
|
120
122
|
|
|
121
|
-
const schema = meta
|
|
123
|
+
const schema = meta.table.schema
|
|
122
124
|
return resource.fields.map(field => {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
125
|
+
const parts = field.split(/\./g)
|
|
126
|
+
let table: string | undefined = undefined
|
|
127
|
+
let column: string | undefined = undefined
|
|
128
|
+
|
|
129
|
+
// Just a column name, e.g.: "column"
|
|
130
|
+
if (parts.length === 1) {
|
|
131
|
+
column = parts[0]
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// A table name and a column name, e.g.: "table.column"
|
|
135
|
+
if (parts.length === 2) {
|
|
136
|
+
table = parts[0]
|
|
137
|
+
column = parts[1]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// A link doc, e.g.: "table.doc1.fieldName"
|
|
141
|
+
if (parts.length > 2) {
|
|
142
|
+
table = parts[0]
|
|
143
|
+
column = parts.slice(1).join(".")
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!column) {
|
|
147
|
+
throw new Error(`Invalid field name: ${field}`)
|
|
134
148
|
}
|
|
149
|
+
|
|
150
|
+
const columnSchema = schema[column]
|
|
151
|
+
|
|
152
|
+
if (
|
|
153
|
+
client === SqlClient.POSTGRES &&
|
|
154
|
+
columnSchema?.externalType?.includes("money")
|
|
155
|
+
) {
|
|
156
|
+
return knex.raw(
|
|
157
|
+
`${quotedIdentifier(
|
|
158
|
+
client,
|
|
159
|
+
[table, column].join(".")
|
|
160
|
+
)}::money::numeric as ${quote(client, field)}`
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
135
164
|
if (
|
|
136
|
-
|
|
165
|
+
client === SqlClient.MS_SQL &&
|
|
137
166
|
columnSchema?.type === FieldType.DATETIME &&
|
|
138
167
|
columnSchema.timeOnly
|
|
139
168
|
) {
|
|
140
|
-
// Time gets returned as timestamp from mssql, not matching the expected
|
|
169
|
+
// Time gets returned as timestamp from mssql, not matching the expected
|
|
170
|
+
// HH:mm format
|
|
141
171
|
return knex.raw(`CONVERT(varchar, ${field}, 108) as "${field}"`)
|
|
142
172
|
}
|
|
143
|
-
|
|
173
|
+
|
|
174
|
+
// There's at least two edge cases being handled in the expression below.
|
|
175
|
+
// 1. The column name could start/end with a space, and in that case we
|
|
176
|
+
// want to preseve that space.
|
|
177
|
+
// 2. Almost all column names are specified in the form table.column, except
|
|
178
|
+
// in the case of relationships, where it's table.doc1.column. In that
|
|
179
|
+
// case, we want to split it into `table`.`doc1.column` for reasons that
|
|
180
|
+
// aren't actually clear to me, but `table`.`doc1` breaks things with the
|
|
181
|
+
// sample data tests.
|
|
182
|
+
if (table) {
|
|
183
|
+
return knex.raw(
|
|
184
|
+
`${quote(client, table)}.${quote(client, column)} as ${quote(
|
|
185
|
+
client,
|
|
186
|
+
field
|
|
187
|
+
)}`
|
|
188
|
+
)
|
|
189
|
+
} else {
|
|
190
|
+
return knex.raw(`${quote(client, field)} as ${quote(client, field)}`)
|
|
191
|
+
}
|
|
144
192
|
})
|
|
145
193
|
}
|
|
146
194
|
|
|
@@ -173,9 +221,9 @@ function convertBooleans(query: SqlQuery | SqlQuery[]): SqlQuery | SqlQuery[] {
|
|
|
173
221
|
}
|
|
174
222
|
|
|
175
223
|
class InternalBuilder {
|
|
176
|
-
private readonly client:
|
|
224
|
+
private readonly client: SqlClient
|
|
177
225
|
|
|
178
|
-
constructor(client:
|
|
226
|
+
constructor(client: SqlClient) {
|
|
179
227
|
this.client = client
|
|
180
228
|
}
|
|
181
229
|
|
|
@@ -250,9 +298,10 @@ class InternalBuilder {
|
|
|
250
298
|
} else {
|
|
251
299
|
const rawFnc = `${fnc}Raw`
|
|
252
300
|
// @ts-ignore
|
|
253
|
-
query = query[rawFnc](
|
|
254
|
-
|
|
255
|
-
|
|
301
|
+
query = query[rawFnc](
|
|
302
|
+
`LOWER(${quotedIdentifier(this.client, key)}) LIKE ?`,
|
|
303
|
+
[`%${value.toLowerCase()}%`]
|
|
304
|
+
)
|
|
256
305
|
}
|
|
257
306
|
}
|
|
258
307
|
|
|
@@ -302,7 +351,10 @@ class InternalBuilder {
|
|
|
302
351
|
}
|
|
303
352
|
statement +=
|
|
304
353
|
(statement ? andOr : "") +
|
|
305
|
-
`COALESCE(LOWER(${
|
|
354
|
+
`COALESCE(LOWER(${quotedIdentifier(
|
|
355
|
+
this.client,
|
|
356
|
+
key
|
|
357
|
+
)}), '') LIKE ?`
|
|
306
358
|
}
|
|
307
359
|
|
|
308
360
|
if (statement === "") {
|
|
@@ -336,9 +388,10 @@ class InternalBuilder {
|
|
|
336
388
|
} else {
|
|
337
389
|
const rawFnc = `${fnc}Raw`
|
|
338
390
|
// @ts-ignore
|
|
339
|
-
query = query[rawFnc](
|
|
340
|
-
|
|
341
|
-
|
|
391
|
+
query = query[rawFnc](
|
|
392
|
+
`LOWER(${quotedIdentifier(this.client, key)}) LIKE ?`,
|
|
393
|
+
[`${value.toLowerCase()}%`]
|
|
394
|
+
)
|
|
342
395
|
}
|
|
343
396
|
})
|
|
344
397
|
}
|
|
@@ -376,12 +429,15 @@ class InternalBuilder {
|
|
|
376
429
|
const fnc = allOr ? "orWhereRaw" : "whereRaw"
|
|
377
430
|
if (this.client === SqlClient.MS_SQL) {
|
|
378
431
|
query = query[fnc](
|
|
379
|
-
`CASE WHEN ${
|
|
432
|
+
`CASE WHEN ${quotedIdentifier(
|
|
433
|
+
this.client,
|
|
434
|
+
key
|
|
435
|
+
)} = ? THEN 1 ELSE 0 END = 1`,
|
|
380
436
|
[value]
|
|
381
437
|
)
|
|
382
438
|
} else {
|
|
383
439
|
query = query[fnc](
|
|
384
|
-
`COALESCE(${
|
|
440
|
+
`COALESCE(${quotedIdentifier(this.client, key)} = ?, FALSE)`,
|
|
385
441
|
[value]
|
|
386
442
|
)
|
|
387
443
|
}
|
|
@@ -392,12 +448,15 @@ class InternalBuilder {
|
|
|
392
448
|
const fnc = allOr ? "orWhereRaw" : "whereRaw"
|
|
393
449
|
if (this.client === SqlClient.MS_SQL) {
|
|
394
450
|
query = query[fnc](
|
|
395
|
-
`CASE WHEN ${
|
|
451
|
+
`CASE WHEN ${quotedIdentifier(
|
|
452
|
+
this.client,
|
|
453
|
+
key
|
|
454
|
+
)} = ? THEN 1 ELSE 0 END = 0`,
|
|
396
455
|
[value]
|
|
397
456
|
)
|
|
398
457
|
} else {
|
|
399
458
|
query = query[fnc](
|
|
400
|
-
`COALESCE(${
|
|
459
|
+
`COALESCE(${quotedIdentifier(this.client, key)} != ?, TRUE)`,
|
|
401
460
|
[value]
|
|
402
461
|
)
|
|
403
462
|
}
|
|
@@ -769,7 +828,7 @@ class SqlQueryBuilder extends SqlTableQueryBuilder {
|
|
|
769
828
|
private readonly limit: number
|
|
770
829
|
|
|
771
830
|
// pass through client to get flavour of SQL
|
|
772
|
-
constructor(client:
|
|
831
|
+
constructor(client: SqlClient, limit: number = BASE_LIMIT) {
|
|
773
832
|
super(client)
|
|
774
833
|
this.limit = limit
|
|
775
834
|
}
|
package/src/sql/sqlTable.ts
CHANGED
|
@@ -195,14 +195,14 @@ function buildDeleteTable(knex: SchemaBuilder, table: Table): SchemaBuilder {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
class SqlTableQueryBuilder {
|
|
198
|
-
private readonly sqlClient:
|
|
198
|
+
private readonly sqlClient: SqlClient
|
|
199
199
|
|
|
200
200
|
// pass through client to get flavour of SQL
|
|
201
|
-
constructor(client:
|
|
201
|
+
constructor(client: SqlClient) {
|
|
202
202
|
this.sqlClient = client
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
getSqlClient():
|
|
205
|
+
getSqlClient(): SqlClient {
|
|
206
206
|
return this.sqlClient
|
|
207
207
|
}
|
|
208
208
|
|