uql-orm 0.6.0 → 0.7.2
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 +25 -0
- package/README.md +6 -5
- package/dist/bunSql/bunSql.util.d.ts +30 -0
- package/dist/bunSql/bunSql.util.d.ts.map +1 -0
- package/dist/bunSql/bunSql.util.js +91 -0
- package/dist/bunSql/bunSql.util.js.map +1 -0
- package/dist/bunSql/bunSqlQuerier.d.ts +14 -0
- package/dist/bunSql/bunSqlQuerier.d.ts.map +1 -0
- package/dist/bunSql/bunSqlQuerier.js +37 -0
- package/dist/bunSql/bunSqlQuerier.js.map +1 -0
- package/dist/bunSql/bunSqlQuerierPool.d.ts +19 -0
- package/dist/bunSql/bunSqlQuerierPool.d.ts.map +1 -0
- package/dist/bunSql/bunSqlQuerierPool.js +58 -0
- package/dist/bunSql/bunSqlQuerierPool.js.map +1 -0
- package/dist/bunSql/index.d.ts +3 -0
- package/dist/bunSql/index.d.ts.map +1 -0
- package/dist/bunSql/index.js +3 -0
- package/dist/bunSql/index.js.map +1 -0
- package/dist/cockroachdb/crdbQuerier.d.ts +3 -8
- package/dist/cockroachdb/crdbQuerier.d.ts.map +1 -1
- package/dist/cockroachdb/crdbQuerier.js +2 -30
- package/dist/cockroachdb/crdbQuerier.js.map +1 -1
- package/dist/cockroachdb/crdbQuerierPool.d.ts +4 -4
- package/dist/cockroachdb/crdbQuerierPool.d.ts.map +1 -1
- package/dist/cockroachdb/crdbQuerierPool.js +5 -7
- package/dist/cockroachdb/crdbQuerierPool.js.map +1 -1
- package/dist/d1/d1Querier.d.ts +2 -1
- package/dist/d1/d1Querier.d.ts.map +1 -1
- package/dist/d1/d1Querier.js +3 -4
- package/dist/d1/d1Querier.js.map +1 -1
- package/dist/d1/d1QuerierPool.d.ts +2 -1
- package/dist/d1/d1QuerierPool.d.ts.map +1 -1
- package/dist/d1/d1QuerierPool.js +3 -2
- package/dist/d1/d1QuerierPool.js.map +1 -1
- package/dist/dialect/abstractDialect.d.ts +1 -0
- package/dist/dialect/abstractDialect.d.ts.map +1 -1
- package/dist/dialect/abstractDialect.js +3 -0
- package/dist/dialect/abstractDialect.js.map +1 -1
- package/dist/dialect/abstractSqlDialect.d.ts +57 -52
- package/dist/dialect/abstractSqlDialect.d.ts.map +1 -1
- package/dist/dialect/abstractSqlDialect.js +194 -142
- package/dist/dialect/abstractSqlDialect.js.map +1 -1
- package/dist/dialect/dialectConfig.d.ts +19 -8
- package/dist/dialect/dialectConfig.d.ts.map +1 -1
- package/dist/dialect/dialectConfig.js +6 -0
- package/dist/dialect/dialectConfig.js.map +1 -1
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +3 -5
- package/dist/dialect/mysqlLikeSqlDialect.d.ts.map +1 -1
- package/dist/dialect/mysqlLikeSqlDialect.js +12 -24
- package/dist/dialect/mysqlLikeSqlDialect.js.map +1 -1
- package/dist/dialect/queryContext.js +1 -1
- package/dist/dialect/queryContext.js.map +1 -1
- package/dist/express/querierMiddleware.d.ts.map +1 -1
- package/dist/express/querierMiddleware.js +13 -20
- package/dist/express/querierMiddleware.js.map +1 -1
- package/dist/libsql/libsqlQuerier.d.ts +2 -1
- package/dist/libsql/libsqlQuerier.d.ts.map +1 -1
- package/dist/libsql/libsqlQuerier.js +3 -4
- package/dist/libsql/libsqlQuerier.js.map +1 -1
- package/dist/libsql/libsqlQuerierPool.d.ts +2 -1
- package/dist/libsql/libsqlQuerierPool.d.ts.map +1 -1
- package/dist/libsql/libsqlQuerierPool.js +3 -2
- package/dist/libsql/libsqlQuerierPool.js.map +1 -1
- package/dist/maria/mariaDialect.d.ts +2 -4
- package/dist/maria/mariaDialect.d.ts.map +1 -1
- package/dist/maria/mariaDialect.js +1 -5
- package/dist/maria/mariaDialect.js.map +1 -1
- package/dist/maria/mariadbQuerier.d.ts +2 -1
- package/dist/maria/mariadbQuerier.d.ts.map +1 -1
- package/dist/maria/mariadbQuerier.js +4 -6
- package/dist/maria/mariadbQuerier.js.map +1 -1
- package/dist/maria/mariadbQuerierPool.d.ts +2 -1
- package/dist/maria/mariadbQuerierPool.d.ts.map +1 -1
- package/dist/maria/mariadbQuerierPool.js +3 -2
- package/dist/maria/mariadbQuerierPool.js.map +1 -1
- package/dist/mongo/mongodbQuerierPool.d.ts +2 -1
- package/dist/mongo/mongodbQuerierPool.d.ts.map +1 -1
- package/dist/mongo/mongodbQuerierPool.js +2 -3
- package/dist/mongo/mongodbQuerierPool.js.map +1 -1
- package/dist/mysql/mysql2Querier.d.ts +2 -1
- package/dist/mysql/mysql2Querier.d.ts.map +1 -1
- package/dist/mysql/mysql2Querier.js +7 -11
- package/dist/mysql/mysql2Querier.js.map +1 -1
- package/dist/mysql/mysql2QuerierPool.d.ts +2 -1
- package/dist/mysql/mysql2QuerierPool.d.ts.map +1 -1
- package/dist/mysql/mysql2QuerierPool.js +3 -2
- package/dist/mysql/mysql2QuerierPool.js.map +1 -1
- package/dist/neon/neonQuerier.d.ts +3 -7
- package/dist/neon/neonQuerier.d.ts.map +1 -1
- package/dist/neon/neonQuerier.js +2 -18
- package/dist/neon/neonQuerier.js.map +1 -1
- package/dist/neon/neonQuerierPool.d.ts +4 -4
- package/dist/neon/neonQuerierPool.d.ts.map +1 -1
- package/dist/neon/neonQuerierPool.js +5 -7
- package/dist/neon/neonQuerierPool.js.map +1 -1
- package/dist/postgres/abstractPgQuerier.d.ts +18 -0
- package/dist/postgres/abstractPgQuerier.d.ts.map +1 -0
- package/dist/postgres/abstractPgQuerier.js +33 -0
- package/dist/postgres/abstractPgQuerier.js.map +1 -0
- package/dist/postgres/abstractPgQuerierPool.d.ts +15 -0
- package/dist/postgres/abstractPgQuerierPool.d.ts.map +1 -0
- package/dist/postgres/abstractPgQuerierPool.js +10 -0
- package/dist/postgres/abstractPgQuerierPool.js.map +1 -0
- package/dist/postgres/pgQuerier.d.ts +3 -8
- package/dist/postgres/pgQuerier.d.ts.map +1 -1
- package/dist/postgres/pgQuerier.js +2 -30
- package/dist/postgres/pgQuerier.js.map +1 -1
- package/dist/postgres/pgQuerierPool.d.ts +6 -6
- package/dist/postgres/pgQuerierPool.d.ts.map +1 -1
- package/dist/postgres/pgQuerierPool.js +7 -9
- package/dist/postgres/pgQuerierPool.js.map +1 -1
- package/dist/postgres/postgresDialect.d.ts +6 -16
- package/dist/postgres/postgresDialect.d.ts.map +1 -1
- package/dist/postgres/postgresDialect.js +49 -72
- package/dist/postgres/postgresDialect.js.map +1 -1
- package/dist/querier/abstractQuerierPool.d.ts +5 -3
- package/dist/querier/abstractQuerierPool.d.ts.map +1 -1
- package/dist/querier/abstractQuerierPool.js +6 -3
- package/dist/querier/abstractQuerierPool.js.map +1 -1
- package/dist/querier/abstractSqlQuerier.d.ts +5 -0
- package/dist/querier/abstractSqlQuerier.d.ts.map +1 -1
- package/dist/querier/abstractSqlQuerier.js +15 -5
- package/dist/querier/abstractSqlQuerier.js.map +1 -1
- package/dist/sqlite/abstractSqliteQuerier.d.ts +0 -2
- package/dist/sqlite/abstractSqliteQuerier.d.ts.map +1 -1
- package/dist/sqlite/abstractSqliteQuerier.js +0 -9
- package/dist/sqlite/abstractSqliteQuerier.js.map +1 -1
- package/dist/sqlite/sqliteDialect.d.ts +6 -20
- package/dist/sqlite/sqliteDialect.d.ts.map +1 -1
- package/dist/sqlite/sqliteDialect.js +20 -33
- package/dist/sqlite/sqliteDialect.js.map +1 -1
- package/dist/sqlite/sqliteQuerier.d.ts +2 -1
- package/dist/sqlite/sqliteQuerier.d.ts.map +1 -1
- package/dist/sqlite/sqliteQuerier.js +3 -4
- package/dist/sqlite/sqliteQuerier.js.map +1 -1
- package/dist/sqlite/sqliteQuerierPool.d.ts +2 -1
- package/dist/sqlite/sqliteQuerierPool.d.ts.map +1 -1
- package/dist/sqlite/sqliteQuerierPool.js +3 -2
- package/dist/sqlite/sqliteQuerierPool.js.map +1 -1
- package/dist/type/querierPool.d.ts +16 -0
- package/dist/type/querierPool.d.ts.map +1 -1
- package/dist/type/query.d.ts +8 -3
- package/dist/type/query.d.ts.map +1 -1
- package/dist/type/utility.d.ts +4 -0
- package/dist/type/utility.d.ts.map +1 -1
- package/dist/util/sql.util.d.ts +26 -3
- package/dist/util/sql.util.d.ts.map +1 -1
- package/dist/util/sql.util.js +51 -9
- package/dist/util/sql.util.js.map +1 -1
- package/package.json +6 -5
|
@@ -33,9 +33,33 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
33
33
|
return new SqlQueryContext(this);
|
|
34
34
|
}
|
|
35
35
|
addValue(values, value) {
|
|
36
|
-
values.push(value
|
|
36
|
+
values.push(this.normalizeValue(value));
|
|
37
37
|
return this.placeholder(values.length);
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Normalizes a parameter value for the database driver.
|
|
41
|
+
* Handles bigint, boolean, and serializes plain objects/arrays to JSON strings.
|
|
42
|
+
* Date values are preserved so SQL drivers can apply native date/time binding.
|
|
43
|
+
* Postgres overrides to pass objects through to its native JSONB driver.
|
|
44
|
+
*/
|
|
45
|
+
normalizeValue(value) {
|
|
46
|
+
if (typeof value === 'bigint')
|
|
47
|
+
return Number(value);
|
|
48
|
+
if (typeof value === 'boolean')
|
|
49
|
+
return value ? 1 : 0;
|
|
50
|
+
if (value instanceof Date)
|
|
51
|
+
return value;
|
|
52
|
+
if (value !== null && typeof value === 'object' && !(value instanceof Uint8Array) && !(value instanceof QueryRaw)) {
|
|
53
|
+
return JSON.stringify(value);
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Normalizes a list of parameter values.
|
|
59
|
+
*/
|
|
60
|
+
normalizeValues(values) {
|
|
61
|
+
return values?.map((v) => this.normalizeValue(v));
|
|
62
|
+
}
|
|
39
63
|
placeholder(_index) {
|
|
40
64
|
return '?';
|
|
41
65
|
}
|
|
@@ -368,48 +392,61 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
368
392
|
$gte: ' >= ',
|
|
369
393
|
$lt: ' < ',
|
|
370
394
|
$lte: ' <= ',
|
|
371
|
-
$regex: ' REGEXP ',
|
|
372
395
|
};
|
|
373
|
-
/** String/LIKE operators: wrap the value, optionally lowercase it. */
|
|
374
396
|
static LIKE_OP_MAP = {
|
|
375
|
-
$startsWith:
|
|
376
|
-
$istartsWith:
|
|
377
|
-
$endsWith:
|
|
378
|
-
$iendsWith:
|
|
379
|
-
$includes:
|
|
380
|
-
$iincludes:
|
|
381
|
-
$like:
|
|
382
|
-
$ilike:
|
|
397
|
+
$startsWith: (v) => `${v}%`,
|
|
398
|
+
$istartsWith: (v) => `${v.toLowerCase()}%`,
|
|
399
|
+
$endsWith: (v) => `%${v}`,
|
|
400
|
+
$iendsWith: (v) => `%${v.toLowerCase()}`,
|
|
401
|
+
$includes: (v) => `%${v}%`,
|
|
402
|
+
$iincludes: (v) => `%${v.toLowerCase()}%`,
|
|
403
|
+
$like: (v) => v,
|
|
404
|
+
$ilike: (v) => v.toLowerCase(),
|
|
383
405
|
};
|
|
406
|
+
resolveColumnWithPrefix(entity, key, { prefix } = {}) {
|
|
407
|
+
const meta = getMeta(entity);
|
|
408
|
+
const field = meta.fields[key];
|
|
409
|
+
const columnName = this.resolveColumnName(key, field);
|
|
410
|
+
const escapedPrefix = this.escapeId(prefix, true, true);
|
|
411
|
+
return escapedPrefix + this.escapeId(columnName);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Resolves the SQL operand for a field comparison.
|
|
415
|
+
* For QueryRaw virtuals, appends the raw expression to ctx and returns undefined.
|
|
416
|
+
*/
|
|
417
|
+
resolveOperandField(ctx, entity, key, opts) {
|
|
418
|
+
const col = getMeta(entity).fields[key];
|
|
419
|
+
if (col?.virtual) {
|
|
420
|
+
if (col.virtual instanceof QueryRaw) {
|
|
421
|
+
this.getComparisonKey(ctx, entity, key, opts);
|
|
422
|
+
return undefined;
|
|
423
|
+
}
|
|
424
|
+
return `(${col.virtual})`;
|
|
425
|
+
}
|
|
426
|
+
return this.resolveColumnWithPrefix(entity, key, opts);
|
|
427
|
+
}
|
|
428
|
+
appendFieldSql(ctx, field, sql) {
|
|
429
|
+
ctx.append(field ? `${field}${sql}` : sql);
|
|
430
|
+
}
|
|
384
431
|
compareFieldOperator(ctx, entity, key, op, val, opts = {}) {
|
|
385
|
-
|
|
432
|
+
const field = this.resolveOperandField(ctx, entity, key, opts);
|
|
386
433
|
const simpleOp = AbstractSqlDialect.COMPARE_OP_MAP[op];
|
|
387
434
|
if (simpleOp) {
|
|
388
|
-
this.
|
|
389
|
-
ctx.append(simpleOp);
|
|
390
|
-
ctx.addValue(val);
|
|
435
|
+
this.appendFieldSql(ctx, field, `${simpleOp}${this.addValue(ctx.values, val)}`);
|
|
391
436
|
return;
|
|
392
437
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
this.getComparisonKey(ctx, entity, key, opts);
|
|
397
|
-
ctx.append(' LIKE ');
|
|
398
|
-
ctx.addValue(likeEntry.wrap(val));
|
|
438
|
+
const likeWrap = AbstractSqlDialect.LIKE_OP_MAP[op];
|
|
439
|
+
if (likeWrap) {
|
|
440
|
+
this.appendLikeOp(ctx, field, op, likeWrap(val));
|
|
399
441
|
return;
|
|
400
442
|
}
|
|
401
443
|
switch (op) {
|
|
402
444
|
case '$eq':
|
|
403
|
-
this.getComparisonKey(ctx, entity, key, opts);
|
|
404
|
-
ctx.append(val === null ? ' IS NULL' : ' = ');
|
|
405
|
-
if (val !== null)
|
|
406
|
-
ctx.addValue(val);
|
|
407
|
-
break;
|
|
408
445
|
case '$ne':
|
|
409
|
-
this.
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
446
|
+
this.appendEqNe(ctx, field, op, val);
|
|
447
|
+
break;
|
|
448
|
+
case '$regex':
|
|
449
|
+
this.appendFieldSql(ctx, field, ` ${this.regexpOp} ${this.addValue(ctx.values, val)}`);
|
|
413
450
|
break;
|
|
414
451
|
case '$not':
|
|
415
452
|
ctx.append('NOT (');
|
|
@@ -417,35 +454,20 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
417
454
|
ctx.append(')');
|
|
418
455
|
break;
|
|
419
456
|
case '$in':
|
|
420
|
-
case '$nin':
|
|
421
|
-
this.
|
|
422
|
-
const keyword = op === '$in' ? ' IN ' : ' NOT IN ';
|
|
423
|
-
if (Array.isArray(val) && val.length > 0) {
|
|
424
|
-
ctx.append(`${keyword}(`);
|
|
425
|
-
this.addValues(ctx, val);
|
|
426
|
-
ctx.append(')');
|
|
427
|
-
}
|
|
428
|
-
else {
|
|
429
|
-
ctx.append(`${keyword}(NULL)`);
|
|
430
|
-
}
|
|
457
|
+
case '$nin':
|
|
458
|
+
this.appendInNin(ctx, field, op, val);
|
|
431
459
|
break;
|
|
432
|
-
}
|
|
433
460
|
case '$between': {
|
|
461
|
+
const col = this.resolveColumnWithPrefix(entity, key, opts);
|
|
434
462
|
const [min, max] = val;
|
|
435
|
-
this.
|
|
436
|
-
ctx.append(' BETWEEN ');
|
|
437
|
-
ctx.addValue(min);
|
|
438
|
-
ctx.append(' AND ');
|
|
439
|
-
ctx.addValue(max);
|
|
463
|
+
ctx.append(`${col} BETWEEN ${this.addValue(ctx.values, min)} AND ${this.addValue(ctx.values, max)}`);
|
|
440
464
|
break;
|
|
441
465
|
}
|
|
442
466
|
case '$isNull':
|
|
443
|
-
this.
|
|
444
|
-
ctx.append(val ? ' IS NULL' : ' IS NOT NULL');
|
|
467
|
+
this.appendFieldSql(ctx, field, val ? ' IS NULL' : ' IS NOT NULL');
|
|
445
468
|
break;
|
|
446
469
|
case '$isNotNull':
|
|
447
|
-
this.
|
|
448
|
-
ctx.append(val ? ' IS NOT NULL' : ' IS NULL');
|
|
470
|
+
this.appendFieldSql(ctx, field, val ? ' IS NOT NULL' : ' IS NULL');
|
|
449
471
|
break;
|
|
450
472
|
case '$all':
|
|
451
473
|
case '$size':
|
|
@@ -455,6 +477,36 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
455
477
|
throw TypeError(`unknown operator: ${op}`);
|
|
456
478
|
}
|
|
457
479
|
}
|
|
480
|
+
appendLikeOp(ctx, field, op, wrappedVal) {
|
|
481
|
+
const isIlike = op.startsWith('$i') || op === '$ilike';
|
|
482
|
+
const ph = this.addValue(ctx.values, wrappedVal);
|
|
483
|
+
if (isIlike && field) {
|
|
484
|
+
ctx.append(this.ilikeExpr(field, ph));
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
this.appendFieldSql(ctx, field, ` ${this.likeFn} ${ph}`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
appendEqNe(ctx, field, op, val) {
|
|
491
|
+
if (val === null) {
|
|
492
|
+
this.appendFieldSql(ctx, field, op === '$eq' ? ' IS NULL' : ' IS NOT NULL');
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
const ph = this.addValue(ctx.values, val);
|
|
496
|
+
if (op === '$eq') {
|
|
497
|
+
this.appendFieldSql(ctx, field, ` = ${ph}`);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (field) {
|
|
501
|
+
ctx.append(this.neExpr(field, ph));
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
this.appendFieldSql(ctx, field, ` ${this.neOp} ${ph}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
appendInNin(ctx, field, op, val) {
|
|
508
|
+
this.appendFieldSql(ctx, field, this.formatIn(ctx, Array.isArray(val) ? val : [], op === '$nin'));
|
|
509
|
+
}
|
|
458
510
|
addValues(ctx, vals) {
|
|
459
511
|
vals.forEach((val, index) => {
|
|
460
512
|
if (index > 0) {
|
|
@@ -465,63 +517,54 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
465
517
|
}
|
|
466
518
|
/**
|
|
467
519
|
* Build a comparison condition for a JSON field.
|
|
468
|
-
* Used by both `$elemMatch` and dot-notation paths.
|
|
520
|
+
* Used by both `$elemMatch` and dot-notation paths.
|
|
521
|
+
* All dialect-specific behavior comes from overridable methods on `this`.
|
|
469
522
|
*/
|
|
470
|
-
buildJsonFieldCondition(ctx,
|
|
471
|
-
const jsonField =
|
|
523
|
+
buildJsonFieldCondition(ctx, fieldAccessor, jsonPath, op, value) {
|
|
524
|
+
const jsonField = fieldAccessor(jsonPath);
|
|
472
525
|
switch (op) {
|
|
473
526
|
case '$eq':
|
|
474
|
-
return value === null ? `${jsonField} IS NULL` : `${jsonField} = ${
|
|
527
|
+
return value === null ? `${jsonField} IS NULL` : `${jsonField} = ${this.addValue(ctx.values, value)}`;
|
|
475
528
|
case '$ne':
|
|
476
529
|
if (value === null)
|
|
477
530
|
return `${jsonField} IS NOT NULL`;
|
|
478
|
-
return
|
|
479
|
-
? config.neExpr(jsonField, config.addValue(ctx, value))
|
|
480
|
-
: `${jsonField} <> ${config.addValue(ctx, value)}`;
|
|
531
|
+
return this.neExpr(jsonField, this.addValue(ctx.values, value));
|
|
481
532
|
case '$gt':
|
|
482
|
-
return `${
|
|
533
|
+
return `${this.numericCast(jsonField)} > ${this.addValue(ctx.values, value)}`;
|
|
483
534
|
case '$gte':
|
|
484
|
-
return `${
|
|
535
|
+
return `${this.numericCast(jsonField)} >= ${this.addValue(ctx.values, value)}`;
|
|
485
536
|
case '$lt':
|
|
486
|
-
return `${
|
|
537
|
+
return `${this.numericCast(jsonField)} < ${this.addValue(ctx.values, value)}`;
|
|
487
538
|
case '$lte':
|
|
488
|
-
return `${
|
|
539
|
+
return `${this.numericCast(jsonField)} <= ${this.addValue(ctx.values, value)}`;
|
|
489
540
|
case '$like':
|
|
490
|
-
return `${jsonField} ${
|
|
541
|
+
return `${jsonField} ${this.likeFn} ${this.addValue(ctx.values, value)}`;
|
|
491
542
|
case '$ilike':
|
|
492
|
-
return
|
|
543
|
+
return this.ilikeExpr(jsonField, this.addValue(ctx.values, value.toLowerCase()));
|
|
493
544
|
case '$startsWith':
|
|
494
|
-
return `${jsonField} ${
|
|
545
|
+
return `${jsonField} ${this.likeFn} ${this.addValue(ctx.values, `${value}%`)}`;
|
|
495
546
|
case '$istartsWith':
|
|
496
|
-
return
|
|
547
|
+
return this.ilikeExpr(jsonField, this.addValue(ctx.values, `${value.toLowerCase()}%`));
|
|
497
548
|
case '$endsWith':
|
|
498
|
-
return `${jsonField} ${
|
|
549
|
+
return `${jsonField} ${this.likeFn} ${this.addValue(ctx.values, `%${value}`)}`;
|
|
499
550
|
case '$iendsWith':
|
|
500
|
-
return
|
|
551
|
+
return this.ilikeExpr(jsonField, this.addValue(ctx.values, `%${value.toLowerCase()}`));
|
|
501
552
|
case '$includes':
|
|
502
|
-
return `${jsonField} ${
|
|
553
|
+
return `${jsonField} ${this.likeFn} ${this.addValue(ctx.values, `%${value}%`)}`;
|
|
503
554
|
case '$iincludes':
|
|
504
|
-
return
|
|
555
|
+
return this.ilikeExpr(jsonField, this.addValue(ctx.values, `%${value.toLowerCase()}%`));
|
|
505
556
|
case '$regex':
|
|
506
|
-
return `${jsonField} ${
|
|
507
|
-
case '$in':
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
511
|
-
const inVals = value;
|
|
512
|
-
return `${jsonField} IN (${inVals.map((v) => config.addValue(ctx, v)).join(', ')})`;
|
|
513
|
-
}
|
|
514
|
-
case '$nin': {
|
|
515
|
-
if (config.ninExpr) {
|
|
516
|
-
return config.ninExpr(jsonField, config.addValue(ctx, value));
|
|
517
|
-
}
|
|
518
|
-
const ninVals = value;
|
|
519
|
-
return `${jsonField} NOT IN (${ninVals.map((v) => config.addValue(ctx, v)).join(', ')})`;
|
|
520
|
-
}
|
|
557
|
+
return `${jsonField} ${this.regexpOp} ${this.addValue(ctx.values, value)}`;
|
|
558
|
+
case '$in':
|
|
559
|
+
case '$nin':
|
|
560
|
+
return this.jsonInNin(ctx, jsonField, op, value);
|
|
521
561
|
default:
|
|
522
562
|
throw TypeError(`JSON field condition does not support operator: ${op}`);
|
|
523
563
|
}
|
|
524
564
|
}
|
|
565
|
+
jsonInNin(ctx, jsonField, op, value) {
|
|
566
|
+
return `${jsonField}${this.formatIn(ctx, Array.isArray(value) ? value : [], op === '$nin')}`;
|
|
567
|
+
}
|
|
525
568
|
getComparisonKey(ctx, entity, key, { prefix } = {}) {
|
|
526
569
|
const meta = getMeta(entity);
|
|
527
570
|
const escapedPrefix = this.escapeId(prefix, true, true);
|
|
@@ -579,7 +622,7 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
579
622
|
// Detect JSONB dot-notation: 'column.path'
|
|
580
623
|
const jsonDot = this.resolveJsonDotPath(meta, key);
|
|
581
624
|
if (jsonDot) {
|
|
582
|
-
ctx.append(jsonDot.
|
|
625
|
+
ctx.append(jsonDot.fieldAccessor(jsonDot.jsonPath) + direction);
|
|
583
626
|
return;
|
|
584
627
|
}
|
|
585
628
|
const field = meta.fields[key];
|
|
@@ -599,34 +642,47 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
599
642
|
return { colName, distance, field, vectorCast };
|
|
600
643
|
}
|
|
601
644
|
/**
|
|
602
|
-
*
|
|
603
|
-
*
|
|
645
|
+
* Mapping of UQL vector distance metrics to native SQL functions.
|
|
646
|
+
* Override in dialects that use function-call syntax (e.g. SQLite, MariaDB).
|
|
647
|
+
* Dialects with operator-based syntax (e.g. Postgres) leave this empty and override `appendVectorSort` directly.
|
|
604
648
|
*/
|
|
605
|
-
|
|
606
|
-
throw new TypeError('Vector similarity sort is not supported by this dialect. Use raw() for vector queries.');
|
|
607
|
-
}
|
|
649
|
+
vectorDistanceFns = {};
|
|
608
650
|
/**
|
|
609
|
-
*
|
|
610
|
-
* Used by
|
|
651
|
+
* Append a vector similarity function call: `fn(col, ?)`.
|
|
652
|
+
* Used by dialects that express vector distance via SQL functions (SQLite, MariaDB).
|
|
611
653
|
*/
|
|
612
|
-
appendFunctionVectorSort(ctx, meta, key, search,
|
|
613
|
-
const { colName, distance } = this.resolveVectorSortParams(meta, key, search);
|
|
614
|
-
const fn =
|
|
654
|
+
appendFunctionVectorSort(ctx, meta, key, search, dialectName) {
|
|
655
|
+
const { colName, distance, vectorCast } = this.resolveVectorSortParams(meta, key, search);
|
|
656
|
+
const fn = this.vectorDistanceFns[distance];
|
|
615
657
|
if (!fn) {
|
|
616
|
-
throw
|
|
658
|
+
throw Error(`${dialectName} does not support vector distance metric: ${distance}`);
|
|
617
659
|
}
|
|
618
660
|
ctx.append(`${fn}(${this.escapeId(colName)}, `);
|
|
619
661
|
ctx.addValue(`[${search.$vector.join(',')}]`);
|
|
662
|
+
if (vectorCast && dialectName === 'PostgreSQL') {
|
|
663
|
+
ctx.append(`::${vectorCast}`);
|
|
664
|
+
}
|
|
620
665
|
ctx.append(')');
|
|
621
666
|
}
|
|
622
667
|
/**
|
|
623
|
-
* Append a vector distance projection
|
|
624
|
-
* Delegates to `appendVectorSort()` (which emits the distance expression) then appends the alias.
|
|
668
|
+
* Append a vector distance projection.
|
|
625
669
|
*/
|
|
626
670
|
appendVectorProjection(ctx, meta, key, search) {
|
|
627
671
|
this.appendVectorSort(ctx, meta, key, search);
|
|
628
672
|
ctx.append(` AS ${this.escapeId(search.$project)}`);
|
|
629
673
|
}
|
|
674
|
+
/**
|
|
675
|
+
* Append a vector similarity ORDER BY expression.
|
|
676
|
+
* Default: auto-delegates to `appendFunctionVectorSort` when `vectorDistanceFns` has entries.
|
|
677
|
+
* Override for operator-based syntax (e.g. PostgreSQL `<=>`, `<->` operators).
|
|
678
|
+
*/
|
|
679
|
+
appendVectorSort(ctx, meta, key, search) {
|
|
680
|
+
if (hasKeys(this.vectorDistanceFns)) {
|
|
681
|
+
this.appendFunctionVectorSort(ctx, meta, key, search, this.dialect);
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
throw new TypeError('Vector similarity sort is not supported by this dialect. Use raw() for vector queries.');
|
|
685
|
+
}
|
|
630
686
|
pager(ctx, opts) {
|
|
631
687
|
if (opts.$limit) {
|
|
632
688
|
ctx.append(` LIMIT ${Number(opts.$limit)}`);
|
|
@@ -733,15 +789,7 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
733
789
|
ctx.addValue(max);
|
|
734
790
|
}
|
|
735
791
|
else if (op === '$in' || op === '$nin') {
|
|
736
|
-
|
|
737
|
-
if (Array.isArray(val) && val.length > 0) {
|
|
738
|
-
ctx.append(`${expr} ${keyword} (`);
|
|
739
|
-
this.addValues(ctx, val);
|
|
740
|
-
ctx.append(')');
|
|
741
|
-
}
|
|
742
|
-
else {
|
|
743
|
-
ctx.append(`${expr} ${keyword} (NULL)`);
|
|
744
|
-
}
|
|
792
|
+
ctx.append(`${expr}${this.formatIn(ctx, Array.isArray(val) ? val : [], op === '$nin')}`);
|
|
745
793
|
}
|
|
746
794
|
else if (op === '$isNull') {
|
|
747
795
|
ctx.append(`${expr}${val ? ' IS NULL' : ' IS NOT NULL'}`);
|
|
@@ -749,6 +797,9 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
749
797
|
else if (op === '$isNotNull') {
|
|
750
798
|
ctx.append(`${expr}${val ? ' IS NOT NULL' : ' IS NULL'}`);
|
|
751
799
|
}
|
|
800
|
+
else if (op === '$ne') {
|
|
801
|
+
ctx.append(this.neExpr(expr, this.addValue(ctx.values, val)));
|
|
802
|
+
}
|
|
752
803
|
else {
|
|
753
804
|
const sqlOp = AbstractSqlDialect.havingOpMap[op];
|
|
754
805
|
if (!sqlOp)
|
|
@@ -1012,15 +1063,14 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
1012
1063
|
const jsonPath = key.slice(dotIndex + 1);
|
|
1013
1064
|
const colName = this.resolveColumnName(root, field);
|
|
1014
1065
|
const escapedCol = (prefix ? this.escapeId(prefix, true, true) : '') + this.escapeId(colName);
|
|
1015
|
-
|
|
1016
|
-
return { root, jsonPath, config };
|
|
1066
|
+
return { root, jsonPath, fieldAccessor: () => this.getJsonPathScalarExpr(escapedCol, jsonPath) };
|
|
1017
1067
|
}
|
|
1018
1068
|
/**
|
|
1019
1069
|
* Compare a JSONB dot-notation path, e.g. `'settings.isArchived': { $ne: true }`.
|
|
1020
1070
|
* Receives a pre-resolved `resolveJsonDotPath` result to avoid redundant computation.
|
|
1021
1071
|
*/
|
|
1022
1072
|
compareJsonPath(ctx, resolved, val) {
|
|
1023
|
-
const { jsonPath,
|
|
1073
|
+
const { jsonPath, fieldAccessor } = resolved;
|
|
1024
1074
|
const value = this.normalizeWhereValue(val);
|
|
1025
1075
|
const operators = getKeys(value);
|
|
1026
1076
|
if (operators.length > 1) {
|
|
@@ -1029,25 +1079,12 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
1029
1079
|
operators.forEach((op, index) => {
|
|
1030
1080
|
if (index > 0)
|
|
1031
1081
|
ctx.append(' AND ');
|
|
1032
|
-
ctx.append(this.buildJsonFieldCondition(ctx,
|
|
1082
|
+
ctx.append(this.buildJsonFieldCondition(ctx, fieldAccessor, jsonPath, op, value[op]));
|
|
1033
1083
|
});
|
|
1034
1084
|
if (operators.length > 1) {
|
|
1035
1085
|
ctx.append(')');
|
|
1036
1086
|
}
|
|
1037
1087
|
}
|
|
1038
|
-
/**
|
|
1039
|
-
* Returns a dialect-specific `JsonFieldConfig` for accessing a nested JSON path.
|
|
1040
|
-
* Dialects should override this to provide their specific JSON accessor syntax.
|
|
1041
|
-
*
|
|
1042
|
-
* @param escapedColumn - The escaped column name (possibly prefixed with table name)
|
|
1043
|
-
* @param jsonPath - The dot-separated path within the JSON field (e.g. 'isArchived' or 'theme.color')
|
|
1044
|
-
*/
|
|
1045
|
-
getJsonFieldConfig(escapedColumn, jsonPath) {
|
|
1046
|
-
return {
|
|
1047
|
-
...this.getBaseJsonConfig(),
|
|
1048
|
-
fieldAccessor: () => this.getJsonPathScalarExpr(escapedColumn, jsonPath),
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
1088
|
/**
|
|
1052
1089
|
* Returns SQL that extracts a scalar value from a JSON path.
|
|
1053
1090
|
* Dialects can override this to customize path access syntax while preserving
|
|
@@ -1062,23 +1099,6 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
1062
1099
|
}
|
|
1063
1100
|
return expr;
|
|
1064
1101
|
}
|
|
1065
|
-
/**
|
|
1066
|
-
* Returns the dialect-invariant portion of `JsonFieldConfig`.
|
|
1067
|
-
* Dialects override this to provide casts, operators, and value binding.
|
|
1068
|
-
* Both `getJsonFieldConfig` (dot-notation) and `buildJsonFieldOperator` ($elemMatch) compose with this.
|
|
1069
|
-
*/
|
|
1070
|
-
getBaseJsonConfig() {
|
|
1071
|
-
return {
|
|
1072
|
-
numericCast: (expr) => `CAST(${expr} AS NUMERIC)`,
|
|
1073
|
-
likeFn: 'LIKE',
|
|
1074
|
-
ilikeExpr: (f, ph) => `LOWER(${f}) LIKE ${ph}`,
|
|
1075
|
-
regexpOp: 'REGEXP',
|
|
1076
|
-
addValue: (c, v) => {
|
|
1077
|
-
c.pushValue(v);
|
|
1078
|
-
return '?';
|
|
1079
|
-
},
|
|
1080
|
-
};
|
|
1081
|
-
}
|
|
1082
1102
|
/**
|
|
1083
1103
|
* Normalizes a raw WHERE value into an operator map.
|
|
1084
1104
|
* Arrays become `$in`, scalars/null become `$eq`, objects pass through.
|
|
@@ -1248,6 +1268,38 @@ export class AbstractSqlDialect extends AbstractDialect {
|
|
|
1248
1268
|
throw TypeError(`unsupported $size comparison operator: ${op}`);
|
|
1249
1269
|
}
|
|
1250
1270
|
}
|
|
1271
|
+
get regexpOp() {
|
|
1272
|
+
return 'REGEXP';
|
|
1273
|
+
}
|
|
1274
|
+
get likeFn() {
|
|
1275
|
+
return 'LIKE';
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Not-equal operator token for non-null comparisons.
|
|
1279
|
+
* Postgres uses `IS DISTINCT FROM`; MySQL/Maria uses custom `neExpr`.
|
|
1280
|
+
*/
|
|
1281
|
+
get neOp() {
|
|
1282
|
+
return '<>';
|
|
1283
|
+
}
|
|
1284
|
+
neExpr(field, ph) {
|
|
1285
|
+
return `${field} ${this.neOp} ${ph}`;
|
|
1286
|
+
}
|
|
1287
|
+
ilikeExpr(f, ph) {
|
|
1288
|
+
return `LOWER(${f}) LIKE ${ph}`;
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Formats an IN/NOT IN expression, binding each value individually.
|
|
1292
|
+
* Postgres overrides to use `= ANY($1)` / `<> ALL($1)` with a single array parameter.
|
|
1293
|
+
*/
|
|
1294
|
+
formatIn(ctx, values, negate) {
|
|
1295
|
+
if (values.length === 0)
|
|
1296
|
+
return negate ? ' NOT IN (NULL)' : ' IN (NULL)';
|
|
1297
|
+
const phs = values.map((v) => this.addValue(ctx.values, v)).join(', ');
|
|
1298
|
+
return ` ${negate ? 'NOT IN' : 'IN'} (${phs})`;
|
|
1299
|
+
}
|
|
1300
|
+
numericCast(expr) {
|
|
1301
|
+
return expr;
|
|
1302
|
+
}
|
|
1251
1303
|
}
|
|
1252
1304
|
/**
|
|
1253
1305
|
* Type guard: narrows a relation select value to a query object (with optional `$required`).
|