Execution
Validate, generate SQL, and execute dataset queries.
createDatasetClient is the execution surface for datasets. In a ClickHouse app, pass the same query builder instance you use for hand-written queries.
import { createDatasetClient } from '@hypequery/datasets';
import { createQueryBuilder } from '@hypequery/clickhouse';
const db = createQueryBuilder({
url: process.env.CLICKHOUSE_URL!,
username: process.env.CLICKHOUSE_USER!,
password: process.env.CLICKHOUSE_PASSWORD!,
database: process.env.CLICKHOUSE_DATABASE!,
});
const analytics = createDatasetClient({ queryBuilder: db });Use db.table(...) for local typed queries and analytics.execute(...) for semantic dataset queries. Both paths share the same ClickHouse connection.
Execution methods
The dataset client exposes these methods:
| Method | Use it for |
|---|---|
analytics.validate(target, query, context?) | Check a query without generating or executing SQL |
analytics.toSQL(target, query, context?) | Inspect generated SQL without executing it |
analytics.compileDataset(dataset, query, context?) | Preview execution SQL, effective limits, dependencies, and preflight statements |
analytics.execute(target, query, context?) | Validate, generate SQL, execute, and return rows |
The target is a dataset. Handles from the older Metrics API are also accepted. compileDataset accepts datasets only and requires the query-builder execution path.
Validate
Use validate when you want errors as data instead of exceptions.
const validation = analytics.validate(Orders, {
dimensions: ['country'],
measures: ['revenue'],
filters: [eq('status', 'completed')],
});
if (!validation.valid) {
console.error(validation.errors);
}Validation checks dimensions, measures, filters, order fields, limits, time grain requirements, tenant context, and derived measure plans.
Generate SQL
Use toSQL to inspect the generated query.
const sql = analytics.toSQL(Orders, {
dimensions: ['country'],
measures: ['revenue'],
filters: [eq('status', 'completed')],
orderBy: [{ field: 'revenue', direction: 'desc' }],
limit: 10,
});toSQL validates first. Invalid queries throw with the same validation errors that execute would throw.
Preview dataset execution
compileDataset and dataset execution use the same compilation pipeline for base,
derived, window, and shift measures. Compilation does not execute a query. It
includes the dataset's effective result ceiling and the extra row fetched to
determine pagination.hasMore. Window and shift queries also expose their
time-axis guard query in preflightStatements.
const query = { measures: ['revenue'], limit: 10 } as const;
const compiled = analytics.compileDataset(Orders, query);
// Structural diagnostics omit SQL, physical columns, filter values and tenant IDs.
console.log(compiled.describe());
// JSON.stringify(compiled) produces the same structural description.
const result = await analytics.execute(Orders, query);Provide the same trusted tenant context and runtime builder override to compilation
and execution. Invalid definitions or queries fail during compilation. SQL and
parameters are available through compiled.sql and compiled.parameters for an
authorized debugger; custom query builders may inline values into SQL, so those
fields must not be returned as public diagnostics. The normalized compiled.query
also contains filter values. toSQL retains its logical query behavior, including
the requested limit; use compileDataset to preview the actual execution limit.
Execute a dataset
Dataset execution returns an ad hoc selection of dimensions and measures from one dataset.
const result = await analytics.execute(Orders, {
dimensions: ['country', 'status'],
measures: ['revenue', 'orderCount'],
filters: [eq('status', 'completed')],
limit: 10,
});Dataset queries use the same by field when the dataset declares a timeKey:
const result = await analytics.execute(Orders, {
by: 'day',
dimensions: ['status'],
measures: ['revenue', 'orderCount'],
});Execution context
Pass execution context as the third argument. This is where runtime tenant scope and per-call builder overrides live.
await analytics.execute(Orders, {
dimensions: ['country'],
measures: ['revenue'],
}, {
runtime: {
tenant: 'tenant_123',
},
});When a dataset has tenantKey, tenant context is required unless you explicitly use a trusted cross-tenant scope. Datasets auto-inject the tenant filter from tenantKey.
Result metadata
execute returns rows plus metadata.
const result = await analytics.execute(Orders, {
dimensions: ['country'],
measures: ['revenue'],
limit: 25,
});
result.data;
result.meta?.sql;
result.meta?.timingMs;
result.meta?.rowCount;
result.meta?.pagination;Pagination metadata is present when a query sets limit. hypequery over-fetches one extra row to compute hasMore without issuing a separate count query.
{
limit: 25,
offset: 0,
hasMore: true,
}