// Small Python-compatible helpers used by the generator.
///|
fn g_first_char(s : String) -> Char? {
for c in s {
return Some(c)
}
None
}
///|
fn g_last_char(s : String) -> Char? {
let mut last : Char? = None
for c in s.rev_iter() {
last = Some(c)
break
}
last
}
///|
/// Python `str.lstrip()`.
pub fn py_lstrip(s : String) -> String {
let chars = s.to_array()
let mut i = 0
while i < chars.length() && is_space(chars[i]) {
i += 1
}
if i == 0 {
s
} else {
String::from_array(chars[i:])
}
}
///|
/// Python `str.rstrip()`.
pub fn py_rstrip(s : String) -> String {
let chars = s.to_array()
let mut j = chars.length()
while j > 0 && is_space(chars[j - 1]) {
j -= 1
}
if j == chars.length() {
s
} else {
String::from_array(chars[:j])
}
}
///|
/// Python `str.lstrip(chars)`.
pub fn lstrip_chars(s : String, chars : String) -> String {
s.trim_start(chars~).to_owned()
}
///|
/// Python `str[n:]` on code points.
fn g_str_from(s : String, n : Int) -> String {
substr(s, n, py_len(s))
}
///|
/// sqlglot `helper.csv`: joins the non-empty strings with `sep`.
pub fn csv(args : Array[String], sep? : String = ", ") -> String {
let out = []
for a in args {
if a != "" {
out.push(a)
}
}
out.join(sep)
}
///|
/// Python `str(int(text, base))`: converts a (possibly huge) number written in
/// `base` into its decimal representation.
pub fn int_str_from_base(
text : String,
base : Int,
) -> String raise SqlglotError {
let t = py_strip(text).replace_all(old="_", new="")
let mut negative = false
let mut body = t
if body.has_prefix("-") {
negative = true
body = g_str_from(body, 1)
} else if body.has_prefix("+") {
body = g_str_from(body, 1)
}
let lower = py_lower(body)
if (base == 16 && lower.has_prefix("0x")) ||
(base == 2 && lower.has_prefix("0b")) ||
(base == 8 && lower.has_prefix("0o")) {
body = g_str_from(body, 2)
}
if body == "" {
raise ValueError(
"invalid literal for int() with base \{base}: \{py_repr_str(text)}",
)
}
// little-endian base-10^4 digits
let digits : Array[Int] = [0]
for c in body {
let d = match c {
'0'..='9' => c.to_int() - '0'.to_int()
'a'..='z' => c.to_int() - 'a'.to_int() + 10
'A'..='Z' => c.to_int() - 'A'.to_int() + 10
_ => 99
}
if d >= base {
raise ValueError(
"invalid literal for int() with base \{base}: \{py_repr_str(text)}",
)
}
let mut carry = d
for i in 0.. 0 {
digits.push(carry % 10000)
carry = carry / 10000
}
}
let sb = StringBuilder()
let n = digits.length()
let mut top = n - 1
while top > 0 && digits[top] == 0 {
top -= 1
}
if negative && !(top == 0 && digits[0] == 0) {
sb.write_char('-')
}
sb.write_string(digits[top].to_string())
let mut i = top - 1
while i >= 0 {
let s = digits[i].to_string()
sb.write_string("0".repeat(4 - s.length()))
sb.write_string(s)
i -= 1
}
sb.to_string()
}
///|
/// Python `str(value)` for an argument value, as used in f-strings
/// (`str(expr)` generates SQL with the default dialect).
pub fn py_str_value(v : Value?) -> String raise SqlglotError {
match v {
None => "None"
Some(Node(e)) => expr_to_sql(e)
Some(Str(s)) => s
Some(Bool(b)) => if b { "True" } else { "False" }
Some(Int(i)) => i.to_string()
Some(DT(d)) => "DType." + d.name()
Some(List(l)) => {
let parts = []
for x in l {
parts.push(
match x {
Str(s) => py_repr_str(s)
other => py_str_value(Some(other))
},
)
}
"[" + parts.join(", ") + "]"
}
}
}
///|
/// The dialect's class name, used in some messages (Python `self.dialect.__class__.__name__`).
pub fn Generator::dialect_class_name(self : Generator) -> String {
dialect_class_name(self.dialect)
}
///|
/// Python `Dialect.__class__.__name__` for a dialect.
pub fn dialect_class_name(d : Dialect) -> String {
match d.name {
"" => "Dialect"
"bigquery" => "BigQuery"
"clickhouse" => "ClickHouse"
"databricks" => "Databricks"
"doris" => "Doris"
"dremio" => "Dremio"
"drill" => "Drill"
"druid" => "Druid"
"duckdb" => "DuckDB"
"dune" => "Dune"
"exasol" => "Exasol"
"fabric" => "Fabric"
"hive" => "Hive"
"materialize" => "Materialize"
"mysql" => "MySQL"
"oracle" => "Oracle"
"postgres" => "Postgres"
"presto" => "Presto"
"prql" => "PRQL"
"redshift" => "Redshift"
"risingwave" => "RisingWave"
"singlestore" => "SingleStore"
"snowflake" => "Snowflake"
"solr" => "Solr"
"spark" => "Spark"
"spark2" => "Spark2"
"sqlite" => "SQLite"
"starrocks" => "StarRocks"
"tableau" => "Tableau"
"teradata" => "Teradata"
"trino" => "Trino"
"tsql" => "TSQL"
"athena" => "Athena"
"dax" => "DAX"
n => {
let sb = StringBuilder()
let mut first = true
for c in n {
sb.write_char(if first { char_upper(c) } else { c })
first = false
}
sb.to_string()
}
}
}
///|
/// `f"{prefix}{s}" if s else ""`.
fn g_pfx(prefix : String, s : String) -> String {
if s != "" {
prefix + s
} else {
""
}
}
///|
/// `f"{prefix}{s}{suffix}" if s else ""`.
fn g_around(prefix : String, s : String, suffix : String) -> String {
if s != "" {
prefix + s + suffix
} else {
""
}
}
///|
/// `s if cond else ""`.
fn g_when(cond : Bool, s : String) -> String {
if cond {
s
} else {
""
}
}
///|
/// Python truthiness of an optional string (`None` and `""` are falsy).
fn g_str_truthy(s : String?) -> Bool {
match s {
Some(x) => x != ""
None => false
}
}
///|
fn g_opt_str(s : String?) -> String {
match s {
Some(x) => x
None => ""
}
}
///|
/// Converts strings to argument values (for `expressions(sqls=...)`).
fn g_strs(xs : Array[String]) -> Array[Value] {
xs.map(x => Str(x))
}
///|
/// Converts expressions to argument values (for `expressions(sqls=...)`).
fn g_nodes(xs : Array[Expr]) -> Array[Value] {
xs.map(x => Node(x))
}
///|
/// Converts expressions to `func` arguments.
fn g_fargs(xs : Array[Expr]) -> Array[&SqlArg] {
xs.map(x => (x : &SqlArg))
}