// Port of the pseudo SQL generator `gen` of sqlglot/optimizer/simplify.py, used to sort
// and deduplicate operands.
///|
priv enum GenItem {
GE(@core.Expr)
GS(String)
GL(Array[GenItem?])
}
///|
fn value_item(v : @core.Value, repr_str : Bool) -> GenItem? {
match v {
Node(e) => Some(GE(e))
List(l) => Some(GL(l.map(x => value_item(x, repr_str))))
Str(s) => Some(GS(if repr_str { @core.py_repr_str(s) } else { s }))
Bool(b) => Some(GS(if b { "True" } else { "False" }))
Int(i) => Some(GS(i.to_string()))
DT(d) => Some(GS("DType.\{d.name()}"))
}
}
///|
fn opt_item(e : @core.Expr?) -> GenItem? {
e.map(x => GE(x))
}
///|
/// Simple pseudo sql generator for quickly generating sortable and uniq strings.
///
/// `comments` controls whether the expressions' comments are included.
pub fn gen(
expression : @core.Expr,
comments? : Bool = false,
) -> String raise @core.SqlglotError {
let stack : Array[GenItem?] = [Some(GE(expression))]
let sqls = StringBuilder::new()
fn push(x : GenItem?) {
stack.push(x)
}
fn s(x : String) {
stack.push(Some(GS(x)))
}
fn args(node : @core.Expr, arg_index : Int) -> Bool {
let kvs : Array[GenItem?] = []
let arg_types = node.kind.arg_types()
for i in arg_index.. kvs.push(Some(GL([Some(GS(":\{k}")), value_item(v, true)])))
None => ()
}
}
if !kvs.is_empty() {
stack.push(Some(GL(kvs)))
true
} else {
false
}
}
fn binary(e : @core.Expr, op : String) {
push(opt_item(e.expression()))
s(op)
push(opt_item(e.this()))
}
fn unary(e : @core.Expr, op : String) {
push(opt_item(e.this()))
s(op)
}
fn parts_of(e : @core.Expr) {
let parts = e.parts()
for i = parts.length() - 1; i >= 0; i = i - 1 {
push(Some(GE(parts[i])))
s(".")
}
stack.pop() |> ignore
}
while stack.pop() is Some(item) {
match item {
None => ()
Some(GS(str)) => sqls.write_string(str)
Some(GL(l)) => {
for i = l.length() - 1; i >= 0; i = i - 1 {
match l[i] {
Some(n) => {
push(Some(n))
s(",")
}
None => ()
}
}
if !l.is_empty() {
stack.pop() |> ignore
}
}
Some(GE(e)) => {
if comments && e.comments is Some(cs) && !cs.is_empty() {
s(" /*" + cs.join(",") + "*/")
}
match e.kind.key() {
"add" => binary(e, " + ")
"alias" => {
push(opt_item(e.arg("alias")))
s(" AS ")
push(opt_item(e.arg("this")))
}
"and" => binary(e, " AND ")
"anonymous" => {
let name = match e.get("this") {
Some(Str(t)) => @core.py_upper(t)
Some(Node(i)) if i.kind == Identifier => {
let n = i.text("this")
if i.has("quoted") {
"\"" + n.replace_all(old="\"", new="\"\"") + "\""
} else {
@core.py_upper(n)
}
}
Some(v) => {
let type_name = match v {
Node(n) => n.kind.name()
List(_) => "list"
Str(_) => "str"
Bool(_) => "bool"
Int(_) => "int"
DT(_) => "Type"
}
raise @core.ValueError(
"Anonymous.this expects a str or an Identifier, got '\{type_name}'.",
)
}
None =>
raise @core.ValueError(
"Anonymous.this expects a str or an Identifier, got 'NoneType'.",
)
}
s(")")
push(Some(GL(e.expressions().map(x => Some(GE(x))))))
s("(")
s(name)
}
"between" => {
push(opt_item(e.arg("high")))
s(" AND ")
push(opt_item(e.arg("low")))
s(" BETWEEN ")
push(opt_item(e.this()))
}
"boolean" => s(if e.has("this") { "TRUE" } else { "FALSE" })
"bracket" => {
s("]")
push(Some(GL(e.expressions().map(x => Some(GE(x))))))
s("[")
push(opt_item(e.this()))
}
"column" => parts_of(e)
"datatype" => {
args(e, 1) |> ignore
let name = match e.get("this") {
Some(DT(d)) => d.name()
Some(Node(n)) => n.name()
Some(Str(x)) => x
_ => ""
}
s("\{name} ")
}
"div" => binary(e, " / ")
"dot" => binary(e, ".")
"eq" => binary(e, " = ")
"from" => {
push(opt_item(e.this()))
s("FROM ")
}
"gt" => binary(e, " > ")
"gte" => binary(e, " >= ")
"identifier" =>
if e.has("quoted") {
s("\"" + e.text("this").replace_all(old="\"", new="\"\"") + "\"")
} else {
s(e.text("this"))
}
"ilike" =>
binary(e, if e.has("negate") { " NOT ILIKE " } else { " ILIKE " })
"in" => {
s(")")
args(e, 1) |> ignore
s("(")
s(" IN ")
push(opt_item(e.this()))
}
"intdiv" => binary(e, " DIV ")
"is" => binary(e, if e.has("negate") { " IS NOT " } else { " IS " })
"like" => binary(e, if e.has("negate") { " NOT Like " } else { " Like " })
"literal" =>
if e.is_string() {
s("'" + e.text("this").replace_all(old="'", new="''") + "'")
} else {
s(e.text("this"))
}
"lt" => binary(e, " < ")
"lte" => binary(e, " <= ")
"mod" => binary(e, " % ")
"mul" => binary(e, " * ")
"neg" => unary(e, "-")
"neq" => binary(e, " <> ")
"not" => unary(e, "NOT ")
"null" => s("NULL")
"or" => binary(e, " OR ")
"paren" => {
s(")")
push(opt_item(e.this()))
s("(")
}
"sub" => binary(e, " - ")
"subquery" => {
args(e, 2) |> ignore
match e.arg("alias") {
Some(a) => push(Some(GE(a)))
None => ()
}
s(")")
push(opt_item(e.this()))
s("(")
}
"table" => {
args(e, 4) |> ignore
match e.arg("alias") {
Some(a) => push(Some(GE(a)))
None => ()
}
parts_of(e)
}
"tablealias" => {
let columns = e.list("columns")
if !columns.is_empty() {
s(")")
push(Some(GL(columns.map(x => Some(GE(x))))))
s("(")
}
push(opt_item(e.this()))
s(" AS ")
}
"var" => s(e.text("this"))
_ =>
if e.kind.is_a(Func) {
s(")")
let vals : Array[GenItem?] = []
for _, v in e.args {
vals.push(value_item(v, false))
}
push(Some(GL(vals)))
s("(")
s(e.kind.sql_name())
} else {
let key = @core.py_upper(e.kind.key())
// `_args` pushes before the key is pushed
let has = args(e, 0)
s(if has { "\{key} " } else { key })
}
}
}
}
}
sqls.to_string()
}