Documentation Index
Fetch the complete documentation index at: https://mintlify.com/questdb/questdb/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Griffin is QuestDB’s SQL compiler and execution engine. Named after the mythical creature, Griffin transforms SQL text into efficient execution plans optimized for time-series workloads. Location:core/src/main/java/io/questdb/griffin/
Architecture
The SQL compiler follows a multi-stage pipeline:Stage 1: Lexing and Parsing
Input: SQL text string Output:QueryModel (Abstract Syntax Tree)
Key classes:
SqlParser— Top-level parser:io/questdb/griffin/SqlParser.javaSqlLexer— Tokenizes SQL textExpressionParser— Parses expressions:io/questdb/griffin/ExpressionParser.javaQueryModel— AST representation:io/questdb/griffin/model/QueryModel.java
Stage 2: Optimization
Input:QueryModel (AST)
Output: Optimized QueryModel
Key class: SqlOptimiser: io/questdb/griffin/SqlOptimiser.java
Optimizations:
- Predicate pushdown — Move filters closer to data source
- Partition pruning — Skip partitions outside time range
- Index selection — Choose best index for filter
- Join reordering — Optimize join order
- Constant folding — Evaluate constants at compile time
- Subquery flattening — Convert subqueries to joins where possible
Stage 3: Code Generation
Input: OptimizedQueryModel
Output: RecordCursorFactory
Key class: SqlCodeGenerator: io/questdb/griffin/SqlCodeGenerator.java
Generates execution plan as a tree of factories:
- Each factory creates a
RecordCursorfor data iteration - Factories compose to form complex plans
- Supports lazy evaluation (only compute what’s needed)
Stage 4: Execution
Input:RecordCursorFactory
Output: RecordCursor (iterator over rows)
Execution is lazy:
- Factory creates cursor
- Cursor iterates row-by-row (or in batches)
- Each row is a
Recordwith column accessors
RecordCursorFactory— Creates cursorsRecordCursor— Iterates over recordsRecord— Represents a single rowRecordMetadata— Schema information
Key Classes
SqlCompiler / SqlCompilerImpl
Location:io/questdb/griffin/SqlCompilerImpl.java:149
Main entry point for SQL compilation.
Key methods:
compile(sql, context)— Compiles SQL toCompiledQuerycompileQuery(sql, context)— Compiles SELECT toRecordCursorFactoryexecute(sql, context)— Executes DDL/DML
SqlParser
Location:io/questdb/griffin/SqlParser.java
Parses SQL text into QueryModel or other model objects.
Key methods:
parseQuery(sql)— Parses SELECT statementparseInsert(sql)— Parses INSERT statementparseCreateTable(sql)— Parses CREATE TABLEparseAlterTable(sql)— Parses ALTER TABLE
- Recursive descent parser
- Zero-allocation (reuses objects from pools)
- Error recovery (provides position of syntax errors)
ExpressionParser
Location:io/questdb/griffin/ExpressionParser.java
Parses SQL expressions (WHERE clauses, SELECT expressions, etc.).
Key methods:
parseExpression(sql)— Parses expression treeparseFunction(sql)— Parses function calls
SqlCodeGenerator
Location:io/questdb/griffin/SqlCodeGenerator.java
Generates execution plans from optimized query models.
Key methods:
generate(queryModel, context)— GeneratesRecordCursorFactorygenerateFilter(queryModel)— Generates filter plangenerateJoin(queryModel)— Generates join plangenerateGroupBy(queryModel)— Generates aggregation plan
- Chooses optimal factory based on query characteristics
- Considers indexes, partition pruning, parallelism
- Generates SIMD-accelerated code where applicable
FunctionFactory
Location:io/questdb/griffin/FunctionFactory.java
Base interface for SQL functions.
Implementation:
- Each function is a separate class (e.g.,
SumDoubleGroupByFunction) - Function factories registered in
FunctionFactoryCache - Code generation instantiates appropriate function class
- Scalar functions (e.g.,
round(),abs()) - Aggregate functions (e.g.,
sum(),avg(),count()) - Window functions (e.g.,
row_number(),rank()) - Time-series functions (e.g.,
sample_by(),fill())
io/questdb/griffin/engine/functions/math/AbsIntFunctionFactory.java
Query Execution Models
Table Scan
Simple sequential scan of a table: Factory:DataFrameRecordCursorFactory
Execution:
- Open table reader
- Iterate partitions
- For each partition, iterate rows
- Return each row as
Record
Filtered Scan
Table scan with WHERE clause: Factory:FilteredRecordCursorFactory
Execution:
- Base cursor (table scan)
- Evaluate filter for each row
- Return only matching rows
Index Scan
Use bitmap index to find matching rows: Factory:BitmapIndexRecordCursorFactory
Execution:
- Lookup value in bitmap index
- Get bitmap of matching rows
- Iterate only matching rows
symbol has a bitmap index:
- Lookup “AAPL” in index → bitmap
- Iterate only rows where bitmap[i] = 1
Hash Join
Join two tables using a hash table: Factory:HashJoinRecordCursorFactory
Execution:
- Build phase: Scan smaller table, build hash table on join key
- Probe phase: Scan larger table, probe hash table for matches
- Return joined rows
INNER JOINLEFT JOINOUTER JOINCROSS JOIN
ASOF Join
Time-series specific join (join on nearest timestamp): Factory:AsOfJoinRecordCursorFactory
Use case: Join time-series tables with different sampling rates
Example:
SPLICE Join
Time-series full outer join: Factory:SpliceJoinRecordCursorFactory
Use case: Merge two time-series tables by timestamp
Example:
Group By / Aggregation
Aggregate data by key: Factory:GroupByRecordCursorFactory
Execution:
- Scan input cursor
- For each row, lookup/create group in hash table
- Update aggregates for group
- Return aggregated results
sum(),avg(),min(),max(),count()first(),last()(time-series specific)ksum()(Kahan summation for precision)
Sample By
Time-series specific downsampling: Factory:SampleByRecordCursorFactory
Use case: Aggregate data into time buckets
Example:
FILL(NONE)— Skip empty bucketsFILL(NULL)— Return NULL for empty bucketsFILL(LINEAR)— Interpolate missing valuesFILL(PREV)— Forward-fill previous value
LATEST ON
Deduplication by key (return latest row per key): Factory:LatestByAllIndexedRecordCursorFactory
Example:
Query Optimization Techniques
Partition Pruning
Skip partitions outside the query time range. Example:- Scan only
2024-01-01/and2024-01-02/partitions - Skip all other partitions
IntrinsicModel extracts time range from WHERE clause
See: io/questdb/griffin/model/IntrinsicModel.java
Index Selection
Choose the best index for a filter. Heuristic:- Equality on indexed symbol column → bitmap index
- IN list on indexed symbol → bitmap index with OR
- Range on timestamp → partition pruning + timestamp index
- Otherwise → full table scan with filter
- Use bitmap index on
symbol = 'AAPL' - Filter remaining rows with
price > 100
Predicate Pushdown
Move filters closer to data source to reduce data scanned. Example:Constant Folding
Evaluate constant expressions at compile time. Example:Join Reordering
Optimize join order to minimize intermediate results. Heuristic:- Smaller table on the right (build side of hash join)
- Apply filters before joins
- Joins on indexed columns preferred
- Filter
small_tablefirst (value > 100) - Build hash table on filtered
small_table - Probe with
large_table
JIT Compilation
Location:core/src/main/java/io/questdb/jit/
QuestDB can JIT-compile WHERE clause filters to native code for maximum performance.
Supported:
- x86-64 only (not available on ARM64)
- Simple filters (comparisons, boolean logic)
- Numeric types
io/questdb/jit/JitUtil.java
Parallel Query Execution
QuestDB can parallelize query execution across partitions. Strategy:- Each partition processed independently
- Worker threads from shared pool
- Results merged in order
- Split 365 partitions across N threads
- Each thread aggregates its partitions
- Merge aggregated results
shared.worker.count— Number of worker threads
Error Handling
SqlException
Location:io/questdb/griffin/SqlException.java
All SQL errors throw SqlException with:
- Error message
- Position in SQL text (character offset)
- Error code (optional)
Related Pages
- Storage Engine — Cairo storage internals
- SIMD Optimizations — Vector operations
- Architecture Overview — System architecture
- Testing — SQL testing conventions