Query Compiler
The heart of Ryx โ transforms Python query expressions into optimized SQL.
Since v0.1.3, the compiler resides in the standalone ryx-query crate, decoupled from the Python bindings for maximum performance and testability.
Pipelineโ
Python QuerySet methods
โ
โผ
QueryNode (Rust AST)
โ
โผ
ryx_query::compiler::compile()
โ
โผ
CompiledQuery { sql: String, values: Vec<SqlValue> }
AST Typesโ
QueryNodeโ
The root of every query:
pub struct QueryNode {
pub table: String,
pub backend: Backend, // DB backend for SQL generation
pub operation: QueryOperation, // Select, Aggregate, Count, Delete, Update, Insert
pub filters: Vec<FilterNode>,
pub q_filter: Option<QNode>,
pub joins: Vec<JoinClause>,
pub annotations: Vec<AggregateExpr>,
pub group_by: Vec<String>,
pub having: Vec<FilterNode>,
pub order_by: Vec<OrderByClause>,
pub limit: Option<u64>,
pub offset: Option<u64>,
pub distinct: bool,
}
QNode โ Boolean Expression Treeโ
pub enum QNode {
Leaf { field: String, lookup: String, value: SqlValue, negated: bool },
And(Vec<QNode>),
Or(Vec<QNode>),
Not(Box<QNode>),
}
SqlValue โ Type-Safe Valuesโ
pub enum SqlValue {
Null,
Bool(bool),
Int(i64),
Float(f64),
Text(String),
List(Vec<SqlValue>),
}
JoinClauseโ
pub enum JoinKind { Inner, LeftOuter, RightOuter, FullOuter, CrossJoin }
pub struct JoinClause {
pub kind: JoinKind,
pub table: String,
pub alias: Option<String>,
pub on_left: String,
pub on_right: String,
}
Compilation Processโ
- SELECT clause โ
columnsor* - FROM clause โ
table - JOINs โ Each
JoinClauserendered with proper kind - WHERE โ Flat
filtersAND-ed, then Q-tree recursively compiled - GROUP BY โ If
group_byis non-empty - HAVING โ If
havingis non-empty - ORDER BY โ Each
OrderByClausewith ASC/DESC - LIMIT/OFFSET โ If set
- DISTINCT โ If flag is true
Q-Tree Compilationโ
The Q-tree is compiled recursively:
Q(active=True) | Q(views__gte=1000)
QNode::Or {
left: Leaf { field: "active", lookup: "exact", value: Bool(true) }
right: Leaf { field: "views", lookup: "gte", value: Int(1000) }
}
โ ("active" = ? OR "views" >= ?)
Nested expressions:
(Q(active=True) & Q(views__gte=100)) | Q(featured=True)
โ (("active" = ? AND "views" >= ?) OR "featured" = ?)
Lookup Compilationโ
Each lookup generates SQL differently:
match lookup.as_str() {
"exact" => format!("{col} = ?"),
"gt" => format!("{col} > ?"),
"contains" => { values.push(wrap_percent(value)); format!("{col} LIKE ?") }
"isnull" => if value { format!("{col} IS NULL") } else { format!("{col} IS NOT NULL") }
"in" => { let placeholders = expand_placeholders(values.len()); format!("{col} IN ({placeholders})") }
"range" => format!("{col} BETWEEN ? AND ?"),
custom => template.replace("{col}", &col), // Custom lookup
}
Identifier Quotingโ
All identifiers are quoted for safety:
fn quote_ident(name: &str) -> String {
format!("\"{}\"", name)
}
// "posts", "author_id", "created_at"
Outputโ
pub struct CompiledQuery {
pub sql: String,
pub values: Vec<SqlValue>,
}
This is passed directly to sqlx::query(&sql).bind(values).fetch_all(pool).
Next Stepsโ
โ Connection Pool โ Pool management