Adding stronger types

This commit is contained in:
Matt Aitken
2025-12-11 11:41:29 +00:00
parent fa7db2c965
commit e4ffbf4935
3 changed files with 311 additions and 158 deletions
+127 -58
View File
@@ -45,9 +45,43 @@ export interface UnknownType extends ConstantType {
data_type: "unknown";
}
export type Expression =
| CTE
| Alias
| ArithmeticOperation
| And
| Or
| CompareOperation
| Not
| BetweenExpr
| OrderExpr
| ArrayAccess
| Array
| Dict
| TupleAccess
| Tuple
| Lambda
| Constant
| Field
| Placeholder
| Call
| ExprCall
| JoinConstraint
| JoinExpr
| WindowFrameExpr
| WindowExpr
| WindowFunction
| LimitByExpr
| SelectQuery
| SelectSetQuery
| RatioExpr
| SampleExpr
| HogQLXTag;
export interface CTE extends Expr {
expression_type: "cte";
name: string;
expr: Expr;
expr: Expression;
cte_type: "column" | "subquery";
}
@@ -186,7 +220,7 @@ export interface FieldTraverserType extends Type {
export interface ExpressionFieldType extends Type {
name: string;
expr: Expr;
expr: Expression;
table_type: TableOrSelectType;
isolate_scope?: boolean;
}
@@ -265,27 +299,27 @@ export type SetOperator =
export interface Declaration extends AST {}
export interface VariableAssignment extends Declaration {
left: Expr;
right: Expr;
left: Expression;
right: Expression;
}
export interface VariableDeclaration extends Declaration {
name: string;
expr?: Expr;
expr?: Expression;
}
export interface Statement extends Declaration {}
export interface ExprStatement extends Statement {
expr?: Expr;
expr?: Expression;
}
export interface ReturnStatement extends Statement {
expr?: Expr;
expr?: Expression;
}
export interface ThrowStatement extends Statement {
expr: Expr;
expr: Expression;
}
export interface TryCatchStatement extends Statement {
@@ -295,27 +329,27 @@ export interface TryCatchStatement extends Statement {
}
export interface IfStatement extends Statement {
expr: Expr;
expr: Expression;
then: Statement;
else_?: Statement;
}
export interface WhileStatement extends Statement {
expr: Expr;
expr: Expression;
body: Statement;
}
export interface ForStatement extends Statement {
initializer?: VariableDeclaration | VariableAssignment | Expr;
condition?: Expr;
increment?: Expr;
initializer?: VariableDeclaration | VariableAssignment | Expression;
condition?: Expression;
increment?: Expression;
body: Statement;
}
export interface ForInStatement extends Statement {
keyVar?: string;
valueVar: string;
expr: Expr;
expr: Expression;
body: Statement;
}
@@ -335,120 +369,141 @@ export interface Program extends AST {
// Expression types
export interface Alias extends Expr {
expression_type: "alias";
alias: string;
expr: Expr;
expr: Expression;
hidden?: boolean;
from_asterisk?: boolean;
}
export interface ArithmeticOperation extends Expr {
left: Expr;
right: Expr;
expression_type: "arithmetic_operation";
left: Expression;
right: Expression;
op: ArithmeticOperationOp;
}
export interface And extends Expr {
expression_type: "and";
type?: ConstantType;
exprs: Expr[];
exprs: Expression[];
}
export interface Or extends Expr {
exprs: Expr[];
expression_type: "or";
exprs: Expression[];
type?: ConstantType;
}
export interface CompareOperation extends Expr {
left: Expr;
right: Expr;
expression_type: "compare_operation";
left: Expression;
right: Expression;
op: CompareOperationOp;
type?: ConstantType;
}
export interface Not extends Expr {
expr: Expr;
expression_type: "not";
expr: Expression;
type?: ConstantType;
}
export interface BetweenExpr extends Expr {
expr: Expr;
low: Expr;
high: Expr;
expression_type: "between_expr";
expr: Expression;
low: Expression;
high: Expression;
negated?: boolean;
type?: ConstantType;
}
export interface OrderExpr extends Expr {
expr: Expr;
expression_type: "order_expr";
expr: Expression;
order?: "ASC" | "DESC";
}
export interface ArrayAccess extends Expr {
array: Expr;
property: Expr;
expression_type: "array_access";
array: Expression;
property: Expression;
nullish?: boolean;
}
export interface Array extends Expr {
exprs: Expr[];
expression_type: "array";
exprs: Expression[];
}
export interface Dict extends Expr {
items: [Expr, Expr][];
expression_type: "dict";
items: [Expression, Expression][];
}
export interface TupleAccess extends Expr {
tuple: Expr;
expression_type: "tuple_access";
tuple: Expression;
index: number;
nullish?: boolean;
}
export interface Tuple extends Expr {
exprs: Expr[];
expression_type: "tuple";
exprs: Expression[];
}
export interface Lambda extends Expr {
expression_type: "lambda";
args: string[];
expr: Expr | Block;
expr: Expression | Block;
}
export interface Constant extends Expr {
expression_type: "constant";
value: any;
}
export interface Field extends Expr {
expression_type: "field";
chain: (string | number)[];
from_asterisk?: boolean;
}
export interface Placeholder extends Expr {
expr: Expr;
expression_type: "placeholder";
expr: Expression;
// Computed properties
chain?: (string | number)[] | null;
field?: string | null;
}
export interface Call extends Expr {
expression_type: "call";
name: string;
args: Expr[];
params?: Expr[];
args: Expression[];
params?: Expression[];
distinct?: boolean;
}
export interface ExprCall extends Expr {
expr: Expr;
args: Expr[];
expression_type: "expr_call";
expr: Expression;
args: Expression[];
}
export interface JoinConstraint extends Expr {
expr: Expr;
expression_type: "join_constraint";
expr: Expression;
constraint_type: "ON" | "USING";
}
export interface JoinExpr extends Expr {
expression_type: "join_expr";
type?: TableOrSelectType;
join_type?: string;
table?: SelectQuery | SelectSetQuery | Placeholder | HogQLXTag | Field;
table_args?: Expr[];
table_args?: Expression[];
alias?: string;
table_final?: boolean;
constraint?: JoinConstraint;
@@ -457,12 +512,14 @@ export interface JoinExpr extends Expr {
}
export interface WindowFrameExpr extends Expr {
expression_type: "window_frame_expr";
frame_type?: "CURRENT ROW" | "PRECEDING" | "FOLLOWING";
frame_value?: number;
}
export interface WindowExpr extends Expr {
partition_by?: Expr[];
expression_type: "window_expr";
partition_by?: Expression[];
order_by?: OrderExpr[];
frame_method?: "ROWS" | "RANGE";
frame_start?: WindowFrameExpr;
@@ -470,37 +527,40 @@ export interface WindowExpr extends Expr {
}
export interface WindowFunction extends Expr {
expression_type: "window_function";
name: string;
args?: Expr[];
exprs?: Expr[];
args?: Expression[];
exprs?: Expression[];
over_expr?: WindowExpr;
over_identifier?: string;
}
export interface LimitByExpr extends Expr {
n: Expr;
exprs: Expr[];
offset_value?: Expr;
expression_type: "limit_by_expr";
n: Expression;
exprs: Expression[];
offset_value?: Expression;
}
export interface SelectQuery extends Expr {
expression_type: "select_query";
type?: SelectQueryType;
ctes?: Record<string, CTE>;
select: Expr[];
select: Expression[];
distinct?: boolean;
select_from?: JoinExpr;
array_join_op?: string;
array_join_list?: Expr[];
array_join_list?: Expression[];
window_exprs?: Record<string, WindowExpr>;
where?: Expr;
prewhere?: Expr;
having?: Expr;
group_by?: Expr[];
where?: Expression;
prewhere?: Expression;
having?: Expression;
group_by?: Expression[];
order_by?: OrderExpr[];
limit?: Expr;
limit?: Expression;
limit_by?: LimitByExpr;
limit_with_ties?: boolean;
offset?: Expr;
offset?: Expression;
settings?: HogQLQuerySettings;
view_name?: string;
}
@@ -511,6 +571,7 @@ export interface SelectSetNode extends AST {
}
export interface SelectSetQuery extends Expr {
expression_type: "select_set_query";
type?: SelectSetQueryType;
initial_select_query: SelectQuery | SelectSetQuery;
subsequent_select_queries: SelectSetNode[];
@@ -529,11 +590,13 @@ export namespace SelectSetQuery {
}
export interface RatioExpr extends Expr {
expression_type: "ratio_expr";
left: Constant;
right?: Constant;
}
export interface SampleExpr extends Expr {
expression_type: "sample_expr";
sample_value: RatioExpr;
offset_value?: RatioExpr;
}
@@ -544,6 +607,7 @@ export interface HogQLXAttribute extends AST {
}
export interface HogQLXTag extends Expr {
expression_type: "hogqlx_tag";
kind: string;
attributes: HogQLXAttribute[];
// Equivalent to to_dict() method
@@ -557,12 +621,17 @@ export function createEmptySelectQuery(columns?: Record<string, FieldOrTable>):
}
return {
expression_type: "select_query",
select: Object.entries(columns).map(([column, field]) => ({
expression_type: "alias" as const,
alias: column,
expr: { value: (field as DatabaseField).default_value?.() ?? null } as Constant,
})) as Alias[],
where: { value: false } as Constant,
} as SelectQuery;
expr: {
expression_type: "constant",
value: (field as DatabaseField).default_value?.() ?? null,
} as Constant,
})),
where: { expression_type: "constant", value: false } as Constant,
};
}
// Add static method equivalent for SelectQuery.empty()
+179 -100
View File
@@ -57,11 +57,35 @@ import {
HogQLXAttribute,
HogQLXTag,
Declaration,
Expression,
AST,
} from "./ast";
import { RESERVED_KEYWORDS } from "./constants";
import { SyntaxError, BaseHogQLError, NotImplementedError } from "./errors";
import type { HogQLTimings } from "./timings";
import { parseStringLiteralCtx, parseStringLiteralText, parseStringTextCtx } from "./parse_string";
import {
CatchBlockContext,
DeclarationContext,
ExprContext,
ExpressionContext,
ExprStmtContext,
ForInStmtContext,
ForStmtContext,
FuncStmtContext,
IdentifierListContext,
IfStmtContext,
KvPairContext,
KvPairListContext,
ProgramContext,
ReturnStmtContext,
StatementContext,
ThrowStmtContext,
TryCatchStmtContext,
VarAssignmentContext,
VarDeclContext,
WhileStmtContext,
} from "../grammar/TSQLParser.js";
/**
* Token with position information.
@@ -182,73 +206,82 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
}
// Program and declarations
visitProgram(ctx: any): Program {
visitProgram(ctx: ProgramContext): Program {
const declarations: Declaration[] = [];
// Implement based on your parser context structure
throw new NotImplementedError("visitProgram not implemented");
}
visitDeclaration(ctx: any): Declaration {
visitDeclaration(ctx: DeclarationContext): Declaration {
return this.visitChildren(ctx);
}
visitExpression(ctx: any): Expr {
visitExpression(ctx: ExpressionContext): Expr {
return this.visitChildren(ctx);
}
visitVarDecl(ctx: any): VariableDeclaration {
visitVarDecl(ctx: VarDeclContext): VariableDeclaration {
const expr = ctx.expression();
return {
name: this.visitIdentifier(ctx.identifier()),
expr: ctx.expression() ? this.visit(ctx.expression()) : undefined,
expr: expr ? this.visit(expr) : undefined,
};
}
visitVarAssignment(ctx: any): VariableAssignment {
visitVarAssignment(ctx: VarAssignmentContext): VariableAssignment {
return {
left: this.visit(ctx.expression(0)),
right: this.visit(ctx.expression(1)),
};
}
visitStatement(ctx: any): Statement {
visitStatement(ctx: StatementContext): Statement {
return this.visitChildren(ctx);
}
visitExprStmt(ctx: any): ExprStatement {
visitExprStmt(ctx: ExprStmtContext): ExprStatement {
return {
expr: this.visit(ctx.expression()),
};
}
visitReturnStmt(ctx: any): ReturnStatement {
visitReturnStmt(ctx: ReturnStmtContext): ReturnStatement {
const expr = ctx.expression();
return {
expr: ctx.expression() ? this.visit(ctx.expression()) : undefined,
expr: expr ? this.visit(expr) : undefined,
};
}
visitThrowStmt(ctx: any): ThrowStatement {
visitThrowStmt(ctx: ThrowStmtContext): ThrowStatement {
const expr = ctx.expression();
return {
expr: ctx.expression() ? this.visit(ctx.expression()) : undefined,
expr: expr ? this.visit(expr) : undefined,
};
}
visitCatchBlock(ctx: any): [string | null, string | null, Statement] {
visitCatchBlock(ctx: CatchBlockContext): [string | null, string | null, Statement] {
const catchVar = ctx._catchVar;
const catchType = ctx._catchType;
const catchStmt = ctx._catchStmt;
return [
ctx.catchVar ? this.visit(ctx.catchVar) : null,
ctx.catchType ? this.visit(ctx.catchType) : null,
this.visit(ctx.catchStmt),
catchVar ? this.visit(catchVar) : null,
catchType ? this.visit(catchType) : null,
catchStmt ? this.visit(catchStmt) : undefined,
];
}
visitTryCatchStmt(ctx: any): TryCatchStatement {
visitTryCatchStmt(ctx: TryCatchStmtContext): TryCatchStatement {
const tryStmt = ctx._tryStmt;
const catchBlocks = ctx.catchBlock();
const finallyStmt = ctx._finallyStmt;
return {
try_stmt: this.visit(ctx.tryStmt),
catches: ctx.catchBlock().map((c: any) => this.visit(c)),
finally_stmt: ctx.finallyStmt ? this.visit(ctx.finallyStmt) : undefined,
try_stmt: tryStmt ? this.visit(tryStmt) : undefined,
catches: catchBlocks.map((c: CatchBlockContext) => this.visit(c)),
finally_stmt: finallyStmt ? this.visit(finallyStmt) : undefined,
};
}
visitIfStmt(ctx: any): IfStatement {
visitIfStmt(ctx: IfStmtContext): IfStatement {
return {
expr: this.visit(ctx.expression()),
then: this.visit(ctx.statement(0)),
@@ -256,14 +289,14 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
};
}
visitWhileStmt(ctx: any): WhileStatement {
visitWhileStmt(ctx: WhileStmtContext): WhileStatement {
return {
expr: this.visit(ctx.expression()),
body: ctx.statement() ? this.visit(ctx.statement()) : undefined,
};
}
visitForInStmt(ctx: any): ForInStatement {
visitForInStmt(ctx: ForInStmtContext): ForInStatement {
const firstIdentifier = this.visitIdentifier(ctx.identifier(0));
const secondIdentifier = ctx.identifier(1) ? this.visitIdentifier(ctx.identifier(1)) : null;
return {
@@ -274,38 +307,39 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
};
}
visitForStmt(ctx: any): ForStatement {
visitForStmt(ctx: ForStmtContext): ForStatement {
const initializer =
ctx.initializerVarDeclr || ctx.initializerVarAssignment || ctx.initializerExpression;
ctx._initializerVarDeclr || ctx._initializerVarAssignment || ctx._initializerExpression;
const increment =
ctx.incrementVarDeclr || ctx.incrementVarAssignment || ctx.incrementExpression;
ctx._incrementVarDeclr || ctx._incrementVarAssignment || ctx._incrementExpression;
return {
initializer: initializer ? this.visit(initializer) : undefined,
condition: ctx.condition ? this.visit(ctx.condition) : undefined,
condition: ctx._condition ? this.visit(ctx._condition) : undefined,
increment: increment ? this.visit(increment) : undefined,
body: this.visit(ctx.statement()),
};
}
visitFuncStmt(ctx: any): Function {
visitFuncStmt(ctx: FuncStmtContext): Function {
const params = ctx.identifierList();
return {
name: this.visitIdentifier(ctx.identifier()),
params: ctx.identifierList() ? this.visit(ctx.identifierList()) : [],
params: params ? this.visit(params) : [],
body: this.visit(ctx.block()),
};
}
visitKvPairList(ctx: any): [Expr, Expr][] {
visitKvPairList(ctx: KvPairListContext): [Expr, Expr][] {
return ctx.kvPair().map((kv: any) => this.visit(kv));
}
visitKvPair(ctx: any): [Expr, Expr] {
visitKvPair(ctx: KvPairContext): [Expr, Expr] {
const exprs = ctx.expression();
return [this.visit(exprs[0]), this.visit(exprs[1])];
}
visitIdentifierList(ctx: any): string[] {
visitIdentifierList(ctx: IdentifierListContext): string[] {
return ctx.identifier().map((ident: any) => this.visitIdentifier(ident));
}
@@ -356,6 +390,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
return initialQuery;
}
return {
expression_type: "select_set_query",
initial_select_query: initialQuery,
subsequent_select_queries: selectQueries,
};
@@ -367,6 +402,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
visitSelectStmt(ctx: any): SelectQuery {
const selectQuery: SelectQuery = {
expression_type: "select_query",
ctes: ctx.withClause() ? this.visit(ctx.withClause()) : undefined,
select: ctx.columnExprList() ? this.visit(ctx.columnExprList()) : [],
distinct: ctx.DISTINCT() ? true : undefined,
@@ -471,6 +507,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
if (Array.isArray(limitExpr) && limitExpr.length === 2) {
const [n, offsetValue] = limitExpr;
return {
expression_type: "limit_by_expr",
n,
offset_value: offsetValue,
exprs: this.visit(ctx.columnExprList()),
@@ -479,6 +516,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
// If no offset, just use limitExpr as n
return {
expression_type: "limit_by_expr",
n: limitExpr,
offset_value: undefined,
exprs: this.visit(ctx.columnExprList()),
@@ -535,6 +573,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
return table;
}
return {
expression_type: "join_expr",
table,
table_final: tableFinal,
sample,
@@ -594,6 +633,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
throw new NotImplementedError("Unsupported: JOIN ... ON with multiple expressions");
}
return {
expression_type: "join_constraint",
expr: columnExprList[0],
constraint_type: ctx.USING() ? "USING" : "ON",
};
@@ -606,6 +646,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
ratioExpressions.length > 1 && ctx.OFFSET() ? this.visit(ratioExpressions[1]) : undefined;
return {
expression_type: "sample_expr",
sample_value: sampleRatioExpr,
offset_value: offsetRatioExpr,
};
@@ -618,6 +659,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
visitOrderExpr(ctx: any): OrderExpr {
const order = ctx.DESC() || ctx.DESCENDING() ? "DESC" : "ASC";
return {
expression_type: "order_expr",
expr: this.visit(ctx.columnExpr()),
order: order as "ASC" | "DESC",
};
@@ -633,6 +675,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
const right = ctx.SLASH() && numberLiterals.length > 1 ? numberLiterals[1] : null;
return {
expression_type: "ratio_expr",
left: this.visitNumberLiteral(left),
right: right ? this.visitNumberLiteral(right) : undefined,
};
@@ -642,6 +685,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
const frame = ctx.winFrameClause();
const visitedFrame = frame ? this.visit(frame) : undefined;
return {
expression_type: "window_expr",
partition_by: ctx.winPartitionByClause() ? this.visit(ctx.winPartitionByClause()) : undefined,
order_by: ctx.winOrderByClause() ? this.visit(ctx.winOrderByClause()) : undefined,
frame_method: frame && frame.RANGE() ? "RANGE" : frame && frame.ROWS() ? "ROWS" : undefined,
@@ -673,6 +717,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
visitWinFrameBound(ctx: any): WindowFrameExpr {
if (ctx.PRECEDING()) {
return {
expression_type: "window_frame_expr",
frame_type: "PRECEDING",
frame_value: ctx.numberLiteral()
? (this.visit(ctx.numberLiteral()) as Constant).value
@@ -681,13 +726,14 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
}
if (ctx.FOLLOWING()) {
return {
expression_type: "window_frame_expr",
frame_type: "FOLLOWING",
frame_value: ctx.numberLiteral()
? (this.visit(ctx.numberLiteral()) as Constant).value
: undefined,
};
}
return { frame_type: "CURRENT ROW" };
return { expression_type: "window_frame_expr", frame_type: "CURRENT ROW" };
}
// Column expressions
@@ -697,6 +743,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
visitColumnExprTernaryOp(ctx: any): Call {
return {
expression_type: "call",
name: "if",
args: [
this.visit(ctx.columnExpr(0)),
@@ -723,11 +770,12 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
);
}
return { expr, alias };
return { expression_type: "alias", expr, alias };
}
visitColumnExprNegate(ctx: any): ArithmeticOperation {
return {
expression_type: "arithmetic_operation",
op: ArithmeticOperationOp.Sub,
left: { value: 0 } as Constant,
right: this.visit(ctx.columnExpr()),
@@ -736,6 +784,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
visitColumnExprDict(ctx: any): Dict {
return {
expression_type: "dict",
items: ctx.kvPairList() ? this.visit(ctx.kvPairList()) : [],
};
}
@@ -750,6 +799,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
visitColumnExprArray(ctx: any): ArrayExpression {
return {
expression_type: "array",
exprs: ctx.columnExprList() ? this.visit(ctx.columnExprList()) : [],
};
}
@@ -768,7 +818,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
// Use columnExpr() method to get left and right operands
const left = this.visit(ctx.columnExpr(0));
const right = this.visit(ctx.columnExpr(1));
return { left, right, op };
return { expression_type: "arithmetic_operation", left, right, op };
}
visitColumnExprPrecedence2(ctx: any): ArithmeticOperation | Call {
@@ -777,11 +827,21 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
const right = this.visit(ctx.columnExpr(1));
if (ctx.PLUS()) {
return { left, right, op: ArithmeticOperationOp.Add };
return {
expression_type: "arithmetic_operation",
left,
right,
op: ArithmeticOperationOp.Add,
};
} else if (ctx.DASH()) {
return { left, right, op: ArithmeticOperationOp.Sub };
return {
expression_type: "arithmetic_operation",
left,
right,
op: ArithmeticOperationOp.Sub,
};
} else if (ctx.CONCAT()) {
const args: Expr[] = [];
const args: Expression[] = [];
if ("name" in left && left.name === "concat" && "args" in left) {
args.push(...left.args);
} else {
@@ -794,7 +854,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
args.push(right);
}
return { name: "concat", args };
return { expression_type: "call", name: "concat", args };
} else {
throw new NotImplementedError(`Unsupported ColumnExprPrecedence2: ${ctx.getText()}`);
}
@@ -840,7 +900,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
throw new NotImplementedError(`Unsupported ColumnExprPrecedence3: ${ctx.getText()}`);
}
return { left, right, op };
return { expression_type: "compare_operation", left, right, op };
}
visitColumnExprInterval(ctx: any): Call {
@@ -866,11 +926,12 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
throw new NotImplementedError(`Unsupported interval type: ${interval.getText()}`);
}
return { name, args: [this.visit(ctx.columnExpr())] };
return { expression_type: "call", name, args: [this.visit(ctx.columnExpr())] };
}
visitColumnExprIsNull(ctx: any): CompareOperation {
return {
expression_type: "compare_operation",
left: this.visit(ctx.columnExpr()),
right: { value: null } as Constant,
op: ctx.NOT() ? CompareOperationOp.NotEq : CompareOperationOp.Eq,
@@ -879,36 +940,38 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
visitColumnExprTuple(ctx: any): Tuple {
return {
expression_type: "tuple",
exprs: ctx.columnExprList() ? this.visit(ctx.columnExprList()) : [],
};
}
visitColumnExprArrayAccess(ctx: any): ArrayAccess {
const object: Expr = this.visit(ctx.columnExpr(0));
const property: Expr = this.visit(ctx.columnExpr(1));
return { array: object, property };
const object: Expression = this.visit(ctx.columnExpr(0));
const property: Expression = this.visit(ctx.columnExpr(1));
return { expression_type: "array_access", array: object, property };
}
visitColumnExprNullArrayAccess(ctx: any): ArrayAccess {
const object: Expr = this.visit(ctx.columnExpr(0));
const property: Expr = this.visit(ctx.columnExpr(1));
return { array: object, property, nullish: true };
const object: Expression = this.visit(ctx.columnExpr(0));
const property: Expression = this.visit(ctx.columnExpr(1));
return { expression_type: "array_access", array: object, property, nullish: true };
}
visitColumnExprPropertyAccess(ctx: any): ArrayAccess {
const object = this.visit(ctx.columnExpr());
const property = { value: this.visitIdentifier(ctx.identifier()) } as Constant;
return { array: object, property };
return { expression_type: "array_access", array: object, property };
}
visitColumnExprNullPropertyAccess(ctx: any): ArrayAccess {
const object = this.visit(ctx.columnExpr());
const property = { value: this.visitIdentifier(ctx.identifier()) } as Constant;
return { array: object, property, nullish: true };
return { expression_type: "array_access", array: object, property, nullish: true };
}
visitColumnExprBetween(ctx: any): BetweenExpr {
return {
expression_type: "between_expr",
expr: this.visit(ctx.columnExpr(0)),
low: this.visit(ctx.columnExpr(1)),
high: this.visit(ctx.columnExpr(2)),
@@ -927,7 +990,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
let right = this.visit(ctx.columnExpr(1));
const rightArray = "exprs" in right ? right.exprs : [right];
return { exprs: [...leftArray, ...rightArray] };
return { expression_type: "and", exprs: [...leftArray, ...rightArray] };
}
visitColumnExprOr(ctx: any): Or {
@@ -937,28 +1000,28 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
let right = this.visit(ctx.columnExpr(1));
const rightArray = "exprs" in right ? right.exprs : [right];
return { exprs: [...leftArray, ...rightArray] };
return { expression_type: "or", exprs: [...leftArray, ...rightArray] };
}
visitColumnExprTupleAccess(ctx: any): TupleAccess {
const tuple = this.visit(ctx.columnExpr());
const index = parseInt(ctx.DECIMAL_LITERAL().getText());
return { tuple, index };
return { expression_type: "tuple_access", tuple, index };
}
visitColumnExprNullTupleAccess(ctx: any): TupleAccess {
const tuple = this.visit(ctx.columnExpr());
const index = parseInt(ctx.DECIMAL_LITERAL().getText());
return { tuple, index, nullish: true };
return { expression_type: "tuple_access", tuple, index, nullish: true };
}
visitColumnExprCase(ctx: any): Call {
const columns = ctx.columnExpr().map((column: any) => this.visit(column));
if (ctx.caseExpr) {
const args: Expr[] = [
const args: Expression[] = [
columns[0],
{ exprs: [] } as ArrayExpression,
{ exprs: [] } as ArrayExpression,
{ expression_type: "array", exprs: [] },
{ expression_type: "array", exprs: [] },
,
columns[columns.length - 1],
];
@@ -966,20 +1029,21 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
const arrayIndex = ((index - 1) % 2) + 1;
(args[arrayIndex] as ArrayExpression).exprs.push(columns[index]);
}
return { name: "transform", args };
return { expression_type: "call", name: "transform", args };
} else if (columns.length === 3) {
return { name: "if", args: columns };
return { expression_type: "call", name: "if", args: columns };
} else {
return { name: "multiIf", args: columns };
return { expression_type: "call", name: "multiIf", args: columns };
}
}
visitColumnExprNot(ctx: any): Not {
return { expr: this.visit(ctx.columnExpr()) };
return { expression_type: "not", expr: this.visit(ctx.columnExpr()) };
}
visitColumnExprWinFunctionTarget(ctx: any): WindowFunction {
return {
expression_type: "window_function",
name: this.visitIdentifier(ctx.identifier(0)),
exprs: ctx.columnExprs ? this.visit(ctx.columnExprs) : [],
args: ctx.columnArgList ? this.visit(ctx.columnArgList) : [],
@@ -989,6 +1053,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
visitColumnExprWinFunction(ctx: any): WindowFunction {
return {
expression_type: "window_function",
name: this.visitIdentifier(ctx.identifier()),
exprs: ctx.columnExprs ? this.visit(ctx.columnExprs) : [],
args: ctx.columnArgList ? this.visit(ctx.columnArgList) : [],
@@ -1003,27 +1068,30 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
visitColumnExprFunction(ctx: any): Call {
const name = this.visitIdentifier(ctx.identifier());
let parameters: Expr[] | undefined = ctx.columnExprs ? this.visit(ctx.columnExprs) : undefined;
let parameters: Expression[] | undefined = ctx.columnExprs
? this.visit(ctx.columnExprs)
: undefined;
// two sets of parameters fn()(), return an empty list for the first even if no parameters
if (ctx.LPAREN && ctx.LPAREN().length > 1 && parameters === undefined) {
parameters = [];
}
const args: Expr[] = ctx.columnArgList ? this.visit(ctx.columnArgList) : [];
const args: Expression[] = ctx.columnArgList ? this.visit(ctx.columnArgList) : [];
const distinct = ctx.DISTINCT() ? true : false;
return { name, params: parameters, args, distinct };
return { expression_type: "call", name, params: parameters, args, distinct };
}
visitColumnExprAsterisk(ctx: any): Field {
if (ctx.tableIdentifier()) {
const table = this.visit(ctx.tableIdentifier());
return { chain: [...table, "*"] };
return { expression_type: "field", chain: [...table, "*"] };
}
return { chain: ["*"] };
return { expression_type: "field", chain: ["*"] };
}
visitColumnLambdaExpr(ctx: any): Lambda {
return {
expression_type: "lambda",
args: ctx.identifier().map((identifier: any) => this.visitIdentifier(identifier)),
expr: ctx.columnExpr() ? this.visit(ctx.columnExpr()) : this.visit(ctx.block()),
};
@@ -1041,16 +1109,16 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
visitWithExprSubquery(ctx: any): CTE {
const subquery = this.visit(ctx.selectSetStmt());
const name = this.visitIdentifier(ctx.identifier());
return { name, expr: subquery, cte_type: "subquery" };
return { expression_type: "cte", name, expr: subquery, cte_type: "subquery" };
}
visitWithExprColumn(ctx: any): CTE {
const expr = this.visit(ctx.columnExpr());
const name = this.visitIdentifier(ctx.identifier());
return { name, expr, cte_type: "column" };
return { expression_type: "cte", name, expr, cte_type: "column" };
}
visitColumnIdentifier(ctx: any): Expr {
visitColumnIdentifier(ctx: any): Expression {
if (ctx.placeholder()) {
return this.visit(ctx.placeholder());
}
@@ -1061,15 +1129,15 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
if (table.length === 0 && nested.length > 0) {
const text = ctx.getText().toLowerCase();
if (text === "true") {
return { value: true } as Constant;
return { expression_type: "constant", value: true };
}
if (text === "false") {
return { value: false } as Constant;
return { expression_type: "constant", value: false };
}
return { chain: nested } as Field;
return { expression_type: "field", chain: nested };
}
return { chain: [...table, ...nested] } as Field;
return { expression_type: "field", chain: [...table, ...nested] };
}
visitNestedIdentifier(ctx: any): string[] {
@@ -1078,7 +1146,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
visitTableExprIdentifier(ctx: any): Field {
const chain = this.visit(ctx.tableIdentifier());
return { chain };
return { expression_type: "field", chain };
}
visitTableExprSubquery(ctx: any): SelectQuery | SelectSetQuery {
@@ -1101,7 +1169,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
table.alias = alias;
return table;
}
return { table, alias };
return { expression_type: "join_expr", table, alias };
}
visitTableExprFunction(ctx: any): JoinExpr {
@@ -1115,7 +1183,11 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
visitTableFunctionExpr(ctx: any): JoinExpr {
const name = this.visitIdentifier(ctx.identifier());
const args = ctx.tableArgList() ? this.visit(ctx.tableArgList()) : [];
return { table: { chain: [name] } as Field, table_args: args };
return {
expression_type: "join_expr",
table: { expression_type: "field", chain: [name] },
table_args: args,
};
}
visitTableIdentifier(ctx: any): string[] {
@@ -1131,7 +1203,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
return nestedArray;
}
visitTableArgList(ctx: any): Expr[] {
visitTableArgList(ctx: any): Expression[] {
return ctx.columnExpr().map((arg: any) => this.visit(arg));
}
@@ -1154,20 +1226,20 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
text === "inf" ||
text === "nan"
) {
return { value: parseFloat(text) };
return { expression_type: "constant", value: parseFloat(text) };
}
return { value: parseInt(text) };
return { expression_type: "constant", value: parseInt(text) };
}
visitLiteral(ctx: any): Constant {
if (ctx.NULL_SQL()) {
return { value: null };
return { expression_type: "constant", value: null };
}
if (ctx.STRING_LITERAL()) {
// STRING_LITERAL() returns a TerminalNode, which has getText()
const stringLiteral = ctx.STRING_LITERAL();
const text = parseStringLiteralCtx(stringLiteral);
return { value: text };
return { expression_type: "constant", value: text };
}
if (ctx.numberLiteral()) {
return this.visitNumberLiteral(ctx.numberLiteral());
@@ -1230,6 +1302,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
visitColumnExprNullish(ctx: any): Call {
return {
expression_type: "call",
name: "ifNull",
args: [this.visit(ctx.columnExpr(0)), this.visit(ctx.columnExpr(1))],
};
@@ -1237,6 +1310,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
visitColumnExprCall(ctx: any): ExprCall {
return {
expression_type: "expr_call",
expr: this.visit(ctx.columnExpr()),
args: ctx.columnExprList() ? this.visit(ctx.columnExprList()) : [],
};
@@ -1246,11 +1320,13 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
const expr = this.visit(ctx.columnExpr());
if ("chain" in expr && expr.chain.length === 1) {
return {
expression_type: "call",
name: String(expr.chain[0]),
args: [this.visit(ctx.selectSetStmt())],
};
}
return {
expression_type: "expr_call",
expr,
args: [this.visit(ctx.selectSetStmt())],
};
@@ -1267,7 +1343,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
}
visitHogqlxText(ctx: any): Constant {
return { value: ctx.HOGQLX_TEXT_TEXT().getText() };
return { expression_type: "constant", value: ctx.HOGQLX_TEXT_TEXT().getText() };
}
visitHogqlxTagElementClosed(ctx: any): HogQLXTag {
@@ -1275,7 +1351,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
const attributes = ctx.hogqlxTagAttribute()
? ctx.hogqlxTagAttribute().map((a: any) => this.visit(a))
: [];
return { kind, attributes };
return { expression_type: "hogqlx_tag", kind, attributes };
}
visitHogqlxTagElementNested(ctx: any): HogQLXTag {
@@ -1292,7 +1368,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
: [];
// ── collect child nodes, discarding pure-indentation whitespace ──
const keptChildren: Expr[] = [];
const keptChildren: Expression[] = [];
for (const element of ctx.hogqlxChildElement()) {
const child = this.visit(element);
@@ -1317,7 +1393,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
attributes.push({ name: "children", value: keptChildren });
}
return { kind: opening, attributes };
return { expression_type: "hogqlx_tag", kind: opening, attributes };
}
visitHogqlxTagAttribute(ctx: any): HogQLXAttribute {
@@ -1332,7 +1408,7 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
}
visitPlaceholder(ctx: any): Placeholder {
return { expr: this.visit(ctx.columnExpr()) };
return { expression_type: "placeholder", expr: this.visit(ctx.columnExpr()) };
}
visitColumnExprTemplateString(ctx: any): Expr {
@@ -1341,66 +1417,69 @@ export class TSQLParseTreeConverter implements TSQLParserVisitor<any> {
visitString(ctx: any): Constant | Expr {
if (ctx.STRING_LITERAL()) {
return { value: parseStringLiteralCtx(ctx.STRING_LITERAL()) };
return { expression_type: "constant", value: parseStringLiteralCtx(ctx.STRING_LITERAL()) };
}
return this.visit(ctx.templateString());
}
visitTemplateString(ctx: any): Constant | Call {
const pieces: Expr[] = [];
const pieces: Expression[] = [];
for (const chunk of ctx.stringContents()) {
pieces.push(this.visit(chunk));
}
if (pieces.length === 0) {
return { value: "" };
return { expression_type: "constant", value: "" };
} else if (pieces.length === 1) {
const first = pieces[0];
// If it's already a Constant or Call, return as-is, otherwise wrap in Call
if ("value" in first || "name" in first) {
return first as Constant | Call;
}
return { name: "concat", args: [first] };
return { expression_type: "call", name: "concat", args: [first] };
}
return { name: "concat", args: pieces };
return { expression_type: "call", name: "concat", args: pieces };
}
visitFullTemplateString(ctx: any): Constant | Call {
const pieces: Expr[] = [];
const pieces: Expression[] = [];
for (const chunk of ctx.stringContentsFull()) {
pieces.push(this.visit(chunk));
}
if (pieces.length === 0) {
return { value: "" };
return { expression_type: "constant", value: "" };
} else if (pieces.length === 1) {
const first = pieces[0];
// If it's already a Constant or Call, return as-is, otherwise wrap in Call
if ("value" in first || "name" in first) {
return first as Constant | Call;
}
return { name: "concat", args: [first] };
return { expression_type: "call", name: "concat", args: [first] };
}
return { name: "concat", args: pieces };
return { expression_type: "call", name: "concat", args: pieces };
}
visitStringContents(ctx: any): Constant | Expr {
visitStringContents(ctx: any): Constant | Expression {
if (ctx.STRING_TEXT()) {
return { value: parseStringTextCtx(ctx.STRING_TEXT(), true) };
return { expression_type: "constant", value: parseStringTextCtx(ctx.STRING_TEXT(), true) };
} else if (ctx.columnExpr()) {
return this.visit(ctx.columnExpr());
}
return { value: "" };
return { expression_type: "constant", value: "" };
}
visitStringContentsFull(ctx: any): Constant | Expr {
visitStringContentsFull(ctx: any): Constant | Expression {
if (ctx.FULL_STRING_TEXT()) {
return { value: parseStringTextCtx(ctx.FULL_STRING_TEXT(), false) };
return {
expression_type: "constant",
value: parseStringTextCtx(ctx.FULL_STRING_TEXT(), false),
};
} else if (ctx.columnExpr()) {
return this.visit(ctx.columnExpr());
}
return { value: "" };
return { expression_type: "constant", value: "" };
}
}
@@ -520,6 +520,7 @@ export class PropertySwapper extends CloningVisitor {
private createToTimeZoneCall(node: Field): Call {
return {
expression_type: "call",
name: "toTimeZone",
args: [node, this.createConstant(this.timezone)],
type: {
@@ -534,6 +535,7 @@ export class PropertySwapper extends CloningVisitor {
private createToDateTimeCall(node: Field): Call {
return {
expression_type: "call",
name: "toDateTime",
args: [node],
start: node.start,
@@ -543,6 +545,7 @@ export class PropertySwapper extends CloningVisitor {
private createToFloatCall(node: Field): Call {
return {
expression_type: "call",
name: "toFloat",
args: [node],
start: node.start,
@@ -552,6 +555,7 @@ export class PropertySwapper extends CloningVisitor {
private createToBoolCall(node: Field): Call {
return {
expression_type: "call",
name: "toBool",
args: [
{
@@ -578,6 +582,7 @@ export class PropertySwapper extends CloningVisitor {
private createConstant(value: any): Constant {
return {
expression_type: "constant",
value,
};
}