Execution
Validate, generate SQL, and execute dataset and metric queries.
createDatasetClient is the execution surface for datasets and metrics. 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 or metric queries. Both paths share the same ClickHouse connection.
Execution methods
The dataset client exposes three main 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.execute(target, query, context?) | Validate, generate SQL, execute, and return rows |
The target can be a metric, a grained metric, or a dataset.
Validate
Use validate when you want errors as data instead of exceptions.
const validation = analytics.validate(revenue, {
dimensions: ['country'],
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 metric plans.
Generate SQL
Use toSQL to inspect the generated query.
const sql = analytics.toSQL(revenue, {
dimensions: ['country'],
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.
Execute a metric
Metric execution returns one named KPI, optionally grouped by dimensions or time.
const revenue = Orders.metric('revenue', { measure: 'revenue' });
const result = await analytics.execute(revenue, {
dimensions: ['country'],
filters: [eq('status', 'completed')],
orderBy: [{ field: 'revenue', direction: 'desc' }],
limit: 10,
});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,
});Execution context
Pass execution context as the third argument. This is where runtime tenant scope and per-call builder overrides live.
await analytics.execute(revenue, {
dimensions: ['country'],
}, {
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(revenue, {
dimensions: ['country'],
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,
}