@atscript/db-sql-tools 0.1.161 → 0.1.162

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/dist/index.cjs CHANGED
@@ -213,11 +213,12 @@ function orderKeySql(dialect, expr, desc, nulls, nullTest) {
213
213
  * reads the same on every dialect (SQLite's quoted path labels have no escape
214
214
  * syntax), so it is rejected rather than escaped.
215
215
  *
216
+ * An empty path (the JSON root — a primitive JSON column's value) has no segments.
217
+ *
216
218
  * @since 0.1.136
217
- * @throws for an empty path or a segment that is empty or holds such a character.
219
+ * @throws for a segment that is empty or holds such a character.
218
220
  */
219
221
  function quotedJsonPathSegments(path) {
220
- if (path.length === 0) throw new Error("JSON extraction needs a path below the JSON column");
221
222
  return path.map((seg) => {
222
223
  let ok = seg.length > 0;
223
224
  for (let i = 0; ok && i < seg.length; i++) {
@@ -522,15 +523,24 @@ function sqlStringLiteral(value) {
522
523
  return `'${value.replace(/'/g, "''")}'`;
523
524
  }
524
525
  /**
526
+ * The value of a single-quoted SQL string literal (`'it''s'` → `it's`) — as
527
+ * engines report a column default — or `undefined` when `literal` is not one.
528
+ * @since 0.1.162
529
+ */
530
+ function unquoteSqlStringLiteral(literal) {
531
+ if (literal.length < 2 || !literal.startsWith("'") || !literal.endsWith("'")) return void 0;
532
+ return literal.slice(1, -1).replace(/''/g, "'");
533
+ }
534
+ /**
525
535
  * The SQL string literal of a `$`-rooted JSON path with every segment quoted
526
536
  * (`'$."a"."b"'`) — the path argument of SQLite's and MySQL's `json_extract` /
527
- * `json_type`.
537
+ * `json_type`; an empty path is the JSON root (`'$'`).
528
538
  *
529
539
  * @since 0.1.136
530
540
  * @throws as {@link quotedJsonPathSegments}.
531
541
  */
532
- function jsonDollarPath(path) {
533
- return sqlStringLiteral(`$.${quotedJsonPathSegments(path).join(".")}`);
542
+ function jsonDollarPath(path, stringLiteral = sqlStringLiteral) {
543
+ return stringLiteral(["$", ...quotedJsonPathSegments(path)].join("."));
534
544
  }
535
545
  /**
536
546
  * A calendar bucket's time zone as an inlined SQL string literal (`'Europe/Berlin'`).
@@ -596,7 +606,7 @@ function defaultValueForType(designType) {
596
606
  * respecting the field's designType. Booleans become 0/1, numbers stay unquoted,
597
607
  * strings are single-quote-escaped.
598
608
  */
599
- function defaultValueToSqlLiteral(designType, value) {
609
+ function defaultValueToSqlLiteral(designType, value, stringLiteral = sqlStringLiteral) {
600
610
  switch (designType) {
601
611
  case "boolean": return value === "true" || value === "1" ? "1" : "0";
602
612
  case "number":
@@ -605,7 +615,7 @@ function defaultValueToSqlLiteral(designType, value) {
605
615
  const n = Number(value);
606
616
  return Number.isFinite(n) ? String(n) : "0";
607
617
  }
608
- default: return sqlStringLiteral(value);
618
+ default: return stringLiteral(value);
609
619
  }
610
620
  }
611
621
  const queryOpToSql = {
@@ -617,9 +627,9 @@ const queryOpToSql = {
617
627
  $lte: "<="
618
628
  };
619
629
  /** An inlined SQL literal for a view-predicate value (DDL — no parameters). */
620
- function predicateLiteral(value) {
630
+ function predicateLiteral(value, stringLiteral) {
621
631
  if (value === null || value === void 0) return "NULL";
622
- if (typeof value === "string") return sqlStringLiteral(value);
632
+ if (typeof value === "string") return stringLiteral(value);
623
633
  if (typeof value === "number") {
624
634
  if (!Number.isFinite(value)) throw new Error(`Non-finite number ${value} in a view predicate`);
625
635
  return String(value);
@@ -637,18 +647,18 @@ function predicateLiteral(value) {
637
647
  * @throws for `matches` (`$regex`) and any other operator — a view predicate
638
648
  * that cannot be rendered fails at sync instead of rendering wrong SQL.
639
649
  */
640
- function queryNodeToSql(node, resolveFieldRef) {
650
+ function queryNodeToSql(node, resolveFieldRef, stringLiteral = sqlStringLiteral) {
641
651
  if ("$and" in node) {
642
652
  const children = node.$and;
643
653
  if (children.length === 0) return EMPTY_AND.sql;
644
- return children.map((n) => queryNodeToSql(n, resolveFieldRef)).join(" AND ");
654
+ return children.map((n) => queryNodeToSql(n, resolveFieldRef, stringLiteral)).join(" AND ");
645
655
  }
646
656
  if ("$or" in node) {
647
657
  const children = node.$or;
648
658
  if (children.length === 0) return EMPTY_OR.sql;
649
- return `(${children.map((n) => queryNodeToSql(n, resolveFieldRef)).join(" OR ")})`;
659
+ return `(${children.map((n) => queryNodeToSql(n, resolveFieldRef, stringLiteral)).join(" OR ")})`;
650
660
  }
651
- if ("$not" in node) return `NOT (${queryNodeToSql(node.$not, resolveFieldRef)})`;
661
+ if ("$not" in node) return `NOT (${queryNodeToSql(node.$not, resolveFieldRef, stringLiteral)})`;
652
662
  const comp = node;
653
663
  const leftSql = resolveFieldRef(comp.left);
654
664
  switch (comp.op) {
@@ -657,7 +667,7 @@ function queryNodeToSql(node, resolveFieldRef) {
657
667
  case "$nin": {
658
668
  const values = Array.isArray(comp.right) ? comp.right : [comp.right];
659
669
  if (values.length === 0) return comp.op === "$in" ? EMPTY_OR.sql : EMPTY_AND.sql;
660
- const list = values.map((v) => predicateLiteral(v)).join(", ");
670
+ const list = values.map((v) => predicateLiteral(v, stringLiteral)).join(", ");
661
671
  return `${leftSql} ${comp.op === "$in" ? "IN" : "NOT IN"} (${list})`;
662
672
  }
663
673
  case "$regex": throw new Error("matches is not supported in view predicates");
@@ -667,7 +677,7 @@ function queryNodeToSql(node, resolveFieldRef) {
667
677
  if (!sqlOp) throw new Error(`Operator "${comp.op}" is not supported in view predicates`);
668
678
  if ((0, _atscript_db.isFieldRef)(comp.right)) return `${leftSql} ${sqlOp} ${resolveFieldRef(comp.right)}`;
669
679
  if (comp.right === null || comp.right === void 0) return comp.op === "$ne" ? `${leftSql} IS NOT NULL` : `${leftSql} IS NULL`;
670
- return `${leftSql} ${sqlOp} ${predicateLiteral(comp.right)}`;
680
+ return `${leftSql} ${sqlOp} ${predicateLiteral(comp.right, stringLiteral)}`;
671
681
  }
672
682
  //#endregion
673
683
  //#region src/agg.ts
@@ -1001,7 +1011,7 @@ function viewAggExpr(dialect, c, resolveFieldRef) {
1001
1011
  const star = c.aggField === "*";
1002
1012
  const src = star ? "*" : viewSourceExpr(dialect, c);
1003
1013
  if (!c.aggFilter) return renderAggCall(c.aggFn, src, c.viewColumn);
1004
- const predicate = queryNodeToSql(c.aggFilter, resolveFieldRef);
1014
+ const predicate = queryNodeToSql(c.aggFilter, resolveFieldRef, dialect.stringLiteral);
1005
1015
  const call = renderAggCall(c.aggFn, `CASE WHEN ${predicate} THEN ${star ? "1" : src} END`, c.viewColumn);
1006
1016
  return c.aggFn === "sum" ? `COALESCE(${call}, 0)` : call;
1007
1017
  }
@@ -1092,14 +1102,14 @@ function buildViewSelect(dialect, plan, columns, resolveFieldRef) {
1092
1102
  const columnExpr = (c) => c.expr !== void 0 ? viewComputeExpr(dialect, c, columnByPath, resolveFieldRef, hasAggregates, computedSql) : c.aggFn ? viewAggExpr(dialect, c, resolveFieldRef) : viewSourceExpr(dialect, c);
1093
1103
  let sql = `SELECT ${columns.map((c) => `${columnExpr(c)} AS ${dialect.quoteIdentifier(c.viewColumn)}`).join(", ")} FROM ${dialect.quoteIdentifier(plan.entryTable)}`;
1094
1104
  for (const join of plan.joins) {
1095
- const condition = queryNodeToSql(join.condition, resolveFieldRef);
1105
+ const condition = queryNodeToSql(join.condition, resolveFieldRef, dialect.stringLiteral);
1096
1106
  const keyword = join.kind === "left" ? "LEFT JOIN" : "JOIN";
1097
1107
  const target = join.scope === join.targetTable ? dialect.quoteIdentifier(join.targetTable) : `${dialect.quoteIdentifier(join.targetTable)} AS ${dialect.quoteIdentifier(join.scope)}`;
1098
1108
  const onClause = join.first ? firstRowOn(dialect, join, target, condition, resolveFieldRef) : condition;
1099
1109
  sql += ` ${keyword} ${target} ON ${onClause}`;
1100
1110
  }
1101
1111
  if (plan.filter) {
1102
- const whereClause = queryNodeToSql(plan.filter, resolveFieldRef);
1112
+ const whereClause = queryNodeToSql(plan.filter, resolveFieldRef, dialect.stringLiteral);
1103
1113
  sql += ` WHERE ${whereClause}`;
1104
1114
  }
1105
1115
  if (hasAggregates) {
@@ -1120,7 +1130,7 @@ function buildViewSelect(dialect, plan, columns, resolveFieldRef) {
1120
1130
  if (!col.json) return expr;
1121
1131
  return dialect.bucketAliasInHaving && groupedColumns().has(col.viewColumn.toLowerCase()) ? `MIN(${expr})` : havingGroupRef(dialect, expr, col.viewColumn);
1122
1132
  };
1123
- const havingClause = queryNodeToSql(plan.having, havingResolver);
1133
+ const havingClause = queryNodeToSql(plan.having, havingResolver, dialect.stringLiteral);
1124
1134
  sql += ` HAVING ${havingClause}`;
1125
1135
  }
1126
1136
  }
@@ -1244,8 +1254,9 @@ function buildInsertMany(dialect, table, rows, columns = insertManyColumns(rows)
1244
1254
  /**
1245
1255
  * Builds a SELECT statement with optional sort, limit, offset, projection.
1246
1256
  * `table` may be a derived source (a pruned view read, since 0.1.153).
1257
+ * `lock` (since 0.1.162) appends a row-locking clause — see {@link readLockSql}.
1247
1258
  */
1248
- function buildSelect(dialect, table, where, controls) {
1259
+ function buildSelect(dialect, table, where, controls, lock) {
1249
1260
  const cols = buildProjection(dialect, controls?.$select);
1250
1261
  let sql = `SELECT ${fromSourceHint(table)}${cols} FROM ${fromSourceSql(dialect, table)} WHERE ${where.sql}`;
1251
1262
  const params = [...where.params];
@@ -1260,12 +1271,26 @@ function buildSelect(dialect, table, where, controls) {
1260
1271
  sql += ` OFFSET ?`;
1261
1272
  params.push(controls.$skip);
1262
1273
  }
1274
+ if (lock) sql += readLockSql(lock);
1263
1275
  return finalizeParams(dialect, {
1264
1276
  sql,
1265
1277
  params
1266
1278
  });
1267
1279
  }
1268
1280
  /**
1281
+ * The row-locking clause of a locking read — `" FOR UPDATE"`,
1282
+ * `" FOR SHARE NOWAIT"`, `" FOR UPDATE SKIP LOCKED"`, … (PostgreSQL and
1283
+ * MySQL 8 share the syntax); `""` without a lock. Goes after `LIMIT` /
1284
+ * `OFFSET`. @since 0.1.162
1285
+ */
1286
+ function readLockSql(lock) {
1287
+ if (!lock) return "";
1288
+ const mode = lock.mode === "share" ? " FOR SHARE" : " FOR UPDATE";
1289
+ if (lock.wait === "nowait") return `${mode} NOWAIT`;
1290
+ if (lock.wait === "skipLocked") return `${mode} SKIP LOCKED`;
1291
+ return mode;
1292
+ }
1293
+ /**
1269
1294
  * `"col" ASC, "other" DESC` of a physical `$sort` — `""` when it orders
1270
1295
  * nothing. A key with a `$nulls` entry places NULL as asked
1271
1296
  * ({@link nullsOrderSql}); `prefix` is prepended verbatim to each quoted
@@ -1352,13 +1377,14 @@ function replaceColumnsFor(fields, nativeFns) {
1352
1377
  * when the engine owns its function default — so an UPDATE-based replace never
1353
1378
  * retains a value the caller left out. This is the SQL counterpart of the
1354
1379
  * whole-document replace the memory and MongoDB adapters do natively. The
1355
- * version column is excluded (`buildUpdate` appends the OCC bump itself).
1356
- * Returns a new object; `data` is not mutated.
1380
+ * version column is excluded (`buildUpdate` appends the OCC bump itself), and
1381
+ * so are `skip` columns (`@db.onUpdate.inc` counters, since 0.1.162 — the
1382
+ * statement increments them). Returns a new object; `data` is not mutated.
1357
1383
  */
1358
- function fillReplacePayload(data, columns, versionColumn) {
1384
+ function fillReplacePayload(data, columns, versionColumn, skip) {
1359
1385
  const full = { ...data };
1360
1386
  for (const col of columns) {
1361
- if (col.name === versionColumn || col.name in full) continue;
1387
+ if (col.name === versionColumn || col.name in full || skip?.includes(col.name)) continue;
1362
1388
  full[col.name] = col.useDefault ? SQL_DEFAULT : null;
1363
1389
  }
1364
1390
  return full;
@@ -1381,6 +1407,9 @@ function fillReplacePayload(data, columns, versionColumn) {
1381
1407
  * `AND <col> = ?` to the WHERE clause and pushes the value. Requires
1382
1408
  * `versionColumn` (CAS targets that column); supplying `expectedVersion`
1383
1409
  * without `versionColumn` is a programmer error and throws.
1410
+ *
1411
+ * `limit` renders `LIMIT n`; as `{ count, orderBy }` (since 0.1.162) also
1412
+ * `ORDER BY <orderBy>` before it (MySQL's bounded `UPDATE`).
1384
1413
  */
1385
1414
  function buildUpdate(dialect, table, data, where, limit, ops, versionColumn, expectedVersion) {
1386
1415
  const setClauses = [];
@@ -1414,10 +1443,8 @@ function buildUpdate(dialect, table, data, where, limit, ops, versionColumn, exp
1414
1443
  whereParams.push(expectedVersion);
1415
1444
  }
1416
1445
  }
1417
- let sql = `UPDATE ${dialect.quoteTable(table)} SET ${setClauses.join(", ")} WHERE ${whereSql}`;
1418
- if (limit !== void 0) sql += ` LIMIT ${limit}`;
1419
1446
  return finalizeParams(dialect, {
1420
- sql,
1447
+ sql: `UPDATE ${dialect.quoteTable(table)} SET ${setClauses.join(", ")} WHERE ${whereSql}` + limitSql(limit),
1421
1448
  params: [
1422
1449
  ...params,
1423
1450
  ...where.params,
@@ -1426,16 +1453,21 @@ function buildUpdate(dialect, table, data, where, limit, ops, versionColumn, exp
1426
1453
  });
1427
1454
  }
1428
1455
  /**
1429
- * Builds a DELETE ... WHERE statement with optional LIMIT.
1456
+ * Builds a DELETE ... WHERE statement with an optional LIMIT — as
1457
+ * `{ count, orderBy }` (since 0.1.162) with an ORDER BY before it.
1430
1458
  */
1431
1459
  function buildDelete(dialect, table, where, limit) {
1432
- let sql = `DELETE FROM ${dialect.quoteTable(table)} WHERE ${where.sql}`;
1433
- if (limit !== void 0) sql += ` LIMIT ${limit}`;
1434
1460
  return finalizeParams(dialect, {
1435
- sql,
1461
+ sql: `DELETE FROM ${dialect.quoteTable(table)} WHERE ${where.sql}` + limitSql(limit),
1436
1462
  params: where.params
1437
1463
  });
1438
1464
  }
1465
+ /** ` [ORDER BY …] LIMIT n` of an UPDATE / DELETE, or `""` when unbounded. */
1466
+ function limitSql(limit) {
1467
+ if (limit === void 0) return "";
1468
+ if (typeof limit === "number") return ` LIMIT ${limit}`;
1469
+ return (limit.orderBy ? ` ORDER BY ${limit.orderBy}` : "") + ` LIMIT ${limit.count}`;
1470
+ }
1439
1471
  /**
1440
1472
  * The expression a `@db.column.derived` column is generated from: the
1441
1473
  * dialect's typed {@link SqlDialect.jsonExtract} over the (unqualified,
@@ -1483,6 +1515,29 @@ function buildKeyViolationCount(dialect, quotedTable, columns) {
1483
1515
  const noNull = cols.map((c) => `${c} IS NOT NULL`).join(" AND ");
1484
1516
  return `SELECT (SELECT COUNT(*) FROM ${quotedTable} WHERE ${anyNull}) + (SELECT COALESCE(SUM(${dialect.quoteIdentifier("n")}), 0) FROM (SELECT COUNT(*) AS ${dialect.quoteIdentifier("n")} FROM ${quotedTable} WHERE ${noNull} GROUP BY ${cols.join(", ")} HAVING COUNT(*) > 1) ${dialect.quoteIdentifier("d")}) AS ` + dialect.quoteIdentifier("violations");
1485
1517
  }
1518
+ /**
1519
+ * Narrows an UPDATE / DELETE to the first rows of a bounded write
1520
+ * ({@link TDbWriteLimit}, since 0.1.162) on engines without
1521
+ * `UPDATE/DELETE … ORDER BY … LIMIT`: `where AND <key> IN (SELECT <key> FROM
1522
+ * <table> WHERE where ORDER BY … LIMIT n)`. `keyCols` are already-quoted key
1523
+ * expressions (the primary key columns, or SQLite's `rowid`). The filter is
1524
+ * repeated on the outer statement so a row changed out of it meanwhile is
1525
+ * left alone. Unfinalized (`?` placeholders).
1526
+ */
1527
+ function writeLimitWhere(dialect, quotedTable, keyCols, where, bound) {
1528
+ const cols = keyCols.join(", ");
1529
+ const key = keyCols.length === 1 ? cols : `(${cols})`;
1530
+ const order = orderByList(dialect, bound.sort, bound.nulls);
1531
+ return {
1532
+ sql: `${where.sql} AND ${key} IN (SELECT ${cols} FROM ${quotedTable} WHERE ${where.sql} ORDER BY ${order} LIMIT ${writeLimitCount(bound)})`,
1533
+ params: [...where.params, ...where.params]
1534
+ };
1535
+ }
1536
+ /** The validated row count of a bounded write — a positive safe integer, rendered inline. */
1537
+ function writeLimitCount(bound) {
1538
+ if (!Number.isSafeInteger(bound.limit) || bound.limit < 1) throw new Error(`Invalid write limit: ${String(bound.limit)}`);
1539
+ return bound.limit;
1540
+ }
1486
1541
  //#endregion
1487
1542
  //#region src/geo.ts
1488
1543
  /**
@@ -1851,6 +1906,7 @@ exports.parseRegexString = parseRegexString;
1851
1906
  exports.queryNodeToSql = queryNodeToSql;
1852
1907
  exports.queryOpToSql = queryOpToSql;
1853
1908
  exports.quotedJsonPathSegments = quotedJsonPathSegments;
1909
+ exports.readLockSql = readLockSql;
1854
1910
  exports.refActionToSql = refActionToSql;
1855
1911
  exports.renameGeoDistance = renameGeoDistance;
1856
1912
  exports.renderArith = renderArith;
@@ -1859,5 +1915,8 @@ exports.sqlStringLiteral = sqlStringLiteral;
1859
1915
  exports.sqlTimeZoneLiteral = sqlTimeZoneLiteral;
1860
1916
  exports.stripPartitionRowNumber = stripPartitionRowNumber;
1861
1917
  exports.toSqlValue = toSqlValue;
1918
+ exports.unquoteSqlStringLiteral = unquoteSqlStringLiteral;
1862
1919
  exports.vectorDistanceSource = vectorDistanceSource;
1863
1920
  exports.viewReadSource = viewReadSource;
1921
+ exports.writeLimitCount = writeLimitCount;
1922
+ exports.writeLimitWhere = writeLimitWhere;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { AtscriptExprNode, AtscriptQueryFieldRef, AtscriptQueryNode, DbControls, DbError, DbQuery, TDbDefaultFn, TDbFieldMeta, TDbForeignKey, TDbReferentialAction, TFieldOps, TJsonCopyTarget, TReadColumnsKind, TResolvedBucket, TViewColumnMapping, TViewJsonType, TViewPlan, UniquSelect } from "@atscript/db";
1
+ import { AtscriptExprNode, AtscriptQueryFieldRef, AtscriptQueryNode, DbControls, DbError, DbQuery, TDbDefaultFn, TDbFieldMeta, TDbForeignKey, TDbReadLock, TDbReferentialAction, TDbWriteLimit, TFieldOps, TJsonCopyTarget, TReadColumnsKind, TResolvedBucket, TViewColumnMapping, TViewJsonType, TViewPlan, UniquSelect } from "@atscript/db";
2
2
  import { FilterExpr, FilterVisitor, NullsPlacement } from "@uniqu/core";
3
3
  import { TDbAggregateFn } from "@atscript/db/agg";
4
4
 
@@ -19,6 +19,14 @@ interface SqlDialect {
19
19
  quoteTable(name: string): string;
20
20
  /** SQL literal for unlimited LIMIT (SQLite: '-1', MySQL: '18446744073709551615') */
21
21
  unlimitedLimit: string;
22
+ /**
23
+ * Renders a string as an SQL string literal inlined into SQL text (view
24
+ * predicates, JSON paths, defaults): `sqlStringLiteral` (quotes doubled),
25
+ * or — for a dialect whose literals treat `\` as an escape (MySQL) — one
26
+ * that escapes it too.
27
+ * @since 0.1.162
28
+ */
29
+ stringLiteral: (value: string) => string;
22
30
  /**
23
31
  * The optimizer hint asking the engine to MERGE the derived table `alias`
24
32
  * into the SELECT reading it (MySQL: `/*+ MERGE(\`alias\`) *\/`) — carried
@@ -251,8 +259,10 @@ declare function orderKeySql(dialect: SqlDialect, expr: string, desc: boolean, n
251
259
  * reads the same on every dialect (SQLite's quoted path labels have no escape
252
260
  * syntax), so it is rejected rather than escaped.
253
261
  *
262
+ * An empty path (the JSON root — a primitive JSON column's value) has no segments.
263
+ *
254
264
  * @since 0.1.136
255
- * @throws for an empty path or a segment that is empty or holds such a character.
265
+ * @throws for a segment that is empty or holds such a character.
256
266
  */
257
267
  declare function quotedJsonPathSegments(path: readonly string[]): string[];
258
268
  declare const EMPTY_AND: TSqlFragment;
@@ -555,8 +565,16 @@ declare function buildInsertMany(dialect: SqlDialect, table: string, rows: reado
555
565
  /**
556
566
  * Builds a SELECT statement with optional sort, limit, offset, projection.
557
567
  * `table` may be a derived source (a pruned view read, since 0.1.153).
568
+ * `lock` (since 0.1.162) appends a row-locking clause — see {@link readLockSql}.
569
+ */
570
+ declare function buildSelect(dialect: SqlDialect, table: TSqlFromSource, where: TSqlFragment, controls?: DbControls, lock?: TDbReadLock): TSqlFragment;
571
+ /**
572
+ * The row-locking clause of a locking read — `" FOR UPDATE"`,
573
+ * `" FOR SHARE NOWAIT"`, `" FOR UPDATE SKIP LOCKED"`, … (PostgreSQL and
574
+ * MySQL 8 share the syntax); `""` without a lock. Goes after `LIMIT` /
575
+ * `OFFSET`. @since 0.1.162
558
576
  */
559
- declare function buildSelect(dialect: SqlDialect, table: TSqlFromSource, where: TSqlFragment, controls?: DbControls): TSqlFragment;
577
+ declare function readLockSql(lock: TDbReadLock | undefined): string;
560
578
  /**
561
579
  * `"col" ASC, "other" DESC` of a physical `$sort` — `""` when it orders
562
580
  * nothing. A key with a `$nulls` entry places NULL as asked
@@ -618,10 +636,11 @@ declare function replaceColumnsFor(fields: readonly TDbFieldMeta[], nativeFns: R
618
636
  * when the engine owns its function default — so an UPDATE-based replace never
619
637
  * retains a value the caller left out. This is the SQL counterpart of the
620
638
  * whole-document replace the memory and MongoDB adapters do natively. The
621
- * version column is excluded (`buildUpdate` appends the OCC bump itself).
622
- * Returns a new object; `data` is not mutated.
639
+ * version column is excluded (`buildUpdate` appends the OCC bump itself), and
640
+ * so are `skip` columns (`@db.onUpdate.inc` counters, since 0.1.162 — the
641
+ * statement increments them). Returns a new object; `data` is not mutated.
623
642
  */
624
- declare function fillReplacePayload(data: Record<string, unknown>, columns: readonly TReplaceColumn[], versionColumn?: string): Record<string, unknown>;
643
+ declare function fillReplacePayload(data: Record<string, unknown>, columns: readonly TReplaceColumn[], versionColumn?: string, skip?: readonly string[]): Record<string, unknown>;
625
644
  /**
626
645
  * Builds an UPDATE ... SET ... WHERE statement with optional LIMIT.
627
646
  *
@@ -640,12 +659,26 @@ declare function fillReplacePayload(data: Record<string, unknown>, columns: read
640
659
  * `AND <col> = ?` to the WHERE clause and pushes the value. Requires
641
660
  * `versionColumn` (CAS targets that column); supplying `expectedVersion`
642
661
  * without `versionColumn` is a programmer error and throws.
662
+ *
663
+ * `limit` renders `LIMIT n`; as `{ count, orderBy }` (since 0.1.162) also
664
+ * `ORDER BY <orderBy>` before it (MySQL's bounded `UPDATE`).
643
665
  */
644
- declare function buildUpdate(dialect: SqlDialect, table: string, data: Record<string, unknown>, where: TSqlFragment, limit?: number, ops?: TFieldOps, versionColumn?: string, expectedVersion?: number): TSqlFragment;
666
+ declare function buildUpdate(dialect: SqlDialect, table: string, data: Record<string, unknown>, where: TSqlFragment, limit?: number | TSqlWriteLimit, ops?: TFieldOps, versionColumn?: string, expectedVersion?: number): TSqlFragment;
645
667
  /**
646
- * Builds a DELETE ... WHERE statement with optional LIMIT.
668
+ * Builds a DELETE ... WHERE statement with an optional LIMIT — as
669
+ * `{ count, orderBy }` (since 0.1.162) with an ORDER BY before it.
647
670
  */
648
- declare function buildDelete(dialect: SqlDialect, table: string, where: TSqlFragment, limit?: number): TSqlFragment;
671
+ declare function buildDelete(dialect: SqlDialect, table: string, where: TSqlFragment, limit?: number | TSqlWriteLimit): TSqlFragment;
672
+ /**
673
+ * The row bound of an `UPDATE` / `DELETE` on engines that render
674
+ * `ORDER BY … LIMIT n` on writes (MySQL; since 0.1.162).
675
+ */
676
+ interface TSqlWriteLimit {
677
+ /** Rows the statement may touch (rendered inline). */
678
+ count: number;
679
+ /** Rendered `ORDER BY` list (see {@link orderByList}). */
680
+ orderBy?: string;
681
+ }
649
682
  /**
650
683
  * The expression a `@db.column.derived` column is generated from: the
651
684
  * dialect's typed {@link SqlDialect.jsonExtract} over the (unqualified,
@@ -673,19 +706,39 @@ declare function buildProjection(dialect: SqlDialect, select?: UniquSelect, qual
673
706
  * @since 0.1.155
674
707
  */
675
708
  declare function buildKeyViolationCount(dialect: SqlDialect, quotedTable: string, columns: readonly string[]): string;
709
+ /**
710
+ * Narrows an UPDATE / DELETE to the first rows of a bounded write
711
+ * ({@link TDbWriteLimit}, since 0.1.162) on engines without
712
+ * `UPDATE/DELETE … ORDER BY … LIMIT`: `where AND <key> IN (SELECT <key> FROM
713
+ * <table> WHERE where ORDER BY … LIMIT n)`. `keyCols` are already-quoted key
714
+ * expressions (the primary key columns, or SQLite's `rowid`). The filter is
715
+ * repeated on the outer statement so a row changed out of it meanwhile is
716
+ * left alone. Unfinalized (`?` placeholders).
717
+ */
718
+ declare function writeLimitWhere(dialect: SqlDialect, quotedTable: string, keyCols: readonly string[], where: TSqlFragment, bound: TDbWriteLimit): TSqlFragment;
719
+ /** The validated row count of a bounded write — a positive safe integer, rendered inline. */
720
+ declare function writeLimitCount(bound: TDbWriteLimit): number;
676
721
  //#endregion
677
722
  //#region src/common.d.ts
678
723
  /** Formats a string value as a SQL literal with single-quote escaping. */
679
724
  declare function sqlStringLiteral(value: string): string;
725
+ /**
726
+ * The value of a single-quoted SQL string literal (`'it''s'` → `it's`) — as
727
+ * engines report a column default — or `undefined` when `literal` is not one.
728
+ * @since 0.1.162
729
+ */
730
+ declare function unquoteSqlStringLiteral(literal: string): string | undefined;
680
731
  /**
681
732
  * The SQL string literal of a `$`-rooted JSON path with every segment quoted
682
733
  * (`'$."a"."b"'`) — the path argument of SQLite's and MySQL's `json_extract` /
683
- * `json_type`.
734
+ * `json_type`; an empty path is the JSON root (`'$'`).
684
735
  *
685
736
  * @since 0.1.136
686
737
  * @throws as {@link quotedJsonPathSegments}.
687
738
  */
688
- declare function jsonDollarPath(path: readonly string[]): string;
739
+ declare function jsonDollarPath(path: readonly string[], /** The dialect's string literal (`SqlDialect.stringLiteral`). @since 0.1.162 */
740
+
741
+ stringLiteral?: (value: string) => string): string;
689
742
  /**
690
743
  * A calendar bucket's time zone as an inlined SQL string literal (`'Europe/Berlin'`).
691
744
  *
@@ -714,7 +767,9 @@ declare function defaultValueForType(designType: string): string;
714
767
  * respecting the field's designType. Booleans become 0/1, numbers stay unquoted,
715
768
  * strings are single-quote-escaped.
716
769
  */
717
- declare function defaultValueToSqlLiteral(designType: string, value: string): string;
770
+ declare function defaultValueToSqlLiteral(designType: string, value: string, /** The dialect's string literal (`SqlDialect.stringLiteral`). @since 0.1.162 */
771
+
772
+ stringLiteral?: (value: string) => string): string;
718
773
  declare const queryOpToSql: Record<string, string>;
719
774
  /**
720
775
  * Renders an AtscriptQueryNode tree to raw SQL (no parameters -- for DDL use only).
@@ -726,7 +781,9 @@ declare const queryOpToSql: Record<string, string>;
726
781
  * @throws for `matches` (`$regex`) and any other operator — a view predicate
727
782
  * that cannot be rendered fails at sync instead of rendering wrong SQL.
728
783
  */
729
- declare function queryNodeToSql(node: AtscriptQueryNode, resolveFieldRef: (ref: AtscriptQueryFieldRef) => string): string;
784
+ declare function queryNodeToSql(node: AtscriptQueryNode, resolveFieldRef: (ref: AtscriptQueryFieldRef) => string, /** The dialect's string literal (`SqlDialect.stringLiteral`). @since 0.1.162 */
785
+
786
+ stringLiteral?: (value: string) => string): string;
730
787
  //#endregion
731
788
  //#region src/agg.d.ts
732
789
  /**
@@ -825,4 +882,4 @@ declare function buildJsonColumnCopy(dialect: SqlDialect, table: string, source:
825
882
  */
826
883
  declare function buildJsonifyText(dialect: SqlDialect, table: string, column: string): string;
827
884
  //#endregion
828
- export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, InsertSqlCache, PARTITION_ROW_NUMBER_ALIAS, SEARCH_SOURCE_ALIAS, SQL_DEFAULT, type SqlDialect, type TArithFailure, type TFilterVisitorOptions, type TGeoCircle, type TGeoSearchControls, type TGeoWindow, type TReplaceColumn, type TSqlDerivedSource, type TSqlFragment, type TSqlFromSource, VECTOR_DISTANCE_ALIAS, arithOverflowError, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildJsonColumnCopy, buildJsonifyText, buildKeyViolationCount, buildPartitionedSelect, buildProjection, buildSelect, buildUpdate, buildVectorSearchCount, buildVectorSearchSelect, buildViewSelect, buildWhere, chunkInsertRows, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, finalizeParams, foreignKeySql, fromSourceHint, fromSourceSql, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, mapQueryErrors, normalizeGeoPointValue, nullsOrderSql, nullsPlacementOf, numericOutOfRangeError, orFragment, orderByList, orderKeySql, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, refActionToSql, renameGeoDistance, renderArith, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, stripPartitionRowNumber, toSqlValue, vectorDistanceSource, viewReadSource };
885
+ export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, InsertSqlCache, PARTITION_ROW_NUMBER_ALIAS, SEARCH_SOURCE_ALIAS, SQL_DEFAULT, type SqlDialect, type TArithFailure, type TFilterVisitorOptions, type TGeoCircle, type TGeoSearchControls, type TGeoWindow, type TReplaceColumn, type TSqlDerivedSource, type TSqlFragment, type TSqlFromSource, type TSqlWriteLimit, VECTOR_DISTANCE_ALIAS, arithOverflowError, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildJsonColumnCopy, buildJsonifyText, buildKeyViolationCount, buildPartitionedSelect, buildProjection, buildSelect, buildUpdate, buildVectorSearchCount, buildVectorSearchSelect, buildViewSelect, buildWhere, chunkInsertRows, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, finalizeParams, foreignKeySql, fromSourceHint, fromSourceSql, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, mapQueryErrors, normalizeGeoPointValue, nullsOrderSql, nullsPlacementOf, numericOutOfRangeError, orFragment, orderByList, orderKeySql, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, readLockSql, refActionToSql, renameGeoDistance, renderArith, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, stripPartitionRowNumber, toSqlValue, unquoteSqlStringLiteral, vectorDistanceSource, viewReadSource, writeLimitCount, writeLimitWhere };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { AtscriptExprNode, AtscriptQueryFieldRef, AtscriptQueryNode, DbControls, DbError, DbQuery, TDbDefaultFn, TDbFieldMeta, TDbForeignKey, TDbReferentialAction, TFieldOps, TJsonCopyTarget, TReadColumnsKind, TResolvedBucket, TViewColumnMapping, TViewJsonType, TViewPlan, UniquSelect } from "@atscript/db";
1
+ import { AtscriptExprNode, AtscriptQueryFieldRef, AtscriptQueryNode, DbControls, DbError, DbQuery, TDbDefaultFn, TDbFieldMeta, TDbForeignKey, TDbReadLock, TDbReferentialAction, TDbWriteLimit, TFieldOps, TJsonCopyTarget, TReadColumnsKind, TResolvedBucket, TViewColumnMapping, TViewJsonType, TViewPlan, UniquSelect } from "@atscript/db";
2
2
  import { FilterExpr, FilterVisitor, NullsPlacement } from "@uniqu/core";
3
3
  import { TDbAggregateFn } from "@atscript/db/agg";
4
4
 
@@ -19,6 +19,14 @@ interface SqlDialect {
19
19
  quoteTable(name: string): string;
20
20
  /** SQL literal for unlimited LIMIT (SQLite: '-1', MySQL: '18446744073709551615') */
21
21
  unlimitedLimit: string;
22
+ /**
23
+ * Renders a string as an SQL string literal inlined into SQL text (view
24
+ * predicates, JSON paths, defaults): `sqlStringLiteral` (quotes doubled),
25
+ * or — for a dialect whose literals treat `\` as an escape (MySQL) — one
26
+ * that escapes it too.
27
+ * @since 0.1.162
28
+ */
29
+ stringLiteral: (value: string) => string;
22
30
  /**
23
31
  * The optimizer hint asking the engine to MERGE the derived table `alias`
24
32
  * into the SELECT reading it (MySQL: `/*+ MERGE(\`alias\`) *\/`) — carried
@@ -251,8 +259,10 @@ declare function orderKeySql(dialect: SqlDialect, expr: string, desc: boolean, n
251
259
  * reads the same on every dialect (SQLite's quoted path labels have no escape
252
260
  * syntax), so it is rejected rather than escaped.
253
261
  *
262
+ * An empty path (the JSON root — a primitive JSON column's value) has no segments.
263
+ *
254
264
  * @since 0.1.136
255
- * @throws for an empty path or a segment that is empty or holds such a character.
265
+ * @throws for a segment that is empty or holds such a character.
256
266
  */
257
267
  declare function quotedJsonPathSegments(path: readonly string[]): string[];
258
268
  declare const EMPTY_AND: TSqlFragment;
@@ -555,8 +565,16 @@ declare function buildInsertMany(dialect: SqlDialect, table: string, rows: reado
555
565
  /**
556
566
  * Builds a SELECT statement with optional sort, limit, offset, projection.
557
567
  * `table` may be a derived source (a pruned view read, since 0.1.153).
568
+ * `lock` (since 0.1.162) appends a row-locking clause — see {@link readLockSql}.
569
+ */
570
+ declare function buildSelect(dialect: SqlDialect, table: TSqlFromSource, where: TSqlFragment, controls?: DbControls, lock?: TDbReadLock): TSqlFragment;
571
+ /**
572
+ * The row-locking clause of a locking read — `" FOR UPDATE"`,
573
+ * `" FOR SHARE NOWAIT"`, `" FOR UPDATE SKIP LOCKED"`, … (PostgreSQL and
574
+ * MySQL 8 share the syntax); `""` without a lock. Goes after `LIMIT` /
575
+ * `OFFSET`. @since 0.1.162
558
576
  */
559
- declare function buildSelect(dialect: SqlDialect, table: TSqlFromSource, where: TSqlFragment, controls?: DbControls): TSqlFragment;
577
+ declare function readLockSql(lock: TDbReadLock | undefined): string;
560
578
  /**
561
579
  * `"col" ASC, "other" DESC` of a physical `$sort` — `""` when it orders
562
580
  * nothing. A key with a `$nulls` entry places NULL as asked
@@ -618,10 +636,11 @@ declare function replaceColumnsFor(fields: readonly TDbFieldMeta[], nativeFns: R
618
636
  * when the engine owns its function default — so an UPDATE-based replace never
619
637
  * retains a value the caller left out. This is the SQL counterpart of the
620
638
  * whole-document replace the memory and MongoDB adapters do natively. The
621
- * version column is excluded (`buildUpdate` appends the OCC bump itself).
622
- * Returns a new object; `data` is not mutated.
639
+ * version column is excluded (`buildUpdate` appends the OCC bump itself), and
640
+ * so are `skip` columns (`@db.onUpdate.inc` counters, since 0.1.162 — the
641
+ * statement increments them). Returns a new object; `data` is not mutated.
623
642
  */
624
- declare function fillReplacePayload(data: Record<string, unknown>, columns: readonly TReplaceColumn[], versionColumn?: string): Record<string, unknown>;
643
+ declare function fillReplacePayload(data: Record<string, unknown>, columns: readonly TReplaceColumn[], versionColumn?: string, skip?: readonly string[]): Record<string, unknown>;
625
644
  /**
626
645
  * Builds an UPDATE ... SET ... WHERE statement with optional LIMIT.
627
646
  *
@@ -640,12 +659,26 @@ declare function fillReplacePayload(data: Record<string, unknown>, columns: read
640
659
  * `AND <col> = ?` to the WHERE clause and pushes the value. Requires
641
660
  * `versionColumn` (CAS targets that column); supplying `expectedVersion`
642
661
  * without `versionColumn` is a programmer error and throws.
662
+ *
663
+ * `limit` renders `LIMIT n`; as `{ count, orderBy }` (since 0.1.162) also
664
+ * `ORDER BY <orderBy>` before it (MySQL's bounded `UPDATE`).
643
665
  */
644
- declare function buildUpdate(dialect: SqlDialect, table: string, data: Record<string, unknown>, where: TSqlFragment, limit?: number, ops?: TFieldOps, versionColumn?: string, expectedVersion?: number): TSqlFragment;
666
+ declare function buildUpdate(dialect: SqlDialect, table: string, data: Record<string, unknown>, where: TSqlFragment, limit?: number | TSqlWriteLimit, ops?: TFieldOps, versionColumn?: string, expectedVersion?: number): TSqlFragment;
645
667
  /**
646
- * Builds a DELETE ... WHERE statement with optional LIMIT.
668
+ * Builds a DELETE ... WHERE statement with an optional LIMIT — as
669
+ * `{ count, orderBy }` (since 0.1.162) with an ORDER BY before it.
647
670
  */
648
- declare function buildDelete(dialect: SqlDialect, table: string, where: TSqlFragment, limit?: number): TSqlFragment;
671
+ declare function buildDelete(dialect: SqlDialect, table: string, where: TSqlFragment, limit?: number | TSqlWriteLimit): TSqlFragment;
672
+ /**
673
+ * The row bound of an `UPDATE` / `DELETE` on engines that render
674
+ * `ORDER BY … LIMIT n` on writes (MySQL; since 0.1.162).
675
+ */
676
+ interface TSqlWriteLimit {
677
+ /** Rows the statement may touch (rendered inline). */
678
+ count: number;
679
+ /** Rendered `ORDER BY` list (see {@link orderByList}). */
680
+ orderBy?: string;
681
+ }
649
682
  /**
650
683
  * The expression a `@db.column.derived` column is generated from: the
651
684
  * dialect's typed {@link SqlDialect.jsonExtract} over the (unqualified,
@@ -673,19 +706,39 @@ declare function buildProjection(dialect: SqlDialect, select?: UniquSelect, qual
673
706
  * @since 0.1.155
674
707
  */
675
708
  declare function buildKeyViolationCount(dialect: SqlDialect, quotedTable: string, columns: readonly string[]): string;
709
+ /**
710
+ * Narrows an UPDATE / DELETE to the first rows of a bounded write
711
+ * ({@link TDbWriteLimit}, since 0.1.162) on engines without
712
+ * `UPDATE/DELETE … ORDER BY … LIMIT`: `where AND <key> IN (SELECT <key> FROM
713
+ * <table> WHERE where ORDER BY … LIMIT n)`. `keyCols` are already-quoted key
714
+ * expressions (the primary key columns, or SQLite's `rowid`). The filter is
715
+ * repeated on the outer statement so a row changed out of it meanwhile is
716
+ * left alone. Unfinalized (`?` placeholders).
717
+ */
718
+ declare function writeLimitWhere(dialect: SqlDialect, quotedTable: string, keyCols: readonly string[], where: TSqlFragment, bound: TDbWriteLimit): TSqlFragment;
719
+ /** The validated row count of a bounded write — a positive safe integer, rendered inline. */
720
+ declare function writeLimitCount(bound: TDbWriteLimit): number;
676
721
  //#endregion
677
722
  //#region src/common.d.ts
678
723
  /** Formats a string value as a SQL literal with single-quote escaping. */
679
724
  declare function sqlStringLiteral(value: string): string;
725
+ /**
726
+ * The value of a single-quoted SQL string literal (`'it''s'` → `it's`) — as
727
+ * engines report a column default — or `undefined` when `literal` is not one.
728
+ * @since 0.1.162
729
+ */
730
+ declare function unquoteSqlStringLiteral(literal: string): string | undefined;
680
731
  /**
681
732
  * The SQL string literal of a `$`-rooted JSON path with every segment quoted
682
733
  * (`'$."a"."b"'`) — the path argument of SQLite's and MySQL's `json_extract` /
683
- * `json_type`.
734
+ * `json_type`; an empty path is the JSON root (`'$'`).
684
735
  *
685
736
  * @since 0.1.136
686
737
  * @throws as {@link quotedJsonPathSegments}.
687
738
  */
688
- declare function jsonDollarPath(path: readonly string[]): string;
739
+ declare function jsonDollarPath(path: readonly string[], /** The dialect's string literal (`SqlDialect.stringLiteral`). @since 0.1.162 */
740
+
741
+ stringLiteral?: (value: string) => string): string;
689
742
  /**
690
743
  * A calendar bucket's time zone as an inlined SQL string literal (`'Europe/Berlin'`).
691
744
  *
@@ -714,7 +767,9 @@ declare function defaultValueForType(designType: string): string;
714
767
  * respecting the field's designType. Booleans become 0/1, numbers stay unquoted,
715
768
  * strings are single-quote-escaped.
716
769
  */
717
- declare function defaultValueToSqlLiteral(designType: string, value: string): string;
770
+ declare function defaultValueToSqlLiteral(designType: string, value: string, /** The dialect's string literal (`SqlDialect.stringLiteral`). @since 0.1.162 */
771
+
772
+ stringLiteral?: (value: string) => string): string;
718
773
  declare const queryOpToSql: Record<string, string>;
719
774
  /**
720
775
  * Renders an AtscriptQueryNode tree to raw SQL (no parameters -- for DDL use only).
@@ -726,7 +781,9 @@ declare const queryOpToSql: Record<string, string>;
726
781
  * @throws for `matches` (`$regex`) and any other operator — a view predicate
727
782
  * that cannot be rendered fails at sync instead of rendering wrong SQL.
728
783
  */
729
- declare function queryNodeToSql(node: AtscriptQueryNode, resolveFieldRef: (ref: AtscriptQueryFieldRef) => string): string;
784
+ declare function queryNodeToSql(node: AtscriptQueryNode, resolveFieldRef: (ref: AtscriptQueryFieldRef) => string, /** The dialect's string literal (`SqlDialect.stringLiteral`). @since 0.1.162 */
785
+
786
+ stringLiteral?: (value: string) => string): string;
730
787
  //#endregion
731
788
  //#region src/agg.d.ts
732
789
  /**
@@ -825,4 +882,4 @@ declare function buildJsonColumnCopy(dialect: SqlDialect, table: string, source:
825
882
  */
826
883
  declare function buildJsonifyText(dialect: SqlDialect, table: string, column: string): string;
827
884
  //#endregion
828
- export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, InsertSqlCache, PARTITION_ROW_NUMBER_ALIAS, SEARCH_SOURCE_ALIAS, SQL_DEFAULT, type SqlDialect, type TArithFailure, type TFilterVisitorOptions, type TGeoCircle, type TGeoSearchControls, type TGeoWindow, type TReplaceColumn, type TSqlDerivedSource, type TSqlFragment, type TSqlFromSource, VECTOR_DISTANCE_ALIAS, arithOverflowError, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildJsonColumnCopy, buildJsonifyText, buildKeyViolationCount, buildPartitionedSelect, buildProjection, buildSelect, buildUpdate, buildVectorSearchCount, buildVectorSearchSelect, buildViewSelect, buildWhere, chunkInsertRows, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, finalizeParams, foreignKeySql, fromSourceHint, fromSourceSql, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, mapQueryErrors, normalizeGeoPointValue, nullsOrderSql, nullsPlacementOf, numericOutOfRangeError, orFragment, orderByList, orderKeySql, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, refActionToSql, renameGeoDistance, renderArith, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, stripPartitionRowNumber, toSqlValue, vectorDistanceSource, viewReadSource };
885
+ export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, InsertSqlCache, PARTITION_ROW_NUMBER_ALIAS, SEARCH_SOURCE_ALIAS, SQL_DEFAULT, type SqlDialect, type TArithFailure, type TFilterVisitorOptions, type TGeoCircle, type TGeoSearchControls, type TGeoWindow, type TReplaceColumn, type TSqlDerivedSource, type TSqlFragment, type TSqlFromSource, type TSqlWriteLimit, VECTOR_DISTANCE_ALIAS, arithOverflowError, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildJsonColumnCopy, buildJsonifyText, buildKeyViolationCount, buildPartitionedSelect, buildProjection, buildSelect, buildUpdate, buildVectorSearchCount, buildVectorSearchSelect, buildViewSelect, buildWhere, chunkInsertRows, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, finalizeParams, foreignKeySql, fromSourceHint, fromSourceSql, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, mapQueryErrors, normalizeGeoPointValue, nullsOrderSql, nullsPlacementOf, numericOutOfRangeError, orFragment, orderByList, orderKeySql, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, readLockSql, refActionToSql, renameGeoDistance, renderArith, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, stripPartitionRowNumber, toSqlValue, unquoteSqlStringLiteral, vectorDistanceSource, viewReadSource, writeLimitCount, writeLimitWhere };
package/dist/index.mjs CHANGED
@@ -212,11 +212,12 @@ function orderKeySql(dialect, expr, desc, nulls, nullTest) {
212
212
  * reads the same on every dialect (SQLite's quoted path labels have no escape
213
213
  * syntax), so it is rejected rather than escaped.
214
214
  *
215
+ * An empty path (the JSON root — a primitive JSON column's value) has no segments.
216
+ *
215
217
  * @since 0.1.136
216
- * @throws for an empty path or a segment that is empty or holds such a character.
218
+ * @throws for a segment that is empty or holds such a character.
217
219
  */
218
220
  function quotedJsonPathSegments(path) {
219
- if (path.length === 0) throw new Error("JSON extraction needs a path below the JSON column");
220
221
  return path.map((seg) => {
221
222
  let ok = seg.length > 0;
222
223
  for (let i = 0; ok && i < seg.length; i++) {
@@ -521,15 +522,24 @@ function sqlStringLiteral(value) {
521
522
  return `'${value.replace(/'/g, "''")}'`;
522
523
  }
523
524
  /**
525
+ * The value of a single-quoted SQL string literal (`'it''s'` → `it's`) — as
526
+ * engines report a column default — or `undefined` when `literal` is not one.
527
+ * @since 0.1.162
528
+ */
529
+ function unquoteSqlStringLiteral(literal) {
530
+ if (literal.length < 2 || !literal.startsWith("'") || !literal.endsWith("'")) return void 0;
531
+ return literal.slice(1, -1).replace(/''/g, "'");
532
+ }
533
+ /**
524
534
  * The SQL string literal of a `$`-rooted JSON path with every segment quoted
525
535
  * (`'$."a"."b"'`) — the path argument of SQLite's and MySQL's `json_extract` /
526
- * `json_type`.
536
+ * `json_type`; an empty path is the JSON root (`'$'`).
527
537
  *
528
538
  * @since 0.1.136
529
539
  * @throws as {@link quotedJsonPathSegments}.
530
540
  */
531
- function jsonDollarPath(path) {
532
- return sqlStringLiteral(`$.${quotedJsonPathSegments(path).join(".")}`);
541
+ function jsonDollarPath(path, stringLiteral = sqlStringLiteral) {
542
+ return stringLiteral(["$", ...quotedJsonPathSegments(path)].join("."));
533
543
  }
534
544
  /**
535
545
  * A calendar bucket's time zone as an inlined SQL string literal (`'Europe/Berlin'`).
@@ -595,7 +605,7 @@ function defaultValueForType(designType) {
595
605
  * respecting the field's designType. Booleans become 0/1, numbers stay unquoted,
596
606
  * strings are single-quote-escaped.
597
607
  */
598
- function defaultValueToSqlLiteral(designType, value) {
608
+ function defaultValueToSqlLiteral(designType, value, stringLiteral = sqlStringLiteral) {
599
609
  switch (designType) {
600
610
  case "boolean": return value === "true" || value === "1" ? "1" : "0";
601
611
  case "number":
@@ -604,7 +614,7 @@ function defaultValueToSqlLiteral(designType, value) {
604
614
  const n = Number(value);
605
615
  return Number.isFinite(n) ? String(n) : "0";
606
616
  }
607
- default: return sqlStringLiteral(value);
617
+ default: return stringLiteral(value);
608
618
  }
609
619
  }
610
620
  const queryOpToSql = {
@@ -616,9 +626,9 @@ const queryOpToSql = {
616
626
  $lte: "<="
617
627
  };
618
628
  /** An inlined SQL literal for a view-predicate value (DDL — no parameters). */
619
- function predicateLiteral(value) {
629
+ function predicateLiteral(value, stringLiteral) {
620
630
  if (value === null || value === void 0) return "NULL";
621
- if (typeof value === "string") return sqlStringLiteral(value);
631
+ if (typeof value === "string") return stringLiteral(value);
622
632
  if (typeof value === "number") {
623
633
  if (!Number.isFinite(value)) throw new Error(`Non-finite number ${value} in a view predicate`);
624
634
  return String(value);
@@ -636,18 +646,18 @@ function predicateLiteral(value) {
636
646
  * @throws for `matches` (`$regex`) and any other operator — a view predicate
637
647
  * that cannot be rendered fails at sync instead of rendering wrong SQL.
638
648
  */
639
- function queryNodeToSql(node, resolveFieldRef) {
649
+ function queryNodeToSql(node, resolveFieldRef, stringLiteral = sqlStringLiteral) {
640
650
  if ("$and" in node) {
641
651
  const children = node.$and;
642
652
  if (children.length === 0) return EMPTY_AND.sql;
643
- return children.map((n) => queryNodeToSql(n, resolveFieldRef)).join(" AND ");
653
+ return children.map((n) => queryNodeToSql(n, resolveFieldRef, stringLiteral)).join(" AND ");
644
654
  }
645
655
  if ("$or" in node) {
646
656
  const children = node.$or;
647
657
  if (children.length === 0) return EMPTY_OR.sql;
648
- return `(${children.map((n) => queryNodeToSql(n, resolveFieldRef)).join(" OR ")})`;
658
+ return `(${children.map((n) => queryNodeToSql(n, resolveFieldRef, stringLiteral)).join(" OR ")})`;
649
659
  }
650
- if ("$not" in node) return `NOT (${queryNodeToSql(node.$not, resolveFieldRef)})`;
660
+ if ("$not" in node) return `NOT (${queryNodeToSql(node.$not, resolveFieldRef, stringLiteral)})`;
651
661
  const comp = node;
652
662
  const leftSql = resolveFieldRef(comp.left);
653
663
  switch (comp.op) {
@@ -656,7 +666,7 @@ function queryNodeToSql(node, resolveFieldRef) {
656
666
  case "$nin": {
657
667
  const values = Array.isArray(comp.right) ? comp.right : [comp.right];
658
668
  if (values.length === 0) return comp.op === "$in" ? EMPTY_OR.sql : EMPTY_AND.sql;
659
- const list = values.map((v) => predicateLiteral(v)).join(", ");
669
+ const list = values.map((v) => predicateLiteral(v, stringLiteral)).join(", ");
660
670
  return `${leftSql} ${comp.op === "$in" ? "IN" : "NOT IN"} (${list})`;
661
671
  }
662
672
  case "$regex": throw new Error("matches is not supported in view predicates");
@@ -666,7 +676,7 @@ function queryNodeToSql(node, resolveFieldRef) {
666
676
  if (!sqlOp) throw new Error(`Operator "${comp.op}" is not supported in view predicates`);
667
677
  if (isFieldRef(comp.right)) return `${leftSql} ${sqlOp} ${resolveFieldRef(comp.right)}`;
668
678
  if (comp.right === null || comp.right === void 0) return comp.op === "$ne" ? `${leftSql} IS NOT NULL` : `${leftSql} IS NULL`;
669
- return `${leftSql} ${sqlOp} ${predicateLiteral(comp.right)}`;
679
+ return `${leftSql} ${sqlOp} ${predicateLiteral(comp.right, stringLiteral)}`;
670
680
  }
671
681
  //#endregion
672
682
  //#region src/agg.ts
@@ -1000,7 +1010,7 @@ function viewAggExpr(dialect, c, resolveFieldRef) {
1000
1010
  const star = c.aggField === "*";
1001
1011
  const src = star ? "*" : viewSourceExpr(dialect, c);
1002
1012
  if (!c.aggFilter) return renderAggCall(c.aggFn, src, c.viewColumn);
1003
- const predicate = queryNodeToSql(c.aggFilter, resolveFieldRef);
1013
+ const predicate = queryNodeToSql(c.aggFilter, resolveFieldRef, dialect.stringLiteral);
1004
1014
  const call = renderAggCall(c.aggFn, `CASE WHEN ${predicate} THEN ${star ? "1" : src} END`, c.viewColumn);
1005
1015
  return c.aggFn === "sum" ? `COALESCE(${call}, 0)` : call;
1006
1016
  }
@@ -1091,14 +1101,14 @@ function buildViewSelect(dialect, plan, columns, resolveFieldRef) {
1091
1101
  const columnExpr = (c) => c.expr !== void 0 ? viewComputeExpr(dialect, c, columnByPath, resolveFieldRef, hasAggregates, computedSql) : c.aggFn ? viewAggExpr(dialect, c, resolveFieldRef) : viewSourceExpr(dialect, c);
1092
1102
  let sql = `SELECT ${columns.map((c) => `${columnExpr(c)} AS ${dialect.quoteIdentifier(c.viewColumn)}`).join(", ")} FROM ${dialect.quoteIdentifier(plan.entryTable)}`;
1093
1103
  for (const join of plan.joins) {
1094
- const condition = queryNodeToSql(join.condition, resolveFieldRef);
1104
+ const condition = queryNodeToSql(join.condition, resolveFieldRef, dialect.stringLiteral);
1095
1105
  const keyword = join.kind === "left" ? "LEFT JOIN" : "JOIN";
1096
1106
  const target = join.scope === join.targetTable ? dialect.quoteIdentifier(join.targetTable) : `${dialect.quoteIdentifier(join.targetTable)} AS ${dialect.quoteIdentifier(join.scope)}`;
1097
1107
  const onClause = join.first ? firstRowOn(dialect, join, target, condition, resolveFieldRef) : condition;
1098
1108
  sql += ` ${keyword} ${target} ON ${onClause}`;
1099
1109
  }
1100
1110
  if (plan.filter) {
1101
- const whereClause = queryNodeToSql(plan.filter, resolveFieldRef);
1111
+ const whereClause = queryNodeToSql(plan.filter, resolveFieldRef, dialect.stringLiteral);
1102
1112
  sql += ` WHERE ${whereClause}`;
1103
1113
  }
1104
1114
  if (hasAggregates) {
@@ -1119,7 +1129,7 @@ function buildViewSelect(dialect, plan, columns, resolveFieldRef) {
1119
1129
  if (!col.json) return expr;
1120
1130
  return dialect.bucketAliasInHaving && groupedColumns().has(col.viewColumn.toLowerCase()) ? `MIN(${expr})` : havingGroupRef(dialect, expr, col.viewColumn);
1121
1131
  };
1122
- const havingClause = queryNodeToSql(plan.having, havingResolver);
1132
+ const havingClause = queryNodeToSql(plan.having, havingResolver, dialect.stringLiteral);
1123
1133
  sql += ` HAVING ${havingClause}`;
1124
1134
  }
1125
1135
  }
@@ -1243,8 +1253,9 @@ function buildInsertMany(dialect, table, rows, columns = insertManyColumns(rows)
1243
1253
  /**
1244
1254
  * Builds a SELECT statement with optional sort, limit, offset, projection.
1245
1255
  * `table` may be a derived source (a pruned view read, since 0.1.153).
1256
+ * `lock` (since 0.1.162) appends a row-locking clause — see {@link readLockSql}.
1246
1257
  */
1247
- function buildSelect(dialect, table, where, controls) {
1258
+ function buildSelect(dialect, table, where, controls, lock) {
1248
1259
  const cols = buildProjection(dialect, controls?.$select);
1249
1260
  let sql = `SELECT ${fromSourceHint(table)}${cols} FROM ${fromSourceSql(dialect, table)} WHERE ${where.sql}`;
1250
1261
  const params = [...where.params];
@@ -1259,12 +1270,26 @@ function buildSelect(dialect, table, where, controls) {
1259
1270
  sql += ` OFFSET ?`;
1260
1271
  params.push(controls.$skip);
1261
1272
  }
1273
+ if (lock) sql += readLockSql(lock);
1262
1274
  return finalizeParams(dialect, {
1263
1275
  sql,
1264
1276
  params
1265
1277
  });
1266
1278
  }
1267
1279
  /**
1280
+ * The row-locking clause of a locking read — `" FOR UPDATE"`,
1281
+ * `" FOR SHARE NOWAIT"`, `" FOR UPDATE SKIP LOCKED"`, … (PostgreSQL and
1282
+ * MySQL 8 share the syntax); `""` without a lock. Goes after `LIMIT` /
1283
+ * `OFFSET`. @since 0.1.162
1284
+ */
1285
+ function readLockSql(lock) {
1286
+ if (!lock) return "";
1287
+ const mode = lock.mode === "share" ? " FOR SHARE" : " FOR UPDATE";
1288
+ if (lock.wait === "nowait") return `${mode} NOWAIT`;
1289
+ if (lock.wait === "skipLocked") return `${mode} SKIP LOCKED`;
1290
+ return mode;
1291
+ }
1292
+ /**
1268
1293
  * `"col" ASC, "other" DESC` of a physical `$sort` — `""` when it orders
1269
1294
  * nothing. A key with a `$nulls` entry places NULL as asked
1270
1295
  * ({@link nullsOrderSql}); `prefix` is prepended verbatim to each quoted
@@ -1351,13 +1376,14 @@ function replaceColumnsFor(fields, nativeFns) {
1351
1376
  * when the engine owns its function default — so an UPDATE-based replace never
1352
1377
  * retains a value the caller left out. This is the SQL counterpart of the
1353
1378
  * whole-document replace the memory and MongoDB adapters do natively. The
1354
- * version column is excluded (`buildUpdate` appends the OCC bump itself).
1355
- * Returns a new object; `data` is not mutated.
1379
+ * version column is excluded (`buildUpdate` appends the OCC bump itself), and
1380
+ * so are `skip` columns (`@db.onUpdate.inc` counters, since 0.1.162 — the
1381
+ * statement increments them). Returns a new object; `data` is not mutated.
1356
1382
  */
1357
- function fillReplacePayload(data, columns, versionColumn) {
1383
+ function fillReplacePayload(data, columns, versionColumn, skip) {
1358
1384
  const full = { ...data };
1359
1385
  for (const col of columns) {
1360
- if (col.name === versionColumn || col.name in full) continue;
1386
+ if (col.name === versionColumn || col.name in full || skip?.includes(col.name)) continue;
1361
1387
  full[col.name] = col.useDefault ? SQL_DEFAULT : null;
1362
1388
  }
1363
1389
  return full;
@@ -1380,6 +1406,9 @@ function fillReplacePayload(data, columns, versionColumn) {
1380
1406
  * `AND <col> = ?` to the WHERE clause and pushes the value. Requires
1381
1407
  * `versionColumn` (CAS targets that column); supplying `expectedVersion`
1382
1408
  * without `versionColumn` is a programmer error and throws.
1409
+ *
1410
+ * `limit` renders `LIMIT n`; as `{ count, orderBy }` (since 0.1.162) also
1411
+ * `ORDER BY <orderBy>` before it (MySQL's bounded `UPDATE`).
1383
1412
  */
1384
1413
  function buildUpdate(dialect, table, data, where, limit, ops, versionColumn, expectedVersion) {
1385
1414
  const setClauses = [];
@@ -1413,10 +1442,8 @@ function buildUpdate(dialect, table, data, where, limit, ops, versionColumn, exp
1413
1442
  whereParams.push(expectedVersion);
1414
1443
  }
1415
1444
  }
1416
- let sql = `UPDATE ${dialect.quoteTable(table)} SET ${setClauses.join(", ")} WHERE ${whereSql}`;
1417
- if (limit !== void 0) sql += ` LIMIT ${limit}`;
1418
1445
  return finalizeParams(dialect, {
1419
- sql,
1446
+ sql: `UPDATE ${dialect.quoteTable(table)} SET ${setClauses.join(", ")} WHERE ${whereSql}` + limitSql(limit),
1420
1447
  params: [
1421
1448
  ...params,
1422
1449
  ...where.params,
@@ -1425,16 +1452,21 @@ function buildUpdate(dialect, table, data, where, limit, ops, versionColumn, exp
1425
1452
  });
1426
1453
  }
1427
1454
  /**
1428
- * Builds a DELETE ... WHERE statement with optional LIMIT.
1455
+ * Builds a DELETE ... WHERE statement with an optional LIMIT — as
1456
+ * `{ count, orderBy }` (since 0.1.162) with an ORDER BY before it.
1429
1457
  */
1430
1458
  function buildDelete(dialect, table, where, limit) {
1431
- let sql = `DELETE FROM ${dialect.quoteTable(table)} WHERE ${where.sql}`;
1432
- if (limit !== void 0) sql += ` LIMIT ${limit}`;
1433
1459
  return finalizeParams(dialect, {
1434
- sql,
1460
+ sql: `DELETE FROM ${dialect.quoteTable(table)} WHERE ${where.sql}` + limitSql(limit),
1435
1461
  params: where.params
1436
1462
  });
1437
1463
  }
1464
+ /** ` [ORDER BY …] LIMIT n` of an UPDATE / DELETE, or `""` when unbounded. */
1465
+ function limitSql(limit) {
1466
+ if (limit === void 0) return "";
1467
+ if (typeof limit === "number") return ` LIMIT ${limit}`;
1468
+ return (limit.orderBy ? ` ORDER BY ${limit.orderBy}` : "") + ` LIMIT ${limit.count}`;
1469
+ }
1438
1470
  /**
1439
1471
  * The expression a `@db.column.derived` column is generated from: the
1440
1472
  * dialect's typed {@link SqlDialect.jsonExtract} over the (unqualified,
@@ -1482,6 +1514,29 @@ function buildKeyViolationCount(dialect, quotedTable, columns) {
1482
1514
  const noNull = cols.map((c) => `${c} IS NOT NULL`).join(" AND ");
1483
1515
  return `SELECT (SELECT COUNT(*) FROM ${quotedTable} WHERE ${anyNull}) + (SELECT COALESCE(SUM(${dialect.quoteIdentifier("n")}), 0) FROM (SELECT COUNT(*) AS ${dialect.quoteIdentifier("n")} FROM ${quotedTable} WHERE ${noNull} GROUP BY ${cols.join(", ")} HAVING COUNT(*) > 1) ${dialect.quoteIdentifier("d")}) AS ` + dialect.quoteIdentifier("violations");
1484
1516
  }
1517
+ /**
1518
+ * Narrows an UPDATE / DELETE to the first rows of a bounded write
1519
+ * ({@link TDbWriteLimit}, since 0.1.162) on engines without
1520
+ * `UPDATE/DELETE … ORDER BY … LIMIT`: `where AND <key> IN (SELECT <key> FROM
1521
+ * <table> WHERE where ORDER BY … LIMIT n)`. `keyCols` are already-quoted key
1522
+ * expressions (the primary key columns, or SQLite's `rowid`). The filter is
1523
+ * repeated on the outer statement so a row changed out of it meanwhile is
1524
+ * left alone. Unfinalized (`?` placeholders).
1525
+ */
1526
+ function writeLimitWhere(dialect, quotedTable, keyCols, where, bound) {
1527
+ const cols = keyCols.join(", ");
1528
+ const key = keyCols.length === 1 ? cols : `(${cols})`;
1529
+ const order = orderByList(dialect, bound.sort, bound.nulls);
1530
+ return {
1531
+ sql: `${where.sql} AND ${key} IN (SELECT ${cols} FROM ${quotedTable} WHERE ${where.sql} ORDER BY ${order} LIMIT ${writeLimitCount(bound)})`,
1532
+ params: [...where.params, ...where.params]
1533
+ };
1534
+ }
1535
+ /** The validated row count of a bounded write — a positive safe integer, rendered inline. */
1536
+ function writeLimitCount(bound) {
1537
+ if (!Number.isSafeInteger(bound.limit) || bound.limit < 1) throw new Error(`Invalid write limit: ${String(bound.limit)}`);
1538
+ return bound.limit;
1539
+ }
1485
1540
  //#endregion
1486
1541
  //#region src/geo.ts
1487
1542
  /**
@@ -1795,4 +1850,4 @@ function buildJsonifyText(dialect, table, column) {
1795
1850
  return `UPDATE ${dialect.quoteTable(table)} SET ${col} = ${dialect.jsonFromText(col)} WHERE ${col} IS NOT NULL`;
1796
1851
  }
1797
1852
  //#endregion
1798
- export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, InsertSqlCache, PARTITION_ROW_NUMBER_ALIAS, SEARCH_SOURCE_ALIAS, SQL_DEFAULT, VECTOR_DISTANCE_ALIAS, arithOverflowError, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildJsonColumnCopy, buildJsonifyText, buildKeyViolationCount, buildPartitionedSelect, buildProjection, buildSelect, buildUpdate, buildVectorSearchCount, buildVectorSearchSelect, buildViewSelect, buildWhere, chunkInsertRows, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, finalizeParams, foreignKeySql, fromSourceHint, fromSourceSql, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, mapQueryErrors, normalizeGeoPointValue, nullsOrderSql, nullsPlacementOf, numericOutOfRangeError, orFragment, orderByList, orderKeySql, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, refActionToSql, renameGeoDistance, renderArith, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, stripPartitionRowNumber, toSqlValue, vectorDistanceSource, viewReadSource };
1853
+ export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, InsertSqlCache, PARTITION_ROW_NUMBER_ALIAS, SEARCH_SOURCE_ALIAS, SQL_DEFAULT, VECTOR_DISTANCE_ALIAS, arithOverflowError, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildJsonColumnCopy, buildJsonifyText, buildKeyViolationCount, buildPartitionedSelect, buildProjection, buildSelect, buildUpdate, buildVectorSearchCount, buildVectorSearchSelect, buildViewSelect, buildWhere, chunkInsertRows, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, finalizeParams, foreignKeySql, fromSourceHint, fromSourceSql, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, mapQueryErrors, normalizeGeoPointValue, nullsOrderSql, nullsPlacementOf, numericOutOfRangeError, orFragment, orderByList, orderKeySql, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, readLockSql, refActionToSql, renameGeoDistance, renderArith, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, stripPartitionRowNumber, toSqlValue, unquoteSqlStringLiteral, vectorDistanceSource, viewReadSource, writeLimitCount, writeLimitWhere };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/db-sql-tools",
3
- "version": "0.1.161",
3
+ "version": "0.1.162",
4
4
  "description": "Shared SQL builder utilities for @atscript database adapters.",
5
5
  "keywords": [
6
6
  "atscript",
@@ -38,11 +38,11 @@
38
38
  },
39
39
  "devDependencies": {
40
40
  "@uniqu/core": "^0.1.15",
41
- "unplugin-atscript": "^0.1.108"
41
+ "unplugin-atscript": "^0.1.109"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@uniqu/core": "^0.1.15",
45
- "@atscript/db": "^0.1.161"
45
+ "@atscript/db": "^0.1.162"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "vp pack",