@minnowdb/core 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/README.md +23 -32
  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 +2 -1
  21. package/dist/engine/artifact-cache.js +5 -1
  22. package/dist/engine/batch.d.ts +11 -3
  23. package/dist/engine/batch.js +17 -7
  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 +24 -5
  29. package/dist/engine/catalog.js +34 -7
  30. package/dist/engine/client.d.ts +29 -7
  31. package/dist/engine/client.js +216 -40
  32. package/dist/engine/database.d.ts +224 -39
  33. package/dist/engine/database.js +7475 -1539
  34. package/dist/engine/defaults.d.ts +4 -8
  35. package/dist/engine/defaults.js +46 -22
  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 +41 -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 -10
  43. package/dist/engine/index.js +12 -9
  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 +504 -126
  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 +262 -27
  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 +1 -2
  59. package/dist/engine/query-cache.js +7 -5
  60. package/dist/engine/query.d.ts +134 -29
  61. package/dist/engine/query.js +1668 -372
  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 +6 -3
  65. package/dist/engine/schema-wire.js +40 -34
  66. package/dist/engine/schema.d.ts +156 -80
  67. package/dist/engine/schema.js +570 -108
  68. package/dist/engine/sort-keys.d.ts +4 -4
  69. package/dist/engine/sort-keys.js +44 -29
  70. package/dist/engine/sql-domains.d.ts +31 -0
  71. package/dist/engine/sql-domains.js +585 -0
  72. package/dist/engine/sql-driver.d.ts +19 -0
  73. package/dist/engine/sql-driver.js +1 -0
  74. package/dist/engine/sql-json.d.ts +7 -9
  75. package/dist/engine/sql-json.js +75 -15
  76. package/dist/engine/sql-semantics.d.ts +8 -3
  77. package/dist/engine/sql-semantics.js +458 -30
  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 +12 -2
  81. package/dist/engine/vector.js +635 -119
  82. package/dist/engine/worker-host.d.ts +9 -2
  83. package/dist/engine/worker-host.js +620 -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 +3 -4
  91. package/dist/plan/index.js +3 -4
  92. package/dist/storage/index.d.ts +1 -1
  93. package/dist/storage/index.js +1 -1
  94. package/dist/storage/indexeddb.d.ts +99 -51
  95. package/dist/storage/indexeddb.js +13448 -2660
  96. package/dist/storage/memory.d.ts +64 -57
  97. package/dist/storage/memory.js +1042 -128
  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 +409 -75
  103. package/dist/storage/opfs/leader.js +4621 -650
  104. package/dist/storage/opfs/rpc.d.ts +15 -3
  105. package/dist/storage/opfs/rpc.js +224 -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 +33 -99
  109. package/dist/storage/opfs/store.js +453 -364
  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 -85
  115. package/dist/storage/snapshot.js +33 -265
  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 +4 -5
  119. package/dist/storage/toolkit/index.js +28 -4
  120. package/dist/storage/toolkit/record-core.d.ts +182 -55
  121. package/dist/storage/toolkit/record-core.js +6158 -1331
  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 +15 -2
  127. package/dist/storage/toolkit/wire.js +199 -10
  128. package/dist/storage/types.d.ts +1439 -204
  129. package/dist/storage/types.js +1745 -141
  130. package/dist/testing/block-store-conformance.d.ts +0 -1
  131. package/dist/testing/block-store-conformance.js +1009 -119
  132. package/dist/testing/index.d.ts +46 -26
  133. package/dist/testing/index.js +112 -57
  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 +97 -0
  137. package/dist/testing/simulator.js +591 -0
  138. package/dist/testing/sqllogictest.d.ts +89 -0
  139. package/dist/testing/sqllogictest.js +434 -0
  140. package/dist/transactions/index.d.ts +92 -16
  141. package/dist/transactions/index.js +1074 -236
  142. package/dist/worker-protocol/index.d.ts +3 -2
  143. package/dist/worker-protocol/index.js +2 -5
  144. package/package.json +53 -3
  145. package/postgres-feature-profile.json +223 -0
  146. package/sql-feature-matrix.json +172 -252
  147. package/dist/block-format/block.d.ts.map +0 -1
  148. package/dist/block-format/block.js.map +0 -1
  149. package/dist/block-format/checksum.d.ts.map +0 -1
  150. package/dist/block-format/checksum.js.map +0 -1
  151. package/dist/block-format/codecs.d.ts.map +0 -1
  152. package/dist/block-format/codecs.js.map +0 -1
  153. package/dist/block-format/column.d.ts.map +0 -1
  154. package/dist/block-format/column.js.map +0 -1
  155. package/dist/block-format/index.d.ts.map +0 -1
  156. package/dist/block-format/index.js.map +0 -1
  157. package/dist/block-format/physical.d.ts.map +0 -1
  158. package/dist/block-format/physical.js.map +0 -1
  159. package/dist/block-format/types.d.ts.map +0 -1
  160. package/dist/block-format/types.js.map +0 -1
  161. package/dist/engine/artifact-cache.d.ts.map +0 -1
  162. package/dist/engine/artifact-cache.js.map +0 -1
  163. package/dist/engine/batch.d.ts.map +0 -1
  164. package/dist/engine/batch.js.map +0 -1
  165. package/dist/engine/buffered-writer.d.ts.map +0 -1
  166. package/dist/engine/buffered-writer.js.map +0 -1
  167. package/dist/engine/catalog.d.ts.map +0 -1
  168. package/dist/engine/catalog.js.map +0 -1
  169. package/dist/engine/client.d.ts.map +0 -1
  170. package/dist/engine/client.js.map +0 -1
  171. package/dist/engine/coordinator.d.ts +0 -17
  172. package/dist/engine/coordinator.d.ts.map +0 -1
  173. package/dist/engine/coordinator.js +0 -60
  174. package/dist/engine/coordinator.js.map +0 -1
  175. package/dist/engine/database.d.ts.map +0 -1
  176. package/dist/engine/database.js.map +0 -1
  177. package/dist/engine/defaults.d.ts.map +0 -1
  178. package/dist/engine/defaults.js.map +0 -1
  179. package/dist/engine/errors.d.ts.map +0 -1
  180. package/dist/engine/errors.js.map +0 -1
  181. package/dist/engine/fts.d.ts.map +0 -1
  182. package/dist/engine/fts.js.map +0 -1
  183. package/dist/engine/group-index.d.ts.map +0 -1
  184. package/dist/engine/group-index.js.map +0 -1
  185. package/dist/engine/index.d.ts.map +0 -1
  186. package/dist/engine/index.js.map +0 -1
  187. package/dist/engine/join-index.d.ts.map +0 -1
  188. package/dist/engine/join-index.js.map +0 -1
  189. package/dist/engine/live.d.ts.map +0 -1
  190. package/dist/engine/live.js.map +0 -1
  191. package/dist/engine/memory.d.ts.map +0 -1
  192. package/dist/engine/memory.js.map +0 -1
  193. package/dist/engine/optimizer.d.ts.map +0 -1
  194. package/dist/engine/optimizer.js.map +0 -1
  195. package/dist/engine/query-cache.d.ts.map +0 -1
  196. package/dist/engine/query-cache.js.map +0 -1
  197. package/dist/engine/query.d.ts.map +0 -1
  198. package/dist/engine/query.js.map +0 -1
  199. package/dist/engine/result-wire.d.ts.map +0 -1
  200. package/dist/engine/result-wire.js.map +0 -1
  201. package/dist/engine/schema-wire.d.ts.map +0 -1
  202. package/dist/engine/schema-wire.js.map +0 -1
  203. package/dist/engine/schema.d.ts.map +0 -1
  204. package/dist/engine/schema.js.map +0 -1
  205. package/dist/engine/sort-keys.d.ts.map +0 -1
  206. package/dist/engine/sort-keys.js.map +0 -1
  207. package/dist/engine/sql-json.d.ts.map +0 -1
  208. package/dist/engine/sql-json.js.map +0 -1
  209. package/dist/engine/sql-semantics.d.ts.map +0 -1
  210. package/dist/engine/sql-semantics.js.map +0 -1
  211. package/dist/engine/vector.d.ts.map +0 -1
  212. package/dist/engine/vector.js.map +0 -1
  213. package/dist/engine/worker-host.d.ts.map +0 -1
  214. package/dist/engine/worker-host.js.map +0 -1
  215. package/dist/engine/worker.d.ts.map +0 -1
  216. package/dist/engine/worker.js.map +0 -1
  217. package/dist/index.d.ts.map +0 -1
  218. package/dist/index.js.map +0 -1
  219. package/dist/plan/index.d.ts.map +0 -1
  220. package/dist/plan/index.js.map +0 -1
  221. package/dist/storage/fixture-shape.d.ts +0 -42
  222. package/dist/storage/fixture-shape.d.ts.map +0 -1
  223. package/dist/storage/fixture-shape.js +0 -146
  224. package/dist/storage/fixture-shape.js.map +0 -1
  225. package/dist/storage/index.d.ts.map +0 -1
  226. package/dist/storage/index.js.map +0 -1
  227. package/dist/storage/indexeddb.d.ts.map +0 -1
  228. package/dist/storage/indexeddb.js.map +0 -1
  229. package/dist/storage/memory.d.ts.map +0 -1
  230. package/dist/storage/memory.js.map +0 -1
  231. package/dist/storage/opfs/files.d.ts.map +0 -1
  232. package/dist/storage/opfs/files.js.map +0 -1
  233. package/dist/storage/opfs/index.d.ts.map +0 -1
  234. package/dist/storage/opfs/index.js.map +0 -1
  235. package/dist/storage/opfs/leader.d.ts.map +0 -1
  236. package/dist/storage/opfs/leader.js.map +0 -1
  237. package/dist/storage/opfs/rpc.d.ts.map +0 -1
  238. package/dist/storage/opfs/rpc.js.map +0 -1
  239. package/dist/storage/opfs/store.d.ts.map +0 -1
  240. package/dist/storage/opfs/store.js.map +0 -1
  241. package/dist/storage/snapshot.d.ts.map +0 -1
  242. package/dist/storage/snapshot.js.map +0 -1
  243. package/dist/storage/toolkit/extents.d.ts.map +0 -1
  244. package/dist/storage/toolkit/extents.js.map +0 -1
  245. package/dist/storage/toolkit/index.d.ts.map +0 -1
  246. package/dist/storage/toolkit/index.js.map +0 -1
  247. package/dist/storage/toolkit/record-core.d.ts.map +0 -1
  248. package/dist/storage/toolkit/record-core.js.map +0 -1
  249. package/dist/storage/toolkit/sync-file.d.ts.map +0 -1
  250. package/dist/storage/toolkit/sync-file.js.map +0 -1
  251. package/dist/storage/toolkit/wal.d.ts.map +0 -1
  252. package/dist/storage/toolkit/wal.js.map +0 -1
  253. package/dist/storage/toolkit/wire.d.ts.map +0 -1
  254. package/dist/storage/toolkit/wire.js.map +0 -1
  255. package/dist/storage/types.d.ts.map +0 -1
  256. package/dist/storage/types.js.map +0 -1
  257. package/dist/testing/block-store-conformance.d.ts.map +0 -1
  258. package/dist/testing/block-store-conformance.js.map +0 -1
  259. package/dist/testing/index.d.ts.map +0 -1
  260. package/dist/testing/index.js.map +0 -1
  261. package/dist/testing/opfs-shim.d.ts.map +0 -1
  262. package/dist/testing/opfs-shim.js.map +0 -1
  263. package/dist/testing/seeds.d.ts +0 -11
  264. package/dist/testing/seeds.d.ts.map +0 -1
  265. package/dist/testing/seeds.js +0 -50
  266. package/dist/testing/seeds.js.map +0 -1
  267. package/dist/transactions/index.d.ts.map +0 -1
  268. package/dist/transactions/index.js.map +0 -1
  269. package/dist/worker-protocol/index.d.ts.map +0 -1
  270. package/dist/worker-protocol/index.js.map +0 -1
@@ -1,6 +1,11 @@
1
1
  import { stringArgument } from "./sql-semantics.js";
2
+ import { externalSqlDomainValue } from "./sql-domains.js";
3
+ import { MAX_CACHEABLE_TEXT_CHARACTERS, MAX_SQL_SCALAR_RESULT_CHARACTERS } from "./cache-limits.js";
2
4
  export function parseJsonPath(path, caller) {
3
5
  const text = stringArgument(caller, path).trim();
6
+ if (text.length > MAX_CACHEABLE_TEXT_CHARACTERS) {
7
+ throw new RangeError(`${caller} path exceeds ${String(MAX_CACHEABLE_TEXT_CHARACTERS)} characters`);
8
+ }
4
9
  if (!text.startsWith("$"))
5
10
  throw new TypeError(`${caller} paths start at $`);
6
11
  const steps = [];
@@ -41,8 +46,9 @@ export function parseJsonPath(path, caller) {
41
46
  export function jsonAtPath(document, path, caller) {
42
47
  const steps = parseJsonPath(path, caller);
43
48
  let current;
49
+ const source = boundedJsonDocument(document, caller);
44
50
  try {
45
- current = JSON.parse(stringArgument(caller, document));
51
+ current = JSON.parse(source);
46
52
  }
47
53
  catch {
48
54
  // A document that is not JSON selects nothing rather than failing the whole statement,
@@ -71,8 +77,12 @@ export function jsonAtPath(document, path, caller) {
71
77
  }
72
78
  /** Whether a value is JSON text of the requested shape (T825). */
73
79
  export function jsonIsValid(document, kind) {
80
+ document = externalSqlDomainValue(document);
74
81
  if (typeof document !== "string")
75
82
  return false;
83
+ if (document.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
84
+ throw new RangeError(`JSON document exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
85
+ }
76
86
  let parsed;
77
87
  try {
78
88
  parsed = JSON.parse(document);
@@ -94,27 +104,77 @@ export function jsonIsValid(document, kind) {
94
104
  }
95
105
  /** A SQL value as its JSON counterpart: datetimes serialize as ISO text, like every cast. */
96
106
  export function jsonValueOf(value) {
97
- return value instanceof Date ? value.toISOString() : value;
107
+ const external = externalSqlDomainValue(value);
108
+ return external instanceof Date ? dateIsoString(external) : external;
98
109
  }
99
110
  /**
100
- * JSON_ARRAY(v, ...) and JSON_OBJECT(k, v, ...) (T811/T812). Both skip NULL members, which is
101
- * the standard's default (ABSENT ON NULL for objects, NULL ON NULL for arrays is the other
102
- * spelling), and both return JSON text.
111
+ * JSON_ARRAY(v, ...) and JSON_OBJECT(k, v, ...) (T811/T812). The omitted null-handling clause is
112
+ * `NULL ON NULL`: SQL NULL becomes a JSON null. `ABSENT ON NULL` is a separate spelling rather
113
+ * than the default. Constructors return JSON text at the JavaScript boundary.
103
114
  */
104
115
  export function jsonConstructor(name, values) {
105
116
  if (name === "JSON_ARRAY") {
106
- return JSON.stringify(values.filter((value) => value !== null && value !== undefined).map(jsonValueOf));
117
+ const members = values.map((value) => boundedJsonValue(value ?? null, "JSON_ARRAY value"));
118
+ return joinBoundedJson("[", members, "]", "JSON_ARRAY result");
107
119
  }
108
- const entries = {};
120
+ const members = [];
109
121
  for (let index = 0; index + 1 < values.length; index += 2) {
110
- const key = values[index];
111
- if (typeof key !== "string")
112
- throw new TypeError("JSON_OBJECT keys must be strings");
122
+ const rawKey = values[index];
123
+ if (rawKey === null || rawKey === undefined) {
124
+ throw new TypeError("JSON_OBJECT keys cannot be NULL");
125
+ }
126
+ let key;
127
+ if (rawKey instanceof Date)
128
+ key = dateIsoString(rawKey);
129
+ else if (typeof rawKey === "string")
130
+ key = rawKey;
131
+ else if (typeof rawKey === "number" || typeof rawKey === "boolean")
132
+ key = String(rawKey);
133
+ else
134
+ throw new TypeError("JSON_OBJECT keys must be scalar values");
113
135
  const member = values[index + 1];
114
- if (member === null || member === undefined)
115
- continue;
116
- entries[key] = jsonValueOf(member);
136
+ // Build JSON text directly. WITHOUT UNIQUE KEYS is the default, so duplicate names must be
137
+ // preserved; assigning through a JavaScript object would collapse them and mishandle
138
+ // special names such as "__proto__".
139
+ const encodedKey = boundedJsonValue(key, "JSON_OBJECT key");
140
+ const encodedValue = boundedJsonValue(member ?? null, "JSON_OBJECT value");
141
+ members.push(`${encodedKey}:${encodedValue}`);
142
+ }
143
+ return joinBoundedJson("{", members, "}", "JSON_OBJECT result");
144
+ }
145
+ function boundedJsonDocument(value, caller) {
146
+ const document = stringArgument(caller, externalSqlDomainValue(value));
147
+ if (document.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
148
+ throw new RangeError(`${caller} document exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
149
+ }
150
+ return document;
151
+ }
152
+ function boundedJsonValue(value, label) {
153
+ const normalized = jsonValueOf(value);
154
+ if (typeof normalized === "string" && normalized.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
155
+ throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
156
+ }
157
+ const encoded = JSON.stringify(normalized);
158
+ if (typeof encoded !== "string")
159
+ throw new TypeError(`${label} is not JSON serializable`);
160
+ if (encoded.length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
161
+ throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
162
+ }
163
+ return encoded;
164
+ }
165
+ function joinBoundedJson(prefix, members, suffix, label) {
166
+ let length = prefix.length + suffix.length;
167
+ for (const member of members) {
168
+ length += member.length;
169
+ if (!Number.isSafeInteger(length) || length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
170
+ throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
171
+ }
172
+ }
173
+ if (members.length > 1)
174
+ length += members.length - 1;
175
+ if (!Number.isSafeInteger(length) || length > MAX_SQL_SCALAR_RESULT_CHARACTERS) {
176
+ throw new RangeError(`${label} exceeds ${String(MAX_SQL_SCALAR_RESULT_CHARACTERS)} characters`);
117
177
  }
118
- return JSON.stringify(entries);
178
+ return `${prefix}${members.join(",")}${suffix}`;
119
179
  }
120
- //# sourceMappingURL=sql-json.js.map
180
+ import { dateIsoString } from "../date-value.js";
@@ -18,8 +18,14 @@ export declare function encodeSqlEqualityValue(value: unknown): readonly unknown
18
18
  * integer and clamps to 0..30, with ties rounded away from zero.
19
19
  */
20
20
  export declare function roundSqlNumber(value: number, precision?: number): number;
21
- /** Compiles SQL LIKE (% = any run, _ = one codepoint) into a bounded cached RegExp. */
22
- export declare function compileLikePattern(pattern: string, caseInsensitive?: boolean, escape?: string): RegExp;
21
+ /** A whole-string SQL pattern matcher. Unlike RegExp, test never coerces its input. */
22
+ export interface SqlPatternMatcher {
23
+ test(value: string): boolean;
24
+ }
25
+ /** Compiles SQL LIKE without exposing input to the host regular-expression engine. */
26
+ export declare function compileLikePattern(pattern: string, caseInsensitive?: boolean, escape?: string): SqlPatternMatcher;
27
+ /** PostgreSQL SIMILAR TO compiled to a Thompson NFA with bounded deterministic work. */
28
+ export declare function compileSimilarPattern(pattern: string, escape?: string): SqlPatternMatcher;
23
29
  /** Defines an own enumerable result-column property, including the special name `__proto__`. */
24
30
  export declare function defineSqlResultProperty(target: Record<string, unknown>, name: string, value: unknown): void;
25
31
  /**
@@ -28,4 +34,3 @@ export declare function defineSqlResultProperty(target: Record<string, unknown>,
28
34
  * a silent coercion here would make `LENGTH(42)` answer instead of failing.
29
35
  */
30
36
  export declare function stringArgument(name: string, value: unknown): string;
31
- //# sourceMappingURL=sql-semantics.d.ts.map
@@ -1,3 +1,7 @@
1
+ import { dateMilliseconds } from "../date-value.js";
2
+ import { assertWellFormedString } from "../block-format/unicode.js";
3
+ import { MAX_SQL_NESTING_DEPTH, MAX_SQL_PATTERN_CHARACTERS, MAX_SQL_PATTERN_MATCH_STEPS, } from "./cache-limits.js";
4
+ import { collatedDomainCompare, enumDomainCompare, exactNumericCompare, externalSqlDomainValue, externalSqlTextValue, } from "./sql-domains.js";
1
5
  /**
2
6
  * SQL value semantics shared by the row and vector executors.
3
7
  *
@@ -9,8 +13,26 @@
9
13
  * and signed zero compares equal because SQL numeric equality does not distinguish it.
10
14
  */
11
15
  export function compareSqlValues(left, right) {
12
- const a = left instanceof Date ? left.getTime() : left;
13
- const b = right instanceof Date ? right.getTime() : right;
16
+ const plainLeft = externalSqlTextValue(left);
17
+ const plainRight = externalSqlTextValue(right);
18
+ if ((typeof left === "string" && plainLeft !== left) ||
19
+ (typeof right === "string" && plainRight !== right)) {
20
+ if (typeof plainLeft !== "string" || typeof plainRight !== "string") {
21
+ throw new TypeError("Values must have comparable SQL types");
22
+ }
23
+ return compareSqlStrings(plainLeft, plainRight);
24
+ }
25
+ const collated = collatedDomainCompare(left, right);
26
+ if (collated !== undefined)
27
+ return collated;
28
+ const enumOrder = enumDomainCompare(left, right);
29
+ if (enumOrder !== undefined)
30
+ return enumOrder;
31
+ const exact = exactNumericCompare(left, right);
32
+ if (exact !== undefined)
33
+ return exact;
34
+ const a = left instanceof Date ? dateMilliseconds(left) : left;
35
+ const b = right instanceof Date ? dateMilliseconds(right) : right;
14
36
  if (a === b)
15
37
  return 0;
16
38
  if (a === null || a === undefined)
@@ -45,9 +67,9 @@ export function encodeSqlEqualityValue(value) {
45
67
  if (typeof value === "number")
46
68
  return [2, String(value === 0 ? 0 : value)];
47
69
  if (typeof value === "string")
48
- return [3, value];
70
+ return [3, externalSqlTextValue(value)];
49
71
  if (value instanceof Date)
50
- return [4, value.getTime()];
72
+ return [4, dateMilliseconds(value)];
51
73
  throw new TypeError("Query produced an unsupported value");
52
74
  }
53
75
  /**
@@ -61,43 +83,449 @@ export function roundSqlNumber(value, precision = 0) {
61
83
  const rounded = Number(value.toFixed(digits));
62
84
  return rounded === 0 ? 0 : rounded;
63
85
  }
86
+ const MAX_PATTERN_CACHE_ENTRIES = 128;
87
+ const MAX_PATTERN_CACHE_RETAINED_SIZE = 64 * 1024;
64
88
  const likeCache = new Map();
65
- /** Compiles SQL LIKE (% = any run, _ = one codepoint) into a bounded cached RegExp. */
66
- export function compileLikePattern(pattern, caseInsensitive = false, escape) {
67
- const key = JSON.stringify([caseInsensitive, escape ?? null, pattern]);
68
- const cached = likeCache.get(key);
69
- if (cached !== undefined)
70
- return cached;
71
- let source = "^";
89
+ const similarCache = new Map();
90
+ let likeCacheRetainedSize = 0;
91
+ let similarCacheRetainedSize = 0;
92
+ function cachedPattern(cache, key) {
93
+ const entry = cache.get(key);
94
+ if (entry === undefined)
95
+ return undefined;
96
+ cache.delete(key);
97
+ cache.set(key, entry);
98
+ return entry[0];
99
+ }
100
+ function retainPattern(cache, key, matcher, retainedSize, setRetainedSize) {
101
+ const entrySize = key.length + matcher.retainedSize;
102
+ if (entrySize > MAX_PATTERN_CACHE_RETAINED_SIZE)
103
+ return;
104
+ while (cache.size >= MAX_PATTERN_CACHE_ENTRIES ||
105
+ retainedSize + entrySize > MAX_PATTERN_CACHE_RETAINED_SIZE) {
106
+ const oldest = cache.keys().next().value;
107
+ if (oldest === undefined)
108
+ break;
109
+ const removed = cache.get(oldest);
110
+ cache.delete(oldest);
111
+ retainedSize -= removed?.[1] ?? 0;
112
+ }
113
+ cache.set(key, [matcher, entrySize]);
114
+ setRetainedSize(retainedSize + entrySize);
115
+ }
116
+ function throwPatternWorkLimit() {
117
+ throw new RangeError(`SQL pattern match exceeds ${String(MAX_SQL_PATTERN_MATCH_STEPS)} deterministic steps`);
118
+ }
119
+ function exactSingleCharacter(value, label) {
120
+ assertBoundedPattern(value, label, 2);
121
+ const characters = value[Symbol.iterator]();
122
+ const first = characters.next();
123
+ if (first.done === true || characters.next().done !== true) {
124
+ throw new TypeError(`${label.replace(" escape", " ESCAPE")} takes one character`);
125
+ }
126
+ return first.value;
127
+ }
128
+ function tokenizeLike(pattern, escape) {
129
+ const tokens = [];
72
130
  let escaped = false;
73
131
  for (const character of pattern) {
74
132
  if (escaped) {
75
- source += escapeRegExp(character);
133
+ tokens.push(character);
76
134
  escaped = false;
77
- continue;
78
135
  }
79
- if (escape !== undefined && character === escape) {
136
+ else if (escape !== undefined && character === escape) {
80
137
  escaped = true;
81
- continue;
82
138
  }
83
- if (character === "%")
84
- source += "[\\s\\S]*";
139
+ else if (character === "%") {
140
+ if (tokens.at(-1) !== 0)
141
+ tokens.push(0);
142
+ }
85
143
  else if (character === "_")
86
- source += "[\\s\\S]";
144
+ tokens.push(1);
87
145
  else
88
- source += escapeRegExp(character);
146
+ tokens.push(character);
89
147
  }
90
148
  if (escaped)
91
149
  throw new TypeError("LIKE pattern ends with a dangling escape character");
92
- source += "$";
93
- const regExp = new RegExp(source, caseInsensitive ? "iu" : "u");
94
- if (likeCache.size >= 128)
95
- likeCache.clear();
96
- likeCache.set(key, regExp);
97
- return regExp;
150
+ return tokens;
151
+ }
152
+ /** Unicode simple-case approximation used consistently by row and vector LIKE execution. */
153
+ function simpleCaseFold(character) {
154
+ const upper = character.toUpperCase();
155
+ const codePoints = upper[Symbol.iterator]();
156
+ return codePoints.next().done !== true && codePoints.next().done === true ? upper : character;
157
+ }
158
+ function likeCharactersEqual(left, right, caseInsensitive) {
159
+ return left === right || (caseInsensitive && simpleCaseFold(left) === simpleCaseFold(right));
160
+ }
161
+ function literalLikeMatcher(tokens, caseInsensitive) {
162
+ if (tokens.includes(1))
163
+ return undefined;
164
+ const leading = tokens[0] === 0;
165
+ const trailing = tokens.at(-1) === 0;
166
+ const begin = leading ? 1 : 0;
167
+ const end = trailing ? tokens.length - 1 : tokens.length;
168
+ if (tokens.slice(begin, end).some((token) => typeof token !== "string"))
169
+ return undefined;
170
+ const body = tokens
171
+ .slice(begin, end)
172
+ .map((token) => (typeof token === "string" ? token : ""))
173
+ .join("");
174
+ if (!caseInsensitive) {
175
+ const test = (value) => {
176
+ if (typeof value !== "string")
177
+ throw new TypeError("LIKE input must be a string");
178
+ if (leading && trailing)
179
+ return value.includes(body);
180
+ if (leading)
181
+ return value.endsWith(body);
182
+ if (trailing)
183
+ return value.startsWith(body);
184
+ return value === body;
185
+ };
186
+ return { test, retainedSize: tokens.length * 16 + body.length * 2 };
187
+ }
188
+ const needle = Array.from(body, simpleCaseFold);
189
+ const prefix = new Uint32Array(needle.length);
190
+ for (let index = 1, matched = 0; index < needle.length; index += 1) {
191
+ while (matched > 0 && needle[index] !== needle[matched])
192
+ matched = prefix[matched - 1] ?? 0;
193
+ if (needle[index] === needle[matched])
194
+ matched += 1;
195
+ prefix[index] = matched;
196
+ }
197
+ const test = (value) => {
198
+ if (typeof value !== "string")
199
+ throw new TypeError("LIKE input must be a string");
200
+ if (needle.length === 0)
201
+ return leading || trailing || value.length === 0;
202
+ let matched = 0;
203
+ let position = 0;
204
+ let matchedAtEnd = false;
205
+ for (const rawCharacter of value) {
206
+ matchedAtEnd = false;
207
+ const character = simpleCaseFold(rawCharacter);
208
+ while (matched > 0 && character !== needle[matched])
209
+ matched = prefix[matched - 1] ?? 0;
210
+ if (character === needle[matched])
211
+ matched += 1;
212
+ position += 1;
213
+ if (matched === needle.length) {
214
+ const startsAt = position - needle.length;
215
+ if (leading && trailing)
216
+ return true;
217
+ if (!leading && startsAt === 0) {
218
+ if (trailing)
219
+ return true;
220
+ matchedAtEnd = true;
221
+ }
222
+ else if (leading && !trailing)
223
+ matchedAtEnd = true;
224
+ matched = prefix[matched - 1] ?? 0;
225
+ }
226
+ }
227
+ return matchedAtEnd;
228
+ };
229
+ return {
230
+ test,
231
+ retainedSize: tokens.length * 16 + body.length * 2 + prefix.byteLength,
232
+ };
233
+ }
234
+ function generalLikeMatcher(tokens, caseInsensitive) {
235
+ const test = (value) => {
236
+ if (typeof value !== "string")
237
+ throw new TypeError("LIKE input must be a string");
238
+ let remaining = MAX_SQL_PATTERN_MATCH_STEPS;
239
+ let tokenIndex = 0;
240
+ let valueIndex = 0;
241
+ let manyIndex = -1;
242
+ let retryValueIndex = 0;
243
+ while (valueIndex < value.length) {
244
+ if (--remaining < 0)
245
+ throwPatternWorkLimit();
246
+ const token = tokens[tokenIndex];
247
+ if (token === 0) {
248
+ manyIndex = tokenIndex;
249
+ tokenIndex += 1;
250
+ retryValueIndex = valueIndex;
251
+ continue;
252
+ }
253
+ const codePoint = value.codePointAt(valueIndex);
254
+ if (codePoint === undefined)
255
+ break;
256
+ const character = String.fromCodePoint(codePoint);
257
+ if (token === 1 ||
258
+ (typeof token === "string" && likeCharactersEqual(token, character, caseInsensitive))) {
259
+ tokenIndex += 1;
260
+ valueIndex += character.length;
261
+ continue;
262
+ }
263
+ if (manyIndex < 0)
264
+ return false;
265
+ const retryCodePoint = value.codePointAt(retryValueIndex);
266
+ if (retryCodePoint === undefined)
267
+ return false;
268
+ retryValueIndex += String.fromCodePoint(retryCodePoint).length;
269
+ valueIndex = retryValueIndex;
270
+ tokenIndex = manyIndex + 1;
271
+ }
272
+ while (tokens[tokenIndex] === 0) {
273
+ if (--remaining < 0)
274
+ throwPatternWorkLimit();
275
+ tokenIndex += 1;
276
+ }
277
+ return tokenIndex === tokens.length;
278
+ };
279
+ return { test, retainedSize: tokens.length * 16 };
280
+ }
281
+ /** Compiles SQL LIKE without exposing input to the host regular-expression engine. */
282
+ export function compileLikePattern(pattern, caseInsensitive = false, escape) {
283
+ assertBoundedPattern(pattern, "LIKE pattern");
284
+ const exactEscape = escape === undefined ? undefined : exactSingleCharacter(escape, "LIKE escape");
285
+ const key = JSON.stringify([caseInsensitive, exactEscape ?? null, pattern]);
286
+ const cached = cachedPattern(likeCache, key);
287
+ if (cached !== undefined)
288
+ return cached;
289
+ const tokens = tokenizeLike(pattern, exactEscape);
290
+ const matcher = literalLikeMatcher(tokens, caseInsensitive) ?? generalLikeMatcher(tokens, caseInsensitive);
291
+ retainPattern(likeCache, key, matcher, likeCacheRetainedSize, (size) => {
292
+ likeCacheRetainedSize = size;
293
+ });
294
+ return matcher;
295
+ }
296
+ const ANY_CHARACTER = () => true;
297
+ /** Parses SIMILAR TO directly into an NFA, avoiding an allocation-heavy intermediate AST. */
298
+ class SimilarCompiler {
299
+ escape;
300
+ #characters;
301
+ states = [];
302
+ #index = 0;
303
+ constructor(pattern, escape) {
304
+ this.escape = escape;
305
+ this.#characters = Array.from(pattern);
306
+ }
307
+ compile() {
308
+ const fragment = this.#alternative(0);
309
+ if (this.#index !== this.#characters.length) {
310
+ throw new TypeError("SIMILAR TO pattern has an unmatched closing parenthesis");
311
+ }
312
+ return fragment;
313
+ }
314
+ #state() {
315
+ this.states.push([[]]);
316
+ return this.states.length - 1;
317
+ }
318
+ #empty() {
319
+ const start = this.#state();
320
+ const end = this.#state();
321
+ this.states[start]?.[0].push(end);
322
+ return [start, end];
323
+ }
324
+ #consume(matches) {
325
+ const start = this.#state();
326
+ const end = this.#state();
327
+ const state = this.states[start];
328
+ if (state !== undefined)
329
+ state[1] = [end, matches];
330
+ return [start, end];
331
+ }
332
+ #repeat(fragment, plus) {
333
+ const start = this.#state();
334
+ const end = this.#state();
335
+ this.states[start]?.[0].push(fragment[0]);
336
+ if (!plus)
337
+ this.states[start]?.[0].push(end);
338
+ this.states[fragment[1]]?.[0].push(fragment[0], end);
339
+ return [start, end];
340
+ }
341
+ #alternative(depth) {
342
+ const first = this.#sequence(depth);
343
+ if (this.#characters[this.#index] !== "|" || this.#characters[this.#index] === this.escape) {
344
+ return first;
345
+ }
346
+ const start = this.#state();
347
+ const end = this.#state();
348
+ this.states[start]?.[0].push(first[0]);
349
+ this.states[first[1]]?.[0].push(end);
350
+ while (this.#characters[this.#index] === "|" && this.#characters[this.#index] !== this.escape) {
351
+ this.#index += 1;
352
+ const next = this.#sequence(depth);
353
+ this.states[start]?.[0].push(next[0]);
354
+ this.states[next[1]]?.[0].push(end);
355
+ }
356
+ return [start, end];
357
+ }
358
+ #sequence(depth) {
359
+ let result;
360
+ for (;;) {
361
+ const character = this.#characters[this.#index];
362
+ if (character === undefined ||
363
+ ((character === "|" || character === ")") && character !== this.escape)) {
364
+ return result ?? this.#empty();
365
+ }
366
+ let fragment = this.#atom(depth);
367
+ const quantifier = this.#characters[this.#index];
368
+ if ((quantifier === "*" || quantifier === "+") && quantifier !== this.escape) {
369
+ this.#index += 1;
370
+ fragment = this.#repeat(fragment, quantifier === "+");
371
+ const repeated = this.#characters[this.#index];
372
+ if ((repeated === "*" || repeated === "+") && repeated !== this.escape) {
373
+ throw new TypeError("SIMILAR TO pattern repeats a quantifier");
374
+ }
375
+ }
376
+ if (result === undefined)
377
+ result = fragment;
378
+ else {
379
+ this.states[result[1]]?.[0].push(fragment[0]);
380
+ result = [result[0], fragment[1]];
381
+ }
382
+ }
383
+ }
384
+ #atom(depth) {
385
+ const character = this.#characters[this.#index];
386
+ if (character === undefined)
387
+ return this.#empty();
388
+ this.#index += 1;
389
+ if (character === this.escape) {
390
+ const literal = this.#characters[this.#index];
391
+ if (literal === undefined) {
392
+ throw new TypeError("SIMILAR TO pattern ends with its escape character");
393
+ }
394
+ this.#index += 1;
395
+ return this.#consume((candidate) => candidate === literal);
396
+ }
397
+ if (character === "*" || character === "+") {
398
+ throw new TypeError("SIMILAR TO quantifier has no preceding expression");
399
+ }
400
+ if (character === "(") {
401
+ if (depth >= MAX_SQL_NESTING_DEPTH) {
402
+ throw new RangeError(`SIMILAR TO nesting exceeds ${String(MAX_SQL_NESTING_DEPTH)} levels`);
403
+ }
404
+ const child = this.#alternative(depth + 1);
405
+ if (this.#characters[this.#index] !== ")") {
406
+ throw new TypeError("SIMILAR TO pattern has an unmatched opening parenthesis");
407
+ }
408
+ this.#index += 1;
409
+ return child;
410
+ }
411
+ if (character === "[")
412
+ return this.#consume(this.#characterClass());
413
+ if (character === "%")
414
+ return this.#repeat(this.#consume(ANY_CHARACTER), false);
415
+ if (character === "_")
416
+ return this.#consume(ANY_CHARACTER);
417
+ return this.#consume((candidate) => candidate === character);
418
+ }
419
+ #characterClass() {
420
+ let source = "[";
421
+ let closed = false;
422
+ let hasMember = false;
423
+ while (this.#index < this.#characters.length) {
424
+ const character = this.#characters[this.#index] ?? "";
425
+ this.#index += 1;
426
+ if (character === this.escape) {
427
+ const literal = this.#characters[this.#index];
428
+ if (literal === undefined) {
429
+ throw new TypeError("SIMILAR TO pattern ends with its escape character");
430
+ }
431
+ this.#index += 1;
432
+ source += ["\\", "]", "^", "-"].includes(literal) ? `\\${literal}` : literal;
433
+ hasMember = true;
434
+ continue;
435
+ }
436
+ if (character === "]") {
437
+ closed = true;
438
+ break;
439
+ }
440
+ source += character;
441
+ if (character !== "^" || source.length > 2)
442
+ hasMember = true;
443
+ }
444
+ if (!closed)
445
+ throw new TypeError("SIMILAR TO pattern has an unterminated character class");
446
+ if (!hasMember)
447
+ throw new TypeError("SIMILAR TO pattern has an empty character class");
448
+ source += "]";
449
+ let expression;
450
+ try {
451
+ // The host expression is deliberately limited to one character class and one codepoint.
452
+ // It cannot contain repetition, grouping, or alternation, so backtracking work is constant.
453
+ expression = new RegExp(`^(?:${source})$`, "u");
454
+ }
455
+ catch {
456
+ throw new TypeError("SIMILAR TO pattern has an invalid character class");
457
+ }
458
+ return (candidate) => expression.test(candidate);
459
+ }
98
460
  }
99
- function escapeRegExp(value) {
100
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
461
+ function nfaMatcher(pattern, escape) {
462
+ const compiler = new SimilarCompiler(pattern, escape);
463
+ const fragment = compiler.compile();
464
+ const { states } = compiler;
465
+ const test = (value) => {
466
+ if (typeof value !== "string")
467
+ throw new TypeError("SIMILAR TO input must be a string");
468
+ let remaining = MAX_SQL_PATTERN_MATCH_STEPS;
469
+ const marks = new Uint32Array(states.length);
470
+ const stack = [];
471
+ let active = [];
472
+ let next = [];
473
+ let generation = 1;
474
+ const addClosure = (seed, output) => {
475
+ stack.push(seed);
476
+ while (stack.length > 0) {
477
+ if (--remaining < 0)
478
+ throwPatternWorkLimit();
479
+ const stateIndex = stack.pop();
480
+ if (stateIndex === undefined || marks[stateIndex] === generation)
481
+ continue;
482
+ marks[stateIndex] = generation;
483
+ output.push(stateIndex);
484
+ for (const target of states[stateIndex]?.[0] ?? [])
485
+ stack.push(target);
486
+ }
487
+ };
488
+ addClosure(fragment[0], active);
489
+ for (const character of value) {
490
+ generation += 1;
491
+ next.length = 0;
492
+ for (const stateIndex of active) {
493
+ if (--remaining < 0)
494
+ throwPatternWorkLimit();
495
+ const transition = states[stateIndex]?.[1];
496
+ if (transition?.[1](character) === true)
497
+ addClosure(transition[0], next);
498
+ }
499
+ if (next.length === 0)
500
+ return false;
501
+ [active, next] = [next, active];
502
+ }
503
+ return active.includes(fragment[1]);
504
+ };
505
+ // State objects, epsilon arrays, transition objects, and matcher closures all cost materially
506
+ // more than their integer payload. A conservative model keeps large compiled patterns out of
507
+ // the cache instead of pretending their Uint32-sized indexes are the whole retained graph.
508
+ return { test, retainedSize: states.length * 32 };
509
+ }
510
+ /** PostgreSQL SIMILAR TO compiled to a Thompson NFA with bounded deterministic work. */
511
+ export function compileSimilarPattern(pattern, escape = "\\") {
512
+ assertBoundedPattern(pattern, "SIMILAR TO pattern");
513
+ const exactEscape = exactSingleCharacter(escape, "SIMILAR TO escape");
514
+ const key = JSON.stringify([exactEscape, pattern]);
515
+ const cached = cachedPattern(similarCache, key);
516
+ if (cached !== undefined)
517
+ return cached;
518
+ const matcher = nfaMatcher(pattern, exactEscape);
519
+ retainPattern(similarCache, key, matcher, similarCacheRetainedSize, (size) => {
520
+ similarCacheRetainedSize = size;
521
+ });
522
+ return matcher;
523
+ }
524
+ function assertBoundedPattern(value, label, limit = MAX_SQL_PATTERN_CHARACTERS) {
525
+ if (value.length > limit) {
526
+ throw new RangeError(`${label} exceeds ${String(limit)} characters`);
527
+ }
528
+ assertWellFormedString(value, label);
101
529
  }
102
530
  /** Defines an own enumerable result-column property, including the special name `__proto__`. */
103
531
  export function defineSqlResultProperty(target, name, value) {
@@ -114,8 +542,8 @@ export function defineSqlResultProperty(target, name, value) {
114
542
  * a silent coercion here would make `LENGTH(42)` answer instead of failing.
115
543
  */
116
544
  export function stringArgument(name, value) {
117
- if (typeof value !== "string")
545
+ const external = externalSqlDomainValue(value);
546
+ if (typeof external !== "string")
118
547
  throw new TypeError(`${name} requires a string argument`);
119
- return value;
548
+ return external;
120
549
  }
121
- //# sourceMappingURL=sql-semantics.js.map