@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.
- package/esm/class/SimpleDB.d.ts.map +1 -1
- package/esm/class/SimpleDB.js +23 -14
- package/esm/class/SimpleTable.d.ts +21 -6
- package/esm/class/SimpleTable.d.ts.map +1 -1
- package/esm/class/SimpleTable.js +23 -8
- package/esm/helpers/cleanCache.d.ts +1 -1
- package/esm/helpers/cleanCache.d.ts.map +1 -1
- package/esm/helpers/cleanCache.js +2 -2
- package/esm/helpers/normalizeCacheCode.d.ts +8 -0
- package/esm/helpers/normalizeCacheCode.d.ts.map +1 -0
- package/esm/helpers/normalizeCacheCode.js +331 -0
- package/esm/helpers/serializeCacheInputs.d.ts.map +1 -1
- package/esm/helpers/serializeCacheInputs.js +2 -1
- package/esm/helpers/updateColumnsWithJS.d.ts.map +1 -1
- package/esm/helpers/updateColumnsWithJS.js +14 -12
- package/esm/methods/cache.d.ts.map +1 -1
- package/esm/methods/cache.js +2 -1
- package/esm/methods/similarityMahalanobis.d.ts +5 -0
- package/esm/methods/similarityMahalanobis.d.ts.map +1 -0
- package/esm/methods/{mahalanobis.js → similarityMahalanobis.js} +9 -9
- package/esm/methods/updateWithJS.js +24 -5
- package/llm.md +22 -10
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/script/class/SimpleDB.d.ts.map +1 -1
- package/script/class/SimpleDB.js +23 -14
- package/script/class/SimpleTable.d.ts +21 -6
- package/script/class/SimpleTable.d.ts.map +1 -1
- package/script/class/SimpleTable.js +23 -8
- package/script/helpers/cleanCache.d.ts +1 -1
- package/script/helpers/cleanCache.d.ts.map +1 -1
- package/script/helpers/cleanCache.js +2 -2
- package/script/helpers/normalizeCacheCode.d.ts +8 -0
- package/script/helpers/normalizeCacheCode.d.ts.map +1 -0
- package/script/helpers/normalizeCacheCode.js +334 -0
- package/script/helpers/serializeCacheInputs.d.ts.map +1 -1
- package/script/helpers/serializeCacheInputs.js +2 -1
- package/script/helpers/updateColumnsWithJS.d.ts.map +1 -1
- package/script/helpers/updateColumnsWithJS.js +13 -11
- package/script/methods/cache.d.ts.map +1 -1
- package/script/methods/cache.js +2 -1
- package/script/methods/similarityMahalanobis.d.ts +5 -0
- package/script/methods/similarityMahalanobis.d.ts.map +1 -0
- package/script/methods/{mahalanobis.js → similarityMahalanobis.js} +10 -10
- package/script/methods/updateWithJS.js +24 -5
- package/esm/methods/mahalanobis.d.ts +0 -5
- package/esm/methods/mahalanobis.d.ts.map +0 -1
- package/script/methods/mahalanobis.d.ts +0 -5
- 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":"
|
|
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;
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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)}
|
|
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
|
-
|
|
127
|
-
|
|
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)}
|
|
149
|
-
(0, tableRegistry_js_1.retainRegisteredTables)(table.sdb, (item) => item !== staged
|
|
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;
|
|
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"}
|
package/script/methods/cache.js
CHANGED
|
@@ -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 =
|
|
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
|
|
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("
|
|
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: "
|
|
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("
|
|
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("
|
|
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(`
|
|
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: "
|
|
55
|
+
: { kind: "scalars", columns }, { method: "similarityMahalanobis()" });
|
|
56
56
|
try {
|
|
57
57
|
if (referencePoint.length !== prepared.dimensions) {
|
|
58
|
-
throw new Error(`
|
|
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: "
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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 (
|
|
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
|
-
|
|
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"}
|