///|
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 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 enum Statement {
  Query(QueryStmt)
  CreateTable(CreateTableStmt)
  CreateView(CreateViewStmt)
  DropView(DropViewStmt)
  DropTable(DropTableStmt)
  Insert(InsertStmt)
  Delete(DeleteStmt)
  Update(UpdateStmt)
  Truncate(TruncateStmt)
  AlterTable(AlterTableStmt)
  Show(ShowStmt)
  LockTables(Array[ObjectName])
  UnlockTables
  // PostgreSQL-specific statements
  Listen(String)
  Notify(String, String?)
} 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()
    DropView(stmt) => stmt.pretty()
    DropTable(stmt) => stmt.pretty()
    Insert(stmt) => stmt.pretty()
    Delete(stmt) => stmt.pretty()
    Update(stmt) => stmt.pretty()
    Truncate(stmt) => stmt.pretty()
    AlterTable(stmt) => stmt.pretty()
    Show(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
      })
  }) +
  @pp.text(";")
}

///|
pub struct QueryStmt {
  // 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) {
  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 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 enum Expr {
  // 
  Identifier(String)
  // .
  CompoundIdentifier(Array[String])
  // string, integer, double, boolean, null
  Literal(Literal)
  //  + 
  BinaryOperation(Expr, BinaryOperator, Expr)
  // - 
  UnaryOperation(UnaryOperator, Expr)
  // sum(*)
  FunctionCall(String, DuplicateTreatment?, Array[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
} 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) =>
      @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)),
      )
    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 => @pp.text("?")
  }
}

///|
pub 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")
  }
}

///|
pub enum BinaryOperator {
  Eq
  Neq
  Lt
  Gt
  LtEq
  GtEq
  Spaceship
  Plus
  Minus
  Mul
  Div
  IntegerDiv  // MySQL DIV keyword for integer division
  Mod
  And
  Or
} 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")
  }
}

///|
pub 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 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 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 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 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 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 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 enum JoinConstraint {
  // ON 
  On(Expr)
  // USING (, ...)
  Using(Array[String])
  None
} 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::None => @pp.empty
  }
}

///|
pub 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 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 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")
}

///|
pub 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 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 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 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 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 struct ViewColumnDef {
  name : String
} derive(Eq, Show)

///|
pub impl @pp.Pretty for ViewColumnDef with pretty(self) {
  @pp.text(self.name)
}

///|
pub 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 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 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 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 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 struct InsertStmt {
  // INSERT INTO 
  table_name : ObjectName
  // Optional column list: (, , ...)
  columns : Array[String]
  // The inserted data source
  source : InsertSource
} derive(Eq, Show)

///|
pub impl @pp.Pretty for InsertStmt with pretty(self) {
  @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()
}

///|
pub 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 struct DeleteStmt {
  /// DELETE FROM 
  table_name : ObjectName
  /// Optional WHERE clause
  where_clause : Option[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 struct UpdateStmt {
  // UPDATE 
  table_name : ObjectName
  // SET assignments
  assignments : Array[Assignment]
  // Optional WHERE clause
  where_clause : Option[Expr]
} derive(Eq, Show)

///|
pub 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()
}

///|
pub 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 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 struct AlterTableStmt {
  // ALTER TABLE 
  table_name : ObjectName
  // IF EXISTS flag
  if_exists : Bool
  // Table alteration operation
  operation : AlterTableOperation
} derive(Eq, Show)

///|
pub 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)
  }
}

///|
/// MySQL SHOW statement
pub struct ShowStmt {
  // The type of SHOW statement
  show_type : ShowType
  // Optional object name (table, database, etc.)
  object : Option[ObjectName]
  // Optional filter condition
  filter : Option[ShowFilter]
} derive(Eq, Show)

///|
pub enum ShowType {
  Tables
  Columns
  Status
  Databases
} derive(Eq, Show)

///|
pub enum ShowFilter {
  Like(String)
  Where(Expr)
} derive(Eq, Show)

///|
pub impl @pp.Pretty for ShowStmt with pretty(self) {
  @pp.text("SHOW ") +
  self.show_type.pretty() +
  (match self.object {
    Some(obj) => @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")
  }
}

///|
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()
  }
}