@pythia-software/query-table-duckdb 0.0.0-stage → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 query-table contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,108 @@
1
- # Temporary Holding Version
1
+ # Query Table DuckDB backend
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `@pythia-software/query-table-duckdb` executes Query Table's row, metric, and
4
+ version 3 grouped-query contracts with DuckDB SQL. The same TypeScript planner
5
+ runs against native DuckDB or DuckDB-Wasm. There is no React, HTTP server, Go,
6
+ database driver, or browser dependency in this package.
7
+
8
+ ```ts
9
+ import { DuckDBBackend } from "@pythia-software/query-table-duckdb";
10
+ import { DuckDBInstance } from "@duckdb/node-api";
11
+
12
+ const instance = await DuckDBInstance.create(":memory:");
13
+ const connection = await instance.connect();
14
+ await connection.run("SET TimeZone='UTC'");
15
+ // Create/load an authorized relation with unique, non-null stable identities.
16
+ const backend = new DuckDBBackend({
17
+ relation: "authorized_records", schema,
18
+ connection: {
19
+ query: async (sql, args) =>
20
+ (await connection.runAndReadAll(sql, args)).getRowObjectsJS(),
21
+ },
22
+ });
23
+ const transport = backend.transport();
24
+ // useQueryTable({ schema: backend.schema, transport })
25
+ ```
26
+
27
+ The `args` value in this structural example must be narrowed to your driver's
28
+ parameter types in a TypeScript host. Query operands are strings, numbers,
29
+ booleans or null. The backend uses numbered parameters, never interpolated values.
30
+ `compileDuckDBRows` also exposes a standalone, parameterized row-query compiler.
31
+
32
+ For Wasm, pass `fromDuckDBWasmConnection(await db.connect())` as the connection.
33
+ Configure Arrow conversion with `castTimestampToDate: true`; retaining bigint
34
+ values allows the adapter to reject unsafe integer results. Set the session
35
+ timezone to UTC. Call `await backend.drain()` before closing an owned connection.
36
+
37
+ ## Host bindings and trust boundary
38
+
39
+ The host supplies a `FieldSchema`, a quoted relation name, and optional trusted
40
+ `bindings: { fieldName: "SQL expression" }`. Bindings are projected to canonical
41
+ field names before execution. A source path is treated as a literal column name.
42
+ Use explicit SQL bindings for nested/expression paths.
43
+
44
+ `loadDuckDBSchema(document)` reads `bindings.duckdb.expr` from JSON schema
45
+ documents and returns `{ schema, bindings }`; it rejects missing bindings and
46
+ never falls back to PostgreSQL or SQLite SQL. Expressions and the relation must
47
+ come from trusted host configuration, not requests. Register a view restricted
48
+ to the authorized dataset/tenant before constructing a backend. A schema is an
49
+ execution allowlist, not an authentication system.
50
+
51
+ Use one backend per exclusive connection. It serializes requests and executes
52
+ each rows/metrics request inside one transaction. Cancellation prevents queued
53
+ requests from starting, discards obsolete results, and rolls back failed work.
54
+ An optional driver's `interrupt()` stops an active query. DuckDB-Wasm's
55
+ structural adapter does not advertise interruption; active SQL finishes before
56
+ the cancelled request rolls back. Separate row/metric requests need a host-owned
57
+ immutable dataset for identical contents. `snapshot` binds a request to a
58
+ host-supplied dataset version; it does not manufacture a retained snapshot.
59
+
60
+ ## Supported execution profile
61
+
62
+ The explicitly negotiated profile is `qt-duckdb-v1`.
63
+
64
+ | Contract | Support |
65
+ | --- | --- |
66
+ | Rows | projection, all filter operators, AND/OR/NOT, nulls, text sets, multi-sort, null placement, pagination |
67
+ | Metadata/items | scalar/array autocomplete with `hasNull`, complete item lookup |
68
+ | Regex | bounded RE2 source filtering and regex sort extraction; unsupported RE2 patterns fail |
69
+ | Scalar metrics | COUNT, COUNT_DISTINCT, SUM, AVG, MIN, MAX, exact MEDIAN; composed formulas; paired scatter measures |
70
+ | Distributions | exact boxes and shared-edge histograms; bounded numeric samples |
71
+ | Scope | all matching rows or the ordered result page |
72
+ | Grouped rows | source WHERE → GROUP BY and summaries → HAVING → sorting → pagination |
73
+ | Representatives | all ROW columns from the same ordered representative, with stable ID tie-breaking |
74
+ | Calendar metrics | hour/day/Monday-week/month/year, IANA zones, repeated/partial DST hours, null dates and empty completion |
75
+ | Computed definitions | host-resolved static definitions, transitive references, revision/token handshake, select/sort/filter/group/measure |
76
+
77
+ Formula SQL supports arithmetic/comparisons/three-valued boolean operations,
78
+ IF/IFS/SWITCH/COALESCE/NULLIF, text slicing/case/matching/padding, common numeric
79
+ functions, scalar conversions, text-array operations and date part/truncation.
80
+ `IFERROR`, regex row formulas, `DATE_ADD`, `DATE_DIFF`, and `FORMAT_DATE` are
81
+ explicitly unsupported SQL functions in this first profile. They remain
82
+ available as browser-only computed columns through the standard frontend worker.
83
+ No unsupported filter or aggregate is silently dropped. SQL runtime failures
84
+ reject execution; they do not become per-row computed error sidecars. Native
85
+ DuckDB string, conversion, floating-point and collation semantics apply within
86
+ this profile; `ROUND` uses the frontend's ties-toward-positive-infinity rule.
87
+
88
+ Dates are ISO strings or native DATE/TIMESTAMP/TIMESTAMPTZ columns. Text arrays
89
+ are native VARCHAR[] columns, not serialized JSON. JSON display fields can be
90
+ stored as VARCHAR and kept out of sorting/filtering through schema capabilities.
91
+ Output numbers must be finite and within JavaScript safe magnitude; integer
92
+ driver values outside safe precision are rejected.
93
+
94
+ ## Budgets and results
95
+
96
+ Defaults: 10,000 result rows, 10,000 groups, 250,000 matching source rows,
97
+ 64 MiB of source JSON payload, 100,000 exact samples per group, 20 metrics,
98
+ 100 filter predicates, and 256 KiB SQL statements. Hosts can lower budgets.
99
+ Population and payload budgets are checked before source staging; distribution
100
+ samples are bounded by that population and their explicit per-group budget.
101
+ DuckDB's database memory limit additionally governs scans, joins and scratch
102
+ space. Budget failures return errors, not partial populations or approximate
103
+ statistics. Metric `groupLimit` is applied after reductions/ranking and reports
104
+ total group count and coverage.
105
+
106
+ For native execution conformance, run `npm run test:duckdb` from the repository.
107
+ `npm run test:showcase` exercises the same adapter in browser Wasm over real
108
+ Parquet data. The public demo is described in `docs/public-demo.md`.
@@ -0,0 +1,110 @@
1
+ import { FieldSchema, FieldDef, WhereTerm, OrderByClause, FormulaPlan, FormulaNode, ServerQuery, ComputedColumn, ComputedExecution, QueryCapabilitiesV3, QueryRequestV3, QueryResultV3, Transport } from '@pythia-software/query-table-core';
2
+
3
+ interface DuckDBStatement {
4
+ sql: string;
5
+ args: unknown[];
6
+ }
7
+ /** Identifiers come only from the host catalogue, and are always quoted. */
8
+ declare function quoteIdentifier(value: string): string;
9
+ declare function quoteRelation(value: string): string;
10
+ declare class SQLBuilder {
11
+ readonly schema: FieldSchema;
12
+ readonly now: number;
13
+ readonly bindings: Record<string, string>;
14
+ readonly args: unknown[];
15
+ constructor(schema: FieldSchema, now?: number, bindings?: Record<string, string>);
16
+ bind(value: unknown): string;
17
+ statement(sql: string): DuckDBStatement;
18
+ definition(name: string): FieldDef;
19
+ field(name: string, normalize?: boolean): string;
20
+ where(terms: WhereTerm[]): string;
21
+ order(terms: OrderByClause[], identity?: boolean): string;
22
+ expression(source: string, aggregate?: boolean): {
23
+ sql: string;
24
+ plan: FormulaPlan;
25
+ };
26
+ node(node: FormulaNode): string;
27
+ private nodeSQL;
28
+ }
29
+ declare function compileDuckDBRows(query: ServerQuery, schema: FieldSchema, relation: string, now?: number): DuckDBStatement;
30
+
31
+ declare const DUCKDB_PROFILE = "qt-duckdb-v1";
32
+ type DuckDBRow = Record<string, unknown>;
33
+ /** One exclusive connection, with bound parameters; no driver runtime dependency. */
34
+ interface DuckDBConnection {
35
+ query(sql: string, args: unknown[]): Promise<DuckDBRow[]>;
36
+ interrupt?(): void | Promise<void>;
37
+ }
38
+ interface DuckDBOptions {
39
+ connection: DuckDBConnection;
40
+ schema: FieldSchema;
41
+ relation: string;
42
+ /** Trusted SQL expressions, keyed by the host's field names. Never request SQL. */
43
+ bindings?: Record<string, string>;
44
+ computedColumns?: readonly ComputedColumn[];
45
+ snapshot?: string;
46
+ maxRows?: number;
47
+ maxGroups?: number;
48
+ maxPopulation?: number;
49
+ maxSamples?: number;
50
+ maxStagingBytes?: number;
51
+ }
52
+ /** Normalize both Arrow and Node values at the transport boundary. */
53
+ declare function normalizeDuckDBValue(value: unknown): unknown;
54
+ declare class DuckDBBackend {
55
+ readonly options: DuckDBOptions;
56
+ readonly schema: FieldSchema;
57
+ readonly execution: ComputedExecution;
58
+ readonly capabilities: QueryCapabilitiesV3;
59
+ readonly maxSamples: number;
60
+ readonly maxStagingBytes: number;
61
+ private queue;
62
+ private sequence;
63
+ private readonly computed;
64
+ constructor(options: DuckDBOptions);
65
+ private serial;
66
+ /** Wait before closing an owned driver connection. */
67
+ drain(): Promise<void>;
68
+ private run;
69
+ private sql;
70
+ private window;
71
+ private builder;
72
+ private projectedSource;
73
+ private transaction;
74
+ private temp;
75
+ private stage;
76
+ private page;
77
+ private formatRow;
78
+ private validateRequest;
79
+ execute(request: QueryRequestV3, signal?: AbortSignal): Promise<QueryResultV3>;
80
+ private metrics;
81
+ private metric;
82
+ transport(): Transport<DuckDBRow>;
83
+ }
84
+ declare function createDuckDBTransport(options: DuckDBOptions): Transport<DuckDBRow>;
85
+
86
+ /** Structural adapter keeps duckdb-wasm optional and out of native bundles. */
87
+ interface DuckDBWasmConnection {
88
+ query(sql: string): Promise<{
89
+ toArray(): {
90
+ toJSON(): unknown;
91
+ }[];
92
+ }>;
93
+ prepare(sql: string): Promise<{
94
+ query(...args: any[]): Promise<{
95
+ toArray(): {
96
+ toJSON(): unknown;
97
+ }[];
98
+ }>;
99
+ close(): Promise<void>;
100
+ }>;
101
+ }
102
+ declare function fromDuckDBWasmConnection(connection: DuckDBWasmConnection): DuckDBConnection;
103
+
104
+ /** Read explicit DuckDB bindings; never reuse another engine's SQL. */
105
+ declare function loadDuckDBSchema(document: unknown): {
106
+ schema: FieldSchema;
107
+ bindings: Record<string, string>;
108
+ };
109
+
110
+ export { DUCKDB_PROFILE, DuckDBBackend, type DuckDBConnection, type DuckDBOptions, type DuckDBRow, type DuckDBStatement, type DuckDBWasmConnection, SQLBuilder, compileDuckDBRows, createDuckDBTransport, fromDuckDBWasmConnection, loadDuckDBSchema, normalizeDuckDBValue, quoteIdentifier, quoteRelation };