// Generator methods for functions.

///|
/// Python `dialects.dialect.map_date_part`.
pub fn gen_map_date_part(part : Expr?, dialect : Dialect) -> Expr? {
  match part {
    Some(p) => {
      if p.kind.is_a(Column) && p.column_parts().length() != 1 {
        return part
      }
      match dialect.cfg.date_part_mapping.get(py_upper(p.name())) {
        Some(mapped) if mapped != "" =>
          Some(
            if p.is_string() {
              literal_string(mapped)
            } else {
              var_(mapped)
            },
          )
        _ => part
      }
    }
    None => None
  }
}

///|
/// Python `dialects.dialect.unit_to_str`.
pub fn gen_unit_to_str(expression : Expr, default? : String = "DAY") -> Expr? {
  match expression.arg("unit") {
    None => if default != "" { Some(literal_string(default)) } else { None }
    Some(unit) =>
      if unit.kind.is_a(WeekStart) {
        Some(literal_string("WEEK"))
      } else if unit.kind.is_a(Placeholder) ||
        !(unit.kind == Var || unit.kind == Literal) {
        Some(unit)
      } else {
        Some(literal_string(unit.name()))
      }
  }
}

///|
pub fn Generator::function_fallback_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let args : Array[&SqlArg] = []
  for kv in expression.kind.arg_types() {
    match expression.args.get(kv.0) {
      Some(List(l)) =>
        for v in l {
          args.push(v)
        }
      Some(v) => args.push(v)
      None => ()
    }
  }
  let name = if self.dialect.cfg.preserve_original_names {
    match expression.meta_get("name") {
      Some(Str(n)) if n != "" => n
      _ => expression.kind.sql_name()
    }
  } else {
    expression.kind.sql_name()
  }
  self.func(name, args)
}

///|
pub fn Generator::anonymous_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let is_qualified = match expression.parent {
    Some(p) =>
      p.kind.is_a(Dot) &&
      (match p.expression() {
        Some(pe) => physical_equal(pe, expression)
        None => false
      })
    None => false
  }
  self.func(
    self.sql_key(expression, "this"),
    g_fargs(expression.expressions()),
    normalize=!is_qualified,
  )
}

///|
pub fn Generator::ceil_floor(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let to_clause = self.sql_key(expression, "to")
  if to_clause != "" {
    return expression.kind.sql_name() +
      "(" +
      self.sql_key(expression, "this") +
      " TO " +
      to_clause +
      ")"
  }
  self.function_fallback_sql(expression)
}

///|
pub fn Generator::extract_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = if self.cfg.normalize_extract_date_parts {
    gen_map_date_part(expression.this(), self.dialect)
  } else {
    expression.this()
  }
  let this_sql = if self.cfg.extract_allows_quotes {
    self.sql(this)
  } else {
    match this {
      Some(t) if t.kind.is_a(WeekStart) => self.weekstart_name(t)
      Some(t) => t.name()
      None => ""
    }
  }
  let expression_sql = self.sql_key(expression, "expression")
  "EXTRACT(" + this_sql + " FROM " + expression_sql + ")"
}

///|
pub fn Generator::trim_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let trim_type = self.sql_key(expression, "position")
  let func_name = if trim_type == "LEADING" {
    "LTRIM"
  } else if trim_type == "TRAILING" {
    "RTRIM"
  } else {
    "TRIM"
  }
  self.func(func_name, [expression.get("this"), expression.get("expression")])
}

///|
pub fn Generator::convert_concat_args(
  self : Generator,
  expression : Expr,
) -> Array[Expr] raise SqlglotError {
  let mut args = expression.expressions()
  let is_concat_ws = expression.kind.is_a(ConcatWs)
  if is_concat_ws {
    args = if args.is_empty() { [] } else { args[1:].to_owned() }
  }
  if self.dialect.cfg.strict_string_concat && expression.has("safe") {
    args = args.map(e => exp_cast(e, DType::TEXT))
  }
  let concat_coalesce = if is_concat_ws {
    self.dialect.cfg.concat_ws_coalesce
  } else {
    self.dialect.cfg.concat_coalesce
  }
  if !concat_coalesce && expression.has("coalesce") {
    let wrapped = []
    for e in args {
      let mut e = e
      if e.get_type() is None {
        e = gen_annotate_types(e, self.dialect)
      }
      if e.is_string() || e.is_type([DType::ARRAY]) {
        wrapped.push(e)
      } else {
        wrapped.push(
          mk(Coalesce, [
            ("this", e.copy()),
            ("expressions", [literal_string("")]),
          ]),
        )
      }
    }
    args = wrapped
  }
  args
}

///|
pub fn Generator::concat_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if self.dialect.cfg.concat_coalesce && !expression.has("coalesce") {
    return self.concat_to_dpipe_sql(expression)
  }
  let expressions = self.convert_concat_args(expression)
  if !self.cfg.supports_single_arg_concat && expressions.length() == 1 {
    return self.sql(Some(expressions[0]))
  }
  self.func("CONCAT", g_fargs(expressions))
}

///|
/// Python `dialects.dialect.concat_to_dpipe_sql`.
pub fn Generator::concat_to_dpipe_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let args = self.convert_concat_args(expression)
  if args.is_empty() {
    raise ValueError("reduce() of empty iterable with no initial value")
  }
  let mut acc = args[0]
  for i in 1.. String raise SqlglotError {
  if self.dialect.cfg.concat_ws_coalesce && !expression.has("coalesce") {
    let all_args = expression.expressions()
    expression.set("coalesce", true)
    let conds = all_args.map(arg => exp_is(arg, null_()))
    let case_ = case_when(exp_case(), exp_or(conds), null_())
    return self.sql(Some(case_else(case_, expression)))
  }
  let args : Array[&SqlArg] = [expression.expressions().get(0)]
  for a in self.convert_concat_args(expression) {
    args.push(a)
  }
  self.func("CONCAT_WS", args)
}

///|
pub fn Generator::matchagainst_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let expressions : Array[&SqlArg] = []
  if g_str_truthy(self.cfg.match_against_table_prefix) {
    for expr in expression.expressions() {
      if expr.kind.is_a(Table) {
        expressions.push("TABLE " + self.sql(Some(expr)))
      } else {
        expressions.push(expr)
      }
    }
  } else {
    for expr in expression.expressions() {
      expressions.push(expr)
    }
  }
  let modifier = match expression.get("modifier") {
    Some(v) if v.truthy() => " " + py_str_value(Some(v))
    _ => ""
  }
  self.func("MATCH", expressions) +
  " AGAINST(" +
  self.sql_key(expression, "this") +
  modifier +
  ")"
}

///|
pub fn Generator::jsonkeyvalue_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql_key(expression, "this") +
  self.cfg.json_key_value_pair_sep +
  " " +
  self.sql_key(expression, "expression")
}

///|
pub fn Generator::jsonpath_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut path = lstrip_chars(
    self.expressions(expression~, sep="", flat=true),
    ".",
  )
  if self.cfg.quote_json_path {
    path = self.escape_str(path)
    path = self.dialect.cfg.quote_start + path + self.dialect.cfg.quote_end
  }
  path
}

///|
pub fn Generator::json_path_part(
  self : Generator,
  expression : Value?,
) -> String raise SqlglotError {
  match expression {
    Some(Node(e)) if e.kind.is_a(JSONPathPart) =>
      match self.fns.transforms.get(e.kind) {
        Some(transform) => transform(self, e)
        None => {
          self.unsupported("Unsupported JSONPathPart type \{e.kind.name()}")
          ""
        }
      }
    Some(Int(i)) => i.to_string()
    Some(Bool(b)) => if b { "True" } else { "False" }
    Some(Str(s)) =>
      if self.quote_json_path_key_using_brackets &&
        self.cfg.json_path_single_quote_escape {
        "'" + s.replace_all(old="'", new="\\'") + "'"
      } else {
        "\"" + s.replace_all(old="\"", new="\\\"") + "\""
      }
    Some(other) => py_str_value(Some(other))
    None => ""
  }
}

///|
/// Python `_jsonpathkey_sql`.
pub fn Generator::jsonpathkey_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = expression.get("this")
  match this {
    Some(Node(w)) if w.kind.is_a(JSONPathWildcard) => {
      let s = self.json_path_part(this)
      return g_pfx(".", s)
    }
    _ => ()
  }
  let quoted = expression.has("quoted")
  match this {
    Some(Str(s)) if !(quoted && self.cfg.json_path_key_quoted_forces_brackets) => {
      let safe = match self.fns.hooks.safe_json_path_key_re {
        Some(f) => f(s)
        None => is_safe_identifier(s)
      }
      if safe {
        return "." + s
      }
    }
    _ => ()
  }
  let this = self.json_path_part(this)
  if self.quote_json_path_key_using_brackets &&
    self.cfg.json_path_bracketed_key_supported {
    "[" + this + "]"
  } else {
    "." + this
  }
}

///|
/// Python `_jsonpathsubscript_sql`.
pub fn Generator::jsonpathsubscript_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.json_path_part(expression.get("this"))
  g_around("[", this, "]")
}

///|
pub fn Generator::formatjson_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql_key(expression, "this") + " FORMAT JSON"
}

///|
pub fn Generator::formatphrase_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let fmt = self.sql_key(expression, "format")
  this + " (FORMAT " + fmt + ")"
}

///|
/// Python `_jsonobject_sql(expression, name)`.
pub fn Generator::jsonobject_sql(
  self : Generator,
  expression : Expr,
  name? : String = "",
) -> String raise SqlglotError {
  let null_handling = match expression.get("null_handling") {
    Some(v) if v.truthy() => " " + py_str_value(Some(v))
    _ => ""
  }
  let unique_keys = match expression.get("unique_keys") {
    Some(v) =>
      " " + (if v.truthy() { "WITH" } else { "WITHOUT" }) + " UNIQUE KEYS"
    None => ""
  }
  let return_type = g_pfx(" RETURNING ", self.sql_key(expression, "return_type"))
  let encoding = g_pfx(" ENCODING ", self.sql_key(expression, "encoding"))
  let name = if name == "" {
    if expression.kind.is_a(JSONObject) {
      "JSON_OBJECT"
    } else {
      "JSON_OBJECTAGG"
    }
  } else {
    name
  }
  self.func(
    name,
    g_fargs(expression.expressions()),
    suffix=null_handling + unique_keys + return_type + encoding + ")",
  )
}

///|
pub fn Generator::jsonarray_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let null_handling = match expression.get("null_handling") {
    Some(v) if v.truthy() => " " + py_str_value(Some(v))
    _ => ""
  }
  let return_type = g_pfx(" RETURNING ", self.sql_key(expression, "return_type"))
  let strict = g_when(expression.has("strict"), " STRICT")
  self.func(
    "JSON_ARRAY",
    g_fargs(expression.expressions()),
    suffix=null_handling + return_type + strict + ")",
  )
}

///|
pub fn Generator::jsonarrayagg_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let order = self.sql_key(expression, "order")
  let null_handling = match expression.get("null_handling") {
    Some(v) if v.truthy() => " " + py_str_value(Some(v))
    _ => ""
  }
  let return_type = g_pfx(" RETURNING ", self.sql_key(expression, "return_type"))
  let strict = g_when(expression.has("strict"), " STRICT")
  self.func(
    "JSON_ARRAYAGG",
    [this],
    suffix=order + null_handling + return_type + strict + ")",
  )
}

///|
pub fn Generator::jsoncolumndef_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let path = g_pfx(" PATH ", self.sql_key(expression, "path"))
  let nested_schema = self.sql_key(expression, "nested_schema")
  if nested_schema != "" {
    return "NESTED" + path + " " + nested_schema
  }
  let this = self.sql_key(expression, "this")
  let kind = g_pfx(" ", self.sql_key(expression, "kind"))
  let format_json = g_when(expression.has("format_json"), " FORMAT JSON")
  let ordinality = g_when(expression.has("ordinality"), " FOR ORDINALITY")
  this + kind + format_json + path + ordinality
}

///|
pub fn Generator::jsonschema_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.func("COLUMNS", g_fargs(expression.expressions()))
}

///|
pub fn Generator::jsontable_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let path = g_pfx(", ", self.sql_key(expression, "path"))
  let error_handling = match expression.get("error_handling") {
    Some(v) if v.truthy() => " " + py_str_value(Some(v))
    _ => ""
  }
  let empty_handling = match expression.get("empty_handling") {
    Some(v) if v.truthy() => " " + py_str_value(Some(v))
    _ => ""
  }
  let schema = self.sql_key(expression, "schema")
  self.func(
    "JSON_TABLE",
    [this],
    suffix=path + error_handling + empty_handling + " " + schema + ")",
  )
}

///|
pub fn Generator::openjsoncolumndef_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let kind = self.sql_key(expression, "kind")
  let path = g_pfx(" ", self.sql_key(expression, "path"))
  let as_json = g_when(expression.has("as_json"), " AS JSON")
  this + " " + kind + path + as_json
}

///|
pub fn Generator::openjson_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let path = g_pfx(", ", self.sql_key(expression, "path"))
  let expressions = self.expressions(expression~)
  let with_ = if expressions != "" {
    " WITH (" +
    self.seg(self.indent(expressions), sep="") +
    self.seg(")", sep="")
  } else {
    ""
  }
  "OPENJSON(" + this + path + ")" + with_
}

///|
pub fn Generator::return_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  "RETURN " + self.sql_key(expression, "this")
}

///|
pub fn Generator::strtotime_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.func("STR_TO_TIME", [expression.get("this"), expression.get("format")])
}

///|
pub fn Generator::strtodate_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.func("STR_TO_DATE", [expression.get("this"), expression.get("format")])
}

///|
pub fn Generator::parsedatetime_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.func("PARSE_DATETIME", [
    expression.get("this"),
    expression.get("format"),
    expression.get("zone"),
  ])
}

///|
pub fn Generator::currentdate_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let zone = self.sql_key(expression, "this")
  if zone != "" {
    "CURRENT_DATE(" + zone + ")"
  } else {
    "CURRENT_DATE"
  }
}

///|
pub fn Generator::fromiso8601date_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql(Some(exp_cast(expression.this_(), DType::DATE)))
}

///|
pub fn Generator::fromiso8601timestamp_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql(Some(exp_cast(expression.this_(), DType::TIMESTAMPTZ)))
}

///|
pub fn Generator::fromiso8601timestampnanos_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql(Some(exp_cast(expression.this_(), DType::TIMESTAMPTZ)))
}

///|
pub fn Generator::tochar_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.unsupported_args(expression, ["format"])
  self.sql(Some(exp_cast(expression.this_(), DType::TEXT)))
}

///|
pub fn Generator::tonumber_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.unsupported_args(expression, ["default"])
  if !self.cfg.supports_to_number {
    self.unsupported("Unsupported TO_NUMBER function")
    return self.tonumber_cast_sql(expression)
  }
  let fmt = match expression.get("format") {
    Some(v) if v.truthy() => v
    _ => {
      self.unsupported("Conversion format is required for TO_NUMBER")
      return self.tonumber_cast_sql(expression)
    }
  }
  self.func("TO_NUMBER", [expression.get("this"), Some(fmt)])
}

///|
/// Python `_tonumber_cast_sql`.
pub fn Generator::tonumber_cast_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if expression.has("safe") {
    return self.sql(
      Some(
        mk(TryCast, [
          ("this", expression.get("this")),
          ("to", Some(Node(datatype_of(DType::DOUBLE)))),
        ]),
      ),
    )
  }
  self.sql(Some(exp_cast(expression.this_(), DType::DOUBLE)))
}

///|
pub fn Generator::distinct_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut this = self.expressions(expression~, flat=true)
  let exprs = expression.expressions()
  if !self.cfg.multi_arg_distinct && exprs.length() > 1 {
    let mut case_ = exp_case()
    for arg in exprs {
      case_ = case_when(case_, exp_is(arg, null_()), null_())
    }
    this = self.sql(Some(case_else(case_, maybe_parse_str("(" + this + ")"))))
  }
  let this = g_pfx(" ", this)
  let on = g_pfx(" ON ", self.sql_key(expression, "on"))
  "DISTINCT" + this + on
}

///|
pub fn Generator::ignorenulls_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.embed_ignore_nulls(expression, "IGNORE NULLS")
}

///|
pub fn Generator::respectnulls_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.embed_ignore_nulls(expression, "RESPECT NULLS")
}

///|
pub fn Generator::havingmax_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this_sql = self.sql_key(expression, "this")
  let expression_sql = self.sql_key(expression, "expression")
  let kind = if expression.has("max") { "MAX" } else { "MIN" }
  this_sql + " HAVING " + kind + " " + expression_sql
}

///|
/// Python `_embed_ignore_nulls`.
pub fn Generator::embed_ignore_nulls(
  self : Generator,
  expression : Expr,
  text : String,
) -> String raise SqlglotError {
  let this = expression.this()
  match this {
    Some(t) if t.kind.is_any(
        self.cfg.respect_ignore_nulls_unsupported_expressions,
      ) => {
      self.unsupported(
        "RESPECT/IGNORE NULLS is not supported for \{t.kind.key()} in \{self.dialect_class_name()}",
      )
      return self.sql(this)
    }
    _ => ()
  }
  if self.cfg.ignore_nulls_in_func && !expression.meta_bool("inline") {
    if self.cfg.ignore_nulls_before_order {
      let found = gen_find_all_in_scope(expression, [HavingMax, Order, Limit])
      let priority = fn(x : Expr) -> Int {
        if x.kind.is_a(HavingMax) {
          0
        } else if x.kind.is_a(Order) {
          1
        } else {
          2
        }
      }
      let mut best : Expr? = None
      for x in found {
        match best {
          None => best = Some(x)
          Some(b) => if priority(x) < priority(b) { best = Some(x) }
        }
      }
      match best {
        Some(m) => {
          let mod_this = m.this_()
          let new_this = mk1(expression.kind, mod_this.copy())
          new_this.get_meta()["inline"] = Bool(true)
          mod_this.replace(Some(new_this)) |> ignore
          return self.sql(expression.this())
        }
        None => ()
      }
    }
    match expression.find([AggFunc]) {
      Some(agg_func) => {
        let s = self.sql(Some(agg_func), comment=false)
        let agg_func_sql = substr(s, 0, py_len(s) - 1) + " " + text + ")"
        return self.maybe_comment(agg_func_sql, comments?=agg_func.comments)
      }
      None => ()
    }
  }
  self.sql_key(expression, "this") + " " + text
}

///|
pub fn Generator::try_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if !self.cfg.try_supported {
    self.unsupported("Unsupported TRY function")
    return self.sql_key(expression, "this")
  }
  self.func("TRY", [expression.get("this")])
}

///|
pub fn Generator::log_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut this = expression.get("this")
  let mut expr = expression.get("expression")
  match self.dialect.cfg.log_base_first {
    Some(false) => {
      let tmp = this
      this = expr
      expr = tmp
    }
    None if expr is Some(_) => {
      let name = match this {
        Some(Node(t)) => t.name()
        _ => ""
      }
      if name == "2" || name == "10" {
        return self.func("LOG" + name, [expr])
      }
      let this_sql = match this {
        Some(v) => self.sql_value(v)
        None => ""
      }
      self.unsupported("Unsupported logarithm with base \{this_sql}")
    }
    _ => ()
  }
  self.func("LOG", [this, expr])
}

///|
pub fn Generator::nvl2_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if self.cfg.nvl2_supported {
    return self.function_fallback_sql(expression)
  }
  let case_ = case_when(
    mk0(Case),
    exp_not(exp_is(expression.this_(), null_()), copy=false),
    match expression.arg("true") {
      Some(t) => t
      None => null_()
    },
    copy=false,
  )
  match expression.arg("false") {
    Some(else_cond) => case_else(case_, else_cond, copy=false) |> ignore
    None => ()
  }
  self.sql(Some(case_))
}

///|
pub fn Generator::nthvalue_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if expression.get("from_first") is Some(Bool(false)) {
    self.unsupported("NTH_VALUE FROM LAST is not supported")
  }
  self.function_fallback_sql(expression)
}

///|
pub fn Generator::comprehension_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let expr = self.sql_key(expression, "expression")
  let position = g_pfx(", ", self.sql_key(expression, "position"))
  let iterator = self.sql_key(expression, "iterator")
  let condition = g_pfx(" IF ", self.sql_key(expression, "condition"))
  this + " FOR " + expr + position + " IN " + iterator + condition
}

///|
pub fn Generator::columnprefix_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql_key(expression, "this") +
  "(" +
  self.sql_key(expression, "expression") +
  ")"
}

///|
pub fn Generator::opclass_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql_key(expression, "this") +
  " " +
  self.sql_key(expression, "expression")
}

///|
/// Python `_ml_sql(expression, name)`.
pub fn Generator::ml_sql(
  self : Generator,
  expression : Expr,
  name : String,
) -> String raise SqlglotError {
  let model = "MODEL " + self.sql_key(expression, "this")
  let expr_sql : String? = match expression.expression() {
    Some(expr) => {
      let s = self.sql_key(expression, "expression")
      Some(if expr.kind.is_a(Table) { "TABLE " + s } else { s })
    }
    None => None
  }
  let parameters = self.sql_key(expression, "params_struct")
  let parameters : String? = if parameters != "" {
    Some(parameters)
  } else {
    None
  }
  self.func(name, [model, expr_sql, parameters])
}

///|
pub fn Generator::predict_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.ml_sql(expression, "PREDICT")
}

///|
pub fn Generator::generateembedding_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let name = if expression.has("is_text") {
    "GENERATE_TEXT_EMBEDDING"
  } else {
    "GENERATE_EMBEDDING"
  }
  self.ml_sql(expression, name)
}

///|
pub fn Generator::generatetext_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.ml_sql(expression, "GENERATE_TEXT")
}

///|
pub fn Generator::generatetable_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.ml_sql(expression, "GENERATE_TABLE")
}

///|
pub fn Generator::generatebool_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.ml_sql(expression, "GENERATE_BOOL")
}

///|
pub fn Generator::generateint_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.ml_sql(expression, "GENERATE_INT")
}

///|
pub fn Generator::generatedouble_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.ml_sql(expression, "GENERATE_DOUBLE")
}

///|
pub fn Generator::mltranslate_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.ml_sql(expression, "TRANSLATE")
}

///|
pub fn Generator::mlforecast_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.ml_sql(expression, "FORECAST")
}

///|
pub fn Generator::table_prefixed_sql(
  self : Generator,
  expression : Expr,
  key : String,
) -> String raise SqlglotError {
  let s = self.sql_key(expression, key)
  match expression.arg(key) {
    Some(t) if t.kind.is_a(Table) => "TABLE " + s
    _ => s
  }
}

///|
pub fn Generator::aiforecast_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this_sql = self.table_prefixed_sql(expression, "this")
  let args : Array[&SqlArg] = [this_sql]
  for
    k in [
      "data_col", "timestamp_col", "model", "id_cols", "horizon", "forecast_end_timestamp",
      "confidence_level", "output_historical_time_series", "context_window",
    ] {
    args.push(expression.get(k))
  }
  self.func("FORECAST", args)
}

///|
pub fn Generator::featuresattime_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this_sql = self.table_prefixed_sql(expression, "this")
  self.func("FEATURES_AT_TIME", [
    this_sql,
    expression.get("time"),
    expression.get("num_rows"),
    expression.get("ignore_feature_nulls"),
  ])
}

///|
pub fn Generator::vectorsearch_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this_sql = self.table_prefixed_sql(expression, "this")
  let query_table = self.table_prefixed_sql(expression, "query_table")
  self.func("VECTOR_SEARCH", [
    this_sql,
    expression.get("column_to_search"),
    query_table,
    expression.get("query_column_to_search"),
    expression.get("top_k"),
    expression.get("distance_type"),
    expression.get("options"),
  ])
}

///|
pub fn Generator::forin_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let expression_sql = self.sql_key(expression, "expression")
  "FOR " + this + " DO " + expression_sql
}

///|
pub fn Generator::refresh_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let kind = match expression.this() {
    Some(t) if t.kind.is_a(Literal) => ""
    _ => expression.text("kind") + " "
  }
  "REFRESH " + kind + this
}

///|
pub fn Generator::toarray_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut arg = expression.this_()
  if arg.get_type() is None {
    arg = gen_annotate_types(arg, self.dialect)
  }
  if arg.is_type([DType::ARRAY]) {
    return self.sql(Some(arg))
  }
  let cond_for_null = exp_is(arg, null_())
  self.sql(
    Some(
      mk(If, [
        ("this", cond_for_null.copy()),
        ("true", null_()),
        ("false", mk(Kind::Array, [("expressions", [arg])]).copy()),
      ]),
    ),
  )
}

///|
pub fn Generator::tsordstotime_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = expression.this_()
  let time_format = self.format_time(expression)
  if g_str_truthy(time_format) {
    return self.sql(
      Some(
        exp_cast(
          mk(StrToTime, [("this", this), ("format", expression.arg("format"))]),
          DType::TIME,
        ),
      ),
    )
  }
  if this.kind.is_a(TsOrDsToTime) || this.is_type([DType::TIME]) {
    return self.sql(Some(this))
  }
  self.sql(Some(exp_cast(this, DType::TIME)))
}

///|
pub fn Generator::tsordstotimestamp_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = expression.this_()
  if this.kind.is_a(TsOrDsToTimestamp) || this.is_type([DType::TIMESTAMP]) {
    return self.sql(Some(this))
  }
  self.sql(Some(exp_cast(this, DType::TIMESTAMP, dialect=self.dialect)))
}

///|
pub fn Generator::tsordstodatetime_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = expression.this_()
  if this.kind.is_a(TsOrDsToDatetime) || this.is_type([DType::DATETIME]) {
    return self.sql(Some(this))
  }
  self.sql(Some(exp_cast(this, DType::DATETIME, dialect=self.dialect)))
}

///|
pub fn Generator::tsordstodate_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = expression.this_()
  let time_format = self.format_time(expression)
  let safe = expression.get("safe")
  match time_format {
    Some(tf) if tf != "" &&
      tf != self.dialect.cfg.time_format &&
      tf != self.dialect.cfg.date_format =>
      return self.sql(
        Some(
          exp_cast(
            mk(StrToTime, [
              ("this", this),
              ("format", expression.arg("format")),
              ("safe", safe),
            ]),
            DType::DATE,
          ),
        ),
      )
    _ => ()
  }
  if this.kind.is_a(TsOrDsToDate) || this.is_type([DType::DATE]) {
    return self.sql(Some(this))
  }
  match safe {
    Some(v) if v.truthy() =>
      return self.sql(
        Some(mk(TryCast, [("this", this), ("to", datatype_of(DType::DATE))])),
      )
    _ => ()
  }
  self.sql(Some(exp_cast(this, DType::DATE)))
}

///|
pub fn Generator::unixdate_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql(
    Some(
      mk(DateDiff, [
        ("this", expression.this_().copy()),
        ("expression", exp_cast(literal_string("1970-01-01"), DType::DATE)),
        ("unit", mk1(Column, to_identifier("day"))),
      ]),
    ),
  )
}

///|
pub fn Generator::lastday_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if self.cfg.last_day_supports_date_part {
    return self.function_fallback_sql(expression)
  }
  match expression.arg("unit") {
    Some(unit) if py_upper(unit.name()) != "MONTH" =>
      self.unsupported("Date parts are not supported in LAST_DAY.")
    _ => ()
  }
  self.func("LAST_DAY", [expression.get("this")])
}

///|
pub fn Generator::dateadd_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.func("DATE_ADD", [
    expression.arg("this"),
    expression.arg("expression"),
    gen_unit_to_str(expression),
  ])
}

///|
/// Virtual `arrayinsert_sql(expression, index_offset)`.
pub fn Generator::arrayinsert_sql_v(
  self : Generator,
  expression : Expr,
  index_offset? : Int = 0,
) -> String raise SqlglotError {
  match self.fns.hooks.arrayinsert_sql {
    Some(f) => f(self, expression, index_offset)
    None => self.arrayinsert_sql(expression, index_offset~)
  }
}

///|
pub fn Generator::arrayinsert_sql(
  self : Generator,
  expression : Expr,
  index_offset? : Int = 0,
) -> String raise SqlglotError {
  let this = expression.this_()
  let mut position = expression.arg("position")
  let stored_offset = match expression.get("offset") {
    Some(Int(i)) => i.to_int()
    Some(Bool(b)) => if b { 1 } else { 0 }
    Some(Node(n)) =>
      match n.to_py_int() {
        Some(v) => v.to_int()
        None => 0
      }
    _ => 0
  }
  let offset = index_offset - stored_offset
  if offset != 0 {
    match position {
      Some(p) if p.is_int() => {
        let value = p.to_py_int().unwrap()
        if value >= 0L {
          position = Some(
            literal_number((value + offset.to_int64()).to_string()),
          )
        } else if offset < 0 && value == -1L {
          position = Some(mk1(ArraySize, this.copy()))
        } else {
          position = Some(
            literal_number((value - offset.to_int64()).to_string()),
          )
        }
      }
      _ =>
        self.unsupported(
          "ARRAY_INSERT position can only be converted if it's a literal",
        )
    }
  }
  self.func("ARRAY_INSERT", [Some(this), position, expression.arg("expression")])
}

///|
pub fn Generator::arrayany_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if self.cfg.can_implement_array_any {
    let filtered = mk2(
      ArrayFilter,
      expression.get("this"),
      expression.get("expression"),
    )
    let filtered_not_empty = exp_neq(mk1(ArraySize, filtered), literal_int(0))
    let original_is_empty = exp_eq(
      mk1(ArraySize, expression.get("this")),
      literal_int(0),
    )
    return self.sql(
      Some(exp_paren(exp_or([original_is_empty, filtered_not_empty]))),
    )
  }
  if self.dialect.name != "" {
    self.unsupported("ARRAY_ANY is unsupported")
  }
  self.function_fallback_sql(expression)
}

///|
pub fn Generator::struct_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let new_exprs = []
  for e in expression.expressions() {
    if e.kind.is_a(PropertyEQ) {
      let alias_ : Value = match e.this() {
        Some(t) if t.is_string() => Str(e.name())
        _ =>
          match e.get("this") {
            Some(v) => v
            None => Str("")
          }
      }
      new_exprs.push(exp_alias(e.expression_(), alias_))
    } else {
      new_exprs.push(e)
    }
  }
  expression.set("expressions", new_exprs)
  self.function_fallback_sql(expression)
}

///|
pub fn Generator::partitionrange_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let low = self.sql_key(expression, "this")
  let high = self.sql_key(expression, "expression")
  low + " TO " + high
}

///|
pub fn Generator::convert_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut to = match expression.this() {
    Some(t) => t
    None => return ""
  }
  let value = match expression.expression() {
    Some(v) => v
    None => return ""
  }
  let style = expression.arg("style")
  let safe = expression.get("safe")
  let strict = expression.has("strict")
  let to_this = to.datatype_this()
  let is_param_text = match to_this {
    Some(d) => self.cfg.parameterizable_text_types.contains(d)
    None => false
  }
  if to.expressions().is_empty() && is_param_text {
    to = datatype_build(to_this.unwrap(), kwargs=[
      ("expressions", [literal_int(30)]),
      ("nested", false),
    ])
  }
  let to_this = to.datatype_this()
  let cast_kind = if strict { Kind::Cast } else { Kind::TryCast }
  let mut transformed : Expr? = None
  match style {
    Some(st) if st.kind.is_a(Literal) && st.is_int() => {
      let style_value = st.name()
      let converted_style = match tsql_convert_format_mapping.get(style_value) {
        Some(s) => s
        None => {
          self.unsupported("Unsupported T-SQL 'style' value: \{style_value}")
          "None"
        }
      }
      let fmt = literal_string(converted_style)
      if to_this == Some(DType::DATE) {
        transformed = Some(mk(StrToDate, [("this", value), ("format", fmt)]))
      } else if to_this == Some(DType::DATETIME) ||
        to_this == Some(DType::DATETIME2) {
        transformed = Some(mk(StrToTime, [("this", value), ("format", fmt)]))
      } else if is_param_text {
        transformed = Some(
          mk(cast_kind, [
            ("this", mk(TimeToStr, [("this", value), ("format", fmt)])),
            ("to", to),
            ("safe", safe),
          ]),
        )
      } else if to_this == Some(DType::TEXT) {
        transformed = Some(mk(TimeToStr, [("this", value), ("format", fmt)]))
      }
    }
    _ => ()
  }
  let transformed = match transformed {
    Some(t) => t
    None => mk(cast_kind, [("this", value), ("to", to), ("safe", safe)])
  }
  self.sql(Some(transformed))
}

///|
pub fn Generator::querytransform_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let transform = self.func("TRANSFORM", g_fargs(expression.expressions()))
  let row_format_before = g_pfx(
    " ",
    self.sql_key(expression, "row_format_before"),
  )
  let record_writer = g_pfx(
    " RECORDWRITER ",
    self.sql_key(expression, "record_writer"),
  )
  let using_ = " USING " + self.sql_key(expression, "command_script")
  let schema = g_pfx(" AS ", self.sql_key(expression, "schema"))
  let row_format_after = g_pfx(" ", self.sql_key(expression, "row_format_after"))
  let record_reader = g_pfx(
    " RECORDREADER ",
    self.sql_key(expression, "record_reader"),
  )
  transform +
  row_format_before +
  record_writer +
  using_ +
  schema +
  row_format_after +
  record_reader
}

///|
pub fn Generator::anyvalue_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut this = self.sql_key(expression, "this")
  let having = self.sql_key(expression, "having")
  if having != "" {
    this = this +
      " HAVING " +
      (if expression.has("max") { "MAX" } else { "MIN" }) +
      " " +
      having
  }
  self.func("ANY_VALUE", [this])
}

///|
pub fn Generator::gapfill_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = "TABLE " + self.sql_key(expression, "this")
  let args : Array[&SqlArg] = [this]
  for k, v in expression.args {
    if k != "this" {
      args.push(v)
    }
  }
  self.func("GAP_FILL", args)
}

///|
/// Virtual `scope_resolution`.
pub fn Generator::scope_resolution(
  self : Generator,
  rhs : String,
  scope_name : String,
) -> String raise SqlglotError {
  match self.fns.hooks.scope_resolution {
    Some(f) => f(self, rhs, scope_name)
    None => self.scope_resolution_base(rhs, scope_name)
  }
}

///|
pub fn Generator::scope_resolution_base(
  self : Generator,
  rhs : String,
  scope_name : String,
) -> String raise SqlglotError {
  let scope : String? = if scope_name != "" { Some(scope_name) } else { None }
  self.func("SCOPE_RESOLUTION", [scope, Some(rhs)])
}

///|
pub fn Generator::scoperesolution_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let expr = match expression.expression() {
    Some(e) if e.kind.is_a(Func) =>
      self.sql_key(e, "this") +
      "(" +
      self.format_args(g_fargs(e.expressions())) +
      ")"
    _ => self.sql_key(expression, "expression")
  }
  self.scope_resolution(expr, this)
}

///|
pub fn Generator::parsejson_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  match self.cfg.parse_json_name {
    None => self.sql(expression.this())
    Some(name) =>
      self.func(name, [expression.get("this"), expression.get("expression")])
  }
}

///|
pub fn Generator::rand_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let lower = self.sql_key(expression, "lower")
  let upper = self.sql_key(expression, "upper")
  if lower != "" && upper != "" {
    return "(" +
      upper +
      " - " +
      lower +
      ") * " +
      self.func("RAND", [expression.get("this")]) +
      " + " +
      lower
  }
  self.func("RAND", [expression.get("this")])
}

///|
pub fn Generator::changes_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let information = "INFORMATION => " + self.sql_key(expression, "information")
  let at_before = g_pfx(self.seg(""), self.sql_key(expression, "at_before"))
  let end = g_pfx(self.seg(""), self.sql_key(expression, "end"))
  "CHANGES (" + information + ")" + at_before + end
}

///|
pub fn Generator::pad_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let prefix = if expression.has("is_left") { "L" } else { "R" }
  let fill_pattern = self.sql_key(expression, "fill_pattern")
  let fill_pattern : String? = if fill_pattern != "" {
    Some(fill_pattern)
  } else if self.cfg.pad_fill_pattern_is_required {
    Some("' '")
  } else {
    None
  }
  self.func(prefix + "PAD", [
    expression.get("this"),
    expression.get("expression"),
    fill_pattern.map(s => Str(s)),
  ])
}

///|
pub fn Generator::summarize_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let table = g_when(expression.has("table"), " TABLE")
  "SUMMARIZE" + table + " " + self.sql(expression.this())
}

///|
pub fn Generator::explodinggenerateseries_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let generate_series = Expr::new(
    GenerateSeries,
    Map::from_iter(expression.args.iter()),
  )
  let mut parent = expression.parent
  match parent {
    Some(p) if p.kind.is_any([Alias, TableAlias]) => parent = p.parent
    _ => ()
  }
  let parent_is = fn(kinds : Array[Kind]) {
    match parent {
      Some(p) => p.kind.is_any(kinds)
      None => false
    }
  }
  if self.cfg.supports_exploding_projections && !parent_is([Table, Unnest]) {
    return self.sql(Some(mk(Unnest, [("expressions", [generate_series])])))
  }
  if parent_is([Select]) {
    self.unsupported("GenerateSeries projection unnesting is not supported.")
  }
  self.sql(Some(generate_series))
}

///|
pub fn Generator::converttimezone_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if self.cfg.supports_convert_timezone {
    return self.function_fallback_sql(expression)
  }
  let source_tz = expression.arg("source_tz")
  let target_tz = expression.arg("target_tz")
  let mut timestamp = expression.arg("timestamp")
  match (source_tz, timestamp) {
    (Some(stz), Some(ts)) =>
      timestamp = Some(
        mk(AtTimeZone, [
          ("this", exp_cast(ts, DType::TIMESTAMPNTZ)),
          ("zone", stz),
        ]),
      )
    _ => ()
  }
  let expr = mk(AtTimeZone, [("this", timestamp), ("zone", target_tz)])
  self.sql(Some(expr))
}

///|
pub fn Generator::json_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = g_pfx(" ", self.sql_key(expression, "this"))
  let with_sql = match expression.get("with_") {
    None => ""
    Some(v) => if !v.truthy() { " WITHOUT" } else { " WITH" }
  }
  let unique_sql = g_when(expression.has("unique"), " UNIQUE KEYS")
  "JSON" + this + with_sql + unique_sql
}

///|
pub fn Generator::jsonvalue_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let path = self.sql_key(expression, "path")
  let returning = g_pfx(" RETURNING ", self.sql_key(expression, "returning"))
  let on_condition = g_pfx(" ", self.sql_key(expression, "on_condition"))
  self.func("JSON_VALUE", [
    expression.get("this"),
    Some(Str(path + returning + on_condition)),
  ])
}

///|
pub fn Generator::skipjsoncolumn_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let regexp = g_when(expression.has("regexp"), " REGEXP")
  "SKIP" + regexp + " " + self.sql(expression.expression())
}

///|
pub fn Generator::conditionalinsert_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let else_ = g_when(expression.has("else_"), "ELSE ")
  let condition = self.sql_key(expression, "expression")
  let condition = if condition != "" {
    "WHEN " + condition + " THEN "
  } else {
    else_
  }
  let insert = py_strip(g_str_from(self.sql_key(expression, "this"), 6))
  condition + insert
}

///|
pub fn Generator::multitableinserts_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let kind = self.sql_key(expression, "kind")
  let expressions = self.seg(self.expressions(expression~, sep=" "))
  "INSERT " + kind + expressions + self.seg(self.sql_key(expression, "source"))
}

///|
pub fn Generator::oncondition_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut empty = match expression.get("empty") {
    Some(Node(e)) => "DEFAULT " + expr_to_sql(e) + " ON EMPTY"
    _ => self.sql_key(expression, "empty")
  }
  let mut error = match expression.get("error") {
    Some(Node(e)) => "DEFAULT " + expr_to_sql(e) + " ON ERROR"
    _ => self.sql_key(expression, "error")
  }
  if error != "" && empty != "" {
    error = if self.dialect.cfg.on_condition_empty_before_error {
      empty + " " + error
    } else {
      error + " " + empty
    }
    empty = ""
  }
  let null = self.sql_key(expression, "null")
  empty + error + null
}

///|
pub fn Generator::jsonextractquote_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let scalar = g_when(expression.has("scalar"), " ON SCALAR STRING")
  self.sql_key(expression, "option") + " QUOTES" + scalar
}

///|
pub fn Generator::jsonexists_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let path = self.sql_key(expression, "path")
  let passing = g_pfx(" PASSING ", self.expressions(expression~, key="passing"))
  let on_condition = g_pfx(" ", self.sql_key(expression, "on_condition"))
  let path = path + passing + on_condition
  self.func("JSON_EXISTS", [this, path])
}

///|
/// Python `_add_arrayagg_null_filter`.
pub fn Generator::add_arrayagg_null_filter(
  self : Generator,
  array_agg_sql : String,
  array_agg_expr : Expr,
  column_expr : Expr?,
) -> String raise SqlglotError {
  if !(self.dialect.cfg.array_agg_includes_nulls == Some(true) &&
    array_agg_expr.has("nulls_excluded")) {
    return array_agg_sql
  }
  let mut array_agg_sql = array_agg_sql
  match array_agg_expr.parent {
    Some(parent) if parent.kind.is_a(Filter) => {
      let parent_cond = parent.expression_().this_()
      match column_expr {
        Some(c) =>
          parent_cond.replace(
            Some(exp_and([parent_cond, exp_not(exp_is(c, null_()))])),
          )
          |> ignore
        None => ()
      }
    }
    _ =>
      match column_expr {
        Some(c) if c.find([Column]) is Some(_) => {
          let this_sql = if c.kind.is_a(Distinct) {
            self.expressions(expression=c)
          } else {
            self.sql(Some(c))
          }
          array_agg_sql = array_agg_sql +
            " FILTER(WHERE " +
            this_sql +
            " IS NOT NULL)"
        }
        _ => ()
      }
  }
  array_agg_sql
}

///|
pub fn Generator::arrayagg_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let array_agg = self.function_fallback_sql(expression)
  let mut column_expr = expression.this()
  match column_expr {
    Some(c) if c.kind.is_a(Order) => column_expr = c.this()
    _ => ()
  }
  self.add_arrayagg_null_filter(array_agg, expression, column_expr)
}

///|
pub fn Generator::apply_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let expr = self.sql_key(expression, "expression")
  this + " APPLY(" + expr + ")"
}

///|
pub fn Generator::columns_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let func = self.function_fallback_sql(expression)
  if expression.has("unpack") {
    "*" + func
  } else {
    func
  }
}

///|
pub fn Generator::overlay_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let expr = self.sql_key(expression, "expression")
  let from_sql = self.sql_key(expression, "from_")
  let for_sql = g_pfx(" FOR ", self.sql_key(expression, "for_"))
  "OVERLAY(" + this + " PLACING " + expr + " FROM " + from_sql + for_sql + ")"
}

///|
pub fn Generator::todouble_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.unsupported_args(expression, ["format"])
  let cast_kind = if expression.has("safe") {
    Kind::TryCast
  } else {
    Kind::Cast
  }
  self.sql(
    Some(
      mk(cast_kind, [
        ("this", expression.get("this")),
        ("to", Some(Node(DType::DOUBLE.into_expr()))),
      ]),
    ),
  )
}

///|
pub fn Generator::string_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut this = expression.this_()
  match expression.arg("zone") {
    Some(zone) =>
      this = mk(ConvertTimezone, [
        ("source_tz", literal_string("UTC")),
        ("target_tz", zone),
        ("timestamp", this),
      ])
    None => ()
  }
  self.sql(Some(exp_cast(this, DType::VARCHAR)))
}

///|
pub fn Generator::median_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if !self.cfg.supports_median {
    return self.sql(
      Some(
        mk(PercentileCont, [
          ("this", expression.get("this")),
          ("expression", Some(Node(literal_number("0.5")))),
        ]),
      ),
    )
  }
  self.function_fallback_sql(expression)
}

///|
pub fn Generator::overflowtruncatebehavior_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let filler = g_pfx(" ", self.sql_key(expression, "this"))
  let with_count = if expression.has("with_count") {
    "WITH COUNT"
  } else {
    "WITHOUT COUNT"
  }
  "TRUNCATE" + filler + " " + with_count
}

///|
pub fn Generator::unixseconds_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if self.cfg.supports_unix_seconds {
    return self.function_fallback_sql(expression)
  }
  let start_ts = exp_cast(
    literal_string("1970-01-01 00:00:00+00"),
    DType::TIMESTAMPTZ,
  )
  self.sql(
    Some(
      mk(TimestampDiff, [
        ("this", expression.get("this")),
        ("expression", Some(Node(start_ts))),
        ("unit", Some(Node(var_("SECONDS")))),
      ]),
    ),
  )
}

///|
pub fn Generator::arraysize_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut dim = expression.expression()
  match dim {
    Some(d) if self.cfg.array_size_dim_required is None => {
      if !(d.is_int() && d.name() == "1") {
        self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH")
      }
      dim = None
    }
    _ => ()
  }
  if self.cfg.array_size_dim_required == Some(true) && dim is None {
    dim = Some(literal_int(1))
  }
  self.func(self.cfg.array_size_name, [expression.arg("this"), dim])
}

///|
pub fn Generator::xmlelement_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let prefix = if expression.has("evalname") { "EVALNAME" } else { "NAME" }
  let name = prefix + " " + self.sql_key(expression, "this")
  let args : Array[&SqlArg] = [name]
  for e in expression.expressions() {
    args.push(e)
  }
  self.func("XMLELEMENT", args)
}

///|
pub fn Generator::xmlkeyvalueoption_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let expr = g_around("(", self.sql_key(expression, "expression"), ")")
  this + expr
}

///|
pub fn Generator::xmltable_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let namespaces = g_around(
    "XMLNAMESPACES(",
    self.expressions(expression~, key="namespaces"),
    "), ",
  )
  let passing = self.expressions(expression~, key="passing")
  let passing = if passing != "" {
    self.sep() + "PASSING" + self.seg(passing)
  } else {
    ""
  }
  let columns = self.expressions(expression~, key="columns")
  let columns = if columns != "" {
    self.sep() + "COLUMNS" + self.seg(columns)
  } else {
    ""
  }
  let by_ref = if expression.has("by_ref") {
    self.sep() + "RETURNING SEQUENCE BY REF"
  } else {
    ""
  }
  "XMLTABLE(" +
  self.sep(sep="") +
  self.indent(namespaces + this + passing + by_ref + columns) +
  self.seg(")", sep="")
}

///|
pub fn Generator::xmlnamespace_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  match expression.this() {
    Some(t) if t.kind.is_a(Alias) => this
    _ => "DEFAULT " + this
  }
}

///|
pub fn Generator::parameterizedagg_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let params = self.expressions(expression~, key="params", flat=true)
  self.func(expression.name(), g_fargs(expression.expressions())) +
  "(" +
  params +
  ")"
}

///|
pub fn Generator::anonymousaggfunc_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.func(expression.name(), g_fargs(expression.expressions()))
}

///|
pub fn Generator::combinedaggfunc_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.call_method(AnonymousAggFunc, expression)
}

///|
pub fn Generator::combinedparameterizedagg_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.call_method(ParameterizedAgg, expression)
}

///|
pub fn Generator::translatecharacters_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let expr = self.sql_key(expression, "expression")
  let with_error = g_when(expression.has("with_error"), " WITH ERROR")
  "TRANSLATE(" + this + " USING " + expr + with_error + ")"
}

///|
pub fn Generator::decodecase_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if self.cfg.supports_decode_case {
    return self.func("DECODE", g_fargs(expression.expressions()))
  }
  let all = expression.expressions()
  let decode_expr = all[0]
  let expressions = all[1:].to_owned()
  let ifs = []
  let mut i = 0
  while i + 1 < expressions.length() {
    let mut search = expressions[i]
    let result = expressions[i + 1]
    if search.kind.is_a(Literal) {
      ifs.push(
        mk(If, [("this", exp_eq(decode_expr, search)), ("true", result)]),
      )
    } else if search.kind.is_a(Null) {
      ifs.push(
        mk(If, [("this", exp_is(decode_expr, mk0(Null))), ("true", result)]),
      )
    } else {
      if search.kind.is_a(Binary) {
        search = exp_paren(search)
      }
      let cond = exp_or(
        [
          exp_eq(decode_expr, search),
          exp_and(
            [exp_is(decode_expr, mk0(Null)), exp_is(search, mk0(Null))],
            copy=false,
          ),
        ],
        copy=false,
      )
      ifs.push(mk(If, [("this", cond), ("true", result)]))
    }
    i += 2
  }
  let default : Expr? = if expressions.length() % 2 == 1 {
    Some(expressions[expressions.length() - 1])
  } else {
    None
  }
  self.sql(Some(mk(Case, [("ifs", ifs), ("default", default)])))
}

///|
pub fn Generator::semanticview_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.seg(self.sql_key(expression, "this"), sep="")
  let dimensions = self.expressions(
    expression~,
    key="dimensions",
    dynamic=true,
    skip_first=true,
    skip_last=true,
  )
  let dimensions = if dimensions != "" {
    self.seg("DIMENSIONS " + dimensions)
  } else {
    ""
  }
  let metrics = self.expressions(
    expression~,
    key="metrics",
    dynamic=true,
    skip_first=true,
    skip_last=true,
  )
  let metrics = if metrics != "" { self.seg("METRICS " + metrics) } else { "" }
  let facts = self.expressions(
    expression~,
    key="facts",
    dynamic=true,
    skip_first=true,
    skip_last=true,
  )
  let facts = if facts != "" { self.seg("FACTS " + facts) } else { "" }
  let where_ = self.sql_key(expression, "where")
  let where_ = if where_ != "" { self.seg("WHERE " + where_) } else { "" }
  let body = self.indent(
    this + metrics + dimensions + facts + where_,
    skip_first=true,
  )
  "SEMANTIC_VIEW(" + body + self.seg(")", sep="")
}

///|
pub fn Generator::getextract_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut this = expression.this_()
  let expr = expression.expression()
  if this.get_type() is None || expression.get_type() is None {
    this = gen_annotate_types(this, self.dialect)
  }
  if this.is_type([DType::ARRAY, DType::MAP]) {
    return self.sql(
      Some(
        mk(Bracket, [
          ("this", Some(this)),
          (
            "expressions",
            match expr {
              Some(x) => [x]
              None => []
            },
          ),
        ]),
      ),
    )
  }
  self.sql(
    Some(
      mk(JSONExtract, [
        ("this", Some(this)),
        ("expression", self.dialect.to_json_path(expr)),
      ]),
    ),
  )
}

///|
pub fn Generator::datefromunixdate_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql(
    Some(
      mk(DateAdd, [
        ("this", exp_cast(literal_string("1970-01-01"), DType::DATE)),
        ("expression", expression.this_()),
        ("unit", var_("DAY")),
      ]),
    ),
  )
}

///|
pub fn Generator::space_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.sql(
    Some(
      mk(Repeat, [
        ("this", Some(Node(literal_string(" ")))),
        ("times", expression.get("this")),
      ]),
    ),
  )
}

///|
pub fn Generator::modelattribute_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  ignore(expression)
  self.unsupported("The model!attribute syntax is not supported")
  ""
}

///|
pub fn Generator::directorystage_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.func("DIRECTORY", [expression.get("this")])
}

///|
pub fn Generator::uuid_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let is_string = expression.has("is_string")
  let uuid_func_sql = self.func("UUID", [])
  if is_string && !self.dialect.cfg.uuid_is_string_type {
    return self.sql(
      Some(
        exp_cast(
          maybe_parse_str(uuid_func_sql, dialect=self.dialect),
          DType::VARCHAR,
          dialect=self.dialect,
        ),
      ),
    )
  }
  uuid_func_sql
}

///|
pub fn Generator::initcap_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut delimiters = expression.expression()
  match delimiters {
    Some(d) =>
      if d.is_string() &&
        d.text("this") == self.dialect.cfg.initcap_default_delimiter_chars {
        delimiters = None
      } else if !self.dialect.cfg.initcap_supports_custom_delimiters {
        self.unsupported("INITCAP does not support custom delimiters")
        delimiters = None
      }
    None => ()
  }
  self.func("INITCAP", [expression.arg("this"), delimiters])
}

///|
pub fn Generator::localtime_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  match expression.get("this") {
    Some(v) if v.truthy() => self.func("LOCALTIME", [v])
    _ => "LOCALTIME"
  }
}

///|
pub fn Generator::localtimestamp_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  match expression.get("this") {
    Some(v) if v.truthy() => self.func("LOCALTIMESTAMP", [v])
    _ => "LOCALTIMESTAMP"
  }
}

///|
pub fn Generator::weekstart_name(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = match expression.this() {
    Some(t) => py_upper(t.name())
    None => ""
  }
  let dow_from_week_start_day = gen_week_start_day_to_dow.get(this)
  let dow_from_week_offset = gen_week_offset_to_dow(self.dialect.cfg.week_offset)
  if dow_from_week_start_day != Some(dow_from_week_offset) {
    self.unsupported(
      "WEEK(\{this}) is not supported; falling back to the default week start day",
    )
  }
  "WEEK"
}

///|
pub fn Generator::weekstart_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let name = self.weekstart_name(expression)
  match expression.parent {
    Some(p) if p.kind.is_a(DateTrunc) => self.sql(Some(literal_string(name)))
    _ => name
  }
}

///|
/// Virtual `chr_sql(expression, name)`.
pub fn Generator::chr_sql_v(
  self : Generator,
  expression : Expr,
  name? : String = "CHR",
) -> String raise SqlglotError {
  match self.fns.hooks.chr_sql {
    Some(f) => f(self, expression, name)
    None => self.chr_sql(expression, name~)
  }
}

///|
pub fn Generator::chr_sql(
  self : Generator,
  expression : Expr,
  name? : String = "CHR",
) -> String raise SqlglotError {
  let this = self.expressions(expression~)
  let charset = self.sql_key(expression, "charset")
  let using_ = g_pfx(" USING ", charset)
  self.func(name, [this + using_])
}

///|
pub fn Generator::block_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let expressions = self.expressions(expression~, sep="; ", flat=true)
  let begin = g_when(expression.has("begin"), "BEGIN ")
  if expressions != "" {
    begin + expressions
  } else {
    ""
  }
}

///|
pub fn Generator::unsupported_stmt(
  self : Generator,
  message : String,
) -> String raise SqlglotError {
  self.unsupported(message)
  ""
}

///|
pub fn Generator::functionspecification_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  ignore(expression)
  self.unsupported_stmt("Unsupported Inline UDFs syntax")
}

///|
pub fn Generator::storedprocedure_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  ignore(expression)
  self.unsupported_stmt("Unsupported Stored Procedure syntax")
}

///|
pub fn Generator::ifblock_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  ignore(expression)
  self.unsupported_stmt("Unsupported If block syntax")
}

///|
pub fn Generator::casestatement_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  ignore(expression)
  self.unsupported_stmt("Unsupported Case statement syntax")
}

///|
pub fn Generator::whileblock_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  ignore(expression)
  self.unsupported_stmt("Unsupported While block syntax")
}

///|
pub fn Generator::loopblock_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  ignore(expression)
  self.unsupported_stmt("Unsupported Loop block syntax")
}

///|
pub fn Generator::repeatblock_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  ignore(expression)
  self.unsupported_stmt("Unsupported Repeat block syntax")
}

///|
pub fn Generator::leave_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  ignore(expression)
  self.unsupported_stmt("Unsupported Leave syntax")
}

///|
pub fn Generator::iterate_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  ignore(expression)
  self.unsupported_stmt("Unsupported Iterate syntax")
}

///|
pub fn Generator::execute_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  ignore(expression)
  self.unsupported_stmt("Unsupported Execute syntax")
}

///|
pub fn Generator::executesql_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  ignore(expression)
  self.unsupported_stmt("Unsupported Execute syntax")
}

///|
pub fn Generator::userdefinedfunction_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let expressions = self.no_identify(() => self.expressions(expression~))
  let expressions = if expression.has("wrapped") {
    self.wrap_str(expressions)
  } else {
    " " + expressions
  }
  if py_strip(expressions) != "" {
    this + expressions
  } else {
    this
  }
}

///|
pub fn Generator::macrooverloads_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.expressions(expression~, flat=true)
}

///|
pub fn Generator::macrooverload_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let params = self.no_identify(() => self.expressions(expression~, flat=true))
  let body = self.sql_key(expression, "this")
  let prefix = g_when(expression.has("is_table"), "TABLE ")
  "(" + params + ") AS " + prefix + body
}

///|
pub fn Generator::joinhint_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let expressions = self.expressions(expression~, flat=true)
  this + "(" + expressions + ")"
}

///|
pub fn Generator::tag_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  py_str_value(expression.get("prefix")) +
  self.sql(expression.this()) +
  py_str_value(expression.get("postfix"))
}

///|
pub fn Generator::nextvaluefor_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let order = match expression.arg("order") {
    Some(o) => " OVER (" + self.order_sql(o, flat=true) + ")"
    None => ""
  }
  "NEXT VALUE FOR " + self.sql_key(expression, "this") + order
}

///|
pub fn Generator::hex_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut text = self.func(self.cfg.hex_func, [self.sql_key(expression, "this")])
  if self.dialect.cfg.hex_lowercase {
    text = self.func("LOWER", [text])
  }
  text
}

///|
pub fn Generator::lowerhex_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut text = self.func(self.cfg.hex_func, [self.sql_key(expression, "this")])
  if !self.dialect.cfg.hex_lowercase {
    text = self.func("LOWER", [text])
  }
  text
}

///|
pub fn Generator::dynamicidentifier_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = expression.this()
  match this {
    Some(t) if t.is_string() => {
      let resolved = self.dialect.generate_sql(maybe_parse_str(t.name()))
      if expression.has_key("expressions") {
        self.unsupported(
          "Transpiling dynamically-invoked IDENTIFIER() functions is unsupported",
        )
      }
      return resolved
    }
    _ => ()
  }
  self.unsupported("IDENTIFIER() with non-literal arguments is not supported")
  self.func("IDENTIFIER", [this])
}

///|
/// Python `Dialect.generate(expression)` / `expression.sql(dialect)` with default options.
pub fn Dialect::generate_sql(
  self : Dialect,
  expression : Expr,
) -> String raise SqlglotError {
  Generator::new(self).generate(expression)
}

///|
pub fn Generator::booland_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  "((" +
  self.sql_key(expression, "this") +
  ") AND (" +
  self.sql_key(expression, "expression") +
  "))"
}

///|
pub fn Generator::boolor_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  "((" +
  self.sql_key(expression, "this") +
  ") OR (" +
  self.sql_key(expression, "expression") +
  "))"
}

///|
pub fn Generator::semicolon_sql(self : Generator, expression : Expr) -> String {
  ignore((self, expression))
  ""
}