// Expression builders (port of parts of sqlglot/expressions/core.py and builders.py).
///|
/// `exp.column(name)` for a simple (possibly quoted) column name.
pub fn column_of(name : String, table? : String, quoted? : Bool) -> Expr {
let col = mk1(Column, to_identifier(name, quoted?))
match table {
Some(t) => col.set("table", to_identifier(t, quoted?))
None => ()
}
col
}
///|
/// `exp.column(col, table, db, catalog, fields=...)` with identifier expressions.
pub fn column_from_parts(
col : Expr,
table? : Expr?,
db? : Expr?,
catalog? : Expr?,
fields? : Array[Expr] = [],
) -> Expr {
let c = mk(Column, [
("this", col),
(
"table",
match table {
Some(t) => t
None => None
},
),
(
"db",
match db {
Some(t) => t
None => None
},
),
(
"catalog",
match catalog {
Some(t) => t
None => None
},
),
])
if fields.is_empty() {
c
} else {
let mut this = c
for f in fields {
this = mk(Dot, [("this", this), ("expression", f)])
}
this
}
}
///|
/// Copies an identifier expression (Python `to_identifier(identifier)`).
pub fn to_identifier_expr(e : Expr) -> Expr {
e.copy()
}
///|
/// `Dot.build(expressions)`
pub fn dot_build(expressions : Array[Expr]) -> Expr {
let mut this = expressions[0]
for i in 1.. Expr {
let parts = self.parts()
let mut parent = self.parent
if include_dots {
while parent is Some(p) && p.kind == Dot {
match p.expression() {
Some(e) => parts.push(e)
None => ()
}
parent = p.parent
}
}
if parts.length() > 1 {
dot_build(parts.map(p => p.copy()))
} else if parts.length() == 1 {
parts[0]
} else {
self
}
}
///|
/// `Table.to_column()`
pub fn Expr::to_column(self : Expr, copy? : Bool = true) -> Expr {
let parts = self.parts()
let last_part = parts[parts.length() - 1]
let mut col = if last_part.kind == Identifier {
let first4 = parts[0:min_int(4, parts.length())].to_array()
first4.rev_in_place()
let fields = parts[min_int(4, parts.length()):].to_array()
let m = fn(e : Expr) { if copy { e.copy() } else { e } }
column_from_parts(
m(first4[0]),
table=first4.get(1).map(m),
db=first4.get(2).map(m),
catalog=first4.get(3).map(m),
fields=fields.map(m),
)
} else {
last_part
}
match self.arg("alias") {
Some(alias) => col = alias_expr(col, alias.this(), copy~)
None => ()
}
col
}
///|
/// `exp.alias_(expression, alias)` with an identifier alias expression.
pub fn alias_expr(e : Expr, alias : Expr?, copy? : Bool = true) -> Expr {
let e = maybe_copy(e, copy)
let alias = match alias {
Some(a) => Some(if copy { a.copy() } else { a })
None => None
}
if e.kind.has_arg("alias") && e.kind != Window {
e.set("alias", alias)
return e
}
mk(Alias, [("this", e), ("alias", alias)])
}
///|
/// `exp.alias_(expression, name)` with a string alias.
pub fn alias_(
e : Expr,
name : String,
quoted? : Bool,
copy? : Bool = true,
) -> Expr {
alias_expr(e, Some(to_identifier(name, quoted?)), copy~)
}
///|
/// `exp.alias_(expression, name, table=True)`
pub fn alias_table(
e : Expr,
name : String,
columns? : Array[String] = [],
copy? : Bool = true,
) -> Expr {
let e = maybe_copy(e, copy)
let table_alias = mk1(TableAlias, to_identifier(name))
e.set("alias", table_alias)
for c in columns {
table_alias.append("columns", to_identifier(c))
}
e
}
///|
/// `Query.subquery(alias, copy)`
pub fn Expr::subquery(
self : Expr,
alias? : String,
copy? : Bool = true,
) -> Expr {
let instance = maybe_copy(self, copy)
let alias = match alias {
Some(a) if !a.is_empty() => Some(mk1(TableAlias, to_identifier(a)))
_ => None
}
mk(Subquery, [("this", instance), ("alias", alias)])
}
///|
/// `Func.from_arg_list(args)`
pub fn from_arg_list(kind : Kind, args : Array[Expr]) -> Expr {
let arg_types = kind.arg_types()
let m : Map[String, Value] = Map([])
if kind.is_var_len_args() {
let var_key = kind.var_len_arg_key()
let mut var_len_index = arg_types.length()
for i, kv in arg_types {
if kv.0 == var_key {
var_len_index = i
break
}
}
for i, a in args {
if i < var_len_index {
m[arg_types[i].0] = Node(a)
}
}
let rest = []
for i in var_len_index.. FuncBuilder {
fn(args, _p) { from_arg_list(kind, args) }
}
///|
/// Combines conditions with a connector (And / Or / Xor), wrapping operands that are
/// themselves connectors in parentheses (Python `_combine`). As in Python, the
/// conditions are copied unless `copy` is false.
pub fn combine_conditions(
conditions : Array[Expr],
connector : Kind,
copy? : Bool = true,
wrap? : Bool = true,
) -> Expr {
let wrap_ = fn(e : Expr) {
if wrap && e.kind.is_a(Connector) {
mk1(Paren, e)
} else {
e
}
}
let conditions = if copy { conditions.map(c => c.copy()) } else { conditions }
let mut this = conditions[0]
if conditions.length() > 1 {
this = wrap_(this)
}
for i in 1.. Expr {
combine_conditions(conditions, And, copy~, wrap~)
}
///|
/// `exp.or_(...)` for expressions (copied unless `copy` is false).
pub fn or_(
conditions : Array[Expr],
copy? : Bool = true,
wrap? : Bool = true,
) -> Expr {
combine_conditions(conditions, Or, copy~, wrap~)
}
///|
/// `exp.not_(expression)` (copied unless `copy` is false).
pub fn not_(e : Expr, copy? : Bool = true) -> Expr {
let e = maybe_copy(e, copy)
mk1(Not, if e.kind.is_a(Connector) { mk1(Paren, e) } else { e })
}
///|
/// `exp.paren(expression)` (copied unless `copy` is false).
pub fn paren(e : Expr, copy? : Bool = true) -> Expr {
mk1(Paren, maybe_copy(e, copy))
}
///|
/// `exp.apply_index_offset`
pub fn apply_index_offset(
this : Expr,
expressions : Array[Expr],
offset : Int,
dialect : Dialect,
) -> Array[Expr] {
// Same logic as the generator's version (falls back to the minimal type annotation
// when the optimizer isn't available), so that parse/generate offsets round-trip.
gen_apply_index_offset(this, expressions, offset, dialect) catch {
_ => expressions
}
}
///|
/// `normalize_identifiers(expression, dialect)` (sqlglot.optimizer.normalize_identifiers).
pub fn normalize_identifiers(
expression : Expr,
dialect : Dialect,
store_original_column_identifiers? : Bool = false,
) -> Expr {
let it = expression.walk(prune=n => n.meta_bool("case_sensitive"))
while it.next() is Some(node) {
if node.meta_bool("case_sensitive") {
continue
}
if store_original_column_identifiers &&
node.kind.is_any([Column, Dot]) &&
!(match node.parent {
Some(p) => p.kind == Dot
None => false
}) {
if node.kind.is_a(Column) {
node.get_meta()["dot_parts"] = List(
node.parts().map(p => Str(p.name())),
)
} else if !node.is_star() {
let mut root = node
let dot_parts = []
while root.kind == Dot {
dot_parts.push(
match root.expression() {
Some(e) => e.name()
None => ""
},
)
match root.this() {
Some(t) => root = t
None => break
}
}
dot_parts.rev_in_place()
let parts = if root.kind.is_a(Column) {
root.parts().map(p => p.name()) + dot_parts
} else {
dot_parts
}
root.get_meta()["dot_parts"] = List(parts.map(p => Str(p)))
}
}
if node.kind == Identifier {
dialect.normalize_identifier(node) |> ignore
}
}
expression
}
///|
/// `exp.parse_identifier(name, dialect)`
pub fn parse_identifier(name : String, dialect? : Dialect) -> Expr {
if is_safe_identifier(name) {
return mk(Identifier, [("this", name), ("quoted", false)])
}
parse_one(name, dialect?, into=[Identifier]) catch {
_ => to_identifier(name)
}
}
///|
/// Python `ensure_list(x)` for an optional expression.
pub fn opt_list(e : Expr?) -> Array[Expr] {
match e {
Some(x) => [x]
None => []
}
}