@nshiab/simple-data-analysis-core 2.1.1 → 2.1.3

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 (49) hide show
  1. package/esm/class/SimpleDB.d.ts.map +1 -1
  2. package/esm/class/SimpleDB.js +23 -14
  3. package/esm/class/SimpleTable.d.ts +21 -6
  4. package/esm/class/SimpleTable.d.ts.map +1 -1
  5. package/esm/class/SimpleTable.js +23 -8
  6. package/esm/helpers/cleanCache.d.ts +1 -1
  7. package/esm/helpers/cleanCache.d.ts.map +1 -1
  8. package/esm/helpers/cleanCache.js +2 -2
  9. package/esm/helpers/normalizeCacheCode.d.ts +8 -0
  10. package/esm/helpers/normalizeCacheCode.d.ts.map +1 -0
  11. package/esm/helpers/normalizeCacheCode.js +331 -0
  12. package/esm/helpers/serializeCacheInputs.d.ts.map +1 -1
  13. package/esm/helpers/serializeCacheInputs.js +2 -1
  14. package/esm/helpers/updateColumnsWithJS.d.ts.map +1 -1
  15. package/esm/helpers/updateColumnsWithJS.js +14 -12
  16. package/esm/methods/cache.d.ts.map +1 -1
  17. package/esm/methods/cache.js +2 -1
  18. package/esm/methods/similarityMahalanobis.d.ts +5 -0
  19. package/esm/methods/similarityMahalanobis.d.ts.map +1 -0
  20. package/esm/methods/{mahalanobis.js → similarityMahalanobis.js} +9 -9
  21. package/esm/methods/updateWithJS.js +24 -5
  22. package/llm.md +22 -10
  23. package/llms.txt +1 -1
  24. package/package.json +2 -2
  25. package/script/class/SimpleDB.d.ts.map +1 -1
  26. package/script/class/SimpleDB.js +23 -14
  27. package/script/class/SimpleTable.d.ts +21 -6
  28. package/script/class/SimpleTable.d.ts.map +1 -1
  29. package/script/class/SimpleTable.js +23 -8
  30. package/script/helpers/cleanCache.d.ts +1 -1
  31. package/script/helpers/cleanCache.d.ts.map +1 -1
  32. package/script/helpers/cleanCache.js +2 -2
  33. package/script/helpers/normalizeCacheCode.d.ts +8 -0
  34. package/script/helpers/normalizeCacheCode.d.ts.map +1 -0
  35. package/script/helpers/normalizeCacheCode.js +334 -0
  36. package/script/helpers/serializeCacheInputs.d.ts.map +1 -1
  37. package/script/helpers/serializeCacheInputs.js +2 -1
  38. package/script/helpers/updateColumnsWithJS.d.ts.map +1 -1
  39. package/script/helpers/updateColumnsWithJS.js +13 -11
  40. package/script/methods/cache.d.ts.map +1 -1
  41. package/script/methods/cache.js +2 -1
  42. package/script/methods/similarityMahalanobis.d.ts +5 -0
  43. package/script/methods/similarityMahalanobis.d.ts.map +1 -0
  44. package/script/methods/{mahalanobis.js → similarityMahalanobis.js} +10 -10
  45. package/script/methods/updateWithJS.js +24 -5
  46. package/esm/methods/mahalanobis.d.ts +0 -5
  47. package/esm/methods/mahalanobis.d.ts.map +0 -1
  48. package/script/methods/mahalanobis.d.ts +0 -5
  49. package/script/methods/mahalanobis.d.ts.map +0 -1
@@ -0,0 +1,334 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = normalizeCacheCode;
4
+ const lineTerminator = /[\r\n\u2028\u2029]/u;
5
+ const word = /^[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*/u;
6
+ const number = /^(?:0[xX][\da-fA-F_]+n?|0[bB][01_]+n?|0[oO][0-7_]+n?|(?:\d[\d_]*(?:\.[\d_]*)?|\.[\d_]+)(?:[eE][+-]?[\d_]+)?n?)/;
7
+ const punctuation = /^(?:>>>=|===|!==|\*\*=|&&=|\|\|=|\?\?=|>>>|<<=|>>=|=>|==|!=|<=|>=|\+\+|--|&&|\|\||\?\?|\?\.(?!\d)|\*\*|<<|>>|\+=|-=|\*=|%=|&=|\|=|\^=|\.\.\.|[{}()[\].,;:?~!+*%<>=&|^\-#])/;
8
+ const expressionPrefixes = new Set([
9
+ "(",
10
+ "[",
11
+ "{",
12
+ ",",
13
+ ";",
14
+ ":",
15
+ "?",
16
+ "=",
17
+ "=>",
18
+ "!",
19
+ "~",
20
+ "+",
21
+ "-",
22
+ "*",
23
+ "**",
24
+ "%",
25
+ "&",
26
+ "|",
27
+ "^",
28
+ "&&",
29
+ "||",
30
+ "??",
31
+ "==",
32
+ "===",
33
+ "!=",
34
+ "!==",
35
+ "<",
36
+ ">",
37
+ "<=",
38
+ ">=",
39
+ "<<",
40
+ ">>",
41
+ ">>>",
42
+ "+=",
43
+ "-=",
44
+ "*=",
45
+ "**=",
46
+ "%=",
47
+ "&=",
48
+ "|=",
49
+ "^=",
50
+ "&&=",
51
+ "||=",
52
+ "??=",
53
+ "<<=",
54
+ ">>=",
55
+ ">>>=",
56
+ "/",
57
+ "/=",
58
+ ]);
59
+ const expressionKeywords = new Set([
60
+ "return",
61
+ "throw",
62
+ "case",
63
+ "delete",
64
+ "void",
65
+ "typeof",
66
+ "new",
67
+ "in",
68
+ "instanceof",
69
+ "yield",
70
+ "await",
71
+ "else",
72
+ "do",
73
+ ]);
74
+ const restrictedLineBreaks = new Set([
75
+ "return",
76
+ "throw",
77
+ "break",
78
+ "continue",
79
+ "yield",
80
+ "async",
81
+ "++",
82
+ "--",
83
+ ]);
84
+ /**
85
+ * Normalizes comments, whitespace, and known optional punctuation without
86
+ * dependencies or executing user code. Literal contents and token boundaries
87
+ * stay intact. Potentially significant line breaks are retained; ambiguous
88
+ * slash syntax falls back to exact source instead of risking a stale cache hit.
89
+ */
90
+ function normalizeCacheCode(source) {
91
+ let position = 0;
92
+ let uncertain = false;
93
+ function quoted(quote) {
94
+ const start = position++;
95
+ while (position < source.length) {
96
+ const character = source[position++];
97
+ if (character === "\\") {
98
+ position++;
99
+ }
100
+ else if (character === quote) {
101
+ return source.slice(start, position);
102
+ }
103
+ else if (lineTerminator.test(character)) {
104
+ return undefined;
105
+ }
106
+ }
107
+ }
108
+ function template() {
109
+ const parts = [];
110
+ let start = ++position;
111
+ while (position < source.length) {
112
+ if (source[position] === "\\") {
113
+ position += 2;
114
+ }
115
+ else if (source[position] === "`") {
116
+ parts.push(source.slice(start, position++));
117
+ return JSON.stringify(parts);
118
+ }
119
+ else if (source.startsWith("${", position)) {
120
+ parts.push(source.slice(start, position));
121
+ position += 2;
122
+ const tokens = scan(true);
123
+ if (uncertain)
124
+ return undefined;
125
+ parts.push(serialize(tokens));
126
+ start = position;
127
+ }
128
+ else {
129
+ position++;
130
+ }
131
+ }
132
+ }
133
+ function regex() {
134
+ const start = position++;
135
+ let inClass = false;
136
+ while (position < source.length) {
137
+ const character = source[position++];
138
+ if (lineTerminator.test(character))
139
+ return undefined;
140
+ if (character === "\\") {
141
+ position++;
142
+ }
143
+ else if (character === "[") {
144
+ // Nested character classes depend on the v flag. Leave these opaque.
145
+ if (inClass)
146
+ return undefined;
147
+ inClass = true;
148
+ }
149
+ else if (character === "]") {
150
+ inClass = false;
151
+ }
152
+ else if (character === "/" && !inClass) {
153
+ const flags = source.slice(position).match(word)?.[0] ?? "";
154
+ position += flags.length;
155
+ return source.slice(start, position);
156
+ }
157
+ }
158
+ }
159
+ function scan(interpolation = false) {
160
+ const tokens = [];
161
+ let braces = 0;
162
+ let lineBreak = false;
163
+ while (position < source.length) {
164
+ const rest = source.slice(position);
165
+ const character = source[position];
166
+ if (/\s/u.test(character)) {
167
+ lineBreak ||= lineTerminator.test(character);
168
+ position++;
169
+ continue;
170
+ }
171
+ if (rest.startsWith("//")) {
172
+ while (position < source.length && !lineTerminator.test(source[position])) {
173
+ position++;
174
+ }
175
+ continue;
176
+ }
177
+ if (rest.startsWith("/*")) {
178
+ const end = source.indexOf("*/", position + 2);
179
+ if (end === -1)
180
+ break;
181
+ lineBreak ||= lineTerminator.test(source.slice(position, end + 2));
182
+ position = end + 2;
183
+ continue;
184
+ }
185
+ if (interpolation && character === "}" && braces === 0) {
186
+ position++;
187
+ return tokens;
188
+ }
189
+ // Legacy HTML comments and native function text are not ordinary tokens.
190
+ if (rest.startsWith("<!--") || rest.startsWith("-->") ||
191
+ rest.startsWith("[native code]"))
192
+ break;
193
+ const previous = tokens.at(-1);
194
+ let kind = "punctuation";
195
+ let text;
196
+ if (character === "'" || character === '"') {
197
+ kind = "literal";
198
+ text = quoted(character);
199
+ }
200
+ else if (character === "`") {
201
+ kind = "template";
202
+ text = template();
203
+ }
204
+ else if (character === "/") {
205
+ const property = [".", "?."].includes(tokens.at(-2)?.text ?? "");
206
+ if (!previous ||
207
+ (previous.kind === "punctuation" &&
208
+ expressionPrefixes.has(previous.text)) ||
209
+ (previous.kind === "word" && !property &&
210
+ expressionKeywords.has(previous.text))) {
211
+ kind = "literal";
212
+ text = regex();
213
+ }
214
+ else if (previous.kind === "literal" || previous.kind === "template") {
215
+ text = rest.startsWith("/=") ? "/=" : "/";
216
+ position += text.length;
217
+ }
218
+ else {
219
+ // After a closing block/parenthesis or contextual keyword, deciding
220
+ // between division and a regex can require a full JavaScript parser.
221
+ break;
222
+ }
223
+ }
224
+ else {
225
+ text = rest.match(word)?.[0];
226
+ if (text !== undefined) {
227
+ kind = "word";
228
+ }
229
+ else {
230
+ text = rest.match(number)?.[0];
231
+ if (text !== undefined)
232
+ kind = "literal";
233
+ else
234
+ text = rest.match(punctuation)?.[0];
235
+ }
236
+ if (text !== undefined)
237
+ position += text.length;
238
+ }
239
+ if (text === undefined) {
240
+ uncertain = true;
241
+ break;
242
+ }
243
+ if (kind === "punctuation") {
244
+ if (text === "{")
245
+ braces++;
246
+ if (text === "}")
247
+ braces--;
248
+ }
249
+ tokens.push({ kind, text, lineBreak });
250
+ lineBreak = false;
251
+ }
252
+ if (position < source.length || interpolation)
253
+ uncertain = true;
254
+ return tokens;
255
+ }
256
+ const tokens = scan();
257
+ return uncertain
258
+ ? JSON.stringify(["source", source])
259
+ : JSON.stringify(["tokens", serialize(tokens)]);
260
+ }
261
+ function serialize(tokens) {
262
+ tokens = normalizePunctuation(tokens);
263
+ return JSON.stringify(tokens.map((token, index) => {
264
+ const previous = tokens[index - 1];
265
+ // Only discard newlines where continuation is certain. Keeping other
266
+ // newlines permits conservative misses rather than unsafe cache hits.
267
+ const significant = token.lineBreak && previous !== undefined && (restrictedLineBreaks.has(previous.text) ||
268
+ ["++", "--"].includes(token.text) ||
269
+ !((previous.kind === "punctuation" &&
270
+ (expressionPrefixes.has(previous.text) ||
271
+ [".", "?.", "}"].includes(previous.text))) ||
272
+ (token.kind === "punctuation" &&
273
+ ([")", "]", "}", ",", ";", ".", "?."].includes(token.text) ||
274
+ (expressionPrefixes.has(token.text) &&
275
+ !["(", "[", "{", "!", "~"].includes(token.text))))));
276
+ return [token.kind, token.text, significant];
277
+ }));
278
+ }
279
+ function normalizePunctuation(input) {
280
+ const tokens = input.map((token) => ({ ...token }));
281
+ const result = [];
282
+ const brackets = [];
283
+ const controls = new Set(["if", "while", "for", "with", "switch", "catch"]);
284
+ let previousControl = false;
285
+ for (let i = 0; i < tokens.length; i++) {
286
+ const token = tokens[i];
287
+ const next = tokens[i + 1];
288
+ const previous = result.at(-1);
289
+ if (token.kind === "literal" && /^["']/.test(token.text) &&
290
+ !token.text.includes("\\")) {
291
+ token.text = JSON.stringify(token.text.slice(1, -1));
292
+ }
293
+ // A single arrow parameter has the same binding with or without parentheses.
294
+ if (token.text === "(" && tokens[i + 1]?.kind === "word" &&
295
+ tokens[i + 2]?.text === ")" && tokens[i + 3]?.text === "=>") {
296
+ result.push({ ...tokens[i + 1], lineBreak: token.lineBreak });
297
+ i += 2;
298
+ previousControl = false;
299
+ continue;
300
+ }
301
+ // Retain commas representing array holes, including the final hole.
302
+ if (token.text === "," && [")", "]", "}"].includes(next?.text ?? "") &&
303
+ previous && ![",", "[", "("].includes(previous.text))
304
+ continue;
305
+ const expressionEnd = previous && (previous.kind !== "punctuation" || previous.text === "]" ||
306
+ (previous.text === ")" && !previousControl));
307
+ if (token.text === ";" && brackets.at(-1)?.text === "{" && expressionEnd &&
308
+ !restrictedLineBreaks.has(previous.text)) {
309
+ if (next?.text === "}")
310
+ continue;
311
+ // A call followed by an identifier starts a new statement. Preserve that
312
+ // boundary as a newline so explicit and automatically inserted semicolons
313
+ // agree. Never do this in a for header or after a control header.
314
+ if (previous.text === ")" && next?.kind === "word" &&
315
+ !["in", "instanceof", "of"].includes(next.text)) {
316
+ next.lineBreak = true;
317
+ continue;
318
+ }
319
+ }
320
+ previousControl = false;
321
+ if (["(", "[", "{"].includes(token.text)) {
322
+ brackets.push({
323
+ text: token.text,
324
+ control: token.text === "(" && (controls.has(previous?.text ?? "") ||
325
+ (previous?.text === "await" && result.at(-2)?.text === "for")),
326
+ });
327
+ }
328
+ else if ([")", "]", "}"].includes(token.text)) {
329
+ previousControl = brackets.pop()?.control ?? false;
330
+ }
331
+ result.push(token);
332
+ }
333
+ return result;
334
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"serializeCacheInputs.d.ts","sourceRoot":"","sources":["../../src/helpers/serializeCacheInputs.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAC1C,MAAM,EAAE,SAAS,OAAO,EAAE,GACzB,MAAM,CASR;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAIrE"}
1
+ {"version":3,"file":"serializeCacheInputs.d.ts","sourceRoot":"","sources":["../../src/helpers/serializeCacheInputs.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAC1C,MAAM,EAAE,SAAS,OAAO,EAAE,GACzB,MAAM,CASR;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAIrE"}
@@ -7,6 +7,7 @@ exports.default = serializeCacheInputs;
7
7
  exports.getCacheInputLabel = getCacheInputLabel;
8
8
  const SimpleTable_js_1 = __importDefault(require("../class/SimpleTable.js"));
9
9
  const tableGeneration_js_1 = require("./tableGeneration.js");
10
+ const normalizeCacheCode_js_1 = __importDefault(require("./normalizeCacheCode.js"));
10
11
  /**
11
12
  * Converts cache inputs to a deterministic, type-tagged representation.
12
13
  * SimpleTable values are represented by their name and current generation.
@@ -41,7 +42,7 @@ function serializeValue(value, path, ancestors) {
41
42
  case "function":
42
43
  return JSON.stringify([
43
44
  "function",
44
- Function.prototype.toString.call(value),
45
+ (0, normalizeCacheCode_js_1.default)(Function.prototype.toString.call(value)),
45
46
  ]);
46
47
  case "symbol":
47
48
  throw new TypeError(`cache() ${path} cannot be a symbol.`);
@@ -1 +1 @@
1
- {"version":3,"file":"updateColumnsWithJS.d.ts","sourceRoot":"","sources":["../../src/helpers/updateColumnsWithJS.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAMvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAA8B,mBAAmB,CAC/C,KAAK,EAAE,WAAW,EAClB,YAAY,EAAE,MAAM,EAAE,EACtB,aAAa,EAAE,MAAM,EAAE,EACvB,QAAQ,EAAE,CACR,IAAI,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EAAE,KAC/B,OAAO,CAAC;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EAAE,CAAC,EAC1C,OAAO,GAAE;IACP;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACf,GACL,OAAO,CAAC,IAAI,CAAC,CAoJf"}
1
+ {"version":3,"file":"updateColumnsWithJS.d.ts","sourceRoot":"","sources":["../../src/helpers/updateColumnsWithJS.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAQvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAA8B,mBAAmB,CAC/C,KAAK,EAAE,WAAW,EAClB,YAAY,EAAE,MAAM,EAAE,EACtB,aAAa,EAAE,MAAM,EAAE,EACvB,QAAQ,EAAE,CACR,IAAI,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EAAE,KAC/B,OAAO,CAAC;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EAAE,CAAC,EAC1C,OAAO,GAAE;IACP;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACf,GACL,OAAO,CAAC,IAAI,CAAC,CA0Jf"}
@@ -8,6 +8,8 @@ const quoteIdentifier_js_1 = __importDefault(require("./quoteIdentifier.js"));
8
8
  const readMutationRows_js_1 = __importDefault(require("./readMutationRows.js"));
9
9
  const tableRegistry_js_1 = require("./tableRegistry.js");
10
10
  const loadArray_js_1 = require("../methods/loadArray.js");
11
+ const appendColumnBatches_js_1 = __importDefault(require("./appendColumnBatches.js"));
12
+ const parseDuckDBType_js_1 = __importDefault(require("./parseDuckDBType.js"));
11
13
  /**
12
14
  * Generates columns from selected input columns, keeping other SQL values in
13
15
  * DuckDB. Call from an asynchronous extension barrier. The callback must return
@@ -74,7 +76,6 @@ async function updateColumnsWithJS(table, inputColumns, outputColumns, generate,
74
76
  const id = `__sda_id_${suffix}`;
75
77
  const snapshot = `__sda_source_${suffix}`;
76
78
  const staged = table.sdb.newTable(`__sda_generated_${suffix}`);
77
- const batch = table.sdb.newTable(`__sda_batch_${suffix}`);
78
79
  const q = quoteIdentifier_js_1.default;
79
80
  try {
80
81
  // A SQL snapshot gives every row a stable identity, including duplicate rows
@@ -98,17 +99,17 @@ async function updateColumnsWithJS(table, inputColumns, outputColumns, generate,
98
99
  throw new Error("Column generation must return one row per input row.");
99
100
  }
100
101
  // Generated vectors retain this helper's existing inference contract.
101
- await (0, loadArray_js_1.executePreparedArray)(batch, (0, loadArray_js_1.prepareArray)(generated.map((row, i) => ({
102
+ const prepared = (0, loadArray_js_1.prepareArray)(generated.map((row, i) => ({
102
103
  ...Object.fromEntries(outputColumns.map((name) => [name, row[name]])),
103
104
  [id]: ids[i],
104
- }))));
105
- const batchTypes = await batch.getTypes();
105
+ })));
106
+ const batchTypes = new Map(prepared.keys.map((name, i) => [name, prepared.types[i]]));
106
107
  for (const name of outputColumns) {
107
108
  const nonNull = generated.some((row) => row[name] !== null && row[name] !== undefined);
108
109
  if (!nonNull)
109
110
  continue;
110
111
  const previous = knownTypes.get(name);
111
- const current = batchTypes[name];
112
+ const current = batchTypes.get(name);
112
113
  if (previous !== undefined && previous !== current) {
113
114
  throw new Error(`Generated column ${JSON.stringify(name)} changed type from ${previous} to ${current}.`);
114
115
  }
@@ -120,12 +121,13 @@ async function updateColumnsWithJS(table, inputColumns, outputColumns, generate,
120
121
  }
121
122
  }
122
123
  if (!hasRows) {
123
- await table.sdb.customQuery(`CREATE TEMP TABLE ${q(staged.name)} AS SELECT * FROM ${q(batch.name)}`);
124
+ await table.sdb.customQuery(`CREATE TEMP TABLE ${q(staged.name)} (${prepared.keys.map((name, i) => `${q(name)} ${prepared.types[i]}`)
125
+ .join(", ")})`);
124
126
  hasRows = true;
125
127
  }
126
- else {
127
- await table.sdb.customQuery(`INSERT INTO ${q(staged.name)} SELECT * FROM ${q(batch.name)}`);
128
- }
128
+ // All-null batches use the column's established type, avoiding a separate
129
+ // batch table and SQL copy just to cast nulls into the staged schema.
130
+ await (0, appendColumnBatches_js_1.default)(table.connection, staged.name, prepared.keys.map((name, i) => (0, parseDuckDBType_js_1.default)(knownTypes.get(name) ?? prepared.types[i])), prepared.rowCount, (column, start, end) => prepared.columnsData[column].slice(start, end));
129
131
  }
130
132
  if (!hasRows)
131
133
  return;
@@ -145,7 +147,7 @@ async function updateColumnsWithJS(table, inputColumns, outputColumns, generate,
145
147
  await table.sdb.customQuery(`CREATE OR REPLACE TABLE ${q(table.name)} AS SELECT ${columns.join(", ")} FROM ${q(snapshot)} original JOIN ${q(staged.name)} generated ON original.${q(id)} = CAST(generated.${q(id)} AS BIGINT) ORDER BY original.${q(id)}`);
146
148
  }
147
149
  finally {
148
- await table.sdb.customQuery(`DROP TABLE IF EXISTS ${q(snapshot)}; DROP TABLE IF EXISTS ${q(staged.name)}; DROP TABLE IF EXISTS ${q(batch.name)};`);
149
- (0, tableRegistry_js_1.retainRegisteredTables)(table.sdb, (item) => item !== staged && item !== batch);
150
+ await table.sdb.customQuery(`DROP TABLE IF EXISTS ${q(snapshot)}; DROP TABLE IF EXISTS ${q(staged.name)};`);
151
+ (0, tableRegistry_js_1.retainRegisteredTables)(table.sdb, (item) => item !== staged);
150
152
  }
151
153
  }
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/methods/cache.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AA4DvD,wBAA8B,KAAK,CAAC,KAAK,SAAS,WAAW,EAC3D,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC/C,OAAO,GAAE;IACP,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;CACd,iBA4LP"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/methods/cache.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AA6DvD,wBAA8B,KAAK,CAAC,KAAK,SAAS,WAAW,EAC3D,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC/C,OAAO,GAAE;IACP,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;CACd,iBA4LP"}
@@ -41,6 +41,7 @@ const node_fs_1 = require("node:fs");
41
41
  const node_crypto_1 = __importDefault(require("node:crypto"));
42
42
  const flushAllTables_js_1 = __importDefault(require("../helpers/flushAllTables.js"));
43
43
  const prettyDuration_js_1 = __importDefault(require("../helpers/prettyDuration.js"));
44
+ const normalizeCacheCode_js_1 = __importDefault(require("../helpers/normalizeCacheCode.js"));
44
45
  const cacheDiagnostics_js_1 = require("../helpers/cacheDiagnostics.js");
45
46
  const serializeCacheInputs_js_1 = __importStar(require("../helpers/serializeCacheInputs.js"));
46
47
  const tableGeneration_js_1 = require("../helpers/tableGeneration.js");
@@ -66,7 +67,7 @@ async function cache(table, compute, options = {}) {
66
67
  if ((0, node_fs_1.existsSync)(cacheSourcesPath)) {
67
68
  cacheSources = JSON.parse((0, node_fs_1.readFileSync)(cacheSourcesPath, "utf-8"));
68
69
  }
69
- const functionBody = compute.toString();
70
+ const functionBody = (0, normalizeCacheCode_js_1.default)(compute.toString());
70
71
  const entryGeneration = (0, tableGeneration_js_1.peekTableGeneration)(table) ?? null;
71
72
  const inputs = options.inputs?.filter((input) => input !== table) ?? [];
72
73
  const hasInputs = inputs.length > 0;
@@ -0,0 +1,5 @@
1
+ import type SimpleTable from "../class/SimpleTable.js";
2
+ type Options = NonNullable<Parameters<SimpleTable["similarityMahalanobis"]>[3]>;
3
+ export default function similarityMahalanobis(table: SimpleTable, columns: string | string[], referencePoint: number[], newColumn: string, options?: Options): void;
4
+ export {};
5
+ //# sourceMappingURL=similarityMahalanobis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"similarityMahalanobis.d.ts","sourceRoot":"","sources":["../../src/methods/similarityMahalanobis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AASvD,KAAK,OAAO,GAAG,WAAW,CAAC,UAAU,CAAC,WAAW,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEhF,MAAM,CAAC,OAAO,UAAU,qBAAqB,CAC3C,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,EAC1B,cAAc,EAAE,MAAM,EAAE,EACxB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,OAAY,GACpB,IAAI,CAyBN"}
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.default = mahalanobis;
6
+ exports.default = similarityMahalanobis;
7
7
  const computeCovariance_js_1 = __importDefault(require("../helpers/computeCovariance.js"));
8
8
  const computeMahalanobisDistances_js_1 = __importDefault(require("../helpers/computeMahalanobisDistances.js"));
9
9
  const foldIdentifier_js_1 = __importDefault(require("../helpers/foldIdentifier.js"));
@@ -11,17 +11,17 @@ const prepareNumericFeatures_js_1 = __importDefault(require("../helpers/prepareN
11
11
  const publishPreparedColumns_js_1 = __importDefault(require("../helpers/publishPreparedColumns.js"));
12
12
  const queueOp_js_1 = __importDefault(require("../helpers/queueOp.js"));
13
13
  const quoteIdentifier_js_1 = __importDefault(require("../helpers/quoteIdentifier.js"));
14
- function mahalanobis(table, columns, referencePoint, newColumn, options = {}) {
14
+ function similarityMahalanobis(table, columns, referencePoint, newColumn, options = {}) {
15
15
  if (!Array.isArray(referencePoint) || referencePoint.length === 0 ||
16
16
  Array.from(referencePoint).some((value) => typeof value !== "number" || !Number.isFinite(value))) {
17
- throw new Error("mahalanobis() referencePoint must be a nonempty array of finite numbers in feature-dimension order.");
17
+ throw new Error("similarityMahalanobis() referencePoint must be a nonempty array of finite numbers in feature-dimension order.");
18
18
  }
19
19
  const selected = typeof columns === "string" ? columns : [...columns];
20
20
  const reference = [...referencePoint];
21
21
  const settings = { ...options };
22
22
  (0, queueOp_js_1.default)(table, {
23
23
  kind: "barrier",
24
- method: "mahalanobis()",
24
+ method: "similarityMahalanobis()",
25
25
  parameters: {
26
26
  columns: selected,
27
27
  referencePoint: reference,
@@ -39,23 +39,23 @@ async function execute(table, columns, referencePoint, newColumn, options) {
39
39
  const seen = new Set();
40
40
  for (const name of outputNames) {
41
41
  if (typeof name !== "string" || name.length === 0 || name.includes("\0")) {
42
- throw new Error("mahalanobis() output column names must be nonempty strings without null characters.");
42
+ throw new Error("similarityMahalanobis() output column names must be nonempty strings without null characters.");
43
43
  }
44
44
  const folded = (0, foldIdentifier_js_1.default)(name);
45
45
  if (seen.has(folded)) {
46
- throw new Error("mahalanobis() distance and similarity score columns must have different names (case-insensitive).");
46
+ throw new Error("similarityMahalanobis() distance and similarity score columns must have different names (case-insensitive).");
47
47
  }
48
48
  seen.add(folded);
49
49
  if (sourceColumns.some((column) => (0, foldIdentifier_js_1.default)(column) === folded)) {
50
- throw new Error(`mahalanobis() cannot create ${(0, quoteIdentifier_js_1.default)(name)} because that column already exists. Remove it first or choose a different name.`);
50
+ throw new Error(`similarityMahalanobis() cannot create ${(0, quoteIdentifier_js_1.default)(name)} because that column already exists. Remove it first or choose a different name.`);
51
51
  }
52
52
  }
53
53
  const prepared = await (0, prepareNumericFeatures_js_1.default)(table, typeof columns === "string"
54
54
  ? { kind: "vector", column: columns }
55
- : { kind: "scalars", columns }, { method: "mahalanobis()" });
55
+ : { kind: "scalars", columns }, { method: "similarityMahalanobis()" });
56
56
  try {
57
57
  if (referencePoint.length !== prepared.dimensions) {
58
- throw new Error(`mahalanobis() referencePoint must contain ${prepared.dimensions} values in feature-dimension order; received ${referencePoint.length}.`);
58
+ throw new Error(`similarityMahalanobis() referencePoint must contain ${prepared.dimensions} values in feature-dimension order; received ${referencePoint.length}.`);
59
59
  }
60
60
  const model = await (0, computeCovariance_js_1.default)(table.connection, {
61
61
  relation: prepared.relation,
@@ -79,7 +79,7 @@ async function execute(table, columns, referencePoint, newColumn, options) {
79
79
  });
80
80
  }
81
81
  await (0, publishPreparedColumns_js_1.default)(table, prepared, {
82
- method: "mahalanobis()",
82
+ method: "similarityMahalanobis()",
83
83
  parameters: { columns, referencePoint, newColumn, options },
84
84
  result: {
85
85
  relation: distances.relation,
@@ -66,6 +66,7 @@ async function executeUpdateWithJS(simpleTable, dataModifier, options) {
66
66
  const accumulator = `updateWithJS_accumulator_${suffix}`;
67
67
  try {
68
68
  let first = true;
69
+ let accumulatorSchema;
69
70
  let lastRowid = null;
70
71
  let sawRows = false;
71
72
  let outputOffset = 0;
@@ -137,9 +138,17 @@ async function executeUpdateWithJS(simpleTable, dataModifier, options) {
137
138
  ? (0, parseDuckDBType_js_1.default)("VARCHAR")
138
139
  : source.types.get(key) ??
139
140
  (0, parseDuckDBType_js_1.default)(added.types[newKeys.indexOf(key)]));
140
- await simpleTable.sdb.customQuery(`CREATE OR REPLACE TABLE ${(0, quoteIdentifier_js_1.default)(scratch.name)} (${keys.map((key, i) => `${(0, quoteIdentifier_js_1.default)(key)} ${columnTypes[i]}`)
141
- .join(", ")})`);
142
- await (0, appendColumnBatches_js_1.default)(simpleTable.connection, scratch.name, columnTypes, modified.length, (column, start, end) => {
141
+ const schema = JSON.stringify(keys.map((key, i) => [key, columnTypes[i].toString()]));
142
+ // Append matching native batches directly. SQL still handles conversion
143
+ // and BY NAME alignment when a callback changes the batch schema.
144
+ const direct = outputGeometry.length === 0 && outputJSON.length === 0 &&
145
+ (first || schema === accumulatorSchema);
146
+ const destination = direct ? accumulator : scratch.name;
147
+ if (!direct || first) {
148
+ await simpleTable.sdb.customQuery(`CREATE OR REPLACE TABLE ${(0, quoteIdentifier_js_1.default)(destination)} (${keys.map((key, i) => `${(0, quoteIdentifier_js_1.default)(key)} ${columnTypes[i]}`)
149
+ .join(", ")})`);
150
+ }
151
+ await (0, appendColumnBatches_js_1.default)(simpleTable.connection, destination, columnTypes, modified.length, (column, start, end) => {
143
152
  const key = keys[column];
144
153
  return outputGeometry.includes(key)
145
154
  ? geometryData.get(key).slice(start, end)
@@ -152,7 +161,11 @@ async function executeUpdateWithJS(simpleTable, dataModifier, options) {
152
161
  : outputJSON.includes(key)
153
162
  ? `${(0, quoteIdentifier_js_1.default)(key)}::JSON AS ${(0, quoteIdentifier_js_1.default)(key)}`
154
163
  : (0, quoteIdentifier_js_1.default)(key)).join(", ");
155
- if (first) {
164
+ if (direct) {
165
+ accumulatorSchema = schema;
166
+ first = false;
167
+ }
168
+ else if (first) {
156
169
  await simpleTable.sdb.customQuery(`CREATE OR REPLACE TABLE ${(0, quoteIdentifier_js_1.default)(accumulator)} AS SELECT ${projection} FROM ${(0, quoteIdentifier_js_1.default)(scratch.name)}`);
157
170
  first = false;
158
171
  }
@@ -165,7 +178,13 @@ async function executeUpdateWithJS(simpleTable, dataModifier, options) {
165
178
  if (first) {
166
179
  throw new Error("The dataModifier returned no rows. updateWithJS can't infer the table schema from zero rows.");
167
180
  }
168
- await simpleTable.sdb.customQuery(`CREATE OR REPLACE TABLE ${(0, quoteIdentifier_js_1.default)(simpleTable.name)} AS SELECT * FROM ${(0, quoteIdentifier_js_1.default)(accumulator)}`);
181
+ // Publish the completed ordinary table without copying its rows again.
182
+ // Qualify the creation namespace: a temporary table or a later search-path
183
+ // schema can shadow the name without being the destination of replacement.
184
+ const namespace = await simpleTable.connection.runAndReadAll("SELECT current_database(), current_schema()");
185
+ const prefix = namespace.getRows()[0].map((name) => (0, quoteIdentifier_js_1.default)(String(name))).join(".");
186
+ await simpleTable.sdb.customQuery(`DROP TABLE IF EXISTS ${prefix}.${(0, quoteIdentifier_js_1.default)(simpleTable.name)};
187
+ ALTER TABLE ${prefix}.${(0, quoteIdentifier_js_1.default)(accumulator)} RENAME TO ${(0, quoteIdentifier_js_1.default)(simpleTable.name)};`);
169
188
  }
170
189
  finally {
171
190
  await simpleTable.sdb.customQuery(`DROP TABLE IF EXISTS ${(0, quoteIdentifier_js_1.default)(accumulator)};
@@ -1,5 +0,0 @@
1
- import type SimpleTable from "../class/SimpleTable.js";
2
- type Options = NonNullable<Parameters<SimpleTable["mahalanobis"]>[3]>;
3
- export default function mahalanobis(table: SimpleTable, columns: string | string[], referencePoint: number[], newColumn: string, options?: Options): void;
4
- export {};
5
- //# sourceMappingURL=mahalanobis.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mahalanobis.d.ts","sourceRoot":"","sources":["../../src/methods/mahalanobis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AASvD,KAAK,OAAO,GAAG,WAAW,CAAC,UAAU,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEtE,MAAM,CAAC,OAAO,UAAU,WAAW,CACjC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,EAC1B,cAAc,EAAE,MAAM,EAAE,EACxB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,OAAY,GACpB,IAAI,CAyBN"}
@@ -1,5 +0,0 @@
1
- import type SimpleTable from "../class/SimpleTable.js";
2
- type Options = NonNullable<Parameters<SimpleTable["mahalanobis"]>[3]>;
3
- export default function mahalanobis(table: SimpleTable, columns: string | string[], referencePoint: number[], newColumn: string, options?: Options): void;
4
- export {};
5
- //# sourceMappingURL=mahalanobis.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mahalanobis.d.ts","sourceRoot":"","sources":["../../src/methods/mahalanobis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AASvD,KAAK,OAAO,GAAG,WAAW,CAAC,UAAU,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEtE,MAAM,CAAC,OAAO,UAAU,WAAW,CACjC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,EAC1B,cAAc,EAAE,MAAM,EAAE,EACxB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,OAAY,GACpB,IAAI,CAyBN"}