From 66992f33f81e4d1ae06db90fe6e678d0ff1ce603 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 17 Dec 2025 09:19:59 +0000 Subject: [PATCH] Changed some HogQL reference to TSQL --- internal-packages/tsql/README.md | 8 +- internal-packages/tsql/src/index.ts | 4 +- internal-packages/tsql/src/query/ast.ts | 29 ++-- internal-packages/tsql/src/query/constants.ts | 7 +- internal-packages/tsql/src/query/context.ts | 23 ++- internal-packages/tsql/src/query/database.ts | 142 ++++++------------ internal-packages/tsql/src/query/errors.ts | 25 ++- internal-packages/tsql/src/query/models.ts | 11 +- internal-packages/tsql/src/query/parser.ts | 54 +++---- .../tsql/src/query/property_types.ts | 43 +++--- internal-packages/tsql/src/query/timings.ts | 9 +- 11 files changed, 147 insertions(+), 208 deletions(-) diff --git a/internal-packages/tsql/README.md b/internal-packages/tsql/README.md index e7a58c92e..2e4992c6c 100644 --- a/internal-packages/tsql/README.md +++ b/internal-packages/tsql/README.md @@ -2,11 +2,15 @@ TriggerSQL is a DSL that is safely converted into ClickHouse SQL queries with protection against SQL injection and it's tenant-safe (users can only query their own data). +## Attribution + +This package is derived from [PostHog's HogQL](https://github.com/PostHog/posthog/tree/master/posthog/hogql) (MIT License). See [NOTICE.md](./NOTICE.md) for the full copyright notice. + ## ANTLR Grammar -The ANTLR grammer is heavily inspired by [PostHog's HogQL](https://github.com/PostHog/posthog/tree/master/posthog/hogql). +The ANTLR grammar is heavily inspired by PostHog's HogQL. -These are found in [./grammar] and are the `.g4` files. +These are found in [./src/grammar](./src/grammar) and are the `.g4` files. ## Generating the source code diff --git a/internal-packages/tsql/src/index.ts b/internal-packages/tsql/src/index.ts index e7b8d05bd..a63195176 100644 --- a/internal-packages/tsql/src/index.ts +++ b/internal-packages/tsql/src/index.ts @@ -1,5 +1,5 @@ -// TSQL - Type-Safe SQL Query Language -// A TypeScript port of PostHog's HogQL for ClickHouse queries +// TSQL - Type-Safe SQL Query Language for ClickHouse +// Originally derived from PostHog's HogQL (see NOTICE.md for attribution) import { CharStreams, CommonTokenStream } from "antlr4ts"; import type { ANTLRErrorListener, RecognitionException, Recognizer } from "antlr4ts"; diff --git a/internal-packages/tsql/src/query/ast.ts b/internal-packages/tsql/src/query/ast.ts index 6bd1906c1..3aaae9575 100644 --- a/internal-packages/tsql/src/query/ast.ts +++ b/internal-packages/tsql/src/query/ast.ts @@ -1,7 +1,6 @@ // TypeScript translation of posthog/hogql/ast.py -// Keep this file in sync with the Python version -import type { HogQLContext } from "./context"; +import type { TSQLContext } from "./context"; import type { DatabaseField, ExpressionField, @@ -15,7 +14,7 @@ import type { UnknownDatabaseField, VirtualTable, } from "./models"; -import type { ConstantDataType, HogQLQuerySettings } from "./constants"; +import type { ConstantDataType, TSQLQuerySettings } from "./constants"; // Base types export interface AST { @@ -25,10 +24,10 @@ export interface AST { } export interface Type extends AST { - get_child?(name: string, context: HogQLContext): Type; - has_child?(name: string, context: HogQLContext): boolean; - resolve_constant_type?(context: HogQLContext): ConstantType; - resolve_column_constant_type?(name: string, context: HogQLContext): ConstantType; + get_child?(name: string, context: TSQLContext): Type; + has_child?(name: string, context: TSQLContext): boolean; + resolve_constant_type?(context: TSQLContext): ConstantType; + resolve_column_constant_type?(name: string, context: TSQLContext): ConstantType; } export interface Expr extends AST { @@ -76,7 +75,7 @@ export type Expression = | SelectSetQuery | RatioExpr | SampleExpr - | HogQLXTag; + | TSQLXTag; export interface CTE extends Expr { expression_type: "cte"; @@ -98,7 +97,7 @@ export interface FieldAliasType extends Type { } export interface BaseTableType extends Type { - resolve_database_table?(context: HogQLContext): Table; + resolve_database_table?(context: TSQLContext): Table; } export interface TableType extends BaseTableType { @@ -504,7 +503,7 @@ export interface JoinExpr extends Expr { expression_type: "join_expr"; type?: TableOrSelectType; join_type?: string; - table?: SelectQuery | SelectSetQuery | Placeholder | HogQLXTag | Field; + table?: SelectQuery | SelectSetQuery | Placeholder | TSQLXTag | Field; table_args?: Expression[]; alias?: string; table_final?: boolean; @@ -563,7 +562,7 @@ export interface SelectQuery extends Expr { limit_by?: LimitByExpr; limit_with_ties?: boolean; offset?: Expression; - settings?: HogQLQuerySettings; + settings?: TSQLQuerySettings; view_name?: string; } @@ -603,15 +602,15 @@ export interface SampleExpr extends Expr { offset_value?: RatioExpr; } -export interface HogQLXAttribute extends AST { +export interface TSQLXAttribute extends AST { name: string; value: any; } -export interface HogQLXTag extends Expr { - expression_type: "hogqlx_tag"; +export interface TSQLXTag extends Expr { + expression_type: "tsqlx_tag"; kind: string; - attributes: HogQLXAttribute[]; + attributes: TSQLXAttribute[]; // Equivalent to to_dict() method to_dict?(): Record; } diff --git a/internal-packages/tsql/src/query/constants.ts b/internal-packages/tsql/src/query/constants.ts index c4014ba70..f698d3cc7 100644 --- a/internal-packages/tsql/src/query/constants.ts +++ b/internal-packages/tsql/src/query/constants.ts @@ -1,5 +1,4 @@ // TypeScript translation of posthog/hogql/constants.py -// Keep this file in sync with the Python version export type ConstantDataType = | "int" @@ -45,15 +44,15 @@ export enum LimitContext { } // Settings applied at the SELECT level -export interface HogQLQuerySettings { +export interface TSQLQuerySettings { optimize_aggregation_in_order?: boolean; date_time_output_format?: string; date_time_input_format?: string; join_algorithm?: string; } -// Settings applied on top of all HogQL queries -export interface HogQLGlobalSettings extends HogQLQuerySettings { +// Settings applied on top of all TSQL queries +export interface TSQLGlobalSettings extends TSQLQuerySettings { readonly?: number; max_execution_time?: number; max_memory_usage?: number; diff --git a/internal-packages/tsql/src/query/context.ts b/internal-packages/tsql/src/query/context.ts index e964bf299..249d3faa2 100644 --- a/internal-packages/tsql/src/query/context.ts +++ b/internal-packages/tsql/src/query/context.ts @@ -1,19 +1,18 @@ // TypeScript translation of posthog/hogql/context.py -// Keep this file in sync with the Python version import type { LimitContext } from "./constants"; import type { Database } from "./database"; import type { PropertySwapper } from "./property_types"; -import type { HogQLTimings } from "./timings"; +import type { TSQLTimings } from "./timings"; -export interface HogQLNotice { +export interface TSQLNotice { start?: number; end?: number; message: string; fix?: string; } -export interface HogQLQueryModifiers { +export interface TSQLQueryModifiers { optimizeJoinedFilters?: boolean; debug?: boolean; timings?: boolean; @@ -24,7 +23,7 @@ export interface HogQLQueryModifiers { optimizeProjections?: boolean; } -export interface HogQLFieldAccess { +export interface TSQLFieldAccess { input: string[]; type?: "run"; field?: string; @@ -36,22 +35,22 @@ export interface Team { project_id: number; } -export interface HogQLContext { +export interface TSQLContext { team_id?: number; team?: Team; database?: Database; values: Record; - within_non_hogql_query?: boolean; + within_non_tsql_query?: boolean; enable_select_queries?: boolean; limit_top_select?: boolean; limit_context?: LimitContext; output_format?: string | null; globals?: Record; - warnings: HogQLNotice[]; - notices: HogQLNotice[]; - errors: HogQLNotice[]; - timings: HogQLTimings; - modifiers: HogQLQueryModifiers; + warnings: TSQLNotice[]; + notices: TSQLNotice[]; + errors: TSQLNotice[]; + timings: TSQLTimings; + modifiers: TSQLQueryModifiers; debug?: boolean; property_swapper?: PropertySwapper; } diff --git a/internal-packages/tsql/src/query/database.ts b/internal-packages/tsql/src/query/database.ts index cfe1a2dfe..e91e0132c 100644 --- a/internal-packages/tsql/src/query/database.ts +++ b/internal-packages/tsql/src/query/database.ts @@ -1,5 +1,4 @@ // TypeScript translation of posthog/hogql/database/database.py -// Keep this file in sync with the Python version // // NOTE: This implementation requires database/ORM access for: // - serialize() method (needs DataWarehouseTable, DataWarehouseSavedQuery queries) @@ -7,7 +6,7 @@ // Adapt these methods to your database/ORM setup import type { ConstantType } from "./ast"; -import type { HogQLContext, HogQLQueryModifiers, Team } from "./context"; +import type { TSQLContext, TSQLQueryModifiers, Team } from "./context"; import type { DatabaseField, ExpressionField, @@ -18,16 +17,12 @@ import type { TableNode, VirtualTable, } from "./models"; -import type { HogQLTimings } from "./timings"; +import type { TSQLTimings } from "./timings"; import { QueryError, ResolutionError } from "./errors"; -import { HogQLTimings as HogQLTimingsClass } from "./timings"; +import { TSQLTimings as TSQLTimingsClass } from "./timings"; // Type definitions for schema serialization (adapt to your schema types) export interface DatabaseSchemaTable { - // Base schema table type -} - -export interface DatabaseSchemaPostHogTable extends DatabaseSchemaTable { fields: Record; id: string; name: string; @@ -78,7 +73,7 @@ export interface DatabaseSchemaEndpointTable extends DatabaseSchemaTable { export interface DatabaseSchemaField { name: string; - hogql_value: string; + tsql_value: string; type: DatabaseSerializedFieldType; schema_valid: boolean; fields?: string[]; @@ -143,6 +138,7 @@ export class Database { private _warehouseTableNames: string[] = []; private _warehouseSelfManagedTableNames: string[] = []; private _viewTableNames: string[] = []; + private _coreTableNames: string[] = []; private _timezone?: string | null; private _weekStartDay?: string | null; // WeekStartDay enum @@ -154,10 +150,6 @@ export class Database { this.tables = new TableNodeImpl("root"); this._timezone = timezone || null; this._weekStartDay = weekStartDay || null; - - // NOTE: In Python, tables are initialized with all PostHog tables. - // You'll need to initialize these based on your table definitions. - // For now, this is a minimal structure. } getTimezone(): string { @@ -221,20 +213,19 @@ export class Database { const warehouseTableNames = this._warehouseTableNames.filter((x) => x.includes(".")); return [ - ...this.getPosthogTableNames(), + ...this._coreTableNames, ...warehouseTableNames, ...this._warehouseSelfManagedTableNames, ...this._viewTableNames, ]; } - // These are the tables exposed via SQL editor autocomplete and data management - getPosthogTableNames(): string[] { - return ["events", "groups", "persons", "sessions", ...this.getSystemTableNames()]; + // Core tables exposed via SQL editor autocomplete and data management + getCoreTableNames(): string[] { + return [...this._coreTableNames, ...this.getSystemTableNames()]; } getSystemTableNames(): string[] { - // NOTE: Adapt this based on your SystemTables implementation const systemNode = this.tables.children["system"]; if (systemNode && systemNode.resolve_all_table_names) { return ["query_log", ...systemNode.resolve_all_table_names()]; @@ -250,6 +241,13 @@ export class Database { return this._viewTableNames; } + addCoreTable(tableName: string, node: TableNode): void { + if (this.tables.add_child) { + this.tables.add_child(node); + } + this._coreTableNames.push(tableName); + } + private _addWarehouseTables(node: TableNode): void { if (this.tables.merge_with) { this.tables.merge_with(node); @@ -280,7 +278,7 @@ export class Database { } } - serialize(context: HogQLContext, includeOnly?: Set): Record { + serialize(context: TSQLContext, includeOnly?: Set): Record { // NOTE: This method requires database queries to fetch: // - DataWarehouseTable objects // - DataWarehouseSavedQuery objects @@ -294,9 +292,9 @@ export class Database { throw new ResolutionError("Must provide team_id to serialize database"); } - // PostHog tables - const posthogTableNames = this.getPosthogTableNames(); - for (const tableName of posthogTableNames) { + // Core tables + const coreTableNames = this.getCoreTableNames(); + for (const tableName of coreTableNames) { if (includeOnly && !includeOnly.has(tableName)) { continue; } @@ -309,13 +307,7 @@ export class Database { fieldInput = table.fields; } - const fields = serializeFields( - fieldInput, - context, - tableName.split("."), - undefined, - "posthog" - ); + const fields = serializeFields(fieldInput, context, tableName.split("."), undefined); const fieldsDict: Record = {}; for (const field of fields) { fieldsDict[field.name] = field; @@ -324,7 +316,7 @@ export class Database { fields: fieldsDict, id: tableName, name: tableName, - } as DatabaseSchemaPostHogTable; + } as DatabaseSchemaTable; } // System tables @@ -342,13 +334,7 @@ export class Database { systemFieldInput = table.fields; } - const fields = serializeFields( - systemFieldInput, - context, - tableKey.split("."), - undefined, - "posthog" - ); + const fields = serializeFields(systemFieldInput, context, tableKey.split("."), undefined); const fieldsDict: Record = {}; for (const field of fields) { fieldsDict[field.name] = field; @@ -373,8 +359,8 @@ export class Database { teamId?: number, options?: { team?: Team; - modifiers?: HogQLQueryModifiers; - timings?: HogQLTimings; + modifiers?: TSQLQueryModifiers; + timings?: TSQLTimings; } ): Database { // NOTE: This method requires extensive database/ORM access: @@ -387,7 +373,7 @@ export class Database { // // This is a skeleton structure - adapt to your setup - const timings = options?.timings || new HogQLTimingsClass(); + const timings = options?.timings || new TSQLTimingsClass(); const { team, modifiers } = options || {}; // Validate team/teamId @@ -412,7 +398,6 @@ export class Database { // NOTE: Apply modifiers, setup tables, etc. // This requires extensive database access and table setup logic - // See Python implementation for full details return database; } @@ -420,7 +405,7 @@ export class Database { // Helper functions -const HOGQL_CHARACTERS_TO_BE_WRAPPED = ["@", "-", "!", "$", "+"]; +const TSQL_CHARACTERS_TO_BE_WRAPPED = ["@", "-", "!", "$", "+"]; function constantTypeToSerializedFieldType( constantType: ConstantType @@ -484,10 +469,9 @@ function constantTypeToSerializedFieldType( export function serializeFields( fieldInput: Record, - context: HogQLContext, + context: TSQLContext, tableChain: string[], - dbColumns?: Record, // DataWarehouseTableColumns - tableType: "posthog" | "external" = "posthog" + dbColumns?: Record // DataWarehouseTableColumns ): DatabaseSchemaField[] { // NOTE: This requires resolve_types_from_table from resolver // Import as needed: import { resolveTypesFromTable } from '../resolver'; @@ -506,21 +490,18 @@ export function serializeFields( } } - let hogqlValue: string; - if (HOGQL_CHARACTERS_TO_BE_WRAPPED.some((char) => fieldKey.includes(char))) { - hogqlValue = `\`${fieldKey}\``; + let tsqlValue: string; + if (TSQL_CHARACTERS_TO_BE_WRAPPED.some((char) => fieldKey.includes(char))) { + tsqlValue = `\`${fieldKey}\``; } else { - hogqlValue = fieldKey; + tsqlValue = fieldKey; } if ("hidden" in field && field.hidden) { continue; } - if (fieldKey === "team_id" && tableType === "posthog") { - // Skip team_id for posthog tables - continue; - } else if ("name" in field && "get_constant_type" in field) { + if ("name" in field && "get_constant_type" in field) { // DatabaseField const dbField = field as DatabaseField; let fieldType: DatabaseSerializedFieldType; @@ -538,7 +519,7 @@ export function serializeFields( fieldOutput.push({ name: fieldKey, - hogql_value: hogqlValue, + tsql_value: tsqlValue, type: fieldType, schema_valid: schemaValid, }); @@ -546,13 +527,13 @@ export function serializeFields( // ExpressionField const exprField = field as ExpressionField; // NOTE: Requires resolve_types_from_table - // const resolvedExpr = resolveTypesFromTable(exprField.expr, tableChain, context, 'hogql'); + // const resolvedExpr = resolveTypesFromTable(exprField.expr, tableChain, context, 'tsql'); // const constantType = resolvedExpr.type?.resolve_constant_type(context); // const fieldType = constantTypeToSerializedFieldType(constantType) || DatabaseSerializedFieldType.EXPRESSION; fieldOutput.push({ name: fieldKey, - hogql_value: hogqlValue, + tsql_value: tsqlValue, type: DatabaseSerializedFieldType.EXPRESSION, schema_valid: schemaValid, }); @@ -568,10 +549,10 @@ export function serializeFields( fieldOutput.push({ name: fieldKey, - hogql_value: hogqlValue, + tsql_value: tsqlValue, type, schema_valid: schemaValid, - table: resolvedTable.to_printed_hogql ? resolvedTable.to_printed_hogql() : fieldKey, + table: resolvedTable.to_printed_tsql ? resolvedTable.to_printed_tsql() : fieldKey, fields: "fields" in resolvedTable ? Object.keys(resolvedTable.fields) : [], id: "id" in resolvedTable && resolvedTable.id ? String(resolvedTable.id) : fieldKey, }); @@ -581,10 +562,10 @@ export function serializeFields( const virtualTable = field as VirtualTable; fieldOutput.push({ name: fieldKey, - hogql_value: hogqlValue, + tsql_value: tsqlValue, type: DatabaseSerializedFieldType.VIRTUAL_TABLE, schema_valid: schemaValid, - table: virtualTable.to_printed_hogql ? virtualTable.to_printed_hogql() : fieldKey, + table: virtualTable.to_printed_tsql ? virtualTable.to_printed_tsql() : fieldKey, fields: Object.keys(virtualTable.fields), }); } else if ("chain" in field) { @@ -592,7 +573,7 @@ export function serializeFields( const traverser = field as FieldTraverser; fieldOutput.push({ name: fieldKey, - hogql_value: hogqlValue, + tsql_value: tsqlValue, type: DatabaseSerializedFieldType.FIELD_TRAVERSER, schema_valid: schemaValid, chain: traverser.chain, @@ -602,42 +583,3 @@ export function serializeFields( return fieldOutput; } - -// Helper functions for database setup (simplified versions) -function usePersonPropertiesFromEvents(database: Database): void { - const table = database.getTable("events"); - table.fields["person"] = { chain: ["poe"] } as FieldTraverser; -} - -function usePersonIdFromPersonOverrides(database: Database): void { - const table = database.getTable("events"); - table.fields["event_person_id"] = { name: "person_id" } as DatabaseField; - // NOTE: Setup LazyJoin and ExpressionField for override logic - // This requires complex setup - see Python implementation -} - -function useErrorTrackingIssueIdFromErrorTrackingIssueOverrides(database: Database): void { - const table = database.getTable("events"); - // NOTE: Setup ExpressionField and LazyJoin for error tracking - // See Python implementation for details -} - -function setupGroupKeyFields(database: Database, team: Team): void { - // NOTE: Requires GroupTypeMapping queries from database - // See Python implementation for full logic - const table = database.getTable("events"); - // Setup group key fields based on GroupTypeMapping -} - -function useVirtualFields( - database: Database, - modifiers: HogQLQueryModifiers, - timings: HogQLTimings -): void { - // NOTE: Requires channel type creation functions - // See Python implementation for full virtual field setup - const eventsTable = database.getTable("events"); - const personsTable = database.getTable("persons"); - const groupsTable = database.getTable("groups"); - // Setup virtual fields like initial_referring_domain_type, initial_channel_type, revenue fields -} diff --git a/internal-packages/tsql/src/query/errors.ts b/internal-packages/tsql/src/query/errors.ts index 235db7891..c0da0d3ce 100644 --- a/internal-packages/tsql/src/query/errors.ts +++ b/internal-packages/tsql/src/query/errors.ts @@ -1,9 +1,8 @@ // TypeScript translation of posthog/hogql/errors.py -// Keep this file in sync with the Python version import type { Expr } from "./ast"; -export class BaseHogQLError extends Error { +export class BaseTSQLError extends Error { message: string; start?: number; end?: number; @@ -29,34 +28,34 @@ export class BaseHogQLError extends Error { } } -export class ExposedHogQLError extends BaseHogQLError { +export class ExposedTSQLError extends BaseTSQLError { /** An exception that can be exposed to the user. */ } -export class InternalHogQLError extends BaseHogQLError { - /** An internal exception in the HogQL engine. */ +export class InternalTSQLError extends BaseTSQLError { + /** An internal exception in the TSQL engine. */ } -export class SyntaxError extends ExposedHogQLError { - /** The input does not conform to HogQL syntax. */ +export class SyntaxError extends ExposedTSQLError { + /** The input does not conform to TSQL syntax. */ } -export class QueryError extends ExposedHogQLError { +export class QueryError extends ExposedTSQLError { /** The query is invalid, though correct syntactically. */ } -export class NotImplementedError extends InternalHogQLError { - /** This feature isn't implemented in HogQL (yet). */ +export class NotImplementedError extends InternalTSQLError { + /** This feature isn't implemented in TSQL (yet). */ } -export class ParsingError extends InternalHogQLError { +export class ParsingError extends InternalTSQLError { /** Parsing failed. */ } -export class ImpossibleASTError extends InternalHogQLError { +export class ImpossibleASTError extends InternalTSQLError { /** Parsing or resolution resulted in an impossible AST. */ } -export class ResolutionError extends InternalHogQLError { +export class ResolutionError extends InternalTSQLError { /** Resolution of a table/field/expression failed. */ } diff --git a/internal-packages/tsql/src/query/models.ts b/internal-packages/tsql/src/query/models.ts index 7b8ebff94..49c8bd4f3 100644 --- a/internal-packages/tsql/src/query/models.ts +++ b/internal-packages/tsql/src/query/models.ts @@ -1,8 +1,7 @@ // TypeScript translation of posthog/hogql/database/models.py -// Keep this file in sync with the Python version import type { Expr, ConstantType } from "./ast"; -import type { HogQLContext } from "./context"; +import type { TSQLContext } from "./context"; export interface FieldOrTable { hidden?: boolean; @@ -43,15 +42,15 @@ export interface Table extends FieldOrTable { fields: Record; has_field?(name: string | number): boolean; get_field?(name: string | number): FieldOrTable; - to_printed_clickhouse?(context: HogQLContext): string; - to_printed_hogql?(): string; + to_printed_clickhouse?(context: TSQLContext): string; + to_printed_tsql?(): string; avoid_asterisk_fields?(): string[]; get_asterisk?(): Record; } export interface LazyJoin extends FieldOrTable { join_function?(from_table: Table, to_table: Table, requesting_table: Table): Expr; - resolve_table?(context: HogQLContext): Table; + resolve_table?(context: TSQLContext): Table; } export interface LazyTable extends Table {} @@ -63,7 +62,7 @@ export interface SavedQuery extends Table { } export interface FunctionCallTable extends Table { - call_function?(context: HogQLContext): Expr; + call_function?(context: TSQLContext): Expr; } export interface TableNode { diff --git a/internal-packages/tsql/src/query/parser.ts b/internal-packages/tsql/src/query/parser.ts index 48b0e532b..28330e81f 100644 --- a/internal-packages/tsql/src/query/parser.ts +++ b/internal-packages/tsql/src/query/parser.ts @@ -148,8 +148,8 @@ import { ForInStatement, ForStatement, Function, - HogQLXAttribute, - HogQLXTag, + TSQLXAttribute, + TSQLXTag, IfStatement, JoinConstraint, JoinExpr, @@ -181,7 +181,7 @@ import { WindowFunction, } from "./ast"; import { RESERVED_KEYWORDS } from "./constants"; -import { BaseHogQLError, NotImplementedError, SyntaxError } from "./errors"; +import { BaseTSQLError, NotImplementedError, SyntaxError } from "./errors"; import { parseStringLiteralText } from "./parse_string"; /** @@ -339,7 +339,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor { } return node; } catch (e: any) { - if (e instanceof BaseHogQLError) { + if (e instanceof BaseTSQLError) { if ( start !== undefined && end !== undefined && @@ -570,7 +570,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor { } // SELECT statements - visitSelect(ctx: SelectContext): SelectQuery | SelectSetQuery | HogQLXTag { + visitSelect(ctx: SelectContext): SelectQuery | SelectSetQuery | TSQLXTag { const selectSetStmt = ctx.selectSetStmt(); const selectStmt = ctx.selectStmt(); const tSQLxTagElement = ctx.tSQLxTagElement(); @@ -579,7 +579,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor { } else if (selectStmt) { return this.visitSelectStmt(selectStmt); } else if (tSQLxTagElement) { - return this.visitHogqlxTagElementNested(tSQLxTagElement); + return this.visitTsqlxTagElementNested(tSQLxTagElement); } throw new SyntaxError( "Select statement must be either a select set statement, a select statement, or a tSQLx tag element" @@ -844,7 +844,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor { return tableResult; } // Otherwise, wrap the table expression in a JoinExpr - const table = tableResult as SelectQuery | SelectSetQuery | Placeholder | HogQLXTag | Field; + const table = tableResult as SelectQuery | SelectSetQuery | Placeholder | TSQLXTag | Field; return { expression_type: "join_expr", table, @@ -856,13 +856,13 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor { /** Helper for visiting table expressions that may return JoinExpr or table types */ private visitTableExprResult( ctx: ParserRuleContext - ): JoinExpr | SelectQuery | SelectSetQuery | Placeholder | HogQLXTag | Field { + ): JoinExpr | SelectQuery | SelectSetQuery | Placeholder | TSQLXTag | Field { return this.visit(ctx) as | JoinExpr | SelectQuery | SelectSetQuery | Placeholder - | HogQLXTag + | TSQLXTag | Field; } @@ -1512,7 +1512,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor { return tableResult; } // Otherwise, wrap in a JoinExpr - const table = tableResult as SelectQuery | SelectSetQuery | Placeholder | HogQLXTag | Field; + const table = tableResult as SelectQuery | SelectSetQuery | Placeholder | TSQLXTag | Field; return { expression_type: "join_expr", table, alias }; } @@ -1520,8 +1520,8 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor { return this.visitTableFunctionExpr(ctx.tableFunctionExpr()); } - visitTableExprTag(ctx: TableExprTagContext): HogQLXTag { - return this.visitHogqlxTagElementNested(ctx.tSQLxTagElement()); + visitTableExprTag(ctx: TableExprTagContext): TSQLXTag { + return this.visitTsqlxTagElementNested(ctx.tSQLxTagElement()); } visitTableFunctionExpr(ctx: TableFunctionExprContext): JoinExpr { @@ -1670,51 +1670,51 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor { }; } - visitHogqlxChildElement(ctx: TSQLxChildElementContext): Expression { + visitTsqlxChildElement(ctx: TSQLxChildElementContext): Expression { const tSQLxTagElement = ctx.tSQLxTagElement(); if (tSQLxTagElement) { - return this.visitHogqlxTagElementNested(tSQLxTagElement); + return this.visitTsqlxTagElementNested(tSQLxTagElement); } if (ctx.TSQLX_TEXT_TEXT()) { - return this.visitHogqlxText(ctx); + return this.visitTsqlxText(ctx); } return this.visitAsExpr(ctx.columnExpr()!); } - visitHogqlxText(ctx: TSQLxChildElementContext): Constant { + visitTsqlxText(ctx: TSQLxChildElementContext): Constant { const text = ctx.TSQLX_TEXT_TEXT(); return { expression_type: "constant", value: text ? text.text : "" }; } - visitHogqlxTagElementClosed(ctx: TSQLxTagElementContext): HogQLXTag { + visitTsqlxTagElementClosed(ctx: TSQLxTagElementContext): TSQLXTag { const kind = this.visitIdentifier(ctx.identifier()[0]); const attributes = ctx.tSQLxTagAttribute() ? ctx .tSQLxTagAttribute() - .map((a: TSQLxTagAttributeContext) => this.visitHogqlxTagAttribute(a)) + .map((a: TSQLxTagAttributeContext) => this.visitTsqlxTagAttribute(a)) : []; - return { expression_type: "hogqlx_tag", kind, attributes }; + return { expression_type: "tsqlx_tag", kind, attributes }; } - visitHogqlxTagElementNested(ctx: TSQLxTagElementContext): HogQLXTag { + visitTsqlxTagElementNested(ctx: TSQLxTagElementContext): TSQLXTag { const opening = this.visitIdentifier(ctx.identifier(0)); const closing = this.visitIdentifier(ctx.identifier(1)); if (opening !== closing) { throw new SyntaxError( - `Opening and closing HogQLX tags must match. Got ${opening} and ${closing}` + `Opening and closing TSQLX tags must match. Got ${opening} and ${closing}` ); } const attributes = ctx.tSQLxTagAttribute() ? ctx .tSQLxTagAttribute() - .map((a: TSQLxTagAttributeContext) => this.visitHogqlxTagAttribute(a)) + .map((a: TSQLxTagAttributeContext) => this.visitTsqlxTagAttribute(a)) : []; // ── collect child nodes, discarding pure-indentation whitespace ── const keptChildren: Expression[] = []; for (const element of ctx.tSQLxChildElement()) { - const child = this.visitHogqlxChildElement(element); + const child = this.visitTsqlxChildElement(element); if ("value" in child && typeof child.value === "string") { const v = child.value; @@ -1729,18 +1729,18 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor { } if (keptChildren.length > 0) { - if (attributes.some((a: HogQLXAttribute) => a.name === "children")) { + if (attributes.some((a: TSQLXAttribute) => a.name === "children")) { throw new SyntaxError( - "Can't have a HogQLX tag with both children and a 'children' attribute" + "Can't have a TSQLX tag with both children and a 'children' attribute" ); } attributes.push({ name: "children", value: keptChildren }); } - return { expression_type: "hogqlx_tag", kind: opening, attributes }; + return { expression_type: "tsqlx_tag", kind: opening, attributes }; } - visitHogqlxTagAttribute(ctx: TSQLxTagAttributeContext): HogQLXAttribute { + visitTsqlxTagAttribute(ctx: TSQLxTagAttributeContext): TSQLXAttribute { const name = this.visitIdentifier(ctx.identifier()); const columnExpr = ctx.columnExpr(); const string = ctx.string(); diff --git a/internal-packages/tsql/src/query/property_types.ts b/internal-packages/tsql/src/query/property_types.ts index 13f2a1dcf..ff2d74c8e 100644 --- a/internal-packages/tsql/src/query/property_types.ts +++ b/internal-packages/tsql/src/query/property_types.ts @@ -1,5 +1,4 @@ // TypeScript translation of posthog/hogql/transforms/property_types.py -// Keep this file in sync with the Python version import type { AST, @@ -16,20 +15,20 @@ import type { CallType, DateTimeType, } from "./ast"; -import type { HogQLContext } from "./context"; +import type { TSQLContext } from "./context"; import type { BooleanDatabaseField, DateTimeDatabaseField, Table } from "./models"; -// Helper function to escape HogQL identifiers -function escapeHogQLIdentifier(identifier: string | number): string { +// Helper function to escape TSQL identifiers +function escapeTSQLIdentifier(identifier: string | number): string { if (typeof identifier === "number") { return String(identifier); } if (identifier.includes("%")) { throw new Error( - `The HogQL identifier "${identifier}" is not permitted as it contains the "%" character` + `The TSQL identifier "${identifier}" is not permitted as it contains the "%" character` ); } - // HogQL allows dollars in the identifier + // TSQL allows dollars in the identifier if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) { return identifier; } @@ -70,8 +69,8 @@ function getVisitorMethodName(node: AST): string { // Handle special cases (matching Python replacements) const replacements: Record = { - hog_qlxtag: "hogqlx_tag", - hog_qlxattribute: "hogqlx_attribute", + tsqlxtag: "tsqlx_tag", + tsqlxattribute: "tsqlx_attribute", uuidtype: "uuid_type", string_jsontype: "string_json_type", }; @@ -264,13 +263,13 @@ class CloningVisitor extends Visitor { // PropertyFinder: Traverses AST to find all property references class PropertyFinder extends TraversingVisitor { - context: HogQLContext; + context: TSQLContext; personProperties: Set = new Set(); eventProperties: Set = new Set(); groupProperties: Map> = new Map(); foundTimestamps: boolean = false; - constructor(context: HogQLContext) { + constructor(context: TSQLContext) { super(); this.context = context; } @@ -281,7 +280,7 @@ class PropertyFinder extends TraversingVisitor { if (this.isBaseTableType(tableType)) { const table = tableType.resolve_database_table?.(this.context); if (table) { - const tableName = table.to_printed_hogql?.() || ""; + const tableName = table.to_printed_tsql?.() || ""; const propertyName = String(node.chain[0]); if (tableName === "persons" || tableName === "raw_persons") { @@ -358,7 +357,7 @@ export class PropertySwapper extends CloningVisitor { eventProperties: Map; personProperties: Map; groupProperties: Map; - context: HogQLContext; + context: TSQLContext; setTimeZones: boolean; constructor( @@ -366,7 +365,7 @@ export class PropertySwapper extends CloningVisitor { eventProperties: Map | Record, personProperties: Map | Record, groupProperties: Map | Record, - context: HogQLContext, + context: TSQLContext, setTimeZones: boolean ) { super(false); // Don't clear types @@ -428,7 +427,7 @@ export class PropertySwapper extends CloningVisitor { } else if (this.isBaseTableType(tableType)) { const table = tableType.resolve_database_table?.(this.context); if (table) { - const tableName = table.to_printed_hogql?.() || ""; + const tableName = table.to_printed_tsql?.() || ""; if (tableName === "persons" || tableName === "raw_persons") { if (this.personProperties.has(propertyName)) { @@ -472,7 +471,7 @@ export class PropertySwapper extends CloningVisitor { if (this.isBaseTableType(tableType)) { const table = tableType.resolve_database_table?.(this.context); if (table) { - const tableName = table.to_printed_hogql?.() || ""; + const tableName = table.to_printed_tsql?.() || ""; if (tableName === "events") { if (this.personProperties.has(propertyName)) { return this.convertStringPropertyToType(node, "person", propertyName); @@ -490,16 +489,16 @@ export class PropertySwapper extends CloningVisitor { propertyType: "event" | "person" | "group", propertyName: string ): Expr { - let posthogFieldType: string | undefined; + let fieldTypeValue: string | undefined; if (propertyType === "person") { - posthogFieldType = this.personProperties.get(propertyName); + fieldTypeValue = this.personProperties.get(propertyName); } else if (propertyType === "group") { - posthogFieldType = this.groupProperties.get(propertyName); + fieldTypeValue = this.groupProperties.get(propertyName); } else { - posthogFieldType = this.eventProperties.get(propertyName); + fieldTypeValue = this.eventProperties.get(propertyName); } - const fieldType = posthogFieldType === "Numeric" ? "Float" : posthogFieldType || "String"; + const fieldType = fieldTypeValue === "Numeric" ? "Float" : fieldTypeValue || "String"; this.addPropertyNotice(node, propertyType, fieldType); return this.fieldTypeToPropertyCall(node, fieldType); @@ -630,7 +629,7 @@ export class PropertySwapper extends CloningVisitor { } // Only highlight the last part of the chain const lastPart = node.chain[node.chain.length - 1]; - const identifierLength = escapeHogQLIdentifier(lastPart).length; + const identifierLength = escapeTSQLIdentifier(lastPart).length; this.context.notices.push({ start: Math.max(node.start, node.end - identifierLength), end: node.end, @@ -672,7 +671,7 @@ export class PropertySwapper extends CloningVisitor { } // Main function to build property swapper -export function buildPropertySwapper(node: AST, context: HogQLContext): void { +export function buildPropertySwapper(node: AST, context: TSQLContext): void { if (!context || !context.team_id) { return; } diff --git a/internal-packages/tsql/src/query/timings.ts b/internal-packages/tsql/src/query/timings.ts index 17838abfa..d868fe066 100644 --- a/internal-packages/tsql/src/query/timings.ts +++ b/internal-packages/tsql/src/query/timings.ts @@ -1,5 +1,4 @@ // TypeScript translation of posthog/hogql/timings.py -// Keep this file in sync with the Python version /** * Get performance counter in milliseconds (Node.js equivalent of perf_counter) @@ -31,8 +30,8 @@ const TIMING_DECIMAL_PLACES = 3; // round to milliseconds // Not thread safe. // See trends_query_runner for an example of how to use for multithreaded queries -export class HogQLTimings { - // Completed time in seconds for different parts of the HogQL query +export class TSQLTimings { + // Completed time in seconds for different parts of the TSQL query timings: Record = {}; // Used for housekeeping @@ -44,8 +43,8 @@ export class HogQLTimings { this._timingStarts[this._timingPointer] = this.perfCounter(); } - cloneForSubquery(seriesIndex: number): HogQLTimings { - return new HogQLTimings(`${this._timingPointer}/series_${seriesIndex}`); + cloneForSubquery(seriesIndex: number): TSQLTimings { + return new TSQLTimings(`${this._timingPointer}/series_${seriesIndex}`); } clearTimings(): void {