// SelectBuilder — fluent builder for SELECT statements.
///|
/// A fluent builder for a SELECT query. Terminal: `.to_sql()` or `.build()`.
pub(all) struct SelectBuilder {
table : Table
columns : Array[@ast.ColumnRef]
aggregates : Array[@ast.Aggregation]
where_clause : @ast.Expr?
joins : Array[@ast.JoinClause]
group_by : Array[@ast.ColumnRef]
having : @ast.Expr?
order_by : Array[@ast.OrderByClause]
limit : Int?
offset : Int?
distinct : Bool
distinct_on : Array[@ast.ColumnRef]
}
///|
/// Create a new SelectBuilder. Internal — users call `table.select(..)` instead.
pub fn SelectBuilder::new(
table : Table,
columns : Array[@ast.ColumnRef],
) -> SelectBuilder {
SelectBuilder::{
table,
columns,
aggregates: [],
where_clause: None,
joins: [],
group_by: [],
having: None,
order_by: [],
limit: None,
offset: None,
distinct: false,
distinct_on: [],
}
}
///|
/// Attach a WHERE condition, AND-ed with any existing conditions.
/// Call multiple times to build up filters from different layers.
pub fn SelectBuilder::where_(
self : SelectBuilder,
condition : @ast.Filter,
) -> SelectBuilder {
let merged = match self.where_clause {
Some(existing) => existing.and_(condition)
None => condition
}
SelectBuilder::{ ..self, where_clause: Some(merged) }
}
///|
/// Add an INNER JOIN clause.
pub fn SelectBuilder::join(
self : SelectBuilder,
other : Table,
on : @ast.Filter,
) -> SelectBuilder {
self.joins.push(@ast.JoinClause::{ table: other.qualified_name(), on })
self
}
///|
/// Add an ORDER BY clause. Chain multiple times for multiple columns.
pub fn SelectBuilder::order_by(
self : SelectBuilder,
clause : @ast.OrderByClause,
) -> SelectBuilder {
self.order_by.push(clause)
self
}
///|
/// Attach a LIMIT clause.
pub fn SelectBuilder::limit(self : SelectBuilder, n : Int) -> SelectBuilder {
SelectBuilder::{ ..self, limit: Some(n) }
}
///|
/// Attach an OFFSET clause.
pub fn SelectBuilder::offset(self : SelectBuilder, n : Int) -> SelectBuilder {
SelectBuilder::{ ..self, offset: Some(n) }
}
///|
/// Add SELECT DISTINCT.
pub fn SelectBuilder::distinct(self : SelectBuilder) -> SelectBuilder {
SelectBuilder::{ ..self, distinct: true }
}
///|
/// Add SELECT DISTINCT ON (columns).
pub fn SelectBuilder::distinct_on(
self : SelectBuilder,
cols : Array[&AnyColumn],
) -> SelectBuilder {
SelectBuilder::{ ..self, distinct_on: cols.map(c => c.to_ref()) }
}
///|
/// Add an aggregate expression to the SELECT list.
pub fn SelectBuilder::agg(
self : SelectBuilder,
a : @ast.Aggregation,
) -> SelectBuilder {
self.aggregates.push(a)
self
}
///|
/// Add a GROUP BY clause.
pub fn SelectBuilder::group_by(
self : SelectBuilder,
cols : Array[&AnyColumn],
) -> SelectBuilder {
SelectBuilder::{ ..self, group_by: cols.map(c => c.to_ref()) }
}
///|
/// Attach a HAVING clause (must be after GROUP BY).
pub fn SelectBuilder::having(
self : SelectBuilder,
condition : @ast.Filter,
) -> SelectBuilder {
SelectBuilder::{ ..self, having: Some(condition) }
}
///|
/// Produce the AST node without rendering.
pub fn SelectBuilder::build(self : SelectBuilder) -> @ast.SelectStmt {
let sel_cols = self.columns.map(fn(c) { @ast.SelectExpr::Col(c) })
for a in self.aggregates {
sel_cols.push(@ast.SelectExpr::Agg(a))
}
@ast.SelectStmt::{
table: self.table.qualified_name(),
columns: sel_cols,
where_clause: self.where_clause,
joins: self.joins,
group_by: self.group_by,
having: self.having,
order_by: self.order_by,
limit: self.limit,
offset: self.offset,
distinct: self.distinct,
distinct_on: self.distinct_on,
}
}
///|
/// Render the query to a parameterised SQL string + args array.
pub fn SelectBuilder::to_sql(
self : SelectBuilder,
) -> (String, Array[&@moonpg.ToValue]) {
@tosql.select_stmt_to_sql(self.build())
}