@minnowdb/core 0.6.9 → 0.7.1

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 (103) hide show
  1. package/dist/block-format/block.js +254 -325
  2. package/dist/block-format/checksum.js +30 -52
  3. package/dist/block-format/codecs.js +112 -125
  4. package/dist/block-format/column.js +187 -205
  5. package/dist/block-format/index.js +5 -1
  6. package/dist/block-format/physical.js +371 -387
  7. package/dist/block-format/types.js +4 -1
  8. package/dist/block-format/unicode.js +30 -39
  9. package/dist/date-value.d.ts +5 -0
  10. package/dist/date-value.js +71 -41
  11. package/dist/engine/artifact-cache.js +106 -116
  12. package/dist/engine/batch.js +36 -44
  13. package/dist/engine/buffered-writer.js +156 -158
  14. package/dist/engine/byte-estimates.js +20 -22
  15. package/dist/engine/cache-limits.js +24 -25
  16. package/dist/engine/cancellation.js +5 -3
  17. package/dist/engine/catalog.js +64 -73
  18. package/dist/engine/client.js +715 -780
  19. package/dist/engine/database.js +15952 -18465
  20. package/dist/engine/defaults.js +104 -111
  21. package/dist/engine/errors.js +113 -128
  22. package/dist/engine/fts.js +233 -280
  23. package/dist/engine/group-index.js +287 -321
  24. package/dist/engine/index.js +23 -12
  25. package/dist/engine/join-index.js +177 -186
  26. package/dist/engine/keyed-live.js +168 -173
  27. package/dist/engine/live-api.js +0 -1
  28. package/dist/engine/live-equal.js +32 -37
  29. package/dist/engine/live.js +603 -647
  30. package/dist/engine/memory.js +109 -111
  31. package/dist/engine/optimizer.d.ts +19 -1
  32. package/dist/engine/optimizer.js +2642 -2515
  33. package/dist/engine/point-read.d.ts +4 -2
  34. package/dist/engine/point-read.js +121 -171
  35. package/dist/engine/query-api.js +12 -3
  36. package/dist/engine/query-cache.js +61 -69
  37. package/dist/engine/query.d.ts +45 -0
  38. package/dist/engine/query.js +8506 -9375
  39. package/dist/engine/result-wire.js +221 -252
  40. package/dist/engine/schema-wire.js +97 -112
  41. package/dist/engine/schema.js +1099 -1254
  42. package/dist/engine/sort-keys.js +315 -389
  43. package/dist/engine/sql-domains.d.ts +7 -1
  44. package/dist/engine/sql-domains.js +648 -734
  45. package/dist/engine/sql-driver.js +0 -1
  46. package/dist/engine/sql-functions.d.ts +11 -0
  47. package/dist/engine/sql-functions.js +1092 -0
  48. package/dist/engine/sql-json.js +199 -218
  49. package/dist/engine/sql-semantics.d.ts +25 -4
  50. package/dist/engine/sql-semantics.js +579 -513
  51. package/dist/engine/typed-live.js +271 -293
  52. package/dist/engine/vector.d.ts +7 -1
  53. package/dist/engine/vector.js +4513 -4580
  54. package/dist/engine/worker-host.d.ts +8 -63
  55. package/dist/engine/worker-host.js +28 -1130
  56. package/dist/engine/worker-indexeddb.d.ts +1 -0
  57. package/dist/engine/worker-indexeddb.js +3 -0
  58. package/dist/engine/worker-memory.d.ts +1 -0
  59. package/dist/engine/worker-memory.js +3 -0
  60. package/dist/engine/worker-opfs.d.ts +1 -0
  61. package/dist/engine/worker-opfs.js +3 -0
  62. package/dist/engine/worker-server.d.ts +93 -0
  63. package/dist/engine/worker-server.js +999 -0
  64. package/dist/engine/worker-store-indexeddb.d.ts +2 -0
  65. package/dist/engine/worker-store-indexeddb.js +11 -0
  66. package/dist/engine/worker-store-memory.d.ts +2 -0
  67. package/dist/engine/worker-store-memory.js +6 -0
  68. package/dist/engine/worker-store-opfs.d.ts +2 -0
  69. package/dist/engine/worker-store-opfs.js +9 -0
  70. package/dist/engine/worker.js +0 -12
  71. package/dist/engine/write-block-planner.js +93 -100
  72. package/dist/plan/index.js +15 -16
  73. package/dist/plan/model.d.ts +9 -1
  74. package/dist/plan/model.js +20 -27
  75. package/dist/storage/index.js +0 -12
  76. package/dist/storage/indexeddb.js +11776 -13171
  77. package/dist/storage/memory.js +1198 -1241
  78. package/dist/storage/opfs/files.js +238 -295
  79. package/dist/storage/opfs/index.js +9 -3
  80. package/dist/storage/opfs/leader.js +4386 -4856
  81. package/dist/storage/opfs/rpc.js +223 -259
  82. package/dist/storage/opfs/snapshot-ledger.js +219 -260
  83. package/dist/storage/opfs/store.js +884 -1003
  84. package/dist/storage/persistence.js +59 -74
  85. package/dist/storage/snapshot-stream.js +788 -826
  86. package/dist/storage/snapshot.js +36 -37
  87. package/dist/storage/toolkit/extents.js +444 -520
  88. package/dist/storage/toolkit/index.js +28 -29
  89. package/dist/storage/toolkit/record-core.js +5792 -6377
  90. package/dist/storage/toolkit/sync-file.js +34 -42
  91. package/dist/storage/toolkit/wal.js +92 -127
  92. package/dist/storage/toolkit/wire.js +232 -256
  93. package/dist/storage/types.js +2952 -3228
  94. package/dist/testing/block-store-conformance.js +1556 -1629
  95. package/dist/testing/index.js +310 -291
  96. package/dist/testing/opfs-shim.js +280 -312
  97. package/dist/testing/simulator.js +496 -534
  98. package/dist/testing/sqllogictest.js +425 -365
  99. package/dist/transactions/index.js +1464 -1712
  100. package/dist/worker-protocol/index.js +70 -69
  101. package/package.json +20 -2
  102. package/postgres-feature-profile.json +15 -5
  103. package/sql-feature-matrix.json +252 -2
@@ -1,421 +1,347 @@
1
1
  import { dateMilliseconds } from "../date-value.js";
2
2
  import { compareSqlStrings, compareSqlValues } from "./sql-semantics.js";
3
- /** Unwraps a datetime to the number the comparisons use; every other value passes through. */
4
3
  function comparableSortValue(value) {
5
- return value instanceof Date ? dateMilliseconds(value) : value;
4
+ return value instanceof Date ? dateMilliseconds(value) : value;
6
5
  }
7
- /**
8
- * Extracts one term's values into a comparison-ready column. `valueAt` is called once per row,
9
- * so a caller that reads through a row object or a vector pays that cost once rather than once
10
- * per comparison. A term that mixes types cannot be unboxed; it is read a second time into the
11
- * generic column, whose comparison reports the type error the way it always has.
12
- *
13
- * Strings are always ranked, whatever their cardinality. Measured at 200k rows, ranking wins by
14
- * ten times when six strings repeat and still by three times when nearly every string is
15
- * distinct: the distinct set sorts with the engine's own string sort, the rows then sort as
16
- * numbers, and neither pays the per-comparison dispatch the generic path does.
17
- */
18
- export function buildSortKeyColumn(count, valueAt) {
19
- const numbers = new Float64Array(count);
20
- const nulls = new Uint8Array(count);
21
- let kind;
22
- let ranks;
23
- for (let index = 0; index < count; index += 1) {
24
- const value = comparableSortValue(valueAt(index));
25
- if (value === null || value === undefined) {
26
- nulls[index] = 1;
27
- continue;
28
- }
29
- if (typeof value === "number" && kind !== "boolean" && kind !== "string") {
30
- kind = "number";
31
- numbers[index] = value;
32
- }
33
- else if (typeof value === "boolean" && kind !== "number" && kind !== "string") {
34
- kind = "boolean";
35
- numbers[index] = value ? 1 : 0;
36
- }
37
- else if (typeof value === "string" &&
38
- // NUL-prefixed strings are the engine's lossless internal SQL-domain values
39
- // (NUMERIC, ENUM, COLLATE, and friends). Their physical encoding is deliberately
40
- // stable, not lexicographically ordered, so ranking the encoded strings would make
41
- // ORDER BY disagree with the SQL comparator. Keep those terms on the generic path.
42
- value.charCodeAt(0) !== 0 &&
43
- kind !== "number" &&
44
- kind !== "boolean") {
45
- kind = "string";
46
- ranks ??= new Map();
47
- let rank = ranks.get(value);
48
- if (rank === undefined) {
49
- rank = ranks.size;
50
- ranks.set(value, rank);
51
- }
52
- numbers[index] = rank;
53
- }
54
- else {
55
- return buildGenericSortKeyColumn(count, valueAt);
56
- }
6
+ function buildSortKeyColumn(count, valueAt) {
7
+ const numbers = new Float64Array(count);
8
+ const nulls = new Uint8Array(count);
9
+ let kind;
10
+ let ranks;
11
+ for (let index = 0; index < count; index += 1) {
12
+ const value = comparableSortValue(valueAt(index));
13
+ if (value === null || value === void 0) {
14
+ nulls[index] = 1;
15
+ continue;
57
16
  }
58
- if (ranks !== undefined) {
59
- // Ranks were handed out in first-seen order; renumber them in sorted order so the numeric
60
- // comparison of two ranks is the string comparison of the two strings.
61
- const distinct = [...ranks.keys()].sort(compareSqlStrings);
62
- const sortedRank = new Float64Array(distinct.length);
63
- for (let position = 0; position < distinct.length; position += 1) {
64
- sortedRank[ranks.get(distinct[position] ?? "") ?? 0] = position;
65
- }
66
- for (let index = 0; index < count; index += 1) {
67
- if (nulls[index] === 0)
68
- numbers[index] = sortedRank[numbers[index] ?? 0] ?? 0;
69
- }
17
+ if (typeof value === "number" && kind !== "boolean" && kind !== "string") {
18
+ kind = "number";
19
+ numbers[index] = value;
20
+ } else if (typeof value === "boolean" && kind !== "number" && kind !== "string") {
21
+ kind = "boolean";
22
+ numbers[index] = value ? 1 : 0;
23
+ } else if (typeof value === "string" && value.charCodeAt(0) !== 0 && kind !== "number" && kind !== "boolean") {
24
+ kind = "string";
25
+ ranks ??= /* @__PURE__ */ new Map();
26
+ let rank = ranks.get(value);
27
+ if (rank === void 0) {
28
+ rank = ranks.size;
29
+ ranks.set(value, rank);
30
+ }
31
+ numbers[index] = rank;
32
+ } else {
33
+ return buildGenericSortKeyColumn(count, valueAt);
34
+ }
35
+ }
36
+ if (ranks !== void 0) {
37
+ const distinct = [...ranks.keys()].sort(compareSqlStrings);
38
+ const sortedRank = new Float64Array(distinct.length);
39
+ for (let position = 0; position < distinct.length; position += 1) {
40
+ sortedRank[ranks.get(distinct[position] ?? "") ?? 0] = position;
70
41
  }
71
- return {
72
- compare: (left, right) => {
73
- const leftNull = nulls[left] === 1;
74
- const rightNull = nulls[right] === 1;
75
- if (leftNull || rightNull)
76
- return leftNull && rightNull ? 0 : leftNull ? -1 : 1;
77
- const leftValue = numbers[left] ?? 0;
78
- const rightValue = numbers[right] ?? 0;
79
- if (leftValue === rightValue)
80
- return 0;
81
- // NaN sorts last, as compareSqlValues has it. The typed path has to say so outright,
82
- // because a NaN comparison is false whichever way round it is asked.
83
- if (Number.isNaN(leftValue))
84
- return Number.isNaN(rightValue) ? 0 : 1;
85
- if (Number.isNaN(rightValue))
86
- return -1;
87
- return leftValue < rightValue ? -1 : 1;
88
- },
89
- isNull: (index) => nulls[index] === 1,
90
- numbers,
91
- nulls,
92
- };
93
- }
94
- function buildGenericSortKeyColumn(count, valueAt) {
95
- const values = new Array(count);
96
42
  for (let index = 0; index < count; index += 1) {
97
- values[index] = comparableSortValue(valueAt(index));
43
+ if (nulls[index] === 0)
44
+ numbers[index] = sortedRank[numbers[index] ?? 0] ?? 0;
98
45
  }
99
- return {
100
- compare: (left, right) => compareSqlValues(values[left], values[right]),
101
- isNull: (index) => {
102
- const value = values[index];
103
- return value === null || value === undefined;
104
- },
105
- numbers: undefined,
106
- nulls: undefined,
107
- };
46
+ }
47
+ return {
48
+ compare: (left, right) => {
49
+ const leftNull = nulls[left] === 1;
50
+ const rightNull = nulls[right] === 1;
51
+ if (leftNull || rightNull)
52
+ return leftNull && rightNull ? 0 : leftNull ? -1 : 1;
53
+ const leftValue = numbers[left] ?? 0;
54
+ const rightValue = numbers[right] ?? 0;
55
+ if (leftValue === rightValue)
56
+ return 0;
57
+ if (Number.isNaN(leftValue))
58
+ return Number.isNaN(rightValue) ? 0 : 1;
59
+ if (Number.isNaN(rightValue))
60
+ return -1;
61
+ return leftValue < rightValue ? -1 : 1;
62
+ },
63
+ isNull: (index) => nulls[index] === 1,
64
+ numbers,
65
+ nulls
66
+ };
108
67
  }
109
- /**
110
- * A stable sort of `count` rows by the given terms, returned as the permutation of row indexes
111
- * in sorted order. Equal rows keep their input order, which is what makes ORDER BY
112
- * deterministic and what lets a bounded top-N decide ties by arrival.
113
- *
114
- * Each term is sorted in turn: the first over every row, then each later term only within the
115
- * runs of rows the earlier terms left tied. A term with unboxed keys is encoded once into
116
- * 64-bit integers whose unsigned order is the term's order — direction and NULL placement
117
- * folded in — and large ranges are radix-sorted on those bits while small ones merge. A term
118
- * without unboxed keys merges under the generic comparison. Both kernels check for input that
119
- * is already in order before doing anything else, since a scan often arrives sorted.
120
- */
121
- export function sortKeyIndexes(count, terms) {
122
- if (count > 0xffffffff)
123
- throw new RangeError("Too many rows to order");
124
- const indexes = new Uint32Array(count);
125
- for (let index = 0; index < count; index += 1)
126
- indexes[index] = index;
127
- if (count < 2 || terms.length === 0)
128
- return indexes;
129
- const sorter = new KeySorter(count, terms);
130
- sorter.sortRange(indexes, 0, count, 0);
68
+ function buildGenericSortKeyColumn(count, valueAt) {
69
+ const values = new Array(count);
70
+ for (let index = 0; index < count; index += 1) {
71
+ values[index] = comparableSortValue(valueAt(index));
72
+ }
73
+ return {
74
+ compare: (left, right) => compareSqlValues(values[left], values[right]),
75
+ isNull: (index) => {
76
+ const value = values[index];
77
+ return value === null || value === void 0;
78
+ },
79
+ numbers: void 0,
80
+ nulls: void 0
81
+ };
82
+ }
83
+ function sortKeyIndexes(count, terms) {
84
+ if (count > 4294967295)
85
+ throw new RangeError("Too many rows to order");
86
+ const indexes = new Uint32Array(count);
87
+ for (let index = 0; index < count; index += 1)
88
+ indexes[index] = index;
89
+ if (count < 2 || terms.length === 0)
131
90
  return indexes;
91
+ const sorter = new KeySorter(count, terms);
92
+ sorter.sortRange(indexes, 0, count, 0);
93
+ return indexes;
132
94
  }
133
- /** Ranges at least this long radix-sort; shorter ones merge, where the constant factor wins. */
134
95
  const RADIX_MIN_ROWS = 2048;
135
96
  const RADIX_DIGIT_BITS = 11;
136
97
  const RADIX_BUCKETS = 1 << RADIX_DIGIT_BITS;
137
- /** Ranges at most this long insertion-sort, which is what a merge bottoms out in anyway. */
138
98
  const INSERTION_MAX_ROWS = 16;
139
- const UINT32_MAX = 0xffffffff;
99
+ const UINT32_MAX = 4294967295;
140
100
  const LITTLE_ENDIAN = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1;
141
101
  const HI = LITTLE_ENDIAN ? 1 : 0;
142
102
  const LO = 1 - HI;
143
103
  class KeySorter {
144
- #count;
145
- #terms;
146
- #scratch;
147
- /** Per term, lazily: the encoded 64-bit keys as (lo, hi) word pairs, when the term has them. */
148
- #encoded;
149
- #comparators;
150
- #counts;
151
- constructor(count, terms) {
152
- this.#count = count;
153
- this.#terms = terms;
154
- this.#scratch = new Uint32Array(count);
155
- this.#encoded = new Array(terms.length);
156
- this.#comparators = new Array(terms.length);
157
- }
158
- /** Sorts `indexes[start, end)` by term `term` and every term after it. */
159
- sortRange(indexes, start, end, term) {
160
- const spec = this.#terms[term];
161
- if (spec === undefined || end - start < 2)
162
- return;
163
- const encoded = this.#encodedKeys(term);
164
- if (encoded === undefined) {
165
- const compare = this.#comparator(term);
166
- this.#mergeSort(indexes, start, end, compare);
167
- if (term + 1 < this.#terms.length) {
168
- let runStart = start;
169
- for (let index = start + 1; index <= end; index += 1) {
170
- if (index < end && compare(indexes[index - 1] ?? 0, indexes[index] ?? 0) === 0)
171
- continue;
172
- if (index - runStart > 1)
173
- this.sortRange(indexes, runStart, index, term + 1);
174
- runStart = index;
175
- }
176
- }
177
- return;
178
- }
179
- this.#sortEncoded(indexes, start, end, encoded, term);
180
- if (term + 1 < this.#terms.length) {
181
- let runStart = start;
182
- let previous = indexes[start] ?? 0;
183
- for (let index = start + 1; index <= end; index += 1) {
184
- const current = indexes[index] ?? 0;
185
- if (index < end &&
186
- encoded[current * 2 + HI] === encoded[previous * 2 + HI] &&
187
- encoded[current * 2 + LO] === encoded[previous * 2 + LO]) {
188
- previous = current;
189
- continue;
190
- }
191
- if (index - runStart > 1)
192
- this.sortRange(indexes, runStart, index, term + 1);
193
- runStart = index;
194
- previous = current;
195
- }
104
+ #count;
105
+ #terms;
106
+ #scratch;
107
+ #encoded;
108
+ #comparators;
109
+ #counts;
110
+ constructor(count, terms) {
111
+ this.#count = count;
112
+ this.#terms = terms;
113
+ this.#scratch = new Uint32Array(count);
114
+ this.#encoded = new Array(terms.length);
115
+ this.#comparators = new Array(terms.length);
116
+ }
117
+ sortRange(indexes, start, end, term) {
118
+ const spec = this.#terms[term];
119
+ if (spec === void 0 || end - start < 2)
120
+ return;
121
+ const encoded = this.#encodedKeys(term);
122
+ if (encoded === void 0) {
123
+ const compare = this.#comparator(term);
124
+ this.#mergeSort(indexes, start, end, compare);
125
+ if (term + 1 < this.#terms.length) {
126
+ let runStart = start;
127
+ for (let index = start + 1; index <= end; index += 1) {
128
+ if (index < end && compare(indexes[index - 1] ?? 0, indexes[index] ?? 0) === 0)
129
+ continue;
130
+ if (index - runStart > 1)
131
+ this.sortRange(indexes, runStart, index, term + 1);
132
+ runStart = index;
196
133
  }
134
+ }
135
+ return;
197
136
  }
198
- #sortEncoded(indexes, start, end, encoded, term) {
199
- const length = end - start;
200
- if (length < RADIX_MIN_ROWS) {
201
- this.#mergeSort(indexes, start, end, this.#comparator(term));
202
- return;
203
- }
204
- // One pass reads everything the radix needs to know: whether the range is already in
205
- // order, in which case there is nothing to do, and which bits vary across it, so the passes
206
- // over constant digits — most of them, for integers and short decimals — are skipped.
207
- let orLo = 0;
208
- let andLo = UINT32_MAX;
209
- let orHi = 0;
210
- let andHi = UINT32_MAX;
211
- let ordered = true;
212
- let previousLo = 0;
213
- let previousHi = 0;
214
- for (let index = start; index < end; index += 1) {
215
- const row = (indexes[index] ?? 0) * 2;
216
- const lo = encoded[row + LO] ?? 0;
217
- const hi = encoded[row + HI] ?? 0;
218
- orLo |= lo;
219
- andLo &= lo;
220
- orHi |= hi;
221
- andHi &= hi;
222
- if (ordered && index > start && (hi < previousHi || (hi === previousHi && lo < previousLo))) {
223
- ordered = false;
224
- }
225
- previousLo = lo;
226
- previousHi = hi;
137
+ this.#sortEncoded(indexes, start, end, encoded, term);
138
+ if (term + 1 < this.#terms.length) {
139
+ let runStart = start;
140
+ let previous = indexes[start] ?? 0;
141
+ for (let index = start + 1; index <= end; index += 1) {
142
+ const current = indexes[index] ?? 0;
143
+ if (index < end && encoded[current * 2 + HI] === encoded[previous * 2 + HI] && encoded[current * 2 + LO] === encoded[previous * 2 + LO]) {
144
+ previous = current;
145
+ continue;
227
146
  }
228
- if (ordered)
229
- return;
230
- const varying = [(orLo ^ andLo) >>> 0, (orHi ^ andHi) >>> 0];
231
- const counts = (this.#counts ??= new Uint32Array(RADIX_BUCKETS));
232
- let source = indexes;
233
- let target = this.#scratch;
234
- for (let pass = 0; pass < 6; pass += 1) {
235
- const word = pass < 3 ? LO : HI;
236
- const shift = (pass % 3) * RADIX_DIGIT_BITS;
237
- const mask = pass % 3 === 2 ? (1 << (32 - 2 * RADIX_DIGIT_BITS)) - 1 : RADIX_BUCKETS - 1;
238
- if (((varying[pass < 3 ? 0 : 1] ?? 0) & (mask << shift)) === 0)
239
- continue;
240
- counts.fill(0);
241
- for (let index = start; index < end; index += 1) {
242
- const digit = ((encoded[(source[index] ?? 0) * 2 + word] ?? 0) >>> shift) & mask;
243
- counts[digit] = (counts[digit] ?? 0) + 1;
244
- }
245
- let offset = start;
246
- for (let digit = 0; digit <= mask; digit += 1) {
247
- const bucket = counts[digit] ?? 0;
248
- counts[digit] = offset;
249
- offset += bucket;
250
- }
251
- for (let index = start; index < end; index += 1) {
252
- const row = source[index] ?? 0;
253
- const digit = ((encoded[row * 2 + word] ?? 0) >>> shift) & mask;
254
- const slot = counts[digit] ?? 0;
255
- target[slot] = row;
256
- counts[digit] = slot + 1;
257
- }
258
- const held = source;
259
- source = target;
260
- target = held;
147
+ if (index - runStart > 1)
148
+ this.sortRange(indexes, runStart, index, term + 1);
149
+ runStart = index;
150
+ previous = current;
151
+ }
152
+ }
153
+ }
154
+ #sortEncoded(indexes, start, end, encoded, term) {
155
+ const length = end - start;
156
+ if (length < RADIX_MIN_ROWS) {
157
+ this.#mergeSort(indexes, start, end, this.#comparator(term));
158
+ return;
159
+ }
160
+ let orLo = 0;
161
+ let andLo = UINT32_MAX;
162
+ let orHi = 0;
163
+ let andHi = UINT32_MAX;
164
+ let ordered = true;
165
+ let previousLo = 0;
166
+ let previousHi = 0;
167
+ for (let index = start; index < end; index += 1) {
168
+ const row = (indexes[index] ?? 0) * 2;
169
+ const lo = encoded[row + LO] ?? 0;
170
+ const hi = encoded[row + HI] ?? 0;
171
+ orLo |= lo;
172
+ andLo &= lo;
173
+ orHi |= hi;
174
+ andHi &= hi;
175
+ if (ordered && index > start && (hi < previousHi || hi === previousHi && lo < previousLo)) {
176
+ ordered = false;
177
+ }
178
+ previousLo = lo;
179
+ previousHi = hi;
180
+ }
181
+ if (ordered)
182
+ return;
183
+ const varying = [(orLo ^ andLo) >>> 0, (orHi ^ andHi) >>> 0];
184
+ const counts = this.#counts ??= new Uint32Array(RADIX_BUCKETS);
185
+ let source = indexes;
186
+ let target = this.#scratch;
187
+ for (let pass = 0; pass < 6; pass += 1) {
188
+ const word = pass < 3 ? LO : HI;
189
+ const shift = pass % 3 * RADIX_DIGIT_BITS;
190
+ const mask = pass % 3 === 2 ? (1 << 32 - 2 * RADIX_DIGIT_BITS) - 1 : RADIX_BUCKETS - 1;
191
+ if (((varying[pass < 3 ? 0 : 1] ?? 0) & mask << shift) === 0)
192
+ continue;
193
+ counts.fill(0);
194
+ for (let index = start; index < end; index += 1) {
195
+ const digit = (encoded[(source[index] ?? 0) * 2 + word] ?? 0) >>> shift & mask;
196
+ counts[digit] = (counts[digit] ?? 0) + 1;
197
+ }
198
+ let offset = start;
199
+ for (let digit = 0; digit <= mask; digit += 1) {
200
+ const bucket = counts[digit] ?? 0;
201
+ counts[digit] = offset;
202
+ offset += bucket;
203
+ }
204
+ for (let index = start; index < end; index += 1) {
205
+ const row = source[index] ?? 0;
206
+ const digit = (encoded[row * 2 + word] ?? 0) >>> shift & mask;
207
+ const slot = counts[digit] ?? 0;
208
+ target[slot] = row;
209
+ counts[digit] = slot + 1;
210
+ }
211
+ const held = source;
212
+ source = target;
213
+ target = held;
214
+ }
215
+ if (source !== indexes)
216
+ indexes.set(source.subarray(start, end), start);
217
+ }
218
+ #mergeSort(indexes, start, end, compare) {
219
+ const length = end - start;
220
+ for (let blockStart = start; blockStart < end; blockStart += INSERTION_MAX_ROWS) {
221
+ const blockEnd = Math.min(blockStart + INSERTION_MAX_ROWS, end);
222
+ for (let index = blockStart + 1; index < blockEnd; index += 1) {
223
+ const row = indexes[index] ?? 0;
224
+ let position = index;
225
+ while (position > blockStart && compare(indexes[position - 1] ?? 0, row) > 0) {
226
+ indexes[position] = indexes[position - 1] ?? 0;
227
+ position -= 1;
261
228
  }
262
- if (source !== indexes)
263
- indexes.set(source.subarray(start, end), start);
229
+ indexes[position] = row;
230
+ }
264
231
  }
265
- /**
266
- * Stable bottom-up merge over `indexes[start, end)`. Blocks begin as insertion-sorted runs,
267
- * and a merge whose halves are already in order — the whole input, when it arrives sorted —
268
- * is a copy rather than a merge.
269
- */
270
- #mergeSort(indexes, start, end, compare) {
271
- const length = end - start;
272
- for (let blockStart = start; blockStart < end; blockStart += INSERTION_MAX_ROWS) {
273
- const blockEnd = Math.min(blockStart + INSERTION_MAX_ROWS, end);
274
- for (let index = blockStart + 1; index < blockEnd; index += 1) {
275
- const row = indexes[index] ?? 0;
276
- let position = index;
277
- while (position > blockStart && compare(indexes[position - 1] ?? 0, row) > 0) {
278
- indexes[position] = indexes[position - 1] ?? 0;
279
- position -= 1;
280
- }
281
- indexes[position] = row;
282
- }
232
+ if (length <= INSERTION_MAX_ROWS)
233
+ return;
234
+ const scratch = this.#scratch;
235
+ let source = indexes;
236
+ let target = scratch;
237
+ for (let width = INSERTION_MAX_ROWS; width < length; width *= 2) {
238
+ for (let left = start; left < end; left += width * 2) {
239
+ const middle = Math.min(left + width, end);
240
+ const right = Math.min(left + width * 2, end);
241
+ if (middle >= right || compare(source[middle - 1] ?? 0, source[middle] ?? 0) <= 0) {
242
+ target.set(source.subarray(left, right), left);
243
+ continue;
283
244
  }
284
- if (length <= INSERTION_MAX_ROWS)
285
- return;
286
- const scratch = this.#scratch;
287
- let source = indexes;
288
- let target = scratch;
289
- for (let width = INSERTION_MAX_ROWS; width < length; width *= 2) {
290
- for (let left = start; left < end; left += width * 2) {
291
- const middle = Math.min(left + width, end);
292
- const right = Math.min(left + width * 2, end);
293
- if (middle >= right || compare(source[middle - 1] ?? 0, source[middle] ?? 0) <= 0) {
294
- target.set(source.subarray(left, right), left);
295
- continue;
296
- }
297
- let leftIndex = left;
298
- let rightIndex = middle;
299
- for (let output = left; output < right; output += 1) {
300
- if (rightIndex >= right ||
301
- (leftIndex < middle && compare(source[leftIndex] ?? 0, source[rightIndex] ?? 0) <= 0)) {
302
- target[output] = source[leftIndex] ?? 0;
303
- leftIndex += 1;
304
- }
305
- else {
306
- target[output] = source[rightIndex] ?? 0;
307
- rightIndex += 1;
308
- }
309
- }
310
- }
311
- const held = source;
312
- source = target;
313
- target = held;
245
+ let leftIndex = left;
246
+ let rightIndex = middle;
247
+ for (let output = left; output < right; output += 1) {
248
+ if (rightIndex >= right || leftIndex < middle && compare(source[leftIndex] ?? 0, source[rightIndex] ?? 0) <= 0) {
249
+ target[output] = source[leftIndex] ?? 0;
250
+ leftIndex += 1;
251
+ } else {
252
+ target[output] = source[rightIndex] ?? 0;
253
+ rightIndex += 1;
254
+ }
314
255
  }
315
- if (source !== indexes)
316
- indexes.set(source.subarray(start, end), start);
256
+ }
257
+ const held = source;
258
+ source = target;
259
+ target = held;
317
260
  }
318
- /**
319
- * The term's keys as 64-bit unsigned integers whose order is the term's order, or undefined
320
- * for a term without unboxed keys. A float's bits already order like the float once the sign
321
- * is folded (flip every bit of a negative, the sign bit of a positive); NaN is pinned just
322
- * past +Infinity and signed zero collapses to +0, which is where compareSqlValues puts them.
323
- * DESC inverts the non-null keys, and NULL takes the end its placement names. When omitted,
324
- * PostgreSQL's default supplies NULLS LAST for ASC and NULLS FIRST for DESC.
325
- */
326
- #encodedKeys(term) {
327
- const cached = this.#encoded[term];
328
- if (cached !== undefined)
329
- return cached;
330
- const spec = this.#terms[term];
331
- const numbers = spec?.column.numbers;
332
- const nulls = spec?.column.nulls;
333
- if (spec === undefined || numbers === undefined || nulls === undefined)
334
- return undefined;
335
- const count = this.#count;
336
- const buffer = new ArrayBuffer(count * 8);
337
- const floats = new Float64Array(buffer);
338
- const words = new Uint32Array(buffer);
339
- const nullsFirst = (spec.nulls ?? (spec.descending ? "first" : "last")) === "first";
340
- const flip = spec.descending ? UINT32_MAX : 0;
341
- // Keep the sentinel's low word aligned with the direction's transformed values. A sentinel
342
- // that differs in an otherwise constant low digit makes radix sorting run a whole extra pass.
343
- // The non-null domains are bounded away from these keys: ascending runs from encoded
344
- // -Infinity through pinned NaN (fff80000:00000000), and descending is its bitwise inverse.
345
- const nullHi = spec.descending
346
- ? nullsFirst
347
- ? 0x0007fffe
348
- : UINT32_MAX
349
- : nullsFirst
350
- ? 0
351
- : 0xfff80001;
352
- const nullLo = spec.descending ? UINT32_MAX : 0;
353
- for (let index = 0; index < count; index += 1) {
354
- const lo = index * 2 + LO;
355
- const hi = index * 2 + HI;
356
- if (nulls[index] === 1) {
357
- words[lo] = nullLo;
358
- words[hi] = nullHi;
359
- continue;
360
- }
361
- const value = numbers[index] ?? 0;
362
- if (value !== value) {
363
- words[lo] = flip;
364
- words[hi] = (0xfff80000 ^ flip) >>> 0;
365
- }
366
- else {
367
- // Adding +0 turns -0 into +0 and leaves every other value alone.
368
- floats[index] = value + 0;
369
- const rawHi = words[hi] ?? 0;
370
- if ((rawHi & 0x80000000) !== 0) {
371
- words[lo] = (~(words[lo] ?? 0) ^ flip) >>> 0;
372
- words[hi] = (~rawHi ^ flip) >>> 0;
373
- }
374
- else {
375
- words[lo] = ((words[lo] ?? 0) ^ flip) >>> 0;
376
- words[hi] = ((rawHi | 0x80000000) ^ flip) >>> 0;
377
- }
378
- }
261
+ if (source !== indexes)
262
+ indexes.set(source.subarray(start, end), start);
263
+ }
264
+ #encodedKeys(term) {
265
+ const cached = this.#encoded[term];
266
+ if (cached !== void 0)
267
+ return cached;
268
+ const spec = this.#terms[term];
269
+ const numbers = spec?.column.numbers;
270
+ const nulls = spec?.column.nulls;
271
+ if (spec === void 0 || numbers === void 0 || nulls === void 0)
272
+ return void 0;
273
+ const count = this.#count;
274
+ const buffer = new ArrayBuffer(count * 8);
275
+ const floats = new Float64Array(buffer);
276
+ const words = new Uint32Array(buffer);
277
+ const nullsFirst = (spec.nulls ?? (spec.descending ? "first" : "last")) === "first";
278
+ const flip = spec.descending ? UINT32_MAX : 0;
279
+ const nullHi = spec.descending ? nullsFirst ? 524286 : UINT32_MAX : nullsFirst ? 0 : 4294443009;
280
+ const nullLo = spec.descending ? UINT32_MAX : 0;
281
+ for (let index = 0; index < count; index += 1) {
282
+ const lo = index * 2 + LO;
283
+ const hi = index * 2 + HI;
284
+ if (nulls[index] === 1) {
285
+ words[lo] = nullLo;
286
+ words[hi] = nullHi;
287
+ continue;
288
+ }
289
+ const value = numbers[index] ?? 0;
290
+ if (value !== value) {
291
+ words[lo] = flip;
292
+ words[hi] = (4294443008 ^ flip) >>> 0;
293
+ } else {
294
+ floats[index] = value + 0;
295
+ const rawHi = words[hi] ?? 0;
296
+ if ((rawHi & 2147483648) !== 0) {
297
+ words[lo] = (~(words[lo] ?? 0) ^ flip) >>> 0;
298
+ words[hi] = (~rawHi ^ flip) >>> 0;
299
+ } else {
300
+ words[lo] = ((words[lo] ?? 0) ^ flip) >>> 0;
301
+ words[hi] = ((rawHi | 2147483648) ^ flip) >>> 0;
379
302
  }
380
- this.#encoded[term] = words;
381
- return words;
303
+ }
382
304
  }
383
- /** The term's order as a comparison, for the ranges too small to radix and the generic terms. */
384
- #comparator(term) {
385
- const cached = this.#comparators[term];
386
- if (cached !== undefined)
387
- return cached;
388
- const spec = this.#terms[term];
389
- if (spec === undefined)
390
- throw new Error("Order term is missing");
391
- const encoded = this.#encodedKeys(term);
392
- let compare;
393
- if (encoded !== undefined) {
394
- compare = (left, right) => {
395
- const leftHi = encoded[left * 2 + HI] ?? 0;
396
- const rightHi = encoded[right * 2 + HI] ?? 0;
397
- if (leftHi !== rightHi)
398
- return leftHi < rightHi ? -1 : 1;
399
- const leftLo = encoded[left * 2 + LO] ?? 0;
400
- const rightLo = encoded[right * 2 + LO] ?? 0;
401
- return leftLo === rightLo ? 0 : leftLo < rightLo ? -1 : 1;
402
- };
403
- }
404
- else {
405
- const { column, descending } = spec;
406
- const nulls = spec.nulls ?? (spec.descending ? "first" : "last");
407
- compare = (left, right) => {
408
- // NULL placement is absolute: direction must not negate it.
409
- const leftNull = column.isNull(left);
410
- const rightNull = column.isNull(right);
411
- if (leftNull || rightNull) {
412
- return leftNull && rightNull ? 0 : (leftNull ? -1 : 1) * (nulls === "first" ? 1 : -1);
413
- }
414
- const comparison = column.compare(left, right);
415
- return descending ? -comparison : comparison;
416
- };
305
+ this.#encoded[term] = words;
306
+ return words;
307
+ }
308
+ #comparator(term) {
309
+ const cached = this.#comparators[term];
310
+ if (cached !== void 0)
311
+ return cached;
312
+ const spec = this.#terms[term];
313
+ if (spec === void 0)
314
+ throw new Error("Order term is missing");
315
+ const encoded = this.#encodedKeys(term);
316
+ let compare;
317
+ if (encoded !== void 0) {
318
+ compare = (left, right) => {
319
+ const leftHi = encoded[left * 2 + HI] ?? 0;
320
+ const rightHi = encoded[right * 2 + HI] ?? 0;
321
+ if (leftHi !== rightHi)
322
+ return leftHi < rightHi ? -1 : 1;
323
+ const leftLo = encoded[left * 2 + LO] ?? 0;
324
+ const rightLo = encoded[right * 2 + LO] ?? 0;
325
+ return leftLo === rightLo ? 0 : leftLo < rightLo ? -1 : 1;
326
+ };
327
+ } else {
328
+ const { column, descending } = spec;
329
+ const nulls = spec.nulls ?? (spec.descending ? "first" : "last");
330
+ compare = (left, right) => {
331
+ const leftNull = column.isNull(left);
332
+ const rightNull = column.isNull(right);
333
+ if (leftNull || rightNull) {
334
+ return leftNull && rightNull ? 0 : (leftNull ? -1 : 1) * (nulls === "first" ? 1 : -1);
417
335
  }
418
- this.#comparators[term] = compare;
419
- return compare;
336
+ const comparison = column.compare(left, right);
337
+ return descending ? -comparison : comparison;
338
+ };
420
339
  }
340
+ this.#comparators[term] = compare;
341
+ return compare;
342
+ }
421
343
  }
344
+ export {
345
+ buildSortKeyColumn,
346
+ sortKeyIndexes
347
+ };