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.
Files changed (150) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +6 -5
  3. package/dist/bunSql/bunSql.util.d.ts +30 -0
  4. package/dist/bunSql/bunSql.util.d.ts.map +1 -0
  5. package/dist/bunSql/bunSql.util.js +91 -0
  6. package/dist/bunSql/bunSql.util.js.map +1 -0
  7. package/dist/bunSql/bunSqlQuerier.d.ts +14 -0
  8. package/dist/bunSql/bunSqlQuerier.d.ts.map +1 -0
  9. package/dist/bunSql/bunSqlQuerier.js +37 -0
  10. package/dist/bunSql/bunSqlQuerier.js.map +1 -0
  11. package/dist/bunSql/bunSqlQuerierPool.d.ts +19 -0
  12. package/dist/bunSql/bunSqlQuerierPool.d.ts.map +1 -0
  13. package/dist/bunSql/bunSqlQuerierPool.js +58 -0
  14. package/dist/bunSql/bunSqlQuerierPool.js.map +1 -0
  15. package/dist/bunSql/index.d.ts +3 -0
  16. package/dist/bunSql/index.d.ts.map +1 -0
  17. package/dist/bunSql/index.js +3 -0
  18. package/dist/bunSql/index.js.map +1 -0
  19. package/dist/cockroachdb/crdbQuerier.d.ts +3 -8
  20. package/dist/cockroachdb/crdbQuerier.d.ts.map +1 -1
  21. package/dist/cockroachdb/crdbQuerier.js +2 -30
  22. package/dist/cockroachdb/crdbQuerier.js.map +1 -1
  23. package/dist/cockroachdb/crdbQuerierPool.d.ts +4 -4
  24. package/dist/cockroachdb/crdbQuerierPool.d.ts.map +1 -1
  25. package/dist/cockroachdb/crdbQuerierPool.js +5 -7
  26. package/dist/cockroachdb/crdbQuerierPool.js.map +1 -1
  27. package/dist/d1/d1Querier.d.ts +2 -1
  28. package/dist/d1/d1Querier.d.ts.map +1 -1
  29. package/dist/d1/d1Querier.js +3 -4
  30. package/dist/d1/d1Querier.js.map +1 -1
  31. package/dist/d1/d1QuerierPool.d.ts +2 -1
  32. package/dist/d1/d1QuerierPool.d.ts.map +1 -1
  33. package/dist/d1/d1QuerierPool.js +3 -2
  34. package/dist/d1/d1QuerierPool.js.map +1 -1
  35. package/dist/dialect/abstractDialect.d.ts +1 -0
  36. package/dist/dialect/abstractDialect.d.ts.map +1 -1
  37. package/dist/dialect/abstractDialect.js +3 -0
  38. package/dist/dialect/abstractDialect.js.map +1 -1
  39. package/dist/dialect/abstractSqlDialect.d.ts +57 -52
  40. package/dist/dialect/abstractSqlDialect.d.ts.map +1 -1
  41. package/dist/dialect/abstractSqlDialect.js +194 -142
  42. package/dist/dialect/abstractSqlDialect.js.map +1 -1
  43. package/dist/dialect/dialectConfig.d.ts +19 -8
  44. package/dist/dialect/dialectConfig.d.ts.map +1 -1
  45. package/dist/dialect/dialectConfig.js +6 -0
  46. package/dist/dialect/dialectConfig.js.map +1 -1
  47. package/dist/dialect/mysqlLikeSqlDialect.d.ts +3 -5
  48. package/dist/dialect/mysqlLikeSqlDialect.d.ts.map +1 -1
  49. package/dist/dialect/mysqlLikeSqlDialect.js +12 -24
  50. package/dist/dialect/mysqlLikeSqlDialect.js.map +1 -1
  51. package/dist/dialect/queryContext.js +1 -1
  52. package/dist/dialect/queryContext.js.map +1 -1
  53. package/dist/express/querierMiddleware.d.ts.map +1 -1
  54. package/dist/express/querierMiddleware.js +13 -20
  55. package/dist/express/querierMiddleware.js.map +1 -1
  56. package/dist/libsql/libsqlQuerier.d.ts +2 -1
  57. package/dist/libsql/libsqlQuerier.d.ts.map +1 -1
  58. package/dist/libsql/libsqlQuerier.js +3 -4
  59. package/dist/libsql/libsqlQuerier.js.map +1 -1
  60. package/dist/libsql/libsqlQuerierPool.d.ts +2 -1
  61. package/dist/libsql/libsqlQuerierPool.d.ts.map +1 -1
  62. package/dist/libsql/libsqlQuerierPool.js +3 -2
  63. package/dist/libsql/libsqlQuerierPool.js.map +1 -1
  64. package/dist/maria/mariaDialect.d.ts +2 -4
  65. package/dist/maria/mariaDialect.d.ts.map +1 -1
  66. package/dist/maria/mariaDialect.js +1 -5
  67. package/dist/maria/mariaDialect.js.map +1 -1
  68. package/dist/maria/mariadbQuerier.d.ts +2 -1
  69. package/dist/maria/mariadbQuerier.d.ts.map +1 -1
  70. package/dist/maria/mariadbQuerier.js +4 -6
  71. package/dist/maria/mariadbQuerier.js.map +1 -1
  72. package/dist/maria/mariadbQuerierPool.d.ts +2 -1
  73. package/dist/maria/mariadbQuerierPool.d.ts.map +1 -1
  74. package/dist/maria/mariadbQuerierPool.js +3 -2
  75. package/dist/maria/mariadbQuerierPool.js.map +1 -1
  76. package/dist/mongo/mongodbQuerierPool.d.ts +2 -1
  77. package/dist/mongo/mongodbQuerierPool.d.ts.map +1 -1
  78. package/dist/mongo/mongodbQuerierPool.js +2 -3
  79. package/dist/mongo/mongodbQuerierPool.js.map +1 -1
  80. package/dist/mysql/mysql2Querier.d.ts +2 -1
  81. package/dist/mysql/mysql2Querier.d.ts.map +1 -1
  82. package/dist/mysql/mysql2Querier.js +7 -11
  83. package/dist/mysql/mysql2Querier.js.map +1 -1
  84. package/dist/mysql/mysql2QuerierPool.d.ts +2 -1
  85. package/dist/mysql/mysql2QuerierPool.d.ts.map +1 -1
  86. package/dist/mysql/mysql2QuerierPool.js +3 -2
  87. package/dist/mysql/mysql2QuerierPool.js.map +1 -1
  88. package/dist/neon/neonQuerier.d.ts +3 -7
  89. package/dist/neon/neonQuerier.d.ts.map +1 -1
  90. package/dist/neon/neonQuerier.js +2 -18
  91. package/dist/neon/neonQuerier.js.map +1 -1
  92. package/dist/neon/neonQuerierPool.d.ts +4 -4
  93. package/dist/neon/neonQuerierPool.d.ts.map +1 -1
  94. package/dist/neon/neonQuerierPool.js +5 -7
  95. package/dist/neon/neonQuerierPool.js.map +1 -1
  96. package/dist/postgres/abstractPgQuerier.d.ts +18 -0
  97. package/dist/postgres/abstractPgQuerier.d.ts.map +1 -0
  98. package/dist/postgres/abstractPgQuerier.js +33 -0
  99. package/dist/postgres/abstractPgQuerier.js.map +1 -0
  100. package/dist/postgres/abstractPgQuerierPool.d.ts +15 -0
  101. package/dist/postgres/abstractPgQuerierPool.d.ts.map +1 -0
  102. package/dist/postgres/abstractPgQuerierPool.js +10 -0
  103. package/dist/postgres/abstractPgQuerierPool.js.map +1 -0
  104. package/dist/postgres/pgQuerier.d.ts +3 -8
  105. package/dist/postgres/pgQuerier.d.ts.map +1 -1
  106. package/dist/postgres/pgQuerier.js +2 -30
  107. package/dist/postgres/pgQuerier.js.map +1 -1
  108. package/dist/postgres/pgQuerierPool.d.ts +6 -6
  109. package/dist/postgres/pgQuerierPool.d.ts.map +1 -1
  110. package/dist/postgres/pgQuerierPool.js +7 -9
  111. package/dist/postgres/pgQuerierPool.js.map +1 -1
  112. package/dist/postgres/postgresDialect.d.ts +6 -16
  113. package/dist/postgres/postgresDialect.d.ts.map +1 -1
  114. package/dist/postgres/postgresDialect.js +49 -72
  115. package/dist/postgres/postgresDialect.js.map +1 -1
  116. package/dist/querier/abstractQuerierPool.d.ts +5 -3
  117. package/dist/querier/abstractQuerierPool.d.ts.map +1 -1
  118. package/dist/querier/abstractQuerierPool.js +6 -3
  119. package/dist/querier/abstractQuerierPool.js.map +1 -1
  120. package/dist/querier/abstractSqlQuerier.d.ts +5 -0
  121. package/dist/querier/abstractSqlQuerier.d.ts.map +1 -1
  122. package/dist/querier/abstractSqlQuerier.js +15 -5
  123. package/dist/querier/abstractSqlQuerier.js.map +1 -1
  124. package/dist/sqlite/abstractSqliteQuerier.d.ts +0 -2
  125. package/dist/sqlite/abstractSqliteQuerier.d.ts.map +1 -1
  126. package/dist/sqlite/abstractSqliteQuerier.js +0 -9
  127. package/dist/sqlite/abstractSqliteQuerier.js.map +1 -1
  128. package/dist/sqlite/sqliteDialect.d.ts +6 -20
  129. package/dist/sqlite/sqliteDialect.d.ts.map +1 -1
  130. package/dist/sqlite/sqliteDialect.js +20 -33
  131. package/dist/sqlite/sqliteDialect.js.map +1 -1
  132. package/dist/sqlite/sqliteQuerier.d.ts +2 -1
  133. package/dist/sqlite/sqliteQuerier.d.ts.map +1 -1
  134. package/dist/sqlite/sqliteQuerier.js +3 -4
  135. package/dist/sqlite/sqliteQuerier.js.map +1 -1
  136. package/dist/sqlite/sqliteQuerierPool.d.ts +2 -1
  137. package/dist/sqlite/sqliteQuerierPool.d.ts.map +1 -1
  138. package/dist/sqlite/sqliteQuerierPool.js +3 -2
  139. package/dist/sqlite/sqliteQuerierPool.js.map +1 -1
  140. package/dist/type/querierPool.d.ts +16 -0
  141. package/dist/type/querierPool.d.ts.map +1 -1
  142. package/dist/type/query.d.ts +8 -3
  143. package/dist/type/query.d.ts.map +1 -1
  144. package/dist/type/utility.d.ts +4 -0
  145. package/dist/type/utility.d.ts.map +1 -1
  146. package/dist/util/sql.util.d.ts +26 -3
  147. package/dist/util/sql.util.d.ts.map +1 -1
  148. package/dist/util/sql.util.js +51 -9
  149. package/dist/util/sql.util.js.map +1 -1
  150. 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 ?? null);
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: { wrap: (v) => `${v}%`, lower: false },
376
- $istartsWith: { wrap: (v) => `${v.toLowerCase()}%`, lower: false },
377
- $endsWith: { wrap: (v) => `%${v}`, lower: false },
378
- $iendsWith: { wrap: (v) => `%${v.toLowerCase()}`, lower: false },
379
- $includes: { wrap: (v) => `%${v}%`, lower: false },
380
- $iincludes: { wrap: (v) => `%${v.toLowerCase()}%`, lower: false },
381
- $like: { wrap: (v) => v, lower: false },
382
- $ilike: { wrap: (v) => v.toLowerCase(), lower: false },
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
- // Simple comparison operators (5 cases)
432
+ const field = this.resolveOperandField(ctx, entity, key, opts);
386
433
  const simpleOp = AbstractSqlDialect.COMPARE_OP_MAP[op];
387
434
  if (simpleOp) {
388
- this.getComparisonKey(ctx, entity, key, opts);
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
- // LIKE string operators (8 cases)
394
- const likeEntry = AbstractSqlDialect.LIKE_OP_MAP[op];
395
- if (likeEntry) {
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.getComparisonKey(ctx, entity, key, opts);
410
- ctx.append(val === null ? ' IS NOT NULL' : ' <> ');
411
- if (val !== null)
412
- ctx.addValue(val);
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.getComparisonKey(ctx, entity, key, opts);
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.getComparisonKey(ctx, entity, key, opts);
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.getComparisonKey(ctx, entity, key, opts);
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.getComparisonKey(ctx, entity, key, opts);
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. Each dialect provides a `JsonFieldConfig`.
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, config, jsonPath, op, value) {
471
- const jsonField = config.fieldAccessor(jsonPath);
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} = ${config.addValue(ctx, value)}`;
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 config.neExpr
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 `${config.numericCast(jsonField)} > ${config.addValue(ctx, value)}`;
533
+ return `${this.numericCast(jsonField)} > ${this.addValue(ctx.values, value)}`;
483
534
  case '$gte':
484
- return `${config.numericCast(jsonField)} >= ${config.addValue(ctx, value)}`;
535
+ return `${this.numericCast(jsonField)} >= ${this.addValue(ctx.values, value)}`;
485
536
  case '$lt':
486
- return `${config.numericCast(jsonField)} < ${config.addValue(ctx, value)}`;
537
+ return `${this.numericCast(jsonField)} < ${this.addValue(ctx.values, value)}`;
487
538
  case '$lte':
488
- return `${config.numericCast(jsonField)} <= ${config.addValue(ctx, value)}`;
539
+ return `${this.numericCast(jsonField)} <= ${this.addValue(ctx.values, value)}`;
489
540
  case '$like':
490
- return `${jsonField} ${config.likeFn} ${config.addValue(ctx, value)}`;
541
+ return `${jsonField} ${this.likeFn} ${this.addValue(ctx.values, value)}`;
491
542
  case '$ilike':
492
- return config.ilikeExpr(jsonField, config.addValue(ctx, value.toLowerCase()));
543
+ return this.ilikeExpr(jsonField, this.addValue(ctx.values, value.toLowerCase()));
493
544
  case '$startsWith':
494
- return `${jsonField} ${config.likeFn} ${config.addValue(ctx, `${value}%`)}`;
545
+ return `${jsonField} ${this.likeFn} ${this.addValue(ctx.values, `${value}%`)}`;
495
546
  case '$istartsWith':
496
- return config.ilikeExpr(jsonField, config.addValue(ctx, `${value.toLowerCase()}%`));
547
+ return this.ilikeExpr(jsonField, this.addValue(ctx.values, `${value.toLowerCase()}%`));
497
548
  case '$endsWith':
498
- return `${jsonField} ${config.likeFn} ${config.addValue(ctx, `%${value}`)}`;
549
+ return `${jsonField} ${this.likeFn} ${this.addValue(ctx.values, `%${value}`)}`;
499
550
  case '$iendsWith':
500
- return config.ilikeExpr(jsonField, config.addValue(ctx, `%${value.toLowerCase()}`));
551
+ return this.ilikeExpr(jsonField, this.addValue(ctx.values, `%${value.toLowerCase()}`));
501
552
  case '$includes':
502
- return `${jsonField} ${config.likeFn} ${config.addValue(ctx, `%${value}%`)}`;
553
+ return `${jsonField} ${this.likeFn} ${this.addValue(ctx.values, `%${value}%`)}`;
503
554
  case '$iincludes':
504
- return config.ilikeExpr(jsonField, config.addValue(ctx, `%${value.toLowerCase()}%`));
555
+ return this.ilikeExpr(jsonField, this.addValue(ctx.values, `%${value.toLowerCase()}%`));
505
556
  case '$regex':
506
- return `${jsonField} ${config.regexpOp} ${config.addValue(ctx, value)}`;
507
- case '$in': {
508
- if (config.inExpr) {
509
- return config.inExpr(jsonField, config.addValue(ctx, value));
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.config.fieldAccessor(jsonDot.jsonPath) + direction);
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
- * Append a vector similarity ORDER BY expression.
603
- * Base implementation throws subclasses (Postgres, SQLite, MariaDB) override for dialect-specific operators.
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
- appendVectorSort(_ctx, _meta, _key, _search) {
606
- throw new TypeError('Vector similarity sort is not supported by this dialect. Use raw() for vector queries.');
607
- }
649
+ vectorDistanceFns = {};
608
650
  /**
609
- * Shared helper for dialects that use function-call syntax: `fn(col, ?)`.
610
- * Used by SQLite (`vec_distance_cosine`) and MariaDB (`VEC_DISTANCE_COSINE`).
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, fnMap, dialectName) {
613
- const { colName, distance } = this.resolveVectorSortParams(meta, key, search);
614
- const fn = fnMap[distance];
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 new TypeError(`${dialectName} does not support vector distance metric: ${distance}`);
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 (`<expr> AS <alias>`) in the SELECT clause.
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
- const keyword = op === '$in' ? 'IN' : 'NOT IN';
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
- const config = this.getJsonFieldConfig(escapedCol, jsonPath);
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, config } = resolved;
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, config, jsonPath, op, value[op]));
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`).