@minnowdb/core 0.6.4 → 0.6.6
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/dist/engine/artifact-cache.d.ts +2 -1
- package/dist/engine/batch.d.ts +0 -1
- package/dist/engine/batch.js +1 -1
- package/dist/engine/buffered-writer.js +1 -12
- package/dist/engine/byte-estimates.d.ts +11 -0
- package/dist/engine/byte-estimates.js +25 -0
- package/dist/engine/database.js +78 -38
- package/dist/engine/fts.d.ts +0 -1
- package/dist/engine/fts.js +1 -1
- package/dist/engine/join-index.d.ts +0 -1
- package/dist/engine/optimizer.js +11 -3
- package/dist/engine/point-read.d.ts +1 -1
- package/dist/engine/point-read.js +3 -2
- package/dist/engine/query-cache.js +1 -12
- package/dist/engine/query.d.ts +20 -59
- package/dist/engine/query.js +374 -71
- package/dist/engine/result-wire.d.ts +2 -1
- package/dist/engine/schema.d.ts +18 -2
- package/dist/engine/schema.js +11 -2
- package/dist/engine/sort-keys.d.ts +2 -3
- package/dist/engine/sort-keys.js +1 -1
- package/dist/engine/sql-domains.d.ts +36 -2
- package/dist/engine/sql-domains.js +143 -6
- package/dist/engine/sql-json.d.ts +12 -2
- package/dist/engine/sql-json.js +41 -1
- package/dist/engine/sql-semantics.d.ts +2 -1
- package/dist/engine/vector.d.ts +7 -7
- package/dist/engine/vector.js +8 -4
- package/dist/engine/write-block-planner.d.ts +2 -1
- package/dist/plan/model.d.ts +19 -0
- package/dist/storage/opfs/leader.d.ts +0 -4
- package/dist/storage/opfs/leader.js +2 -2
- package/dist/storage/opfs/snapshot-ledger.d.ts +3 -2
- package/dist/storage/toolkit/index.d.ts +1 -1
- package/dist/storage/toolkit/record-core.d.ts +0 -1
- package/dist/storage/toolkit/record-core.js +0 -7
- package/dist/testing/opfs-shim.d.ts +2 -1
- package/dist/testing/simulator.js +3 -0
- package/dist/worker-protocol/index.d.ts +1 -1
- package/dist/worker-protocol/index.js +0 -1
- package/package.json +2 -1
- package/postgres-feature-profile.json +18 -3
- package/sql-feature-matrix.json +95 -11
|
@@ -13,7 +13,7 @@ import type { QueryResult, QueryRow, QueryValue } from "./query.js";
|
|
|
13
13
|
* a byte mask, and a column name like `__proto__` comes back as the own property the engine
|
|
14
14
|
* defined — never as a prototype assignment.
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
type WireResultColumn = {
|
|
17
17
|
kind: "number";
|
|
18
18
|
values: Float64Array;
|
|
19
19
|
nulls?: Uint8Array;
|
|
@@ -67,3 +67,4 @@ export declare function encodeQueryResult(result: QueryResult): EncodedQueryResu
|
|
|
67
67
|
export declare function encodeQueryRows(rows: readonly QueryRow[]): EncodedQueryResult;
|
|
68
68
|
export declare function decodeQueryResult(payload: unknown): QueryResult;
|
|
69
69
|
export declare function isWireQueryResult(value: unknown): value is WireQueryResult;
|
|
70
|
+
export {};
|
package/dist/engine/schema.d.ts
CHANGED
|
@@ -55,6 +55,17 @@ export declare function foreignKeyName(tableName: string, columnName: string): s
|
|
|
55
55
|
export type HasDefault<TValue> = TValue & {
|
|
56
56
|
readonly __minnowHasDefault?: true;
|
|
57
57
|
};
|
|
58
|
+
/**
|
|
59
|
+
* A JSON column's select value carrying a declared document shape. The value is still JSON
|
|
60
|
+
* text — the brand is an optional phantom property, so plain strings stay assignable in both
|
|
61
|
+
* directions — and adapters read the shape to offer typed traversal. The tuple wrapper keeps
|
|
62
|
+
* `never` and union shapes intact through inference.
|
|
63
|
+
*/
|
|
64
|
+
export type JsonShape<TShape> = string & {
|
|
65
|
+
readonly __minnowJsonShape?: readonly [TShape];
|
|
66
|
+
};
|
|
67
|
+
/** A JSON column without a declared shape selects as exactly `string`, with no brand. */
|
|
68
|
+
type JsonColumnValue<TShape> = [TShape] extends [never] ? string : JsonShape<TShape>;
|
|
58
69
|
export interface ColumnBuilder<TValue extends SchemaValue, TNullable extends boolean, TUnique extends boolean = false, THasDefault extends boolean = false, TInput extends SchemaValue = TValue, TDomain extends SqlDomain["kind"] | undefined = undefined, TGenerated extends boolean = false> {
|
|
59
70
|
readonly kind: "column";
|
|
60
71
|
readonly type: SchemaColumnType;
|
|
@@ -179,8 +190,13 @@ export declare const column: {
|
|
|
179
190
|
precision?: number;
|
|
180
191
|
scale?: number;
|
|
181
192
|
}) => ColumnBuilder<string, false, false, false, string | number, "numeric", false>;
|
|
182
|
-
|
|
183
|
-
|
|
193
|
+
/**
|
|
194
|
+
* JSON text at the JavaScript boundary. Optionally declare the document's shape —
|
|
195
|
+
* `column.json<{ name: string }>()` — for adapters to type `->`/`->>` traversal; the runtime
|
|
196
|
+
* value stays JSON text either way.
|
|
197
|
+
*/
|
|
198
|
+
json: <TShape = never>() => ColumnBuilder<JsonColumnValue<TShape>, false, false, false, string, "json", false>;
|
|
199
|
+
jsonb: <TShape = never>() => ColumnBuilder<JsonColumnValue<TShape>, false, false, false, string, "jsonb", false>;
|
|
184
200
|
uuid: () => ColumnBuilder<string, false, false, false, string, "uuid", false>;
|
|
185
201
|
/** A zoneless calendar date, represented publicly as canonical `YYYY-MM-DD` text. */
|
|
186
202
|
date: () => ColumnBuilder<string, false, false, false, string, "date", false>;
|
package/dist/engine/schema.js
CHANGED
|
@@ -228,8 +228,17 @@ export const column = {
|
|
|
228
228
|
const sqlDomain = validateSqlDomain({ kind: "numeric", ...options }, "numeric column");
|
|
229
229
|
return createColumn("string", { sqlDomain });
|
|
230
230
|
},
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
/**
|
|
232
|
+
* JSON text at the JavaScript boundary. Optionally declare the document's shape —
|
|
233
|
+
* `column.json<{ name: string }>()` — for adapters to type `->`/`->>` traversal; the runtime
|
|
234
|
+
* value stays JSON text either way.
|
|
235
|
+
*/
|
|
236
|
+
json: () => createColumn("string", {
|
|
237
|
+
sqlDomain: { kind: "json" },
|
|
238
|
+
}),
|
|
239
|
+
jsonb: () => createColumn("string", {
|
|
240
|
+
sqlDomain: { kind: "jsonb" },
|
|
241
|
+
}),
|
|
233
242
|
uuid: () => createColumn("string", { sqlDomain: { kind: "uuid" } }),
|
|
234
243
|
/** A zoneless calendar date, represented publicly as canonical `YYYY-MM-DD` text. */
|
|
235
244
|
date: () => createColumn("string", { sqlDomain: { kind: "date" } }),
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* next, so what a multi-term sort pays for its later terms is proportional to how many rows tie
|
|
20
20
|
* on the earlier ones.
|
|
21
21
|
*/
|
|
22
|
-
|
|
22
|
+
interface SortKeyColumn {
|
|
23
23
|
/**
|
|
24
24
|
* Ascending comparison with NULL smallest. This is the column's raw comparison, not SQL's
|
|
25
25
|
* default placement: the sorter applies PostgreSQL's NULLS LAST for ASC and NULLS FIRST for
|
|
@@ -45,8 +45,6 @@ export interface SortKeyTerm {
|
|
|
45
45
|
readonly descending: boolean;
|
|
46
46
|
readonly nulls: "first" | "last" | undefined;
|
|
47
47
|
}
|
|
48
|
-
/** Unwraps a datetime to the number the comparisons use; every other value passes through. */
|
|
49
|
-
export declare function comparableSortValue(value: unknown): unknown;
|
|
50
48
|
/**
|
|
51
49
|
* Extracts one term's values into a comparison-ready column. `valueAt` is called once per row,
|
|
52
50
|
* so a caller that reads through a row object or a vector pays that cost once rather than once
|
|
@@ -72,3 +70,4 @@ export declare function buildSortKeyColumn(count: number, valueAt: (index: numbe
|
|
|
72
70
|
* is already in order before doing anything else, since a scan often arrives sorted.
|
|
73
71
|
*/
|
|
74
72
|
export declare function sortKeyIndexes(count: number, terms: readonly SortKeyTerm[]): Uint32Array;
|
|
73
|
+
export {};
|
package/dist/engine/sort-keys.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { dateMilliseconds } from "../date-value.js";
|
|
2
2
|
import { compareSqlStrings, compareSqlValues } from "./sql-semantics.js";
|
|
3
3
|
/** Unwraps a datetime to the number the comparisons use; every other value passes through. */
|
|
4
|
-
|
|
4
|
+
function comparableSortValue(value) {
|
|
5
5
|
return value instanceof Date ? dateMilliseconds(value) : value;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
@@ -9,7 +9,24 @@ export declare function protectedSqlTextValue(value: string): string;
|
|
|
9
9
|
export declare function externalSqlTextValue(value: unknown): unknown;
|
|
10
10
|
export declare function isExactNumeric(value: unknown): value is string;
|
|
11
11
|
export declare function exactNumericValue(value: unknown, precision?: number, scale?: number): string | null;
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Tags a SQL numeric constant with its exact digits, as written. Unlike `exactNumericValue`
|
|
14
|
+
* this does not canonicalize: trailing fractional zeros are PostgreSQL display scale, and
|
|
15
|
+
* division selects its result scale from the operands' scales, so a literal's digits are part
|
|
16
|
+
* of its meaning. Scientific notation is the exception — PostgreSQL expands the exponent when
|
|
17
|
+
* it parses the literal, so `1.5e2` is `150` and `1e400` is the full digit string, and the
|
|
18
|
+
* display scale comes from that expansion. The text is still validated and bounded.
|
|
19
|
+
*/
|
|
20
|
+
export declare function exactNumericLiteral(text: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* The Float64 this exact numeric value reads back identically from — the nearest float's
|
|
23
|
+
* canonical rendering re-parses to the same decimal value, so `0.1` qualifies while
|
|
24
|
+
* `9007199254740993` does not — or undefined when the trip through a number would visibly
|
|
25
|
+
* round. A value that survives stays an ordinary number so every number-typed path keeps
|
|
26
|
+
* running; only visibly rounded values stay tagged NUMERIC.
|
|
27
|
+
*/
|
|
28
|
+
export declare function exactNumericAsNumber(value: string): number | undefined;
|
|
29
|
+
export declare function exactNumericBinary(operator: "+" | "-" | "*" | "/" | "%", left: unknown, right: unknown, minimumQuotientScale?: number, canonicalize?: boolean): string | null | undefined;
|
|
13
30
|
export declare function exactNumericCompare(left: unknown, right: unknown): number | undefined;
|
|
14
31
|
/**
|
|
15
32
|
* Serializes a JSON value while bounding traversal, nesting, and output before concatenation.
|
|
@@ -33,10 +50,27 @@ export declare function isDateDomainValue(value: unknown): value is string;
|
|
|
33
50
|
export declare function timeDomainValue(value: unknown): string | null;
|
|
34
51
|
export declare function intervalDomainValue(value: unknown): string | null;
|
|
35
52
|
export declare function arrayDomainValue(values: readonly unknown[]): string;
|
|
36
|
-
export declare function enumDomainValue(value: unknown, name: string, values: readonly string[]): string | null;
|
|
37
53
|
export declare function enumDomainCompare(left: unknown, right: unknown): number | undefined;
|
|
38
54
|
export declare function normalizeSqlDomainValue(domain: SqlDomain, value: unknown): string | null;
|
|
39
55
|
export declare function collatedDomainValue(value: unknown, collation: unknown): string | null;
|
|
40
56
|
export declare function collatedDomainCompare(left: unknown, right: unknown): number | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Renders one result value at the JavaScript boundary using its column's logical domain.
|
|
59
|
+
* A NUMERIC column with a declared scale displays PostgreSQL-style at exactly that scale:
|
|
60
|
+
* the physical encoding is canonical (trailing fractional zeros stripped), so the declared
|
|
61
|
+
* scale is restored by padding — never rounding — and a value carrying more fractional
|
|
62
|
+
* digits than the declaration keeps every digit it has. Every other domain, and every value
|
|
63
|
+
* without one, renders exactly as externalSqlDomainValue.
|
|
64
|
+
*/
|
|
65
|
+
export declare function externalSqlDomainColumnValue(value: unknown, domain: SqlDomain | null | undefined): unknown;
|
|
41
66
|
export declare function externalSqlDomainValue(value: unknown): unknown;
|
|
67
|
+
/**
|
|
68
|
+
* SQL `||` over internal string values, shared by both executors and constant folding so the
|
|
69
|
+
* three paths cannot disagree. PostgreSQL resolves `||` to text concatenation only when one
|
|
70
|
+
* side is text: its array and JSONB `||` operators are structural concatenation, which this
|
|
71
|
+
* engine does not implement, and two non-text operands have no `||` operator at all. Refusing
|
|
72
|
+
* those shapes keeps `||` from inventing a text concatenation PostgreSQL does not have —
|
|
73
|
+
* and from ever concatenating internal domain encodings.
|
|
74
|
+
*/
|
|
75
|
+
export declare function concatenatedSqlValue(left: string, right: string): string;
|
|
42
76
|
export declare function isSqlDomainValue(value: unknown): value is string;
|
|
@@ -53,6 +53,9 @@ function decimalParts(value) {
|
|
|
53
53
|
let coefficient = BigInt(digits || "0") * (match[1] === "-" ? -1n : 1n);
|
|
54
54
|
let scale = fraction.length - exponent;
|
|
55
55
|
if (scale < 0) {
|
|
56
|
+
if (-scale > 100_000) {
|
|
57
|
+
throw new RangeError(`NUMERIC exponent is outside the supported range: ${source}`);
|
|
58
|
+
}
|
|
56
59
|
coefficient *= pow10(-scale);
|
|
57
60
|
scale = 0;
|
|
58
61
|
}
|
|
@@ -122,7 +125,79 @@ export function exactNumericValue(value, precision, scale) {
|
|
|
122
125
|
}
|
|
123
126
|
return boundedTaggedDomainValue(NUMERIC, formatDecimal(normalizeDecimal(adjusted)), "NUMERIC value");
|
|
124
127
|
}
|
|
125
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Tags a SQL numeric constant with its exact digits, as written. Unlike `exactNumericValue`
|
|
130
|
+
* this does not canonicalize: trailing fractional zeros are PostgreSQL display scale, and
|
|
131
|
+
* division selects its result scale from the operands' scales, so a literal's digits are part
|
|
132
|
+
* of its meaning. Scientific notation is the exception — PostgreSQL expands the exponent when
|
|
133
|
+
* it parses the literal, so `1.5e2` is `150` and `1e400` is the full digit string, and the
|
|
134
|
+
* display scale comes from that expansion. The text is still validated and bounded.
|
|
135
|
+
*/
|
|
136
|
+
export function exactNumericLiteral(text) {
|
|
137
|
+
const parts = decimalParts(text);
|
|
138
|
+
const plain = /[eE]/.test(text) ? formatDecimal(parts) : text;
|
|
139
|
+
return boundedTaggedDomainValue(NUMERIC, plain, "NUMERIC literal");
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The Float64 this exact numeric value reads back identically from — the nearest float's
|
|
143
|
+
* canonical rendering re-parses to the same decimal value, so `0.1` qualifies while
|
|
144
|
+
* `9007199254740993` does not — or undefined when the trip through a number would visibly
|
|
145
|
+
* round. A value that survives stays an ordinary number so every number-typed path keeps
|
|
146
|
+
* running; only visibly rounded values stay tagged NUMERIC.
|
|
147
|
+
*/
|
|
148
|
+
export function exactNumericAsNumber(value) {
|
|
149
|
+
const parts = taggedDecimalParts(value);
|
|
150
|
+
if (parts === undefined)
|
|
151
|
+
return undefined;
|
|
152
|
+
const candidate = Number(value.slice(NUMERIC.length));
|
|
153
|
+
if (!Number.isFinite(candidate))
|
|
154
|
+
return undefined;
|
|
155
|
+
const roundTrip = normalizeDecimal(decimalParts(String(candidate)));
|
|
156
|
+
const exact = normalizeDecimal(parts);
|
|
157
|
+
return roundTrip.coefficient === exact.coefficient && roundTrip.scale === exact.scale
|
|
158
|
+
? candidate
|
|
159
|
+
: undefined;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The leading base-10000 digit of a decimal, aligned at the decimal point the way PostgreSQL's
|
|
163
|
+
* NUMERIC digit array is: group k covers the decimal digits at 10^(4k)..10^(4k+3). `weight` is
|
|
164
|
+
* the group index of the first nonzero group and `firstDigit` its value (1..9999); zero reports
|
|
165
|
+
* both as 0, matching PostgreSQL's defaults when no nonzero digit exists.
|
|
166
|
+
*/
|
|
167
|
+
function nbaseLeading(parts) {
|
|
168
|
+
const magnitude = parts.coefficient < 0n ? -parts.coefficient : parts.coefficient;
|
|
169
|
+
if (magnitude === 0n)
|
|
170
|
+
return { weight: 0, firstDigit: 0 };
|
|
171
|
+
const fractionGroups = Math.ceil(parts.scale / 4);
|
|
172
|
+
const aligned = magnitude * pow10(fractionGroups * 4 - parts.scale);
|
|
173
|
+
const groups = Math.ceil(aligned.toString().length / 4);
|
|
174
|
+
return {
|
|
175
|
+
weight: groups - 1 - fractionGroups,
|
|
176
|
+
firstDigit: Number(aligned / pow10((groups - 1) * 4)),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* PostgreSQL's `select_div_scale`: the fractional digits a NUMERIC quotient is computed and
|
|
181
|
+
* rounded to. The estimated quotient weight guarantees roughly sixteen significant digits, and
|
|
182
|
+
* the result never displays fewer fractional digits than either operand. `minimumScale` stands
|
|
183
|
+
* in for display scale the canonical encoding cannot carry: PostgreSQL floors this selection at
|
|
184
|
+
* each operand's dscale, which for an AVG over a declared-scale column is that declared scale.
|
|
185
|
+
*/
|
|
186
|
+
function quotientScale(a, b, minimumScale) {
|
|
187
|
+
const leftLeading = nbaseLeading(a);
|
|
188
|
+
const rightLeading = nbaseLeading(b);
|
|
189
|
+
let estimatedWeight = leftLeading.weight - rightLeading.weight;
|
|
190
|
+
if (leftLeading.firstDigit <= rightLeading.firstDigit)
|
|
191
|
+
estimatedWeight -= 1;
|
|
192
|
+
const scale = Math.max(16 - estimatedWeight * 4, a.scale, b.scale, minimumScale, 0);
|
|
193
|
+
return Math.min(scale, 1000);
|
|
194
|
+
}
|
|
195
|
+
export function exactNumericBinary(operator, left, right, minimumQuotientScale = 0,
|
|
196
|
+
// Stored and returned values are canonical (trailing fractional zeros stripped). Constant
|
|
197
|
+
// folding keeps the computed scale instead: PostgreSQL carries display scale through each
|
|
198
|
+
// step — max for +, -, %, the operands' sum for *, the selected scale for / — and a later
|
|
199
|
+
// division selects its own scale from it, so folding must not strip what execution never sees.
|
|
200
|
+
canonicalize = true) {
|
|
126
201
|
const leftTagged = taggedDecimalParts(left);
|
|
127
202
|
const rightTagged = taggedDecimalParts(right);
|
|
128
203
|
if (leftTagged === undefined && rightTagged === undefined)
|
|
@@ -149,9 +224,10 @@ export function exactNumericBinary(operator, left, right) {
|
|
|
149
224
|
};
|
|
150
225
|
}
|
|
151
226
|
else {
|
|
152
|
-
// PostgreSQL NUMERIC division
|
|
153
|
-
//
|
|
154
|
-
|
|
227
|
+
// PostgreSQL NUMERIC division computes to its selected result scale and rounds the final
|
|
228
|
+
// digit half away from zero; matching the scale selection keeps every rendered digit in
|
|
229
|
+
// agreement, deterministically and without ever crossing binary Float64.
|
|
230
|
+
const scale = quotientScale(a, b, minimumQuotientScale);
|
|
155
231
|
const numerator = a.coefficient * pow10(scale + b.scale);
|
|
156
232
|
const denominator = b.coefficient * pow10(a.scale);
|
|
157
233
|
let coefficient = numerator / denominator;
|
|
@@ -162,7 +238,7 @@ export function exactNumericBinary(operator, left, right) {
|
|
|
162
238
|
}
|
|
163
239
|
result = { coefficient, scale };
|
|
164
240
|
}
|
|
165
|
-
return boundedTaggedDomainValue(NUMERIC, formatDecimal(normalizeDecimal(result)), "NUMERIC result");
|
|
241
|
+
return boundedTaggedDomainValue(NUMERIC, formatDecimal(canonicalize ? normalizeDecimal(result) : result), "NUMERIC result");
|
|
166
242
|
}
|
|
167
243
|
export function exactNumericCompare(left, right) {
|
|
168
244
|
const leftTagged = taggedDecimalParts(left);
|
|
@@ -470,7 +546,7 @@ export function intervalDomainValue(value) {
|
|
|
470
546
|
export function arrayDomainValue(values) {
|
|
471
547
|
return boundedTaggedDomainValue(ARRAY_VALUE, boundedJsonText(values.map(externalSqlDomainValue), true, "ARRAY value"), "ARRAY value");
|
|
472
548
|
}
|
|
473
|
-
|
|
549
|
+
function enumDomainValue(value, name, values) {
|
|
474
550
|
if (value === null || value === undefined)
|
|
475
551
|
return null;
|
|
476
552
|
if (typeof value !== "string")
|
|
@@ -621,6 +697,29 @@ function collatorFor(locale, displayName) {
|
|
|
621
697
|
collators.set(locale, created);
|
|
622
698
|
return created;
|
|
623
699
|
}
|
|
700
|
+
/**
|
|
701
|
+
* Renders one result value at the JavaScript boundary using its column's logical domain.
|
|
702
|
+
* A NUMERIC column with a declared scale displays PostgreSQL-style at exactly that scale:
|
|
703
|
+
* the physical encoding is canonical (trailing fractional zeros stripped), so the declared
|
|
704
|
+
* scale is restored by padding — never rounding — and a value carrying more fractional
|
|
705
|
+
* digits than the declaration keeps every digit it has. Every other domain, and every value
|
|
706
|
+
* without one, renders exactly as externalSqlDomainValue.
|
|
707
|
+
*/
|
|
708
|
+
export function externalSqlDomainColumnValue(value, domain) {
|
|
709
|
+
if (domain?.kind === "numeric" &&
|
|
710
|
+
domain.scale !== undefined &&
|
|
711
|
+
domain.scale > 0 &&
|
|
712
|
+
typeof value === "string" &&
|
|
713
|
+
value.startsWith(NUMERIC)) {
|
|
714
|
+
const text = value.slice(NUMERIC.length);
|
|
715
|
+
const dot = text.indexOf(".");
|
|
716
|
+
const fractionDigits = dot === -1 ? 0 : text.length - dot - 1;
|
|
717
|
+
if (fractionDigits >= domain.scale)
|
|
718
|
+
return text;
|
|
719
|
+
return (dot === -1 ? `${text}.` : text) + "0".repeat(domain.scale - fractionDigits);
|
|
720
|
+
}
|
|
721
|
+
return externalSqlDomainValue(value);
|
|
722
|
+
}
|
|
624
723
|
export function externalSqlDomainValue(value) {
|
|
625
724
|
if (typeof value !== "string")
|
|
626
725
|
return value;
|
|
@@ -644,6 +743,44 @@ export function externalSqlDomainValue(value) {
|
|
|
644
743
|
}
|
|
645
744
|
return value;
|
|
646
745
|
}
|
|
746
|
+
/**
|
|
747
|
+
* The domain kind carried by an internal string value, or undefined for ordinary text. The
|
|
748
|
+
* protected-TEXT wrapper and collated text are both ordinary text to PostgreSQL's operator
|
|
749
|
+
* resolution, so they report no kind.
|
|
750
|
+
*/
|
|
751
|
+
function concatenationDomainKind(value) {
|
|
752
|
+
if (!value.startsWith(PREFIX))
|
|
753
|
+
return undefined;
|
|
754
|
+
if (value.startsWith(TEXT_VALUE) || value.startsWith(COLLATION_VALUE))
|
|
755
|
+
return undefined;
|
|
756
|
+
const rest = value.slice(PREFIX.length);
|
|
757
|
+
const colon = rest.indexOf(":");
|
|
758
|
+
return colon === -1 ? undefined : rest.slice(0, colon);
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* SQL `||` over internal string values, shared by both executors and constant folding so the
|
|
762
|
+
* three paths cannot disagree. PostgreSQL resolves `||` to text concatenation only when one
|
|
763
|
+
* side is text: its array and JSONB `||` operators are structural concatenation, which this
|
|
764
|
+
* engine does not implement, and two non-text operands have no `||` operator at all. Refusing
|
|
765
|
+
* those shapes keeps `||` from inventing a text concatenation PostgreSQL does not have —
|
|
766
|
+
* and from ever concatenating internal domain encodings.
|
|
767
|
+
*/
|
|
768
|
+
export function concatenatedSqlValue(left, right) {
|
|
769
|
+
const leftKind = concatenationDomainKind(left);
|
|
770
|
+
const rightKind = concatenationDomainKind(right);
|
|
771
|
+
for (const kind of [leftKind, rightKind]) {
|
|
772
|
+
if (kind === "array") {
|
|
773
|
+
throw new TypeError("PostgreSQL array concatenation (||) is not supported");
|
|
774
|
+
}
|
|
775
|
+
if (kind === "jsonb") {
|
|
776
|
+
throw new TypeError("PostgreSQL JSONB concatenation (||) is not supported");
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (leftKind !== undefined && rightKind !== undefined) {
|
|
780
|
+
throw new TypeError(`No || operator for ${leftKind} and ${rightKind} values`);
|
|
781
|
+
}
|
|
782
|
+
return protectedSqlTextValue(String(externalSqlDomainValue(left)) + String(externalSqlDomainValue(right)));
|
|
783
|
+
}
|
|
647
784
|
export function isSqlDomainValue(value) {
|
|
648
785
|
return typeof value === "string" && value.startsWith(PREFIX);
|
|
649
786
|
}
|
|
@@ -15,10 +15,20 @@ export declare function jsonAtPath(document: unknown, path: unknown, caller: str
|
|
|
15
15
|
found: boolean;
|
|
16
16
|
value?: unknown;
|
|
17
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* One step of PostgreSQL's `->`/`->>` access. A text key selects an object member; an integer
|
|
20
|
+
* key selects an array element, counting from the end when negative. The behaviour follows
|
|
21
|
+
* PostgreSQL's `json` type: a document of the wrong shape for the key selects nothing (NULL)
|
|
22
|
+
* rather than jsonb's scalar-as-one-element-array reading. Unlike the SQL/JSON functions, whose
|
|
23
|
+
* standard ON ERROR default swallows malformed documents, PostgreSQL's operators only exist on
|
|
24
|
+
* values already parsed as json, so a document that is not JSON is an error here.
|
|
25
|
+
*/
|
|
26
|
+
export declare function jsonArrowStep(document: unknown, key: unknown, caller: string): {
|
|
27
|
+
found: boolean;
|
|
28
|
+
value?: unknown;
|
|
29
|
+
};
|
|
18
30
|
/** Whether a value is JSON text of the requested shape (T825). */
|
|
19
31
|
export declare function jsonIsValid(document: unknown, kind: string): boolean;
|
|
20
|
-
/** A SQL value as its JSON counterpart: datetimes serialize as ISO text, like every cast. */
|
|
21
|
-
export declare function jsonValueOf(value: unknown): unknown;
|
|
22
32
|
/**
|
|
23
33
|
* JSON_ARRAY(v, ...) and JSON_OBJECT(k, v, ...) (T811/T812). The omitted null-handling clause is
|
|
24
34
|
* `NULL ON NULL`: SQL NULL becomes a JSON null. `ABSENT ON NULL` is a separate spelling rather
|
package/dist/engine/sql-json.js
CHANGED
|
@@ -75,6 +75,46 @@ export function jsonAtPath(document, path, caller) {
|
|
|
75
75
|
}
|
|
76
76
|
return { found: true, value: current };
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* One step of PostgreSQL's `->`/`->>` access. A text key selects an object member; an integer
|
|
80
|
+
* key selects an array element, counting from the end when negative. The behaviour follows
|
|
81
|
+
* PostgreSQL's `json` type: a document of the wrong shape for the key selects nothing (NULL)
|
|
82
|
+
* rather than jsonb's scalar-as-one-element-array reading. Unlike the SQL/JSON functions, whose
|
|
83
|
+
* standard ON ERROR default swallows malformed documents, PostgreSQL's operators only exist on
|
|
84
|
+
* values already parsed as json, so a document that is not JSON is an error here.
|
|
85
|
+
*/
|
|
86
|
+
export function jsonArrowStep(document, key, caller) {
|
|
87
|
+
const source = boundedJsonDocument(document, caller);
|
|
88
|
+
let parsed;
|
|
89
|
+
try {
|
|
90
|
+
parsed = JSON.parse(source);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
throw new TypeError(`${caller} requires a JSON document`);
|
|
94
|
+
}
|
|
95
|
+
const step = externalSqlDomainValue(key);
|
|
96
|
+
if (typeof step === "number") {
|
|
97
|
+
if (!Number.isInteger(step)) {
|
|
98
|
+
throw new TypeError(`${caller} array positions are integers`);
|
|
99
|
+
}
|
|
100
|
+
if (!Array.isArray(parsed))
|
|
101
|
+
return { found: false };
|
|
102
|
+
const index = step < 0 ? parsed.length + step : step;
|
|
103
|
+
if (index < 0 || index >= parsed.length)
|
|
104
|
+
return { found: false };
|
|
105
|
+
return { found: true, value: parsed[index] };
|
|
106
|
+
}
|
|
107
|
+
if (typeof step === "string") {
|
|
108
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
109
|
+
return { found: false };
|
|
110
|
+
}
|
|
111
|
+
const members = parsed;
|
|
112
|
+
if (!Object.hasOwn(members, step))
|
|
113
|
+
return { found: false };
|
|
114
|
+
return { found: true, value: members[step] };
|
|
115
|
+
}
|
|
116
|
+
throw new TypeError(`${caller} keys are member names or array positions`);
|
|
117
|
+
}
|
|
78
118
|
/** Whether a value is JSON text of the requested shape (T825). */
|
|
79
119
|
export function jsonIsValid(document, kind) {
|
|
80
120
|
document = externalSqlDomainValue(document);
|
|
@@ -103,7 +143,7 @@ export function jsonIsValid(document, kind) {
|
|
|
103
143
|
}
|
|
104
144
|
}
|
|
105
145
|
/** A SQL value as its JSON counterpart: datetimes serialize as ISO text, like every cast. */
|
|
106
|
-
|
|
146
|
+
function jsonValueOf(value) {
|
|
107
147
|
const external = externalSqlDomainValue(value);
|
|
108
148
|
return external instanceof Date ? dateIsoString(external) : external;
|
|
109
149
|
}
|
|
@@ -19,7 +19,7 @@ export declare function encodeSqlEqualityValue(value: unknown): readonly unknown
|
|
|
19
19
|
*/
|
|
20
20
|
export declare function roundSqlNumber(value: number, precision?: number): number;
|
|
21
21
|
/** A whole-string SQL pattern matcher. Unlike RegExp, test never coerces its input. */
|
|
22
|
-
|
|
22
|
+
interface SqlPatternMatcher {
|
|
23
23
|
test(value: string): boolean;
|
|
24
24
|
}
|
|
25
25
|
/** Compiles SQL LIKE without exposing input to the host regular-expression engine. */
|
|
@@ -34,3 +34,4 @@ export declare function defineSqlResultProperty(target: Record<string, unknown>,
|
|
|
34
34
|
* a silent coercion here would make `LENGTH(42)` answer instead of failing.
|
|
35
35
|
*/
|
|
36
36
|
export declare function stringArgument(name: string, value: unknown): string;
|
|
37
|
+
export {};
|
package/dist/engine/vector.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { DatabaseRow } from "./database.js";
|
|
|
2
2
|
import type { CompiledQuery, QueryResult, QueryValue } from "./query.js";
|
|
3
3
|
import { type FtsStats } from "./fts.js";
|
|
4
4
|
import { QueryMemoryContext, type QueryMemoryUsage } from "./memory.js";
|
|
5
|
-
|
|
5
|
+
type VectorType = "boolean" | "number" | "string" | "datetime";
|
|
6
6
|
/**
|
|
7
7
|
* A resident slice of a logically longer streamed column. Window arrays index rows relative to
|
|
8
8
|
* `start`; `length` on the vector remains the logical table length.
|
|
@@ -16,25 +16,25 @@ interface VectorBase {
|
|
|
16
16
|
readonly length: number;
|
|
17
17
|
readonly window?: VectorWindow;
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
interface BooleanVector extends VectorBase {
|
|
20
20
|
readonly kind: "boolean";
|
|
21
21
|
readonly values: Uint8Array;
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
interface NumberVector extends VectorBase {
|
|
24
24
|
readonly kind: "number";
|
|
25
25
|
readonly values: Float64Array;
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
interface DateTimeVector extends VectorBase {
|
|
28
28
|
readonly kind: "datetime";
|
|
29
29
|
readonly values: Float64Array;
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
interface StringVector extends VectorBase {
|
|
32
32
|
readonly kind: "string";
|
|
33
33
|
readonly codes: Uint32Array;
|
|
34
34
|
readonly dictionary: readonly string[];
|
|
35
35
|
}
|
|
36
36
|
export type ColumnVector = BooleanVector | NumberVector | DateTimeVector | StringVector;
|
|
37
|
-
|
|
37
|
+
interface ColumnarColumnInput {
|
|
38
38
|
readonly type: VectorType;
|
|
39
39
|
readonly values: readonly QueryValue[];
|
|
40
40
|
}
|
|
@@ -91,7 +91,7 @@ export interface QueryBatchExecutionOptions extends AsyncQueryExecutionOptions {
|
|
|
91
91
|
/** Maximum result rows handed to the consumer at once. */
|
|
92
92
|
readonly batchRows: number;
|
|
93
93
|
}
|
|
94
|
-
|
|
94
|
+
interface PrepareVectorQueryOptions {
|
|
95
95
|
readonly memoryContext?: QueryMemoryContext;
|
|
96
96
|
/**
|
|
97
97
|
* Exact BM25 corpus statistics served by the persisted full-text index, keyed by each
|
package/dist/engine/vector.js
CHANGED
|
@@ -9,7 +9,7 @@ import { ByteJoinIndex } from "./join-index.js";
|
|
|
9
9
|
import { UnknownTableError } from "./errors.js";
|
|
10
10
|
import { QueryMemoryBudgetError, QueryMemoryContext, } from "./memory.js";
|
|
11
11
|
import { compareSqlValues, compileSimilarPattern, defineSqlResultProperty, } from "./sql-semantics.js";
|
|
12
|
-
import { exactNumericBinary, externalSqlDomainValue, isDateDomainValue, isExactNumeric, preservedJsonDomainValue, protectedSqlTextValue, } from "./sql-domains.js";
|
|
12
|
+
import { concatenatedSqlValue, exactNumericBinary, externalSqlDomainValue, isDateDomainValue, isExactNumeric, preservedJsonDomainValue, protectedSqlTextValue, } from "./sql-domains.js";
|
|
13
13
|
import { buildSortKeyColumn, sortKeyIndexes } from "./sort-keys.js";
|
|
14
14
|
const DEFAULT_BATCH_ROWS = 2_048;
|
|
15
15
|
/** Above this, locating each IN member separately costs more than scanning between them. */
|
|
@@ -892,6 +892,9 @@ function bindExpression(expression, sources, aggregateSpecs, aggregateIndexes, m
|
|
|
892
892
|
...(expression.distinct === true ? { distinct: true } : {}),
|
|
893
893
|
...(rawDatetime === undefined ? {} : { rawDatetime }),
|
|
894
894
|
...(rawNumber === undefined ? {} : { rawNumber }),
|
|
895
|
+
...(expression.avgArgumentScale === undefined
|
|
896
|
+
? {}
|
|
897
|
+
: { avgScale: expression.avgArgumentScale }),
|
|
895
898
|
});
|
|
896
899
|
}
|
|
897
900
|
return {
|
|
@@ -4029,8 +4032,9 @@ function evaluateFinalExpression(plan, expression, group) {
|
|
|
4029
4032
|
if (expression.name === "SUM")
|
|
4030
4033
|
return isExactNumeric(value) ? value : (group.sums[aggregateIndex] ?? 0);
|
|
4031
4034
|
if (expression.name === "AVG") {
|
|
4032
|
-
if (isExactNumeric(value))
|
|
4033
|
-
return exactNumericBinary("/", value, count);
|
|
4035
|
+
if (isExactNumeric(value)) {
|
|
4036
|
+
return exactNumericBinary("/", value, count, plan.aggregates[aggregateIndex]?.avgScale);
|
|
4037
|
+
}
|
|
4034
4038
|
return (group.sums[aggregateIndex] ?? 0) / count;
|
|
4035
4039
|
}
|
|
4036
4040
|
// Raw-millisecond datetime extremes re-box into a Date only here, once per surviving group.
|
|
@@ -4764,7 +4768,7 @@ function binaryValue(operator, left, right) {
|
|
|
4764
4768
|
if (typeof left !== "string" || typeof right !== "string") {
|
|
4765
4769
|
throw new TypeError("|| requires string operands");
|
|
4766
4770
|
}
|
|
4767
|
-
return
|
|
4771
|
+
return concatenatedSqlValue(left, right);
|
|
4768
4772
|
}
|
|
4769
4773
|
const exact = exactNumericBinary(operator, left, right);
|
|
4770
4774
|
if (exact !== undefined)
|
|
@@ -4,7 +4,7 @@ export interface WriteColumnValues {
|
|
|
4
4
|
readonly values: readonly unknown[];
|
|
5
5
|
readonly stringByteLengths?: readonly number[];
|
|
6
6
|
}
|
|
7
|
-
|
|
7
|
+
interface WriteBlockRange {
|
|
8
8
|
readonly start: number;
|
|
9
9
|
readonly end: number;
|
|
10
10
|
}
|
|
@@ -16,3 +16,4 @@ export interface WriteBlockRange {
|
|
|
16
16
|
export declare function planAlignedWriteBlockRanges(columns: readonly WriteColumnValues[], rowCount: number, maximumRows: number, targetBytes: number, measureString?: (value: string) => number): WriteBlockRange[];
|
|
17
17
|
/** Conservative first estimate used by both physical compaction planners. */
|
|
18
18
|
export declare function estimateCompactionRowsPerOutput(targetBlockBytes: number, maximumEncodedBytesPerRow: number): number;
|
|
19
|
+
export {};
|
package/dist/plan/model.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export type AggregateName = "COUNT" | "SUM" | "AVG" | "MIN" | "MAX" | "JSON_ARRA
|
|
|
18
18
|
/** Optimizer-only aggregate that enforces scalar-subquery cardinality. */
|
|
19
19
|
| "MINNOW_SINGLE_VALUE";
|
|
20
20
|
export type ScalarFunctionName = "ROUND" | "COALESCE" | "DATE_TRUNC" | "DATE_ADD" | "UPPER" | "LOWER" | "LENGTH" | "ABS" | "TRIM" | "LTRIM" | "RTRIM" | "SUBSTR" | "REPLACE" | "INSTR" | "NULLIF" | "GREATEST" | "LEAST" | "FLOOR" | "CEIL" | "MOD" | "POWER" | "SQRT" | "EXTRACT" | "CAST" | "OCTET_LENGTH" | "LPAD" | "RPAD" | "OVERLAY" | "CURRENT_DATE" | "CURRENT_TIMESTAMP" | "LOCALTIME" | "GROUPING" | "JSON_VALUE" | "JSON_QUERY" | "JSON_EXISTS" | "JSON_OBJECT" | "JSON_ARRAY" | "IS_JSON" | "ARRAY"
|
|
21
|
+
/** Parser-produced `->` JSON member/element access returning a JSON value. */
|
|
22
|
+
| "MINNOW_JSON_GET"
|
|
23
|
+
/** Parser-produced `->>` JSON member/element access returning text. */
|
|
24
|
+
| "MINNOW_JSON_GET_TEXT"
|
|
21
25
|
/** Optimizer-only, prefix-free equality key for hashable multi-column decorrelation. */
|
|
22
26
|
| "MINNOW_TUPLE_KEY"
|
|
23
27
|
/** Parser-produced wrapper carrying one explicit collation through ordering/comparison. */
|
|
@@ -36,6 +40,14 @@ export type Expression = {
|
|
|
36
40
|
value: QueryValue;
|
|
37
41
|
internalSqlValue?: true;
|
|
38
42
|
sqlDomain?: SqlDomain;
|
|
43
|
+
/**
|
|
44
|
+
* A numeric constant's exact source digits, kept when the value survives the number
|
|
45
|
+
* boundary but the spelling carries more (`1.000000000000000000000000` is the number 1
|
|
46
|
+
* with display scale 24).
|
|
47
|
+
* Constant folding reads these so seeded arithmetic runs in exact decimal space, the way
|
|
48
|
+
* PostgreSQL types every decimal constant NUMERIC before evaluating it.
|
|
49
|
+
*/
|
|
50
|
+
exactText?: string;
|
|
39
51
|
}
|
|
40
52
|
/** A `?` or `$n` placeholder; `index` is 0-based. Replaced by a literal at bind time. */
|
|
41
53
|
| {
|
|
@@ -64,6 +76,13 @@ export type Expression = {
|
|
|
64
76
|
direction: "asc" | "desc";
|
|
65
77
|
nulls?: "first" | "last";
|
|
66
78
|
}>;
|
|
79
|
+
/**
|
|
80
|
+
* AVG over a declared-scale NUMERIC column: the argument's declared scale, annotated by a
|
|
81
|
+
* schema-aware pass before execution. PostgreSQL floors its quotient scale at the summed
|
|
82
|
+
* operand's display scale; canonical NUMERIC encoding strips trailing zeros, so without
|
|
83
|
+
* this the internal division cannot know the digits the declared scale will render.
|
|
84
|
+
*/
|
|
85
|
+
avgArgumentScale?: number;
|
|
67
86
|
} | {
|
|
68
87
|
kind: "list";
|
|
69
88
|
items: Expression[];
|
|
@@ -4,8 +4,6 @@ import { type Placement } from "../toolkit/extents.js";
|
|
|
4
4
|
/** A failed checkpoint may defer compaction, but the recovery log itself stays bounded. */
|
|
5
5
|
export declare const MAX_OPFS_WAL_BYTES: number;
|
|
6
6
|
export declare const MAX_OPFS_CHECKPOINT_BYTES: number;
|
|
7
|
-
/** Failed physical reclamation cannot permit byte-growing work forever. */
|
|
8
|
-
export declare const MAX_OPFS_CLEANUP_DEBT_BYTES: number;
|
|
9
7
|
interface IdPlacement {
|
|
10
8
|
id: string;
|
|
11
9
|
placement: Placement;
|
|
@@ -459,6 +457,4 @@ export declare class OpfsLeader {
|
|
|
459
457
|
export declare function assertBlockReadBatchByteLimit(placements: ReadonlyArray<{
|
|
460
458
|
length: number;
|
|
461
459
|
} | undefined>): void;
|
|
462
|
-
/** Conservative retained-heap model for one decoded postings chunk and its nested arrays. */
|
|
463
|
-
export declare function modeledFtsChunkBytes(chunk: readonly FtsPosting[]): number;
|
|
464
460
|
export {};
|
|
@@ -17,7 +17,7 @@ const CHECKPOINT_ENTRIES = 1024;
|
|
|
17
17
|
export const MAX_OPFS_WAL_BYTES = 256 * 1024 * 1024;
|
|
18
18
|
export const MAX_OPFS_CHECKPOINT_BYTES = 256 * 1024 * 1024;
|
|
19
19
|
/** Failed physical reclamation cannot permit byte-growing work forever. */
|
|
20
|
-
|
|
20
|
+
const MAX_OPFS_CLEANUP_DEBT_BYTES = 64 * 1024 * 1024;
|
|
21
21
|
/** Decoded full-text base cache: bounded primarily by modeled retained heap, count secondarily. */
|
|
22
22
|
const FTS_CHUNK_CACHE_BYTES = 16 * 1024 * 1024;
|
|
23
23
|
const FTS_CHUNK_CACHE_SIZE = 64;
|
|
@@ -5035,7 +5035,7 @@ function samePlacement(left, right) {
|
|
|
5035
5035
|
left.checksum === right.checksum);
|
|
5036
5036
|
}
|
|
5037
5037
|
/** Conservative retained-heap model for one decoded postings chunk and its nested arrays. */
|
|
5038
|
-
|
|
5038
|
+
function modeledFtsChunkBytes(chunk) {
|
|
5039
5039
|
let bytes = 64;
|
|
5040
5040
|
for (const posting of chunk) {
|
|
5041
5041
|
bytes += 64 + posting.term.length * 2 + posting.rowIds.length * 40;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type SnapshotFrame, type SnapshotFrameKind } from "../types.js";
|
|
2
2
|
import { type Placement } from "../toolkit/extents.js";
|
|
3
3
|
import { OpfsTree } from "./files.js";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
type SnapshotLedgerKind = "export" | "import" | "completed";
|
|
5
|
+
interface SnapshotLedgerRecord {
|
|
6
6
|
readonly sequence: number;
|
|
7
7
|
readonly kind: SnapshotFrameKind;
|
|
8
8
|
readonly itemCount: number;
|
|
@@ -38,3 +38,4 @@ export declare class SnapshotFrameLedger {
|
|
|
38
38
|
adoptLength(length: number): void;
|
|
39
39
|
close(): void;
|
|
40
40
|
}
|
|
41
|
+
export {};
|