uql-orm 0.81.0 → 0.83.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.
Files changed (113) hide show
  1. package/README.md +3 -3
  2. package/dist/browser/querier/httpQuerier.d.ts +2 -2
  3. package/dist/browser/querier/httpQuerier.js +2 -1
  4. package/dist/browser/type/clientQuerier.d.ts +2 -2
  5. package/dist/browser/uql-browser.min.js +2 -2
  6. package/dist/browser/uql-browser.min.js.map +9 -8
  7. package/dist/bunSql/bunSql.util.js +2 -1
  8. package/dist/cockroachdb/crdbQuerierPool.js +2 -2
  9. package/dist/dialect/abstractSqlDialect.d.ts +16 -6
  10. package/dist/dialect/abstractSqlDialect.js +110 -41
  11. package/dist/dialect/hydrateColumn.js +2 -12
  12. package/dist/dialect/mysqlLikeSqlDialect.d.ts +2 -0
  13. package/dist/dialect/mysqlLikeSqlDialect.js +5 -0
  14. package/dist/dialect/operators.d.ts +7 -1
  15. package/dist/dialect/operators.js +13 -1
  16. package/dist/dialect/pgLikeSqlDialect.d.ts +1 -0
  17. package/dist/dialect/pgLikeSqlDialect.js +13 -4
  18. package/dist/entity/metadata/definition.d.ts +1 -2
  19. package/dist/entity/metadata/definition.js +37 -39
  20. package/dist/http/handler.js +5 -4
  21. package/dist/http/query.d.ts +1 -1
  22. package/dist/http/query.js +2 -2
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.js +1 -0
  25. package/dist/maria/mariadbQuerierPool.js +4 -2
  26. package/dist/migrate/acquireQuerierForMigrations.js +2 -1
  27. package/dist/migrate/assertCliConfig.js +7 -6
  28. package/dist/migrate/bin.js +0 -0
  29. package/dist/migrate/builder/expressions.d.ts +2 -0
  30. package/dist/migrate/builder/expressions.js +20 -10
  31. package/dist/migrate/builder/tableBuilder.js +1 -1
  32. package/dist/migrate/cli-config.js +5 -4
  33. package/dist/migrate/ddl/indexDdl.js +4 -3
  34. package/dist/migrate/ddl/mysqlIndexDdl.js +5 -4
  35. package/dist/migrate/ddl/pgIndexDdl.js +2 -1
  36. package/dist/migrate/ddl/sqliteIndexDdl.js +2 -1
  37. package/dist/migrate/ddl/tableDdl.js +2 -1
  38. package/dist/migrate/generator/mongoCommand.js +2 -1
  39. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -1
  40. package/dist/migrate/generator/mongoSchemaGenerator.js +7 -6
  41. package/dist/migrate/indexPredicate.js +2 -1
  42. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +2 -1
  43. package/dist/migrate/introspection/mongoIntrospector.d.ts +1 -1
  44. package/dist/migrate/introspection/mongoIntrospector.js +3 -2
  45. package/dist/migrate/introspection/mssqlIntrospector.js +13 -1
  46. package/dist/migrate/introspection/mysqlIntrospector.d.ts +2 -0
  47. package/dist/migrate/introspection/mysqlIntrospector.js +6 -2
  48. package/dist/migrate/introspection/postgresIntrospector.js +1 -1
  49. package/dist/migrate/migrationTarget.js +2 -1
  50. package/dist/migrate/migrator.js +2 -1
  51. package/dist/migrate/schemaGenerator.js +5 -4
  52. package/dist/migrate/storage/databaseStorage.js +1 -1
  53. package/dist/migrate/triggerSql.d.ts +1 -1
  54. package/dist/migrate/triggerSql.js +77 -61
  55. package/dist/mongo/mongoDialect.d.ts +1 -3
  56. package/dist/mongo/mongoDialect.js +9 -14
  57. package/dist/mongo/mongodbQuerier.js +7 -10
  58. package/dist/mssql/mssqlQuerier.d.ts +2 -0
  59. package/dist/mssql/mssqlQuerier.js +8 -5
  60. package/dist/mysql/mysql2QuerierPool.d.ts +1 -0
  61. package/dist/mysql/mysql2QuerierPool.js +20 -2
  62. package/dist/neon/neonQuerierPool.js +2 -2
  63. package/dist/pglite/pgliteQuerierPool.js +10 -4
  64. package/dist/postgres/pgQuerierPool.js +2 -2
  65. package/dist/postgres/{pgNumericTypes.d.ts → pgWireTypes.d.ts} +4 -3
  66. package/dist/postgres/{pgNumericTypes.js → pgWireTypes.js} +7 -3
  67. package/dist/querier/abstractQuerier.d.ts +9 -4
  68. package/dist/querier/abstractQuerier.js +26 -19
  69. package/dist/querier/abstractQuerierPool.d.ts +3 -3
  70. package/dist/querier/abstractSqlQuerier.d.ts +2 -2
  71. package/dist/querier/abstractSqlQuerier.js +1 -1
  72. package/dist/querier/abstractSqlQuerierPool.d.ts +2 -2
  73. package/dist/querier/queryError.d.ts +2 -2
  74. package/dist/schema/canonicalType.d.ts +3 -0
  75. package/dist/schema/canonicalType.js +31 -9
  76. package/dist/schema/schemaASTBuilder.js +2 -1
  77. package/dist/schema/schemaASTDiffer.js +4 -2
  78. package/dist/sqlite/sqliteDialect.d.ts +1 -3
  79. package/dist/sqlite/sqliteDialect.js +3 -6
  80. package/dist/type/dialect.d.ts +23 -1
  81. package/dist/type/entity.d.ts +16 -12
  82. package/dist/type/logger.d.ts +2 -2
  83. package/dist/type/querier.d.ts +3 -3
  84. package/dist/type/query.d.ts +3 -13
  85. package/dist/type/queryAggregate.d.ts +4 -10
  86. package/dist/type/queryRaw.d.ts +17 -3
  87. package/dist/type/queryRaw.js +2 -1
  88. package/dist/type/queryWhere.d.ts +7 -7
  89. package/dist/type/universalQuerier.d.ts +3 -3
  90. package/dist/type/vector.d.ts +2 -1
  91. package/dist/type/vector.js +2 -1
  92. package/dist/util/date.d.ts +11 -0
  93. package/dist/util/date.js +19 -0
  94. package/dist/util/dialect.util.d.ts +13 -5
  95. package/dist/util/dialect.util.js +28 -20
  96. package/dist/util/field.util.d.ts +4 -4
  97. package/dist/util/field.util.js +10 -2
  98. package/dist/util/fieldOption.util.d.ts +5 -3
  99. package/dist/util/fieldOption.util.js +6 -5
  100. package/dist/util/hook.util.d.ts +1 -1
  101. package/dist/util/hook.util.js +8 -1
  102. package/dist/util/index.d.ts +1 -0
  103. package/dist/util/index.js +1 -0
  104. package/dist/util/logger.d.ts +3 -3
  105. package/dist/util/object.util.js +3 -2
  106. package/dist/util/raw.d.ts +6 -7
  107. package/dist/util/raw.js +10 -12
  108. package/dist/util/sqlLiteral.d.ts +8 -1
  109. package/dist/util/sqlLiteral.js +14 -9
  110. package/dist/util/triggerWrite.d.ts +15 -0
  111. package/dist/util/triggerWrite.js +20 -0
  112. package/package.json +1 -1
  113. package/skills/uql-orm/SKILL.md +4 -4
@@ -1,12 +1,12 @@
1
1
  import { fieldOf, getMeta, relationOf, soleIdOf } from '../entity/index.js';
2
2
  import { COUNT_RESULT_KEY, parseQueryLock, QueryRaw, RAW_ALIAS, } from '../type/index.js';
3
3
  import { isInlinedExpression } from '../util/field.util.js';
4
- import { asSelectMap, assertNonNegativeInteger, escapeSqlId, fillOnFields, filterFieldKeys, getInsertFieldKeys, getKeys, getRelationRequestSummary, getSoftDeleteValue, hasKeys, idOnlyQuery, columnFamily, countedRelations, fieldUpdateOf, fulltextIndexOver, fulltextWeights, isFieldUpdateOp, isJsonObject, isJsonUpdateOp, isOperatorMap, isOperatorKey, isVectorSearch, normalizeScalarFieldSelection, parentJoins, rankedTextSearch, targetKeyColumns, textSearchFields, textSortOf, textWeightSteps, parseGroupMap, parseRelationAtKey, parseRelationSize, populatesRelations, aggregateOf, raw, refs, throwUnknownAggregateColumn, withoutSoftDeleteFilter, } from '../util/index.js';
4
+ import { asSelectMap, assertNonNegativeInteger, assertWhere, definedEntries, escapeSqlId, fillOnFields, filterFieldKeys, getInsertFieldKeys, getKeys, getRelationRequestSummary, getSoftDeleteValue, hasKeys, idOnlyQuery, columnFamily, countedRelations, fieldUpdateOf, fulltextIndexOver, fulltextWeights, isFieldUpdateOp, isJsonObject, isJsonUpdateOp, isOperatorMap, isOperatorKey, isVectorSearch, normalizeScalarFieldSelection, parentJoins, rankedTextSearch, targetKeyColumns, textSearchFields, textSortOf, textWeightSteps, parseGroupMap, parseRelationAtKey, parseRelationSize, populatesRelations, aggregateOf, raw, refs, throwUnknownAggregateColumn, withoutSoftDeleteFilter, } from '../util/index.js';
5
5
  import { escapeAnsiSqlLiteral } from '../util/sqlLiteral.js';
6
6
  import { kindOf, UqlUsageError } from '../util/uqlError.js';
7
7
  import { AGGREGATE_PAGE_ALIAS, AGGREGATE_VALUE_ALIAS, ROWS_ALIAS, JSON_ELEM_ALIAS, JSON_PULL_ALIAS, relationSortColumn, } from './aliases.js';
8
8
  import { holdsOperator, isJsonScalar, jsonCompareMode, jsonElemExists, jsonPath, } from './jsonSql.js';
9
- import { betweenBounds, COMPARE_OPS, EQUALITY_OPS, GROUP_OPS, groupClauses, inOperands, isGroupOp, isOrderedOp, isVectorQuery, LIKE_OPS, ORDERED_OPS, VECTOR_QUERY_KEY_SET, whereOperators, } from './operators.js';
9
+ import { betweenBounds, COMPARE_OPS, EQUALITY_OPS, GROUP_OPS, groupClauses, inOperands, isGroupOp, isOrderedOp, isVectorQuery, LIKE_OPS, namesRows, ORDERED_OPS, VECTOR_QUERY_KEY_SET, whereOperators, } from './operators.js';
10
10
  import { SqlQueryContext } from './queryContext.js';
11
11
  import { groupPathField, NO_JOINS, aggregateColumnField, resolveGroupJoins, resolveQueryJoins, relationSortTerms, resolveSortableJoin, } from './queryJoins.js';
12
12
  import { resolveVectorCast } from './vectorCast.js';
@@ -34,6 +34,13 @@ function orRefuse(value, refusal) {
34
34
  }
35
35
  return value;
36
36
  }
37
+ /**
38
+ * Whether an assignment builds on what each row before it left: `$inc`, `$mul`, `$push`. A set-based
39
+ * `UPDATE` writes a target once however many rows of its set match it, so it would apply one of them.
40
+ */
41
+ function accumulates(value) {
42
+ return isFieldUpdateOp(value) || (isJsonUpdateOp(value) && value.$push !== undefined);
43
+ }
37
44
  /** Conditions joined by `AND`, parenthesized where there is more than one. */
38
45
  function conjunction(parts) {
39
46
  return parts.length > 1 ? `(${parts.join(' AND ')})` : parts.join('');
@@ -178,7 +185,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
178
185
  returningIdExpression(meta) {
179
186
  const [idKey] = meta.ids;
180
187
  return meta.ids.length === 1
181
- ? `${this.returnedRowPrefix}${this.escapeId(this.columnOf(meta, idKey))} ${this.escapeId('id')}`
188
+ ? `${this.returnedRowPrefix}${this.escapedColumnName(meta, idKey)} ${this.escapeId('id')}`
182
189
  : '';
183
190
  }
184
191
  search(ctx, entity, q = {}, opts = {}, joins = NO_JOINS, order) {
@@ -423,7 +430,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
423
430
  join.relation.references.forEach((reference, index) => {
424
431
  if (index > 0)
425
432
  ctx.append(' AND ');
426
- const foreign = this.escapeId(this.columnOf(join.meta, reference.foreign));
433
+ const foreign = this.escapedColumnName(join.meta, reference.foreign);
427
434
  // Two calls rather than one over a union: the parent is either another join's entity or the
428
435
  // queried one, and their metadata types have nothing in common.
429
436
  const local = this.escapeId(join.parent ? this.columnOf(join.parent.meta, reference.local) : this.columnOf(meta, reference.local));
@@ -1254,30 +1261,99 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1254
1261
  const meta = getMeta(entity);
1255
1262
  const [filledPayload] = fillOnFields(meta, payload, 'onUpdate');
1256
1263
  const keys = filterFieldKeys(meta, filledPayload, 'onUpdate');
1257
- const tableName = this.escapedTableName(meta);
1258
- ctx.append(`UPDATE ${tableName} SET `);
1264
+ ctx.append(`UPDATE ${this.escapedTableName(meta)} SET `);
1259
1265
  for (let i = 0; i < keys.length; i++) {
1260
1266
  if (i > 0) {
1261
1267
  ctx.append(', ');
1262
1268
  }
1263
- const key = keys[i];
1264
- const field = meta.fields[key];
1265
- const escapedCol = this.escapedColumnName(meta, key);
1266
- const value = filledPayload[key];
1267
- if (isJsonUpdateOp(value)) {
1268
- this.formatJsonUpdate(ctx, escapedCol, value, field);
1269
- }
1270
- else if (isFieldUpdateOp(value)) {
1271
- const [op, operand] = fieldUpdateOf(key, value);
1272
- ctx.append(`${escapedCol} = COALESCE(${escapedCol}, 0) ${SQL_ARITHMETIC[op]} `);
1273
- ctx.addValue(operand);
1269
+ this.appendAssignment(ctx, meta, keys[i], filledPayload[keys[i]]);
1270
+ }
1271
+ this.search(ctx, entity, q, opts);
1272
+ }
1273
+ /** `col = value`, a JSON operator or a `$inc`/`$mul` spelled as the engine's. */
1274
+ appendAssignment(ctx, meta, key, value) {
1275
+ const field = meta.fields[key];
1276
+ const escapedCol = this.escapedColumnName(meta, key);
1277
+ if (isJsonUpdateOp(value)) {
1278
+ this.formatJsonUpdate(ctx, escapedCol, value, field);
1279
+ }
1280
+ else if (isFieldUpdateOp(value)) {
1281
+ const [op, operand] = fieldUpdateOf(key, value);
1282
+ ctx.append(`${escapedCol} = COALESCE(${escapedCol}, 0) ${SQL_ARITHMETIC[op]} `);
1283
+ ctx.addValue(operand);
1284
+ }
1285
+ else {
1286
+ ctx.append(`${escapedCol} = `);
1287
+ this.formatPersistableValue(ctx, field, value);
1288
+ }
1289
+ }
1290
+ /**
1291
+ * A write in a trigger's body. Only the columns it names, none filled in JavaScript - that would bake
1292
+ * one value into the trigger - no id read back, and no entity filter, which a request resolves and a
1293
+ * trigger has none of. `rows` is the `FROM` a set-based engine's body reads its rows through.
1294
+ */
1295
+ triggerWrite(ctx, write, rows) {
1296
+ const meta = getMeta(write.entity);
1297
+ const table = this.escapedTableName(meta);
1298
+ if (write.kind === 'insert') {
1299
+ const unfilled = definedEntries(meta.fields).find(([key, field]) => field.onInsert !== undefined && field.defaultValue === undefined && write.row[key] === undefined);
1300
+ if (unfilled) {
1301
+ throw new UqlUsageError(`'${meta.name}.${unfilled[0]}' is filled on insert by uql, which a trigger does not run: ` +
1302
+ 'name it, or give it a defaultValue');
1274
1303
  }
1275
- else {
1276
- ctx.append(`${escapedCol} = `);
1277
- this.formatPersistableValue(ctx, field, value);
1304
+ const entries = this.writtenEntries(meta, write.row);
1305
+ const columns = entries.map(([key]) => this.escapedColumnName(meta, key)).join(', ');
1306
+ const [open, close] = rows ? ['SELECT ', ` ${rows};`] : ['VALUES (', ');'];
1307
+ ctx.append(`INSERT INTO ${table} (${columns}) ${open}`);
1308
+ entries.forEach(([key, value], i) => {
1309
+ if (i > 0) {
1310
+ ctx.append(', ');
1311
+ }
1312
+ this.formatPersistableValue(ctx, meta.fields[key], value);
1313
+ });
1314
+ ctx.append(close);
1315
+ return;
1316
+ }
1317
+ assertWhere(meta, write.where);
1318
+ if (!namesRows(write.where)) {
1319
+ throw new UqlUsageError(`a trigger's ${write.kind} over '${meta.name}' names no rows, so it would address every one`);
1320
+ }
1321
+ if (write.kind === 'update') {
1322
+ const entries = this.writtenEntries(meta, write.set);
1323
+ const accumulating = rows && entries.find(([, value]) => accumulates(value));
1324
+ if (accumulating) {
1325
+ throw new UqlUsageError(`'${meta.name}.${accumulating[0]}' cannot accumulate per row in a trigger fired once per statement, ` +
1326
+ 'which applies $inc, $mul and $push once');
1278
1327
  }
1328
+ ctx.append(`UPDATE ${table} SET `);
1329
+ entries.forEach(([key, value], i) => {
1330
+ if (i > 0) {
1331
+ ctx.append(', ');
1332
+ }
1333
+ this.appendAssignment(ctx, meta, key, value);
1334
+ });
1279
1335
  }
1280
- this.search(ctx, entity, q, opts);
1336
+ else {
1337
+ ctx.append(`DELETE FROM ${table}`);
1338
+ }
1339
+ if (rows) {
1340
+ ctx.append(` ${rows}`);
1341
+ }
1342
+ // Qualified by the table: on a set-based engine `inserted` holds the same column names.
1343
+ this.renderWhere(ctx, write.entity, write.where, { escapedPrefix: `${table}.` });
1344
+ ctx.append(';');
1345
+ }
1346
+ /** The values a write names, refusing one the entity has no column for. */
1347
+ writtenEntries(meta, values) {
1348
+ const entries = Object.entries(values).filter(([, value]) => value !== undefined);
1349
+ const unknown = entries.find(([key]) => !meta.fields[key]);
1350
+ if (unknown) {
1351
+ throw new UqlUsageError(`'${meta.name}' has no field '${unknown[0]}' for a trigger to write`);
1352
+ }
1353
+ if (!entries.length) {
1354
+ throw new UqlUsageError(`a trigger's write to '${meta.name}' names no field`);
1355
+ }
1356
+ return entries;
1281
1357
  }
1282
1358
  /**
1283
1359
  * `INSERT ... ON CONFLICT ... DO UPDATE/NOTHING RETURNING`. The assignments are built before the insert
@@ -1312,23 +1388,18 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1312
1388
  return fields
1313
1389
  .filter((col) => !conflictPaths[col])
1314
1390
  .map((col) => {
1315
- const field = meta.fields[col];
1316
- const columnName = this.resolveColumnName(col, field);
1391
+ const column = this.escapedColumnName(meta, col);
1317
1392
  if (Object.hasOwn(sample, col)) {
1318
- return `${this.escapeId(columnName)} = ${callback(this.escapeId(columnName))}`;
1393
+ return `${column} = ${callback(column)}`;
1319
1394
  }
1320
- const text = this.buildFragment(ctx, (fragmentCtx) => this.formatPersistableValue(fragmentCtx, field, filledPayload[col]));
1321
- return `${this.escapeId(columnName)} = ${text}`;
1395
+ const text = this.buildFragment(ctx, (fragmentCtx) => this.formatPersistableValue(fragmentCtx, meta.fields[col], filledPayload[col]));
1396
+ return `${column} = ${text}`;
1322
1397
  })
1323
1398
  .join(', ');
1324
1399
  }
1325
1400
  getUpsertConflictPathsStr(meta, conflictPaths) {
1326
1401
  return getKeys(conflictPaths)
1327
- .map((key) => {
1328
- const field = meta.fields[key];
1329
- const columnName = this.resolveColumnName(key, field);
1330
- return this.escapeId(columnName);
1331
- })
1402
+ .map((key) => this.escapedColumnName(meta, key))
1332
1403
  .join(', ');
1333
1404
  }
1334
1405
  delete(ctx, entity, q, opts = {}) {
@@ -1337,10 +1408,8 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1337
1408
  // Soft-delete (stamp only live rows) unless `hardDelete` is requested or the entity has no
1338
1409
  // soft-delete field (e.g. a cascade onto a non-soft-deletable child).
1339
1410
  if (!opts.hardDelete && meta.softDelete) {
1340
- const field = fieldOf(meta, meta.softDelete);
1341
- const columnName = this.resolveColumnName(meta.softDelete, field);
1342
- ctx.append(`UPDATE ${tableName} SET ${this.escapeId(columnName)} = `);
1343
- this.formatPersistableValue(ctx, field, getSoftDeleteValue(field));
1411
+ ctx.append(`UPDATE ${tableName} SET `);
1412
+ this.appendAssignment(ctx, meta, meta.softDelete, getSoftDeleteValue(fieldOf(meta, meta.softDelete)));
1344
1413
  this.search(ctx, entity, q, opts);
1345
1414
  return;
1346
1415
  }
@@ -1537,19 +1606,19 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1537
1606
  jsonDiffers(elem, operand) {
1538
1607
  return `${elem} <> ${operand}`;
1539
1608
  }
1540
- compileDdl(sql, entity) {
1609
+ compileDdl(sql, entity, { rows, escapedPrefix, operand } = {}) {
1541
1610
  const ctx = this.createContext({ inlineValues: true });
1542
1611
  if (sql instanceof QueryRaw) {
1543
- sql.render({ ctx, dialect: this, prefix: '', escapedPrefix: '', entity });
1612
+ sql.render({ ctx, dialect: this, prefix: '', escapedPrefix: escapedPrefix ?? '', entity, rows });
1544
1613
  }
1545
1614
  else if (entity) {
1546
- this.renderWhere(ctx, entity, sql, { clause: false });
1615
+ this.renderWhere(ctx, entity, sql, { clause: false, escapedPrefix, operand });
1547
1616
  }
1548
1617
  else {
1549
- throw new TypeError('a predicate compiles against the entity it is written for, and none was given');
1618
+ throw new UqlUsageError('a predicate compiles against the entity it is written for, and none was given');
1550
1619
  }
1551
1620
  if (ctx.values.length) {
1552
- throw new TypeError(`DDL has no placeholder to bind a value into, and this SQL left one bound: ${ctx.sql}`);
1621
+ throw new UqlUsageError(`DDL has no placeholder to bind a value into, and this SQL left one bound: ${ctx.sql}`);
1553
1622
  }
1554
1623
  return ctx.sql;
1555
1624
  }
@@ -1884,7 +1953,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1884
1953
  const distance = (fragmentCtx) => this.appendVectorDistance(fragmentCtx, meta, key, near, prefix);
1885
1954
  return this.boundConditions(ctx, distance, bounds, (operand, op, val) => (isOrderedOp(op) ? this.operatorCondition(ctx, operand, op, val) : undefined), 'unsupported $near bound');
1886
1955
  }
1887
- /** ANSI-style single-quote escaping. MySQL-family dialects override this for backslash escaping. */
1956
+ /** ANSI-style single-quote escaping. */
1888
1957
  escape(value) {
1889
1958
  return escapeAnsiSqlLiteral(value);
1890
1959
  }
@@ -1,3 +1,4 @@
1
+ import { decodeDate } from '../util/date.js';
1
2
  import { decodeWideNumber } from '../util/wideNumber.js';
2
3
  import { decodeFloat32s, parseVectorLiteral } from './vectorCast.js';
3
4
  /**
@@ -31,7 +32,7 @@ const float32Decoder = (value) => {
31
32
  const DECODERS = {
32
33
  // 0/1 from SQLite's INTEGER or MySQL's TINYINT(1). Already a boolean on Postgres.
33
34
  boolean: (value) => (typeof value === 'boolean' ? value : Boolean(value)),
34
- date: (value) => (typeof value === 'string' ? (parseDate(value) ?? value) : value),
35
+ date: (value) => (typeof value === 'string' ? decodeDate(value) : value),
35
36
  // Only a string can be bytes that crossed JSON: bytes a driver already decoded stay as they are.
36
37
  bytes: (value) => typeof value === 'string' && value.startsWith(BYTES_PREFIX) ? hexBytes(value.slice(BYTES_PREFIX.length)) : value,
37
38
  // A number too, not just text: `type: BigInt` is BIGINT, which the pg pools decode at the wire.
@@ -61,17 +62,6 @@ const DECODERS = {
61
62
  halfvec: vectorDecoder('halfvec'),
62
63
  sparsevec: vectorDecoder('sparsevec'),
63
64
  };
64
- /**
65
- * An ISO 8601 timestamp as a `Date`, its fraction cut to the milliseconds one holds, and a bare date at
66
- * local midnight, which is how `pg` reads a `date`. `undefined` for text that is neither.
67
- */
68
- function parseDate(text) {
69
- const day = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
70
- const date = day
71
- ? new Date(Number(day[1]), Number(day[2]) - 1, Number(day[3]))
72
- : new Date(text.replace(/(\.\d{3})\d+/, '$1'));
73
- return Number.isNaN(date.getTime()) ? undefined : date;
74
- }
75
65
  /**
76
66
  * What bytes crossing JSON start with, before two hex digits per byte: Postgres's own text for `bytea`,
77
67
  * which every dialect spells, so a string a driver reads from a column on its own is never mistaken.
@@ -78,6 +78,8 @@ export declare abstract class MysqlLikeSqlDialect extends AbstractSqlDialect {
78
78
  /** Bytes as the hex text `decodeColumn` reads back, whole. */
79
79
  protected bytesAsText(expr: string): string;
80
80
  escape(value: unknown): string;
81
+ /** A date as UTC text, which a `DATETIME` stores as is, where a driver would convert it to its own zone. */
82
+ normalizeValue(value: unknown): unknown;
81
83
  /**
82
84
  * `MATCH(cols) AGAINST(?)`, which needs a `FULLTEXT` index over exactly those columns: without one
83
85
  * the server answers "Can't find FULLTEXT index matching the column list". Declare it with
@@ -1,4 +1,5 @@
1
1
  import { getMeta } from '../entity/index.js';
2
+ import { utcTimestamp } from '../util/date.js';
2
3
  import { textSearchFields } from '../util/index.js';
3
4
  import { escapeMysqlSqlLiteral, escapeSingleQuotes } from '../util/sqlLiteral.js';
4
5
  import { AbstractSqlDialect, } from './abstractSqlDialect.js';
@@ -187,6 +188,10 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
187
188
  escape(value) {
188
189
  return escapeMysqlSqlLiteral(value);
189
190
  }
191
+ /** A date as UTC text, which a `DATETIME` stores as is, where a driver would convert it to its own zone. */
192
+ normalizeValue(value) {
193
+ return value instanceof Date ? utcTimestamp(value) : super.normalizeValue(value);
194
+ }
190
195
  /**
191
196
  * `MATCH(cols) AGAINST(?)`, which needs a `FULLTEXT` index over exactly those columns: without one
192
197
  * the server answers "Can't find FULLTEXT index matching the column list". Declare it with
@@ -1,4 +1,4 @@
1
- import { type QueryGroupOp, type QueryOrderedOp, type QueryVectorQuery, type QueryWhereArray, type QueryWhereFieldOp } from '../type/index.js';
1
+ import { type QueryGroupOp, type QueryOrderedOp, type QueryVectorQuery, type QueryWhere, type QueryWhereArray, type QueryWhereFieldOp } from '../type/index.js';
2
2
  /**
3
3
  * How each grouping operator renders: the operator joining its clauses, and whether the group is
4
4
  * negated (`$not` is `NOT (a AND b)`). Total over {@link QueryGroupOp}.
@@ -29,6 +29,12 @@ export declare function isGroupOp(key: string): key is QueryGroupOp;
29
29
  * refuse the same payload rather than one throwing and the other failing further in.
30
30
  */
31
31
  export declare function groupClauses<E>(key: QueryGroupOp, val: QueryWhereArray<E> | undefined): QueryWhereArray<E>;
32
+ /**
33
+ * Whether a `$where` names any rows, as the WHERE it renders would: an `undefined` value, an empty
34
+ * operator map and a group none of whose clauses names one all render nothing, which a write reads as
35
+ * the whole table.
36
+ */
37
+ export declare function namesRows<E>(where: QueryWhere<E> | undefined): boolean;
32
38
  /**
33
39
  * A WHERE value as the operators it applies: an array is `$in`, an operator map its own entries, and
34
40
  * anything else `$eq`. Each key is checked here, once, refused as `refusal` where it is no operator.
@@ -1,5 +1,6 @@
1
- import { VECTOR_QUERY_KEYS, } from '../type/index.js';
1
+ import { QueryRaw, VECTOR_QUERY_KEYS, } from '../type/index.js';
2
2
  import { isOperatorMap } from '../util/dialect.util.js';
3
+ import { hasKeys, isWhereMap, someKey } from '../util/object.util.js';
3
4
  import { kindOf, UqlUsageError } from '../util/uqlError.js';
4
5
  /**
5
6
  * How each grouping operator renders: the operator joining its clauses, and whether the group is
@@ -26,6 +27,17 @@ export function groupClauses(key, val) {
26
27
  }
27
28
  return val ?? [];
28
29
  }
30
+ /**
31
+ * Whether a `$where` names any rows, as the WHERE it renders would: an `undefined` value, an empty
32
+ * operator map and a group none of whose clauses names one all render nothing, which a write reads as
33
+ * the whole table.
34
+ */
35
+ export function namesRows(where) {
36
+ return (where !== undefined &&
37
+ someKey(where, (key) => isGroupOp(key)
38
+ ? groupClauses(key, where[key]).some((clause) => clause instanceof QueryRaw || namesRows(clause))
39
+ : where[key] !== undefined && !(isWhereMap(where[key]) && !hasKeys(where[key]))));
40
+ }
29
41
  /**
30
42
  * A WHERE value as the operators it applies: an array is `$in`, an operator map its own entries, and
31
43
  * anything else `$eq`. Each key is checked here, once, refused as `refusal` where it is no operator.
@@ -105,6 +105,7 @@ export declare abstract class PgLikeSqlDialect extends AbstractSqlDialect {
105
105
  * array takes its type from `operand`, so it needs none of the casts `bind` would give each value.
106
106
  */
107
107
  protected formatIn(ctx: QueryContext, operand: string, values: unknown[], negate: boolean, bind: (value: unknown) => string): string;
108
+ escape(value: unknown): string;
108
109
  protected numericCast(expr: string): string;
109
110
  protected appendJsonValue(ctx: QueryContext, value: unknown, type: JsonColumnType): void;
110
111
  /**
@@ -1,6 +1,7 @@
1
1
  import { QueryRaw, } from '../type/index.js';
2
+ import { utcTimestamp } from '../util/date.js';
2
3
  import { fulltextConfig, fulltextIndexOver, hasVectorNear, textSearchFields } from '../util/dialect.util.js';
3
- import { escapeSingleQuotes } from '../util/sqlLiteral.js';
4
+ import { escapePgSqlLiteral, escapeSingleQuotes, PG_UTC } from '../util/sqlLiteral.js';
4
5
  import { AbstractSqlDialect } from './abstractSqlDialect.js';
5
6
  import { JSON_PULL_ALIAS, RELATION_ROW_ALIAS } from './aliases.js';
6
7
  import { BYTES_PREFIX } from './hydrateColumn.js';
@@ -128,10 +129,11 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
128
129
  return statements;
129
130
  }
130
131
  normalizeValue(value) {
131
- if (value != null && typeof value === 'object' && Array.isArray(value)) {
132
- return this.driverCapabilities.nativeArrays ? value : toPgArray(value);
132
+ if (Array.isArray(value)) {
133
+ const values = value.map(utcDate);
134
+ return this.driverCapabilities.nativeArrays ? values : toPgArray(values);
133
135
  }
134
- return super.normalizeValue(value);
136
+ return super.normalizeValue(utcDate(value));
135
137
  }
136
138
  placeholder(index) {
137
139
  return `$${index}`;
@@ -240,6 +242,9 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
240
242
  const ph = this.addValue(ctx, values);
241
243
  return negate ? `${operand} <> ALL(${ph})` : `${operand} = ANY(${ph})`;
242
244
  }
245
+ escape(value) {
246
+ return escapePgSqlLiteral(value);
247
+ }
243
248
  numericCast(expr) {
244
249
  return `(${expr})::numeric`;
245
250
  }
@@ -306,6 +311,10 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
306
311
  return this.driverCapabilities.explicitJsonCast ? `(${ph}::text)::${type}` : `${ph}::${type}`;
307
312
  }
308
313
  }
314
+ /** A date as UTC text, which a zoneless `TIMESTAMP` stores as is, where each driver would pick its own zone. */
315
+ function utcDate(value) {
316
+ return value instanceof Date ? utcTimestamp(value, PG_UTC) : value;
317
+ }
309
318
  /**
310
319
  * Converts a JS array to a Postgres array literal string: `{"val1","val2"}`.
311
320
  * Safely handles nesting and escaping of special characters.
@@ -45,7 +45,7 @@ export declare function fieldOf<E>(meta: EntityMeta<E>, key: string): FieldMeta;
45
45
  /** The relation `key` names, for a caller that took `key` from the metadata itself. */
46
46
  export declare function relationOf<E>(meta: EntityMeta<E>, key: RelationKey<E>): RelationMeta;
47
47
  /** A row of `E` as far as reading its key goes: a record or a write, whatever its values. */
48
- type KeyedRow<E> = {
48
+ export type KeyedRow<E> = {
49
49
  readonly [K in keyof E]?: unknown;
50
50
  };
51
51
  /** Whether the row names every column of its primary key, `0` and `''` included. */
@@ -67,4 +67,3 @@ export declare function getMeta<E>(entity: Type<E>): EntityMeta<E>;
67
67
  * schema build constrains and a junction joins by, settling the relations holding them first.
68
68
  */
69
69
  export declare function foreignKeysOf<E>(meta: EntityMeta<E>): RelationMeta[];
70
- export {};