@warlock.js/cascade 5.19.1 → 5.21.0
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/CHANGELOG.md +36 -1
- package/cjs/index.cjs +2356 -1039
- package/cjs/index.cjs.map +1 -1
- package/esm/context/database-transaction-context.d.mts +10 -0
- package/esm/context/database-transaction-context.d.mts.map +1 -1
- package/esm/context/database-transaction-context.mjs +17 -0
- package/esm/context/database-transaction-context.mjs.map +1 -1
- package/esm/contracts/query-builder.contract.d.mts +30 -3
- package/esm/contracts/query-builder.contract.d.mts.map +1 -1
- package/esm/data-source/data-source-registry.d.mts +33 -1
- package/esm/data-source/data-source-registry.d.mts.map +1 -1
- package/esm/data-source/data-source-registry.mjs +75 -5
- package/esm/data-source/data-source-registry.mjs.map +1 -1
- package/esm/database-dirty-tracker.d.mts +5 -0
- package/esm/database-dirty-tracker.d.mts.map +1 -1
- package/esm/database-dirty-tracker.mjs +16 -0
- package/esm/database-dirty-tracker.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.d.mts +12 -0
- package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.mjs +58 -19
- package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.d.mts +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.mjs +15 -18
- package/esm/drivers/mongodb/mongodb-migration-driver.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts +13 -3
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.mjs +72 -19
- package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts +6 -0
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.mjs +128 -30
- package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/index.d.mts +2 -3
- package/esm/drivers/postgres/index.mjs +1 -2
- package/esm/drivers/postgres/postgres-blueprint.mjs +4 -4
- package/esm/drivers/postgres/postgres-blueprint.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.d.mts +5 -1
- package/esm/drivers/postgres/postgres-dialect.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.mjs +38 -8
- package/esm/drivers/postgres/postgres-dialect.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-driver.d.mts +52 -29
- package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-driver.mjs +196 -58
- package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-migration-driver.d.mts +2 -1
- package/esm/drivers/postgres/postgres-migration-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-migration-driver.mjs +8 -6
- package/esm/drivers/postgres/postgres-migration-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.d.mts +18 -7
- package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.mjs +146 -31
- package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.d.mts +23 -1
- package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.mjs +160 -43
- package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-sql-serializer.mjs +3 -2
- package/esm/drivers/postgres/postgres-sql-serializer.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-type-converters.mjs +57 -0
- package/esm/drivers/postgres/postgres-type-converters.mjs.map +1 -0
- package/esm/events/model-events.d.mts +1 -1
- package/esm/events/model-events.d.mts.map +1 -1
- package/esm/events/model-events.mjs.map +1 -1
- package/esm/index.d.mts +6 -6
- package/esm/index.mjs +5 -5
- package/esm/migration/column-builder.d.mts.map +1 -1
- package/esm/migration/column-builder.mjs +2 -2
- package/esm/migration/column-builder.mjs.map +1 -1
- package/esm/migration/index.d.mts +2 -2
- package/esm/migration/index.mjs +1 -1
- package/esm/migration/migration-order.mjs +5 -2
- package/esm/migration/migration-order.mjs.map +1 -1
- package/esm/migration/migration-runner.d.mts +50 -5
- package/esm/migration/migration-runner.d.mts.map +1 -1
- package/esm/migration/migration-runner.mjs +370 -147
- package/esm/migration/migration-runner.mjs.map +1 -1
- package/esm/migration/migration.d.mts +36 -3
- package/esm/migration/migration.d.mts.map +1 -1
- package/esm/migration/migration.mjs +62 -5
- package/esm/migration/migration.mjs.map +1 -1
- package/esm/model/methods/accessor-methods.mjs +1 -3
- package/esm/model/methods/accessor-methods.mjs.map +1 -1
- package/esm/model/methods/delete-methods.mjs +6 -2
- package/esm/model/methods/delete-methods.mjs.map +1 -1
- package/esm/model/methods/hydration-methods.mjs +16 -2
- package/esm/model/methods/hydration-methods.mjs.map +1 -1
- package/esm/model/methods/instance-event-methods.mjs +2 -1
- package/esm/model/methods/instance-event-methods.mjs.map +1 -1
- package/esm/model/methods/meta-methods.mjs +53 -16
- package/esm/model/methods/meta-methods.mjs.map +1 -1
- package/esm/model/methods/query-methods.mjs +86 -15
- package/esm/model/methods/query-methods.mjs.map +1 -1
- package/esm/model/methods/static-event-methods.mjs +16 -1
- package/esm/model/methods/static-event-methods.mjs.map +1 -1
- package/esm/model/methods/write-methods.d.mts.map +1 -1
- package/esm/model/methods/write-methods.mjs +50 -50
- package/esm/model/methods/write-methods.mjs.map +1 -1
- package/esm/model/model.d.mts +16 -1
- package/esm/model/model.d.mts.map +1 -1
- package/esm/model/model.mjs +39 -5
- package/esm/model/model.mjs.map +1 -1
- package/esm/model/register-model.d.mts +6 -1
- package/esm/model/register-model.d.mts.map +1 -1
- package/esm/model/register-model.mjs +16 -1
- package/esm/model/register-model.mjs.map +1 -1
- package/esm/query-builder/query-builder.d.mts +21 -2
- package/esm/query-builder/query-builder.d.mts.map +1 -1
- package/esm/query-builder/query-builder.mjs +97 -20
- package/esm/query-builder/query-builder.mjs.map +1 -1
- package/esm/relations/key-conventions.mjs +13 -3
- package/esm/relations/key-conventions.mjs.map +1 -1
- package/esm/relations/pivot-operations.mjs +1 -1
- package/esm/relations/relation-loader.d.mts +15 -5
- package/esm/relations/relation-loader.d.mts.map +1 -1
- package/esm/relations/relation-loader.mjs +51 -16
- package/esm/relations/relation-loader.mjs.map +1 -1
- package/esm/remover/database-remover.d.mts.map +1 -1
- package/esm/remover/database-remover.mjs +21 -7
- package/esm/remover/database-remover.mjs.map +1 -1
- package/esm/restorer/database-restorer.d.mts +12 -2
- package/esm/restorer/database-restorer.d.mts.map +1 -1
- package/esm/restorer/database-restorer.mjs +43 -13
- package/esm/restorer/database-restorer.mjs.map +1 -1
- package/esm/sync/model-events.mjs +63 -3
- package/esm/sync/model-events.mjs.map +1 -1
- package/esm/sync/model-sync-operation.d.mts.map +1 -1
- package/esm/sync/model-sync-operation.mjs +3 -2
- package/esm/sync/model-sync-operation.mjs.map +1 -1
- package/esm/sync/sync-manager.d.mts +3 -42
- package/esm/sync/sync-manager.d.mts.map +1 -1
- package/esm/sync/sync-manager.mjs +37 -126
- package/esm/sync/sync-manager.mjs.map +1 -1
- package/esm/transactions/after-commit.d.mts +24 -0
- package/esm/transactions/after-commit.d.mts.map +1 -0
- package/esm/transactions/after-commit.mjs +47 -0
- package/esm/transactions/after-commit.mjs.map +1 -0
- package/esm/utils/connect-to-database.d.mts.map +1 -1
- package/esm/utils/connect-to-database.mjs +4 -4
- package/esm/utils/connect-to-database.mjs.map +1 -1
- package/esm/utils/database-writer.utils.d.mts +1 -1
- package/esm/utils/database-writer.utils.d.mts.map +1 -1
- package/esm/utils/database-writer.utils.mjs +1 -0
- package/esm/utils/database-writer.utils.mjs.map +1 -1
- package/esm/utils/define-model.d.mts.map +1 -1
- package/esm/utils/define-model.mjs +2 -10
- package/esm/utils/define-model.mjs.map +1 -1
- package/esm/utils/is-valid-date-value.mjs +3 -2
- package/esm/utils/is-valid-date-value.mjs.map +1 -1
- package/esm/utils/once-connected.d.mts.map +1 -1
- package/esm/utils/once-connected.mjs +12 -2
- package/esm/utils/once-connected.mjs.map +1 -1
- package/esm/validation/mutators/embed-mutator.mjs +19 -11
- package/esm/validation/mutators/embed-mutator.mjs.map +1 -1
- package/esm/validation/plugins/embed-validator-plugin.d.mts +2 -2
- package/esm/validation/plugins/embed-validator-plugin.d.mts.map +1 -1
- package/esm/validation/plugins/embed-validator-plugin.mjs +8 -2
- package/esm/validation/plugins/embed-validator-plugin.mjs.map +1 -1
- package/esm/validation/rules/database-model-rule.mjs +6 -5
- package/esm/validation/rules/database-model-rule.mjs.map +1 -1
- package/esm/validation/rules/exists-rule.mjs +6 -4
- package/esm/validation/rules/exists-rule.mjs.map +1 -1
- package/esm/validation/rules/unique-rule.mjs +5 -2
- package/esm/validation/rules/unique-rule.mjs.map +1 -1
- package/esm/validation/validators/embed-validator.d.mts.map +1 -1
- package/esm/validation/validators/embed-validator.mjs +1 -1
- package/esm/validation/validators/embed-validator.mjs.map +1 -1
- package/esm/writer/database-writer.d.mts +27 -0
- package/esm/writer/database-writer.d.mts.map +1 -1
- package/esm/writer/database-writer.mjs +84 -17
- package/esm/writer/database-writer.mjs.map +1 -1
- package/llms-full.txt +2783 -2766
- package/llms.txt +15 -15
- package/package.json +6 -6
- package/skills/aggregate-data/SKILL.md +165 -165
- package/skills/alter-migration/SKILL.md +531 -531
- package/skills/configure-delete-strategy/SKILL.md +130 -130
- package/skills/define-model/SKILL.md +205 -205
- package/skills/define-relations/SKILL.md +171 -171
- package/skills/index.md +23 -0
- package/skills/manage-data-sources/SKILL.md +140 -140
- package/skills/manage-transactions/SKILL.md +208 -191
- package/skills/paginate-results/SKILL.md +122 -122
- package/skills/perform-atomic-ops/SKILL.md +156 -156
- package/skills/query-data/SKILL.md +213 -213
- package/skills/run-cascade-cli/SKILL.md +125 -125
- package/skills/search-by-vector/SKILL.md +127 -127
- package/skills/subscribe-to-model-events/SKILL.md +148 -148
- package/skills/track-changes/SKILL.md +127 -127
- package/skills/write-migration/SKILL.md +206 -206
- package/esm/drivers/postgres/postgres-sync-adapter.d.mts +0 -79
- package/esm/drivers/postgres/postgres-sync-adapter.d.mts.map +0 -1
- package/esm/drivers/postgres/postgres-sync-adapter.mjs +0 -162
- package/esm/drivers/postgres/postgres-sync-adapter.mjs.map +0 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { PostgresDialect } from "./postgres-dialect.mjs";
|
|
1
|
+
import { PostgresDialect, escapeSqlLiteral } from "./postgres-dialect.mjs";
|
|
2
2
|
|
|
3
3
|
//#region ../cascade/src/drivers/postgres/postgres-query-parser.ts
|
|
4
|
+
/** Allowed `::cast` type names, e.g. `int`, `numeric(10,2)`, `text[]`. */
|
|
5
|
+
const SAFE_CAST_PATTERN = /^[a-z_][a-z0-9_ ]*(\(\d+(,\d+)?\))?(\[\])?$/i;
|
|
6
|
+
/** Allowed orderBy direction, with optional NULLS placement. */
|
|
7
|
+
const SAFE_DIRECTION_PATTERN = /^(asc|desc)(\s+nulls\s+(first|last))?$/i;
|
|
4
8
|
/**
|
|
5
9
|
* PostgreSQL Query Parser.
|
|
6
10
|
*
|
|
@@ -188,12 +192,27 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
188
192
|
case "whereNotIn":
|
|
189
193
|
this.processWhereIn(data, true);
|
|
190
194
|
break;
|
|
195
|
+
case "orWhereIn":
|
|
196
|
+
this.processWhereIn(data, false, "OR");
|
|
197
|
+
break;
|
|
198
|
+
case "orWhereNotIn":
|
|
199
|
+
this.processWhereIn(data, true, "OR");
|
|
200
|
+
break;
|
|
191
201
|
case "whereNull":
|
|
192
202
|
this.processWhereNull(data, false);
|
|
193
203
|
break;
|
|
194
204
|
case "whereNotNull":
|
|
195
205
|
this.processWhereNull(data, true);
|
|
196
206
|
break;
|
|
207
|
+
case "orWhereNull":
|
|
208
|
+
this.processWhereNull(data, false, "OR");
|
|
209
|
+
break;
|
|
210
|
+
case "orWhereNotNull":
|
|
211
|
+
this.processWhereNull(data, true, "OR");
|
|
212
|
+
break;
|
|
213
|
+
case "orWhereBetween":
|
|
214
|
+
this.processWhereBetween(data, false, "OR");
|
|
215
|
+
break;
|
|
197
216
|
case "whereBetween":
|
|
198
217
|
this.processWhereBetween(data, false);
|
|
199
218
|
break;
|
|
@@ -221,6 +240,30 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
221
240
|
case "whereFullText":
|
|
222
241
|
this.processWhereFullText(data);
|
|
223
242
|
break;
|
|
243
|
+
case "whereNot":
|
|
244
|
+
this.processNested(data.nested, "AND", true, type);
|
|
245
|
+
break;
|
|
246
|
+
case "orWhereNot":
|
|
247
|
+
this.processNested(data.nested, "OR", true, type);
|
|
248
|
+
break;
|
|
249
|
+
case "whereExists":
|
|
250
|
+
this.processNested(data.subquery, "AND", false, type);
|
|
251
|
+
break;
|
|
252
|
+
case "whereNotExists":
|
|
253
|
+
this.processNested(data.subquery, "AND", true, type);
|
|
254
|
+
break;
|
|
255
|
+
case "whereDate":
|
|
256
|
+
this.processWhereDate(data, "=");
|
|
257
|
+
break;
|
|
258
|
+
case "whereDateBefore":
|
|
259
|
+
this.processWhereDate(data, "<");
|
|
260
|
+
break;
|
|
261
|
+
case "whereDateAfter":
|
|
262
|
+
this.processWhereDate(data, ">");
|
|
263
|
+
break;
|
|
264
|
+
case "whereDateBetween":
|
|
265
|
+
this.processWhereDateBetween(data);
|
|
266
|
+
break;
|
|
224
267
|
case "select":
|
|
225
268
|
this.processSelect(data);
|
|
226
269
|
break;
|
|
@@ -286,7 +329,9 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
286
329
|
case "selectRelatedColumns":
|
|
287
330
|
this.processSelectRelatedColumns(data);
|
|
288
331
|
break;
|
|
289
|
-
|
|
332
|
+
case "cursor":
|
|
333
|
+
case "joinWith": break;
|
|
334
|
+
default: throw new Error(`PostgresQueryParser: unsupported operation type "${type}"`);
|
|
290
335
|
}
|
|
291
336
|
}
|
|
292
337
|
/**
|
|
@@ -369,40 +414,40 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
369
414
|
* single placeholder (between, in, like-variants, exists, etc.).
|
|
370
415
|
*/
|
|
371
416
|
processWhere(data, boolean) {
|
|
417
|
+
if (data.nested !== void 0) return this.processNested(data.nested, boolean, false, "where");
|
|
372
418
|
const field = data.field;
|
|
373
419
|
const operator = data.operator ?? "=";
|
|
374
420
|
const value = data.value;
|
|
421
|
+
const range = value;
|
|
422
|
+
const like = {
|
|
423
|
+
field,
|
|
424
|
+
pattern: value
|
|
425
|
+
};
|
|
375
426
|
switch (operator) {
|
|
376
427
|
case "between": return this.processWhereBetween({
|
|
377
428
|
field,
|
|
378
|
-
range
|
|
379
|
-
}, false);
|
|
429
|
+
range
|
|
430
|
+
}, false, boolean);
|
|
380
431
|
case "notBetween": return this.processWhereBetween({
|
|
381
432
|
field,
|
|
382
|
-
range
|
|
383
|
-
}, true);
|
|
433
|
+
range
|
|
434
|
+
}, true, boolean);
|
|
384
435
|
case "in": return this.processWhereIn({
|
|
385
436
|
field,
|
|
386
437
|
values: value
|
|
387
|
-
}, false);
|
|
438
|
+
}, false, boolean);
|
|
388
439
|
case "notIn": return this.processWhereIn({
|
|
389
440
|
field,
|
|
390
441
|
values: value
|
|
391
|
-
}, true);
|
|
442
|
+
}, true, boolean);
|
|
392
443
|
case "like":
|
|
393
|
-
case "ilike":
|
|
444
|
+
case "ilike": return this.processWhereLike(like, false, boolean);
|
|
394
445
|
case "startsWith":
|
|
395
|
-
case "endsWith": return this.processWhereLike(
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
case "
|
|
400
|
-
case "notStartsWith":
|
|
401
|
-
case "notEndsWith": return this.processWhereLike({
|
|
402
|
-
field,
|
|
403
|
-
pattern: value
|
|
404
|
-
}, true);
|
|
405
|
-
case "exists": return this.addWhereClause(`EXISTS (${value})`, boolean);
|
|
446
|
+
case "endsWith": return this.processWhereLike(like, false, boolean, operator);
|
|
447
|
+
case "notLike": return this.processWhereLike(like, true, boolean);
|
|
448
|
+
case "notStartsWith": return this.processWhereLike(like, true, boolean, "startsWith");
|
|
449
|
+
case "notEndsWith": return this.processWhereLike(like, true, boolean, "endsWith");
|
|
450
|
+
case "exists": throw new Error("PostgresQueryParser: where(field, \"exists\", value) does not accept a raw SQL string; use a query builder or callback");
|
|
406
451
|
}
|
|
407
452
|
const quotedField = this.parseColumnIdentifier(field, this.table, this.alias);
|
|
408
453
|
if (value === null) {
|
|
@@ -413,6 +458,42 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
413
458
|
this.addWhereClause(`${quotedField} ${this.mapOperator(operator)} ${placeholder}`, boolean);
|
|
414
459
|
}
|
|
415
460
|
/**
|
|
461
|
+
* Process a nested group of operations as `(<group>)` / `NOT (<group>)`.
|
|
462
|
+
*
|
|
463
|
+
* The group is parsed by a sub-parser; its params are renumbered into this
|
|
464
|
+
* parser's namespace.
|
|
465
|
+
*/
|
|
466
|
+
processNested(nested, boolean, negate, opType) {
|
|
467
|
+
const subParser = new PostgresQueryParser({
|
|
468
|
+
table: this.table,
|
|
469
|
+
alias: this.alias,
|
|
470
|
+
dialect: this.dialect,
|
|
471
|
+
operations: nested ?? []
|
|
472
|
+
});
|
|
473
|
+
subParser.parse();
|
|
474
|
+
if (subParser.whereClauses.length === 0) throw new Error(`PostgresQueryParser: "${opType}" received an empty condition group`);
|
|
475
|
+
const group = `(${this.absorbSubParserParams(subParser)(subParser.whereClauses.join(" "))})`;
|
|
476
|
+
this.addWhereClause(negate ? `NOT ${group}` : group, boolean);
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Compare the date portion of a column (time ignored).
|
|
480
|
+
*/
|
|
481
|
+
processWhereDate(data, operator) {
|
|
482
|
+
const quotedField = this.parseColumnIdentifier(data.field, this.table, this.alias);
|
|
483
|
+
const placeholder = this.addParam(data.value);
|
|
484
|
+
this.addWhereClause(`${quotedField}::date ${operator} ${placeholder}::date`, "AND");
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Date portion of a column within an inclusive [from, to] range.
|
|
488
|
+
*/
|
|
489
|
+
processWhereDateBetween(data) {
|
|
490
|
+
const range = data.range;
|
|
491
|
+
const quotedField = this.parseColumnIdentifier(data.field, this.table, this.alias);
|
|
492
|
+
const from = this.addParam(range[0]);
|
|
493
|
+
const to = this.addParam(range[1]);
|
|
494
|
+
this.addWhereClause(`${quotedField}::date BETWEEN ${from}::date AND ${to}::date`, "AND");
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
416
497
|
* Process a raw WHERE operation.
|
|
417
498
|
*/
|
|
418
499
|
processWhereRaw(data, boolean) {
|
|
@@ -425,53 +506,72 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
425
506
|
/**
|
|
426
507
|
* Process WHERE IN / NOT IN.
|
|
427
508
|
*/
|
|
428
|
-
processWhereIn(data, negate) {
|
|
509
|
+
processWhereIn(data, negate, boolean = "AND") {
|
|
429
510
|
const field = data.field;
|
|
430
511
|
const values = data.values;
|
|
431
512
|
const quotedField = this.parseColumnIdentifier(field, this.table, this.alias);
|
|
432
513
|
const operator = negate ? "!= ALL" : "= ANY";
|
|
433
514
|
const placeholder = this.addParam(values);
|
|
434
|
-
this.addWhereClause(`${quotedField} ${operator}(${placeholder})`,
|
|
515
|
+
this.addWhereClause(`${quotedField} ${operator}(${placeholder})`, boolean);
|
|
435
516
|
}
|
|
436
517
|
/**
|
|
437
518
|
* Process WHERE NULL / NOT NULL.
|
|
438
519
|
*/
|
|
439
|
-
processWhereNull(data, negate) {
|
|
520
|
+
processWhereNull(data, negate, boolean = "AND") {
|
|
440
521
|
const field = data.field;
|
|
441
522
|
const quotedField = this.parseColumnIdentifier(field, this.table, this.alias);
|
|
442
523
|
const clause = negate ? `${quotedField} IS NOT NULL` : `${quotedField} IS NULL`;
|
|
443
|
-
this.addWhereClause(clause,
|
|
524
|
+
this.addWhereClause(clause, boolean);
|
|
444
525
|
}
|
|
445
526
|
/**
|
|
446
527
|
* Process WHERE BETWEEN / NOT BETWEEN.
|
|
447
528
|
*/
|
|
448
|
-
processWhereBetween(data, negate) {
|
|
529
|
+
processWhereBetween(data, negate, boolean = "AND") {
|
|
449
530
|
const field = data.field;
|
|
450
531
|
const range = data.range;
|
|
451
532
|
const quotedField = this.parseColumnIdentifier(field, this.table, this.alias);
|
|
452
533
|
const placeholder1 = this.addParam(range[0]);
|
|
453
534
|
const placeholder2 = this.addParam(range[1]);
|
|
454
535
|
const keyword = negate ? "NOT BETWEEN" : "BETWEEN";
|
|
455
|
-
this.addWhereClause(`${quotedField} ${keyword} ${placeholder1} AND ${placeholder2}`,
|
|
536
|
+
this.addWhereClause(`${quotedField} ${keyword} ${placeholder1} AND ${placeholder2}`, boolean);
|
|
456
537
|
}
|
|
457
538
|
/**
|
|
458
539
|
* Process WHERE LIKE / NOT LIKE.
|
|
540
|
+
*
|
|
541
|
+
* Cross-driver semantics (matches MongoDB): `%` is the only wildcard, `_`
|
|
542
|
+
* and backslash are literal, and a pattern with no `%` matches as a
|
|
543
|
+
* substring. `startsWith` / `endsWith` anchor the escaped text explicitly.
|
|
544
|
+
* A `RegExp` has no SQL equivalent and is rejected.
|
|
459
545
|
*/
|
|
460
|
-
processWhereLike(data, negate) {
|
|
546
|
+
processWhereLike(data, negate, boolean = "AND", modeArg) {
|
|
461
547
|
const field = data.field;
|
|
462
|
-
const
|
|
548
|
+
const mode = modeArg ?? data.mode;
|
|
549
|
+
if (data.isRegExp === true || data.pattern instanceof RegExp) {
|
|
550
|
+
const source = data.pattern instanceof RegExp ? data.pattern.source : String(data.pattern);
|
|
551
|
+
const regexField = this.parseColumnIdentifier(field, this.table, this.alias);
|
|
552
|
+
const regexPlaceholder = this.addParam(source);
|
|
553
|
+
return this.addWhereClause(`${regexField} ${negate ? "!~*" : "~*"} ${regexPlaceholder}`, boolean);
|
|
554
|
+
}
|
|
555
|
+
const pattern = String(mode && data.value !== void 0 ? data.value : data.pattern);
|
|
463
556
|
const quotedField = this.parseColumnIdentifier(field, this.table, this.alias);
|
|
464
557
|
const { operator } = this.dialect.likePattern(pattern, true);
|
|
465
|
-
|
|
558
|
+
let escaped = pattern.replace(/\\/g, "\\\\").replace(/_/g, "\\_");
|
|
559
|
+
if (mode) escaped = escaped.replace(/%/g, "\\%");
|
|
560
|
+
let finalPattern = escaped;
|
|
561
|
+
if (mode === "startsWith") finalPattern = `${escaped}%`;
|
|
562
|
+
else if (mode === "endsWith") finalPattern = `%${escaped}`;
|
|
563
|
+
else if (!escaped.includes("%")) finalPattern = `%${escaped}%`;
|
|
564
|
+
const placeholder = this.addParam(finalPattern);
|
|
466
565
|
const keyword = negate ? `NOT ${operator}` : operator;
|
|
467
|
-
|
|
566
|
+
const escapeClause = mode ? " ESCAPE '\\'" : "";
|
|
567
|
+
this.addWhereClause(`${quotedField} ${keyword} ${placeholder}${escapeClause}`, boolean);
|
|
468
568
|
}
|
|
469
569
|
/**
|
|
470
570
|
* Process WHERE column comparison.
|
|
471
571
|
*/
|
|
472
572
|
processWhereColumn(data, boolean) {
|
|
473
573
|
const first = data.first;
|
|
474
|
-
const operator = data.operator ?? "=";
|
|
574
|
+
const operator = this.mapOperator(data.operator ?? "=");
|
|
475
575
|
const second = data.second;
|
|
476
576
|
const quotedFirst = this.parseColumnIdentifier(first, this.table, this.alias);
|
|
477
577
|
const quotedSecond = this.parseColumnIdentifier(second, this.table, this.alias);
|
|
@@ -484,9 +584,13 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
484
584
|
const path = data.path;
|
|
485
585
|
const value = data.value;
|
|
486
586
|
const quotedPath = this.parseColumnIdentifier(path, this.table, this.alias);
|
|
487
|
-
const jsonValue = JSON.stringify(value);
|
|
488
587
|
const operator = negate ? "NOT @>" : "@>";
|
|
489
|
-
this.
|
|
588
|
+
const placeholder = this.addParam(JSON.stringify(value));
|
|
589
|
+
this.addWhereClause(`${quotedPath} ${operator} ${placeholder}::jsonb`, "AND");
|
|
590
|
+
}
|
|
591
|
+
/** Text search config name; anything but a plain identifier falls back to english. */
|
|
592
|
+
safeLanguage(language) {
|
|
593
|
+
return typeof language === "string" && /^[a-z_]+$/i.test(language) ? language : "english";
|
|
490
594
|
}
|
|
491
595
|
/**
|
|
492
596
|
* Process full-text search WHERE.
|
|
@@ -494,9 +598,14 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
494
598
|
processWhereFullText(data) {
|
|
495
599
|
const fields = data.fields;
|
|
496
600
|
const query = data.query;
|
|
497
|
-
const
|
|
601
|
+
const language = this.safeLanguage(data.language);
|
|
602
|
+
const weights = data.weights ?? {};
|
|
603
|
+
const tsVectors = fields.map((f) => {
|
|
604
|
+
const weight = /^[A-D]$/.test(weights[f] ?? "") ? weights[f] : "A";
|
|
605
|
+
return `setweight(to_tsvector('${language}', COALESCE(${this.dialect.quoteIdentifier(f)}, '')), '${weight}')`;
|
|
606
|
+
}).join(" || ");
|
|
498
607
|
const placeholder = this.addParam(query);
|
|
499
|
-
this.addWhereClause(`(${tsVectors}) @@ plainto_tsquery('
|
|
608
|
+
this.addWhereClause(`(${tsVectors}) @@ plainto_tsquery('${language}', ${placeholder})`, "AND");
|
|
500
609
|
}
|
|
501
610
|
/**
|
|
502
611
|
* Process SELECT operation.
|
|
@@ -560,7 +669,7 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
560
669
|
const quotedLocalKey = this.dialect.quoteIdentifier(localKey);
|
|
561
670
|
const quotedMainTable = this.dialect.quoteIdentifier(this.table);
|
|
562
671
|
let innerSelect;
|
|
563
|
-
if (select && select.length > 0) innerSelect = `json_agg(json_build_object(${select.map((col) => `'${col}', a.${this.dialect.quoteIdentifier(col)}`).join(", ")}))`;
|
|
672
|
+
if (select && select.length > 0) innerSelect = `json_agg(json_build_object(${select.map((col) => `'${escapeSqlLiteral(col)}', a.${this.dialect.quoteIdentifier(col)}`).join(", ")}))`;
|
|
564
673
|
else innerSelect = `json_agg(row_to_json(a.*))`;
|
|
565
674
|
const fkCondition = `a.${quotedForeignKey} = ${quotedMainTable}.${quotedLocalKey}`;
|
|
566
675
|
let extraWhere = "";
|
|
@@ -576,7 +685,7 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
576
685
|
const renumber = this.absorbSubParserParams(subParser);
|
|
577
686
|
if (subParser.whereClauses.length > 0) extraWhere = ` AND ${renumber(subParser.whereClauses.join(" "))}`;
|
|
578
687
|
if (subParser.orderClauses.length > 0) orderBy = ` ORDER BY ${renumber(subParser.orderClauses.join(", "))}`;
|
|
579
|
-
if (subParser.limitValue !== void 0) limitClause = `
|
|
688
|
+
if (subParser.limitValue !== void 0) limitClause = ` ${this.dialect.limitOffset(subParser.limitValue)}`;
|
|
580
689
|
}
|
|
581
690
|
orderBy || limitClause ? `${quotedRelatedTable}${fkCondition}${extraWhere}${orderBy}${limitClause}` : `${innerSelect.replace("json_agg(", "").replace(/\)$/, "")}${quotedRelatedTable}${fkCondition}${extraWhere}`;
|
|
582
691
|
const aggregated = orderBy || limitClause ? `(SELECT json_agg(row_to_json(sub.*)) FROM (SELECT * FROM ${quotedRelatedTable} a WHERE ${fkCondition}${extraWhere}${orderBy}${limitClause}) sub) AS ${quotedAlias}` : `(SELECT ${innerSelect} FROM ${quotedRelatedTable} a WHERE ${fkCondition}${extraWhere}) AS ${quotedAlias}`;
|
|
@@ -679,7 +788,11 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
679
788
|
if (this.isTableReference(first)) expression = this.buildJsonbPath(first, second, rest);
|
|
680
789
|
else expression = this.buildJsonbPath(effectiveTable, first, [second, ...rest]);
|
|
681
790
|
}
|
|
682
|
-
|
|
791
|
+
if (cast) {
|
|
792
|
+
if (!SAFE_CAST_PATTERN.test(cast)) throw new Error(`PostgresQueryParser: unsafe cast type "${cast}"`);
|
|
793
|
+
return `(${expression})::${cast}`;
|
|
794
|
+
}
|
|
795
|
+
return expression;
|
|
683
796
|
}
|
|
684
797
|
/**
|
|
685
798
|
* Check if a string is a table reference (main table or join table).
|
|
@@ -715,7 +828,7 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
715
828
|
let expression = `${quotedTable}.${quotedColumn}`;
|
|
716
829
|
for (let i = 0; i < path.length; i++) {
|
|
717
830
|
const operator = i === path.length - 1 ? "->>" : "->";
|
|
718
|
-
expression += `${operator}'${path[i]}'`;
|
|
831
|
+
expression += `${operator}'${escapeSqlLiteral(path[i])}'`;
|
|
719
832
|
}
|
|
720
833
|
return expression;
|
|
721
834
|
}
|
|
@@ -742,7 +855,9 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
742
855
|
*/
|
|
743
856
|
processOrderBy(data) {
|
|
744
857
|
const field = data.field;
|
|
745
|
-
const
|
|
858
|
+
const rawDirection = String(data.direction ?? "asc").trim();
|
|
859
|
+
if (!SAFE_DIRECTION_PATTERN.test(rawDirection)) throw new Error(`PostgresQueryParser: invalid orderBy direction "${rawDirection}"`);
|
|
860
|
+
const direction = rawDirection.replace(/\s+/g, " ").toUpperCase();
|
|
746
861
|
const quotedField = this.parseColumnIdentifier(field, this.table, this.alias);
|
|
747
862
|
this.orderClauses.push(`${quotedField} ${direction}`);
|
|
748
863
|
}
|
|
@@ -777,7 +892,7 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
777
892
|
*/
|
|
778
893
|
processHaving(data) {
|
|
779
894
|
const field = data.field;
|
|
780
|
-
const operator = data.operator ?? "=";
|
|
895
|
+
const operator = this.mapOperator(data.operator ?? "=");
|
|
781
896
|
const value = data.value;
|
|
782
897
|
const quotedField = this.dialect.quoteIdentifier(field);
|
|
783
898
|
const placeholder = this.addParam(value);
|
|
@@ -808,7 +923,7 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
808
923
|
* dedicated processors and should never reach this method.
|
|
809
924
|
*/
|
|
810
925
|
mapOperator(operator) {
|
|
811
|
-
|
|
926
|
+
const mapped = {
|
|
812
927
|
"=": "=",
|
|
813
928
|
"!=": "!=",
|
|
814
929
|
"<>": "<>",
|
|
@@ -819,7 +934,9 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
819
934
|
like: "LIKE",
|
|
820
935
|
notlike: "NOT LIKE",
|
|
821
936
|
ilike: "ILIKE"
|
|
822
|
-
}[operator.toLowerCase()]
|
|
937
|
+
}[String(operator).toLowerCase()];
|
|
938
|
+
if (mapped === void 0) throw new Error(`PostgresQueryParser: unsupported operator "${operator}"`);
|
|
939
|
+
return mapped;
|
|
823
940
|
}
|
|
824
941
|
};
|
|
825
942
|
|