Class QueryExecutor

java.lang.Object
dev.frostlake.executor.QueryExecutor

public class QueryExecutor extends Object
Query Executor - Executes SQL statements using ANTLR-based parsing
  • Constructor Details

  • Method Details

    • setStreamManager

      public void setStreamManager(StreamManager streamManager)
    • setTaskScheduler

      public void setTaskScheduler(TaskScheduler taskScheduler)
    • setDatabaseEngine

      public void setDatabaseEngine(DatabaseEngine engine)
    • getDatabaseEngine

      public DatabaseEngine getDatabaseEngine()
    • setSecurityManager

      public void setSecurityManager(SecurityManager securityManager)
    • getSecurityManager

      public SecurityManager getSecurityManager()
    • getSessionVariables

      public Map<String,Object> getSessionVariables()
    • getResultCache

      public QueryResultCache getResultCache()
    • evaluateSystemFunction

      public Object evaluateSystemFunction(String funcName, List<Object> args)
      Evaluate a SYSTEM$FUNCNAME call from expression context (no ANTLR ctx available).
    • getQueryHistoryTracker

      public QueryHistoryTracker getQueryHistoryTracker()
    • execute

      public List<ResultSet> execute(String sql)
      Execute SQL using ANTLR parsing and visitor pattern
    • setNextStatementInstant

      public void setNextStatementInstant(Instant instant)
      The instant the NEXT statement should call "now", set only by write-ahead-log replay so a re-executed statement resolves CURRENT_TIMESTAMP to the value it was originally written with. Consumed by the next execute(java.lang.String); null means read the wall clock, which is every normal call.
    • currentStatementInstant

      public Instant currentStatementInstant()
      The instant the most recent outermost statement pinned, for the log to record alongside its SQL. Read after execution — at commit — so it is the remembered value, not a fresh clock reading.
    • isDurableStatement

      public boolean isDurableStatement(String sql)
    • isQueryStatement

      public boolean isQueryStatement(String sql)
      True when sql parses as a single query statement (SELECT / WITH … SELECT). Lets a SQL-UDF body that is a query be distinguished from a bare scalar expression via the parse tree, not a string prefix (so WITH … SELECT CTE bodies are recognised too). A body that does not parse as a statement (e.g. x * 2) returns false. Cached per body.
    • subqueryStatement

      public FrostlakeParser.SelectStatementContext subqueryStatement(String sql)
      The query sql parses to, read from the cache statements use, or null when it is not one query. A subquery's text is a statement of its own, so its shape can be read without running it.
    • storedRowCount

      public long storedRowCount(String storageKey)
      The rows a catalog table's storage holds right now, by its storage key, or -1 when there is none: a metadata question, as live's planner asks it before any row is read.
    • storedRowCountOf

      public long storedRowCountOf(String writtenName)
      storedRowCount(java.lang.String) for a table named as a query writes it, or -1 when the name reaches no storage.
    • isProceduralBlock

      public boolean isProceduralBlock(String sql)
      True when sql parses as a single Snowflake-Scripting block — BEGIN … END or DECLARE … BEGIN … END. Sibling of isQueryStatement(String): the decision is made on the parse tree, never on a string prefix, so the transaction statement BEGIN; (a different grammar rule that also starts with BEGIN) is correctly not a block, while a lower-cased or multi-line block is.

      Used to compile a LANGUAGE SQL stored-procedure body at CREATE time: Snowflake requires such a body to be a scripting block and rejects a bare statement outright.

    • reportBlockSyntaxError

      public void reportBlockSyntaxError(String sql)
      Throw the syntax error of a body that OPENS a scripting block — its first token is BEGIN or DECLARE — but does not parse as one; a body that opens no block, or parses as something else, returns quietly.

      The CREATE-time body checks fail OPEN on a body this grammar cannot read (see the note on parsesAsScript(String)), which is right there — the body may use a construct the engine does not model. At CALL time the body has to RUN, so its real parse error is the honest answer; without this the caller falls through to the expression evaluator and reports whatever opaque failure that produces.

    • reportUnterminatedBlock

      public void reportUnterminatedBlock(String sql)
      Throw the syntax error of a block-opening body that RUNS OUT before its block closes — the parse error sits at end of input, so the engine has read every token and positively knows the block never ended, which a real account also refuses at CREATE. Any other parse failure returns quietly: this grammar is a SUBSET of Snowflake's, so a mid-body error may be a construct the engine does not model, and the CREATE-time body checks fail OPEN on those (see the note on parsesAsScript(String)).
    • queryStatementOf

      public FrostlakeParser.SqlScriptContext queryStatementOf(String sql)
      The parsed script sql forms when it is a single query statement, or null when it is not one. The parse-tree half of isQueryStatement(String), for the CREATE-time SQL-UDF body check; unlike that method this parses afresh rather than consulting the hot-path cache, because it is only ever reached once per CREATE.
    • statementCountOf

      public int statementCountOf(String sql)
      How many statements sql parses into — every stage of every flow chain — or 0 when it does not parse. The CREATE-time SQL-UDF body check refuses a body of more than one.
    • missingTerminatorRefusal

      public String missingTerminatorRefusal(String sql)
      The syntax error sql earns when the FIRST thing wrong with it is a statement inside a block that lacks its semicolon, or null for any other text. The engine read that statement whole, so the fault is positively known rather than a construct the grammar lacks, and a CREATE PROCEDURE refuses it as the account does.
    • proceduralBlockOf

      public FrostlakeParser.BeginEndBlockContext proceduralBlockOf(String sql)
    • parsesAsScript

      public boolean parsesAsScript(String sql)
      Whether sql parses at all under the engine's grammar. Frostlake's grammar is a SUBSET of Snowflake's, so a parse FAILURE means "this engine does not recognise some construct" — never "Snowflake would reject it". The CREATE-time routine-body checks use this to fail OPEN: they only reject a body whose shape they can positively read and know to be wrong.
    • isSinglePlainStatement

      public boolean isSinglePlainStatement(String sql)
      Whether sql is exactly ONE plain (non-procedural) SQL statement — a bare SELECT … / UPDATE … and nothing else. This is the one routine-body shape the engine can positively call wrong: Snowflake requires a LANGUAGE SQL procedure body to be a scripting block and rejects a bare statement outright. A body that does not parse, or parses as several statements, or contains any procedural construct, is NOT this shape.
    • isInLateralExecution

      public boolean isInLateralExecution()
      Whether a correlated / lateral re-execution is on the stack.
    • executeWithLateralContext

      public List<ResultSet> executeWithLateralContext(String sql, Map<String,Object> lateralContext)
    • executeInsertFromContext

      public Object executeInsertFromContext(FrostlakeParser.InsertStatementContext ctx)
      Execute INSERT from parsed context
    • executeMultiTableInsert

      public Object executeMultiTableInsert(FrostlakeParser.MultiTableInsertStatementContext ctx)
      Snowflake multi-table INSERT — route each row of the subquery to one or more target tables. INSERT [OVERWRITE] ALL INTO ... (unconditional), or INSERT [OVERWRITE] {FIRST | ALL} WHEN cond THEN INTO ... [ELSE INTO ...]. FIRST stops at the first matching WHEN; ALL applies every matching WHEN; ELSE applies only when no WHEN matched.
    • executeSelectFromContext

      public ResultSet executeSelectFromContext(FrostlakeParser.SelectStatementContext ctx)
      Execute SELECT from parsed context
    • executeUpdateFromContext

      public Object executeUpdateFromContext(FrostlakeParser.UpdateStatementContext ctx)
      Execute UPDATE from parsed context
    • executeDeleteFromContext

      public Object executeDeleteFromContext(FrostlakeParser.DeleteStatementContext ctx)
      Execute DELETE from parsed context
    • enforceColumnConstraints

      public void enforceColumnConstraints(Table table, Row row)
      Enforce column constraints at statement time on the COPY write path: coerce values to the column type (only when constraints.enforce.types is on), then check NOT NULL (always — Snowflake enforces NOT NULL, unlike the informational PK/UK/FK). The violation sentences here are BARE — a COPY reports them as the file's first_error, naming the column separately in first_error_column_name. DML statements go through enforceColumnConstraintsForDml(Table, Row), which wraps the same sentences in the per-statement envelope live uses.
    • enforceColumnConstraintsForDml

      public void enforceColumnConstraintsForDml(Table table, Row row)
      The DML flavour of enforceColumnConstraints(Table, Row): INSERT, INSERT … SELECT, UPDATE and MERGE all report a write violation inside a per-statement envelope naming the table and the column — live-verified verbatim, DML operation to table NN failed on column C with error: NULL result in a non-nullable column — around the same inner sentences the COPY path reports bare. Enforcement is per column in declaration order, so the first violating column names the failure.
    • enforceColumnConstraintsForDml

      public void enforceColumnConstraintsForDml(Table table, Row row, boolean boundToColumnSlot)
    • enforceColumnConstraintsForDml

      public void enforceColumnConstraintsForDml(Table table, Row row, boolean boundToColumnSlot, String writtenName)
      The write-time constraints of one row, each failure wrapped in the DML envelope that names the table AS THE STATEMENT WROTE IT — TEST_DB.TEST_SCHEMA.TWO for a qualified INSERT, TWO for a bare one, four parts when the account was written (live-verified for INSERT, UPDATE, UPDATE … FROM and both MERGE branches). The spelling travels as a PARAMETER from the handler that read the statement: a thread-local for it leaked into the next statement once.
      Parameters:
      writtenName - the table as written, upper-cased per part, or null for the bare catalog name
    • writeCtasRow

      public void writeCtasRow(Table table, Row row, String envelopeName)
      A CTAS row written into the declared types of its column list, as live writes it: each value is converted to its column the way an INSERT converts it — a number rounded to the scale, text parsed — and a value that does not fit is refused inside the DML envelope naming the table and the column ("DML operation to table DB.S.T failed on column X with error: String 'abcd' is too long and would be truncated"). A NULL in a NOT NULL column is the bare "NULL result in a non-nullable column" there (live-verified).
      Parameters:
      envelopeName - the table as the CTAS envelope names it, one level up from how it was written
    • coerceRowTypesLenient

      public void coerceRowTypesLenient(Table table, Row row)
      Lenient per-column coercion for CTAS rows: coerce each value to the created table's column type when the conversion works, and keep the ORIGINAL value when it does not. The result-column types a SELECT reports are best-effort (an expression column can say NUMBER while a row carries a UUID string), so the strict INSERT-style coercion would reject rows Snowflake accepts.
    • executeTruncate

      public void executeTruncate(String tableName, boolean ifExists)
      Execute TRUNCATE TABLE
    • emptyTableContents

      public void emptyTableContents(String fullyQualifiedName)
      Discard every row of fullyQualifiedName, shared by TRUNCATE TABLE and INSERT OVERWRITE.

      Under deferred-apply this MUST route through the write set (like INSERT/UPDATE): every committed row becomes a pending delete and the transaction's own buffered inserts/updates for the table are dropped, so the rest of the transaction sees the table empty and the change applies atomically at COMMIT (a ROLLBACK restores the rows). Clearing the base store directly would leave this transaction's not-yet-committed inserts in place, to reappear on the next read — which is precisely what made INSERT OVERWRITE APPEND to, instead of replace, rows written earlier in the same transaction (the stage/rebuild pattern INSERT INTO tmp …; INSERT OVERWRITE INTO tmp … SELECT … FROM tmp …). A transaction is begun when none is active, as INSERT does, so an autocommit truncate applies at statement end and an explicit one stays rollback-safe.

    • executeMergeFromContext

      public Object executeMergeFromContext(FrostlakeParser.MergeStatementContext ctx)
      Execute MERGE from parsed context
    • executeCopyIntoFromContext

      public Object executeCopyIntoFromContext(FrostlakeParser.CopyIntoStatementContext ctx)
    • executePutFromContext

      public ResultSet executePutFromContext(FrostlakeParser.PutStatementContext ctx)
      PUT — live's nine-column result shape: compressions, the constant ENCRYPTED marker, and a message cell. AUTO_COMPRESS defaults to TRUE, gzipping an uncompressed source and renaming the target name.gz; re-PUTting an identical file answers SKIPPED with live's own sentence (OVERWRITE=TRUE forces the upload).
    • gzipBytes

      public static byte[] gzipBytes(byte[] bytes) throws IOException
      The gzip of bytes; GZIPOutputStream writes no timestamp, so equal input gives equal output.
      Throws:
      IOException
    • executeGetFromContext

      public ResultSet executeGetFromContext(FrostlakeParser.GetStatementContext ctx)
    • executeRemoveFromContext

      public ResultSet executeRemoveFromContext(FrostlakeParser.RemoveStatementContext ctx)
      REMOVE/RM @stage [PATTERN = '…'] — delete staged files (optionally matching a regex pattern).
    • stagedDisplayName

      public String stagedDisplayName(String fromLocation, Path file)
      How LIST and REMOVE display a staged file, live-verified: the path relative to the STAGE ROOT (not the referenced subdirectory), prefixed with the lowercase stage name for a named stage; user and table stages show the bare root-relative path.
    • executeListFromContext

      public ResultSet executeListFromContext(FrostlakeParser.ListStatementContext ctx)
      LIST @stage [PATTERN] — live's four columns over the stage's whole tree.
    • executeCopyRefresh

      public Object executeCopyRefresh(String copyStatement, String prefix, String modifiedAfter)
      ALTER PIPE … REFRESH: run the pipe's stored COPY, but restrict the files it loads to those whose stage-relative path starts with prefix and/or whose last-modified time is strictly after modifiedAfter (matching Snowflake's REFRESH PREFIX / MODIFIED_AFTER). Either filter may be null (absent). The filters are scoped to this single COPY and cleared afterwards.
    • resolveStageFilePath

      public Path resolveStageFilePath(FrostlakeParser.StageRefContext stageRef)
      Resolve a stage reference (e.g. @stg/script.sql) to its local file path — for EXECUTE IMMEDIATE FROM.
    • resolveStageFilePath

      public Path resolveStageFilePath(String location)
      Resolve a location string (e.g. 'file://…/script.sql') to its local file path.
    • resolveCopyBaseDir

      public Path resolveCopyBaseDir(String fromLocation)
    • stageRefToLocation

      public String stageRefToLocation(FrostlakeParser.StageRefContext ctx)
      Encode a stage reference as a COPY location string: @name[/path], @~[/path], or @%table[/path].
    • resetInternalStageDir

      public void resetInternalStageDir(String stageName)
      A NEW internal named stage starts EMPTY, exactly like a real account's: the engine-managed directory is durable on disk, so without this a re-created stage would inherit the files a dropped same-name predecessor staged.
    • resetInternalTableStageDir

      public void resetInternalTableStageDir(String tableName)
      The same reset for a TABLE's implicit stage — a new table's stage is empty likewise.
    • listCopyFiles

      public List<Path> listCopyFiles(Path baseDir, String pattern, List<String> files)
      Regular files in baseDir, narrowed either by an explicit FILES = (…) list or by a PATTERN regex — never by both. Live-verified on a real account: when a statement carries both, the PATTERN is ignored outright. FILES = ('good1.csv','good2.csv') beside PATTERN = '.*good1.*' loaded BOTH files, and FILES = ('good1.csv') beside a pattern matching nothing still loaded good1 — so a FILES list is the whole selection, not a candidate set the pattern then filters.

      A FILES entry is a PATH relative to the FROM location, not a bare file name: the account loads FILES = ('sub/nested.csv') from @stage and reports it as stage/sub/nested.csv. Entries are returned in the order the statement wrote them (duplicates collapsed); a directory scan keeps its name order.

    • listCopyFiles

      public List<Path> listCopyFiles(Path baseDir, String pattern, List<String> files, String patternPrefix)
      The staged files a COPY-family statement addresses. pattern is a full-match regex; the string it must match is patternPrefix + <root-relative name> — COPY passes the internal-path prefix (stages/<name>/…) because the account applies PATTERN to the file's whole internal path, not its bare name: a stage holding good.csv matches '.*good\.csv' but neither 'good\.csv' nor '<stage>/good\.csv'.
    • copyPatternPrefix

      public String copyPatternPrefix(String fromLocation)
      The prefix PATTERN-matching prepends to a staged file's root-relative name, mirroring the shape of the account's internal paths: stages/<name>/ for a named stage, tables/<name>/ for a table stage and users/<user>/ for the user stage (the account puts an opaque id where the name sits here). Lower-cased, subpath included.
    • resetCopyLoadHistory

      public void resetCopyLoadHistory(String fullyQualifiedTableName)
      Forget a table's COPY load history — TRUNCATE and CREATE OR REPLACE / DROP TABLE all do.
    • resolveStagedFile

      public Path resolveStagedFile(Path baseDir, String name)
    • swapTables

      public void swapTables(String tableA, String tableB)
      ALTER TABLE a SWAP WITH b — exchange the two tables' row storage (compatible structure assumed).
    • resolveObjectName

      public String resolveObjectName(FrostlakeParser.ObjectNameContext ctx)
      Resolve an objectName parse node to the name it denotes: IDENTIFIER(<expr>) evaluates the expression (a string literal or a session/bind variable) to the name; otherwise it is the literal qualified name as written. Lets object names be supplied dynamically (Snowflake IDENTIFIER()).
    • resolveObjectNameParts

      public String[] resolveObjectNameParts(FrostlakeParser.ObjectNameContext ctx)
      Canonical name PARTS of an objectName — one element per dotted level, so a quoted identifier containing a dot stays one part where splitting resolveObjectName(dev.frostlake.parser.FrostlakeParser.ObjectNameContext)'s joined spelling would break it. The literal path reads the parse tree; the IDENTIFIER(<expr>) path evaluates the expression and canonicalises its VALUE per dotted part (the string IS the identifier reference, so its dots and quotes are the value's own syntax).
    • renameTableStorage

      public void renameTableStorage(String oldName, String newShortName)
      Re-key a renamed table's row storage: the new name keeps the old table's database/schema.
    • moveTableStorage

      public void moveTableStorage(String oldName, String targetDatabase, String targetSchema, String newShortName)
      Re-key a moved table's row storage to a new database/schema/name (cross-schema RENAME TO).
    • backfillColumn

      public void backfillColumn(String tableName, TableColumn newColumn)
      Backfill every existing row of a table after ALTER TABLE ADD COLUMN, appending one value for the new column so stored rows match the widened schema — otherwise every read of the new column throws IndexOutOfBounds. Snowflake populates existing rows with the column's DEFAULT literal when present, otherwise NULL; the default is evaluated once (via the same path INSERT uses) and shared across rows.
    • getFullyQualifiedTableName

      public String getFullyQualifiedTableName(String tableName)
    • resolveViewShapeInScope

      public List<TableColumn> resolveViewShapeInScope(String database, String schema, String body, List<String> columnNames)
      Resolve a view's body shape at CREATE where the view will live, as executeInViewScope(java.lang.String, java.lang.String) reads it: a bare name in the body is the view's schema's, and a missing one is named there.
      Parameters:
      database - the view's database
      schema - the view's schema
      body - the body
      columnNames - the view's written column names, or null
      Returns:
      the body's columns
    • requireSourceRelations

      public void requireSourceRelations(FrostlakeParser.SelectStatementContext source)
      Find every relation an INSERT's source query reads, in the order written, and refuse the first that names nothing, before the target is looked up. Live reports a missing source relation ahead of a missing target or a bad column list, while a missing target still comes ahead of the source's column and function errors (live-verified). Nothing is read: each name is only found, the way a FROM clause finds it.
      Parameters:
      source - the INSERT's source query
    • requireSecurable

      public void requireSecurable(String objectType, String[] parts)
      The object a GRANT, a REVOKE or a SHOW GRANTS names must exist, and is found before anything else the statement names (live-verified). A table and a view resolve each other, the named kind wording the miss; a schema, a database, a function, a sequence and a stage are each found in their own place, a missing container refused as such. Other kinds are not looked up.
      Parameters:
      objectType - the kind the statement names, upper-case
      parts - the object's name, part by part
    • requireSecurable

      public void requireSecurable(String objectType, String[] parts, Integer signatureArity)
      requireSecurable(String, String[]) with a routine's written argument count: a function none of whose overloads takes that many arguments names nothing (live-verified).
      Parameters:
      objectType - the kind the statement names, upper-case
      parts - the object's name, part by part
      signatureArity - the number of argument types written after the name, or null for none
    • requireRelations

      public void requireRelations(org.antlr.v4.runtime.tree.ParseTree statement)
    • rejectAggregatesInWhere

      public void rejectAggregatesInWhere(FrostlakeParser.WhereClauseContext where, Table table)
      An UPDATE's or DELETE's WHERE refuses an aggregate as a query's does: Invalid aggregate function in where clause [COUNT(*)] (live-verified).
      Parameters:
      where - the statement's WHERE, or null
      table - the statement's target
    • getCatalog

      public Catalog getCatalog()
    • getProceduralExecutor

      public ProceduralExecutor getProceduralExecutor()
      The session's procedural (Snowflake Scripting) executor — used to resolve :name bind variables.
    • executeScriptingFunctionBody

      public Object executeScriptingFunctionBody(Function function, List<Object> args)
      See Also:
    • getEngineConfig

      public EngineConfig getEngineConfig()
    • getS3PathResolver

      public S3PathResolver getS3PathResolver()
      Resolver for s3:// (and S3-backed @stage) references to local files; null if no config was supplied.
    • getStorageEngine

      public StorageEngine getStorageEngine()
    • getFunctionRegistry

      public FunctionRegistry getFunctionRegistry()
    • getTransactionManager

      public TransactionManager getTransactionManager()
    • getStreamManager

      public StreamManager getStreamManager()
    • getTaskScheduler

      public TaskScheduler getTaskScheduler()
    • getShowExecutor

      public ShowCommandExecutor getShowExecutor()
    • describeResult

      DESCRIBE RESULT '<queryId>' | LAST_QUERY_ID() — the column metadata of a prior query's cached result.
    • showQueryHistory

      public ResultSet showQueryHistory(Integer limit)
    • columnValueRange

      public ValueRange columnValueRange(Table owner, String columnName)
      A catalog column's statistics: the interval between the least and the greatest value in the WHOLE table — every row, whatever the query's WHERE keeps — which is what the account tags the column by, and whether any row holds a NULL, which decides whether the column may answer one. Empty for a column holding only NULLs or no rows; null for a table this executor cannot read.
      Parameters:
      owner - the catalog table, as the FROM clause resolved it
      columnName - the column
      Returns:
      the interval, or null when the table's rows are not reachable
    • baseTableRowCount

      public Long baseTableRowCount(Table owner)
      The rows a catalog table holds, as this transaction sees them, or null when the table's rows are not reachable — the bound the account gives a COUNT over it.
    • countIsUnbounded

      public boolean countIsUnbounded()
      Whether the SELECT clause executing on this thread reads past its table's statistics — a join, a grouping key, a HAVING, or a WHERE the statistics cannot prove — so that its COUNT is tagged by its declared width and a typeof over COUNT, MIN or MAX is no longer answered from them.
    • resolveViewStreamBaseTables

      public List<String> resolveViewStreamBaseTables(View view)
      Resolve the base tables of a view eligible for change tracking (CREATE STREAM ON VIEW). A view stream supports projections, WHERE filters, and UNION ALL over single-table branches — SELECT ... FROM t [WHERE ...] [UNION ALL SELECT ... FROM t2 [WHERE ...]] .... Plain UNION (which deduplicates), joins, GROUP BY/HAVING/QUALIFY, DISTINCT, LIMIT, CTEs, and subquery sources are rejected, matching Snowflake's change-tracking restrictions. Returns one bare (upper-case) base table name per branch — the names change capture matches on.
    • resolveViewStreamBaseTable

      public String resolveViewStreamBaseTable(View view)
      Back-compat single-table resolver: the first (often only) branch's base table.
    • executeCtasSourceSelect

      public ResultSet executeCtasSourceSelect(FrostlakeParser.SelectStatementContext ctx)
      Execute the source SELECT of a CTAS in a stream-consuming context. Streams read are captured (CTAS has no transaction to register on) and consumed by consumeCtasStreams() once the table is created.
    • consumeCtasStreams

      public void consumeCtasStreams()
      Advance the offset of streams read by the last CTAS source SELECT. Call only after the CTAS succeeds.
    • resolveRelationShape

      public List<TableColumn> resolveRelationShape(String definition, List<String> explicitColumnNames)
      A relation's column list from its definition's SHAPE alone — no row is read and no value computed (see RelationShapeOnly), which is how CREATE VIEW learns its columns: a value-time fault in the body is the reader's, not the creator's.
    • resolveRelationColumns

      public List<TableColumn> resolveRelationColumns(String definition, List<String> explicitColumnNames)
    • resolveColumnInTables

      public Object resolveColumnInTables(Row row, List<Table> tables, Map<String,Table> aliasToTable, String qualifiedName)
      Resolve a (possibly qualified) column reference against a multi-table row context — the alias-aware resolution used by JOIN evaluation, exposed for the AST evaluator so a parsed Expression can be evaluated directly over joined rows (e.g. a.id = b.id resolves each side to its own table). Delegates to getQualifiedColumnValueFromTables(dev.frostlake.storage.Row, java.util.List<dev.frostlake.metastore.model.Table>, java.util.Map<java.lang.String, dev.frostlake.metastore.model.Table>, java.lang.String); throws if the name is not a column of any table.
    • resolveColumnInTables

      public Object resolveColumnInTables(Row row, List<Table> tables, Map<String,Table> aliasToTable, String qualifiedName, int[] resolvedOffset)
      Resolve like resolveColumnInTables(Row, List, Map, String), additionally reporting HOW the reference resolved through resolvedOffset[0]: the flat row offset when the value came from the plain table-column scan (a fixed layout position the caller may cache for this context), left untouched when it resolved any other way (literal fallback, merged join key — per-row decisions that must re-run).