Query Compiler
The heart of Ryx — transforms Python query expressions into optimized SQL.
Pipeline
Python QuerySet methods
│
▼
QueryNode (Rust AST)
│
▼
compiler::compile()
│
▼
CompiledQuery { sql: String, values: Vec<SqlValue> }
AST Types
QueryNode
The root of every query:
pub struct QueryNode {
pub operation: QueryOperation, // Select, Aggregate, Count, Delete, Update, Insert
pub table: String,
pub columns: Vec<String>,
pub filters: Vec<FilterNode>,
pub q_tree: Option<QNode>,
pub joins: Vec<JoinClause>,
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 { left: Box<QNode>, right: Box<QNode> },
Or { left: Box<QNode>, right: Box<QNode> },
Not { inner: Box<QNode> },
}
SqlValue — Type-Safe Values
pub enum SqlValue {
Null,
Bool(bool),
Int(i64),
Float(f64),
Text(String),
Bytes(Vec<u8>),
Date(chrono::NaiveDate),
Time(chrono::NaiveTime),
DateTime(chrono::NaiveDateTime),
Json(serde_json::Value),
}
JoinClause
pub enum JoinKind { Inner, LeftOuter, RightOuter, FullOuter, Cross }
pub struct JoinClause {
pub table: String,
pub condition: String,
pub kind: JoinKind,
pub alias: Option<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