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.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +211 -2
  3. package/dist/aggregate.d.ts +45 -0
  4. package/dist/aggregate.d.ts.map +1 -0
  5. package/dist/aggregate.js +305 -0
  6. package/dist/aggregate.js.map +1 -0
  7. package/dist/arrow.d.ts +7 -0
  8. package/dist/arrow.d.ts.map +1 -0
  9. package/dist/arrow.js +25 -0
  10. package/dist/arrow.js.map +1 -0
  11. package/dist/engine.d.ts +40 -0
  12. package/dist/engine.d.ts.map +1 -0
  13. package/dist/engine.js +1022 -0
  14. package/dist/engine.js.map +1 -0
  15. package/dist/evaluate.d.ts +37 -0
  16. package/dist/evaluate.d.ts.map +1 -0
  17. package/dist/evaluate.js +774 -0
  18. package/dist/evaluate.js.map +1 -0
  19. package/dist/export.d.ts +23 -0
  20. package/dist/export.d.ts.map +1 -0
  21. package/dist/export.js +48 -0
  22. package/dist/export.js.map +1 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +2 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/lazy.d.ts +65 -0
  28. package/dist/lazy.d.ts.map +1 -0
  29. package/dist/lazy.js +177 -0
  30. package/dist/lazy.js.map +1 -0
  31. package/dist/numbers.d.ts +20 -0
  32. package/dist/numbers.d.ts.map +1 -0
  33. package/dist/numbers.js +129 -0
  34. package/dist/numbers.js.map +1 -0
  35. package/dist/order.d.ts +67 -0
  36. package/dist/order.d.ts.map +1 -0
  37. package/dist/order.js +561 -0
  38. package/dist/order.js.map +1 -0
  39. package/dist/parquet.d.ts +38 -0
  40. package/dist/parquet.d.ts.map +1 -0
  41. package/dist/parquet.js +673 -0
  42. package/dist/parquet.js.map +1 -0
  43. package/dist/plan.d.ts +37 -0
  44. package/dist/plan.d.ts.map +1 -0
  45. package/dist/plan.js +51 -0
  46. package/dist/plan.js.map +1 -0
  47. package/dist/summary.d.ts +37 -0
  48. package/dist/summary.d.ts.map +1 -0
  49. package/dist/summary.js +175 -0
  50. package/dist/summary.js.map +1 -0
  51. package/dist/table.d.ts +172 -0
  52. package/dist/table.d.ts.map +1 -0
  53. package/dist/table.js +154 -0
  54. package/dist/table.js.map +1 -0
  55. package/dist/text.d.ts +18 -0
  56. package/dist/text.d.ts.map +1 -0
  57. package/dist/text.js +1204 -0
  58. package/dist/text.js.map +1 -0
  59. package/dist/workbook.d.ts +6 -0
  60. package/dist/workbook.d.ts.map +1 -0
  61. package/dist/workbook.js +35 -0
  62. package/dist/workbook.js.map +1 -0
  63. package/package.json +46 -4
  64. package/src/aggregate.ts +358 -0
  65. package/src/arrow.ts +24 -0
  66. package/src/engine.ts +1163 -0
  67. package/src/evaluate.ts +764 -0
  68. package/src/export.ts +73 -0
  69. package/src/index.ts +2 -0
  70. package/src/lazy.ts +185 -0
  71. package/src/numbers.ts +128 -0
  72. package/src/order.ts +594 -0
  73. package/src/parquet.ts +801 -0
  74. package/src/plan.ts +99 -0
  75. package/src/summary.ts +180 -0
  76. package/src/table.ts +297 -0
  77. package/src/text.ts +1285 -0
  78. package/src/workbook.ts +48 -0
package/src/order.ts ADDED
@@ -0,0 +1,594 @@
1
+ /**
2
+ * The order of values and views (docs/spec.md §3.1, §3.3).
3
+ *
4
+ * Each column's values are ranked once: its distinct values that are not blank are numbered from 0 in
5
+ * ascending order, the empty string of a string column ranks `distinct` and null `distinct + 1`. A view is
6
+ * sorted by the ranks of its sort keys, with one stable counting sort per key from the last key to the first,
7
+ * starting from row ID order: blanks come last in both directions and tie with each other, ties go to the
8
+ * next key, and rows equal for every key stay in row ID order.
9
+ *
10
+ * Values of fixed width are ranked through 32-bit words compared as unsigned integers, most significant
11
+ * first, with a radix sort; strings and bytes are ranked with a comparison sort of runs, which are then merged.
12
+ */
13
+ import {
14
+ type Column,
15
+ daysFromCivil,
16
+ floatOf,
17
+ fromBase64,
18
+ isValueOf,
19
+ type JsonValue,
20
+ normalizeInterval,
21
+ } from "vastgrid-spec";
22
+ import type { Vector } from "./table.js";
23
+
24
+ /** The ranks of one column's values. */
25
+ export interface ColumnRanks {
26
+ /** The rank of each row's value. */
27
+ ranks: Uint32Array;
28
+ /** The number of distinct values that are not blank; blanks rank from here. */
29
+ distinct: number;
30
+ /**
31
+ * The rank of a value written as §2.2 writes the column's values, or −1 when no row holds a value equal to
32
+ * it (§3.1). Exact numbers are equal whatever their scales, so the decimal `"1.5"` finds `"1.50"`.
33
+ */
34
+ rankOf(value: JsonValue): number;
35
+ }
36
+
37
+ /** The values of a column by row ID. */
38
+ type Values = Vector;
39
+
40
+ /** How the values of a column of fixed width become words. */
41
+ interface WordCodec {
42
+ width: number;
43
+ /** Writes the words of a value of the column, or of a value that `accept` gave, at a row of `words`. */
44
+ write(value: JsonValue, words: readonly Uint32Array[], at: number): void;
45
+ /** A value to look up in the column as `write` takes it, or null when no row can hold a value equal to it. */
46
+ accept(value: JsonValue): JsonValue | null;
47
+ }
48
+
49
+ export const TWO_32 = 4_294_967_296;
50
+ const MASK_32 = 0xffff_ffffn;
51
+ export const SIGN = 0x8000_0000;
52
+ const INT64_MIN = -(2n ** 63n);
53
+ const INT64_MAX = 2n ** 63n - 1n;
54
+
55
+ /** A signed 32-bit integer as a word whose unsigned order is the integer's order. */
56
+ export function int32Word(value: number): number {
57
+ return (value ^ SIGN) >>> 0;
58
+ }
59
+
60
+ /** Writes a safe integer as a signed 64-bit integer, in two words. */
61
+ export function writeInt64(value: number, high: Uint32Array, low: Uint32Array, at: number) {
62
+ const upper = Math.floor(value / TWO_32);
63
+ high[at] = int32Word(upper);
64
+ low[at] = value - upper * TWO_32;
65
+ }
66
+
67
+ /** Writes a signed 64-bit integer in two words. */
68
+ function writeBigInt64(value: bigint, high: Uint32Array, low: Uint32Array, at: number) {
69
+ const bits = BigInt.asUintN(64, value);
70
+ high[at] = int32Word(Number(bits >> 32n));
71
+ low[at] = Number(bits & MASK_32);
72
+ }
73
+
74
+ /** Writes a signed 128-bit integer in four words. */
75
+ function writeBigInt128(value: bigint, words: readonly Uint32Array[], at: number) {
76
+ const bits = BigInt.asUintN(128, value);
77
+ words[0][at] = int32Word(Number(bits >> 96n));
78
+ words[1][at] = Number((bits >> 64n) & MASK_32);
79
+ words[2][at] = Number((bits >> 32n) & MASK_32);
80
+ words[3][at] = Number(bits & MASK_32);
81
+ }
82
+
83
+ const floatBits = new Float64Array(1);
84
+ const floatWords = new Uint32Array(floatBits.buffer);
85
+ const littleEndian = new Uint8Array(new Uint32Array([1]).buffer)[0] === 1;
86
+ export const HIGH = littleEndian ? 1 : 0;
87
+ export const LOW = littleEndian ? 0 : 1;
88
+
89
+ /**
90
+ * Writes a float in two words: the bits of a positive number with the sign bit set, and those of a negative
91
+ * number inverted, so that the words order as the numbers do. −0.0 is written as 0.0, and every NaN as the
92
+ * greatest words, above Infinity (§3.1).
93
+ */
94
+ export function writeFloat(value: number, high: Uint32Array, low: Uint32Array, at: number) {
95
+ if (Number.isNaN(value)) {
96
+ high[at] = 0xffff_ffff;
97
+ low[at] = 0xffff_ffff;
98
+ return;
99
+ }
100
+ floatBits[0] = value === 0 ? 0 : value;
101
+ const upper = floatWords[HIGH];
102
+ const lower = floatWords[LOW];
103
+ if (upper >= SIGN) {
104
+ high[at] = ~upper >>> 0;
105
+ low[at] = ~lower >>> 0;
106
+ } else {
107
+ high[at] = (upper | SIGN) >>> 0;
108
+ low[at] = lower;
109
+ }
110
+ }
111
+
112
+ /** The number of the decimal digits of `text` from `start`, `length` of them. */
113
+ function digitsAt(text: string, start: number, length: number): number {
114
+ let number = 0;
115
+ for (let index = start; index < start + length; index++) number = number * 10 + text.charCodeAt(index) - 48;
116
+ return number;
117
+ }
118
+
119
+ /**
120
+ * The fraction of a second whose point is at `start`, as a count of 10^−`places` seconds: 0 without a point.
121
+ * The digits end at the end of the text or at its time zone.
122
+ */
123
+ function fractionAt(text: string, start: number, places: number): number {
124
+ if (text.charCodeAt(start) !== 46) return 0;
125
+ let number = 0;
126
+ let place = 0;
127
+ for (let index = start + 1; place < places; index++, place++) {
128
+ const digit = text.charCodeAt(index) - 48;
129
+ if (!(digit >= 0 && digit <= 9)) break;
130
+ number = number * 10 + digit;
131
+ }
132
+ for (; place < places; place++) number *= 10;
133
+ return number;
134
+ }
135
+
136
+ /** Days since 1970-01-01 of `YYYY-MM-DD` at `start` (§2.2). */
137
+ function daysAt(text: string, start: number): number {
138
+ return daysFromCivil(digitsAt(text, start, 4), digitsAt(text, start + 5, 2), digitsAt(text, start + 8, 2));
139
+ }
140
+
141
+ /** Seconds since midnight of `HH:MM:SS` at `start` (§2.2). */
142
+ function secondsAt(text: string, start: number): number {
143
+ return digitsAt(text, start, 2) * 3600 + digitsAt(text, start + 3, 2) * 60 + digitsAt(text, start + 6, 2);
144
+ }
145
+
146
+ /**
147
+ * The digits of a decimal's unscaled integer at a scale, with its sign: `"-12.5"` at scale 2 is `"-1250"`.
148
+ * Null when the text is not a decimal, or has digits other than 0 beyond the scale.
149
+ */
150
+ function unscaledDigits(text: string, scale: number): string | null {
151
+ const match = /^(-?)([0-9]+)(?:\.([0-9]+))?$/.exec(text);
152
+ if (!match) return null;
153
+ const fraction = match[3] ?? "";
154
+ if (fraction.length > scale && !/^0*$/.test(fraction.slice(scale))) return null;
155
+ return `${match[1]}${match[2]}${fraction.slice(0, scale).padEnd(scale, "0")}`;
156
+ }
157
+
158
+ function codecOf(column: Column): WordCodec {
159
+ const accept = (value: JsonValue) => (isValueOf(value, column) ? value : null);
160
+ switch (column.type) {
161
+ case "boolean":
162
+ return {
163
+ width: 1,
164
+ write: (value, words, at) => {
165
+ words[0][at] = value === true ? 1 : 0;
166
+ },
167
+ accept,
168
+ };
169
+ case "integer":
170
+ return {
171
+ width: 1,
172
+ write: (value, words, at) => {
173
+ words[0][at] = int32Word(value as number);
174
+ },
175
+ accept,
176
+ };
177
+ case "bigint":
178
+ return {
179
+ width: 2,
180
+ write: (value, words, at) => {
181
+ if (typeof value === "number") writeInt64(value, words[0], words[1], at);
182
+ else writeBigInt64(BigInt(value as string), words[0], words[1], at);
183
+ },
184
+ accept,
185
+ };
186
+ case "decimal": {
187
+ const scale = column.scale ?? 0;
188
+ const precision = column.precision ?? 38;
189
+ // Unscaled integers of up to 18 digits fit 64 bits; those of up to 15 digits are safe integers.
190
+ const wide = precision > 18;
191
+ return {
192
+ width: wide ? 4 : 2,
193
+ write: (value, words, at) => {
194
+ const digits = (value as string).replace(".", "");
195
+ if (wide) writeBigInt128(BigInt(digits), words, at);
196
+ else if (digits.length <= 15) writeInt64(Number(digits), words[0], words[1], at);
197
+ else writeBigInt64(BigInt(digits), words[0], words[1], at);
198
+ },
199
+ // A key is looked up by its unscaled digits at the column's scale.
200
+ accept: (value) => {
201
+ const digits = typeof value === "string" ? unscaledDigits(value, scale) : null;
202
+ return digits !== null && digits.replace(/^-?0*/, "").length <= precision ? digits : null;
203
+ },
204
+ };
205
+ }
206
+ case "float": {
207
+ // The values of 32-bit columns are the 32-bit floats that blocks carry (§8.4).
208
+ const single = column.bits === 32;
209
+ return {
210
+ width: 2,
211
+ write: (value, words, at) => {
212
+ const number = floatOf(value);
213
+ writeFloat(single ? Math.fround(number) : number, words[0], words[1], at);
214
+ },
215
+ accept,
216
+ };
217
+ }
218
+ case "date":
219
+ return {
220
+ width: 1,
221
+ write: (value, words, at) => {
222
+ words[0][at] = int32Word(daysAt(value as string, 0));
223
+ },
224
+ accept,
225
+ };
226
+ case "time":
227
+ return {
228
+ width: 2,
229
+ write: (value, words, at) => {
230
+ const text = value as string;
231
+ writeInt64(secondsAt(text, 0) * 1_000_000 + fractionAt(text, 8, 6), words[0], words[1], at);
232
+ },
233
+ accept,
234
+ };
235
+ case "timestamp":
236
+ case "timestamptz":
237
+ // Seconds since 1970-01-01 in two words, then the nanoseconds of the second, whatever the unit.
238
+ return {
239
+ width: 3,
240
+ write: (value, words, at) => {
241
+ const text = value as string;
242
+ writeInt64(daysAt(text, 0) * 86_400 + secondsAt(text, 11), words[0], words[1], at);
243
+ words[2][at] = fractionAt(text, 19, 9);
244
+ },
245
+ accept,
246
+ };
247
+ case "interval":
248
+ // The months (64 bits), the days (0 to 29) and the nanoseconds (64 bits) of normalizeInterval.
249
+ return {
250
+ width: 5,
251
+ write: (value, words, at) => {
252
+ const [months, days, nanos] = normalizeInterval(value as string) as [bigint, bigint, bigint];
253
+ writeBigInt64(months, words[0], words[1], at);
254
+ words[2][at] = int32Word(Number(days));
255
+ writeBigInt64(nanos, words[3], words[4], at);
256
+ },
257
+ accept: (value) => {
258
+ const parts = typeof value === "string" ? normalizeInterval(value) : null;
259
+ return parts && parts[0] >= INT64_MIN && parts[0] <= INT64_MAX ? value : null;
260
+ },
261
+ };
262
+ case "uuid":
263
+ return {
264
+ width: 4,
265
+ write: (value, words, at) => {
266
+ const hex = (value as string).replaceAll("-", "");
267
+ for (let word = 0; word < 4; word++) {
268
+ words[word][at] = Number.parseInt(hex.slice(word * 8, word * 8 + 8), 16);
269
+ }
270
+ },
271
+ accept,
272
+ };
273
+ default:
274
+ throw new TypeError(`${column.type} values are not written as words.`);
275
+ }
276
+ }
277
+
278
+ /** Writes the words of the value at a row of a column's numbers (`Vector.numbers`) at a row of `words`. */
279
+ export type NumberWriter = (row: number, words: readonly Uint32Array[], at: number) => void;
280
+
281
+ /**
282
+ * Sorts rows by their words, most significant first, compared as unsigned integers: a least significant digit
283
+ * radix sort with 16-bit digits, which keeps the order of `rows` among rows with the same words.
284
+ */
285
+ function radixSort(rows: Uint32Array, words: readonly Uint32Array[]): Uint32Array {
286
+ const length = rows.length;
287
+ let order = rows.slice();
288
+ if (length < 2) return order;
289
+ let spare = new Uint32Array(length);
290
+ const counts = new Uint32Array(65_536);
291
+ for (let index = words.length - 1; index >= 0; index--) {
292
+ const word = words[index];
293
+ for (let shift = 0; shift < 32; shift += 16) {
294
+ counts.fill(0);
295
+ for (let at = 0; at < length; at++) counts[(word[order[at]] >>> shift) & 0xffff]++;
296
+ // A digit that every row shares changes nothing.
297
+ if (counts[(word[order[0]] >>> shift) & 0xffff] === length) continue;
298
+ let total = 0;
299
+ for (let digit = 0; digit < 65_536; digit++) {
300
+ const count = counts[digit];
301
+ counts[digit] = total;
302
+ total += count;
303
+ }
304
+ for (let at = 0; at < length; at++) {
305
+ const row = order[at];
306
+ spare[counts[(word[row] >>> shift) & 0xffff]++] = row;
307
+ }
308
+ [order, spare] = [spare, order];
309
+ }
310
+ }
311
+ return order;
312
+ }
313
+
314
+ /**
315
+ * Ranks a column of fixed width through its words: written by `write` from the numbers that hold its values
316
+ * when it is given (for an Arrow table's column), otherwise from their JSON.
317
+ */
318
+ function rankWords(column: Column, values: Values, write: NumberWriter | null): ColumnRanks {
319
+ const codec = codecOf(column);
320
+ const count = values.length;
321
+ const words = Array.from({ length: codec.width }, () => new Uint32Array(count));
322
+ const filled = new Uint32Array(count);
323
+ let present = 0;
324
+ // No closure here reads `values`, which `rankOf` would keep with the scope it shares.
325
+ const validity = values.validity ?? null;
326
+ if (write) {
327
+ for (let row = 0; row < count; row++) {
328
+ if (validity !== null && ((validity[row >> 3] >> (row & 7)) & 1) === 0) continue;
329
+ write(row, words, row);
330
+ filled[present++] = row;
331
+ }
332
+ } else {
333
+ for (let row = 0; row < count; row++) {
334
+ const value = values.at(row);
335
+ if (value === null) continue;
336
+ codec.write(value, words, row);
337
+ filled[present++] = row;
338
+ }
339
+ }
340
+ const order = radixSort(filled.subarray(0, present), words);
341
+ const ranks = new Uint32Array(count);
342
+ // The words of each distinct value, in ascending order, for rankOf.
343
+ const distinctWords = words.map(() => new Uint32Array(present));
344
+ let distinct = 0;
345
+ for (let at = 0; at < present; at++) {
346
+ const row = order[at];
347
+ const previous = at > 0 ? order[at - 1] : -1;
348
+ if (previous < 0 || words.some((word) => word[row] !== word[previous])) {
349
+ for (let which = 0; which < words.length; which++) distinctWords[which][distinct] = words[which][row];
350
+ distinct++;
351
+ }
352
+ ranks[row] = distinct - 1;
353
+ }
354
+ if (present < count) {
355
+ for (let row = 0; row < count; row++) {
356
+ const blank = write
357
+ ? validity !== null && ((validity[row >> 3] >> (row & 7)) & 1) === 0
358
+ : values.at(row) === null;
359
+ if (blank) ranks[row] = distinct + 1;
360
+ }
361
+ }
362
+ const key = words.map(() => new Uint32Array(1));
363
+ return {
364
+ ranks,
365
+ distinct,
366
+ rankOf(value) {
367
+ const accepted = value === null ? null : codec.accept(value);
368
+ if (accepted === null) return -1;
369
+ codec.write(accepted, key, 0);
370
+ let low = 0;
371
+ let high = distinct - 1;
372
+ while (low <= high) {
373
+ const middle = (low + high) >>> 1;
374
+ let difference = 0;
375
+ for (let which = 0; which < key.length && difference === 0; which++) {
376
+ difference = distinctWords[which][middle] - key[which][0];
377
+ }
378
+ if (difference === 0) return middle;
379
+ if (difference < 0) low = middle + 1;
380
+ else high = middle - 1;
381
+ }
382
+ return -1;
383
+ },
384
+ };
385
+ }
386
+
387
+ const SURROGATE = /[\uD800-\uDFFF]/;
388
+ const HIGH_UNITS = /[\uD800-￿]/g;
389
+
390
+ /**
391
+ * The string with its UTF-16 units from U+D800 moved so that comparing units compares code points (§3.1):
392
+ * units from U+E000 go down by 0x800 and surrogates up by 0x2000, after every other unit. For Unicode text
393
+ * (surrogates in pairs), the first unit that differs then decides as the code points do.
394
+ */
395
+ function codePointKey(text: string): string {
396
+ return text.replace(HIGH_UNITS, (unit) => {
397
+ const code = unit.charCodeAt(0);
398
+ return String.fromCharCode(code >= 0xe000 ? code - 0x800 : code + 0x2000);
399
+ });
400
+ }
401
+
402
+ /** Bytes as a string of one unit per byte, which compares as the bytes do. */
403
+ function byteString(bytes: Uint8Array): string {
404
+ let text = "";
405
+ for (let start = 0; start < bytes.length; start += 0x8000) {
406
+ text += String.fromCharCode(...bytes.subarray(start, start + 0x8000));
407
+ }
408
+ return text;
409
+ }
410
+
411
+ /** Text is ranked in steps of this many rows (a power of two), between which `rankColumnPausing` pauses. */
412
+ const STEP_ROWS = 32_768;
413
+
414
+ /**
415
+ * Rows sorted by their keys. A function of its own, so that the comparison's closure does not keep the keys of every
416
+ * row with `rankOf`, which shares the scope of the function that ranks.
417
+ */
418
+ function sortRun(rows: Uint32Array, keys: string[]): void {
419
+ rows.sort((a, b) => {
420
+ const x = keys[a];
421
+ const y = keys[b];
422
+ return x < y ? -1 : x > y ? 1 : 0;
423
+ });
424
+ }
425
+
426
+ /** Rows sorted by their keys in steps: runs of `STEP_ROWS` rows sorted, then merged. */
427
+ function* sortByText(rows: Uint32Array, keys: string[]): Generator<void, Uint32Array> {
428
+ const count = rows.length;
429
+ for (let start = 0; start < count; start += STEP_ROWS) {
430
+ sortRun(rows.subarray(start, start + STEP_ROWS), keys);
431
+ yield;
432
+ }
433
+ let from: Uint32Array = rows;
434
+ let to: Uint32Array = new Uint32Array(count);
435
+ for (let width = STEP_ROWS; width < count; width *= 2) {
436
+ for (let start = 0; start < count; start += 2 * width) {
437
+ const middle = Math.min(start + width, count);
438
+ const end = Math.min(middle + width, count);
439
+ let left = start;
440
+ let right = middle;
441
+ let at = start;
442
+ while (left < middle && right < end) {
443
+ to[at++] = keys[from[right]] < keys[from[left]] ? from[right++] : from[left++];
444
+ if ((at & (STEP_ROWS - 1)) === 0) yield;
445
+ }
446
+ to.set(from.subarray(left, middle), at);
447
+ to.set(from.subarray(right, end), at + middle - left);
448
+ }
449
+ [from, to] = [to, from];
450
+ }
451
+ return from;
452
+ }
453
+
454
+ /** Ranks a column of strings by their code points, or of binary values by their bytes, in steps. */
455
+ function* rankTexts(column: Column, values: Values): Generator<void, ColumnRanks> {
456
+ const strings = column.type === "string";
457
+ const count = values.length;
458
+ // Each value is read once: the rows that have no key are null, or empty strings marked in `blank`.
459
+ const keys: string[] = new Array(count);
460
+ const blank = new Uint8Array(count);
461
+ const filled = new Uint32Array(count);
462
+ let present = 0;
463
+ let empty = false;
464
+ let surrogates = false;
465
+ for (let row = 0; row < count; row++) {
466
+ if ((row & (STEP_ROWS - 1)) === STEP_ROWS - 1) yield;
467
+ const value = values.at(row);
468
+ if (value === null) continue;
469
+ if (strings && value === "") {
470
+ empty = true;
471
+ blank[row] = 1;
472
+ continue;
473
+ }
474
+ const text = value as string;
475
+ if (strings && !surrogates) surrogates = SURROGATE.test(text);
476
+ keys[row] = strings ? text : byteString(fromBase64(text));
477
+ filled[present++] = row;
478
+ }
479
+ // Without surrogates, comparing UTF-16 units already compares code points.
480
+ if (surrogates) {
481
+ for (let at = 0; at < present; at++) keys[filled[at]] = codePointKey(keys[filled[at]]);
482
+ }
483
+ const order = yield* sortByText(filled.subarray(0, present), keys);
484
+ const ranks = new Uint32Array(count);
485
+ const distinctKeys: string[] = [];
486
+ for (let at = 0; at < present; at++) {
487
+ if ((at & (STEP_ROWS - 1)) === STEP_ROWS - 1) yield;
488
+ const key = keys[order[at]];
489
+ if (at === 0 || key !== distinctKeys[distinctKeys.length - 1]) distinctKeys.push(key);
490
+ ranks[order[at]] = distinctKeys.length - 1;
491
+ }
492
+ const distinct = distinctKeys.length;
493
+ if (present < count) {
494
+ for (let row = 0; row < count; row++)
495
+ if (keys[row] === undefined) ranks[row] = blank[row] ? distinct : distinct + 1;
496
+ }
497
+ return {
498
+ ranks,
499
+ distinct,
500
+ rankOf(value) {
501
+ if (typeof value !== "string" || !isValueOf(value, column)) return -1;
502
+ if (strings && value === "") return empty ? distinct : -1;
503
+ let key = strings ? value : byteString(fromBase64(value));
504
+ if (surrogates) key = codePointKey(key);
505
+ let low = 0;
506
+ let high = distinct - 1;
507
+ while (low <= high) {
508
+ const middle = (low + high) >>> 1;
509
+ const other = distinctKeys[middle];
510
+ if (other === key) return middle;
511
+ if (other < key) low = middle + 1;
512
+ else high = middle - 1;
513
+ }
514
+ return -1;
515
+ },
516
+ };
517
+ }
518
+
519
+ /**
520
+ * Ranks the values of a column by row ID in the order of §3.1; blanks rank after the other values (§3.2). A column
521
+ * of fixed width that has numbers (`Vector.numbers`) is ranked from them by `write`, when it is given.
522
+ */
523
+ export function rankColumn(column: Column, values: Values, write: NumberWriter | null = null): ColumnRanks {
524
+ if (column.type !== "string" && column.type !== "binary") return rankWords(column, values, write);
525
+ const steps = rankTexts(column, values);
526
+ for (;;) {
527
+ const step = steps.next();
528
+ if (step.done) return step.value;
529
+ }
530
+ }
531
+
532
+ /**
533
+ * `rankColumn`, which calls `pause` between the steps of ranking text, where a million rows take about half a
534
+ * second: a request that ranks lets other messages in, and stops where `pause` throws.
535
+ */
536
+ export async function rankColumnPausing(
537
+ column: Column,
538
+ values: Values,
539
+ write: NumberWriter | null,
540
+ pause: () => Promise<void>,
541
+ ): Promise<ColumnRanks> {
542
+ if (column.type !== "string" && column.type !== "binary") return rankWords(column, values, write);
543
+ const steps = rankTexts(column, values);
544
+ for (;;) {
545
+ const step = steps.next();
546
+ if (step.done) return step.value;
547
+ await pause();
548
+ }
549
+ }
550
+
551
+ /** A sort key of a view: the ranks of its column and its direction. */
552
+ export interface RankedKey {
553
+ ranks: ColumnRanks;
554
+ descending: boolean;
555
+ }
556
+
557
+ /**
558
+ * Sorts rows by one key, keeping the order of `order` (row ID order when null) among rows that are equal for
559
+ * it: a counting sort whose buckets hold the values in the key's direction, then the blanks (§3.3).
560
+ */
561
+ function sortByKey(order: Uint32Array | null, count: number, key: RankedKey): Uint32Array {
562
+ const { ranks, distinct } = key.ranks;
563
+ const last = distinct - 1;
564
+ const descending = key.descending;
565
+ const starts = new Uint32Array(distinct + 2);
566
+ for (let at = 0; at < count; at++) {
567
+ const rank = ranks[order ? order[at] : at];
568
+ starts[(rank > last ? distinct : descending ? last - rank : rank) + 1]++;
569
+ }
570
+ for (let bucket = 1; bucket <= distinct + 1; bucket++) starts[bucket] += starts[bucket - 1];
571
+ const sorted = new Uint32Array(count);
572
+ for (let at = 0; at < count; at++) {
573
+ const row = order ? order[at] : at;
574
+ const rank = ranks[row];
575
+ sorted[starts[rank > last ? distinct : descending ? last - rank : rank]++] = row;
576
+ }
577
+ return sorted;
578
+ }
579
+
580
+ /**
581
+ * The row IDs of `count` rows in the order of the keys (§3.3): by the first key, ties by the next, and rows
582
+ * equal for every key in row ID order. The rows are the row IDs from 0, or those of `rows` (in row ID order)
583
+ * when it is given. Without keys, the rows as they are: null for the row IDs from 0.
584
+ */
585
+ export function sortRows(
586
+ count: number,
587
+ keys: readonly RankedKey[],
588
+ rows: Uint32Array | null = null,
589
+ ): Uint32Array | null {
590
+ let order = rows;
591
+ const length = rows ? rows.length : count;
592
+ for (let index = keys.length - 1; index >= 0; index--) order = sortByKey(order, length, keys[index]);
593
+ return order;
594
+ }