From 3b7d0ae28da6a49d3581968fd597e16226ea167b Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Mon, 15 Dec 2025 17:32:28 +0000 Subject: [PATCH] Actual query function --- internal-packages/clickhouse/package.json | 1 + .../clickhouse/src/client/tsql.ts | 92 ++++--------------- internal-packages/tsql/package.json | 1 - internal-packages/tsql/src/index.ts | 67 ++++++++++++++ pnpm-lock.yaml | 6 +- 5 files changed, 88 insertions(+), 79 deletions(-) diff --git a/internal-packages/clickhouse/package.json b/internal-packages/clickhouse/package.json index da5531463..846ded63f 100644 --- a/internal-packages/clickhouse/package.json +++ b/internal-packages/clickhouse/package.json @@ -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" diff --git a/internal-packages/clickhouse/src/client/tsql.ts b/internal-packages/clickhouse/src/client/tsql.ts index c4bf7e171..aff385255 100644 --- a/internal-packages/clickhouse/src/client/tsql.ts +++ b/internal-packages/clickhouse/src/client/tsql.ts @@ -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 { /** 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; - /** 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; } /** @@ -81,11 +47,8 @@ export type TSQLQueryResult = [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( reader: ClickhouseReader, options: ExecuteTSQLOptions ): Promise>> { - // 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( * }); * ``` */ -export function createTSQLExecutor(reader: ClickhouseReader, tableSchema: TSQLTableSchema[]) { +export function createTSQLExecutor(reader: ClickhouseReader, tableSchema: TableSchema[]) { return { execute: ( options: Omit, "tableSchema"> @@ -178,4 +121,3 @@ export function createTSQLExecutor(reader: ClickhouseReader, tableSchema: TSQLTa }, }; } - diff --git a/internal-packages/tsql/package.json b/internal-packages/tsql/package.json index 7baf40726..0cac36e7b 100644 --- a/internal-packages/tsql/package.json +++ b/internal-packages/tsql/package.json @@ -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" diff --git a/internal-packages/tsql/src/index.ts b/internal-packages/tsql/src/index.ts index 937527644..effbaa024 100644 --- a/internal-packages/tsql/src/index.ts +++ b/internal-packages/tsql/src/index.ts @@ -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; +} + +/** + * 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); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad29c6c31..e4147e17a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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