@minnowdb/core 0.3.0 → 0.4.1

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 (276) hide show
  1. package/README.md +4 -2
  2. package/dist/block-format/block.d.ts +32 -13
  3. package/dist/block-format/block.js +179 -55
  4. package/dist/block-format/checksum.d.ts +2 -1
  5. package/dist/block-format/checksum.js +5 -2
  6. package/dist/block-format/codecs.d.ts +7 -2
  7. package/dist/block-format/codecs.js +53 -12
  8. package/dist/block-format/column.d.ts +3 -2
  9. package/dist/block-format/column.js +96 -30
  10. package/dist/block-format/index.d.ts +2 -2
  11. package/dist/block-format/index.js +2 -2
  12. package/dist/block-format/physical.d.ts +8 -2
  13. package/dist/block-format/physical.js +14 -6
  14. package/dist/block-format/types.d.ts +7 -3
  15. package/dist/block-format/types.js +0 -1
  16. package/dist/block-format/unicode.d.ts +11 -0
  17. package/dist/block-format/unicode.js +46 -0
  18. package/dist/date-value.d.ts +15 -0
  19. package/dist/date-value.js +64 -0
  20. package/dist/engine/artifact-cache.d.ts +0 -1
  21. package/dist/engine/artifact-cache.js +0 -1
  22. package/dist/engine/batch.d.ts +5 -2
  23. package/dist/engine/batch.js +0 -1
  24. package/dist/engine/buffered-writer.d.ts +2 -1
  25. package/dist/engine/buffered-writer.js +34 -9
  26. package/dist/engine/cache-limits.d.ts +25 -0
  27. package/dist/engine/cache-limits.js +25 -0
  28. package/dist/engine/catalog.d.ts +5 -6
  29. package/dist/engine/catalog.js +5 -5
  30. package/dist/engine/client.d.ts +27 -7
  31. package/dist/engine/client.js +197 -31
  32. package/dist/engine/database.d.ts +161 -38
  33. package/dist/engine/database.js +4326 -1523
  34. package/dist/engine/defaults.d.ts +0 -1
  35. package/dist/engine/defaults.js +2 -4
  36. package/dist/engine/errors.d.ts +19 -1
  37. package/dist/engine/errors.js +32 -2
  38. package/dist/engine/fts.d.ts +0 -6
  39. package/dist/engine/fts.js +39 -14
  40. package/dist/engine/group-index.d.ts +0 -1
  41. package/dist/engine/group-index.js +6 -11
  42. package/dist/engine/index.d.ts +16 -11
  43. package/dist/engine/index.js +12 -10
  44. package/dist/engine/join-index.d.ts +0 -1
  45. package/dist/engine/join-index.js +2 -2
  46. package/dist/engine/keyed-live.d.ts +57 -0
  47. package/dist/engine/keyed-live.js +226 -0
  48. package/dist/engine/live-api.d.ts +4 -0
  49. package/dist/engine/live-api.js +4 -0
  50. package/dist/engine/live.d.ts +43 -26
  51. package/dist/engine/live.js +499 -129
  52. package/dist/engine/memory.d.ts +2 -1
  53. package/dist/engine/memory.js +3 -2
  54. package/dist/engine/optimizer.d.ts +0 -1
  55. package/dist/engine/optimizer.js +4 -4
  56. package/dist/engine/query-api.d.ts +3 -0
  57. package/dist/engine/query-api.js +3 -0
  58. package/dist/engine/query-cache.d.ts +0 -1
  59. package/dist/engine/query-cache.js +4 -4
  60. package/dist/engine/query.d.ts +4 -7
  61. package/dist/engine/query.js +348 -155
  62. package/dist/engine/result-wire.d.ts +0 -1
  63. package/dist/engine/result-wire.js +2 -2
  64. package/dist/engine/schema-wire.d.ts +1 -2
  65. package/dist/engine/schema-wire.js +0 -1
  66. package/dist/engine/schema.d.ts +3 -8
  67. package/dist/engine/schema.js +41 -24
  68. package/dist/engine/sort-keys.d.ts +0 -1
  69. package/dist/engine/sort-keys.js +2 -2
  70. package/dist/engine/sql-domains.d.ts +6 -1
  71. package/dist/engine/sql-domains.js +209 -45
  72. package/dist/engine/sql-driver.d.ts +2 -2
  73. package/dist/engine/sql-driver.js +0 -1
  74. package/dist/engine/sql-json.d.ts +0 -1
  75. package/dist/engine/sql-json.js +53 -7
  76. package/dist/engine/sql-semantics.d.ts +8 -5
  77. package/dist/engine/sql-semantics.js +427 -65
  78. package/dist/engine/typed-live.d.ts +67 -0
  79. package/dist/engine/typed-live.js +349 -0
  80. package/dist/engine/vector.d.ts +7 -1
  81. package/dist/engine/vector.js +202 -58
  82. package/dist/engine/worker-host.d.ts +8 -2
  83. package/dist/engine/worker-host.js +616 -152
  84. package/dist/engine/worker.d.ts +0 -1
  85. package/dist/engine/worker.js +3 -3
  86. package/dist/engine/write-block-planner.d.ts +18 -0
  87. package/dist/engine/write-block-planner.js +112 -0
  88. package/dist/index.d.ts +0 -1
  89. package/dist/index.js +0 -1
  90. package/dist/plan/index.d.ts +0 -1
  91. package/dist/plan/index.js +0 -1
  92. package/dist/storage/index.d.ts +1 -1
  93. package/dist/storage/index.js +1 -1
  94. package/dist/storage/indexeddb.d.ts +74 -65
  95. package/dist/storage/indexeddb.js +12301 -2076
  96. package/dist/storage/memory.d.ts +56 -66
  97. package/dist/storage/memory.js +1004 -139
  98. package/dist/storage/opfs/files.d.ts +18 -7
  99. package/dist/storage/opfs/files.js +115 -28
  100. package/dist/storage/opfs/index.d.ts +1 -1
  101. package/dist/storage/opfs/index.js +1 -1
  102. package/dist/storage/opfs/leader.d.ts +403 -91
  103. package/dist/storage/opfs/leader.js +4318 -602
  104. package/dist/storage/opfs/rpc.d.ts +15 -3
  105. package/dist/storage/opfs/rpc.js +223 -2
  106. package/dist/storage/opfs/snapshot-ledger.d.ts +40 -0
  107. package/dist/storage/opfs/snapshot-ledger.js +281 -0
  108. package/dist/storage/opfs/store.d.ts +26 -121
  109. package/dist/storage/opfs/store.js +436 -388
  110. package/dist/storage/persistence.d.ts +37 -0
  111. package/dist/storage/persistence.js +78 -0
  112. package/dist/storage/snapshot-stream.d.ts +24 -0
  113. package/dist/storage/snapshot-stream.js +897 -0
  114. package/dist/storage/snapshot.d.ts +9 -99
  115. package/dist/storage/snapshot.js +33 -309
  116. package/dist/storage/toolkit/extents.d.ts +41 -7
  117. package/dist/storage/toolkit/extents.js +402 -39
  118. package/dist/storage/toolkit/index.d.ts +3 -4
  119. package/dist/storage/toolkit/index.js +27 -3
  120. package/dist/storage/toolkit/record-core.d.ts +174 -61
  121. package/dist/storage/toolkit/record-core.js +5401 -849
  122. package/dist/storage/toolkit/sync-file.d.ts +10 -1
  123. package/dist/storage/toolkit/sync-file.js +50 -2
  124. package/dist/storage/toolkit/wal.d.ts +23 -5
  125. package/dist/storage/toolkit/wal.js +89 -27
  126. package/dist/storage/toolkit/wire.d.ts +4 -6
  127. package/dist/storage/toolkit/wire.js +80 -25
  128. package/dist/storage/types.d.ts +1271 -218
  129. package/dist/storage/types.js +1471 -138
  130. package/dist/testing/block-store-conformance.d.ts +0 -1
  131. package/dist/testing/block-store-conformance.js +877 -138
  132. package/dist/testing/index.d.ts +42 -31
  133. package/dist/testing/index.js +100 -68
  134. package/dist/testing/opfs-shim.d.ts +13 -3
  135. package/dist/testing/opfs-shim.js +40 -6
  136. package/dist/testing/simulator.d.ts +0 -1
  137. package/dist/testing/simulator.js +43 -12
  138. package/dist/testing/sqllogictest.d.ts +0 -1
  139. package/dist/testing/sqllogictest.js +0 -1
  140. package/dist/transactions/index.d.ts +81 -20
  141. package/dist/transactions/index.js +1050 -268
  142. package/dist/worker-protocol/index.d.ts +3 -2
  143. package/dist/worker-protocol/index.js +2 -5
  144. package/package.json +49 -1
  145. package/dist/block-format/block.d.ts.map +0 -1
  146. package/dist/block-format/block.js.map +0 -1
  147. package/dist/block-format/checksum.d.ts.map +0 -1
  148. package/dist/block-format/checksum.js.map +0 -1
  149. package/dist/block-format/codecs.d.ts.map +0 -1
  150. package/dist/block-format/codecs.js.map +0 -1
  151. package/dist/block-format/column.d.ts.map +0 -1
  152. package/dist/block-format/column.js.map +0 -1
  153. package/dist/block-format/index.d.ts.map +0 -1
  154. package/dist/block-format/index.js.map +0 -1
  155. package/dist/block-format/physical.d.ts.map +0 -1
  156. package/dist/block-format/physical.js.map +0 -1
  157. package/dist/block-format/types.d.ts.map +0 -1
  158. package/dist/block-format/types.js.map +0 -1
  159. package/dist/engine/artifact-cache.d.ts.map +0 -1
  160. package/dist/engine/artifact-cache.js.map +0 -1
  161. package/dist/engine/batch.d.ts.map +0 -1
  162. package/dist/engine/batch.js.map +0 -1
  163. package/dist/engine/buffered-writer.d.ts.map +0 -1
  164. package/dist/engine/buffered-writer.js.map +0 -1
  165. package/dist/engine/catalog.d.ts.map +0 -1
  166. package/dist/engine/catalog.js.map +0 -1
  167. package/dist/engine/client.d.ts.map +0 -1
  168. package/dist/engine/client.js.map +0 -1
  169. package/dist/engine/coordinator.d.ts +0 -17
  170. package/dist/engine/coordinator.d.ts.map +0 -1
  171. package/dist/engine/coordinator.js +0 -60
  172. package/dist/engine/coordinator.js.map +0 -1
  173. package/dist/engine/database.d.ts.map +0 -1
  174. package/dist/engine/database.js.map +0 -1
  175. package/dist/engine/defaults.d.ts.map +0 -1
  176. package/dist/engine/defaults.js.map +0 -1
  177. package/dist/engine/errors.d.ts.map +0 -1
  178. package/dist/engine/errors.js.map +0 -1
  179. package/dist/engine/fts.d.ts.map +0 -1
  180. package/dist/engine/fts.js.map +0 -1
  181. package/dist/engine/group-index.d.ts.map +0 -1
  182. package/dist/engine/group-index.js.map +0 -1
  183. package/dist/engine/index.d.ts.map +0 -1
  184. package/dist/engine/index.js.map +0 -1
  185. package/dist/engine/join-index.d.ts.map +0 -1
  186. package/dist/engine/join-index.js.map +0 -1
  187. package/dist/engine/live.d.ts.map +0 -1
  188. package/dist/engine/live.js.map +0 -1
  189. package/dist/engine/memory.d.ts.map +0 -1
  190. package/dist/engine/memory.js.map +0 -1
  191. package/dist/engine/optimizer.d.ts.map +0 -1
  192. package/dist/engine/optimizer.js.map +0 -1
  193. package/dist/engine/query-cache.d.ts.map +0 -1
  194. package/dist/engine/query-cache.js.map +0 -1
  195. package/dist/engine/query.d.ts.map +0 -1
  196. package/dist/engine/query.js.map +0 -1
  197. package/dist/engine/result-wire.d.ts.map +0 -1
  198. package/dist/engine/result-wire.js.map +0 -1
  199. package/dist/engine/schema-wire.d.ts.map +0 -1
  200. package/dist/engine/schema-wire.js.map +0 -1
  201. package/dist/engine/schema.d.ts.map +0 -1
  202. package/dist/engine/schema.js.map +0 -1
  203. package/dist/engine/sort-keys.d.ts.map +0 -1
  204. package/dist/engine/sort-keys.js.map +0 -1
  205. package/dist/engine/sql-domains.d.ts.map +0 -1
  206. package/dist/engine/sql-domains.js.map +0 -1
  207. package/dist/engine/sql-driver.d.ts.map +0 -1
  208. package/dist/engine/sql-driver.js.map +0 -1
  209. package/dist/engine/sql-json.d.ts.map +0 -1
  210. package/dist/engine/sql-json.js.map +0 -1
  211. package/dist/engine/sql-semantics.d.ts.map +0 -1
  212. package/dist/engine/sql-semantics.js.map +0 -1
  213. package/dist/engine/vector.d.ts.map +0 -1
  214. package/dist/engine/vector.js.map +0 -1
  215. package/dist/engine/worker-host.d.ts.map +0 -1
  216. package/dist/engine/worker-host.js.map +0 -1
  217. package/dist/engine/worker.d.ts.map +0 -1
  218. package/dist/engine/worker.js.map +0 -1
  219. package/dist/index.d.ts.map +0 -1
  220. package/dist/index.js.map +0 -1
  221. package/dist/plan/index.d.ts.map +0 -1
  222. package/dist/plan/index.js.map +0 -1
  223. package/dist/storage/fixture-shape.d.ts +0 -42
  224. package/dist/storage/fixture-shape.d.ts.map +0 -1
  225. package/dist/storage/fixture-shape.js +0 -146
  226. package/dist/storage/fixture-shape.js.map +0 -1
  227. package/dist/storage/index.d.ts.map +0 -1
  228. package/dist/storage/index.js.map +0 -1
  229. package/dist/storage/indexeddb.d.ts.map +0 -1
  230. package/dist/storage/indexeddb.js.map +0 -1
  231. package/dist/storage/memory.d.ts.map +0 -1
  232. package/dist/storage/memory.js.map +0 -1
  233. package/dist/storage/opfs/files.d.ts.map +0 -1
  234. package/dist/storage/opfs/files.js.map +0 -1
  235. package/dist/storage/opfs/index.d.ts.map +0 -1
  236. package/dist/storage/opfs/index.js.map +0 -1
  237. package/dist/storage/opfs/leader.d.ts.map +0 -1
  238. package/dist/storage/opfs/leader.js.map +0 -1
  239. package/dist/storage/opfs/rpc.d.ts.map +0 -1
  240. package/dist/storage/opfs/rpc.js.map +0 -1
  241. package/dist/storage/opfs/store.d.ts.map +0 -1
  242. package/dist/storage/opfs/store.js.map +0 -1
  243. package/dist/storage/snapshot.d.ts.map +0 -1
  244. package/dist/storage/snapshot.js.map +0 -1
  245. package/dist/storage/toolkit/extents.d.ts.map +0 -1
  246. package/dist/storage/toolkit/extents.js.map +0 -1
  247. package/dist/storage/toolkit/index.d.ts.map +0 -1
  248. package/dist/storage/toolkit/index.js.map +0 -1
  249. package/dist/storage/toolkit/record-core.d.ts.map +0 -1
  250. package/dist/storage/toolkit/record-core.js.map +0 -1
  251. package/dist/storage/toolkit/sync-file.d.ts.map +0 -1
  252. package/dist/storage/toolkit/sync-file.js.map +0 -1
  253. package/dist/storage/toolkit/wal.d.ts.map +0 -1
  254. package/dist/storage/toolkit/wal.js.map +0 -1
  255. package/dist/storage/toolkit/wire.d.ts.map +0 -1
  256. package/dist/storage/toolkit/wire.js.map +0 -1
  257. package/dist/storage/types.d.ts.map +0 -1
  258. package/dist/storage/types.js.map +0 -1
  259. package/dist/testing/block-store-conformance.d.ts.map +0 -1
  260. package/dist/testing/block-store-conformance.js.map +0 -1
  261. package/dist/testing/index.d.ts.map +0 -1
  262. package/dist/testing/index.js.map +0 -1
  263. package/dist/testing/opfs-shim.d.ts.map +0 -1
  264. package/dist/testing/opfs-shim.js.map +0 -1
  265. package/dist/testing/seeds.d.ts +0 -11
  266. package/dist/testing/seeds.d.ts.map +0 -1
  267. package/dist/testing/seeds.js +0 -50
  268. package/dist/testing/seeds.js.map +0 -1
  269. package/dist/testing/simulator.d.ts.map +0 -1
  270. package/dist/testing/simulator.js.map +0 -1
  271. package/dist/testing/sqllogictest.d.ts.map +0 -1
  272. package/dist/testing/sqllogictest.js.map +0 -1
  273. package/dist/transactions/index.d.ts.map +0 -1
  274. package/dist/transactions/index.js.map +0 -1
  275. package/dist/worker-protocol/index.d.ts.map +0 -1
  276. package/dist/worker-protocol/index.js.map +0 -1
@@ -1,3 +1,6 @@
1
+ import { copyDate, dateIsoString, dateMilliseconds, dateUtcDate, dateUtcDay, dateUtcFullYear, dateUtcHours, dateUtcMinutes, dateUtcMonth, dateUtcSeconds, setDateUtcDate, setDateUtcMonth, } from "../date-value.js";
2
+ import { assertWellFormedString, wellFormedUtf8ByteLength } from "../block-format/unicode.js";
3
+ import { MAX_SQL_NESTING_DEPTH, MAX_SQL_PARAMETERS, MAX_SQL_SCALAR_RESULT_CHARACTERS, MAX_SQL_TEXT_CHARACTERS, MAX_SQL_TOKENS, } from "./cache-limits.js";
1
4
  import { SqlCompileError } from "./errors.js";
2
5
  import { cachedQueryTerms, ftsBm25Row, FtsStatsAccumulator, ftsMatchTruth, renderDocumentValue, tokenize as ftsTokenize, validateFtsQuery, } from "./fts.js";
3
6
  import { QueryMemoryContext } from "./memory.js";
@@ -6,7 +9,7 @@ import { stringArgument } from "./sql-semantics.js";
6
9
  import { jsonAtPath, jsonConstructor, jsonIsValid, jsonValueOf, parseJsonPath, } from "./sql-json.js";
7
10
  import { optimizePlan } from "./optimizer.js";
8
11
  import { compareSqlValues as compareValues, compileLikePattern, compileSimilarPattern, encodeSqlEqualityValue, roundSqlNumber, } from "./sql-semantics.js";
9
- import { arrayDomainValue, collatedDomainValue, exactNumericBinary, exactNumericValue, externalSqlDomainValue, intervalDomainValue, isExactNumeric, isSqlDomainValue, jsonDomainValue, normalizeSqlDomainValue, protectedSqlTextValue, timeDomainValue, uuidDomainValue, } from "./sql-domains.js";
12
+ import { arrayDomainValue, boundedJsonText, collatedDomainValue, exactNumericBinary, exactNumericValue, externalSqlDomainValue, intervalDomainValue, isExactNumeric, isSqlDomainValue, jsonDomainValue, normalizeSqlDomainValue, protectedSqlTextValue, timeDomainValue, uuidDomainValue, } from "./sql-domains.js";
10
13
  import { columnarTableFromRows, prepareVectorQuery, } from "./vector.js";
11
14
  export const scalarFunctionNames = new Set([
12
15
  "ROUND",
@@ -148,7 +151,7 @@ function castValue(value, target) {
148
151
  if (typeof value === "boolean")
149
152
  return protectedSqlTextValue(value ? "true" : "false");
150
153
  if (value instanceof Date)
151
- return protectedSqlTextValue(value.toISOString());
154
+ return protectedSqlTextValue(dateIsoString(value));
152
155
  }
153
156
  if (target === "number" || target === "number-integer") {
154
157
  let parsed;
@@ -198,7 +201,7 @@ function castValue(value, target) {
198
201
  return value;
199
202
  if (typeof value === "string" || typeof value === "number") {
200
203
  const parsed = new Date(value);
201
- if (Number.isFinite(parsed.getTime()))
204
+ if (Number.isFinite(dateMilliseconds(parsed)))
202
205
  return parsed;
203
206
  throw new TypeError(`Cannot cast this value to a datetime: ${String(value)}`);
204
207
  }
@@ -262,7 +265,7 @@ export function scalarFunctionValue(name, values) {
262
265
  // datetimes distinct, so the ordinary single-key hash join can safely carry a composite.
263
266
  if (values.some((value) => value === null || value === undefined))
264
267
  return null;
265
- return JSON.stringify(values.map(encodeSqlEqualityValue));
268
+ return boundedJsonText(values.map(encodeSqlEqualityValue), false, "Composite key");
266
269
  }
267
270
  const first = values[0];
268
271
  if (first === null || first === undefined)
@@ -307,10 +310,16 @@ export function scalarFunctionValue(name, values) {
307
310
  return null;
308
311
  if (values[2] === null || values[2] === undefined)
309
312
  return null;
313
+ const source = stringArgument("REPLACE", first);
310
314
  const search = stringArgument("REPLACE", values[1]);
315
+ const replacement = stringArgument("REPLACE", values[2]);
316
+ assertScalarInputLength(source, "REPLACE source");
317
+ assertScalarInputLength(search, "REPLACE search text");
318
+ assertScalarInputLength(replacement, "REPLACE replacement text");
311
319
  if (search === "")
312
- return stringArgument("REPLACE", first);
313
- return protectedSqlTextValue(stringArgument("REPLACE", first).split(search).join(stringArgument("REPLACE", values[2])));
320
+ return source;
321
+ assertReplacementResultLength(source, search, replacement);
322
+ return protectedSqlTextValue(source.split(search).join(replacement));
314
323
  }
315
324
  case "INSTR": {
316
325
  if (values[1] === null || values[1] === undefined)
@@ -319,7 +328,7 @@ export function scalarFunctionValue(name, values) {
319
328
  const needle = stringArgument("INSTR", values[1]);
320
329
  const index = haystack.indexOf(needle);
321
330
  // 1-based character position, 0 when absent, counting codepoints like LENGTH.
322
- return index === -1 ? 0 : Array.from(haystack.slice(0, index)).length + 1;
331
+ return index === -1 ? 0 : codePointLength(haystack, index) + 1;
323
332
  }
324
333
  case "EXTRACT":
325
334
  return extractDatePart(typeof first === "string" ? first : "", values[1]);
@@ -331,17 +340,23 @@ export function scalarFunctionValue(name, values) {
331
340
  }
332
341
  case "ABS":
333
342
  return Math.abs(numeric(first));
334
- case "UPPER":
335
- return protectedSqlTextValue(stringArgument("UPPER", first).toUpperCase());
336
- case "LOWER":
337
- return protectedSqlTextValue(stringArgument("LOWER", first).toLowerCase());
343
+ case "UPPER": {
344
+ const source = stringArgument("UPPER", first);
345
+ assertScalarInputLength(source, "UPPER input");
346
+ return boundedScalarResult(source.toUpperCase(), "UPPER result");
347
+ }
348
+ case "LOWER": {
349
+ const source = stringArgument("LOWER", first);
350
+ assertScalarInputLength(source, "LOWER input");
351
+ return boundedScalarResult(source.toLowerCase(), "LOWER result");
352
+ }
338
353
  case "TRIM":
339
354
  // SQL TRIM removes spaces, not general whitespace.
340
355
  return trimEnds("TRIM", first, values[1], "both");
341
356
  case "LENGTH":
342
- return Array.from(stringArgument("LENGTH", first)).length;
357
+ return codePointLength(stringArgument("LENGTH", first));
343
358
  case "OCTET_LENGTH":
344
- return new TextEncoder().encode(stringArgument("OCTET_LENGTH", first)).length;
359
+ return wellFormedUtf8ByteLength(stringArgument("OCTET_LENGTH", first), "OCTET_LENGTH input");
345
360
  case "IS_JSON": {
346
361
  const kind = values[1];
347
362
  return jsonIsValid(first, typeof kind === "string" ? kind : "value");
@@ -375,24 +390,24 @@ export function scalarFunctionValue(name, values) {
375
390
  if (!Number.isInteger(width) || width < 0) {
376
391
  throw new TypeError(`${name} length must be a non-negative integer`);
377
392
  }
378
- const text = Array.from(stringArgument(name, first));
379
- if (text.length >= width)
380
- return text.slice(0, width).join("");
393
+ if (width > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
394
+ throw new RangeError(`${name} result exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
395
+ }
396
+ const source = stringArgument(name, first);
397
+ const text = codePointPrefix(source, width);
398
+ if (text.count >= width)
399
+ return boundedScalarResult(text.text, `${name} result`);
381
400
  let fill = " ";
382
401
  if (values.length > 2) {
383
402
  if (values[2] === null || values[2] === undefined)
384
403
  return null;
385
404
  fill = stringArgument(name, values[2]);
386
405
  }
387
- const filler = Array.from(fill);
388
406
  // An empty fill cannot pad, so the value passes through, matching PostgreSQL.
389
- if (filler.length === 0)
390
- return protectedSqlTextValue(text.join(""));
391
- const padding = [];
392
- while (padding.length < width - text.length) {
393
- padding.push(filler[padding.length % filler.length] ?? "");
394
- }
395
- return protectedSqlTextValue(name === "LPAD" ? padding.join("") + text.join("") : text.join("") + padding.join(""));
407
+ const padding = repeatedCodePointPrefix(fill, width - text.count);
408
+ if (padding === "")
409
+ return protectedSqlTextValue(text.text);
410
+ return boundedScalarResult(name === "LPAD" ? padding + text.text : text.text + padding, `${name} result`);
396
411
  }
397
412
  case "OVERLAY": {
398
413
  // OVERLAY(s PLACING r FROM start [FOR length]) replaces `length` characters of `s`
@@ -402,8 +417,12 @@ export function scalarFunctionValue(name, values) {
402
417
  return null;
403
418
  }
404
419
  }
405
- const text = Array.from(stringArgument("OVERLAY", first));
406
- const replacement = Array.from(stringArgument("OVERLAY", values[1]));
420
+ const sourceText = stringArgument("OVERLAY", first);
421
+ const replacementText = stringArgument("OVERLAY", values[1]);
422
+ assertScalarInputLength(sourceText, "OVERLAY source");
423
+ assertScalarInputLength(replacementText, "OVERLAY replacement");
424
+ const text = Array.from(sourceText);
425
+ const replacement = Array.from(replacementText);
407
426
  const start = numeric(values[2]);
408
427
  if (!Number.isInteger(start) || start < 1) {
409
428
  throw new TypeError("OVERLAY start must be a positive integer");
@@ -412,11 +431,11 @@ export function scalarFunctionValue(name, values) {
412
431
  if (!Number.isInteger(span) || span < 0) {
413
432
  throw new TypeError("OVERLAY length must be a non-negative integer");
414
433
  }
415
- return protectedSqlTextValue([
434
+ return boundedScalarResult([
416
435
  ...text.slice(0, start - 1),
417
436
  ...replacement,
418
437
  ...text.slice(Math.min(start - 1 + span, text.length)),
419
- ].join(""));
438
+ ].join(""), "OVERLAY result");
420
439
  }
421
440
  case "CAST":
422
441
  return castValue(first, typeof values[1] === "string" ? values[1] : "");
@@ -424,13 +443,13 @@ export function scalarFunctionValue(name, values) {
424
443
  // PostgreSQL SUBSTRING returns the characters whose positions fall in both the requested
425
444
  // window and the string, so a start before 1 shortens the result instead of shifting it,
426
445
  // and a window entirely off the string is empty rather than an error.
427
- const text = Array.from(stringArgument("SUBSTR", first));
446
+ const source = stringArgument("SUBSTR", first);
428
447
  if (values[1] === null || values[1] === undefined)
429
448
  return null;
430
449
  const start = numeric(values[1]);
431
450
  if (!Number.isInteger(start))
432
451
  throw new TypeError("SUBSTR start must be an integer");
433
- let until = text.length + 1;
452
+ let until = Number.POSITIVE_INFINITY;
434
453
  if (values.length > 2) {
435
454
  if (values[2] === null || values[2] === undefined)
436
455
  return null;
@@ -441,9 +460,109 @@ export function scalarFunctionValue(name, values) {
441
460
  until = start + length;
442
461
  }
443
462
  const from = Math.max(start, 1);
444
- const to = Math.min(until, text.length + 1);
445
- return protectedSqlTextValue(to <= from ? "" : text.slice(from - 1, to - 1).join(""));
463
+ if (until <= from)
464
+ return "";
465
+ return boundedScalarResult(codePointSlice(source, from - 1, until - 1), "SUBSTR result");
466
+ }
467
+ }
468
+ }
469
+ function assertScalarInputLength(value, label) {
470
+ if (value.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
471
+ throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
472
+ }
473
+ }
474
+ function boundedScalarResult(value, label) {
475
+ if (value.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
476
+ throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
477
+ }
478
+ return protectedSqlTextValue(value);
479
+ }
480
+ function codePointLength(value, end = value.length) {
481
+ let count = 0;
482
+ for (let index = 0; index < end; index += 1) {
483
+ const first = value.charCodeAt(index);
484
+ if (first >= 0xd800 && first <= 0xdbff && index + 1 < end) {
485
+ const second = value.charCodeAt(index + 1);
486
+ if (second >= 0xdc00 && second <= 0xdfff)
487
+ index += 1;
488
+ }
489
+ count += 1;
490
+ }
491
+ return count;
492
+ }
493
+ function codePointPrefix(value, count) {
494
+ let index = 0;
495
+ let found = 0;
496
+ while (index < value.length && found < count) {
497
+ const first = value.charCodeAt(index);
498
+ index +=
499
+ first >= 0xd800 &&
500
+ first <= 0xdbff &&
501
+ index + 1 < value.length &&
502
+ value.charCodeAt(index + 1) >= 0xdc00 &&
503
+ value.charCodeAt(index + 1) <= 0xdfff
504
+ ? 2
505
+ : 1;
506
+ found += 1;
507
+ }
508
+ return { text: value.slice(0, index), count: found };
509
+ }
510
+ function repeatedCodePointPrefix(value, count) {
511
+ if (count <= 0 || value.length === 0)
512
+ return "";
513
+ const first = codePointPrefix(value, count);
514
+ if (first.count >= count)
515
+ return first.text;
516
+ const copies = Math.floor(count / first.count);
517
+ const remainder = count % first.count;
518
+ return first.text.repeat(copies) + codePointPrefix(first.text, remainder).text;
519
+ }
520
+ function codePointSlice(value, from, to) {
521
+ let codePoint = 0;
522
+ let index = 0;
523
+ let start = value.length;
524
+ let end = value.length;
525
+ while (index < value.length) {
526
+ if (codePoint === from)
527
+ start = index;
528
+ if (codePoint === to) {
529
+ end = index;
530
+ break;
446
531
  }
532
+ const first = value.charCodeAt(index);
533
+ index +=
534
+ first >= 0xd800 &&
535
+ first <= 0xdbff &&
536
+ index + 1 < value.length &&
537
+ value.charCodeAt(index + 1) >= 0xdc00 &&
538
+ value.charCodeAt(index + 1) <= 0xdfff
539
+ ? 2
540
+ : 1;
541
+ codePoint += 1;
542
+ }
543
+ if (codePoint < from)
544
+ return "";
545
+ if (to === Number.POSITIVE_INFINITY)
546
+ end = value.length;
547
+ const length = end - start;
548
+ if (length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
549
+ throw new RangeError(`SUBSTR result exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
550
+ }
551
+ return value.slice(start, end);
552
+ }
553
+ function assertReplacementResultLength(source, search, replacement) {
554
+ let matches = 0;
555
+ let from = 0;
556
+ for (;;) {
557
+ const index = source.indexOf(search, from);
558
+ if (index < 0)
559
+ break;
560
+ matches += 1;
561
+ from = index + search.length;
562
+ }
563
+ const resultLength = source.length + matches * (replacement.length - search.length);
564
+ if (!Number.isSafeInteger(resultLength) || resultLength > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
565
+ throw new RangeError(`REPLACE result exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
447
566
  }
448
567
  }
449
568
  export const dateTruncUnits = new Set([
@@ -508,15 +627,15 @@ export function dateAddValue(value, months, milliseconds) {
508
627
  if (!(value instanceof Date))
509
628
  throw new TypeError("Date arithmetic requires a datetime value");
510
629
  const monthCount = Number(months ?? 0);
511
- const shifted = new Date(value.getTime());
630
+ const shifted = copyDate(value);
512
631
  if (monthCount !== 0) {
513
- const day = shifted.getUTCDate();
514
- shifted.setUTCDate(1);
515
- shifted.setUTCMonth(shifted.getUTCMonth() + monthCount);
516
- const lastDay = new Date(Date.UTC(shifted.getUTCFullYear(), shifted.getUTCMonth() + 1, 0)).getUTCDate();
517
- shifted.setUTCDate(Math.min(day, lastDay));
632
+ const day = dateUtcDate(shifted);
633
+ setDateUtcDate(shifted, 1);
634
+ setDateUtcMonth(shifted, dateUtcMonth(shifted) + monthCount);
635
+ const lastDay = new Date(Date.UTC(dateUtcFullYear(shifted), dateUtcMonth(shifted) + 1, 0));
636
+ setDateUtcDate(shifted, Math.min(day, dateUtcDate(lastDay)));
518
637
  }
519
- return new Date(shifted.getTime() + Number(milliseconds ?? 0));
638
+ return new Date(dateMilliseconds(shifted) + Number(milliseconds ?? 0));
520
639
  }
521
640
  export function dateTruncValue(unit, value) {
522
641
  if (typeof unit !== "string" || !dateTruncUnits.has(unit.toLowerCase())) {
@@ -527,9 +646,9 @@ export function dateTruncValue(unit, value) {
527
646
  if (!(value instanceof Date))
528
647
  throw new TypeError("DATE_TRUNC requires a datetime value");
529
648
  const normalized = unit.toLowerCase();
530
- const year = value.getUTCFullYear();
531
- const month = value.getUTCMonth();
532
- const day = value.getUTCDate();
649
+ const year = dateUtcFullYear(value);
650
+ const month = dateUtcMonth(value);
651
+ const day = dateUtcDate(value);
533
652
  switch (normalized) {
534
653
  case "year":
535
654
  return new Date(Date.UTC(year, 0, 1));
@@ -539,17 +658,17 @@ export function dateTruncValue(unit, value) {
539
658
  return new Date(Date.UTC(year, month, 1));
540
659
  case "week": {
541
660
  const start = new Date(Date.UTC(year, month, day));
542
- start.setUTCDate(start.getUTCDate() - ((start.getUTCDay() + 6) % 7));
661
+ setDateUtcDate(start, dateUtcDate(start) - ((dateUtcDay(start) + 6) % 7));
543
662
  return start;
544
663
  }
545
664
  case "day":
546
665
  return new Date(Date.UTC(year, month, day));
547
666
  case "hour":
548
- return new Date(Date.UTC(year, month, day, value.getUTCHours()));
667
+ return new Date(Date.UTC(year, month, day, dateUtcHours(value)));
549
668
  case "minute":
550
- return new Date(Date.UTC(year, month, day, value.getUTCHours(), value.getUTCMinutes()));
669
+ return new Date(Date.UTC(year, month, day, dateUtcHours(value), dateUtcMinutes(value)));
551
670
  default:
552
- return new Date(Date.UTC(year, month, day, value.getUTCHours(), value.getUTCMinutes(), value.getUTCSeconds()));
671
+ return new Date(Date.UTC(year, month, day, dateUtcHours(value), dateUtcMinutes(value), dateUtcSeconds(value)));
553
672
  }
554
673
  }
555
674
  /** The output column type of one window: rankings and most aggregates count, MIN/MAX carry. */
@@ -693,6 +812,7 @@ function throwLocated(error, offset, span) {
693
812
  throw error;
694
813
  }
695
814
  export function compileQuery(sql, options = {}) {
815
+ validateSqlSource(sql);
696
816
  const { text, offset } = normalizeSql(sql);
697
817
  if (text.length === 0)
698
818
  throw new SqlCompileError("Enter a SELECT query", offset, 0);
@@ -737,7 +857,7 @@ function columnDefaultFor(expression, sql) {
737
857
  return { kind: "literal", value };
738
858
  }
739
859
  if (value instanceof Date)
740
- return { kind: "literal", value: new Date(value.getTime()) };
860
+ return { kind: "literal", value: copyDate(value) };
741
861
  }
742
862
  return { kind: "expression", sql };
743
863
  }
@@ -996,7 +1116,7 @@ function parseCreateTrigger(text, tokens) {
996
1116
  if (compiled.kind === "insert" && compiled.onConflict !== undefined) {
997
1117
  throw new TypeError("Trigger body INSERTs cannot carry ON CONFLICT");
998
1118
  }
999
- if (compiled.kind !== "insert" && compiled.returning !== undefined) {
1119
+ if (compiled.returning !== undefined) {
1000
1120
  throw new TypeError("Trigger bodies cannot carry RETURNING");
1001
1121
  }
1002
1122
  if ((compiled.parameterCount ?? 0) !== bindings.length) {
@@ -1015,6 +1135,7 @@ function parseCreateTrigger(text, tokens) {
1015
1135
  * leading keyword fails explicitly.
1016
1136
  */
1017
1137
  export function compileStatement(sql) {
1138
+ validateSqlSource(sql);
1018
1139
  const { text, offset } = normalizeSql(sql);
1019
1140
  if (text.length === 0)
1020
1141
  throw new SqlCompileError("Enter a SQL statement", offset, 0);
@@ -1378,7 +1499,7 @@ function validateParameters(count, params) {
1378
1499
  return;
1379
1500
  }
1380
1501
  if (value instanceof Date) {
1381
- if (!Number.isFinite(value.getTime())) {
1502
+ if (!Number.isFinite(dateMilliseconds(value))) {
1382
1503
  throw new TypeError(`Parameter ${label} must be a valid date`);
1383
1504
  }
1384
1505
  return;
@@ -1500,7 +1621,7 @@ function cloneTreeValue(value) {
1500
1621
  if (typeof value !== "object" || value === null)
1501
1622
  return value;
1502
1623
  if (value instanceof Date)
1503
- return new Date(value.getTime());
1624
+ return copyDate(value);
1504
1625
  if (Array.isArray(value)) {
1505
1626
  const copy = new Array(value.length);
1506
1627
  for (let index = 0; index < value.length; index += 1)
@@ -2105,6 +2226,7 @@ function trimPreparedResults(prepared, trim) {
2105
2226
  },
2106
2227
  execute: () => trim(prepared.execute()),
2107
2228
  executeAsync: async (options) => trim(await prepared.executeAsync(options)),
2229
+ executeBatches: (options, consume) => prepared.executeBatches(options, (batch) => consume(trim(batch))),
2108
2230
  close: () => {
2109
2231
  prepared.close();
2110
2232
  },
@@ -2129,21 +2251,25 @@ export function createPreparedQuery(plan, tables, options = {}) {
2129
2251
  // Every engine entry expands MATCH(*) exactly once, here against the row tables' own
2130
2252
  // columns; past this point no executor sees the "*" sentinel.
2131
2253
  plan = expandFtsColumns(plan, rowTableSearchableColumns(tables));
2132
- const resolution = subqueryResolutionSteps(plan);
2133
- for (const step of resolution.steps)
2134
- step.substitute(executeRowQueryInternal(step.block, tables));
2135
- plan = resolution.plan;
2136
- tables = resolveDerivedRowTables(plan, tables);
2137
- plan = expandDistinctWildcard(plan, wildcardRowColumns(tables));
2138
2254
  const memory = new QueryMemoryContext(options.executionMemoryBudgetBytes);
2139
- if ([...tables.values()].some((rows) => rows.length === 0)) {
2140
- if (options.executionMemoryBudgetBytes !== undefined) {
2141
- memory.close();
2142
- throw new TypeError("Query memory budgets require typed columnar schemas when an input table is empty");
2143
- }
2144
- return createPreparedRowQuery(plan, tables, memory);
2145
- }
2146
2255
  try {
2256
+ // Scalar subqueries and derived row sources are materialized before the vector plan is
2257
+ // prepared. They must share the same context: otherwise a tiny (or the finite default)
2258
+ // budget would apply only after an arbitrarily large intermediate had already escaped the
2259
+ // memory model.
2260
+ const resolution = subqueryResolutionSteps(plan);
2261
+ for (const step of resolution.steps) {
2262
+ step.substitute(executeRowQueryInternal(step.block, tables, memory));
2263
+ }
2264
+ plan = resolution.plan;
2265
+ tables = resolveDerivedRowTables(plan, tables, memory);
2266
+ plan = expandDistinctWildcard(plan, wildcardRowColumns(tables));
2267
+ if ([...tables.values()].some((rows) => rows.length === 0)) {
2268
+ if (options.executionMemoryBudgetBytes !== undefined) {
2269
+ throw new TypeError("Query memory budgets require typed columnar schemas when an input table is empty");
2270
+ }
2271
+ return createPreparedRowQuery(plan, tables, memory);
2272
+ }
2147
2273
  return trimPreparedResults(createPreparedColumnarQuery(plan, normalizeColumnarTables(plan, tables), memory), externalizeQueryResult);
2148
2274
  }
2149
2275
  catch (error) {
@@ -2196,6 +2322,11 @@ export function createPreparedColumnarQuery(plan, tables, memory = new QueryMemo
2196
2322
  throw new Error("Prepared query is closed");
2197
2323
  return markExternalizationState(await prepared.executeAsync(options), outputNeedsExternalization);
2198
2324
  },
2325
+ executeBatches(options, consume) {
2326
+ if (closed || prepared === undefined)
2327
+ throw new Error("Prepared query is closed");
2328
+ return prepared.executeBatches(options, (batch) => consume(markExternalizationState(batch, outputNeedsExternalization)));
2329
+ },
2199
2330
  close() {
2200
2331
  if (closed)
2201
2332
  return;
@@ -2218,12 +2349,47 @@ function createPreparedRowQuery(plan, tables, memory) {
2218
2349
  execute() {
2219
2350
  if (closed || rows === undefined)
2220
2351
  throw new Error("Prepared query is closed");
2221
- return externalizeQueryResult(executeRowQueryInternal(plan, cloneRowTables(rows)));
2352
+ const executionMemory = memory.createChild();
2353
+ try {
2354
+ return externalizeQueryResult(executeRowQueryInternal(plan, cloneRowTables(rows), executionMemory));
2355
+ }
2356
+ finally {
2357
+ executionMemory.close();
2358
+ }
2222
2359
  },
2223
2360
  async executeAsync() {
2224
2361
  if (closed || rows === undefined)
2225
2362
  throw new Error("Prepared query is closed");
2226
- return externalizeQueryResult(executeRowQueryInternal(plan, cloneRowTables(rows)));
2363
+ const executionMemory = memory.createChild();
2364
+ try {
2365
+ return externalizeQueryResult(executeRowQueryInternal(plan, cloneRowTables(rows), executionMemory));
2366
+ }
2367
+ finally {
2368
+ executionMemory.close();
2369
+ }
2370
+ },
2371
+ async executeBatches(options, consume) {
2372
+ if (closed || rows === undefined)
2373
+ throw new Error("Prepared query is closed");
2374
+ if (!Number.isSafeInteger(options.batchRows) || options.batchRows <= 0) {
2375
+ throw new RangeError("Query batch rows must be a positive whole number");
2376
+ }
2377
+ const executionMemory = memory.createChild();
2378
+ let result;
2379
+ try {
2380
+ result = externalizeQueryResult(executeRowQueryInternal(plan, cloneRowTables(rows), executionMemory));
2381
+ }
2382
+ finally {
2383
+ executionMemory.close();
2384
+ }
2385
+ for (let start = 0; start < result.rows.length; start += options.batchRows) {
2386
+ options.signal?.throwIfAborted();
2387
+ await consume({
2388
+ columns: [...result.columns],
2389
+ rows: result.rows.slice(start, start + options.batchRows),
2390
+ });
2391
+ }
2392
+ return result.columns;
2227
2393
  },
2228
2394
  close() {
2229
2395
  closed = true;
@@ -2238,7 +2404,7 @@ function cloneRowTables(tables, protectText = false) {
2238
2404
  rows.map((row) => Object.fromEntries(Object.entries(row).map(([column, value]) => [
2239
2405
  column,
2240
2406
  value instanceof Date
2241
- ? new Date(value.getTime())
2407
+ ? copyDate(value)
2242
2408
  : protectText && typeof value === "string"
2243
2409
  ? protectedSqlTextValue(value)
2244
2410
  : value,
@@ -2519,10 +2685,10 @@ export function mapBlockExpressions(block, map) {
2519
2685
  export function resolveStatementDatetimes(plan, now = new Date()) {
2520
2686
  if (plan.usesStatementDatetime !== true)
2521
2687
  return plan;
2522
- const iso = now.toISOString();
2688
+ const iso = dateIsoString(now);
2523
2689
  const values = new Map([
2524
2690
  ["CURRENT_DATE", new Date(`${iso.slice(0, 10)}T00:00:00.000Z`)],
2525
- ["CURRENT_TIMESTAMP", new Date(now.getTime())],
2691
+ ["CURRENT_TIMESTAMP", copyDate(now)],
2526
2692
  ["LOCALTIME", iso.slice(11, 19)],
2527
2693
  ]);
2528
2694
  const resolved = clonePlanTree(plan);
@@ -3111,7 +3277,7 @@ export function applyWindowFunctions(result, windows, options = {}) {
3111
3277
  };
3112
3278
  }
3113
3279
  /** Executes each derived or set-operation source with the row reference. */
3114
- function resolveDerivedRowTables(plan, tables) {
3280
+ function resolveDerivedRowTables(plan, tables, memory) {
3115
3281
  const sources = [plan.base, ...plan.joins];
3116
3282
  const needsDual = sources.some((source) => source.derived === undefined && source.table === DUAL_TABLE);
3117
3283
  if (!needsDual &&
@@ -3127,28 +3293,28 @@ function resolveDerivedRowTables(plan, tables) {
3127
3293
  for (const source of sources) {
3128
3294
  if (source.recursive !== undefined) {
3129
3295
  const { reference, base, step, all } = source.recursive;
3130
- const state = createRecursiveCteState(executeRowQueryInternal(base, tables), all);
3296
+ const state = createRecursiveCteState(executeRowQueryInternal(base, tables, memory), all);
3131
3297
  while (state.frontier.length > 0) {
3132
3298
  const stepTables = new Map(tables);
3133
3299
  stepTables.set(reference, state.frontier);
3134
- state.absorb(executeRowQueryInternal(step, stepTables));
3300
+ state.absorb(executeRowQueryInternal(step, stepTables, memory));
3135
3301
  }
3136
3302
  resolved.set(source.table, state.rows);
3137
3303
  continue;
3138
3304
  }
3139
3305
  if (source.union !== undefined) {
3140
- const results = source.union.blocks.map((block) => executeRowQueryInternal(block, tables));
3306
+ const results = source.union.blocks.map((block) => executeRowQueryInternal(block, tables, memory));
3141
3307
  resolved.set(source.table, combineUnionResults(results, source.union.ops).rows);
3142
3308
  continue;
3143
3309
  }
3144
3310
  if (source.windowed !== undefined) {
3145
- const inner = executeRowQueryInternal(source.windowed.block, tables);
3311
+ const inner = executeRowQueryInternal(source.windowed.block, tables, memory);
3146
3312
  resolved.set(source.table, applyWindowFunctions(inner, source.windowed.windows).rows);
3147
3313
  continue;
3148
3314
  }
3149
3315
  if (source.derived === undefined)
3150
3316
  continue;
3151
- resolved.set(source.table, executeRowQueryInternal(source.derived, tables).rows);
3317
+ resolved.set(source.table, executeRowQueryInternal(source.derived, tables, memory).rows);
3152
3318
  }
3153
3319
  return resolved;
3154
3320
  }
@@ -3170,7 +3336,7 @@ function normalizeColumnarTables(plan, tables) {
3170
3336
  export function executeRowQuery(plan, tables) {
3171
3337
  return externalizeQueryResult(executeRowQueryInternal(plan, cloneRowTables(tables, true)));
3172
3338
  }
3173
- function executeRowQueryInternal(plan, tables) {
3339
+ function executeRowQueryInternal(plan, tables, memory) {
3174
3340
  assertTailParametersBound(plan);
3175
3341
  validateGrouping(plan);
3176
3342
  plan = resolveStatementDatetimes(plan);
@@ -3182,7 +3348,7 @@ function executeRowQueryInternal(plan, tables) {
3182
3348
  plan = expandFtsColumns(plan, rowTableSearchableColumns(tables));
3183
3349
  const resolution = subqueryResolutionSteps(plan);
3184
3350
  for (const step of resolution.steps) {
3185
- step.substitute(executeRowQueryInternal(step.block, tables));
3351
+ step.substitute(executeRowQueryInternal(step.block, tables, memory));
3186
3352
  }
3187
3353
  // Stats annotation writes into the plan; when resolution had nothing to clone, the plan is
3188
3354
  // still the caller's (possibly cached) object, and frozen statistics would survive into later
@@ -3191,7 +3357,7 @@ function executeRowQueryInternal(plan, tables) {
3191
3357
  resolution.plan === plan && planContainsFts(plan, "bm25")
3192
3358
  ? structuredClone(plan)
3193
3359
  : resolution.plan;
3194
- tables = resolveDerivedRowTables(plan, tables);
3360
+ tables = resolveDerivedRowTables(plan, tables, memory);
3195
3361
  plan = expandDistinctWildcard(plan, wildcardRowColumns(tables));
3196
3362
  annotateRowFtsStats(plan, tables);
3197
3363
  let contexts = (tables.get(plan.base.table) ?? []).map((row) => ({
@@ -3212,17 +3378,28 @@ function executeRowQueryInternal(plan, tables) {
3212
3378
  group.push(context);
3213
3379
  groups.set(key, group);
3214
3380
  }
3215
- rows = [...groups.values()]
3216
- .filter((group) => plan.having.every((predicate) => evaluateBooleanExpression({
3217
- kind: "condition",
3218
- operator: predicate.operator,
3219
- left: predicate.left,
3220
- right: predicate.right,
3221
- }, (nested) => evaluate(nested, group[0] ?? {}, group)) === true))
3222
- .map((group) => project(plan.select, group[0] ?? {}, group));
3381
+ rows = [];
3382
+ for (const group of groups.values()) {
3383
+ if (!plan.having.every((predicate) => evaluateBooleanExpression({
3384
+ kind: "condition",
3385
+ operator: predicate.operator,
3386
+ left: predicate.left,
3387
+ right: predicate.right,
3388
+ }, (nested) => evaluate(nested, group[0] ?? {}, group)) === true)) {
3389
+ continue;
3390
+ }
3391
+ const row = project(plan.select, group[0] ?? {}, group);
3392
+ memory?.tally(rowQueryPayloadBytes(row), "Row query result");
3393
+ rows.push(row);
3394
+ }
3223
3395
  }
3224
3396
  else {
3225
- rows = contexts.map((context) => project(plan.select, context));
3397
+ rows = [];
3398
+ for (const context of contexts) {
3399
+ const row = project(plan.select, context);
3400
+ memory?.tally(rowQueryPayloadBytes(row), "Row query result");
3401
+ rows.push(row);
3402
+ }
3226
3403
  }
3227
3404
  if (plan.orderBy.length > 0) {
3228
3405
  // Only a wildcard select needs the source shapes: every other select resolves against its
@@ -3260,6 +3437,25 @@ function executeRowQueryInternal(plan, tables) {
3260
3437
  : plan.select.map((item) => item.alias);
3261
3438
  return ties.trim({ columns, rows });
3262
3439
  }
3440
+ /** Matches the vector executor's deliberately inexpensive modeled result-payload accounting. */
3441
+ function rowQueryPayloadBytes(row) {
3442
+ let total = 8;
3443
+ for (const key in row) {
3444
+ const value = row[key] ?? null;
3445
+ total +=
3446
+ value === null
3447
+ ? 1
3448
+ : typeof value === "boolean"
3449
+ ? 2
3450
+ : typeof value === "number" || value instanceof Date
3451
+ ? 9
3452
+ : 1 + value.length;
3453
+ }
3454
+ if (!Number.isSafeInteger(total) || total < 0) {
3455
+ throw new RangeError("Row query result payload exceeds the safe integer range");
3456
+ }
3457
+ return total;
3458
+ }
3263
3459
  /**
3264
3460
  * Resolves one ORDER BY reference to the output column that carries its values, throwing when
3265
3461
  * nothing matches. Dropping an unresolved sort key silently would return rows in an arbitrary
@@ -3532,7 +3728,7 @@ function evaluate(expression, context, group) {
3532
3728
  if (expression.distinct === true) {
3533
3729
  const seen = new Set();
3534
3730
  values = values.filter((value) => {
3535
- const key = value instanceof Date ? ` d${String(value.getTime())}` : value;
3731
+ const key = value instanceof Date ? ` d${String(dateMilliseconds(value))}` : value;
3536
3732
  if (seen.has(key))
3537
3733
  return false;
3538
3734
  seen.add(key);
@@ -3664,10 +3860,6 @@ export function cachedListMembership(node, items) {
3664
3860
  }
3665
3861
  return cached;
3666
3862
  }
3667
- /** Compiles a LIKE pattern (% = any run, _ = any character) to an anchored RegExp, cached. */
3668
- export function likeRegExp(pattern, caseInsensitive = false, escape) {
3669
- return compileLikePattern(pattern, caseInsensitive, escape);
3670
- }
3671
3863
  const extractFields = new Set([
3672
3864
  "year",
3673
3865
  "quarter",
@@ -3695,70 +3887,38 @@ function extractDatePart(field, value) {
3695
3887
  throw new TypeError("EXTRACT requires a datetime value");
3696
3888
  switch (normalized) {
3697
3889
  case "year":
3698
- return value.getUTCFullYear();
3890
+ return dateUtcFullYear(value);
3699
3891
  case "quarter":
3700
- return Math.floor(value.getUTCMonth() / 3) + 1;
3892
+ return Math.floor(dateUtcMonth(value) / 3) + 1;
3701
3893
  case "month":
3702
- return value.getUTCMonth() + 1;
3894
+ return dateUtcMonth(value) + 1;
3703
3895
  case "week": {
3704
- const date = new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()));
3896
+ const date = new Date(Date.UTC(dateUtcFullYear(value), dateUtcMonth(value), dateUtcDate(value)));
3705
3897
  // ISO week: shift to the Thursday of this week, then count weeks from January 1st.
3706
- date.setUTCDate(date.getUTCDate() + 4 - (date.getUTCDay() || 7));
3707
- const yearStart = Date.UTC(date.getUTCFullYear(), 0, 1);
3708
- return Math.ceil(((date.getTime() - yearStart) / 86_400_000 + 1) / 7);
3898
+ setDateUtcDate(date, dateUtcDate(date) + 4 - (dateUtcDay(date) || 7));
3899
+ const yearStart = Date.UTC(dateUtcFullYear(date), 0, 1);
3900
+ return Math.ceil(((dateMilliseconds(date) - yearStart) / 86_400_000 + 1) / 7);
3709
3901
  }
3710
3902
  case "day":
3711
- return value.getUTCDate();
3903
+ return dateUtcDate(value);
3712
3904
  case "hour":
3713
- return value.getUTCHours();
3905
+ return dateUtcHours(value);
3714
3906
  case "minute":
3715
- return value.getUTCMinutes();
3907
+ return dateUtcMinutes(value);
3716
3908
  case "second":
3717
- return value.getUTCSeconds();
3909
+ return dateUtcSeconds(value);
3718
3910
  case "epoch":
3719
- return value.getTime() / 1000;
3911
+ return dateMilliseconds(value) / 1000;
3720
3912
  default:
3721
- return value.getUTCDay();
3913
+ return dateUtcDay(value);
3722
3914
  }
3723
3915
  }
3724
- const likeMatcherCache = new Map();
3725
3916
  /**
3726
- * A compiled LIKE matcher. Patterns shaped `abc%`, `%abc`, `%abc%`, and `abc` skip the regular
3727
- * expression entirely prefix/suffix/containment string scans are several times faster and
3728
- * dominate real workloads — and everything else falls back to the anchored RegExp.
3917
+ * Shared bounded LIKE matcher. Compilation and the common literal prefix/suffix/containment
3918
+ * fast paths are cached in sql-semantics; complex wildcard shapes have deterministic work caps.
3729
3919
  */
3730
3920
  export function likeMatches(pattern, value, caseInsensitive = false, escape) {
3731
- const key = `${caseInsensitive ? "i" : "s"}${escape ?? ""}\0${pattern}`;
3732
- let matcher = likeMatcherCache.get(key);
3733
- if (matcher === undefined) {
3734
- matcher = buildLikeMatcher(pattern, caseInsensitive, escape);
3735
- if (likeMatcherCache.size >= 128)
3736
- likeMatcherCache.clear();
3737
- likeMatcherCache.set(key, matcher);
3738
- }
3739
- return matcher(value);
3740
- }
3741
- function buildLikeMatcher(pattern, caseInsensitive, escape) {
3742
- if (escape === undefined && !pattern.includes("_")) {
3743
- const leading = pattern.startsWith("%");
3744
- const trailing = pattern.endsWith("%");
3745
- const body = pattern.slice(leading ? 1 : 0, trailing ? pattern.length - 1 : undefined);
3746
- if (!body.includes("%")) {
3747
- const needle = caseInsensitive ? body.toLowerCase() : body;
3748
- const fold = caseInsensitive
3749
- ? (value) => value.toLowerCase()
3750
- : (value) => value;
3751
- if (leading && trailing)
3752
- return (value) => fold(value).includes(needle);
3753
- if (trailing)
3754
- return (value) => fold(value).startsWith(needle);
3755
- if (leading)
3756
- return (value) => fold(value).endsWith(needle);
3757
- return (value) => fold(value) === needle;
3758
- }
3759
- }
3760
- const regExp = likeRegExp(pattern, caseInsensitive, escape);
3761
- return (value) => regExp.test(value);
3921
+ return compileLikePattern(pattern, caseInsensitive, escape).test(value);
3762
3922
  }
3763
3923
  /** Splits a quantified operator like "> ANY" into its comparison and quantifier. */
3764
3924
  export function parseQuantified(operator) {
@@ -4087,7 +4247,7 @@ export function expressionAliases(expression) {
4087
4247
  return new Set(expressionColumns(expression).flatMap((reference) => reference.includes(".") ? [reference.split(".")[0] ?? ""] : []));
4088
4248
  }
4089
4249
  function comparable(value) {
4090
- return value instanceof Date ? value.getTime() : value;
4250
+ return value instanceof Date ? dateMilliseconds(value) : value;
4091
4251
  }
4092
4252
  /**
4093
4253
  * Resolves NULL placement for one order term. An omitted placement follows PostgreSQL: NULLS LAST
@@ -6529,6 +6689,9 @@ class Parser {
6529
6689
  throw new TypeError("Use either ? or $n placeholders in one statement, not both");
6530
6690
  }
6531
6691
  this.#positionalParameters += 1;
6692
+ if (this.#positionalParameters > MAX_SQL_PARAMETERS) {
6693
+ throw new RangeError(`A SQL statement cannot exceed ${String(MAX_SQL_PARAMETERS)} parameters`);
6694
+ }
6532
6695
  return { kind: "parameter", index: this.#positionalParameters - 1 };
6533
6696
  }
6534
6697
  if (this.#positionalParameters > 0) {
@@ -6538,6 +6701,9 @@ class Parser {
6538
6701
  if (!Number.isInteger(number) || number < 1) {
6539
6702
  throw new TypeError(`Parameter numbers start at $1: $${token.text}`);
6540
6703
  }
6704
+ if (number > MAX_SQL_PARAMETERS) {
6705
+ throw new RangeError(`A SQL statement cannot exceed ${String(MAX_SQL_PARAMETERS)} parameters`);
6706
+ }
6541
6707
  this.#highestNumberedParameter = Math.max(this.#highestNumberedParameter, number);
6542
6708
  return { kind: "parameter", index: number - 1 };
6543
6709
  }
@@ -6789,7 +6955,7 @@ class Parser {
6789
6955
  }
6790
6956
  if (upper === "DATE" && this.#peek().kind === "string") {
6791
6957
  const date = new Date(`${this.#take("string").text}T00:00:00.000Z`);
6792
- if (!Number.isFinite(date.getTime()))
6958
+ if (!Number.isFinite(dateMilliseconds(date)))
6793
6959
  throw new TypeError("Invalid DATE literal");
6794
6960
  return { kind: "literal", value: date };
6795
6961
  }
@@ -8245,8 +8411,9 @@ function jsonTableColumnValue(column, value) {
8245
8411
  date = new Date(value);
8246
8412
  else
8247
8413
  throw new TypeError("JSON_TABLE datetime is invalid");
8248
- if (!Number.isFinite(date.getTime()))
8414
+ if (!Number.isFinite(dateMilliseconds(date))) {
8249
8415
  throw new TypeError("JSON_TABLE datetime is invalid");
8416
+ }
8250
8417
  return date;
8251
8418
  }
8252
8419
  export function timestampLiteral(text) {
@@ -8257,8 +8424,9 @@ export function timestampLiteral(text) {
8257
8424
  const [, day, time = "00:00:00", zone = "Z"] = match;
8258
8425
  const seconds = time.length === 5 ? `${time}:00` : time;
8259
8426
  const date = new Date(`${String(day)}T${seconds}${zone === "Z" ? "Z" : zone}`);
8260
- if (!Number.isFinite(date.getTime()))
8427
+ if (!Number.isFinite(dateMilliseconds(date))) {
8261
8428
  throw new TypeError(`Invalid TIMESTAMP literal: ${text}`);
8429
+ }
8262
8430
  return date;
8263
8431
  }
8264
8432
  /** The parser's OFFSET range contract, shared with the typed builder. */
@@ -8433,6 +8601,24 @@ function validNumericLiteral(text, radix) {
8433
8601
  }
8434
8602
  function tokenize(sql) {
8435
8603
  const tokens = [];
8604
+ let nestingDepth = 0;
8605
+ const push = (token) => {
8606
+ if (tokens.length >= MAX_SQL_TOKENS) {
8607
+ throw new SqlCompileError(`A SQL statement cannot exceed ${String(MAX_SQL_TOKENS)} tokens`, token.start, Math.max(token.end - token.start, 1));
8608
+ }
8609
+ if (token.kind === "punctuation") {
8610
+ if (token.text === "(" || token.text === "[") {
8611
+ nestingDepth += 1;
8612
+ if (nestingDepth > MAX_SQL_NESTING_DEPTH) {
8613
+ throw new SqlCompileError(`SQL nesting cannot exceed ${String(MAX_SQL_NESTING_DEPTH)} levels`, token.start, 1);
8614
+ }
8615
+ }
8616
+ else if ((token.text === ")" || token.text === "]") && nestingDepth > 0) {
8617
+ nestingDepth -= 1;
8618
+ }
8619
+ }
8620
+ tokens.push(token);
8621
+ };
8436
8622
  let index = 0;
8437
8623
  while (index < sql.length) {
8438
8624
  const character = sql[index] ?? "";
@@ -8444,7 +8630,7 @@ function tokenize(sql) {
8444
8630
  const start = index++;
8445
8631
  while (index < sql.length && /[A-Za-z0-9_]/.test(sql[index] ?? ""))
8446
8632
  index += 1;
8447
- tokens.push({ kind: "identifier", text: sql.slice(start, index), start, end: index });
8633
+ push({ kind: "identifier", text: sql.slice(start, index), start, end: index });
8448
8634
  continue;
8449
8635
  }
8450
8636
  if (/\d/.test(character)) {
@@ -8461,7 +8647,7 @@ function tokenize(sql) {
8461
8647
  if (!validNumericLiteral(digits, radix) || !Number.isSafeInteger(value)) {
8462
8648
  throw new SqlCompileError(`Invalid number: ${sql.slice(start, index)}`, start, index - start);
8463
8649
  }
8464
- tokens.push({ kind: "number", text: String(value), start, end: index });
8650
+ push({ kind: "number", text: String(value), start, end: index });
8465
8651
  continue;
8466
8652
  }
8467
8653
  index += 1;
@@ -8471,7 +8657,7 @@ function tokenize(sql) {
8471
8657
  const text = sql.slice(start, index);
8472
8658
  if (!validNumericLiteral(text, 10))
8473
8659
  throw new SqlCompileError(`Invalid number: ${text}`, start, index - start);
8474
- tokens.push({ kind: "number", text: text.replaceAll("_", ""), start, end: index });
8660
+ push({ kind: "number", text: text.replaceAll("_", ""), start, end: index });
8475
8661
  continue;
8476
8662
  }
8477
8663
  if (character === "'") {
@@ -8493,7 +8679,7 @@ function tokenize(sql) {
8493
8679
  }
8494
8680
  if (!closed)
8495
8681
  throw new SqlCompileError("Unterminated string literal", start, sql.length - start);
8496
- tokens.push({ kind: "string", text: value, start, end: index });
8682
+ push({ kind: "string", text: value, start, end: index });
8497
8683
  continue;
8498
8684
  }
8499
8685
  if (character === '"') {
@@ -8519,11 +8705,11 @@ function tokenize(sql) {
8519
8705
  if (value.length === 0) {
8520
8706
  throw new SqlCompileError("Quoted identifiers cannot be empty", start, index - start);
8521
8707
  }
8522
- tokens.push({ kind: "identifier", text: value, quoted: true, start, end: index });
8708
+ push({ kind: "identifier", text: value, quoted: true, start, end: index });
8523
8709
  continue;
8524
8710
  }
8525
8711
  if (character === "?") {
8526
- tokens.push({ kind: "parameter", text: "", start: index, end: index + 1 });
8712
+ push({ kind: "parameter", text: "", start: index, end: index + 1 });
8527
8713
  index += 1;
8528
8714
  continue;
8529
8715
  }
@@ -8535,7 +8721,7 @@ function tokenize(sql) {
8535
8721
  if (digits.length === 0) {
8536
8722
  throw new SqlCompileError("Expected a parameter number after $", start, 1);
8537
8723
  }
8538
- tokens.push({ kind: "parameter", text: digits, start, end: index });
8724
+ push({ kind: "parameter", text: digits, start, end: index });
8539
8725
  continue;
8540
8726
  }
8541
8727
  const pair = sql.slice(index, index + 2);
@@ -8557,19 +8743,19 @@ function tokenize(sql) {
8557
8743
  if (character === ";") {
8558
8744
  // Statement separators lex normally; the routers reject them everywhere except inside
8559
8745
  // a CREATE TRIGGER body, which is the one multi-statement construct.
8560
- tokens.push({ kind: "punctuation", text: ";", start: index, end: index + 1 });
8746
+ push({ kind: "punctuation", text: ";", start: index, end: index + 1 });
8561
8747
  index += 1;
8562
8748
  continue;
8563
8749
  }
8564
8750
  if ([">=", "<=", "!=", "<>", "||"].includes(pair)) {
8565
- tokens.push({ kind: "operator", text: pair, start: index, end: index + 2 });
8751
+ push({ kind: "operator", text: pair, start: index, end: index + 2 });
8566
8752
  index += 2;
8567
8753
  continue;
8568
8754
  }
8569
8755
  if (["+", "-", "*", "/", "%", "=", ">", "<"].includes(character))
8570
- tokens.push({ kind: "operator", text: character, start: index, end: index + 1 });
8756
+ push({ kind: "operator", text: character, start: index, end: index + 1 });
8571
8757
  else if (["(", ")", "[", "]", ",", "."].includes(character))
8572
- tokens.push({ kind: "punctuation", text: character, start: index, end: index + 1 });
8758
+ push({ kind: "punctuation", text: character, start: index, end: index + 1 });
8573
8759
  else
8574
8760
  throw new SqlCompileError(`Unsupported SQL character: ${character}`, index, 1);
8575
8761
  index += 1;
@@ -8577,4 +8763,11 @@ function tokenize(sql) {
8577
8763
  tokens.push({ kind: "eof", text: "", start: sql.length, end: sql.length });
8578
8764
  return tokens;
8579
8765
  }
8580
- //# sourceMappingURL=query.js.map
8766
+ function validateSqlSource(sql) {
8767
+ if (typeof sql !== "string")
8768
+ throw new TypeError("SQL must be a string");
8769
+ if (sql.length > MAX_SQL_TEXT_CHARACTERS) {
8770
+ throw new RangeError(`SQL text cannot exceed ${String(MAX_SQL_TEXT_CHARACTERS)} characters`);
8771
+ }
8772
+ assertWellFormedString(sql, "SQL text");
8773
+ }