///|
pub fn[T : @pp.Pretty] pretty_print(obj : T) -> String {
@pp.pretty(obj).to_string()
}
///|
pub fn[T : Show] structural_print(obj : T) -> String {
obj.to_string()
}
///|
pub(all) struct Statements {
/// The list of SQL statements parsed from the input.
stmts : Array[Statement]
} derive(Eq, Show)
///|
pub impl @pp.Pretty for Statements with pretty(self) {
@pp.separate(@pp.hardline + @pp.hardline, self.stmts.map(@pp.pretty))
}
///|
pub fn Statements::op_get(self : Self, index : Int) -> Statement {
self.stmts[index]
}
///|
pub(all) enum Statement {
Query(QueryStmt)
CreateTable(CreateTableStmt)
CreateView(CreateViewStmt)
CreateIndex(CreateIndexStmt)
CreateDatabase(CreateDatabaseStmt)
CreateSchema(CreateSchemaStmt)
CreateFunction(CreateFunctionStmt)
CreateProcedure(CreateProcedureStmt)
CreateSequence(CreateSequenceStmt)
DropView(DropViewStmt)
DropTable(DropTableStmt)
DropIndex(DropIndexStmt)
Insert(InsertStmt)
Delete(DeleteStmt)
Update(UpdateStmt)
Merge(MergeStmt)
Truncate(TruncateStmt)
AlterTable(AlterTableStmt)
AlterIndex(AlterIndexStmt)
Show(ShowStmt)
Set(SetStmt)
Use(UseStmt)
Copy(CopyStmt)
LoadData(LoadDataStmt)
Prepare(PrepareStmt)
Execute(ExecuteStmt)
Deallocate(DeallocateStmt)
LockTables(Array[ObjectName])
UnlockTables
// PostgreSQL-specific statements
Listen(String)
Notify(String, String?)
// TCL (Transaction Control Language) statements
Begin(BeginStmt)
Commit(CommitStmt)
Rollback(RollbackStmt)
Savepoint(SavepointStmt)
ReleaseSavepoint(ReleaseSavepointStmt)
// DCL (Data Control Language) statements
Grant(GrantStmt)
Revoke(RevokeStmt)
} derive(Eq, Show)
///|
pub impl @pp.Pretty for Statement with pretty(self) {
(match self {
Statement::Query(stmt) => stmt.pretty()
CreateTable(stmt) => stmt.pretty()
CreateView(stmt) => stmt.pretty()
CreateIndex(stmt) => stmt.pretty()
CreateDatabase(stmt) => stmt.pretty()
CreateSchema(stmt) => stmt.pretty()
CreateFunction(stmt) => stmt.pretty()
CreateProcedure(stmt) => stmt.pretty()
CreateSequence(stmt) => stmt.pretty()
DropView(stmt) => stmt.pretty()
DropTable(stmt) => stmt.pretty()
DropIndex(stmt) => stmt.pretty()
Insert(stmt) => stmt.pretty()
Delete(stmt) => stmt.pretty()
Update(stmt) => stmt.pretty()
Merge(stmt) => stmt.pretty()
Truncate(stmt) => stmt.pretty()
AlterTable(stmt) => stmt.pretty()
AlterIndex(stmt) => stmt.pretty()
Show(stmt) => stmt.pretty()
Set(stmt) => stmt.pretty()
Use(stmt) => stmt.pretty()
Copy(stmt) => stmt.pretty()
LoadData(stmt) => stmt.pretty()
Prepare(stmt) => stmt.pretty()
Execute(stmt) => stmt.pretty()
Deallocate(stmt) => stmt.pretty()
LockTables(tables) =>
@pp.text("LOCK TABLES ") +
@pp.separate(@pp.text(", "), tables.map(@pp.pretty))
UnlockTables => @pp.text("UNLOCK TABLES")
Listen(channel) => @pp.text("LISTEN ") + @pp.text(channel)
Notify(channel, payload) =>
@pp.text("NOTIFY ") +
@pp.text(channel) +
(match payload {
Some(msg) => @pp.text(", '") + @pp.text(msg) + @pp.text("'")
None => @pp.empty
})
Begin(stmt) => stmt.pretty()
Commit(stmt) => stmt.pretty()
Rollback(stmt) => stmt.pretty()
Savepoint(stmt) => stmt.pretty()
ReleaseSavepoint(stmt) => stmt.pretty()
Grant(stmt) => stmt.pretty()
Revoke(stmt) => stmt.pretty()
}) +
@pp.text(";")
}
///|
pub(all) struct QueryStmt {
// WITH AS (...), AS (...)
with_clause : Array[Cte]?
// SELECT ...
body : SetExpr
// ORDER BY
order_by : Array[OrderByExpr]
// LIMIT
limit : Expr?
// OFFSET
offset : Expr?
} derive(Eq, Show)
///|
pub impl @pp.Pretty for QueryStmt with pretty(self) {
(match self.with_clause {
Some(ctes) =>
@pp.text("WITH ") +
@pp.separate(@pp.text(", ") + @pp.hardline, ctes.map(@pp.pretty)) +
@pp.hardline
None => @pp.empty
}) +
self.body.pretty() +
(if self.order_by.length() > 0 {
@pp.hardline +
@pp.text("ORDER BY") +
@pp.nest(
@pp.hardline +
@pp.separate(@pp.char(',') + @pp.hardline, self.order_by.map(@pp.pretty)),
)
} else {
@pp.text("")
}) +
(match self.limit {
Some(expr) =>
@pp.hardline + @pp.text("LIMIT") + @pp.nest(@pp.hardline + expr.pretty())
None => @pp.empty
}) +
(match self.offset {
Some(expr) =>
@pp.hardline + @pp.text("OFFSET") + @pp.nest(@pp.hardline + expr.pretty())
None => @pp.empty
})
}
///|
enum SetExpr {
// Restricted
Select(SelectStmt)
// Parenthesized
Query(QueryStmt)
// UNION, INTERSECT, EXCEPT
SetOperation(SetOperator, SetExpr, SetExpr)
} derive(Eq, Show)
///|
impl @pp.Pretty for SetExpr with pretty(self) {
match self {
Select(stmt) => stmt.pretty()
Query(stmt) =>
@pp.parens(@pp.nest(@pp.hardline + stmt.pretty()) + @pp.hardline)
SetOperation(op, left, right) =>
left.pretty() + @pp.hardline + op.pretty() + @pp.hardline + right.pretty()
}
}
///|
struct SelectStmt {
// SELECT
projections : Array[Projection]
// TOP
top : Top?
// (DISTINCT )
distinct : Bool
// FROM
from : Array[TableRef]
// WHERE
where_clause : Expr?
// GROUP BY
group_by : Array[Expr]
// HAVING
having : Expr?
} derive(Eq, Show)
///|
pub impl @pp.Pretty for SelectStmt with pretty(self) {
@pp.text("SELECT") +
(if self.top is Some(top) { top.pretty() } else { @pp.empty }) +
(if self.distinct { @pp.text(" DISTINCT") } else { @pp.empty }) +
(if self.projections.length() == 0 {
@pp.nest(@pp.hardline + @pp.text("*"))
} else {
@pp.nest(
@pp.hardline +
@pp.separate(
@pp.char(',') + @pp.hardline,
self.projections.map(@pp.pretty),
),
)
}) +
@pp.hardline +
@pp.text("FROM") +
@pp.nest(
@pp.hardline +
@pp.separate(@pp.char(',') + @pp.hardline, self.from.map(@pp.pretty)),
) +
(match self.where_clause {
Some(selection) =>
@pp.hardline +
@pp.text("WHERE") +
@pp.nest(@pp.hardline + selection.pretty())
None => @pp.empty
}) +
(if self.group_by.length() > 0 {
@pp.hardline +
@pp.text("GROUP BY") +
@pp.nest(
@pp.hardline +
@pp.separate(@pp.char(',') + @pp.hardline, self.group_by.map(@pp.pretty)),
)
} else {
@pp.text("")
}) +
(match self.having {
Some(expr) =>
@pp.hardline + @pp.text("HAVING") + @pp.nest(@pp.hardline + expr.pretty())
None => @pp.empty
})
}
///|
enum SetOperator {
Union
Intersect
Except
} derive(Eq, Show)
///|
pub impl @pp.Pretty for SetOperator with pretty(self) {
match self {
SetOperator::Union => @pp.text("UNION")
SetOperator::Intersect => @pp.text("INTERSECT")
SetOperator::Except => @pp.text("EXCEPT")
}
}
///|
pub(all) enum Projection {
Wildcard
UnamedExpr(Expr)
AliasedExpr(Expr, String)
} derive(Eq, Show)
///|
pub impl @pp.Pretty for Projection with pretty(self) {
match self {
Projection::Wildcard => @pp.char('*')
Projection::UnamedExpr(expr) => expr.pretty()
Projection::AliasedExpr(expr, alias_name) =>
expr.pretty() + @pp.text(" AS ") + @pp.text(alias_name)
}
}
///|
pub(all) enum Expr {
//
Identifier(String)
// .
CompoundIdentifier(Array[String])
// string, integer, double, boolean, null
Literal(Literal)
// +
BinaryOperation(Expr, BinaryOperator, Expr)
// -
UnaryOperation(UnaryOperator, Expr)
// sum(*) [FILTER (WHERE condition)]
FunctionCall(String, DuplicateTreatment?, Array[Expr], Expr?)
// *
Wildcard
// DATE '1998-05-19'
Datetime(String)
// INTERVAL '1 day' YEAR TO MONTH
Interval(String, IntervalQualifier)
// [NOT] LIKE
Like(positive~ : Bool, Expr, Expr)
// [NOT] ILIKE
ILike(positive~ : Bool, Expr, Expr)
// (SELECT ...)
SubQuery(QueryStmt)
// [NOT] EXISTS
Exists(positive~ : Bool, QueryStmt)
// [NOT] BETWEEN AND
Between(positive~ : Bool, Expr, Expr, Expr)
// EXTRACT(YEAR FROM )
Extract(PrimaryDatetimeField, Expr)
// CASE [operand] WHEN THEN [...] [ELSE ] END
Case(CaseExpr)
// [NOT] IN (, ...)
InList(positive~ : Bool, Expr, Array[Expr])
// [NOT] IN (SELECT ...)
InSubQuery(positive~ : Bool, Expr, QueryStmt)
// ```sql
// SUBSTRING( [FROM ] [FOR ])
// ```
// or
// ```sql
// SUBSTRING(, , )
// ```
Substring(Expr, Expr?, Expr?)
// Placeholder for prepared statements: ?, $1, :name, @param
Placeholder(String)
// Array expression: ARRAY[1, 2, 3] or [1, 2, 3]
Array(ArrayExpr)
// Array/Map indexing and slicing: arr[1], arr[1:3], obj.field
CompoundFieldAccess(Expr, Array[AccessExpr])
// Window function: func() OVER (PARTITION BY ... ORDER BY ... ROWS/RANGE ...)
WindowFunction(String, DuplicateTreatment?, Array[Expr], WindowSpec)
} derive(Eq, Show)
///|
fn wrap_parens(expr : Expr, current_op : BinaryOperator) -> @pp.Document {
if current_op == BinaryOperator::Or &&
expr is Expr::BinaryOperation(_, And, _) {
@pp.parens(@pp.nest(@pp.hardline + expr.pretty()) + @pp.hardline)
} else if expr is Expr::BinaryOperation(_, op, _) &&
op.get_precedence().value() < current_op.get_precedence().value() {
@pp.parens(expr.pretty())
} else {
expr.pretty()
}
}
///|
pub impl @pp.Pretty for Expr with pretty(self) {
match self {
Expr::Identifier(name) => @pp.text(name)
Expr::CompoundIdentifier(parts) =>
@pp.separate(@pp.text("."), parts.map(@pp.text))
Expr::Literal(lit) => lit.pretty()
Expr::BinaryOperation(left, op, right) =>
wrap_parens(left, op) +
@pp.space +
op.pretty() +
@pp.space +
wrap_parens(right, op)
Expr::UnaryOperation(op, expr) => op.pretty() + expr.pretty()
Expr::FunctionCall(name, dup, args, filter) =>
@pp.text(name) +
@pp.parens(
(if dup is Some(dup) { dup.pretty() + @pp.space } else { @pp.empty }) +
@pp.separate(@pp.text(", "), args.map(@pp.pretty)),
) +
(if filter is Some(filter) {
@pp.text(" FILTER (WHERE ") + filter.pretty() + @pp.text(")")
} else {
@pp.empty
})
Expr::Wildcard => @pp.char('*')
Datetime(s) => @pp.text("DATE '") + @pp.text(s) + @pp.char('\'')
Interval(s, qualifier) =>
@pp.text("INTERVAL '") + @pp.text(s) + @pp.text("' ") + qualifier.pretty()
Expr::Like(positive~, left, right) =>
left.pretty() +
@pp.text(if positive { "" } else { " NOT" }) +
@pp.text(" LIKE ") +
right.pretty()
Expr::ILike(positive~, left, right) =>
left.pretty() +
@pp.text(if positive { "" } else { " NOT" }) +
@pp.text(" ILIKE ") +
right.pretty()
Expr::SubQuery(stmt) =>
@pp.parens(@pp.nest(@pp.hardline + stmt.pretty()) + @pp.hardline)
Expr::Exists(positive~, stmt) =>
(if positive { @pp.text("EXISTS (") } else { @pp.text("NOT EXISTS (") }) +
@pp.hardline +
@pp.nest(stmt.pretty()) +
@pp.hardline +
@pp.char(')')
Expr::Between(positive~, e, low, hi) =>
e.pretty() +
@pp.text(if positive { " BETWEEN " } else { " NOT BETWEEN " }) +
low.pretty() +
@pp.text(" AND ") +
hi.pretty()
Expr::Extract(field, expr) =>
@pp.text("EXTRACT(") +
field.pretty() +
@pp.text(" FROM ") +
expr.pretty() +
@pp.char(')')
Expr::Case(case_expr) => case_expr.pretty()
Expr::InList(positive~, expr, items) =>
expr.pretty() +
@pp.text(if positive { " IN (" } else { " NOT IN (" }) +
@pp.separate(@pp.text(", "), items.map(@pp.pretty)) +
@pp.char(')')
Expr::InSubQuery(positive~, expr, sub_query) =>
expr.pretty() +
@pp.text(if positive { " IN (" } else { " NOT IN (" }) +
@pp.nest(@pp.hardline + sub_query.pretty()) +
@pp.hardline +
@pp.char(')')
Expr::Substring(expr, from, for_expr) =>
@pp.text("SUBSTRING(") +
expr.pretty() +
(match from {
Some(from_expr) => @pp.text(" FROM ") + from_expr.pretty()
None => @pp.empty
}) +
(match for_expr {
Some(for_expr) => @pp.text(" FOR ") + for_expr.pretty()
None => @pp.empty
}) +
@pp.char(')')
Expr::Placeholder(s) => @pp.text(s)
Expr::Array(arr) => arr.pretty()
Expr::CompoundFieldAccess(root, access_chain) =>
root.pretty() + @pp.separate(@pp.empty, access_chain.map(@pp.pretty))
Expr::WindowFunction(name, dup, args, window_spec) =>
@pp.text(name) +
@pp.parens(
(if dup is Some(dup) { dup.pretty() + @pp.space } else { @pp.empty }) +
@pp.separate(@pp.text(", "), args.map(@pp.pretty)),
) +
@pp.text(" OVER (") +
window_spec.pretty() +
@pp.text(")")
}
}
///|
pub(all) enum Literal {
Integer(Int)
Double(Double) // TODO: float32
String(String)
Boolean(Bool)
Null
} derive(Eq, Show)
///|
pub impl @pp.Pretty for Literal with pretty(self) {
match self {
Literal::Integer(value) => @pp.text("\{value}")
Literal::Double(value) => @pp.text("\{value}")
Literal::String(value) => @pp.text("'\{value}'")
Literal::Boolean(value) => @pp.text(if value { "TRUE" } else { "FALSE" })
Literal::Null => @pp.text("NULL")
}
}
///|
/// PostgreSQL array expression
pub(all) struct ArrayExpr {
elem : Array[Expr] // The list of expressions between brackets
named : Bool // true for ARRAY[..], false for [..]
} derive(Eq, Show)
///|
pub impl @pp.Pretty for ArrayExpr with pretty(self) {
(if self.named { @pp.text("ARRAY") } else { @pp.empty }) +
@pp.text("[") +
@pp.separate(@pp.text(", "), self.elem.map(@pp.pretty)) +
@pp.text("]")
}
///|
/// Access expression for array indexing, slicing, and field access
pub(all) enum AccessExpr {
Dot(Expr) // Field access: obj.field
Subscript(Subscript) // Array/map subscript: arr[index] or arr[start:end]
} derive(Eq, Show)
///|
pub impl @pp.Pretty for AccessExpr with pretty(self) {
match self {
AccessExpr::Dot(expr) => @pp.text(".") + expr.pretty()
AccessExpr::Subscript(subscript) => subscript.pretty()
}
}
///|
/// Subscript expression for array indexing and slicing
pub(all) enum Subscript {
Index(Expr) // Simple index: arr[1]
Slice(Expr?, Expr?, Expr?) // Array slice: arr[1:3] (lower, upper, stride)
} derive(Eq, Show)
///|
pub impl @pp.Pretty for Subscript with pretty(self) {
match self {
Subscript::Index(index) => @pp.text("[") + index.pretty() + @pp.text("]")
Subscript::Slice(lower, upper, stride) =>
@pp.text("[") +
(match lower {
Some(l) => l.pretty()
None => @pp.empty
}) +
@pp.text(":") +
(match upper {
Some(u) => u.pretty()
None => @pp.empty
}) +
(match stride {
Some(s) => @pp.text(":") + s.pretty()
None => @pp.empty
}) +
@pp.text("]")
}
}
///|
/// Common Table Expression (CTE) for WITH clauses
pub(all) struct Cte {
name : String // CTE name
query : QueryStmt // CTE query body
columns : Array[String]? // Optional column list: WITH cte(col1, col2) AS (...)
} derive(Eq, Show)
///|
pub impl @pp.Pretty for Cte with pretty(self) {
@pp.text(self.name) +
(match self.columns {
Some(cols) =>
@pp.text("(") +
@pp.separate(@pp.text(", "), cols.map(@pp.text)) +
@pp.text(")")
None => @pp.empty
}) +
@pp.text(" AS (") +
@pp.nest(@pp.hardline + self.query.pretty()) +
@pp.hardline +
@pp.text(")")
}
///|
pub(all) enum BinaryOperator {
Eq
Neq
Lt
Gt
LtEq
GtEq
Spaceship
Plus
Minus
Mul
Div
IntegerDiv // MySQL DIV keyword for integer division
Mod
And
Or
// PostgreSQL JSON operators
JsonExtract // ->
JsonExtractText // ->>
JsonExtractPath // #>
JsonExtractPathText // #>>
JsonContains // @>
JsonContainedIn // <@
} derive(Eq, Show)
///|
pub impl @pp.Pretty for BinaryOperator with pretty(self) {
match self {
BinaryOperator::Eq => @pp.text("=")
BinaryOperator::Neq => @pp.text("<>")
BinaryOperator::Lt => @pp.text("<")
BinaryOperator::Gt => @pp.text(">")
BinaryOperator::LtEq => @pp.text("<=")
BinaryOperator::GtEq => @pp.text(">=")
BinaryOperator::Spaceship => @pp.text("<=>")
BinaryOperator::Plus => @pp.text("+")
BinaryOperator::Minus => @pp.text("-")
BinaryOperator::Mul => @pp.text("*")
BinaryOperator::Div => @pp.text("/")
BinaryOperator::IntegerDiv => @pp.text(" DIV ")
BinaryOperator::Mod => @pp.text("%")
BinaryOperator::And => @pp.hardline + @pp.text("AND")
BinaryOperator::Or => @pp.hardline + @pp.text("OR")
// PostgreSQL JSON operators
BinaryOperator::JsonExtract => @pp.text("->")
BinaryOperator::JsonExtractText => @pp.text("->>")
BinaryOperator::JsonExtractPath => @pp.text("#>")
BinaryOperator::JsonExtractPathText => @pp.text("#>>")
BinaryOperator::JsonContains => @pp.text("@>")
BinaryOperator::JsonContainedIn => @pp.text("<@")
}
}
///|
pub(all) enum UnaryOperator {
Plus
Minus
Not
} derive(Eq, Show)
///|
pub impl @pp.Pretty for UnaryOperator with pretty(self) {
match self {
UnaryOperator::Plus => @pp.text("+")
UnaryOperator::Minus => @pp.text("-")
UnaryOperator::Not => @pp.text("NOT ")
}
}
///|
pub(all) struct OrderByExpr {
// ORDER BY
expr : Expr
// ASC | DESC
asc : Bool?
// NULLS FIRST | NULLS LAST
nulls_first : Bool?
} derive(Eq, Show)
///|
pub impl @pp.Pretty for OrderByExpr with pretty(self) {
self.expr.pretty() +
(match self.asc {
Some(true) => @pp.text(" ASC")
Some(false) => @pp.text(" DESC")
None => @pp.text("")
}) +
(match self.nulls_first {
Some(true) => @pp.text(" NULLS FIRST")
Some(false) => @pp.text(" NULLS LAST")
None => @pp.text("")
})
}
///|
pub(all) struct TableRef {
factor : TableFactor
joins : Array[Join]
} derive(Eq, Show)
///|
pub impl @pp.Pretty for TableRef with pretty(self) {
self.factor.pretty() +
@pp.separate_map(@pp.hardline, self.joins, fn(join) { join.pretty() })
}
///|
pub(all) enum TableFactor {
// [AS ]
Column(ObjectName, TableAlias?)
// FROM (SELECT ...) [AS ]
SubQuery(QueryStmt, TableAlias?)
} derive(Eq, Show)
///|
pub impl @pp.Pretty for TableFactor with pretty(self) {
match self {
TableFactor::Column(name, alias_name) =>
name.pretty() +
(match alias_name {
Some(a) => a.pretty()
None => @pp.empty
})
TableFactor::SubQuery(stmt, alias_name) =>
@pp.parens(@pp.nest(@pp.hardline + stmt.pretty()) + @pp.hardline) +
(match alias_name {
Some(a) => a.pretty()
None => @pp.empty
})
}
}
///|
pub(all) struct TableAlias {
/// AS
name : String
/// AS (, ...)
columns : Array[String]
} derive(Eq, Show)
///|
pub impl @pp.Pretty for TableAlias with pretty(self) {
@pp.text(" AS ") +
@pp.text(self.name) +
(if self.columns.length() > 0 {
@pp.space +
@pp.parens(@pp.separate(@pp.text(", "), self.columns.map(@pp.text)))
} else {
@pp.empty
})
}
///|
pub(all) struct Join {
// JOIN ON
table_ref : TableRef
// LEFT JOIN | RIGHT JOIN | FULL | INNER | CROSS
join_operator : JoinOperator
} derive(Eq, Show)
///|
pub impl @pp.Pretty for Join with pretty(self) {
@pp.space +
self.join_operator.pretty() +
@pp.space +
self.table_ref.pretty() +
(match self.join_operator {
JoinOperator::Join(constraint) => constraint.pretty()
JoinOperator::Left(constraint) => constraint.pretty()
JoinOperator::Right(constraint) => constraint.pretty()
JoinOperator::Full(constraint) => constraint.pretty()
JoinOperator::Inner(constraint) => constraint.pretty()
JoinOperator::LeftOuter(constraint) => constraint.pretty()
JoinOperator::RightOuter(constraint) => constraint.pretty()
JoinOperator::FullOuter(constraint) => constraint.pretty()
JoinOperator::Cross => @pp.empty
})
}
///|
pub(all) enum JoinOperator {
Join(JoinConstraint)
Left(JoinConstraint)
LeftOuter(JoinConstraint)
Right(JoinConstraint)
RightOuter(JoinConstraint)
Full(JoinConstraint)
FullOuter(JoinConstraint)
Inner(JoinConstraint)
Cross
} derive(Eq, Show)
///|
pub impl @pp.Pretty for JoinOperator with pretty(self) {
match self {
JoinOperator::Join(_) => @pp.text("JOIN")
JoinOperator::Left(_) => @pp.text("LEFT JOIN")
JoinOperator::LeftOuter(_) => @pp.text("LEFT OUTER JOIN")
JoinOperator::Right(_) => @pp.text("RIGHT JOIN")
JoinOperator::RightOuter(_) => @pp.text("RIGHT OUTER JOIN")
JoinOperator::Full(_) => @pp.text("FULL JOIN")
JoinOperator::FullOuter(_) => @pp.text("FULL OUTER JOIN")
JoinOperator::Inner(_) => @pp.text("INNER JOIN")
JoinOperator::Cross => @pp.text("CROSS JOIN")
}
}
///|
pub(all) enum JoinConstraint {
// ON
On(Expr)
// USING (, ...)
Using(Array[String])
Non
} derive(Eq, Show)
///|
pub impl @pp.Pretty for JoinConstraint with pretty(self) {
match self {
JoinConstraint::On(expr) =>
@pp.nest(@pp.hardline + @pp.text("ON ") + expr.pretty())
JoinConstraint::Using(cols) =>
@pp.nest(
@pp.hardline +
@pp.text("USING (") +
@pp.separate(@pp.text(", "), cols.map(@pp.text)) +
@pp.char(')'),
)
JoinConstraint::Non => @pp.empty
}
}
///|
pub(all) enum IntervalQualifier {
// YEAR, MONTH, DAY, HOUR, MINUTE, SECOND
Single(PrimaryDatetimeField)
// TO
Range(PrimaryDatetimeField, PrimaryDatetimeField)
} derive(Eq, Show)
///|
pub impl @pp.Pretty for IntervalQualifier with pretty(self) {
match self {
IntervalQualifier::Single(field) => field.pretty()
IntervalQualifier::Range(from, to) =>
@pp.group(from.pretty() + @pp.text(" TO ") + to.pretty())
}
}
///|
struct PrimaryDatetimeField {
// YEAR, MONTH, DAY, HOUR, MINUTE, SECOND
field : DatetimeUnit
// (3)
precision : Int?
} derive(Eq, Show)
///|
pub impl @pp.Pretty for PrimaryDatetimeField with pretty(self) {
self.field.pretty() +
@pp.group(
if self.precision is Some(p) {
@pp.text(" (\{p})")
} else {
@pp.text("")
},
)
}
///|
pub(all) enum DatetimeUnit {
Year
Month
Day
Hour
Minute
Second
} derive(Eq, Show)
///|
pub impl @pp.Pretty for DatetimeUnit with pretty(self) {
match self {
DatetimeUnit::Year => @pp.text("YEAR")
DatetimeUnit::Month => @pp.text("MONTH")
DatetimeUnit::Day => @pp.text("DAY")
DatetimeUnit::Hour => @pp.text("HOUR")
DatetimeUnit::Minute => @pp.text("MINUTE")
DatetimeUnit::Second => @pp.text("SECOND")
}
}
///|
/// CASE [operand] WHEN THEN [...] [ELSE ] END
pub(all) struct CaseExpr {
// CASE [operand]
operand : Expr?
// WHEN THEN [...]
when_then_clauses : Array[(Expr, Expr)]
// ELSE
else_expr : Expr?
} derive(Eq, Show)
///|
pub impl @pp.Pretty for CaseExpr with pretty(self) {
@pp.text("CASE") +
(match self.operand {
Some(op) => @pp.space + op.pretty()
None => @pp.empty
}) +
@pp.nest(
@pp.hardline +
@pp.separate(
@pp.hardline,
self.when_then_clauses.map(fn(condition_result) {
let condition = condition_result.0
let result = condition_result.1
@pp.text("WHEN ") +
condition.pretty() +
@pp.text(" THEN ") +
result.pretty()
}),
) +
(match self.else_expr {
Some(else_expr) => @pp.hardline + @pp.text("ELSE ") + else_expr.pretty()
None => @pp.empty
}),
) +
@pp.hardline +
@pp.text("END")
}
///|
/// Window function specification: OVER ([PARTITION BY ...] [ORDER BY ...] [frame_clause])
pub(all) struct WindowSpec {
// PARTITION BY , ...
partition_by : Array[Expr]
// ORDER BY [ASC|DESC] [NULLS FIRST|LAST], ...
order_by : Array[OrderByExpr]
// frame clause: ROWS/RANGE BETWEEN ... AND ... or ROWS/RANGE ...
frame_clause : WindowFrameClause?
} derive(Eq, Show)
///|
/// Window frame clause
pub(all) struct WindowFrameClause {
// ROWS or RANGE
frame_units : WindowFrameUnits
// Frame bounds
frame_start : WindowFrameBound
frame_end : WindowFrameBound?
} derive(Eq, Show)
///|
/// Window frame units
pub(all) enum WindowFrameUnits {
Rows
Range
} derive(Eq, Show)
///|
/// Window frame bound
pub(all) enum WindowFrameBound {
UnboundedPreceding
UnboundedFollowing
CurrentRow
Preceding(Expr)
Following(Expr)
} derive(Eq, Show)
///|
pub impl @pp.Pretty for WindowSpec with pretty(self) {
let partition_clause = if self.partition_by.is_empty() {
@pp.empty
} else {
@pp.text("PARTITION BY ") +
@pp.separate(@pp.text(", "), self.partition_by.map(@pp.pretty))
}
let order_clause = if self.order_by.is_empty() {
@pp.empty
} else {
@pp.text("ORDER BY ") +
@pp.separate(@pp.text(", "), self.order_by.map(@pp.pretty))
}
let frame_clause = match self.frame_clause {
Some(frame) => @pp.space + frame.pretty()
None => @pp.empty
}
let clauses = []
if !self.partition_by.is_empty() {
clauses.push(partition_clause)
}
if !self.order_by.is_empty() {
clauses.push(order_clause)
}
@pp.separate(@pp.space, clauses) + frame_clause
}
///|
pub impl @pp.Pretty for WindowFrameClause with pretty(self) {
let units_text = match self.frame_units {
WindowFrameUnits::Rows => "ROWS"
WindowFrameUnits::Range => "RANGE"
}
match self.frame_end {
Some(end_bound) =>
@pp.text(units_text) +
@pp.text(" BETWEEN ") +
self.frame_start.pretty() +
@pp.text(" AND ") +
end_bound.pretty()
None => @pp.text(units_text) + @pp.space + self.frame_start.pretty()
}
}
///|
pub impl @pp.Pretty for WindowFrameBound with pretty(self) {
match self {
WindowFrameBound::UnboundedPreceding => @pp.text("UNBOUNDED PRECEDING")
WindowFrameBound::UnboundedFollowing => @pp.text("UNBOUNDED FOLLOWING")
WindowFrameBound::CurrentRow => @pp.text("CURRENT ROW")
WindowFrameBound::Preceding(expr) => expr.pretty() + @pp.text(" PRECEDING")
WindowFrameBound::Following(expr) => expr.pretty() + @pp.text(" FOLLOWING")
}
}
///|
pub(all) struct CreateTableStmt {
// CREATE TABLE
name : String
// IF NOT EXISTS flag
if_not_exists : Bool
// Table definition - either column definitions or AS SELECT query
definition : CreateTableDefinition
} derive(Eq, Show)
///|
pub(all) enum CreateTableDefinition {
/// (col_name col_type, ...)
Columns(Array[ColumnDef], Array[TableConstraint])
/// AS SELECT ...
AsQuery(QueryStmt)
} derive(Eq, Show)
///|
pub impl @pp.Pretty for CreateTableStmt with pretty(self) {
@pp.text("CREATE TABLE ") +
(if self.if_not_exists { @pp.text("IF NOT EXISTS ") } else { @pp.empty }) +
@pp.text(self.name) +
@pp.space +
self.definition.pretty()
}
///|
pub impl @pp.Pretty for CreateTableDefinition with pretty(self) {
match self {
CreateTableDefinition::Columns(columns, constraints) =>
@pp.text("(") +
@pp.nest(
@pp.hardline +
@pp.separate(@pp.text(", ") + @pp.hardline, columns.map(@pp.pretty)) +
(if constraints.length() == 0 {
@pp.empty
} else {
@pp.text(", ") +
@pp.hardline +
@pp.separate(
@pp.text(", ") + @pp.hardline,
constraints.map(@pp.pretty),
)
}),
) +
@pp.hardline +
@pp.text(")")
CreateTableDefinition::AsQuery(query) => @pp.text("AS ") + query.pretty()
}
}
///|
pub(all) struct ColumnDef {
name : String
data_type : DataType
options : Array[ColumnDefOption]
} derive(Eq, Show)
///|
pub impl @pp.Pretty for ColumnDef with pretty(self) {
@pp.text(self.name) +
@pp.space +
self.data_type.pretty() +
@pp.separate(@pp.empty, self.options.map(@pp.pretty))
}
///|
pub(all) enum DataType {
Integer
Smallint
Bigint
Float(Int?)
Real
Double
Char(Int)
Varchar(Int)
Text
Boolean
Timestamp
Blob
// Interval
} derive(Eq, Show)
///|
pub impl @pp.Pretty for DataType with pretty(self) {
match self {
Integer => @pp.text("INTEGER")
Smallint => @pp.text("SMALLINT")
Bigint => @pp.text("BIGINT")
Float(Some(p)) => @pp.text("FLOAT(\{p})")
Float(None) => @pp.text("FLOAT")
Real => @pp.text("REAL")
Double => @pp.text("DOUBLE")
Char(size) => @pp.text("CHAR(\{size})")
Varchar(size) => @pp.text("VARCHAR(\{size})")
Text => @pp.text("TEXT")
Boolean => @pp.text("BOOLEAN")
Timestamp => @pp.text("TIMESTAMP")
Blob => @pp.text("BLOB")
}
}
///|
pub(all) enum ColumnDefOption {
NotNull
Unique
Default(Expr)
PrimaryKey
} derive(Eq, Show)
///|
pub impl @pp.Pretty for ColumnDefOption with pretty(self) {
match self {
NotNull => @pp.text(" NOT NULL")
Unique => @pp.text(" UNIQUE")
Default(expr) => @pp.text(" DEFAULT ") + expr.pretty()
PrimaryKey => @pp.text(" PRIMARY KEY")
}
}
///|
struct CreateViewStmt {
// CREATE VIEW
name : String
// [(, ...)]
columns : Array[ViewColumnDef]
// AS
query : QueryStmt
} derive(Eq, Show)
///|
pub impl @pp.Pretty for CreateViewStmt with pretty(self) {
@pp.text("CREATE VIEW ") +
@pp.text(self.name) +
(if self.columns.length() > 0 {
@pp.text(" (") +
@pp.separate(@pp.text(", "), self.columns.map(@pp.pretty)) +
@pp.char(')')
} else {
@pp.empty
}) +
@pp.space +
@pp.text("AS") +
@pp.hardline +
self.query.pretty()
}
///|
pub(all) struct ViewColumnDef {
name : String
} derive(Eq, Show)
///|
/// CREATE INDEX statement
pub(all) struct CreateIndexStmt {
// CREATE [UNIQUE] INDEX [CONCURRENTLY] [IF NOT EXISTS]
unique : Bool
concurrently : Bool
if_not_exists : Bool
name : String
// ON
table_name : ObjectName
// [USING ] - e.g., BTREE, HASH, GIN, GIST
index_method : IndexMethod?
// ( [ASC|DESC] [NULLS FIRST|LAST], ...)
columns : Array[IndexColumn]
// [WHERE ] - partial index
where_clause : Expr?
} derive(Eq, Show)
///|
/// Index creation method
pub(all) enum IndexMethod {
Btree
Hash
Gin
Gist
Spgist
Brin
} derive(Eq, Show)
///|
/// Index column specification
pub(all) struct IndexColumn {
name : Expr // Can be column name or expression
asc : Bool? // None = default, Some(true) = ASC, Some(false) = DESC
nulls_first : Bool? // None = default, Some(true) = NULLS FIRST, Some(false) = NULLS LAST
} derive(Eq, Show)
///|
pub impl @pp.Pretty for CreateIndexStmt with pretty(self) {
@pp.text("CREATE") +
(if self.unique { @pp.text(" UNIQUE") } else { @pp.empty }) +
@pp.text(" INDEX") +
(if self.concurrently { @pp.text(" CONCURRENTLY") } else { @pp.empty }) +
(if self.if_not_exists { @pp.text(" IF NOT EXISTS") } else { @pp.empty }) +
@pp.text(" ") +
@pp.text(self.name) +
@pp.text(" ON ") +
self.table_name.pretty() +
(match self.index_method {
Some(index_method) => @pp.text(" USING ") + index_method.pretty()
None => @pp.empty
}) +
@pp.text(" (") +
@pp.separate(@pp.text(", "), self.columns.map(@pp.pretty)) +
@pp.text(")") +
(match self.where_clause {
Some(expr) => @pp.text(" WHERE ") + expr.pretty()
None => @pp.empty
})
}
///|
pub impl @pp.Pretty for IndexMethod with pretty(self) {
match self {
IndexMethod::Btree => @pp.text("BTREE")
IndexMethod::Hash => @pp.text("HASH")
IndexMethod::Gin => @pp.text("GIN")
IndexMethod::Gist => @pp.text("GIST")
IndexMethod::Spgist => @pp.text("SPGIST")
IndexMethod::Brin => @pp.text("BRIN")
}
}
///|
pub impl @pp.Pretty for IndexColumn with pretty(self) {
self.name.pretty() +
(match self.asc {
Some(true) => @pp.text(" ASC")
Some(false) => @pp.text(" DESC")
None => @pp.empty
}) +
(match self.nulls_first {
Some(true) => @pp.text(" NULLS FIRST")
Some(false) => @pp.text(" NULLS LAST")
None => @pp.empty
})
}
///|
pub impl @pp.Pretty for ViewColumnDef with pretty(self) {
@pp.text(self.name)
}
///|
/// CREATE DATABASE statement
pub(all) struct CreateDatabaseStmt {
/// CREATE DATABASE
name : String
/// IF NOT EXISTS flag
if_not_exists : Bool
/// Optional character set specification
character_set : String?
/// Optional collation specification
collate : String?
} derive(Eq, Show)
///|
pub impl @pp.Pretty for CreateDatabaseStmt with pretty(self) {
@pp.text("CREATE DATABASE ") +
(if self.if_not_exists { @pp.text("IF NOT EXISTS ") } else { @pp.empty }) +
@pp.text(self.name) +
(match self.character_set {
Some(charset) => @pp.text(" CHARACTER SET ") + @pp.text(charset)
None => @pp.empty
}) +
(match self.collate {
Some(collation) => @pp.text(" COLLATE ") + @pp.text(collation)
None => @pp.empty
})
}
///|
/// CREATE SCHEMA statement (synonym for CREATE DATABASE in many dialects)
pub(all) struct CreateSchemaStmt {
/// CREATE SCHEMA
name : String
/// IF NOT EXISTS flag
if_not_exists : Bool
/// Optional AUTHORIZATION user
authorization : String?
} derive(Eq, Show)
///|
pub impl @pp.Pretty for CreateSchemaStmt with pretty(self) {
@pp.text("CREATE SCHEMA ") +
(if self.if_not_exists { @pp.text("IF NOT EXISTS ") } else { @pp.empty }) +
@pp.text(self.name) +
(match self.authorization {
Some(user) => @pp.text(" AUTHORIZATION ") + @pp.text(user)
None => @pp.empty
})
}
///|
/// CREATE FUNCTION statement
pub(all) struct CreateFunctionStmt {
/// CREATE FUNCTION
name : String
/// Function parameters: (param1 type1, param2 type2, ...)
parameters : Array[FunctionParameter]
/// RETURNS return_type
return_type : DataType?
/// Function body language (SQL, plpgsql, etc.)
language : String?
/// Function body/definition
body : String?
/// DETERMINISTIC flag
deterministic : Bool
/// IF NOT EXISTS flag
if_not_exists : Bool
} derive(Eq, Show)
///|
/// Function parameter definition
pub(all) struct FunctionParameter {
/// Parameter name
name : String
/// Parameter data type
param_type : DataType
/// IN/OUT/INOUT mode (default IN)
mode : ParameterMode?
} derive(Eq, Show)
///|
/// Parameter mode for functions/procedures
pub(all) enum ParameterMode {
In
Out
InOut
} derive(Eq, Show)
///|
/// CREATE PROCEDURE statement
pub(all) struct CreateProcedureStmt {
/// CREATE PROCEDURE
name : String
/// Procedure parameters: (param1 type1, param2 type2, ...)
parameters : Array[FunctionParameter]
/// Procedure body language (SQL, plpgsql, etc.)
language : String?
/// Procedure body/definition
body : String?
/// IF NOT EXISTS flag
if_not_exists : Bool
} derive(Eq, Show)
///|
pub impl @pp.Pretty for CreateFunctionStmt with pretty(self) {
@pp.text("CREATE FUNCTION ") +
(if self.if_not_exists { @pp.text("IF NOT EXISTS ") } else { @pp.empty }) +
@pp.text(self.name) +
@pp.parens(@pp.separate(@pp.text(", "), self.parameters.map(@pp.pretty))) +
(match self.return_type {
Some(ret_type) => @pp.text(" RETURNS ") + ret_type.pretty()
None => @pp.empty
}) +
(match self.language {
Some(lang) => @pp.text(" LANGUAGE ") + @pp.text(lang)
None => @pp.empty
}) +
(if self.deterministic { @pp.text(" DETERMINISTIC") } else { @pp.empty }) +
(match self.body {
Some(body_text) =>
@pp.text(" AS ") + @pp.text("'") + @pp.text(body_text) + @pp.text("'")
None => @pp.empty
})
}
///|
pub impl @pp.Pretty for CreateProcedureStmt with pretty(self) {
@pp.text("CREATE PROCEDURE ") +
(if self.if_not_exists { @pp.text("IF NOT EXISTS ") } else { @pp.empty }) +
@pp.text(self.name) +
@pp.parens(@pp.separate(@pp.text(", "), self.parameters.map(@pp.pretty))) +
(match self.language {
Some(lang) => @pp.text(" LANGUAGE ") + @pp.text(lang)
None => @pp.empty
}) +
(match self.body {
Some(body_text) =>
@pp.text(" AS ") + @pp.text("'") + @pp.text(body_text) + @pp.text("'")
None => @pp.empty
})
}
///|
/// CREATE SEQUENCE statement
pub(all) struct CreateSequenceStmt {
/// CREATE SEQUENCE
name : String
/// IF NOT EXISTS flag
if_not_exists : Bool
/// TEMPORARY flag
temporary : Bool
/// INCREMENT BY value (default 1)
increment : Int?
/// MINVALUE value or NO MINVALUE
minvalue : SequenceLimit?
/// MAXVALUE value or NO MAXVALUE
maxvalue : SequenceLimit?
/// START WITH value (default 1)
start_with : Int?
/// CACHE value
cache : Int?
/// CYCLE or NO CYCLE (default NO CYCLE)
cycle : Bool?
/// OWNED BY table.column or OWNED BY NONE
owned_by : SequenceOwnedBy?
} derive(Eq, Show)
///|
/// Sequence limit specification
pub(all) enum SequenceLimit {
/// MINVALUE/MAXVALUE value
Value(Int)
/// NO MINVALUE/NO MAXVALUE
NoLimit
} derive(Eq, Show)
///|
/// OWNED BY specification for sequences
pub(all) enum SequenceOwnedBy {
/// OWNED BY table.column
Column(ObjectName, String)
/// OWNED BY NONE
ByNone
} derive(Eq, Show)
///|
pub impl @pp.Pretty for CreateSequenceStmt with pretty(self) {
@pp.text("CREATE ") +
(if self.temporary { @pp.text("TEMPORARY ") } else { @pp.empty }) +
@pp.text("SEQUENCE ") +
(if self.if_not_exists { @pp.text("IF NOT EXISTS ") } else { @pp.empty }) +
@pp.text(self.name) +
(match self.increment {
Some(inc) => @pp.text(" INCREMENT BY ") + @pp.text(inc.to_string())
None => @pp.empty
}) +
(match self.minvalue {
Some(SequenceLimit::Value(val)) =>
@pp.text(" MINVALUE ") + @pp.text(val.to_string())
Some(SequenceLimit::NoLimit) => @pp.text(" NO MINVALUE")
None => @pp.empty
}) +
(match self.maxvalue {
Some(SequenceLimit::Value(val)) =>
@pp.text(" MAXVALUE ") + @pp.text(val.to_string())
Some(SequenceLimit::NoLimit) => @pp.text(" NO MAXVALUE")
None => @pp.empty
}) +
(match self.start_with {
Some(start) => @pp.text(" START WITH ") + @pp.text(start.to_string())
None => @pp.empty
}) +
(match self.cache {
Some(cache_val) => @pp.text(" CACHE ") + @pp.text(cache_val.to_string())
None => @pp.empty
}) +
(match self.cycle {
Some(true) => @pp.text(" CYCLE")
Some(false) => @pp.text(" NO CYCLE")
None => @pp.empty
}) +
(match self.owned_by {
Some(SequenceOwnedBy::Column(table, column)) =>
@pp.text(" OWNED BY ") + table.pretty() + @pp.text(".") + @pp.text(column)
Some(SequenceOwnedBy::ByNone) => @pp.text(" OWNED BY NONE")
None => @pp.empty
})
}
///|
pub impl @pp.Pretty for SequenceLimit with pretty(self) {
match self {
SequenceLimit::Value(val) => @pp.text(val.to_string())
SequenceLimit::NoLimit => @pp.text("NO LIMIT")
}
}
///|
pub impl @pp.Pretty for SequenceOwnedBy with pretty(self) {
match self {
SequenceOwnedBy::Column(table, column) =>
table.pretty() + @pp.text(".") + @pp.text(column)
SequenceOwnedBy::ByNone => @pp.text("NONE")
}
}
///|
pub impl @pp.Pretty for FunctionParameter with pretty(self) {
(match self.mode {
Some(ParameterMode::In) => @pp.text("IN ")
Some(ParameterMode::Out) => @pp.text("OUT ")
Some(ParameterMode::InOut) => @pp.text("INOUT ")
None => @pp.empty
}) +
@pp.text(self.name) +
@pp.space +
self.param_type.pretty()
}
///|
pub impl @pp.Pretty for ParameterMode with pretty(self) {
match self {
ParameterMode::In => @pp.text("IN")
ParameterMode::Out => @pp.text("OUT")
ParameterMode::InOut => @pp.text("INOUT")
}
}
///|
pub(all) struct DropViewStmt {
// DROP VIEW
name : String
} derive(Eq, Show)
///|
pub impl @pp.Pretty for DropViewStmt with pretty(self) {
@pp.text("DROP VIEW ") + @pp.text(self.name)
}
///|
pub(all) enum DuplicateTreatment {
// DISTINCT
Distinct
// ALL
All
} derive(Eq, Show)
///|
pub impl @pp.Pretty for DuplicateTreatment with pretty(self) {
match self {
DuplicateTreatment::Distinct => @pp.text("DISTINCT")
DuplicateTreatment::All => @pp.text("ALL")
}
}
///|
pub(all) enum TableConstraint {
Unique(Array[OrderByExpr])
PrimaryKey(Array[OrderByExpr])
ForeignKey(
columns~ : Array[OrderByExpr],
foreign_table~ : ObjectName,
foreign_columns~ : Array[String]
)
Check(Expr)
} derive(Eq, Show)
///|
pub impl @pp.Pretty for TableConstraint with pretty(self) {
match self {
Unique(cols) =>
@pp.text("UNIQUE ") +
@pp.parens(@pp.separate(@pp.text(", "), cols.map(@pp.pretty)))
PrimaryKey(cols) =>
@pp.text("PRIMARY KEY ") +
@pp.parens(@pp.separate(@pp.text(", "), cols.map(@pp.pretty)))
ForeignKey(columns~, foreign_table~, foreign_columns~) =>
@pp.text("FOREIGN KEY ") +
@pp.parens(@pp.separate(@pp.text(", "), columns.map(@pp.pretty))) +
@pp.text(" REFERENCES ") +
foreign_table.pretty() +
(if foreign_columns.length() == 0 {
@pp.empty
} else {
@pp.space +
@pp.parens(@pp.separate(@pp.text(", "), foreign_columns.map(@pp.text)))
})
Check(expr) => @pp.text("CHECK (") + expr.pretty() + @pp.char(')')
}
}
///|
pub(all) struct ObjectName {
// ..
parts : Array[String]
} derive(Eq, Show)
///|
pub impl @pp.Pretty for ObjectName with pretty(self) {
@pp.separate(
@pp.text("."),
self.parts.map(fn(part) {
// Add backticks if the identifier contains spaces or special characters
if part.contains(" ") || part.contains("-") || part.contains(".") {
@pp.text("`" + part + "`")
} else {
@pp.text(part)
}
}),
)
}
///|
pub(all) enum Top {
Constant(Int)
Expr(Expr)
} derive(Eq, Show)
///|
pub impl @pp.Pretty for Top with pretty(self) {
match self {
Top::Constant(value) => @pp.text(" TOP \{value}")
Top::Expr(expr) => @pp.text(" TOP (") + expr.pretty() + @pp.text(")")
}
}
///|
pub(all) struct InsertStmt {
// INSERT INTO
table_name : ObjectName
// Optional column list: (, , ...)
columns : Array[String]
// The inserted data source
source : InsertSource
// SQLite-style conflict resolution (OR REPLACE, etc.)
or : SqliteOnConflict?
// MySQL ON DUPLICATE KEY UPDATE or PostgreSQL ON CONFLICT
on : OnInsert?
} derive(Eq, Show)
///|
pub impl @pp.Pretty for InsertStmt with pretty(self) {
// Handle REPLACE vs INSERT OR REPLACE
(match self.or {
Some(SqliteOnConflict::Replace) => @pp.text("REPLACE INTO ")
Some(conflict) =>
@pp.text("INSERT ") + conflict.pretty() + @pp.text(" INTO ")
None => @pp.text("INSERT INTO ")
}) +
self.table_name.pretty() +
(if self.columns.length() > 0 {
@pp.space +
@pp.parens(@pp.separate(@pp.text(", "), self.columns.map(@pp.text)))
} else {
@pp.empty
}) +
@pp.space +
self.source.pretty() +
(match self.on {
Some(on_insert) => @pp.space + on_insert.pretty()
None => @pp.empty
})
}
///|
pub(all) enum InsertSource {
// VALUES (, ...), (, ...), ...
Values(Array[Array[Expr]])
// SELECT ...
Query(QueryStmt)
} derive(Eq, Show)
///|
pub impl @pp.Pretty for InsertSource with pretty(self) {
match self {
InsertSource::Values(rows) =>
@pp.text("VALUES ") +
@pp.separate(
@pp.text(", "),
rows.map(fn(row) {
@pp.parens(@pp.separate(@pp.text(", "), row.map(@pp.pretty)))
}),
)
InsertSource::Query(query) => query.pretty()
}
}
///|
pub(all) struct DeleteStmt {
/// DELETE FROM
table_name : ObjectName
/// Optional WHERE clause
where_clause : Expr?
} derive(Eq, Show)
///|
pub impl @pp.Pretty for DeleteStmt with pretty(self) {
@pp.text("DELETE FROM ") +
self.table_name.pretty() +
(match self.where_clause {
Some(expr) => @pp.hardline + @pp.text("WHERE") + @pp.space + expr.pretty()
None => @pp.empty
})
}
///|
pub(all) struct UpdateStmt {
// UPDATE
table_name : ObjectName
// SET assignments
assignments : Array[Assignment]
// Optional WHERE clause
where_clause : Expr?
} derive(Eq, Show)
///|
pub(all) struct Assignment {
// Column name
column : String
// Value expression
value : Expr
} derive(Eq, Show)
///|
pub impl @pp.Pretty for UpdateStmt with pretty(self) {
@pp.text("UPDATE ") +
self.table_name.pretty() +
@pp.hardline +
@pp.text("SET ") +
@pp.separate(@pp.text(", "), self.assignments.map(@pp.pretty)) +
(match self.where_clause {
Some(expr) => @pp.hardline + @pp.text("WHERE") + @pp.space + expr.pretty()
None => @pp.empty
})
}
///|
pub impl @pp.Pretty for Assignment with pretty(self) {
@pp.text(self.column) + @pp.text(" = ") + self.value.pretty()
}
///|
/// MERGE statement for conditional INSERT/UPDATE/DELETE operations
pub(all) struct MergeStmt {
/// Target table: MERGE INTO target_table
target_table : ObjectName
/// Target table alias: MERGE INTO target_table AS alias
target_alias : String?
/// Source table/query: USING source_table or USING (SELECT ...)
source : MergeSource
/// Source alias: USING source AS alias
source_alias : String?
/// Join condition: ON condition
join_condition : Expr
/// WHEN clauses defining actions
when_clauses : Array[MergeWhenClause]
} derive(Eq, Show)
///|
/// Source for MERGE statement
pub(all) enum MergeSource {
/// Table source: USING table_name
Table(ObjectName)
/// Subquery source: USING (SELECT ...)
Query(QueryStmt)
} derive(Eq, Show)
///|
/// WHEN clause in MERGE statement
pub(all) struct MergeWhenClause {
/// WHEN MATCHED / WHEN NOT MATCHED
match_type : MergeMatchType
/// Optional additional condition: WHEN MATCHED AND condition
condition : Expr?
/// Action to perform: INSERT / UPDATE / DELETE
action : MergeAction
} derive(Eq, Show)
///|
/// Match type for WHEN clause
pub(all) enum MergeMatchType {
/// WHEN MATCHED
Matched
/// WHEN NOT MATCHED
NotMatched
} derive(Eq, Show)
///|
/// Actions that can be performed in MERGE
pub(all) enum MergeAction {
/// INSERT (columns) VALUES (values)
Insert(Array[String], Array[Expr]) // columns, values
/// UPDATE SET assignments
Update(Array[Assignment])
/// DELETE (no parameters needed)
Delete
} derive(Eq, Show)
///|
pub impl @pp.Pretty for MergeStmt with pretty(self) {
@pp.text("MERGE INTO ") +
self.target_table.pretty() +
(match self.target_alias {
Some(target_alias) => @pp.text(" AS ") + @pp.text(target_alias)
None => @pp.empty
}) +
@pp.hardline +
@pp.text("USING ") +
self.source.pretty() +
(match self.source_alias {
Some(source_alias) => @pp.text(" AS ") + @pp.text(source_alias)
None => @pp.empty
}) +
@pp.hardline +
@pp.text("ON ") +
self.join_condition.pretty() +
(if self.when_clauses.is_empty() {
@pp.empty
} else {
@pp.hardline +
@pp.separate(
@pp.hardline,
self.when_clauses.map(fn(clause) { @pp.text(" ") + clause.pretty() }),
)
})
}
///|
pub impl @pp.Pretty for MergeSource with pretty(self) {
match self {
MergeSource::Table(table) => table.pretty()
MergeSource::Query(query) =>
@pp.parens(@pp.nest(@pp.hardline + query.pretty()) + @pp.hardline)
}
}
///|
pub impl @pp.Pretty for MergeWhenClause with pretty(self) {
@pp.text("WHEN ") +
self.match_type.pretty() +
(match self.condition {
Some(cond) => @pp.text(" AND ") + cond.pretty()
None => @pp.empty
}) +
@pp.text(" THEN ") +
self.action.pretty()
}
///|
pub impl @pp.Pretty for MergeMatchType with pretty(self) {
match self {
MergeMatchType::Matched => @pp.text("MATCHED")
MergeMatchType::NotMatched => @pp.text("NOT MATCHED")
}
}
///|
pub impl @pp.Pretty for MergeAction with pretty(self) {
match self {
MergeAction::Insert(columns, values) =>
@pp.text("INSERT ") +
(if columns.length() > 0 {
@pp.parens(@pp.separate(@pp.text(", "), columns.map(@pp.text))) +
@pp.space
} else {
@pp.empty
}) +
@pp.text("VALUES ") +
@pp.parens(@pp.separate(@pp.text(", "), values.map(@pp.pretty)))
MergeAction::Update(assignments) =>
@pp.text("UPDATE SET ") +
@pp.separate(@pp.text(", "), assignments.map(@pp.pretty))
MergeAction::Delete => @pp.text("DELETE")
}
}
///|
pub(all) struct TruncateStmt {
// TRUNCATE
table_name : ObjectName
} derive(Eq, Show)
///|
pub impl @pp.Pretty for TruncateStmt with pretty(self) {
@pp.text("TRUNCATE ") + self.table_name.pretty()
}
///|
pub(all) struct DropTableStmt {
/// DROP TABLE
table_name : ObjectName
/// IF EXISTS flag
if_exists : Bool
} derive(Eq, Show)
///|
pub impl @pp.Pretty for DropTableStmt with pretty(self) {
@pp.text("DROP TABLE ") +
(if self.if_exists { @pp.text("IF EXISTS ") } else { @pp.empty }) +
self.table_name.pretty()
}
///|
pub(all) struct DropIndexStmt {
/// DROP INDEX
name : String
/// IF EXISTS flag
if_exists : Bool
/// CONCURRENTLY flag (PostgreSQL)
concurrently : Bool
/// ON table_name (some dialects require table name)
table_name : ObjectName?
} derive(Eq, Show)
///|
pub impl @pp.Pretty for DropIndexStmt with pretty(self) {
@pp.text("DROP INDEX ") +
(if self.concurrently { @pp.text("CONCURRENTLY ") } else { @pp.empty }) +
(if self.if_exists { @pp.text("IF EXISTS ") } else { @pp.empty }) +
@pp.text(self.name) +
(match self.table_name {
Some(table) => @pp.text(" ON ") + table.pretty()
None => @pp.empty
})
}
///|
pub(all) struct AlterTableStmt {
// ALTER TABLE
table_name : ObjectName
// IF EXISTS flag
if_exists : Bool
// Table alteration operation
operation : AlterTableOperation
} derive(Eq, Show)
///|
pub(all) enum AlterTableOperation {
// DROP COLUMN [IF EXISTS]
DropColumn(String, Bool) // column_name, if_exists
} derive(Eq, Show)
///|
pub impl @pp.Pretty for AlterTableStmt with pretty(self) {
@pp.text("ALTER TABLE ") +
(if self.if_exists { @pp.text("IF EXISTS ") } else { @pp.empty }) +
self.table_name.pretty() +
@pp.space +
self.operation.pretty()
}
///|
pub impl @pp.Pretty for AlterTableOperation with pretty(self) {
match self {
AlterTableOperation::DropColumn(column_name, if_exists) =>
@pp.text("DROP COLUMN ") +
(if if_exists { @pp.text("IF EXISTS ") } else { @pp.empty }) +
@pp.text(column_name)
}
}
///|
/// ALTER INDEX statement
pub(all) struct AlterIndexStmt {
// ALTER INDEX
name : String
// IF EXISTS flag
if_exists : Bool
// Index alteration operation
operation : AlterIndexOperation
} derive(Eq, Show)
///|
/// ALTER INDEX operations
pub(all) enum AlterIndexOperation {
/// RENAME TO new_name
RenameTo(String)
/// SET TABLESPACE tablespace_name
SetTablespace(String)
/// RESET (param1, param2, ...)
Reset(Array[String])
/// SET (param1 = value1, param2 = value2, ...)
Set(Array[IndexParameter])
} derive(Eq, Show)
///|
/// Index parameter for SET operations
pub(all) struct IndexParameter {
name : String
value : String
} derive(Eq, Show)
///|
pub impl @pp.Pretty for AlterIndexStmt with pretty(self) {
@pp.text("ALTER INDEX ") +
(if self.if_exists { @pp.text("IF EXISTS ") } else { @pp.empty }) +
@pp.text(self.name) +
@pp.space +
self.operation.pretty()
}
///|
pub impl @pp.Pretty for AlterIndexOperation with pretty(self) {
match self {
AlterIndexOperation::RenameTo(new_name) =>
@pp.text("RENAME TO ") + @pp.text(new_name)
AlterIndexOperation::SetTablespace(tablespace) =>
@pp.text("SET TABLESPACE ") + @pp.text(tablespace)
AlterIndexOperation::Reset(params) =>
@pp.text("RESET (") +
@pp.separate(@pp.text(", "), params.map(@pp.text)) +
@pp.text(")")
AlterIndexOperation::Set(params) =>
@pp.text("SET (") +
@pp.separate(@pp.text(", "), params.map(@pp.pretty)) +
@pp.text(")")
}
}
///|
pub impl @pp.Pretty for IndexParameter with pretty(self) {
@pp.text(self.name) + @pp.text(" = ") + @pp.text(self.value)
}
///|
/// MySQL SHOW statement
pub(all) struct ShowStmt {
// The type of SHOW statement
show_type : ShowType
// Optional object name (table, database, etc.)
object : ObjectName?
// Optional filter condition
filter : ShowFilter?
// Additional options
extended : Bool // SHOW EXTENDED ...
full : Bool // SHOW FULL ...
global_scope : Bool // GLOBAL/SESSION for STATUS/VARIABLES
} derive(Eq, Show)
///|
pub(all) enum ShowType {
Tables
Columns
Status
Databases
Schemas // synonym for Databases
Variables
Processlist
Grants
Functions
CreateTable
CreateView
CreateFunction
CreateProcedure
CreateEvent
CreateTrigger
} derive(Eq, Show)
///|
pub(all) enum ShowFilter {
Like(String)
Where(Expr)
} derive(Eq, Show)
///|
pub impl @pp.Pretty for ShowStmt with pretty(self) {
@pp.text("SHOW ") +
(if self.extended { @pp.text("EXTENDED ") } else { @pp.empty }) +
(if self.full { @pp.text("FULL ") } else { @pp.empty }) +
(if self.global_scope { @pp.text("GLOBAL ") } else { @pp.empty }) +
self.show_type.pretty() +
(match self.object {
Some(obj) =>
// For CREATE statements, object comes directly after type (no FROM)
match self.show_type {
ShowType::CreateTable
| ShowType::CreateView
| ShowType::CreateFunction
| ShowType::CreateProcedure
| ShowType::CreateEvent
| ShowType::CreateTrigger => @pp.space + obj.pretty()
_ => @pp.space + @pp.text("FROM ") + obj.pretty()
}
None => @pp.empty
}) +
(match self.filter {
Some(filter) => @pp.space + filter.pretty()
None => @pp.empty
})
}
///|
pub impl @pp.Pretty for ShowType with pretty(self) {
match self {
ShowType::Tables => @pp.text("TABLES")
ShowType::Columns => @pp.text("COLUMNS")
ShowType::Status => @pp.text("STATUS")
ShowType::Databases => @pp.text("DATABASES")
ShowType::Schemas => @pp.text("SCHEMAS")
ShowType::Variables => @pp.text("VARIABLES")
ShowType::Processlist => @pp.text("PROCESSLIST")
ShowType::Grants => @pp.text("GRANTS")
ShowType::Functions => @pp.text("FUNCTIONS")
ShowType::CreateTable => @pp.text("CREATE TABLE")
ShowType::CreateView => @pp.text("CREATE VIEW")
ShowType::CreateFunction => @pp.text("CREATE FUNCTION")
ShowType::CreateProcedure => @pp.text("CREATE PROCEDURE")
ShowType::CreateEvent => @pp.text("CREATE EVENT")
ShowType::CreateTrigger => @pp.text("CREATE TRIGGER")
}
}
///|
pub impl @pp.Pretty for ShowFilter with pretty(self) {
match self {
ShowFilter::Like(pattern) =>
@pp.text("LIKE '") + @pp.text(pattern) + @pp.text("'")
ShowFilter::Where(expr) => @pp.text("WHERE ") + expr.pretty()
}
}
///|
pub(all) struct SetStmt {
// Variable scope (GLOBAL, SESSION, or LOCAL/user variables)
scope : SetScope
// Variable assignments
assignments : Array[SetAssignment]
} derive(Eq, Show)
///|
pub(all) enum SetScope {
Global // SET GLOBAL var = value
Session // SET SESSION var = value
UserVar // SET @var = value
Local // Default scope (session for system vars)
} derive(Eq, Show)
///|
pub(all) struct SetAssignment {
// Variable name
variable : String
// Assigned value
value : Expr
} derive(Eq, Show)
///|
pub impl @pp.Pretty for SetStmt with pretty(self) {
@pp.text("SET ") +
(match self.scope {
SetScope::Global => @pp.text("GLOBAL ")
SetScope::Session => @pp.text("SESSION ")
SetScope::UserVar => @pp.empty
SetScope::Local => @pp.empty
}) +
@pp.separate(@pp.text(", "), self.assignments.map(fn(a) { a.pretty() }))
}
///|
pub impl @pp.Pretty for SetAssignment with pretty(self) {
(match self.variable {
variable if variable.strip_prefix("@") != None => @pp.text(variable)
variable => @pp.text(variable)
}) +
@pp.text(" = ") +
self.value.pretty()
}
///|
/// USE database statement for database switching
pub(all) struct UseStmt {
/// Database name to switch to
database_name : ObjectName
} derive(Eq, Show)
///|
pub impl @pp.Pretty for UseStmt with pretty(self) {
@pp.text("USE ") + self.database_name.pretty()
}
///|
/// COPY statement for bulk data import/export operations
pub(all) struct CopyStmt {
/// Source or target specification
source : CopySource
/// Direction of the operation (TO file or FROM file)
direction : CopyDirection
/// Target specification (file path or STDOUT/STDIN)
target : CopyTarget
/// Format options (CSV, TEXT, BINARY, etc.)
format_options : Array[CopyOption]
} derive(Eq, Show)
///|
/// Source specification for COPY statement
pub(all) enum CopySource {
/// Copy from a table: COPY table_name
Table(ObjectName, Array[String]?) // table_name, optional column_list
/// Copy from a query: COPY (SELECT ...)
Query(QueryStmt)
} derive(Eq, Show)
///|
/// Direction of COPY operation
pub(all) enum CopyDirection {
/// COPY ... TO target
To
/// COPY ... FROM target
From
} derive(Eq, Show)
///|
/// Target specification for COPY statement
pub(all) enum CopyTarget {
/// File path: COPY ... TO/FROM 'filename'
File(String)
/// Standard input: COPY ... FROM STDIN
Stdin
/// Standard output: COPY ... TO STDOUT
Stdout
/// Program: COPY ... TO/FROM PROGRAM 'command'
Program(String)
} derive(Eq, Show)
///|
/// COPY statement options
pub(all) enum CopyOption {
/// FORMAT format_name (CSV, TEXT, BINARY)
Format(CopyFormat)
/// DELIMITER 'delimiter_string'
Delimiter(String)
/// NULL 'null_string'
Null(String)
/// HEADER [boolean]
Header(Bool?)
/// QUOTE 'quote_character'
Quote(String)
/// ESCAPE 'escape_character'
Escape(String)
/// FORCE_QUOTE (column_list) or FORCE_QUOTE *
ForceQuote(CopyForceQuote)
/// FORCE_NOT_NULL (column_list)
ForceNotNull(Array[String])
/// FORCE_NULL (column_list)
ForceNull(Array[String])
/// ENCODING 'encoding_name'
Encoding(String)
} derive(Eq, Show)
///|
/// COPY format specification
pub(all) enum CopyFormat {
Csv
Text
Binary
} derive(Eq, Show)
///|
/// FORCE_QUOTE specification
pub(all) enum CopyForceQuote {
/// FORCE_QUOTE *
All
/// FORCE_QUOTE (column_list)
Columns(Array[String])
} derive(Eq, Show)
///|
pub impl @pp.Pretty for CopyStmt with pretty(self) {
@pp.text("COPY ") +
self.source.pretty() +
@pp.space +
self.direction.pretty() +
@pp.space +
self.target.pretty() +
(if self.format_options.is_empty() {
@pp.empty
} else {
@pp.space +
@pp.text("(") +
@pp.separate(@pp.text(", "), self.format_options.map(@pp.pretty)) +
@pp.text(")")
})
}
///|
pub impl @pp.Pretty for CopySource with pretty(self) {
match self {
CopySource::Table(table_name, columns) =>
table_name.pretty() +
(match columns {
Some(col_list) if col_list.length() > 0 =>
@pp.space +
@pp.parens(@pp.separate(@pp.text(", "), col_list.map(@pp.text)))
_ => @pp.empty
})
CopySource::Query(query) =>
@pp.parens(@pp.nest(@pp.hardline + query.pretty()) + @pp.hardline)
}
}
///|
pub impl @pp.Pretty for CopyDirection with pretty(self) {
match self {
CopyDirection::To => @pp.text("TO")
CopyDirection::From => @pp.text("FROM")
}
}
///|
pub impl @pp.Pretty for CopyTarget with pretty(self) {
match self {
CopyTarget::File(filename) =>
@pp.text("'") + @pp.text(filename) + @pp.text("'")
CopyTarget::Stdin => @pp.text("STDIN")
CopyTarget::Stdout => @pp.text("STDOUT")
CopyTarget::Program(command) =>
@pp.text("PROGRAM '") + @pp.text(command) + @pp.text("'")
}
}
///|
pub impl @pp.Pretty for CopyOption with pretty(self) {
match self {
CopyOption::Format(format) => @pp.text("FORMAT ") + format.pretty()
CopyOption::Delimiter(delim) =>
@pp.text("DELIMITER '") + @pp.text(delim) + @pp.text("'")
CopyOption::Null(null_str) =>
@pp.text("NULL '") + @pp.text(null_str) + @pp.text("'")
CopyOption::Header(Some(true)) => @pp.text("HEADER true")
CopyOption::Header(Some(false)) => @pp.text("HEADER false")
CopyOption::Header(None) => @pp.text("HEADER")
CopyOption::Quote(quote_char) =>
@pp.text("QUOTE '") + @pp.text(quote_char) + @pp.text("'")
CopyOption::Escape(escape_char) =>
@pp.text("ESCAPE '") + @pp.text(escape_char) + @pp.text("'")
CopyOption::ForceQuote(force_quote) =>
@pp.text("FORCE_QUOTE ") + force_quote.pretty()
CopyOption::ForceNotNull(columns) =>
@pp.text("FORCE_NOT_NULL (") +
@pp.separate(@pp.text(", "), columns.map(@pp.text)) +
@pp.text(")")
CopyOption::ForceNull(columns) =>
@pp.text("FORCE_NULL (") +
@pp.separate(@pp.text(", "), columns.map(@pp.text)) +
@pp.text(")")
CopyOption::Encoding(encoding) =>
@pp.text("ENCODING '") + @pp.text(encoding) + @pp.text("'")
}
}
///|
pub impl @pp.Pretty for CopyFormat with pretty(self) {
match self {
CopyFormat::Csv => @pp.text("CSV")
CopyFormat::Text => @pp.text("TEXT")
CopyFormat::Binary => @pp.text("BINARY")
}
}
///|
pub impl @pp.Pretty for CopyForceQuote with pretty(self) {
match self {
CopyForceQuote::All => @pp.text("*")
CopyForceQuote::Columns(columns) =>
@pp.parens(@pp.separate(@pp.text(", "), columns.map(@pp.text)))
}
}
///|
/// MySQL ON DUPLICATE KEY UPDATE or PostgreSQL ON CONFLICT
pub(all) enum OnInsert {
/// ON DUPLICATE KEY UPDATE (MySQL when the key already exists, then execute an update instead)
DuplicateKeyUpdate(Array[Assignment])
/// ON CONFLICT clause (PostgreSQL and SQLite extension)
OnConflict(OnConflictClause)
} derive(Eq, Show)
///|
pub impl @pp.Pretty for OnInsert with pretty(self) {
match self {
OnInsert::DuplicateKeyUpdate(assignments) =>
@pp.text("ON DUPLICATE KEY UPDATE ") +
@pp.separate(@pp.text(", "), assignments.map(@pp.pretty))
OnInsert::OnConflict(conflict_clause) =>
@pp.text("ON CONFLICT ") + conflict_clause.pretty()
}
}
///|
/// PostgreSQL ON CONFLICT clause for advanced conflict resolution
/// Syntax: ON CONFLICT [ conflict_target ] conflict_action
pub(all) struct OnConflictClause {
/// Optional conflict target (column names, index expression, or constraint name)
conflict_target : ConflictTarget?
/// Action to take when conflict occurs
conflict_action : ConflictAction
} derive(Eq, Show)
///|
/// Conflict target specification
pub(all) enum ConflictTarget {
/// ON CONFLICT (column1, column2, ...)
Columns(Array[String])
/// ON CONFLICT ON CONSTRAINT constraint_name
OnConstraint(String)
/// ON CONFLICT (expression) WHERE condition
OnExpression(Expr, Expr?) // expression, optional WHERE condition
} derive(Eq, Show)
///|
/// Action to take when conflict occurs
pub(all) enum ConflictAction {
/// DO NOTHING
DoNothing
/// DO UPDATE SET assignments [WHERE condition]
DoUpdate(Array[Assignment], Expr?) // assignments, optional WHERE condition
} derive(Eq, Show)
///|
pub impl @pp.Pretty for OnConflictClause with pretty(self) {
(match self.conflict_target {
Some(target) => target.pretty() + @pp.space
None => @pp.empty
}) +
self.conflict_action.pretty()
}
///|
pub impl @pp.Pretty for ConflictTarget with pretty(self) {
match self {
ConflictTarget::Columns(columns) =>
@pp.parens(@pp.separate(@pp.text(", "), columns.map(@pp.text)))
ConflictTarget::OnConstraint(constraint_name) =>
@pp.text("ON CONSTRAINT ") + @pp.text(constraint_name)
ConflictTarget::OnExpression(expr, where_condition) =>
@pp.parens(expr.pretty()) +
(match where_condition {
Some(condition) => @pp.text(" WHERE ") + condition.pretty()
None => @pp.empty
})
}
}
///|
pub impl @pp.Pretty for ConflictAction with pretty(self) {
match self {
ConflictAction::DoNothing => @pp.text("DO NOTHING")
ConflictAction::DoUpdate(assignments, where_condition) =>
@pp.text("DO UPDATE SET ") +
@pp.separate(@pp.text(", "), assignments.map(@pp.pretty)) +
(match where_condition {
Some(condition) => @pp.text(" WHERE ") + condition.pretty()
None => @pp.empty
})
}
}
///|
/// SQLite-style conflict resolution for INSERT statements
/// See: https://sqlite.org/lang_conflict.html
pub(all) enum SqliteOnConflict {
Rollback
Abort
Fail
Ignore
Replace
} derive(Eq, Show)
///|
pub impl @pp.Pretty for SqliteOnConflict with pretty(self) {
match self {
SqliteOnConflict::Rollback => @pp.text("OR ROLLBACK")
SqliteOnConflict::Abort => @pp.text("OR ABORT")
SqliteOnConflict::Fail => @pp.text("OR FAIL")
SqliteOnConflict::Ignore => @pp.text("OR IGNORE")
SqliteOnConflict::Replace => @pp.text("OR REPLACE")
}
}
///|
// TCL (Transaction Control Language) statement structures
pub(all) struct BeginStmt {
// BEGIN [WORK] [TRANSACTION] or START TRANSACTION
work : Bool // whether WORK keyword was used
transaction : Bool // whether TRANSACTION keyword was used
} derive(Eq, Show)
///|
pub impl @pp.Pretty for BeginStmt with pretty(self) {
@pp.text("BEGIN") +
(if self.work { @pp.text(" WORK") } else { @pp.empty }) +
(if self.transaction { @pp.text(" TRANSACTION") } else { @pp.empty })
}
///|
pub(all) struct CommitStmt {
// COMMIT [WORK] [TRANSACTION]
work : Bool // whether WORK keyword was used
transaction : Bool // whether TRANSACTION keyword was used
} derive(Eq, Show)
///|
pub impl @pp.Pretty for CommitStmt with pretty(self) {
@pp.text("COMMIT") +
(if self.work { @pp.text(" WORK") } else { @pp.empty }) +
(if self.transaction { @pp.text(" TRANSACTION") } else { @pp.empty })
}
///|
pub(all) struct RollbackStmt {
// ROLLBACK [WORK] [TRANSACTION] [TO [SAVEPOINT] savepoint_name]
work : Bool // whether WORK keyword was used
transaction : Bool // whether TRANSACTION keyword was used
savepoint : String? // optional savepoint name to rollback to
} derive(Eq, Show)
///|
pub impl @pp.Pretty for RollbackStmt with pretty(self) {
@pp.text("ROLLBACK") +
(if self.work { @pp.text(" WORK") } else { @pp.empty }) +
(if self.transaction { @pp.text(" TRANSACTION") } else { @pp.empty }) +
(match self.savepoint {
Some(name) => @pp.text(" TO SAVEPOINT ") + @pp.text(name)
None => @pp.empty
})
}
///|
pub(all) struct SavepointStmt {
// SAVEPOINT savepoint_name
name : String
} derive(Eq, Show)
///|
pub impl @pp.Pretty for SavepointStmt with pretty(self) {
@pp.text("SAVEPOINT ") + @pp.text(self.name)
}
///|
pub(all) struct ReleaseSavepointStmt {
// RELEASE [SAVEPOINT] savepoint_name
savepoint_keyword : Bool // whether SAVEPOINT keyword was used
name : String
} derive(Eq, Show)
///|
pub impl @pp.Pretty for ReleaseSavepointStmt with pretty(self) {
@pp.text("RELEASE ") +
(if self.savepoint_keyword { @pp.text("SAVEPOINT ") } else { @pp.empty }) +
@pp.text(self.name)
}
///|
// DCL (Data Control Language) statement structures
pub(all) struct GrantStmt {
// GRANT privileges ON object TO grantees [WITH GRANT OPTION]
privileges : Array[Privilege]
objects : Array[ObjectName] // tables, views, etc.
grantees : Array[String] // users or roles
with_grant_option : Bool
} derive(Eq, Show)
///|
pub(all) enum Privilege {
// Basic privileges
Select(Array[String]?) // SELECT [(column_list)]
Insert(Array[String]?) // INSERT [(column_list)]
Update(Array[String]?) // UPDATE [(column_list)]
Delete // DELETE
References(Array[String]?) // REFERENCES [(column_list)]
// DDL privileges
Create // CREATE
Drop // DROP
Alter // ALTER
Index // INDEX/CREATE INDEX
// Special privileges
All // ALL [PRIVILEGES]
Usage // USAGE (for sequences, schemas)
Execute // EXECUTE (for functions, procedures)
Connect // CONNECT (for databases)
Temporary // TEMPORARY/TEMP (for databases)
} derive(Eq, Show)
///|
pub impl @pp.Pretty for GrantStmt with pretty(self) {
@pp.text("GRANT ") +
@pp.separate(@pp.text(", "), self.privileges.map(@pp.pretty)) +
@pp.text(" ON ") +
@pp.separate(@pp.text(", "), self.objects.map(@pp.pretty)) +
@pp.text(" TO ") +
@pp.separate(@pp.text(", "), self.grantees.map(@pp.text)) +
(if self.with_grant_option {
@pp.text(" WITH GRANT OPTION")
} else {
@pp.empty
})
}
///|
pub impl @pp.Pretty for Privilege with pretty(self) {
match self {
Privilege::Select(cols) =>
@pp.text("SELECT") +
(match cols {
Some(column_names) if column_names.length() > 0 =>
@pp.parens(@pp.separate(@pp.text(", "), column_names.map(@pp.text)))
_ => @pp.empty
})
Privilege::Insert(cols) =>
@pp.text("INSERT") +
(match cols {
Some(column_names) if column_names.length() > 0 =>
@pp.parens(@pp.separate(@pp.text(", "), column_names.map(@pp.text)))
_ => @pp.empty
})
Privilege::Update(cols) =>
@pp.text("UPDATE") +
(match cols {
Some(column_names) if column_names.length() > 0 =>
@pp.parens(@pp.separate(@pp.text(", "), column_names.map(@pp.text)))
_ => @pp.empty
})
Privilege::Delete => @pp.text("DELETE")
Privilege::References(cols) =>
@pp.text("REFERENCES") +
(match cols {
Some(column_names) if column_names.length() > 0 =>
@pp.parens(@pp.separate(@pp.text(", "), column_names.map(@pp.text)))
_ => @pp.empty
})
Privilege::Create => @pp.text("CREATE")
Privilege::Drop => @pp.text("DROP")
Privilege::Alter => @pp.text("ALTER")
Privilege::Index => @pp.text("INDEX")
Privilege::All => @pp.text("ALL PRIVILEGES")
Privilege::Usage => @pp.text("USAGE")
Privilege::Execute => @pp.text("EXECUTE")
Privilege::Connect => @pp.text("CONNECT")
Privilege::Temporary => @pp.text("TEMPORARY")
}
}
///|
pub(all) struct RevokeStmt {
// REVOKE [GRANT OPTION FOR] privileges ON object FROM grantees [RESTRICT | CASCADE]
grant_option_for : Bool // whether GRANT OPTION FOR was specified
privileges : Array[Privilege]
objects : Array[ObjectName] // tables, views, etc.
grantees : Array[String] // users or roles
cascade : RevokeOption? // RESTRICT | CASCADE
} derive(Eq, Show)
///|
pub(all) enum RevokeOption {
Restrict // RESTRICT
Cascade // CASCADE
} derive(Eq, Show)
///|
pub impl @pp.Pretty for RevokeStmt with pretty(self) {
@pp.text("REVOKE ") +
(if self.grant_option_for {
@pp.text("GRANT OPTION FOR ")
} else {
@pp.empty
}) +
@pp.separate(@pp.text(", "), self.privileges.map(@pp.pretty)) +
@pp.text(" ON ") +
@pp.separate(@pp.text(", "), self.objects.map(@pp.pretty)) +
@pp.text(" FROM ") +
@pp.separate(@pp.text(", "), self.grantees.map(@pp.text)) +
(match self.cascade {
Some(RevokeOption::Restrict) => @pp.text(" RESTRICT")
Some(RevokeOption::Cascade) => @pp.text(" CASCADE")
None => @pp.empty
})
}
///|
pub impl @pp.Pretty for RevokeOption with pretty(self) {
match self {
RevokeOption::Restrict => @pp.text("RESTRICT")
RevokeOption::Cascade => @pp.text("CASCADE")
}
}
///|
/// LOAD DATA statement for MySQL-style bulk data loading
pub(all) struct LoadDataStmt {
/// LOAD DATA [LOCAL] INFILE 'filename'
is_local : Bool // LOCAL keyword
filename : String // INFILE filename
/// [REPLACE | IGNORE]
duplicate_handling : LoadDataDuplicateHandling?
/// INTO TABLE table_name
table_name : ObjectName
/// [CHARACTER SET charset_name]
character_set : String?
/// [FIELDS [TERMINATED BY 'string'] [ENCLOSED BY 'char'] [ESCAPED BY 'char']]
fields_options : LoadDataFieldsOptions?
/// [LINES [STARTING BY 'string'] [TERMINATED BY 'string']]
lines_options : LoadDataLinesOptions?
/// [IGNORE number LINES]
ignore_lines : Int?
/// [(column_name_or_user_var, ...)]
columns : Array[String]?
/// [SET column_name = expr, ...]
set_assignments : Array[Assignment]?
} derive(Eq, Show)
///|
/// Duplicate handling for LOAD DATA
pub(all) enum LoadDataDuplicateHandling {
Replace // REPLACE
Ignore // IGNORE
} derive(Eq, Show)
///|
/// FIELDS options for LOAD DATA
pub(all) struct LoadDataFieldsOptions {
/// TERMINATED BY 'string'
terminated_by : String?
/// [OPTIONALLY] ENCLOSED BY 'char'
enclosed_by : String?
optionally_enclosed : Bool
/// ESCAPED BY 'char'
escaped_by : String?
} derive(Eq, Show)
///|
/// LINES options for LOAD DATA
pub(all) struct LoadDataLinesOptions {
/// STARTING BY 'string'
starting_by : String?
/// TERMINATED BY 'string'
terminated_by : String?
} derive(Eq, Show)
///|
pub impl @pp.Pretty for LoadDataStmt with pretty(self) {
@pp.text("LOAD DATA ") +
(if self.is_local { @pp.text("LOCAL ") } else { @pp.empty }) +
@pp.text("INFILE '") +
@pp.text(self.filename) +
@pp.text("'") +
@pp.nest(
@pp.hardline +
(match self.duplicate_handling {
Some(handling) => handling.pretty() + @pp.space
None => @pp.empty
}) +
@pp.text("INTO TABLE ") +
self.table_name.pretty() +
(match self.character_set {
Some(charset) =>
@pp.hardline + @pp.text("CHARACTER SET ") + @pp.text(charset)
None => @pp.empty
}) +
(match self.fields_options {
Some(fields) => @pp.hardline + fields.pretty()
None => @pp.empty
}) +
(match self.lines_options {
Some(lines) => @pp.hardline + lines.pretty()
None => @pp.empty
}) +
(match self.ignore_lines {
Some(num) =>
@pp.hardline +
@pp.text("IGNORE ") +
@pp.text(num.to_string()) +
@pp.text(" LINES")
None => @pp.empty
}) +
(match self.columns {
Some(cols) if cols.length() > 0 =>
@pp.hardline +
@pp.parens(@pp.separate(@pp.text(", "), cols.map(@pp.text)))
_ => @pp.empty
}) +
(match self.set_assignments {
Some(assignments) if assignments.length() > 0 =>
@pp.hardline +
@pp.text("SET ") +
@pp.separate(@pp.text(", "), assignments.map(@pp.pretty))
_ => @pp.empty
}),
)
}
///|
pub impl @pp.Pretty for LoadDataDuplicateHandling with pretty(self) {
match self {
LoadDataDuplicateHandling::Replace => @pp.text("REPLACE")
LoadDataDuplicateHandling::Ignore => @pp.text("IGNORE")
}
}
///|
pub impl @pp.Pretty for LoadDataFieldsOptions with pretty(self) {
let parts = []
match self.terminated_by {
Some(term) =>
parts.push(@pp.text("TERMINATED BY '") + @pp.text(term) + @pp.text("'"))
None => ()
}
match self.enclosed_by {
Some(enc) => {
let prefix = if self.optionally_enclosed {
"OPTIONALLY ENCLOSED BY '"
} else {
"ENCLOSED BY '"
}
parts.push(@pp.text(prefix) + @pp.text(enc) + @pp.text("'"))
}
None => ()
}
match self.escaped_by {
Some(esc) =>
parts.push(@pp.text("ESCAPED BY '") + @pp.text(esc) + @pp.text("'"))
None => ()
}
if parts.length() > 0 {
@pp.text("FIELDS ") + @pp.separate(@pp.space, parts)
} else {
@pp.empty
}
}
///|
pub impl @pp.Pretty for LoadDataLinesOptions with pretty(self) {
let parts = []
match self.starting_by {
Some(start) =>
parts.push(@pp.text("STARTING BY '") + @pp.text(start) + @pp.text("'"))
None => ()
}
match self.terminated_by {
Some(term) =>
parts.push(@pp.text("TERMINATED BY '") + @pp.text(term) + @pp.text("'"))
None => ()
}
if parts.length() > 0 {
@pp.text("LINES ") + @pp.separate(@pp.space, parts)
} else {
@pp.empty
}
}
///|
/// PREPARE statement
/// Syntax: PREPARE name [(data_type [, ...])] AS statement
pub(all) struct PrepareStmt {
/// Name of the prepared statement
name : String
/// Optional data types for parameters
data_types : Array[DataType]
/// The SQL statement to prepare
statement : Statement
} derive(Eq, Show)
///|
pub impl @pp.Pretty for PrepareStmt with pretty(self) {
@pp.text("PREPARE ") +
@pp.text(self.name) +
(if self.data_types.length() > 0 {
@pp.text(" (") +
@pp.separate(@pp.text(", "), self.data_types.map(@pp.pretty)) +
@pp.text(")")
} else {
@pp.empty
}) +
@pp.text(" AS ") +
(match self.statement {
Statement::Query(stmt) => stmt.pretty()
CreateTable(stmt) => stmt.pretty()
CreateView(stmt) => stmt.pretty()
CreateIndex(stmt) => stmt.pretty()
CreateDatabase(stmt) => stmt.pretty()
CreateSchema(stmt) => stmt.pretty()
CreateFunction(stmt) => stmt.pretty()
CreateProcedure(stmt) => stmt.pretty()
CreateSequence(stmt) => stmt.pretty()
DropView(stmt) => stmt.pretty()
DropTable(stmt) => stmt.pretty()
DropIndex(stmt) => stmt.pretty()
Insert(stmt) => stmt.pretty()
Delete(stmt) => stmt.pretty()
Update(stmt) => stmt.pretty()
Merge(stmt) => stmt.pretty()
Truncate(stmt) => stmt.pretty()
AlterTable(stmt) => stmt.pretty()
AlterIndex(stmt) => stmt.pretty()
Show(stmt) => stmt.pretty()
Set(stmt) => stmt.pretty()
Use(stmt) => stmt.pretty()
Copy(stmt) => stmt.pretty()
LoadData(stmt) => stmt.pretty()
Prepare(stmt) => stmt.pretty()
Execute(stmt) => stmt.pretty()
Deallocate(stmt) => stmt.pretty()
LockTables(tables) =>
@pp.text("LOCK TABLES ") +
@pp.separate(@pp.text(", "), tables.map(@pp.pretty))
UnlockTables => @pp.text("UNLOCK TABLES")
Listen(channel) => @pp.text("LISTEN ") + @pp.text(channel)
Notify(channel, payload) =>
@pp.text("NOTIFY ") +
@pp.text(channel) +
(match payload {
Some(msg) => @pp.text(", '") + @pp.text(msg) + @pp.text("'")
None => @pp.empty
})
Begin(stmt) => stmt.pretty()
Commit(stmt) => stmt.pretty()
Rollback(stmt) => stmt.pretty()
Savepoint(stmt) => stmt.pretty()
ReleaseSavepoint(stmt) => stmt.pretty()
Grant(stmt) => stmt.pretty()
Revoke(stmt) => stmt.pretty()
})
}
///|
/// EXECUTE statement
/// Syntax:
/// EXECUTE name [(param [, ...])]
/// EXECUTE name USING expr [, ...]
/// EXECUTE IMMEDIATE 'sql_string' [INTO var [, ...]] [USING expr [, ...]]
pub(all) struct ExecuteStmt {
/// Statement name (None for EXECUTE IMMEDIATE)
name : ObjectName?
/// Parameters for EXECUTE name(params...)
parameters : Array[Expr]
/// Whether parameters are in parentheses
has_parentheses : Bool
/// INTO clause variables (Oracle style)
into : Array[String]
/// USING clause expressions (PostgreSQL style)
using_exprs : Array[Expr]
} derive(Eq, Show)
///|
pub impl @pp.Pretty for ExecuteStmt with pretty(self) {
@pp.text("EXECUTE ") +
(match self.name {
Some(name) =>
name.pretty() +
(if self.has_parentheses && self.parameters.length() > 0 {
@pp.text("(") +
@pp.separate(@pp.text(", "), self.parameters.map(@pp.pretty)) +
@pp.text(")")
} else if self.parameters.length() > 0 {
@pp.space +
@pp.separate(@pp.text(", "), self.parameters.map(@pp.pretty))
} else {
@pp.empty
})
None => @pp.text("IMMEDIATE")
}) +
(if self.into.length() > 0 {
@pp.text(" INTO ") + @pp.separate(@pp.text(", "), self.into.map(@pp.text))
} else {
@pp.empty
}) +
(if self.using_exprs.length() > 0 {
@pp.text(" USING ") +
@pp.separate(@pp.text(", "), self.using_exprs.map(@pp.pretty))
} else {
@pp.empty
})
}
///|
/// DEALLOCATE statement
/// Syntax: DEALLOCATE [PREPARE] name
pub(all) struct DeallocateStmt {
/// Name of the prepared statement to deallocate
name : String
/// Whether PREPARE keyword is present
prepare : Bool
} derive(Eq, Show)
///|
pub impl @pp.Pretty for DeallocateStmt with pretty(self) {
@pp.text("DEALLOCATE ") +
(if self.prepare { @pp.text("PREPARE ") } else { @pp.empty }) +
@pp.text(self.name)
}