Actual query function

This commit is contained in:
Matt Aitken
2025-12-15 17:32:28 +00:00
parent 8809e1be79
commit 3b7d0ae28d
5 changed files with 88 additions and 79 deletions
@@ -8,6 +8,7 @@
"dependencies": {
"@clickhouse/client": "^1.12.1",
"@internal/tracing": "workspace:*",
"@internal/tsql": "workspace:*",
"@trigger.dev/core": "workspace:*",
"zod": "3.25.76",
"zod-error": "1.5.0"
+17 -75
View File
@@ -7,9 +7,13 @@
import type { ClickHouseSettings } from "@clickhouse/client";
import { z } from "zod";
import { compileTSQL, type TableSchema, type QuerySettings } from "@internal/tsql";
import type { ClickhouseReader } from "./types.js";
import { QueryError } from "./errors.js";
// Re-export TableSchema for convenience
export type { TableSchema, QuerySettings };
/**
* Options for executing a TSQL query
*/
@@ -27,49 +31,11 @@ export interface ExecuteTSQLOptions<TOut extends z.ZodSchema> {
/** The environment ID for tenant isolation */
environmentId: string;
/** Schema registry defining allowed tables and columns */
tableSchema: TSQLTableSchema[];
tableSchema: TableSchema[];
/** Optional ClickHouse query settings */
settings?: ClickHouseSettings;
/** Maximum number of rows to return (default: 10000) */
maxRows?: number;
/** Timezone for date/time operations (default: UTC) */
timezone?: string;
}
/**
* Schema definition for a table accessible via TSQL
*/
export interface TSQLTableSchema {
/** The name of the table as used in TSQL queries */
name: string;
/** The fully qualified ClickHouse table name */
clickhouseName: string;
/** Column definitions */
columns: Record<string, TSQLColumnSchema>;
/** Tenant isolation column configuration */
tenantColumns: {
organizationId: string;
projectId: string;
environmentId: string;
};
}
/**
* Schema definition for a column
*/
export interface TSQLColumnSchema {
/** The column name */
name: string;
/** The ClickHouse column name (if different from name) */
clickhouseName?: string;
/** Whether the column can be selected */
selectable?: boolean;
/** Whether the column can be used in WHERE */
filterable?: boolean;
/** Whether the column can be used in ORDER BY */
sortable?: boolean;
/** Whether the column can be used in GROUP BY */
groupable?: boolean;
clickhouseSettings?: ClickHouseSettings;
/** Optional TSQL query settings (maxRows, timezone, etc.) */
querySettings?: Partial<QuerySettings>;
}
/**
@@ -81,11 +47,8 @@ export type TSQLQueryResult<T> = [QueryError, null] | [null, T[]];
* Execute a TSQL query against ClickHouse
*
* This function:
* 1. Parses the TSQL query into an AST
* 2. Validates tables and columns against the schema
* 3. Injects tenant isolation WHERE clauses
* 4. Generates parameterized ClickHouse SQL
* 5. Executes the query and returns validated results
* 1. Compiles the TSQL query to ClickHouse SQL (parse, validate, inject tenant guards)
* 2. Executes the query and returns validated results
*
* @example
* ```typescript
@@ -104,43 +67,23 @@ export async function executeTSQL<TOut extends z.ZodSchema>(
reader: ClickhouseReader,
options: ExecuteTSQLOptions<TOut>
): Promise<TSQLQueryResult<z.output<TOut>>> {
// Lazy import to avoid circular dependencies and keep the TSQL package optional
const {
parseTSQLSelect,
createPrinterContext,
createSchemaRegistry,
printToClickHouse,
} = await import("@internal/tsql");
try {
// 1. Parse the TSQL query
const ast = parseTSQLSelect(options.query);
// 2. Create schema registry from table schemas
const schemaRegistry = createSchemaRegistry(options.tableSchema);
// 3. Create printer context with tenant IDs
const context = createPrinterContext({
// 1. Compile the TSQL query to ClickHouse SQL
const { sql, params } = compileTSQL(options.query, {
organizationId: options.organizationId,
projectId: options.projectId,
environmentId: options.environmentId,
schema: schemaRegistry,
settings: {
maxRows: options.maxRows ?? 10000,
timezone: options.timezone ?? "UTC",
},
tableSchema: options.tableSchema,
settings: options.querySettings,
});
// 4. Print the AST to ClickHouse SQL
const { sql, params } = printToClickHouse(ast, context);
// 5. Execute the query
// 2. Execute the query
const queryFn = reader.query({
name: options.name,
query: sql,
params: z.record(z.any()),
schema: options.schema,
settings: options.settings,
settings: options.clickhouseSettings,
});
return await queryFn(params);
@@ -169,7 +112,7 @@ export async function executeTSQL<TOut extends z.ZodSchema>(
* });
* ```
*/
export function createTSQLExecutor(reader: ClickhouseReader, tableSchema: TSQLTableSchema[]) {
export function createTSQLExecutor(reader: ClickhouseReader, tableSchema: TableSchema[]) {
return {
execute: <TOut extends z.ZodSchema>(
options: Omit<ExecuteTSQLOptions<TOut>, "tableSchema">
@@ -178,4 +121,3 @@ export function createTSQLExecutor(reader: ClickhouseReader, tableSchema: TSQLTa
},
};
}
-1
View File
@@ -6,7 +6,6 @@
"types": "./src/index.ts",
"type": "module",
"dependencies": {
"@internal/clickhouse": "workspace:*",
"@trigger.dev/core": "workspace:*",
"antlr4ts": "0.5.0-alpha.4",
"zod": "3.25.76"
+67
View File
@@ -9,6 +9,9 @@ import { TSQLParser } from "./grammar/TSQLParser.js";
import { TSQLParseTreeConverter } from "./query/parser.js";
import type { SelectQuery, SelectSetQuery, Expression } from "./query/ast.js";
import { SyntaxError } from "./query/errors.js";
import { createSchemaRegistry, type TableSchema } from "./query/schema.js";
import { createPrinterContext, type QuerySettings } from "./query/printer_context.js";
import { printToClickHouse, type PrintResult } from "./query/printer.js";
/**
* Simple error listener that captures syntax errors
@@ -164,3 +167,67 @@ export function parseTSQLExpr(expr: string): Expression {
const converter = new TSQLParseTreeConverter();
return converter.visit(parseTree) as Expression;
}
/**
* Options for compiling a TSQL query to ClickHouse SQL
*/
export interface CompileTSQLOptions {
/** The organization ID for tenant isolation */
organizationId: string;
/** The project ID for tenant isolation */
projectId: string;
/** The environment ID for tenant isolation */
environmentId: string;
/** Schema definitions for allowed tables and columns */
tableSchema: TableSchema[];
/** Optional query settings */
settings?: Partial<QuerySettings>;
}
/**
* Compile a TSQL query string to ClickHouse SQL with parameters
*
* This function:
* 1. Parses the TSQL query into an AST
* 2. Validates tables and columns against the schema
* 3. Injects tenant isolation WHERE clauses
* 4. Generates parameterized ClickHouse SQL
*
* @param query - The TSQL query string to compile
* @param options - Compilation options including tenant IDs and schema
* @returns The compiled SQL and parameters
* @throws SyntaxError if the query is invalid
* @throws QueryError if tables/columns are not allowed
*
* @example
* ```typescript
* const { sql, params } = compileTSQL(
* "SELECT * FROM task_runs WHERE status = 'completed' LIMIT 100",
* {
* organizationId: "org_123",
* projectId: "proj_456",
* environmentId: "env_789",
* tableSchema: [taskRunsSchema],
* }
* );
* ```
*/
export function compileTSQL(query: string, options: CompileTSQLOptions): PrintResult {
// 1. Parse the TSQL query
const ast = parseTSQLSelect(query);
// 2. Create schema registry from table schemas
const schemaRegistry = createSchemaRegistry(options.tableSchema);
// 3. Create printer context with tenant IDs
const context = createPrinterContext({
organizationId: options.organizationId,
projectId: options.projectId,
environmentId: options.environmentId,
schema: schemaRegistry,
settings: options.settings,
});
// 4. Print the AST to ClickHouse SQL
return printToClickHouse(ast, context);
}
+3 -3
View File
@@ -999,6 +999,9 @@ importers:
'@internal/tracing':
specifier: workspace:*
version: link:../tracing
'@internal/tsql':
specifier: workspace:*
version: link:../tsql
'@trigger.dev/core':
specifier: workspace:*
version: link:../../packages/core
@@ -1265,9 +1268,6 @@ importers:
internal-packages/tsql:
dependencies:
'@internal/clickhouse':
specifier: workspace:*
version: link:../clickhouse
'@trigger.dev/core':
specifier: workspace:*
version: link:../../packages/core