Class ExpressionEvaluator
Expressions are parsed once into an Expression AST (via the ANTLR grammar and
ExpressionAstBuilder) and evaluated by
ExpressionEvaluatorVisitor. Parse once with parse(String) and call
evaluate(Expression, Row) per row to avoid re-parsing.
-
Constructor Summary
ConstructorsConstructorDescriptionExpressionEvaluator(Table table, FunctionRegistry functionRegistry, Catalog catalog) ExpressionEvaluator(Table table, FunctionRegistry functionRegistry, Catalog catalog, QueryExecutor queryExecutor) -
Method Summary
Modifier and TypeMethodDescriptionargumentTypeText(Expression expression) How Snowflake NAMES an expression's type when it refuses it as an argument —VARCHAR(7)for a seven-character literal,VARCHAR(50)for a declared column,NUMBER(1,0),DATE.collationOf(Expression expression) The collationexpressioncarries, lower-cased, or null for none — seeExpressionEvaluatorVisitor.collationOf(dev.frostlake.executor.expressions.Expression).booleancountsStoredColumn(Expression argument) Whether a COUNT overargumentis answered from the statistics: over a constant or a stored column (see ExpressionEvaluatorVisitor#countsStoredColumn).evaluate(Expression expression, Row row) Evaluate an expression AST against a row (RECOMMENDED) This method is more efficient than string-based evaluation because the expression is already parsed.Evaluate a string expression against a row: parse it to an AST (cached) and evaluate viaExpressionEvaluatorVisitor.inferStaticRange(Expression expression) The intervalexpression's values lie in, as the account's statistics would bound it, or null when none can be known.inferStaticType(Expression expression) The typeexpressionis statically KNOWN to produce in this table context, or null when it cannot be determined.keyCollation(Expression expression) The collation an ORDER BY, GROUP BY, DISTINCT, PARTITION BY or set-operation key compares under.static ExpressionParse an expression string into anExpressionAST — the parse-once entry point for operators that previously re-evaluated the same text once per row.qualifiedAggregateText(Expression expression) qualifiedText(dev.frostlake.executor.expressions.Expression)for an aggregate call that a NESTING message brackets: a plain AVG at that root prints as its SUM half, the way live names it — seeExpressionEvaluatorVisitor.strictAggregateText(dev.frostlake.executor.expressions.Expression).qualifiedText(Expression expression) How Snowflake RE-PRINTS an expression when it names it in a refusal: from the analysed plan, so a bare column reference comes back qualified with the name its relation is known by in scope —noverFROM ebprintsEB.N, and overFROM eb xprintsX.N.voidrejectArgumentFamilies(Expression expression) Applies the argument-family refusals to every call inexpressionfrom declared types, before any row is read — seeExpressionEvaluatorVisitor.rejectArgumentFamilies(dev.frostlake.executor.expressions.Expression).booleanresolvesToAFunctionName(Expression expression) PHASE TWO as a QUESTION rather than a refusal.voidThe relation a reference's DECLARED type is read from whentablecannot say — seeExpressionEvaluatorVisitor.setDeclaredTypeBase(dev.frostlake.metastore.model.Table, java.util.Map<java.lang.String, dev.frostlake.metastore.model.Table>, java.util.List<dev.frostlake.metastore.model.Table>).voidProvide a multi-table (JOIN) resolution context so a parsed Expression can be evaluated directly over joined rows with alias-aware column resolution.voidsetOuterLateralContext(Map<String, Object> context) voidsetOutputAliasTypes(Map<String, DataType> types) The static types of the query's SELECT output aliases, so a bare alias in a predicate is typed from the expression it names.voidsetOutputScopeNames(Set<String> names) The names a refusal's echo prints BARE, because the expression it re-prints resolves in the query's OUTPUT scope rather than against the FROM — a projected column has no relation to name.voidsetQueryExecutor(QueryExecutor queryExecutor) voidsetResultContext(Map<String, Object> resultContext) Provide precomputed SELECT-list outputs (HAVING / QUALIFY), keyed by canonical AST form and alias, so a condition referencing an aggregate/window/alias resolves to the computed value.voidsetScopeExemptNames(Set<String> names) Bare names the plan-time scope walk must not reject — the query's SELECT output aliases / output column names, legal in every non-SELECT clause (WHERE included, live-verified).voidsetScopeOpaqueToSubqueries(boolean opaque) Declares this evaluator's relation a PROJECTION SCOPE — a FROM-less select's own item list — which answers the clauses of that select and nothing nested inside them, so a subquery written in one of those clauses does not correlate to it.spelledNumber(Expression expression) The NUMBERexpressionspells when it is a bare string literal, or a column carrying one out of a derived relation — what a projected column hands on so a conditional over it folds as live folds the literal (see TableColumn#getSpelledNumber); null for anything else.Live's sentence for a set of unresolvable calls — see the visitor's own documentation.voidvalidateBooleanPositions(Expression expression) The BOOLEAN positions insideexpression— a searched CASE's WHEN conditions, IFF's condition — judged at plan time on their static types, for a path that evaluates values before the strict walk would type them (the FROM-less select list).voidvalidateCollations(Expression expression) A JOIN condition's collation rules, judged before any row is read — seeExpressionEvaluatorVisitor.validateCollations(dev.frostlake.executor.expressions.Expression).voidvalidateColumnScope(Expression expression) PHASE ONE of the plan-time walk: every column reference, and nothing else.voidvalidateFunctionNames(Expression expression) PHASE TWO: every call's NAME, and nothing else.voidvalidateKey(Expression expression, SortKeyRole role) Key-position validation: Snowflake rejects a FILE-typed expression used as a GROUP BY, ORDER BY or window PARTITION BY key at compile time, while leaving it usable everywhere else (equality, DISTINCT, joins).voidvalidateOrderedValue(Expression expression) WITHIN GROUP (ORDER BY …) value validation: Snowflake rejects a semi-structured value in the position an ordering aggregate accumulates, with the same "incompatible types" sentenceMEDIANuses for its argument.voidvalidatePredicate(Expression expression) Predicate-position validation (Snowflake rejects VARCHAR/NUMBER-typed WHERE, HAVING and QUALIFY conditions at compile time) — run once per query on the parsed predicate before row evaluation.voidvalidateStrict(Expression expression) Plan-time strict-argument validation over a parsed expression: runs the Snowflake argument-type checks for every function call inexpressionWITHOUT evaluating it, so rejection fires even over zero input rows.voidvalidateStrictWindowed(Expression expression) validateStrict(dev.frostlake.executor.expressions.Expression)for a WINDOW call re-formed without its OVER clause — the walk runs with the windowed marker set, so the bare-form desugared-name rewrites stay off (live reports the WRITTEN name for windowed forms, measured).voidvalidateUdfArguments(Expression expression) The plan-time user-defined function argument check alone, over a parsed expression.withNarrowingCastEqualitiesAnswered(Expression predicate) A filter's predicate with the narrowing casts live answers without converting dropped: seeExpressionEvaluatorVisitor.withNarrowingCastEqualitiesAnswered(dev.frostlake.executor.expressions.Expression).
-
Constructor Details
-
ExpressionEvaluator
-
ExpressionEvaluator
public ExpressionEvaluator(Table table, FunctionRegistry functionRegistry, Catalog catalog, QueryExecutor queryExecutor)
-
-
Method Details
-
setQueryExecutor
-
setOuterLateralContext
-
setScopeOpaqueToSubqueries
public void setScopeOpaqueToSubqueries(boolean opaque) Declares this evaluator's relation a PROJECTION SCOPE — a FROM-less select's own item list — which answers the clauses of that select and nothing nested inside them, so a subquery written in one of those clauses does not correlate to it.- Parameters:
opaque- whether the relation is such a scope
-
setMultiTableContext
Provide a multi-table (JOIN) resolution context so a parsed Expression can be evaluated directly over joined rows with alias-aware column resolution. Set once per query. -
setResultContext
Provide precomputed SELECT-list outputs (HAVING / QUALIFY), keyed by canonical AST form and alias, so a condition referencing an aggregate/window/alias resolves to the computed value. -
setDeclaredTypeBase
The relation a reference's DECLARED type is read from whentablecannot say — seeExpressionEvaluatorVisitor.setDeclaredTypeBase(dev.frostlake.metastore.model.Table, java.util.Map<java.lang.String, dev.frostlake.metastore.model.Table>, java.util.List<dev.frostlake.metastore.model.Table>).- Parameters:
base- the base relation, or nullaliasToTable- its alias map, or nullallTables- its joined relations, or null
-
evaluate
Evaluate an expression AST against a row (RECOMMENDED) This method is more efficient than string-based evaluation because the expression is already parsed. Use this when you need to evaluate the same expression multiple times.- Parameters:
expression- The parsed expression ASTrow- The row to evaluate against- Returns:
- The result of the expression evaluation
-
evaluate
Evaluate a string expression against a row: parse it to an AST (cached) and evaluate viaExpressionEvaluatorVisitor. For repeated evaluation of the same text, preferparse(String)once +evaluate(Expression, Row)per row.- Parameters:
expression- the expression as a stringrow- the row to evaluate against- Returns:
- the result of the expression evaluation
-
validateStrict
Plan-time strict-argument validation over a parsed expression: runs the Snowflake argument-type checks for every function call inexpressionWITHOUT evaluating it, so rejection fires even over zero input rows. -
validateUdfArguments
The plan-time user-defined function argument check alone, over a parsed expression. -
validateColumnScope
PHASE ONE of the plan-time walk: every column reference, and nothing else. Live orders its refusals by KIND rather than by position — an invalid identifier beats an unknown function name, which beats every argument-type complaint, whichever item each of them sits in — so the select list is walked once per kind. -
withNarrowingCastEqualitiesAnswered
A filter's predicate with the narrowing casts live answers without converting dropped: seeExpressionEvaluatorVisitor.withNarrowingCastEqualitiesAnswered(dev.frostlake.executor.expressions.Expression).- Parameters:
predicate- the parsed predicate of a WHERE or an ON- Returns:
- the predicate to evaluate
-
validateFunctionNames
PHASE TWO: every call's NAME, and nothing else. SeevalidateColumnScope(dev.frostlake.executor.expressions.Expression). -
resolvesToAFunctionName
PHASE TWO as a QUESTION rather than a refusal.validateFunctionNames(dev.frostlake.executor.expressions.Expression)stops at the first unresolvable name, which is right for one expression; a whole statement's refusal names every one of them at once, so the caller asks this per call and refuses after.- Parameters:
expression- one parsed call — anything that is not a call answers true- Returns:
- whether the name resolves
-
unknownFunctionSentence
Live's sentence for a set of unresolvable calls — see the visitor's own documentation. -
keyCollation
The collation an ORDER BY, GROUP BY, DISTINCT, PARTITION BY or set-operation key compares under. A key with none compares by code point, as it always did.- Parameters:
expression- the key expression- Returns:
- its collation rules, or null when it carries none
-
setScopeExemptNames
Bare names the plan-time scope walk must not reject — the query's SELECT output aliases / output column names, legal in every non-SELECT clause (WHERE included, live-verified). -
setOutputScopeNames
The names a refusal's echo prints BARE, because the expression it re-prints resolves in the query's OUTPUT scope rather than against the FROM — a projected column has no relation to name.- Parameters:
names- the output column names, upper-cased
-
setOutputAliasTypes
The static types of the query's SELECT output aliases, so a bare alias in a predicate is typed from the expression it names.- Parameters:
types- alias name (upper-cased) to type
-
validateStrictWindowed
validateStrict(dev.frostlake.executor.expressions.Expression)for a WINDOW call re-formed without its OVER clause — the walk runs with the windowed marker set, so the bare-form desugared-name rewrites stay off (live reports the WRITTEN name for windowed forms, measured). -
validateKey
Key-position validation: Snowflake rejects a FILE-typed expression used as a GROUP BY, ORDER BY or window PARTITION BY key at compile time, while leaving it usable everywhere else (equality, DISTINCT, joins). Run once per query on each parsed key, before any row is compared. -
validateOrderedValue
WITHIN GROUP (ORDER BY …) value validation: Snowflake rejects a semi-structured value in the position an ordering aggregate accumulates, with the same "incompatible types" sentenceMEDIANuses for its argument. Run once per query on the parsed clause expression. -
validateCollations
A JOIN condition's collation rules, judged before any row is read — seeExpressionEvaluatorVisitor.validateCollations(dev.frostlake.executor.expressions.Expression).- Parameters:
expression- the parsed ON condition
-
collationOf
The collationexpressioncarries, lower-cased, or null for none — seeExpressionEvaluatorVisitor.collationOf(dev.frostlake.executor.expressions.Expression). Over no relation a column reference carries none, so what is left is what the expression names itself.- Parameters:
expression- the parsed expression- Returns:
- its collation specification, or null
-
validatePredicate
Predicate-position validation (Snowflake rejects VARCHAR/NUMBER-typed WHERE, HAVING and QUALIFY conditions at compile time) — run once per query on the parsed predicate before row evaluation. The BOOLEAN positions INSIDE the predicate — a searched CASE's WHEN, IFF's condition — are judged first, by the same narrow walk, so a CASE over a VARCHAR refuses here as it does in a SELECT list.The GEOSPATIAL comparison walk runs here too. It is a separate, deliberately NARROW walk rather than the full
validateStrict(dev.frostlake.executor.expressions.Expression)one: a WHERE clause is where a geo comparison is actually written (WHERE g = TO_GEOGRAPHY(…),JOIN … ON a.g = b.glowered into a filter), live refuses every such spelling, and a rule that can only fire on a statically GEOGRAPHY- or GEOMETRY-typed operand cannot reject anything else. Running the whole strict walk here would newly enforce the OBJECT / FILE operator rules in predicate position as well, which has not been measured and is not this rule's business. -
validateBooleanPositions
The BOOLEAN positions insideexpression— a searched CASE's WHEN conditions, IFF's condition — judged at plan time on their static types, for a path that evaluates values before the strict walk would type them (the FROM-less select list).- Parameters:
expression- the parsed expression
-
inferStaticType
The typeexpressionis statically KNOWN to produce in this table context, or null when it cannot be determined. Used at projection time to give a derived relation (subquery, CTE, view) a typed column list, so an outer reference to one of its columns resolves to the inner expression's type instead of the VARCHAR placeholder. Never throws: an expression this evaluator cannot even look at is simply undetermined. -
countsStoredColumn
Whether a COUNT overargumentis answered from the statistics: over a constant or a stored column (see ExpressionEvaluatorVisitor#countsStoredColumn).- Parameters:
argument- the COUNT's one argument- Returns:
- true when the statistics answer it
-
spelledNumber
The NUMBERexpressionspells when it is a bare string literal, or a column carrying one out of a derived relation — what a projected column hands on so a conditional over it folds as live folds the literal (see TableColumn#getSpelledNumber); null for anything else. Never throws. -
inferStaticRange
The intervalexpression's values lie in, as the account's statistics would bound it, or null when none can be known. The channel that decides SYSTEM$TYPEOF's storage tag; never throws. -
rejectArgumentFamilies
Applies the argument-family refusals to every call inexpressionfrom declared types, before any row is read — seeExpressionEvaluatorVisitor.rejectArgumentFamilies(dev.frostlake.executor.expressions.Expression). -
argumentTypeText
How Snowflake NAMES an expression's type when it refuses it as an argument —VARCHAR(7)for a seven-character literal,VARCHAR(50)for a declared column,NUMBER(1,0),DATE. A literal is measured from its own text, so the width is the value's, not the family's maximum. -
qualifiedText
How Snowflake RE-PRINTS an expression when it names it in a refusal: from the analysed plan, so a bare column reference comes back qualified with the name its relation is known by in scope —noverFROM ebprintsEB.N, and overFROM eb xprintsX.N. Keyword and identifier case and all spacing are normalised with it. -
qualifiedAggregateText
qualifiedText(dev.frostlake.executor.expressions.Expression)for an aggregate call that a NESTING message brackets: a plain AVG at that root prints as its SUM half, the way live names it — seeExpressionEvaluatorVisitor.strictAggregateText(dev.frostlake.executor.expressions.Expression). -
parse
Parse an expression string into anExpressionAST — the parse-once entry point for operators that previously re-evaluated the same text once per row. Parses via the ANTLR grammar and caches by text. Blank input yields a literal TRUE (matching the old empty-predicate behaviour).--line comments are handled by the ANTLR lexer (LINE_COMMENT -> skip), which tokenizes string literals first — so a literal such as'a--b'keeps its dashes. (A prior quote-blind regex pre-strip here truncated any literal containing--.)
-