22 * TSQL Query Execution for ClickHouse
33 *
44 * This module provides a safe interface for executing TSQL queries against ClickHouse
5- * with automatic tenant isolation and SQL injection protection.
5+ * with enforced WHERE clause conditions ( tenant isolation + plan limits) and SQL injection protection.
66 */
77
88import type { ClickHouseSettings } from "@clickhouse/client" ;
@@ -14,7 +14,7 @@ import {
1414 type TableSchema ,
1515 type QuerySettings ,
1616 type FieldMappings ,
17- type WhereClauseFallback ,
17+ type WhereClauseCondition
1818} from "@internal/tsql" ;
1919import type { ClickhouseReader , QueryStats } from "./types.js" ;
2020import { QueryError } from "./errors.js" ;
@@ -25,7 +25,7 @@ const logger = new Logger("tsql", "info");
2525
2626export type { QueryStats } ;
2727
28- export type { TableSchema , QuerySettings , FieldMappings , WhereClauseFallback } ;
28+ export type { TableSchema , QuerySettings , FieldMappings , WhereClauseCondition } ;
2929
3030/**
3131 * Options for executing a TSQL query
@@ -37,14 +37,26 @@ export interface ExecuteTSQLOptions<TOut extends z.ZodSchema> {
3737 query : string ;
3838 /** The Zod schema for validating output rows */
3939 schema : TOut ;
40- /** The organization ID for tenant isolation (required) */
41- organizationId : string ;
42- /** The project ID for tenant isolation (optional - omit to query across all projects) */
43- projectId ?: string ;
44- /** The environment ID for tenant isolation (optional - omit to query across all environments) */
45- environmentId ?: string ;
4640 /** Schema registry defining allowed tables and columns */
4741 tableSchema : TableSchema [ ] ;
42+ /**
43+ * REQUIRED: Conditions always applied at the table level.
44+ * Must include tenant columns (e.g., organization_id) for multi-tenant tables.
45+ * Applied to every table reference including subqueries, CTEs, and JOINs.
46+ *
47+ * @example
48+ * ```typescript
49+ * {
50+ * // Tenant isolation
51+ * organization_id: { op: "eq", value: "org_123" },
52+ * project_id: { op: "eq", value: "proj_456" },
53+ * environment_id: { op: "eq", value: "env_789" },
54+ * // Plan-based time limit
55+ * triggered_at: { op: "gte", value: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) }
56+ * }
57+ * ```
58+ */
59+ enforcedWhereClause : Record < string , WhereClauseCondition > ;
4860 /** Optional ClickHouse query settings */
4961 clickhouseSettings ?: ClickHouseSettings ;
5062 /** Optional TSQL query settings (maxRows, timezone, etc.) */
@@ -78,6 +90,7 @@ export interface ExecuteTSQLOptions<TOut extends z.ZodSchema> {
7890 /**
7991 * Fallback WHERE conditions to apply when the user hasn't filtered on a column.
8092 * Key is the column name, value is the fallback condition.
93+ * These are applied at the AST level (top-level query only).
8194 *
8295 * @example
8396 * ```typescript
@@ -87,7 +100,7 @@ export interface ExecuteTSQLOptions<TOut extends z.ZodSchema> {
87100 * }
88101 * ```
89102 */
90- whereClauseFallback ?: Record < string , WhereClauseFallback > ;
103+ whereClauseFallback ?: Record < string , WhereClauseCondition > ;
91104}
92105
93106/**
@@ -123,7 +136,7 @@ export type TSQLQueryResult<T> = [QueryError, null] | [null, TSQLQuerySuccess<T>
123136 * Execute a TSQL query against ClickHouse
124137 *
125138 * This function:
126- * 1. Compiles the TSQL query to ClickHouse SQL (parse, validate, inject tenant guards )
139+ * 1. Compiles the TSQL query to ClickHouse SQL (parse, validate, inject enforced WHERE clauses )
127140 * 2. Executes the query and returns validated results
128141 *
129142 * @example
@@ -132,10 +145,12 @@ export type TSQLQueryResult<T> = [QueryError, null] | [null, TSQLQuerySuccess<T>
132145 * name: "get_task_runs",
133146 * query: "SELECT id, status FROM task_runs WHERE status = 'completed' ORDER BY created_at DESC LIMIT 100",
134147 * schema: z.object({ id: z.string(), status: z.string() }),
135- * organizationId: "org_123",
136- * projectId: "proj_456",
137- * environmentId: "env_789",
138148 * tableSchema: [taskRunsSchema],
149+ * enforcedWhereClause: {
150+ * organization_id: { op: "eq", value: "org_123" },
151+ * project_id: { op: "eq", value: "proj_456" },
152+ * environment_id: { op: "eq", value: "env_789" },
153+ * },
139154 * });
140155 * ```
141156 */
@@ -152,10 +167,8 @@ export async function executeTSQL<TOut extends z.ZodSchema>(
152167 try {
153168 // 1. Compile the TSQL query to ClickHouse SQL
154169 const { sql, params, columns, hiddenColumns } = compileTSQL ( options . query , {
155- organizationId : options . organizationId ,
156- projectId : options . projectId ,
157- environmentId : options . environmentId ,
158170 tableSchema : options . tableSchema ,
171+ enforcedWhereClause : options . enforcedWhereClause ,
159172 settings : options . querySettings ,
160173 fieldMappings : options . fieldMappings ,
161174 whereClauseFallback : options . whereClauseFallback ,
@@ -284,9 +297,11 @@ export async function executeTSQL<TOut extends z.ZodSchema>(
284297 * name: "get_task_runs",
285298 * query: "SELECT * FROM task_runs LIMIT 10",
286299 * schema: taskRunRowSchema,
287- * organizationId: "org_123",
288- * projectId: "proj_456",
289- * environmentId: "env_789",
300+ * enforcedWhereClause: {
301+ * organization_id: { op: "eq", value: "org_123" },
302+ * project_id: { op: "eq", value: "proj_456" },
303+ * environment_id: { op: "eq", value: "env_789" },
304+ * },
290305 * });
291306 * ```
292307 */
0 commit comments