vastgrid-js 0.0.0-stage → 0.2.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.
- package/LICENSE +21 -0
- package/README.md +211 -2
- package/dist/aggregate.d.ts +45 -0
- package/dist/aggregate.d.ts.map +1 -0
- package/dist/aggregate.js +305 -0
- package/dist/aggregate.js.map +1 -0
- package/dist/arrow.d.ts +7 -0
- package/dist/arrow.d.ts.map +1 -0
- package/dist/arrow.js +25 -0
- package/dist/arrow.js.map +1 -0
- package/dist/engine.d.ts +40 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +1022 -0
- package/dist/engine.js.map +1 -0
- package/dist/evaluate.d.ts +37 -0
- package/dist/evaluate.d.ts.map +1 -0
- package/dist/evaluate.js +774 -0
- package/dist/evaluate.js.map +1 -0
- package/dist/export.d.ts +23 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +48 -0
- package/dist/export.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/lazy.d.ts +65 -0
- package/dist/lazy.d.ts.map +1 -0
- package/dist/lazy.js +177 -0
- package/dist/lazy.js.map +1 -0
- package/dist/numbers.d.ts +20 -0
- package/dist/numbers.d.ts.map +1 -0
- package/dist/numbers.js +129 -0
- package/dist/numbers.js.map +1 -0
- package/dist/order.d.ts +67 -0
- package/dist/order.d.ts.map +1 -0
- package/dist/order.js +561 -0
- package/dist/order.js.map +1 -0
- package/dist/parquet.d.ts +38 -0
- package/dist/parquet.d.ts.map +1 -0
- package/dist/parquet.js +673 -0
- package/dist/parquet.js.map +1 -0
- package/dist/plan.d.ts +37 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +51 -0
- package/dist/plan.js.map +1 -0
- package/dist/summary.d.ts +37 -0
- package/dist/summary.d.ts.map +1 -0
- package/dist/summary.js +175 -0
- package/dist/summary.js.map +1 -0
- package/dist/table.d.ts +172 -0
- package/dist/table.d.ts.map +1 -0
- package/dist/table.js +154 -0
- package/dist/table.js.map +1 -0
- package/dist/text.d.ts +18 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +1204 -0
- package/dist/text.js.map +1 -0
- package/dist/workbook.d.ts +6 -0
- package/dist/workbook.d.ts.map +1 -0
- package/dist/workbook.js +35 -0
- package/dist/workbook.js.map +1 -0
- package/package.json +46 -4
- package/src/aggregate.ts +358 -0
- package/src/arrow.ts +24 -0
- package/src/engine.ts +1163 -0
- package/src/evaluate.ts +764 -0
- package/src/export.ts +73 -0
- package/src/index.ts +2 -0
- package/src/lazy.ts +185 -0
- package/src/numbers.ts +128 -0
- package/src/order.ts +594 -0
- package/src/parquet.ts +801 -0
- package/src/plan.ts +99 -0
- package/src/summary.ts +180 -0
- package/src/table.ts +297 -0
- package/src/text.ts +1285 -0
- package/src/workbook.ts +48 -0
package/dist/evaluate.js
ADDED
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluating expressions (docs/spec.md §3, §4.3, §4.4) for the rows of a table: a checked expression becomes a
|
|
3
|
+
* function of a row ID that gives its value as typed JSON (§2.2) of the expression's type.
|
|
4
|
+
*
|
|
5
|
+
* Exact numbers are computed as integers scaled by their scale, with BigInt where a JavaScript number would not
|
|
6
|
+
* be exact; floats as 64-bit numbers. Errors and numbers that are not finite give null (§3.6).
|
|
7
|
+
*/
|
|
8
|
+
import { dateText, daysFromCivil, floatJson, floatOf, fromBase64, isDateText, isValueOf, normalizeInterval, parseDateText, parseTimestampText, sameType, textOf, timestampText, trimSpaces, UNIT_PER_SECOND, } from "vastgrid-spec";
|
|
9
|
+
const INT32_MIN = -2_147_483_648;
|
|
10
|
+
const INT32_MAX = 2_147_483_647;
|
|
11
|
+
const INT64_MIN = -(2n ** 63n);
|
|
12
|
+
const INT64_MAX = 2n ** 63n - 1n;
|
|
13
|
+
const POWERS = [1n];
|
|
14
|
+
/** 10^n as a BigInt. */
|
|
15
|
+
function power10(n) {
|
|
16
|
+
while (POWERS.length <= n)
|
|
17
|
+
POWERS.push(POWERS[POWERS.length - 1] * 10n);
|
|
18
|
+
return POWERS[n];
|
|
19
|
+
}
|
|
20
|
+
function isExact(type) {
|
|
21
|
+
return type.type === "integer" || type.type === "bigint" || type.type === "decimal";
|
|
22
|
+
}
|
|
23
|
+
function scaleOf(type) {
|
|
24
|
+
return type.type === "decimal" ? (type.scale ?? 0) : 0;
|
|
25
|
+
}
|
|
26
|
+
/** An exact number as its integer at the scale of its type: the decimal `"-0.10"` is −10. */
|
|
27
|
+
export function unscaled(value, type) {
|
|
28
|
+
if (typeof value === "number")
|
|
29
|
+
return BigInt(value);
|
|
30
|
+
const text = value;
|
|
31
|
+
return type.type === "decimal" && (type.scale ?? 0) > 0 ? BigInt(text.replace(".", "")) : BigInt(text);
|
|
32
|
+
}
|
|
33
|
+
/** The text of a decimal from its integer at a scale: −10 at scale 2 is `"-0.10"`. */
|
|
34
|
+
export function decimalText(value, scale) {
|
|
35
|
+
const negative = value < 0n;
|
|
36
|
+
const digits = (negative ? -value : value).toString();
|
|
37
|
+
if (scale === 0)
|
|
38
|
+
return negative ? `-${digits}` : digits;
|
|
39
|
+
const padded = digits.padStart(scale + 1, "0");
|
|
40
|
+
const point = padded.length - scale;
|
|
41
|
+
return `${negative ? "-" : ""}${padded.slice(0, point)}.${padded.slice(point)}`;
|
|
42
|
+
}
|
|
43
|
+
/** The typed JSON of an exact number of a type from its integer at the type's scale; null when it does not fit. */
|
|
44
|
+
export function exactJson(value, type) {
|
|
45
|
+
switch (type.type) {
|
|
46
|
+
case "integer":
|
|
47
|
+
return value >= INT32_MIN && value <= INT32_MAX ? Number(value) : null;
|
|
48
|
+
case "bigint":
|
|
49
|
+
if (value < INT64_MIN || value > INT64_MAX)
|
|
50
|
+
return null;
|
|
51
|
+
return value >= -9007199254740991n && value <= 9007199254740991n ? Number(value) : value.toString();
|
|
52
|
+
default: {
|
|
53
|
+
const limit = power10(type.precision ?? 38);
|
|
54
|
+
return value > -limit && value < limit ? decimalText(value, type.scale ?? 0) : null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** A number as a 64-bit float; the exact side of a comparison is rounded to the nearest float (§3.1). */
|
|
59
|
+
export function toFloat(value, type) {
|
|
60
|
+
if (typeof value === "number")
|
|
61
|
+
return value;
|
|
62
|
+
return type.type === "float" ? floatOf(value) : Number(value);
|
|
63
|
+
}
|
|
64
|
+
/** A float result: null when it is not finite (§3.6). */
|
|
65
|
+
function finite(value) {
|
|
66
|
+
return Number.isFinite(value) ? value : null;
|
|
67
|
+
}
|
|
68
|
+
/** `value / 10^digits` rounded half away from zero (§4.4). */
|
|
69
|
+
function roundHalfAway(value, digits) {
|
|
70
|
+
if (digits <= 0)
|
|
71
|
+
return value;
|
|
72
|
+
const divisor = power10(digits);
|
|
73
|
+
const quotient = value / divisor;
|
|
74
|
+
const rest = value % divisor;
|
|
75
|
+
const twice = (rest < 0n ? -rest : rest) * 2n;
|
|
76
|
+
return twice >= divisor ? quotient + (value < 0n ? -1n : 1n) : quotient;
|
|
77
|
+
}
|
|
78
|
+
/** `value / 10^digits` rounded down or up. */
|
|
79
|
+
function floorOrCeil(value, digits, up) {
|
|
80
|
+
const divisor = power10(digits);
|
|
81
|
+
const quotient = value / divisor;
|
|
82
|
+
const rest = value % divisor;
|
|
83
|
+
if (rest === 0n)
|
|
84
|
+
return quotient;
|
|
85
|
+
if (up)
|
|
86
|
+
return rest > 0n ? quotient + 1n : quotient;
|
|
87
|
+
return rest < 0n ? quotient - 1n : quotient;
|
|
88
|
+
}
|
|
89
|
+
/** Compares two strings by their code points (§3.1), which for Unicode text is the order of their UTF-8 bytes. */
|
|
90
|
+
function compareText(a, b) {
|
|
91
|
+
const length = Math.min(a.length, b.length);
|
|
92
|
+
for (let at = 0; at < length; at++) {
|
|
93
|
+
let x = a.charCodeAt(at);
|
|
94
|
+
let y = b.charCodeAt(at);
|
|
95
|
+
if (x === y)
|
|
96
|
+
continue;
|
|
97
|
+
// Units from U+E000 come before surrogates, which hold the code points from U+10000.
|
|
98
|
+
if (x >= 0xd800)
|
|
99
|
+
x = x >= 0xe000 ? x - 0x800 : x + 0x2000;
|
|
100
|
+
if (y >= 0xd800)
|
|
101
|
+
y = y >= 0xe000 ? y - 0x800 : y + 0x2000;
|
|
102
|
+
return x < y ? -1 : 1;
|
|
103
|
+
}
|
|
104
|
+
return a.length - b.length;
|
|
105
|
+
}
|
|
106
|
+
function compareBytes(a, b) {
|
|
107
|
+
const length = Math.min(a.length, b.length);
|
|
108
|
+
for (let at = 0; at < length; at++)
|
|
109
|
+
if (a[at] !== b[at])
|
|
110
|
+
return a[at] - b[at];
|
|
111
|
+
return a.length - b.length;
|
|
112
|
+
}
|
|
113
|
+
function compareBigInts(a, b) {
|
|
114
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
115
|
+
}
|
|
116
|
+
/** Floats by §3.1: NaN equals NaN and is greater than every other number, and −0.0 equals 0.0. */
|
|
117
|
+
function compareFloats(x, y) {
|
|
118
|
+
if (Number.isNaN(x))
|
|
119
|
+
return Number.isNaN(y) ? 0 : 1;
|
|
120
|
+
if (Number.isNaN(y))
|
|
121
|
+
return -1;
|
|
122
|
+
return x < y ? -1 : x > y ? 1 : 0;
|
|
123
|
+
}
|
|
124
|
+
/** The text of a date or a timestamp as a timestamp, which compares by its text (§3.1). */
|
|
125
|
+
function asTimestampText(value, type) {
|
|
126
|
+
return type.type === "date" ? `${value} 00:00:00` : value;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* How two values that are not null of comparable types compare (§3.1): negative, 0 or positive. Dates,
|
|
130
|
+
* timestamps, times and UUIDs compare by their text, whose fields have fixed widths.
|
|
131
|
+
*/
|
|
132
|
+
export function comparer(a, b) {
|
|
133
|
+
if (isExact(a) && isExact(b)) {
|
|
134
|
+
const sa = scaleOf(a);
|
|
135
|
+
const sb = scaleOf(b);
|
|
136
|
+
const scale = Math.max(sa, sb);
|
|
137
|
+
const fa = power10(scale - sa);
|
|
138
|
+
const fb = power10(scale - sb);
|
|
139
|
+
return (x, y) => {
|
|
140
|
+
if (typeof x === "number" && typeof y === "number" && sa === 0 && sb === 0)
|
|
141
|
+
return x - y;
|
|
142
|
+
return compareBigInts(unscaled(x, a) * fa, unscaled(y, b) * fb);
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (a.type === "float" || b.type === "float")
|
|
146
|
+
return (x, y) => compareFloats(toFloat(x, a), toFloat(y, b));
|
|
147
|
+
switch (a.type) {
|
|
148
|
+
case "boolean":
|
|
149
|
+
return (x, y) => Number(x) - Number(y);
|
|
150
|
+
case "string":
|
|
151
|
+
case "time":
|
|
152
|
+
case "timestamptz":
|
|
153
|
+
case "uuid":
|
|
154
|
+
return (x, y) => compareText(x, y);
|
|
155
|
+
case "date":
|
|
156
|
+
case "timestamp":
|
|
157
|
+
if (a.type === b.type)
|
|
158
|
+
return (x, y) => compareText(x, y);
|
|
159
|
+
return (x, y) => compareText(asTimestampText(x, a), asTimestampText(y, b));
|
|
160
|
+
case "binary":
|
|
161
|
+
return (x, y) => compareBytes(fromBase64(x), fromBase64(y));
|
|
162
|
+
case "interval":
|
|
163
|
+
return (x, y) => {
|
|
164
|
+
const p = normalizeInterval(x);
|
|
165
|
+
const q = normalizeInterval(y);
|
|
166
|
+
return compareBigInts(p[0], q[0]) || compareBigInts(p[1], q[1]) || compareBigInts(p[2], q[2]);
|
|
167
|
+
};
|
|
168
|
+
default:
|
|
169
|
+
throw new TypeError(`${a.type} and ${b.type} values are not compared.`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/** A value of one type as a value of another that holds it: a common type of §4.4. */
|
|
173
|
+
function caster(from, to) {
|
|
174
|
+
if (sameType(from, to))
|
|
175
|
+
return (value) => value;
|
|
176
|
+
if (to.type === "float")
|
|
177
|
+
return (value) => (value === null ? null : floatJson(toFloat(value, from)));
|
|
178
|
+
if (isExact(from) && isExact(to)) {
|
|
179
|
+
const factor = power10(scaleOf(to) - scaleOf(from));
|
|
180
|
+
return (value) => (value === null ? null : exactJson(unscaled(value, from) * factor, to));
|
|
181
|
+
}
|
|
182
|
+
if (to.type === "timestamp") {
|
|
183
|
+
const unit = to.unit ?? "us";
|
|
184
|
+
// Counted in the source's unit, which holds its values (a date at midnight in microseconds), then in the
|
|
185
|
+
// result's.
|
|
186
|
+
const source = from.type === "timestamp" ? (from.unit ?? "us") : "us";
|
|
187
|
+
const [a, b] = [UNIT_PER_SECOND[source], UNIT_PER_SECOND[unit]];
|
|
188
|
+
return (value) => {
|
|
189
|
+
if (value === null)
|
|
190
|
+
return null;
|
|
191
|
+
const count = parseTimestampText(asTimestampText(value, from), source);
|
|
192
|
+
const result = timestampText(b >= a ? count * (b / a) : floorDivide(count, a / b), unit);
|
|
193
|
+
return isValueOf(result, to) ? result : null;
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return (value) => value;
|
|
197
|
+
}
|
|
198
|
+
function floorDivide(a, b) {
|
|
199
|
+
const quotient = a / b;
|
|
200
|
+
return a % b !== 0n && a < 0n ? quotient - 1n : quotient;
|
|
201
|
+
}
|
|
202
|
+
// Simple case mappings that DuckDB 2.0 (utf8proc) makes where JavaScript maps a code point to several (§3.4).
|
|
203
|
+
const LOWER_SPECIAL = new Map([[0x130, "i"]]);
|
|
204
|
+
const UPPER_SPECIAL = new Map([
|
|
205
|
+
[0xdf, "ẞ"],
|
|
206
|
+
[0x1fb3, "ᾼ"],
|
|
207
|
+
[0x1fc3, "ῌ"],
|
|
208
|
+
[0x1ff3, "ῼ"],
|
|
209
|
+
]);
|
|
210
|
+
for (let code = 0x1f80; code <= 0x1faf; code++) {
|
|
211
|
+
// Greek letters with ypogegrammeni map to the same letters with prosgegrammeni, 8 code points on.
|
|
212
|
+
if ((code & 0xf) < 8)
|
|
213
|
+
UPPER_SPECIAL.set(code, String.fromCodePoint(code + 8));
|
|
214
|
+
else
|
|
215
|
+
UPPER_SPECIAL.set(code, String.fromCodePoint(code));
|
|
216
|
+
}
|
|
217
|
+
const ASCII = /^[\0-\x7f]*$/;
|
|
218
|
+
const lowerCache = new Map();
|
|
219
|
+
const upperCache = new Map();
|
|
220
|
+
/** The string with each code point mapped by itself to lower or upper case (§3.4). */
|
|
221
|
+
export function mapCase(text, upper) {
|
|
222
|
+
if (ASCII.test(text))
|
|
223
|
+
return upper ? text.toUpperCase() : text.toLowerCase();
|
|
224
|
+
const cache = upper ? upperCache : lowerCache;
|
|
225
|
+
const special = upper ? UPPER_SPECIAL : LOWER_SPECIAL;
|
|
226
|
+
let result = "";
|
|
227
|
+
for (const point of text) {
|
|
228
|
+
let mapped = cache.get(point);
|
|
229
|
+
if (mapped === undefined) {
|
|
230
|
+
const code = point.codePointAt(0);
|
|
231
|
+
mapped = special.get(code);
|
|
232
|
+
if (mapped === undefined) {
|
|
233
|
+
const plain = upper ? point.toUpperCase() : point.toLowerCase();
|
|
234
|
+
// A code point that JavaScript maps to several stays as it is, as in utf8proc's simple mappings.
|
|
235
|
+
mapped = plain.length === point.length || [...plain].length === 1 ? plain : point;
|
|
236
|
+
}
|
|
237
|
+
if (cache.size < 4096)
|
|
238
|
+
cache.set(point, mapped);
|
|
239
|
+
}
|
|
240
|
+
result += mapped;
|
|
241
|
+
}
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
/** The number of code points of a string. */
|
|
245
|
+
function codePoints(text) {
|
|
246
|
+
let count = text.length;
|
|
247
|
+
for (let at = 0; at < text.length; at++) {
|
|
248
|
+
const unit = text.charCodeAt(at);
|
|
249
|
+
if (unit >= 0xd800 && unit < 0xdc00)
|
|
250
|
+
count--;
|
|
251
|
+
}
|
|
252
|
+
return count;
|
|
253
|
+
}
|
|
254
|
+
/** The UTF-16 offset of the code point at `points` from the start (the length when there are fewer). */
|
|
255
|
+
function offsetOf(text, points) {
|
|
256
|
+
let at = 0;
|
|
257
|
+
for (let point = 0; point < points && at < text.length; point++) {
|
|
258
|
+
const unit = text.charCodeAt(at);
|
|
259
|
+
at += unit >= 0xd800 && unit < 0xdc00 ? 2 : 1;
|
|
260
|
+
}
|
|
261
|
+
return at;
|
|
262
|
+
}
|
|
263
|
+
/** A whole number argument as a JavaScript number; bigints beyond 2^53 are far beyond every string. */
|
|
264
|
+
function whole(value) {
|
|
265
|
+
return typeof value === "number" ? value : Number(value);
|
|
266
|
+
}
|
|
267
|
+
const NUMBER = /^[+-]?([0-9]+(\.[0-9]*)?|\.[0-9]+)([eE][+-]?[0-9]+)?$/;
|
|
268
|
+
const TIMESTAMP_TEXT = /^([0-9]{4}-[0-9]{2}-[0-9]{2})[ T]([0-9]{2}:[0-9]{2})(?::([0-9]{2})(?:\.([0-9]{1,9}))?)?$/;
|
|
269
|
+
/** `to_timestamp` (§4.4): the fraction cut to microseconds, as DuckDB 2.0 casts it. */
|
|
270
|
+
function toTimestamp(text) {
|
|
271
|
+
const match = TIMESTAMP_TEXT.exec(text);
|
|
272
|
+
if (!match)
|
|
273
|
+
return null;
|
|
274
|
+
const fraction = (match[4] ?? "").slice(0, 6).replace(/0+$/, "");
|
|
275
|
+
const result = `${match[1]} ${match[2]}:${match[3] ?? "00"}${fraction ? `.${fraction}` : ""}`;
|
|
276
|
+
return isValueOf(result, { type: "timestamp" }) ? result : null;
|
|
277
|
+
}
|
|
278
|
+
/** Days since 1970-01-01 of a date or of a timestamp's date. */
|
|
279
|
+
function daysOf(value) {
|
|
280
|
+
return parseDateText(value.slice(0, 10));
|
|
281
|
+
}
|
|
282
|
+
/** The type of an expression, which the checker has settled for every untyped null that has a place. */
|
|
283
|
+
function typeOf(type) {
|
|
284
|
+
return type.type === "null" ? { type: "string" } : type;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Compiles a checked expression to a function of a row ID. `read` gives the values of the columns it names,
|
|
288
|
+
* and of the table's own columns, which `search` reads.
|
|
289
|
+
*/
|
|
290
|
+
export function compile(expr, read) {
|
|
291
|
+
switch (expr.kind) {
|
|
292
|
+
case "column":
|
|
293
|
+
return read(expr.name);
|
|
294
|
+
case "literal": {
|
|
295
|
+
const value = expr.value;
|
|
296
|
+
return () => value;
|
|
297
|
+
}
|
|
298
|
+
case "search": {
|
|
299
|
+
const needle = mapCase(expr.text, false);
|
|
300
|
+
const columns = expr.columns.map((column) => ({ column, values: read(column.name) }));
|
|
301
|
+
return (row) => {
|
|
302
|
+
for (const { column, values } of columns) {
|
|
303
|
+
const value = values(row);
|
|
304
|
+
if (value === null)
|
|
305
|
+
continue;
|
|
306
|
+
const text = column.type === "string" ? value : textOf(value, column);
|
|
307
|
+
if (mapCase(text, false).includes(needle))
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
return false;
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
case "call":
|
|
314
|
+
return call(expr, read);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function call(expr, read) {
|
|
318
|
+
const args = expr.args.map((arg) => compile(arg, read));
|
|
319
|
+
const types = expr.args.map((arg) => typeOf(arg.type));
|
|
320
|
+
const type = expr.type;
|
|
321
|
+
const [a, b, c] = args;
|
|
322
|
+
switch (expr.fn) {
|
|
323
|
+
case "and":
|
|
324
|
+
return (row) => {
|
|
325
|
+
let unknown = false;
|
|
326
|
+
for (const arg of args) {
|
|
327
|
+
const value = arg(row);
|
|
328
|
+
if (value === false)
|
|
329
|
+
return false;
|
|
330
|
+
if (value === null)
|
|
331
|
+
unknown = true;
|
|
332
|
+
}
|
|
333
|
+
return unknown ? null : true;
|
|
334
|
+
};
|
|
335
|
+
case "or":
|
|
336
|
+
return (row) => {
|
|
337
|
+
let unknown = false;
|
|
338
|
+
for (const arg of args) {
|
|
339
|
+
const value = arg(row);
|
|
340
|
+
if (value === true)
|
|
341
|
+
return true;
|
|
342
|
+
if (value === null)
|
|
343
|
+
unknown = true;
|
|
344
|
+
}
|
|
345
|
+
return unknown ? null : false;
|
|
346
|
+
};
|
|
347
|
+
case "not":
|
|
348
|
+
return (row) => {
|
|
349
|
+
const value = a(row);
|
|
350
|
+
return value === null ? null : !value;
|
|
351
|
+
};
|
|
352
|
+
case "eq":
|
|
353
|
+
case "ne":
|
|
354
|
+
case "lt":
|
|
355
|
+
case "le":
|
|
356
|
+
case "gt":
|
|
357
|
+
case "ge": {
|
|
358
|
+
const compare = comparer(types[0], types[1]);
|
|
359
|
+
const test = {
|
|
360
|
+
eq: (n) => n === 0,
|
|
361
|
+
ne: (n) => n !== 0,
|
|
362
|
+
lt: (n) => n < 0,
|
|
363
|
+
le: (n) => n <= 0,
|
|
364
|
+
gt: (n) => n > 0,
|
|
365
|
+
ge: (n) => n >= 0,
|
|
366
|
+
}[expr.fn];
|
|
367
|
+
return (row) => {
|
|
368
|
+
const x = a(row);
|
|
369
|
+
if (x === null)
|
|
370
|
+
return null;
|
|
371
|
+
const y = b(row);
|
|
372
|
+
return y === null ? null : test(compare(x, y));
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
case "in": {
|
|
376
|
+
const items = expr.args.slice(1).map((arg, index) => ({
|
|
377
|
+
value: arg.value,
|
|
378
|
+
compare: comparer(types[0], types[index + 1]),
|
|
379
|
+
}));
|
|
380
|
+
// Strings are looked up in a set: in may have many literals.
|
|
381
|
+
if (types[0].type === "string") {
|
|
382
|
+
const set = new Set(items.map((item) => item.value));
|
|
383
|
+
return (row) => {
|
|
384
|
+
const value = a(row);
|
|
385
|
+
return value === null ? null : set.has(value);
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
return (row) => {
|
|
389
|
+
const value = a(row);
|
|
390
|
+
if (value === null)
|
|
391
|
+
return null;
|
|
392
|
+
return items.some((item) => item.compare(value, item.value) === 0);
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
case "is_null":
|
|
396
|
+
return (row) => a(row) === null;
|
|
397
|
+
case "is_blank": {
|
|
398
|
+
const strings = types[0].type === "string";
|
|
399
|
+
return (row) => {
|
|
400
|
+
const value = a(row);
|
|
401
|
+
return value === null || (strings && value === "");
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
case "if": {
|
|
405
|
+
const [then, otherwise] = [caster(types[1], type), caster(types[2], type)];
|
|
406
|
+
return (row) => (a(row) === true ? then(b(row)) : otherwise(c(row)));
|
|
407
|
+
}
|
|
408
|
+
case "coalesce": {
|
|
409
|
+
const casts = types.map((from) => caster(from, type));
|
|
410
|
+
return (row) => {
|
|
411
|
+
for (let index = 0; index < args.length; index++) {
|
|
412
|
+
const value = args[index](row);
|
|
413
|
+
if (value !== null)
|
|
414
|
+
return casts[index](value);
|
|
415
|
+
}
|
|
416
|
+
return null;
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
case "least":
|
|
420
|
+
case "greatest": {
|
|
421
|
+
const casts = types.map((from) => caster(from, type));
|
|
422
|
+
const compare = comparer(type, type);
|
|
423
|
+
const greatest = expr.fn === "greatest";
|
|
424
|
+
return (row) => {
|
|
425
|
+
let best = null;
|
|
426
|
+
for (let index = 0; index < args.length; index++) {
|
|
427
|
+
const raw = args[index](row);
|
|
428
|
+
if (raw === null)
|
|
429
|
+
continue;
|
|
430
|
+
const value = casts[index](raw);
|
|
431
|
+
if (value === null)
|
|
432
|
+
continue;
|
|
433
|
+
if (best === null)
|
|
434
|
+
best = value;
|
|
435
|
+
else {
|
|
436
|
+
const order = compare(value, best);
|
|
437
|
+
if (greatest ? order > 0 : order < 0)
|
|
438
|
+
best = value;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return best;
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
case "add":
|
|
445
|
+
case "sub":
|
|
446
|
+
case "mul":
|
|
447
|
+
case "mod":
|
|
448
|
+
return arithmetic(expr.fn, a, b, types[0], types[1], type);
|
|
449
|
+
case "div":
|
|
450
|
+
return (row) => {
|
|
451
|
+
const x = a(row);
|
|
452
|
+
if (x === null)
|
|
453
|
+
return null;
|
|
454
|
+
const y = b(row);
|
|
455
|
+
if (y === null)
|
|
456
|
+
return null;
|
|
457
|
+
const p = toFloat(x, types[0]);
|
|
458
|
+
const q = toFloat(y, types[1]);
|
|
459
|
+
return Number.isFinite(p) && Number.isFinite(q) ? finite(p / q) : null;
|
|
460
|
+
};
|
|
461
|
+
case "neg":
|
|
462
|
+
case "abs": {
|
|
463
|
+
const negate = expr.fn === "neg";
|
|
464
|
+
if (type.type === "float") {
|
|
465
|
+
return (row) => {
|
|
466
|
+
const x = a(row);
|
|
467
|
+
if (x === null)
|
|
468
|
+
return null;
|
|
469
|
+
const number = toFloat(x, types[0]);
|
|
470
|
+
return Number.isFinite(number) ? (negate ? -number : Math.abs(number)) : null;
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
return (row) => {
|
|
474
|
+
const x = a(row);
|
|
475
|
+
if (x === null)
|
|
476
|
+
return null;
|
|
477
|
+
const value = unscaled(x, types[0]);
|
|
478
|
+
return exactJson(negate || value < 0n ? -value : value, type);
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
case "round": {
|
|
482
|
+
const digits = expr.args.length === 2 ? expr.args[1].value : 0;
|
|
483
|
+
if (type.type === "float") {
|
|
484
|
+
const factor = 10 ** Math.abs(digits);
|
|
485
|
+
return float1(a, types[0], (x) => {
|
|
486
|
+
const magnitude = Math.abs(x);
|
|
487
|
+
const rounded = digits >= 0 ? Math.round(magnitude * factor) / factor : Math.round(magnitude / factor) * factor;
|
|
488
|
+
return x < 0 ? -rounded : rounded;
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
const scale = scaleOf(types[0]);
|
|
492
|
+
return (row) => {
|
|
493
|
+
const x = a(row);
|
|
494
|
+
if (x === null)
|
|
495
|
+
return null;
|
|
496
|
+
const value = unscaled(x, types[0]);
|
|
497
|
+
if (digits >= scale)
|
|
498
|
+
return exactJson(value, type);
|
|
499
|
+
const rounded = roundHalfAway(value, scale - digits);
|
|
500
|
+
return exactJson(digits < 0 ? rounded * power10(-digits) : rounded, type);
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
case "floor":
|
|
504
|
+
case "ceil": {
|
|
505
|
+
const up = expr.fn === "ceil";
|
|
506
|
+
if (type.type === "float")
|
|
507
|
+
return float1(a, types[0], up ? Math.ceil : Math.floor);
|
|
508
|
+
const scale = scaleOf(types[0]);
|
|
509
|
+
if (scale === 0)
|
|
510
|
+
return a;
|
|
511
|
+
return (row) => {
|
|
512
|
+
const x = a(row);
|
|
513
|
+
return x === null ? null : exactJson(floorOrCeil(unscaled(x, types[0]), scale, up), type);
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
case "sqrt":
|
|
517
|
+
return float1(a, types[0], Math.sqrt);
|
|
518
|
+
case "exp":
|
|
519
|
+
return float1(a, types[0], Math.exp);
|
|
520
|
+
case "ln":
|
|
521
|
+
return float1(a, types[0], Math.log);
|
|
522
|
+
case "log10":
|
|
523
|
+
return float1(a, types[0], Math.log10);
|
|
524
|
+
case "power":
|
|
525
|
+
return (row) => {
|
|
526
|
+
const x = a(row);
|
|
527
|
+
if (x === null)
|
|
528
|
+
return null;
|
|
529
|
+
const y = b(row);
|
|
530
|
+
if (y === null)
|
|
531
|
+
return null;
|
|
532
|
+
const p = toFloat(x, types[0]);
|
|
533
|
+
const q = toFloat(y, types[1]);
|
|
534
|
+
return Number.isFinite(p) && Number.isFinite(q) ? finite(p ** q) : null;
|
|
535
|
+
};
|
|
536
|
+
case "sign":
|
|
537
|
+
return (row) => {
|
|
538
|
+
const x = a(row);
|
|
539
|
+
if (x === null)
|
|
540
|
+
return null;
|
|
541
|
+
const number = toFloat(x, types[0]);
|
|
542
|
+
if (!Number.isFinite(number))
|
|
543
|
+
return null;
|
|
544
|
+
return number > 0 ? 1 : number < 0 ? -1 : 0;
|
|
545
|
+
};
|
|
546
|
+
case "to_number":
|
|
547
|
+
return (row) => {
|
|
548
|
+
const x = a(row);
|
|
549
|
+
if (x === null)
|
|
550
|
+
return null;
|
|
551
|
+
const text = trimSpaces(x);
|
|
552
|
+
return NUMBER.test(text) ? finite(Number(text)) : null;
|
|
553
|
+
};
|
|
554
|
+
case "concat":
|
|
555
|
+
return (row) => {
|
|
556
|
+
let result = "";
|
|
557
|
+
for (let index = 0; index < args.length; index++)
|
|
558
|
+
result += textOf(args[index](row), types[index]) ?? "";
|
|
559
|
+
return result;
|
|
560
|
+
};
|
|
561
|
+
case "text":
|
|
562
|
+
return (row) => textOf(a(row), types[0]);
|
|
563
|
+
case "length":
|
|
564
|
+
return string1(a, codePoints);
|
|
565
|
+
case "lower":
|
|
566
|
+
return string1(a, (text) => mapCase(text, false));
|
|
567
|
+
case "upper":
|
|
568
|
+
return string1(a, (text) => mapCase(text, true));
|
|
569
|
+
case "trim":
|
|
570
|
+
return string1(a, trimSpaces);
|
|
571
|
+
case "left":
|
|
572
|
+
case "right": {
|
|
573
|
+
const left = expr.fn === "left";
|
|
574
|
+
return (row) => {
|
|
575
|
+
const x = a(row);
|
|
576
|
+
if (x === null)
|
|
577
|
+
return null;
|
|
578
|
+
const y = b(row);
|
|
579
|
+
if (y === null)
|
|
580
|
+
return null;
|
|
581
|
+
const text = x;
|
|
582
|
+
const count = Math.max(0, whole(y));
|
|
583
|
+
if (left)
|
|
584
|
+
return text.slice(0, offsetOf(text, count));
|
|
585
|
+
const total = codePoints(text);
|
|
586
|
+
return count >= total ? text : text.slice(offsetOf(text, total - count));
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
case "substr":
|
|
590
|
+
return (row) => {
|
|
591
|
+
const x = a(row);
|
|
592
|
+
if (x === null)
|
|
593
|
+
return null;
|
|
594
|
+
const y = b(row);
|
|
595
|
+
if (y === null)
|
|
596
|
+
return null;
|
|
597
|
+
const text = x;
|
|
598
|
+
const start = offsetOf(text, Math.max(1, whole(y)) - 1);
|
|
599
|
+
if (!c)
|
|
600
|
+
return text.slice(start);
|
|
601
|
+
const z = c(row);
|
|
602
|
+
if (z === null)
|
|
603
|
+
return null;
|
|
604
|
+
const rest = text.slice(start);
|
|
605
|
+
return rest.slice(0, offsetOf(rest, Math.max(0, whole(z))));
|
|
606
|
+
};
|
|
607
|
+
case "replace":
|
|
608
|
+
return (row) => {
|
|
609
|
+
const x = a(row);
|
|
610
|
+
if (x === null)
|
|
611
|
+
return null;
|
|
612
|
+
const y = b(row);
|
|
613
|
+
if (y === null)
|
|
614
|
+
return null;
|
|
615
|
+
const z = c(row);
|
|
616
|
+
if (z === null)
|
|
617
|
+
return null;
|
|
618
|
+
return y === "" ? x : x.split(y).join(z);
|
|
619
|
+
};
|
|
620
|
+
case "contains":
|
|
621
|
+
case "starts_with":
|
|
622
|
+
case "ends_with": {
|
|
623
|
+
const test = expr.fn === "contains"
|
|
624
|
+
? (x, y) => x.includes(y)
|
|
625
|
+
: expr.fn === "starts_with"
|
|
626
|
+
? (x, y) => x.startsWith(y)
|
|
627
|
+
: (x, y) => x.endsWith(y);
|
|
628
|
+
return (row) => {
|
|
629
|
+
const x = a(row);
|
|
630
|
+
if (x === null)
|
|
631
|
+
return null;
|
|
632
|
+
const y = b(row);
|
|
633
|
+
return y === null ? null : test(x, y);
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
case "to_date":
|
|
637
|
+
return string1(a, (text) => (isDateText(text) ? text : null));
|
|
638
|
+
case "to_timestamp":
|
|
639
|
+
return string1(a, toTimestamp);
|
|
640
|
+
case "year":
|
|
641
|
+
return string1(a, (text) => Number(text.slice(0, 4)));
|
|
642
|
+
case "month":
|
|
643
|
+
return string1(a, (text) => Number(text.slice(5, 7)));
|
|
644
|
+
case "day":
|
|
645
|
+
return string1(a, (text) => Number(text.slice(8, 10)));
|
|
646
|
+
case "isodow":
|
|
647
|
+
return string1(a, (text) => ((((daysOf(text) + 3) % 7) + 7) % 7) + 1);
|
|
648
|
+
case "hour":
|
|
649
|
+
case "minute":
|
|
650
|
+
case "second": {
|
|
651
|
+
const at = (types[0].type === "time" ? 0 : 11) + { hour: 0, minute: 3, second: 6 }[expr.fn];
|
|
652
|
+
return string1(a, (text) => Number(text.slice(at, at + 2)));
|
|
653
|
+
}
|
|
654
|
+
case "date_diff_days":
|
|
655
|
+
return (row) => {
|
|
656
|
+
const x = a(row);
|
|
657
|
+
if (x === null)
|
|
658
|
+
return null;
|
|
659
|
+
const y = b(row);
|
|
660
|
+
return y === null ? null : daysOf(y) - daysOf(x);
|
|
661
|
+
};
|
|
662
|
+
case "add_days":
|
|
663
|
+
return (row) => {
|
|
664
|
+
const x = a(row);
|
|
665
|
+
if (x === null)
|
|
666
|
+
return null;
|
|
667
|
+
const y = b(row);
|
|
668
|
+
if (y === null)
|
|
669
|
+
return null;
|
|
670
|
+
const count = whole(y);
|
|
671
|
+
if (count < INT32_MIN || count > INT32_MAX)
|
|
672
|
+
return null;
|
|
673
|
+
const days = daysOf(x) + count;
|
|
674
|
+
// Years from 1 to 9999 (§2.2); a timestamp keeps its time of day, and its unit's range.
|
|
675
|
+
if (days < daysFromCivil(1, 1, 1) || days > daysFromCivil(9999, 12, 31))
|
|
676
|
+
return null;
|
|
677
|
+
const result = dateText(days) + x.slice(10);
|
|
678
|
+
return isValueOf(result, type) ? result : null;
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
/** A function of one string that gives null for null. */
|
|
683
|
+
function string1(a, apply) {
|
|
684
|
+
return (row) => {
|
|
685
|
+
const value = a(row);
|
|
686
|
+
return value === null ? null : apply(value);
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
/** A function of one number computed as a float: null for null, for numbers that are not finite and results. */
|
|
690
|
+
function float1(a, type, apply) {
|
|
691
|
+
return (row) => {
|
|
692
|
+
const value = a(row);
|
|
693
|
+
if (value === null)
|
|
694
|
+
return null;
|
|
695
|
+
const number = toFloat(value, type);
|
|
696
|
+
return Number.isFinite(number) ? finite(apply(number)) : null;
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
/** `add`, `sub`, `mul` and `mod` (§3.7): exact for exact numbers, null when the result does not fit its type. */
|
|
700
|
+
function arithmetic(fn, a, b, ta, tb, type) {
|
|
701
|
+
if (type.type === "float") {
|
|
702
|
+
const op = {
|
|
703
|
+
add: (x, y) => x + y,
|
|
704
|
+
sub: (x, y) => x - y,
|
|
705
|
+
mul: (x, y) => x * y,
|
|
706
|
+
mod: (x, y) => x % y,
|
|
707
|
+
}[fn];
|
|
708
|
+
return (row) => {
|
|
709
|
+
const x = a(row);
|
|
710
|
+
if (x === null)
|
|
711
|
+
return null;
|
|
712
|
+
const y = b(row);
|
|
713
|
+
if (y === null)
|
|
714
|
+
return null;
|
|
715
|
+
const p = toFloat(x, ta);
|
|
716
|
+
const q = toFloat(y, tb);
|
|
717
|
+
return Number.isFinite(p) && Number.isFinite(q) ? finite(op(p, q)) : null;
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
// Operands at the result's scale; mul's scale is the sum of theirs.
|
|
721
|
+
const scale = scaleOf(type);
|
|
722
|
+
const fa = fn === "mul" ? 1n : power10(scale - scaleOf(ta));
|
|
723
|
+
const fb = fn === "mul" ? 1n : power10(scale - scaleOf(tb));
|
|
724
|
+
const op = {
|
|
725
|
+
add: (x, y) => x + y,
|
|
726
|
+
sub: (x, y) => x - y,
|
|
727
|
+
mul: (x, y) => x * y,
|
|
728
|
+
mod: (x, y) => (y === 0n ? null : x % y),
|
|
729
|
+
}[fn];
|
|
730
|
+
const quick = {
|
|
731
|
+
add: (x, y) => x + y,
|
|
732
|
+
sub: (x, y) => x - y,
|
|
733
|
+
mul: (x, y) => x * y,
|
|
734
|
+
mod: (x, y) => (y === 0 ? Number.NaN : x % y),
|
|
735
|
+
}[fn];
|
|
736
|
+
const whole = type.type === "bigint";
|
|
737
|
+
return (row) => {
|
|
738
|
+
const x = a(row);
|
|
739
|
+
if (x === null)
|
|
740
|
+
return null;
|
|
741
|
+
const y = b(row);
|
|
742
|
+
if (y === null)
|
|
743
|
+
return null;
|
|
744
|
+
if (whole && typeof x === "number" && typeof y === "number") {
|
|
745
|
+
const result = quick(x, y);
|
|
746
|
+
// An integer has no −0, which JavaScript makes of 0 × −1.
|
|
747
|
+
if (Number.isSafeInteger(result))
|
|
748
|
+
return result + 0;
|
|
749
|
+
if (Number.isNaN(result))
|
|
750
|
+
return null;
|
|
751
|
+
}
|
|
752
|
+
const result = op(unscaled(x, ta) * fa, unscaled(y, tb) * fb);
|
|
753
|
+
return result === null ? null : exactJson(result, type);
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
/** The values of a derived column, or of a condition, for every row of a table. */
|
|
757
|
+
export function evaluate(expr, read, count) {
|
|
758
|
+
const value = compile(expr, read);
|
|
759
|
+
const values = new Array(count);
|
|
760
|
+
for (let row = 0; row < count; row++)
|
|
761
|
+
values[row] = value(row);
|
|
762
|
+
return values;
|
|
763
|
+
}
|
|
764
|
+
/** The row IDs, in order, for which a condition is true (§3.6). */
|
|
765
|
+
export function filter(expr, read, count) {
|
|
766
|
+
const test = compile(expr, read);
|
|
767
|
+
const kept = new Uint32Array(count);
|
|
768
|
+
let length = 0;
|
|
769
|
+
for (let row = 0; row < count; row++)
|
|
770
|
+
if (test(row) === true)
|
|
771
|
+
kept[length++] = row;
|
|
772
|
+
return kept.slice(0, length);
|
|
773
|
+
}
|
|
774
|
+
//# sourceMappingURL=evaluate.js.map
|