@budibase/backend-core 2.33.1 → 2.33.3

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.
@@ -90,6 +90,35 @@ function isSqs(table) {
90
90
  return (table.sourceType === types_1.TableSourceType.INTERNAL ||
91
91
  table.sourceId === types_1.INTERNAL_TABLE_SOURCE_ID);
92
92
  }
93
+ function escapeQuotes(value, quoteChar = '"') {
94
+ return value.replace(new RegExp(quoteChar, "g"), `${quoteChar}${quoteChar}`);
95
+ }
96
+ function wrap(value, quoteChar = '"') {
97
+ return `${quoteChar}${escapeQuotes(value, quoteChar)}${quoteChar}`;
98
+ }
99
+ function stringifyArray(value, quoteStyle = '"') {
100
+ for (let i in value) {
101
+ if (typeof value[i] === "string") {
102
+ value[i] = wrap(value[i], quoteStyle);
103
+ }
104
+ }
105
+ return `[${value.join(",")}]`;
106
+ }
107
+ const allowEmptyRelationships = {
108
+ [types_1.BasicOperator.EQUAL]: false,
109
+ [types_1.BasicOperator.NOT_EQUAL]: true,
110
+ [types_1.BasicOperator.EMPTY]: false,
111
+ [types_1.BasicOperator.NOT_EMPTY]: true,
112
+ [types_1.BasicOperator.FUZZY]: false,
113
+ [types_1.BasicOperator.STRING]: false,
114
+ [types_1.RangeOperator.RANGE]: false,
115
+ [types_1.ArrayOperator.CONTAINS]: false,
116
+ [types_1.ArrayOperator.NOT_CONTAINS]: true,
117
+ [types_1.ArrayOperator.CONTAINS_ANY]: false,
118
+ [types_1.ArrayOperator.ONE_OF]: false,
119
+ [types_1.LogicalOperator.AND]: false,
120
+ [types_1.LogicalOperator.OR]: false,
121
+ };
93
122
  class InternalBuilder {
94
123
  constructor(client, knex, query) {
95
124
  // states the various situations in which we need a full mapped select statement
@@ -116,28 +145,24 @@ class InternalBuilder {
116
145
  get table() {
117
146
  return this.query.meta.table;
118
147
  }
148
+ get knexClient() {
149
+ return this.knex.client;
150
+ }
119
151
  getFieldSchema(key) {
120
152
  const { column } = this.splitter.run(key);
121
153
  return this.table.schema[column];
122
154
  }
155
+ supportsILike() {
156
+ return !(this.client === types_1.SqlClient.ORACLE || this.client === types_1.SqlClient.SQL_LITE);
157
+ }
123
158
  quoteChars() {
124
- switch (this.client) {
125
- case types_1.SqlClient.ORACLE:
126
- case types_1.SqlClient.POSTGRES:
127
- return ['"', '"'];
128
- case types_1.SqlClient.MS_SQL:
129
- return ["[", "]"];
130
- case types_1.SqlClient.MARIADB:
131
- case types_1.SqlClient.MY_SQL:
132
- case types_1.SqlClient.SQL_LITE:
133
- return ["`", "`"];
134
- }
159
+ const wrapped = this.knexClient.wrapIdentifier("foo", {});
160
+ return [wrapped[0], wrapped[wrapped.length - 1]];
135
161
  }
136
- // Takes a string like foo and returns a quoted string like [foo] for SQL Server
137
- // and "foo" for Postgres.
162
+ // Takes a string like foo and returns a quoted string like [foo] for SQL
163
+ // Server and "foo" for Postgres.
138
164
  quote(str) {
139
- const [start, end] = this.quoteChars();
140
- return `${start}${str}${end}`;
165
+ return this.knexClient.wrapIdentifier(str, {});
141
166
  }
142
167
  isQuoted(key) {
143
168
  const [start, end] = this.quoteChars();
@@ -152,6 +177,27 @@ class InternalBuilder {
152
177
  }
153
178
  return key.map(part => this.quote(part)).join(".");
154
179
  }
180
+ quotedValue(value) {
181
+ const formatter = this.knexClient.formatter(this.knexClient.queryBuilder());
182
+ return formatter.wrap(value, false);
183
+ }
184
+ rawQuotedValue(value) {
185
+ return this.knex.raw(this.quotedValue(value));
186
+ }
187
+ // Unfortuantely we cannot rely on knex's identifier escaping because it trims
188
+ // the identifier string before escaping it, which breaks cases for us where
189
+ // columns that start or end with a space aren't referenced correctly anymore.
190
+ //
191
+ // So whenever you're using an identifier binding in knex, e.g. knex.raw("??
192
+ // as ?", ["foo", "bar"]), you need to make sure you call this:
193
+ //
194
+ // knex.raw("?? as ?", [this.quotedIdentifier("foo"), "bar"])
195
+ //
196
+ // Issue we filed against knex about this:
197
+ // https://github.com/knex/knex/issues/6143
198
+ rawQuotedIdentifier(key) {
199
+ return this.knex.raw(this.quotedIdentifier(key));
200
+ }
155
201
  // Turns an identifier like a.b.c or `a`.`b`.`c` into ["a", "b", "c"]
156
202
  splitIdentifier(key) {
157
203
  const [start, end] = this.quoteChars();
@@ -191,7 +237,7 @@ class InternalBuilder {
191
237
  const alias = this.getTableName(endpoint.entityId);
192
238
  const schema = meta.table.schema;
193
239
  if (!this.isFullSelectStatementRequired()) {
194
- return [this.knex.raw(`${this.quote(alias)}.*`)];
240
+ return [this.knex.raw("??", [`${alias}.*`])];
195
241
  }
196
242
  // get just the fields for this table
197
243
  return resource.fields
@@ -210,17 +256,28 @@ class InternalBuilder {
210
256
  .map(({ table, column, field }) => {
211
257
  const columnSchema = schema[column];
212
258
  if (this.SPECIAL_SELECT_CASES.POSTGRES_MONEY(columnSchema)) {
213
- return this.knex.raw(`${this.quotedIdentifier([table, column].join("."))}::money::numeric as ${this.quote(field)}`);
259
+ // TODO: figure out how to express this safely without string
260
+ // interpolation.
261
+ return this.knex.raw(`??::money::numeric as "${field}"`, [
262
+ this.rawQuotedIdentifier([table, column].join(".")),
263
+ field,
264
+ ]);
214
265
  }
215
266
  if (this.SPECIAL_SELECT_CASES.MSSQL_DATES(columnSchema)) {
216
267
  // Time gets returned as timestamp from mssql, not matching the expected
217
268
  // HH:mm format
218
- return this.knex.raw(`CONVERT(varchar, ${field}, 108) as "${field}"`);
269
+ // TODO: figure out how to express this safely without string
270
+ // interpolation.
271
+ return this.knex.raw(`CONVERT(varchar, ??, 108) as "${field}"`, [
272
+ this.rawQuotedIdentifier(field),
273
+ ]);
274
+ }
275
+ if (table) {
276
+ return this.rawQuotedIdentifier(`${table}.${column}`);
277
+ }
278
+ else {
279
+ return this.rawQuotedIdentifier(field);
219
280
  }
220
- const quoted = table
221
- ? `${this.quote(table)}.${this.quote(column)}`
222
- : this.quote(field);
223
- return this.knex.raw(quoted);
224
281
  });
225
282
  }
226
283
  // OracleDB can't use character-large-objects (CLOBs) in WHERE clauses,
@@ -233,7 +290,7 @@ class InternalBuilder {
233
290
  const parts = this.splitIdentifier(field);
234
291
  const col = parts.pop();
235
292
  const schema = this.table.schema[col];
236
- let identifier = this.quotedIdentifier(field);
293
+ let identifier = this.rawQuotedIdentifier(field);
237
294
  if (schema.type === types_1.FieldType.STRING ||
238
295
  schema.type === types_1.FieldType.LONGFORM ||
239
296
  schema.type === types_1.FieldType.BB_REFERENCE_SINGLE ||
@@ -241,10 +298,13 @@ class InternalBuilder {
241
298
  schema.type === types_1.FieldType.OPTIONS ||
242
299
  schema.type === types_1.FieldType.BARCODEQR) {
243
300
  if (opts === null || opts === void 0 ? void 0 : opts.forSelect) {
244
- identifier = `to_char(${identifier}) as ${this.quotedIdentifier(col)}`;
301
+ identifier = this.knex.raw("to_char(??) as ??", [
302
+ identifier,
303
+ this.rawQuotedIdentifier(col),
304
+ ]);
245
305
  }
246
306
  else {
247
- identifier = `to_char(${identifier})`;
307
+ identifier = this.knex.raw("to_char(??)", [identifier]);
248
308
  }
249
309
  }
250
310
  return identifier;
@@ -351,26 +411,35 @@ class InternalBuilder {
351
411
  const document = ((_a = relationship.from) === null || _a === void 0 ? void 0 : _a.split(".")[0]) || "";
352
412
  return query.andWhere(`${document}.fieldName`, "=", relationship.column);
353
413
  }
354
- addRelationshipForFilter(query, filterKey, whereCb) {
355
- const mainKnex = this.knex;
414
+ addRelationshipForFilter(query, allowEmptyRelationships, filterKey, whereCb) {
356
415
  const { relationships, endpoint, tableAliases: aliases } = this.query;
357
416
  const tableName = endpoint.entityId;
358
417
  const fromAlias = (aliases === null || aliases === void 0 ? void 0 : aliases[tableName]) || tableName;
359
- const matches = (possibleTable) => filterKey.startsWith(`${possibleTable}`);
418
+ const matches = (value) => filterKey.match(new RegExp(`^${value}\\.`));
360
419
  if (!relationships) {
361
420
  return query;
362
421
  }
363
422
  for (const relationship of relationships) {
364
423
  const relatedTableName = relationship.tableName;
365
424
  const toAlias = (aliases === null || aliases === void 0 ? void 0 : aliases[relatedTableName]) || relatedTableName;
425
+ const matchesTableName = matches(relatedTableName) || matches(toAlias);
426
+ const matchesRelationName = matches(relationship.column);
366
427
  // this is the relationship which is being filtered
367
- if ((matches(relatedTableName) || matches(toAlias)) &&
428
+ if ((matchesTableName || matchesRelationName) &&
368
429
  relationship.to &&
369
430
  relationship.tableName) {
370
- let subQuery = mainKnex
371
- .select(mainKnex.raw(1))
431
+ const joinTable = this.knex
432
+ .select(this.knex.raw(1))
372
433
  .from({ [toAlias]: relatedTableName });
434
+ let subQuery = joinTable.clone();
373
435
  const manyToMany = (0, utils_1.validateManyToMany)(relationship);
436
+ let updatedKey;
437
+ if (!matchesTableName) {
438
+ updatedKey = filterKey.replace(new RegExp(`^${relationship.column}.`), `${aliases[relationship.tableName]}.`);
439
+ }
440
+ else {
441
+ updatedKey = filterKey;
442
+ }
374
443
  if (manyToMany) {
375
444
  const throughAlias = (aliases === null || aliases === void 0 ? void 0 : aliases[manyToMany.through]) || relationship.through;
376
445
  let throughTable = this.tableNameWithSchema(manyToMany.through, {
@@ -380,23 +449,36 @@ class InternalBuilder {
380
449
  subQuery = subQuery
381
450
  // add a join through the junction table
382
451
  .innerJoin(throughTable, function () {
383
- // @ts-ignore
384
452
  this.on(`${toAlias}.${manyToMany.toPrimary}`, "=", `${throughAlias}.${manyToMany.to}`);
385
453
  })
386
454
  // check the document in the junction table points to the main table
387
- .where(`${throughAlias}.${manyToMany.from}`, "=", mainKnex.raw(this.quotedIdentifier(`${fromAlias}.${manyToMany.fromPrimary}`)));
455
+ .where(`${throughAlias}.${manyToMany.from}`, "=", this.rawQuotedIdentifier(`${fromAlias}.${manyToMany.fromPrimary}`));
388
456
  // in SQS the same junction table is used for different many-to-many relationships between the
389
457
  // two same tables, this is needed to avoid rows ending up in all columns
390
458
  if (this.client === types_1.SqlClient.SQL_LITE) {
391
459
  subQuery = this.addJoinFieldCheck(subQuery, manyToMany);
392
460
  }
461
+ query = query.where(q => {
462
+ q.whereExists(whereCb(updatedKey, subQuery));
463
+ if (allowEmptyRelationships) {
464
+ q.orWhereNotExists(joinTable.clone().innerJoin(throughTable, function () {
465
+ this.on(`${fromAlias}.${manyToMany.fromPrimary}`, "=", `${throughAlias}.${manyToMany.from}`);
466
+ }));
467
+ }
468
+ });
393
469
  }
394
470
  else {
471
+ const toKey = `${toAlias}.${relationship.to}`;
472
+ const foreignKey = `${fromAlias}.${relationship.from}`;
395
473
  // "join" to the main table, making sure the ID matches that of the main
396
- subQuery = subQuery.where(`${toAlias}.${relationship.to}`, "=", mainKnex.raw(this.quotedIdentifier(`${fromAlias}.${relationship.from}`)));
474
+ subQuery = subQuery.where(toKey, "=", this.rawQuotedIdentifier(foreignKey));
475
+ query = query.where(q => {
476
+ q.whereExists(whereCb(updatedKey, subQuery.clone()));
477
+ if (allowEmptyRelationships) {
478
+ q.orWhereNotExists(subQuery);
479
+ }
480
+ });
397
481
  }
398
- query = query.whereExists(whereCb(subQuery));
399
- break;
400
482
  }
401
483
  }
402
484
  return query;
@@ -410,14 +492,14 @@ class InternalBuilder {
410
492
  filters = this.parseFilters(Object.assign({}, filters));
411
493
  const aliases = this.query.tableAliases;
412
494
  // if all or specified in filters, then everything is an or
413
- const allOr = filters.allOr;
495
+ const shouldOr = filters.allOr;
414
496
  const isSqlite = this.client === types_1.SqlClient.SQL_LITE;
415
497
  const tableName = isSqlite ? this.table._id : this.table.name;
416
498
  function getTableAlias(name) {
417
499
  const alias = aliases === null || aliases === void 0 ? void 0 : aliases[name];
418
500
  return alias || name;
419
501
  }
420
- function iterate(structure, fn, complexKeyFn) {
502
+ function iterate(structure, operation, fn, complexKeyFn) {
421
503
  const handleRelationship = (q, key, value) => {
422
504
  const [filterTableName, ...otherProperties] = key.split(".");
423
505
  const property = otherProperties.join(".");
@@ -441,81 +523,100 @@ class InternalBuilder {
441
523
  query = fn(query, alias ? `${alias}.${updatedKey}` : updatedKey, value);
442
524
  }
443
525
  else if (shouldProcessRelationship) {
444
- if (allOr) {
526
+ if (shouldOr) {
445
527
  query = query.or;
446
528
  }
447
- query = builder.addRelationshipForFilter(query, updatedKey, q => {
529
+ query = builder.addRelationshipForFilter(query, allowEmptyRelationships[operation], updatedKey, (updatedKey, q) => {
448
530
  return handleRelationship(q, updatedKey, value);
449
531
  });
450
532
  }
451
533
  }
452
534
  }
453
535
  const like = (q, key, value) => {
454
- const fuzzyOr = filters === null || filters === void 0 ? void 0 : filters.fuzzyOr;
455
- const fnc = fuzzyOr || allOr ? "orWhere" : "where";
456
- // postgres supports ilike, nothing else does
457
- if (this.client === types_1.SqlClient.POSTGRES) {
458
- return q[fnc](key, "ilike", `%${value}%`);
536
+ if ((filters === null || filters === void 0 ? void 0 : filters.fuzzyOr) || shouldOr) {
537
+ q = q.or;
459
538
  }
460
- else {
461
- const rawFnc = `${fnc}Raw`;
462
- // @ts-ignore
463
- return q[rawFnc](`LOWER(${this.quotedIdentifier(key)}) LIKE ?`, [
539
+ if (this.client === types_1.SqlClient.ORACLE ||
540
+ this.client === types_1.SqlClient.SQL_LITE) {
541
+ return q.whereRaw(`LOWER(??) LIKE ?`, [
542
+ this.rawQuotedIdentifier(key),
464
543
  `%${value.toLowerCase()}%`,
465
544
  ]);
466
545
  }
546
+ return q.whereILike(
547
+ // @ts-expect-error knex types are wrong, raw is fine here
548
+ this.rawQuotedIdentifier(key), this.knex.raw("?", [`%${value}%`]));
467
549
  };
468
550
  const contains = (mode, any = false) => {
469
- const rawFnc = allOr ? "orWhereRaw" : "whereRaw";
470
- const not = mode === (filters === null || filters === void 0 ? void 0 : filters.notContains) ? "NOT " : "";
471
- function stringifyArray(value, quoteStyle = '"') {
472
- for (let i in value) {
473
- if (typeof value[i] === "string") {
474
- value[i] = `${quoteStyle}${value[i]}${quoteStyle}`;
475
- }
551
+ function addModifiers(q) {
552
+ if (shouldOr || mode === (filters === null || filters === void 0 ? void 0 : filters.containsAny)) {
553
+ q = q.or;
554
+ }
555
+ if (mode === (filters === null || filters === void 0 ? void 0 : filters.notContains)) {
556
+ q = q.not;
476
557
  }
477
- return `[${value.join(",")}]`;
558
+ return q;
478
559
  }
479
560
  if (this.client === types_1.SqlClient.POSTGRES) {
480
- iterate(mode, (q, key, value) => {
481
- const wrap = any ? "" : "'";
482
- const op = any ? "\\?| array" : "@>";
483
- const fieldNames = key.split(/\./g);
484
- const table = fieldNames[0];
485
- const col = fieldNames[1];
486
- return q[rawFnc](`${not}COALESCE("${table}"."${col}"::jsonb ${op} ${wrap}${stringifyArray(value, any ? "'" : '"')}${wrap}, FALSE)`);
561
+ iterate(mode, types_1.ArrayOperator.CONTAINS, (q, key, value) => {
562
+ q = addModifiers(q);
563
+ if (any) {
564
+ return q.whereRaw(`COALESCE(??::jsonb \\?| array??, FALSE)`, [
565
+ this.rawQuotedIdentifier(key),
566
+ this.knex.raw(stringifyArray(value, "'")),
567
+ ]);
568
+ }
569
+ else {
570
+ return q.whereRaw(`COALESCE(??::jsonb @> '??', FALSE)`, [
571
+ this.rawQuotedIdentifier(key),
572
+ this.knex.raw(stringifyArray(value)),
573
+ ]);
574
+ }
487
575
  });
488
576
  }
489
577
  else if (this.client === types_1.SqlClient.MY_SQL ||
490
578
  this.client === types_1.SqlClient.MARIADB) {
491
- const jsonFnc = any ? "JSON_OVERLAPS" : "JSON_CONTAINS";
492
- iterate(mode, (q, key, value) => {
493
- return q[rawFnc](`${not}COALESCE(${jsonFnc}(${key}, '${stringifyArray(value)}'), FALSE)`);
579
+ iterate(mode, types_1.ArrayOperator.CONTAINS, (q, key, value) => {
580
+ return addModifiers(q).whereRaw(`COALESCE(?(??, ?), FALSE)`, [
581
+ this.knex.raw(any ? "JSON_OVERLAPS" : "JSON_CONTAINS"),
582
+ this.rawQuotedIdentifier(key),
583
+ this.knex.raw(wrap(stringifyArray(value))),
584
+ ]);
494
585
  });
495
586
  }
496
587
  else {
497
- const andOr = mode === (filters === null || filters === void 0 ? void 0 : filters.containsAny) ? " OR " : " AND ";
498
- iterate(mode, (q, key, value) => {
499
- let statement = "";
500
- const identifier = this.quotedIdentifier(key);
501
- for (let i in value) {
502
- if (typeof value[i] === "string") {
503
- value[i] = `%"${value[i].toLowerCase()}"%`;
504
- }
505
- else {
506
- value[i] = `%${value[i]}%`;
507
- }
508
- statement += `${statement ? andOr : ""}COALESCE(LOWER(${identifier}), '') LIKE ?`;
509
- }
510
- if (statement === "") {
588
+ iterate(mode, types_1.ArrayOperator.CONTAINS, (q, key, value) => {
589
+ if (value.length === 0) {
511
590
  return q;
512
591
  }
513
- if (not) {
514
- return q[rawFnc](`(NOT (${statement}) OR ${identifier} IS NULL)`, value);
515
- }
516
- else {
517
- return q[rawFnc](statement, value);
518
- }
592
+ q = q.where(subQuery => {
593
+ if (mode === (filters === null || filters === void 0 ? void 0 : filters.notContains)) {
594
+ subQuery = subQuery.not;
595
+ }
596
+ subQuery = subQuery.where(subSubQuery => {
597
+ for (const elem of value) {
598
+ if (mode === (filters === null || filters === void 0 ? void 0 : filters.containsAny)) {
599
+ subSubQuery = subSubQuery.or;
600
+ }
601
+ else {
602
+ subSubQuery = subSubQuery.and;
603
+ }
604
+ const lower = typeof elem === "string" ? `"${elem.toLowerCase()}"` : elem;
605
+ subSubQuery = subSubQuery.whereLike(
606
+ // @ts-expect-error knex types are wrong, raw is fine here
607
+ this.knex.raw(`COALESCE(LOWER(??), '')`, [
608
+ this.rawQuotedIdentifier(key),
609
+ ]), `%${lower}%`);
610
+ }
611
+ });
612
+ if (mode === (filters === null || filters === void 0 ? void 0 : filters.notContains)) {
613
+ subQuery = subQuery.or.whereNull(
614
+ // @ts-expect-error knex types are wrong, raw is fine here
615
+ this.rawQuotedIdentifier(key));
616
+ }
617
+ return subQuery;
618
+ });
619
+ return q;
519
620
  });
520
621
  }
521
622
  };
@@ -536,51 +637,48 @@ class InternalBuilder {
536
637
  });
537
638
  }
538
639
  if (filters.oneOf) {
539
- const fnc = allOr ? "orWhereIn" : "whereIn";
540
- iterate(filters.oneOf, (q, key, array) => {
640
+ iterate(filters.oneOf, types_1.ArrayOperator.ONE_OF, (q, key, array) => {
641
+ if (shouldOr) {
642
+ q = q.or;
643
+ }
541
644
  if (this.client === types_1.SqlClient.ORACLE) {
645
+ // @ts-ignore
542
646
  key = this.convertClobs(key);
543
- array = Array.isArray(array) ? array : [array];
544
- const binding = new Array(array.length).fill("?").join(",");
545
- return q.whereRaw(`${key} IN (${binding})`, array);
546
- }
547
- else {
548
- return q[fnc](key, Array.isArray(array) ? array : [array]);
549
647
  }
648
+ return q.whereIn(key, Array.isArray(array) ? array : [array]);
550
649
  }, (q, key, array) => {
551
- if (this.client === types_1.SqlClient.ORACLE) {
552
- const keyStr = `(${key.map(k => this.convertClobs(k)).join(",")})`;
553
- const binding = `(${array
554
- .map((a) => `(${new Array(a.length).fill("?").join(",")})`)
555
- .join(",")})`;
556
- return q.whereRaw(`${keyStr} IN ${binding}`, array.flat());
650
+ if (shouldOr) {
651
+ q = q.or;
557
652
  }
558
- else {
559
- return q[fnc](key, Array.isArray(array) ? array : [array]);
653
+ if (this.client === types_1.SqlClient.ORACLE) {
654
+ // @ts-ignore
655
+ key = key.map(k => this.convertClobs(k));
560
656
  }
657
+ return q.whereIn(key, Array.isArray(array) ? array : [array]);
561
658
  });
562
659
  }
563
660
  if (filters.string) {
564
- iterate(filters.string, (q, key, value) => {
565
- const fnc = allOr ? "orWhere" : "where";
566
- // postgres supports ilike, nothing else does
567
- if (this.client === types_1.SqlClient.POSTGRES) {
568
- return q[fnc](key, "ilike", `${value}%`);
661
+ iterate(filters.string, types_1.BasicOperator.STRING, (q, key, value) => {
662
+ if (shouldOr) {
663
+ q = q.or;
569
664
  }
570
- else {
571
- const rawFnc = `${fnc}Raw`;
572
- // @ts-ignore
573
- return q[rawFnc](`LOWER(${this.quotedIdentifier(key)}) LIKE ?`, [
665
+ if (this.client === types_1.SqlClient.ORACLE ||
666
+ this.client === types_1.SqlClient.SQL_LITE) {
667
+ return q.whereRaw(`LOWER(??) LIKE ?`, [
668
+ this.rawQuotedIdentifier(key),
574
669
  `${value.toLowerCase()}%`,
575
670
  ]);
576
671
  }
672
+ else {
673
+ return q.whereILike(key, `${value}%`);
674
+ }
577
675
  });
578
676
  }
579
677
  if (filters.fuzzy) {
580
- iterate(filters.fuzzy, like);
678
+ iterate(filters.fuzzy, types_1.BasicOperator.FUZZY, like);
581
679
  }
582
680
  if (filters.range) {
583
- iterate(filters.range, (q, key, value) => {
681
+ iterate(filters.range, types_1.RangeOperator.RANGE, (q, key, value) => {
584
682
  const isEmptyObject = (val) => {
585
683
  return (val &&
586
684
  Object.keys(val).length === 0 &&
@@ -594,93 +692,106 @@ class InternalBuilder {
594
692
  }
595
693
  const lowValid = (0, utils_1.isValidFilter)(value.low), highValid = (0, utils_1.isValidFilter)(value.high);
596
694
  const schema = this.getFieldSchema(key);
695
+ let rawKey = key;
696
+ let high = value.high;
697
+ let low = value.low;
597
698
  if (this.client === types_1.SqlClient.ORACLE) {
598
- // @ts-ignore
599
- key = this.knex.raw(this.convertClobs(key));
699
+ rawKey = this.convertClobs(key);
700
+ }
701
+ else if (this.client === types_1.SqlClient.SQL_LITE &&
702
+ (schema === null || schema === void 0 ? void 0 : schema.type) === types_1.FieldType.BIGINT) {
703
+ rawKey = this.knex.raw("CAST(?? AS INTEGER)", [
704
+ this.rawQuotedIdentifier(key),
705
+ ]);
706
+ high = this.knex.raw("CAST(? AS INTEGER)", [value.high]);
707
+ low = this.knex.raw("CAST(? AS INTEGER)", [value.low]);
708
+ }
709
+ if (shouldOr) {
710
+ q = q.or;
600
711
  }
601
712
  if (lowValid && highValid) {
602
- if ((schema === null || schema === void 0 ? void 0 : schema.type) === types_1.FieldType.BIGINT &&
603
- this.client === types_1.SqlClient.SQL_LITE) {
604
- return q.whereRaw(`CAST(${key} AS INTEGER) BETWEEN CAST(? AS INTEGER) AND CAST(? AS INTEGER)`, [value.low, value.high]);
605
- }
606
- else {
607
- const fnc = allOr ? "orWhereBetween" : "whereBetween";
608
- return q[fnc](key, [value.low, value.high]);
609
- }
713
+ // @ts-expect-error knex types are wrong, raw is fine here
714
+ return q.whereBetween(rawKey, [low, high]);
610
715
  }
611
716
  else if (lowValid) {
612
- if ((schema === null || schema === void 0 ? void 0 : schema.type) === types_1.FieldType.BIGINT &&
613
- this.client === types_1.SqlClient.SQL_LITE) {
614
- return q.whereRaw(`CAST(${key} AS INTEGER) >= CAST(? AS INTEGER)`, [
615
- value.low,
616
- ]);
617
- }
618
- else {
619
- const fnc = allOr ? "orWhere" : "where";
620
- return q[fnc](key, ">=", value.low);
621
- }
717
+ // @ts-expect-error knex types are wrong, raw is fine here
718
+ return q.where(rawKey, ">=", low);
622
719
  }
623
720
  else if (highValid) {
624
- if ((schema === null || schema === void 0 ? void 0 : schema.type) === types_1.FieldType.BIGINT &&
625
- this.client === types_1.SqlClient.SQL_LITE) {
626
- return q.whereRaw(`CAST(${key} AS INTEGER) <= CAST(? AS INTEGER)`, [
627
- value.high,
628
- ]);
629
- }
630
- else {
631
- const fnc = allOr ? "orWhere" : "where";
632
- return q[fnc](key, "<=", value.high);
633
- }
721
+ // @ts-expect-error knex types are wrong, raw is fine here
722
+ return q.where(rawKey, "<=", high);
634
723
  }
635
724
  return q;
636
725
  });
637
726
  }
638
727
  if (filters.equal) {
639
- iterate(filters.equal, (q, key, value) => {
640
- const fnc = allOr ? "orWhereRaw" : "whereRaw";
728
+ iterate(filters.equal, types_1.BasicOperator.EQUAL, (q, key, value) => {
729
+ if (shouldOr) {
730
+ q = q.or;
731
+ }
641
732
  if (this.client === types_1.SqlClient.MS_SQL) {
642
- return q[fnc](`CASE WHEN ${this.quotedIdentifier(key)} = ? THEN 1 ELSE 0 END = 1`, [value]);
733
+ return q.whereRaw(`CASE WHEN ?? = ? THEN 1 ELSE 0 END = 1`, [
734
+ this.rawQuotedIdentifier(key),
735
+ value,
736
+ ]);
643
737
  }
644
738
  else if (this.client === types_1.SqlClient.ORACLE) {
645
739
  const identifier = this.convertClobs(key);
646
- return q[fnc](`(${identifier} IS NOT NULL AND ${identifier} = ?)`, [
647
- value,
648
- ]);
740
+ return q.where(subq =>
741
+ // @ts-expect-error knex types are wrong, raw is fine here
742
+ subq.whereNotNull(identifier).andWhere(identifier, value));
649
743
  }
650
744
  else {
651
- return q[fnc](`COALESCE(${this.quotedIdentifier(key)} = ?, FALSE)`, [
745
+ return q.whereRaw(`COALESCE(?? = ?, FALSE)`, [
746
+ this.rawQuotedIdentifier(key),
652
747
  value,
653
748
  ]);
654
749
  }
655
750
  });
656
751
  }
657
752
  if (filters.notEqual) {
658
- iterate(filters.notEqual, (q, key, value) => {
659
- const fnc = allOr ? "orWhereRaw" : "whereRaw";
753
+ iterate(filters.notEqual, types_1.BasicOperator.NOT_EQUAL, (q, key, value) => {
754
+ if (shouldOr) {
755
+ q = q.or;
756
+ }
660
757
  if (this.client === types_1.SqlClient.MS_SQL) {
661
- return q[fnc](`CASE WHEN ${this.quotedIdentifier(key)} = ? THEN 1 ELSE 0 END = 0`, [value]);
758
+ return q.whereRaw(`CASE WHEN ?? = ? THEN 1 ELSE 0 END = 0`, [
759
+ this.rawQuotedIdentifier(key),
760
+ value,
761
+ ]);
662
762
  }
663
763
  else if (this.client === types_1.SqlClient.ORACLE) {
664
764
  const identifier = this.convertClobs(key);
665
- return q[fnc](`(${identifier} IS NOT NULL AND ${identifier} != ?) OR ${identifier} IS NULL`, [value]);
765
+ return (q
766
+ .where(subq => subq.not
767
+ // @ts-expect-error knex types are wrong, raw is fine here
768
+ .whereNull(identifier)
769
+ .and.where(identifier, "!=", value))
770
+ // @ts-expect-error knex types are wrong, raw is fine here
771
+ .or.whereNull(identifier));
666
772
  }
667
773
  else {
668
- return q[fnc](`COALESCE(${this.quotedIdentifier(key)} != ?, TRUE)`, [
774
+ return q.whereRaw(`COALESCE(?? != ?, TRUE)`, [
775
+ this.rawQuotedIdentifier(key),
669
776
  value,
670
777
  ]);
671
778
  }
672
779
  });
673
780
  }
674
781
  if (filters.empty) {
675
- iterate(filters.empty, (q, key) => {
676
- const fnc = allOr ? "orWhereNull" : "whereNull";
677
- return q[fnc](key);
782
+ iterate(filters.empty, types_1.BasicOperator.EMPTY, (q, key) => {
783
+ if (shouldOr) {
784
+ q = q.or;
785
+ }
786
+ return q.whereNull(key);
678
787
  });
679
788
  }
680
789
  if (filters.notEmpty) {
681
- iterate(filters.notEmpty, (q, key) => {
682
- const fnc = allOr ? "orWhereNotNull" : "whereNotNull";
683
- return q[fnc](key);
790
+ iterate(filters.notEmpty, types_1.BasicOperator.NOT_EMPTY, (q, key) => {
791
+ if (shouldOr) {
792
+ q = q.or;
793
+ }
794
+ return q.whereNotNull(key);
684
795
  });
685
796
  }
686
797
  if (filters.contains) {
@@ -753,9 +864,7 @@ class InternalBuilder {
753
864
  if (this.client === types_1.SqlClient.ORACLE) {
754
865
  const groupByFields = qualifiedFields.map(field => this.convertClobs(field));
755
866
  const selectFields = qualifiedFields.map(field => this.convertClobs(field, { forSelect: true }));
756
- query = query
757
- .groupByRaw(groupByFields.join(", "))
758
- .select(this.knex.raw(selectFields.join(", ")));
867
+ query = query.groupBy(groupByFields).select(selectFields);
759
868
  }
760
869
  else {
761
870
  query = query.groupBy(qualifiedFields).select(qualifiedFields);
@@ -767,7 +876,10 @@ class InternalBuilder {
767
876
  if ("distinct" in aggregation && aggregation.distinct) {
768
877
  if (this.client === types_1.SqlClient.ORACLE) {
769
878
  const field = this.convertClobs(`${tableName}.${aggregation.field}`);
770
- query = query.select(this.knex.raw(`COUNT(DISTINCT ${field}) as ${this.quotedIdentifier(aggregation.name)}`));
879
+ query = query.select(this.knex.raw(`COUNT(DISTINCT ??) as ??`, [
880
+ field,
881
+ aggregation.name,
882
+ ]));
771
883
  }
772
884
  else {
773
885
  query = query.countDistinct(`${tableName}.${aggregation.field} as ${aggregation.name}`);
@@ -826,7 +938,11 @@ class InternalBuilder {
826
938
  else {
827
939
  let composite = `${aliased}.${key}`;
828
940
  if (this.client === types_1.SqlClient.ORACLE) {
829
- query = query.orderByRaw(`${this.convertClobs(composite)} ${direction} nulls ${nulls}`);
941
+ query = query.orderByRaw(`?? ?? nulls ??`, [
942
+ this.convertClobs(composite),
943
+ this.knex.raw(direction),
944
+ this.knex.raw(nulls),
945
+ ]);
830
946
  }
831
947
  else {
832
948
  query = query.orderBy(composite, direction, nulls);
@@ -851,18 +967,21 @@ class InternalBuilder {
851
967
  }
852
968
  buildJsonField(field) {
853
969
  const parts = field.split(".");
854
- let tableField, unaliased;
970
+ let unaliased;
971
+ let tableField;
855
972
  if (parts.length > 1) {
856
973
  const alias = parts.shift();
857
974
  unaliased = parts.join(".");
858
- tableField = `${this.quote(alias)}.${this.quote(unaliased)}`;
975
+ tableField = `${alias}.${unaliased}`;
859
976
  }
860
977
  else {
861
978
  unaliased = parts.join(".");
862
- tableField = this.quote(unaliased);
979
+ tableField = unaliased;
863
980
  }
864
981
  const separator = this.client === types_1.SqlClient.ORACLE ? " VALUE " : ",";
865
- return `'${unaliased}'${separator}${tableField}`;
982
+ return this.knex
983
+ .raw(`?${separator}??`, [unaliased, this.rawQuotedIdentifier(tableField)])
984
+ .toString();
866
985
  }
867
986
  maxFunctionParameters() {
868
987
  // functions like say json_build_object() in SQL have a limit as to how many can be performed
@@ -931,11 +1050,11 @@ class InternalBuilder {
931
1050
  });
932
1051
  }
933
1052
  // add the correlation to the overall query
934
- subQuery = subQuery.where(correlatedTo, "=", knex.raw(this.quotedIdentifier(correlatedFrom)));
1053
+ subQuery = subQuery.where(correlatedTo, "=", this.rawQuotedIdentifier(correlatedFrom));
935
1054
  const standardWrap = (select) => {
936
1055
  subQuery = subQuery.select(`${toAlias}.*`).limit(getRelationshipLimit());
937
1056
  // @ts-ignore - the from alias syntax isn't in Knex typing
938
- return knex.select(knex.raw(select)).from({
1057
+ return knex.select(select).from({
939
1058
  [toAlias]: subQuery,
940
1059
  });
941
1060
  };
@@ -944,10 +1063,10 @@ class InternalBuilder {
944
1063
  case types_1.SqlClient.SQL_LITE:
945
1064
  // need to check the junction table document is to the right column, this is just for SQS
946
1065
  subQuery = this.addJoinFieldCheck(subQuery, relationship);
947
- wrapperQuery = standardWrap(`json_group_array(json_object(${fieldList}))`);
1066
+ wrapperQuery = standardWrap(this.knex.raw(`json_group_array(json_object(${fieldList}))`));
948
1067
  break;
949
1068
  case types_1.SqlClient.POSTGRES:
950
- wrapperQuery = standardWrap(`json_agg(json_build_object(${fieldList}))`);
1069
+ wrapperQuery = standardWrap(this.knex.raw(`json_agg(json_build_object(${fieldList}))`));
951
1070
  break;
952
1071
  case types_1.SqlClient.MARIADB:
953
1072
  // can't use the standard wrap due to correlated sub-query limitations in MariaDB
@@ -955,18 +1074,20 @@ class InternalBuilder {
955
1074
  break;
956
1075
  case types_1.SqlClient.MY_SQL:
957
1076
  case types_1.SqlClient.ORACLE:
958
- wrapperQuery = standardWrap(`json_arrayagg(json_object(${fieldList}))`);
1077
+ wrapperQuery = standardWrap(this.knex.raw(`json_arrayagg(json_object(${fieldList}))`));
959
1078
  break;
960
- case types_1.SqlClient.MS_SQL:
961
- wrapperQuery = knex.raw(`(SELECT ${this.quote(toAlias)} = (${knex
1079
+ case types_1.SqlClient.MS_SQL: {
1080
+ const comparatorQuery = knex
962
1081
  .select(`${fromAlias}.*`)
963
1082
  // @ts-ignore - from alias syntax not TS supported
964
1083
  .from({
965
1084
  [fromAlias]: subQuery
966
1085
  .select(`${toAlias}.*`)
967
1086
  .limit(getRelationshipLimit()),
968
- })} FOR JSON PATH))`);
1087
+ });
1088
+ wrapperQuery = knex.raw(`(SELECT ?? = (${comparatorQuery} FOR JSON PATH))`, [this.rawQuotedIdentifier(toAlias)]);
969
1089
  break;
1090
+ }
970
1091
  default:
971
1092
  throw new Error(`JSON relationships not implement for ${sqlClient}`);
972
1093
  }
@@ -990,11 +1111,9 @@ class InternalBuilder {
990
1111
  })
991
1112
  : undefined;
992
1113
  if (!throughTable) {
993
- // @ts-ignore
994
1114
  query = query.leftJoin(toTableWithSchema, function () {
995
1115
  for (let relationship of columns) {
996
1116
  const from = relationship.from, to = relationship.to;
997
- // @ts-ignore
998
1117
  this.orOn(`${fromAlias}.${from}`, "=", `${toAlias}.${to}`);
999
1118
  }
1000
1119
  });
@@ -1006,7 +1125,6 @@ class InternalBuilder {
1006
1125
  for (let relationship of columns) {
1007
1126
  const fromPrimary = relationship.fromPrimary;
1008
1127
  const from = relationship.from;
1009
- // @ts-ignore
1010
1128
  this.orOn(`${fromAlias}.${fromPrimary}`, "=", `${throughAlias}.${from}`);
1011
1129
  }
1012
1130
  })
@@ -1014,7 +1132,6 @@ class InternalBuilder {
1014
1132
  for (let relationship of columns) {
1015
1133
  const toPrimary = relationship.toPrimary;
1016
1134
  const to = relationship.to;
1017
- // @ts-ignore
1018
1135
  this.orOn(`${toAlias}.${toPrimary}`, `${throughAlias}.${to}`);
1019
1136
  }
1020
1137
  });