// Generator methods for queries: SELECT, set operations, CTEs, joins, modifiers.

///|
/// The `@unsupported_args(...)` decorator check.
pub fn Generator::unsupported_args(
  self : Generator,
  expression : Expr,
  args : Array[String],
) -> Unit raise SqlglotError {
  for arg_name in args {
    if expression.has(arg_name) {
      self.unsupported(
        "Argument '\{arg_name}' is not supported for expression '\{expression.kind.name()}' when targeting \{self.dialect_class_name()}.",
      )
    }
  }
}

///|
pub fn Generator::prepend_ctes(
  self : Generator,
  expression : Expr,
  sql : String,
) -> String raise SqlglotError {
  let with_ = self.sql_key(expression, "with_")
  if with_ != "" {
    with_ + self.sep() + sql
  } else {
    sql
  }
}

///|
pub fn Generator::with_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let udfs = g_pfx("WITH ", self.expressions(expression~, key="udfs", flat=true))
  let sql = self.expressions(expression~, flat=true)
  let recursive = g_when(
    self.cfg.cte_recursive_keyword_required && expression.has("recursive"),
    "RECURSIVE ",
  )
  let search = g_pfx(" ", self.sql_key(expression, "search"))
  let sql = if sql != "" { "WITH " + recursive + sql + search } else { "" }
  if udfs != "" && sql != "" {
    udfs + " " + sql
  } else {
    udfs + sql
  }
}

///|
pub fn Generator::cte_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  match expression.arg("alias") {
    Some(alias_) => alias_.add_comments(Some(expression.pop_comments()))
    None => ()
  }
  let alias_sql = self.sql_key(expression, "alias")
  let materialized = match expression.get("materialized") {
    Some(Bool(false)) => "NOT MATERIALIZED "
    Some(v) if v.truthy() => "MATERIALIZED "
    _ => ""
  }
  let key_expressions = g_around(
    " USING KEY (",
    self.expressions(expression~, key="key_expressions", flat=true),
    ")",
  )
  alias_sql + key_expressions + " AS " + materialized + self.wrap(expression)
}

///|
pub fn Generator::tablealias_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut alias_ = self.sql_key(expression, "this")
  let mut columns = g_around(
    "(",
    self.expressions(expression~, key="columns", flat=true),
    ")",
  )
  if columns != "" &&
    !self.cfg.supports_table_alias_columns &&
    !(self.cfg.supports_named_cte_columns &&
    (match expression.parent {
      Some(p) => p.kind.is_a(CTE)
      None => false
    })) {
    columns = ""
    self.unsupported("Named columns are not supported in table alias.")
  }
  if alias_ == "" && !self.dialect.cfg.unnest_column_only {
    alias_ = (self.next_name)()
  }
  alias_ + columns
}

// ---------------------------------------------------------------------------
// Set operations

///|
pub fn Generator::set_operation(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let op_type = expression.kind
  let op_name = py_upper(op_type.key())
  let mut distinct : Bool? = match expression.get("distinct") {
    Some(Bool(b)) => Some(b)
    Some(v) => Some(v.truthy())
    None => None
  }
  if distinct == Some(false) &&
    (op_type == Except || op_type == Intersect) &&
    !self.cfg.except_intersect_support_all_clause {
    self.unsupported("\{op_name} ALL is not supported")
  }
  let default_distinct = match
    self.dialect.cfg.set_op_distinct_by_default.get(op_type) {
    Some(d) => d
    None => None
  }
  if distinct is None {
    distinct = default_distinct
    if distinct is None {
      self.unsupported("\{op_name} requires DISTINCT or ALL to be specified")
    }
  }
  let distinct_or_all = if distinct == default_distinct {
    ""
  } else if distinct == Some(true) {
    " DISTINCT"
  } else {
    " ALL"
  }
  let parts = []
  let side = py_upper(expression.text("side"))
  if side != "" {
    parts.push(side)
  }
  let kind = py_upper(expression.text("kind"))
  if kind != "" {
    parts.push(kind)
  }
  let side_kind = g_pfx("", parts.join(" "))
  let side_kind = if side_kind != "" { side_kind + " " } else { "" }
  let by_name = g_when(expression.has("by_name"), " BY NAME")
  let on = g_around(
    " ON (",
    self.expressions(expression~, key="on", flat=true),
    ")",
  )
  side_kind + op_name + distinct_or_all + by_name + on
}

///|
/// Virtual `set_operations`.
pub fn Generator::set_operations(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  match self.fns.hooks.set_operations {
    Some(f) => f(self, expression)
    None => self.set_operations_base(expression)
  }
}

///|
pub fn Generator::set_operations_base(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if !self.cfg.set_op_modifiers {
    if expression.has("limit") ||
      expression.has("order") ||
      expression.has("offset") {
      let sub = exp_subquery_of(expression, alias_="_l_0", copy=false)
      let select = self.move_ctes_to_top_level(
        exp_select_from([mk0(Star)], sub),
      )
      for arg in ["limit", "order", "offset"] {
        match expression.arg(arg) {
          Some(value) => select.set(arg, value.pop())
          None => ()
        }
      }
      return self.sql(Some(select))
    }
  }
  let sqls : Array[String] = []
  let stack : Array[BinItem] = [BNode(Some(expression))]
  while stack.pop() is Some(item) {
    match item {
      BNode(Some(node)) if node.kind.is_a(SetOperation) => {
        stack.push(BNode(node.arg("expression")))
        stack.push(
          BStr(
            self.maybe_comment(
              self.set_operation(node),
              comments?=node.comments,
              separated=true,
            ),
          ),
        )
        stack.push(BNode(node.arg("this")))
      }
      BNode(Some(node)) => {
        let mut node = node
        if !self.cfg.set_op_limits &&
          node.kind.is_a(Select) &&
          node.has("limit") {
          node = exp_subquery_of(node, copy=false)
          if !self.cfg.set_op_parenthesized_operands {
            node = exp_select_from([mk0(Star)], node)
          }
        }
        sqls.push(self.sql(Some(node)))
      }
      BNode(None) => sqls.push("")
      BStr(s) => sqls.push(s)
    }
  }
  let this = sqls.join(self.sep())
  let this = self.query_modifiers(expression, [this])
  self.prepend_ctes(expression, this)
}

// ---------------------------------------------------------------------------
// Limit / fetch / offset

///|
pub fn Generator::fetch_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let direction = match expression.get("direction") {
    Some(v) if v.truthy() => " " + py_str_value(Some(v))
    _ => ""
  }
  let count = g_pfx(" ", self.sql_key(expression, "count"))
  let limit_options = self.sql_key(expression, "limit_options")
  let limit_options = if limit_options != "" {
    limit_options
  } else {
    " ROWS ONLY"
  }
  self.seg("FETCH") + direction + count + limit_options
}

///|
pub fn Generator::limitoptions_sql(
  self : Generator,
  expression : Expr,
) -> String {
  ignore(self)
  let percent = g_when(expression.has("percent"), " PERCENT")
  let rows = g_when(expression.has("rows"), " ROWS")
  let mut with_ties = g_when(expression.has("with_ties"), " WITH TIES")
  if with_ties == "" && rows != "" {
    with_ties = " ONLY"
  }
  percent + rows + with_ties
}

///|
pub fn Generator::filter_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let where_ = py_strip(self.sql_key(expression, "expression"))
  this + " FILTER(" + where_ + ")"
}

///|
pub fn Generator::hint_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if !self.cfg.query_hints {
    self.unsupported("Hints are not supported")
    return ""
  }
  " /*+ " +
  py_strip(self.expressions(expression~, sep=self.cfg.query_hint_sep)) +
  " */"
}

///|
pub fn Generator::simplify_unless_literal(
  self : Generator,
  expression : Expr,
) -> Expr raise SqlglotError {
  if !expression.kind.is_a(Literal) {
    return gen_simplify(expression, self.dialect)
  }
  expression
}

///|
pub fn Generator::limit_sql(
  self : Generator,
  expression : Expr,
  top? : Bool = false,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let args = []
  for k in ["offset", "expression"] {
    match expression.arg(k) {
      Some(e) =>
        args.push(
          if self.cfg.limit_only_literals {
            self.simplify_unless_literal(e)
          } else {
            e
          },
        )
      None => ()
    }
  }
  let parts = []
  for e in args {
    parts.push(self.sql(Some(e)))
  }
  let mut args_sql = parts.join(", ")
  if top && args.iter().any(e => !e.is_number()) {
    args_sql = "(" + args_sql + ")"
  }
  let expressions = g_pfx(" BY ", self.expressions(expression~, flat=true))
  let limit_options = self.sql_key(expression, "limit_options")
  this +
  self.seg(if top { "TOP" } else { "LIMIT" }) +
  " " +
  args_sql +
  limit_options +
  expressions
}

///|
pub fn Generator::offset_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let value = match expression.arg("expression") {
    Some(v) =>
      Some(
        if self.cfg.limit_only_literals {
          self.simplify_unless_literal(v)
        } else {
          v
        },
      )
    None => None
  }
  let expressions = g_pfx(" BY ", self.expressions(expression~, flat=true))
  this + self.seg("OFFSET") + " " + self.sql(value) + expressions
}

// ---------------------------------------------------------------------------
// Tables

///|
/// Virtual `table_parts`.
pub fn Generator::table_parts(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  match self.fns.hooks.table_parts {
    Some(f) => f(self, expression)
    None => self.table_parts_base(expression)
  }
}

///|
pub fn Generator::table_parts_base(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let parts = []
  for k in ["catalog", "db", "this"] {
    match expression.get(k) {
      Some(v) => parts.push(self.sql_value(v))
      None => ()
    }
  }
  parts.join(".")
}

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

///|
pub fn Generator::table_sql(
  self : Generator,
  expression : Expr,
  sep? : String = " AS ",
) -> String raise SqlglotError {
  let mut table = self.table_parts(expression)
  let only = g_when(expression.has("only"), "ONLY ")
  let partition = g_pfx(" ", self.sql_key(expression, "partition"))
  let version = g_pfx(" ", self.sql_key(expression, "version"))
  let mut alias_ = g_pfx(sep, self.sql_key(expression, "alias"))
  let sample = self.sql_key(expression, "sample")
  let mut post_alias = ""
  let mut pre_alias = ""
  if self.dialect.cfg.alias_post_tablesample {
    pre_alias = sample
  } else {
    post_alias = sample
  }
  if self.dialect.cfg.alias_post_version {
    pre_alias = pre_alias + version
  } else {
    post_alias = post_alias + version
  }
  let hints = self.expressions(expression~, key="hints", sep=" ")
  let hints = if hints != "" && self.cfg.table_hints { " " + hints } else { "" }
  let pivots = self.expressions(expression~, key="pivots", sep="", flat=true)
  let joins = self.indent(
    self.expressions(expression~, key="joins", sep="", flat=true),
    skip_first=true,
  )
  let laterals = self.expressions(expression~, key="laterals", sep="")
  let mut file_format = self.sql_key(expression, "format")
  let pattern = self.sql_key(expression, "pattern")
  if file_format != "" {
    let pattern = g_pfx(", PATTERN => ", pattern)
    file_format = " (FILE_FORMAT => " + file_format + pattern + ")"
  } else if pattern != "" {
    file_format = " (PATTERN => " + pattern + ")"
  }
  let mut ordinality = ""
  if expression.has("ordinality") {
    ordinality = " WITH ORDINALITY" + alias_
    alias_ = ""
  }
  let when_ = self.sql_key(expression, "when")
  if when_ != "" {
    if self.cfg.historical_data_post_alias {
      alias_ = alias_ + " " + when_
    } else {
      table = table + " " + when_
    }
  }
  let changes = g_pfx(" ", self.sql_key(expression, "changes"))
  let rows_from = self.expressions(expression~, key="rows_from")
  if rows_from != "" {
    table = "ROWS FROM " + self.wrap_str(rows_from)
  }
  let indexed = match expression.get("indexed") {
    Some(v) =>
      if v.truthy() {
        " INDEXED BY " + self.sql_value(v)
      } else {
        " NOT INDEXED"
      }
    None => ""
  }
  only +
  table +
  changes +
  partition +
  file_format +
  pre_alias +
  alias_ +
  indexed +
  hints +
  pivots +
  post_alias +
  joins +
  laterals +
  ordinality
}

///|
pub fn Generator::tablefromrows_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let table = self.func("TABLE", [expression.get("this")])
  let alias_ = g_pfx(" AS ", self.sql_key(expression, "alias"))
  let sample = self.sql_key(expression, "sample")
  let pivots = self.expressions(expression~, key="pivots", sep="", flat=true)
  let joins = self.indent(
    self.expressions(expression~, key="joins", sep="", flat=true),
    skip_first=true,
  )
  table + alias_ + pivots + sample + joins
}

///|
/// Virtual `tablesample_sql(expression, tablesample_keyword)`.
pub fn Generator::tablesample_sql_v(
  self : Generator,
  expression : Expr,
  tablesample_keyword? : String,
) -> String raise SqlglotError {
  match self.fns.hooks.tablesample_sql {
    Some(f) => f(self, expression, tablesample_keyword)
    None => self.tablesample_sql(expression, tablesample_keyword?)
  }
}

///|
pub fn Generator::tablesample_sql(
  self : Generator,
  expression : Expr,
  tablesample_keyword? : String,
) -> String raise SqlglotError {
  let method_ = self.sql_key(expression, "method")
  let method_ = if method_ != "" && self.cfg.tablesample_with_method {
    method_ + " "
  } else {
    ""
  }
  let numerator = self.sql_key(expression, "bucket_numerator")
  let denominator = self.sql_key(expression, "bucket_denominator")
  let field = g_pfx(" ON ", self.sql_key(expression, "bucket_field"))
  let bucket = if numerator != "" {
    "BUCKET " + numerator + " OUT OF " + denominator + field
  } else {
    ""
  }
  let seed = g_around(
    " " + self.cfg.tablesample_seed_keyword + " (",
    self.sql_key(expression, "seed"),
    ")",
  )
  let mut size = self.sql_key(expression, "size")
  if size != "" && self.cfg.tablesample_size_is_rows {
    size = size + " ROWS"
  }
  let mut percent = self.sql_key(expression, "percent")
  if percent != "" && !self.dialect.cfg.tablesample_size_is_percent {
    percent = percent + " PERCENT"
  }
  let mut expr = bucket + percent + size
  if self.cfg.tablesample_requires_parens {
    expr = "(" + expr + ")"
  }
  let keyword = match tablesample_keyword {
    Some(k) if k != "" => k
    _ => self.cfg.tablesample_keywords
  }
  " " + keyword + " " + method_ + expr + seed
}

///|
fn g_value_bool_or(v : Value?, default : Bool) -> Bool {
  match v {
    Some(Bool(b)) => b
    Some(x) => x.truthy()
    None => default
  }
}

///|
/// Python `_pivot_in_value_aliases`.
pub fn Generator::pivot_in_value_aliases(
  self : Generator,
  expression : Expr,
) -> Array[Expr]? raise SqlglotError {
  let columns = expression.list("columns")
  let fields = expression.list("fields")
  if columns.is_empty() || fields.length() != 1 {
    return None
  }
  let pcfg = self.dialect.parser_cfg
  let tgt_identify_pivot_strings = pcfg.identify_pivot_strings
  let tgt_prefixed_pivot_columns = pcfg.prefixed_pivot_columns
  let tgt_pivot_column_naming = pcfg.pivot_column_naming
  let src_identify_pivot_strings = g_value_bool_or(
    expression.get("identify_pivot_strings"),
    tgt_identify_pivot_strings,
  )
  let src_prefixed_pivot_columns = g_value_bool_or(
    expression.get("prefixed_pivot_columns"),
    tgt_prefixed_pivot_columns,
  )
  let src_pivot_column_naming = match expression.get("pivot_column_naming") {
    Some(Str(s)) => s
    Some(v) => py_str_value(Some(v))
    None => tgt_pivot_column_naming
  }
  if src_identify_pivot_strings == tgt_identify_pivot_strings &&
    src_prefixed_pivot_columns == tgt_prefixed_pivot_columns &&
    src_pivot_column_naming == tgt_pivot_column_naming {
    return None
  }
  let in_exprs = fields[0].expressions()
  if in_exprs.is_empty() {
    return None
  }
  let step = columns.length() / in_exprs.length()
  let first_base = if src_identify_pivot_strings {
    expr_to_sql(in_exprs[0])
  } else {
    in_exprs[0].alias_or_name()
  }
  let first_stored = columns[0].name()
  if !first_stored.has_prefix(first_base) {
    return None
  }
  let suffix = g_str_from(first_stored, py_len(first_base))
  let target_has_suffix = (
      expression.expressions().length() > 1 ||
      tgt_pivot_column_naming != "agg_name_if_multiple"
    ) &&
    expression.expressions().iter().any(a => a.alias() != "")
  let source_has_suffix = suffix != ""
  let new_exprs = []
  let mut modified = false
  for val_idx, e in in_exprs {
    if e.kind.is_a(PivotAlias) {
      new_exprs.push(e)
      continue
    }
    let i = val_idx * step
    let stored_full = columns[i].name()
    let stored_value = if suffix != "" {
      substr(stored_full, 0, py_len(stored_full) - py_len(suffix))
    } else {
      stored_full
    }
    let target_value = if tgt_identify_pivot_strings {
      expr_to_sql(e)
    } else {
      e.alias_or_name()
    }
    if source_has_suffix && !target_has_suffix {
      new_exprs.push(
        mk(PivotAlias, [
          ("this", e),
          ("alias", to_identifier(stored_full, quoted=true)),
        ]),
      )
      modified = true
    } else if stored_value != target_value {
      new_exprs.push(
        mk(PivotAlias, [
          ("this", e),
          ("alias", to_identifier(stored_value, quoted=true)),
        ]),
      )
      modified = true
    } else {
      new_exprs.push(e)
    }
  }
  if modified {
    Some(new_exprs)
  } else {
    None
  }
}

///|
pub fn Generator::pivot_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let expressions = self.expressions(expression~, flat=true)
  let unpivot = expression.has("unpivot")
  let direction = if unpivot { "UNPIVOT" } else { "PIVOT" }
  let group = self.sql_key(expression, "group")
  if expression.has("this") {
    let this = self.sql_key(expression, "this")
    let sql = if expressions == "" {
      "UNPIVOT " + this
    } else {
      let on = self.seg("ON") + " " + expressions
      let into = self.sql_key(expression, "into")
      let into = if into != "" { self.seg("INTO") + " " + into } else { "" }
      let using_ = self.expressions(expression~, key="using", flat=true)
      let using_ = if using_ != "" {
        self.seg("USING") + " " + using_
      } else {
        ""
      }
      direction + " " + this + on + into + using_ + group
    }
    return self.prepend_ctes(expression, sql)
  }
  if !unpivot {
    match self.pivot_in_value_aliases(expression) {
      Some(new_field_exprs) =>
        expression.list("fields")[0].set("expressions", new_field_exprs)
      None => ()
    }
  }
  let mut alias_ = self.sql_key(expression, "alias")
  if alias_ != "" {
    alias_ = if self.cfg.pivot_alias_with_as {
      " AS " + alias_
    } else {
      " " + alias_
    }
  }
  let fields = self.expressions(
    expression~,
    key="fields",
    sep=" ",
    dynamic=true,
    new_line=true,
    skip_first=true,
    skip_last=true,
  )
  let nulls = match expression.get("include_nulls") {
    Some(v) => if v.truthy() { " INCLUDE NULLS " } else { " EXCLUDE NULLS " }
    None => ""
  }
  let default_on_null = g_around(
    " DEFAULT ON NULL (",
    self.sql_key(expression, "default_on_null"),
    ")",
  )
  let sql = self.seg(direction) +
    nulls +
    "(" +
    expressions +
    " FOR " +
    fields +
    default_on_null +
    group +
    ")" +
    alias_
  self.prepend_ctes(expression, sql)
}

///|
pub fn Generator::version_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = "FOR " + expression.name()
  let kind = expression.text("kind")
  let expr = self.sql_key(expression, "expression")
  this + " " + kind + " " + expr
}

///|
pub fn Generator::tuple_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  "(" +
  self.expressions(
    expression~,
    dynamic=true,
    new_line=true,
    skip_first=true,
    skip_last=true,
  ) +
  ")"
}

// ---------------------------------------------------------------------------
// DML

///|
/// Python `_update_from_joins_sql`: returns (join_sql, from_sql).
pub fn Generator::update_from_joins_sql(
  self : Generator,
  expression : Expr,
) -> (String, String) raise SqlglotError {
  let from_expr = match expression.arg("from_") {
    Some(f) if !self.cfg.update_statement_supports_from => f
    _ => return ("", self.sql_key(expression, "from_"))
  }
  match expression.this() {
    Some(target_table) if target_table.kind.is_a(Table) => {
      let target_name = to_identifier(target_table.alias_or_name())
      for eq in expression.expressions() {
        match eq.this() {
          Some(col) if col.kind.is_a(Column) && col.text("table") == "" =>
            col.set("table", target_name)
          _ => ()
        }
      }
    }
    _ => ()
  }
  let table = from_expr.this_()
  let nested_joins = table.list("joins")
  if !nested_joins.is_empty() {
    table.set("joins", null_arg)
  }
  let mut join_sql = self.sql(
    Some(mk(Join, [("this", table), ("on", true_())])),
  )
  for nested in nested_joins {
    if !nested.has("on") && !nested.has("using") {
      nested.set("on", true_())
    }
    join_sql = join_sql + self.sql(Some(nested))
  }
  (join_sql, "")
}

///|
pub fn Generator::update_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let hint = self.sql_key(expression, "hint")
  let this = self.sql_key(expression, "this")
  let (join_sql, from_sql) = self.update_from_joins_sql(expression)
  let set_sql = self.expressions(expression~, flat=true)
  let where_sql = self.sql_key(expression, "where")
  let returning = self.sql_key(expression, "returning")
  let order = self.sql_key(expression, "order")
  let limit = self.sql_key(expression, "limit")
  let expression_sql = if self.cfg.returning_end {
    from_sql + where_sql + returning
  } else {
    returning + from_sql + where_sql
  }
  let options = g_around(
    " OPTION(",
    self.expressions(expression~, key="options"),
    ")",
  )
  let sql = "UPDATE" +
    hint +
    " " +
    this +
    join_sql +
    " SET " +
    set_sql +
    expression_sql +
    order +
    limit +
    options
  self.prepend_ctes(expression, sql)
}

///|
/// Virtual `values_sql(expression, values_as_table)`.
pub fn Generator::values_sql_v(
  self : Generator,
  expression : Expr,
  values_as_table? : Bool = true,
) -> String raise SqlglotError {
  match self.fns.hooks.values_sql {
    Some(f) => f(self, expression, values_as_table)
    None => self.values_sql(expression, values_as_table~)
  }
}

///|
pub fn Generator::values_sql(
  self : Generator,
  expression : Expr,
  values_as_table? : Bool = true,
) -> String raise SqlglotError {
  let values_as_table = values_as_table && self.cfg.values_as_table
  if values_as_table || expression.find_ancestor([From, Join]) is None {
    let args = self.expressions(expression~)
    let alias_ = self.sql_key(expression, "alias")
    let mut values = "VALUES" + self.seg("") + args
    if self.cfg.wrap_derived_values &&
      (
        alias_ != "" ||
        (match expression.parent {
          Some(p) => p.kind.is_any([From, Table])
          None => false
        })
      ) {
      values = "(" + values + ")"
    }
    values = self.query_modifiers(expression, [values])
    return if alias_ != "" { values + " AS " + alias_ } else { values }
  }
  let alias_node = expression.arg("alias")
  let column_names = match alias_node {
    Some(a) => a.raw_list("columns")
    None => []
  }
  let selects = []
  for i, tup in expression.expressions() {
    let mut row = tup.expressions()
    if i == 0 && !column_names.is_empty() {
      let aliased = []
      let n = if row.length() < column_names.length() {
        row.length()
      } else {
        column_names.length()
      }
      for j in 0.. a.this()
      None => None
    }
    return self.call_method(
      Subquery,
      exp_subquery_with_alias_expr(query, alias_this, copy=false),
    )
  }
  let alias_ = match alias_node {
    Some(a) => " AS " + self.sql_key(a, "this")
    None => ""
  }
  let parts = []
  for select in selects {
    parts.push(self.sql(Some(select)))
  }
  "(" + parts.join(" UNION ALL ") + ")" + alias_
}

///|
pub fn Generator::into_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.unsupported_args(expression, ["expressions"])
  let temporary = g_when(expression.has("temporary"), " TEMPORARY")
  let unlogged = g_when(expression.has("unlogged"), " UNLOGGED")
  self.seg("INTO") +
  (if temporary != "" { temporary } else { unlogged }) +
  " " +
  self.sql_key(expression, "this")
}

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

///|
pub fn Generator::groupingsets_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let grouping_sets = self.expressions(expression~, indent=false)
  "GROUPING SETS " + self.wrap_str(grouping_sets)
}

///|
pub fn Generator::rollup_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let expressions = self.expressions(expression~, indent=false)
  if expressions != "" {
    "ROLLUP " + self.wrap_str(expressions)
  } else {
    "WITH ROLLUP"
  }
}

///|
pub fn Generator::rollupindex_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let columns = self.expressions(expression~, flat=true)
  let from_sql = g_pfx(" FROM ", self.sql_key(expression, "from_index"))
  let properties_sql = match expression.arg("properties") {
    Some(p) => " " + self.properties(p, prefix="PROPERTIES")
    None => ""
  }
  this + "(" + columns + ")" + from_sql + properties_sql
}

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

///|
pub fn Generator::cube_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let expressions = self.expressions(expression~, indent=false)
  if expressions != "" {
    "CUBE " + self.wrap_str(expressions)
  } else {
    "WITH CUBE"
  }
}

///|
pub fn Generator::group_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let modifier = match expression.get("all") {
    Some(Bool(true)) => " ALL"
    Some(Bool(false)) => " DISTINCT"
    _ => ""
  }
  let mut group_by = self.op_expressions("GROUP BY" + modifier, expression)
  let grouping_sets = self.expressions(expression~, key="grouping_sets")
  let cube = self.expressions(expression~, key="cube")
  let rollup = self.expressions(expression~, key="rollup")
  let groupings = csv(
    [
      if grouping_sets != "" {
        self.seg(grouping_sets)
      } else {
        ""
      },
      if cube != "" {
        self.seg(cube)
      } else {
        ""
      },
      if rollup != "" {
        self.seg(rollup)
      } else {
        ""
      },
      if expression.has("totals") {
        self.seg("WITH TOTALS")
      } else {
        ""
      },
    ],
    sep=self.cfg.groupings_sep,
  )
  let stripped = py_strip(groupings)
  if !expression.expressions().is_empty() &&
    groupings != "" &&
    stripped != "WITH CUBE" &&
    stripped != "WITH ROLLUP" {
    let mut add_separator = true
    if grouping_sets != "" {
      if self.cfg.supports_grouping_sets_as_suffix {
        add_separator = false
      } else {
        self.unsupported(
          "GROUPING SETS without a comma after GROUP BY expressions is not supported",
        )
      }
    }
    if add_separator {
      group_by = group_by + self.cfg.groupings_sep
    }
  }
  group_by + groupings
}

///|
pub fn Generator::having_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.indent(self.sql_key(expression, "this"))
  self.seg("HAVING") + self.sep() + this
}

///|
pub fn Generator::connect_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let start = self.sql_key(expression, "start")
  let start = if start != "" { self.seg("START WITH " + start) } else { "" }
  let nocycle = g_when(expression.has("nocycle"), " NOCYCLE")
  let connect = self.sql_key(expression, "connect")
  let connect = self.seg("CONNECT BY" + nocycle + " " + connect)
  start + connect
}

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

///|
pub fn Generator::join_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let kind = py_upper(expression.text("kind"))
  let side = if !self.cfg.semi_anti_join_with_side &&
    (kind == "SEMI" || kind == "ANTI") {
    ""
  } else {
    py_upper(expression.text("side"))
  }
  let ops = [
    py_upper(expression.text("method")),
    g_when(expression.has("global_"), "GLOBAL"),
    side,
    kind,
    if self.cfg.join_hints {
      py_upper(expression.text("hint"))
    } else {
      ""
    },
    g_when(expression.has("directed") && self.cfg.directed_joins, "DIRECTED"),
  ]
  let mut op_sql = csv(ops, sep=" ")
  let match_cond = g_around(
    " MATCH_CONDITION (",
    self.sql_key(expression, "match_condition"),
    ")",
  )
  let mut on_sql = self.sql_key(expression, "on")
  let using_ = expression.raw_list("using")
  if on_sql == "" && !using_.is_empty() {
    let parts = []
    for column in using_ {
      parts.push(self.sql_value(column))
    }
    on_sql = csv(parts)
  }
  let this = expression.this()
  let mut this_sql = self.sql(this)
  let exprs = self.expressions(expression~)
  if exprs != "" {
    this_sql = this_sql + "," + self.seg(exprs)
  }
  if on_sql != "" {
    on_sql = self.indent(on_sql, skip_first=true)
    let space = if self.pretty { self.seg(" ".repeat(self.pad)) } else { " " }
    if !using_.is_empty() {
      on_sql = space + "USING (" + on_sql + ")"
    } else {
      on_sql = space + "ON " + on_sql
    }
  } else if op_sql == "" {
    match this {
      Some(t) if t.kind.is_a(Lateral) && t.get("cross_apply") is Some(_) =>
        return " " + this_sql
      _ => ()
    }
    return ", " + this_sql
  }
  if op_sql != "STRAIGHT_JOIN" {
    op_sql = if op_sql != "" { op_sql + " JOIN" } else { "JOIN" }
  }
  let pivots = self.expressions(expression~, key="pivots", sep="", flat=true)
  self.seg(op_sql) + " " + this_sql + match_cond + on_sql + pivots
}

///|
/// Virtual `lateral_op`.
pub fn Generator::lateral_op(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  match self.fns.hooks.lateral_op {
    Some(f) => f(self, expression)
    None => self.lateral_op_base(expression)
  }
}

///|
pub fn Generator::lateral_op_base(
  self : Generator,
  expression : Expr,
) -> String {
  ignore(self)
  let op = match expression.get("cross_apply") {
    Some(Bool(true)) => "INNER JOIN "
    Some(Bool(false)) => "LEFT JOIN "
    _ => ""
  }
  op + "LATERAL"
}

///|
pub fn Generator::lateral_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  if expression.has("view") {
    let alias_ = expression.arg("alias")
    let columns = match alias_ {
      Some(a) => self.expressions(expression=a, key="columns", flat=true)
      None => ""
    }
    let alias_name = match alias_ {
      Some(a) => a.name()
      None => ""
    }
    let table = g_pfx(" ", alias_name)
    let columns = g_pfx(" AS ", columns)
    let op_sql = self.seg(
      "LATERAL VIEW" + g_when(expression.has("outer"), " OUTER"),
    )
    return op_sql + self.sep() + this + table + columns
  }
  let mut table_alias = expression.arg("alias")
  let this_expr = expression.this()
  let offset = match this_expr {
    Some(t) => t.arg("offset")
    None => None
  }
  match (table_alias, this_expr, offset) {
    (Some(ta), Some(t), Some(off)) if self.cfg.unnest_with_ordinality &&
      t.kind.is_a(Unnest) &&
      off.kind.is_a(Identifier) => {
      let ta = ta.copy()
      ta.append("columns", off.copy())
      table_alias = Some(ta)
    }
    _ => ()
  }
  let mut alias_ = g_pfx(" AS ", self.sql(table_alias))
  let mut ordinality = ""
  if expression.has("ordinality") {
    ordinality = " WITH ORDINALITY" + alias_
    alias_ = ""
  }
  self.lateral_op(expression) + " " + this + alias_ + ordinality
}

///|
pub fn Generator::setitem_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut kind = self.sql_key(expression, "kind")
  if !self.cfg.set_assignment_requires_variable_keyword && kind == "VARIABLE" {
    kind = ""
  } else {
    kind = g_pfx("", kind)
    if kind != "" {
      kind = kind + " "
    }
  }
  let this = self.sql_key(expression, "this")
  let expressions = self.expressions(expression~)
  let collate = g_pfx(" COLLATE ", self.sql_key(expression, "collate"))
  let global_ = g_when(expression.has("global_"), "GLOBAL ")
  global_ + kind + this + expressions + collate
}

///|
pub fn Generator::set_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let expressions = " " + self.expressions(expression~, flat=true)
  let tag = g_when(expression.has("tag"), " TAG")
  (if expression.has("unset") { "UNSET" } else { "SET" }) + tag + expressions
}

///|
pub fn Generator::queryband_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let update = g_when(expression.has("update"), " UPDATE")
  let scope = g_pfx(" FOR ", self.sql_key(expression, "scope"))
  "QUERY_BAND = " + this + update + scope
}

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

///|
pub fn Generator::lock_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if !self.cfg.locking_reads_supported {
    self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported")
    return ""
  }
  let update = expression.has("update")
  let key = expression.has("key")
  let lock_type = if update {
    if key {
      "FOR NO KEY UPDATE"
    } else {
      "FOR UPDATE"
    }
  } else if key {
    "FOR KEY SHARE"
  } else {
    "FOR SHARE"
  }
  let expressions = g_pfx(" OF ", self.expressions(expression~, flat=true))
  let wait = match expression.get("wait") {
    Some(Node(w)) if w.kind.is_a(Literal) => " WAIT " + self.sql(Some(w))
    Some(v) => if v.truthy() { " NOWAIT" } else { " SKIP LOCKED" }
    None => ""
  }
  lock_type + expressions + wait
}

///|
pub fn Generator::order_sql(
  self : Generator,
  expression : Expr,
  flat? : Bool = false,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let this = if this != "" { this + " " } else { this }
  let siblings = g_when(expression.has("siblings"), "SIBLINGS ")
  self.op_expressions(
    this + "ORDER " + siblings + "BY",
    expression,
    flat=this != "" || flat,
  )
}

///|
pub fn Generator::withfill_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let from_sql = g_pfx(" FROM ", self.sql_key(expression, "from_"))
  let to_sql = g_pfx(" TO ", self.sql_key(expression, "to"))
  let step_sql = g_pfx(" STEP ", self.sql_key(expression, "step"))
  let interpolated_values = []
  for e in expression.list("interpolate") {
    interpolated_values.push(
      if e.kind.is_a(Alias) {
        self.sql_key(e, "alias") + " AS " + self.sql_key(e, "this")
      } else {
        self.sql_key(e, "this")
      },
    )
  }
  let interpolate = if interpolated_values.is_empty() {
    ""
  } else {
    " INTERPOLATE (" + interpolated_values.join(", ") + ")"
  }
  "WITH FILL" + from_sql + to_sql + step_sql + interpolate
}

///|
pub fn Generator::cluster_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.op_expressions("CLUSTER BY", expression)
}

///|
pub fn Generator::clusterproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if expression.has("this") {
    self.unsupported(
      "Unsupported CLUSTER BY " + self.sql_key(expression, "this"),
    )
    return ""
  }
  "CLUSTER BY (" + self.expressions(expression~, flat=true) + ")"
}

///|
pub fn Generator::distribute_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.op_expressions("DISTRIBUTE BY", expression)
}

///|
pub fn Generator::sort_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.op_expressions("SORT BY", expression)
}

///|
/// Python `_resolve_ordered_for_null_ordering_simulation`.
pub fn Generator::resolve_ordered_for_null_ordering_simulation(
  self : Generator,
  expression : Expr,
) -> Expr? {
  ignore(self)
  let this = match expression.this() {
    Some(t) if t.kind.is_a(Column) && t.text("table") == "" => t
    _ => return None
  }
  let ancestor = match expression.find_ancestor([Select, Window]) {
    Some(a) if a.kind.is_a(Select) => a
    _ => return None
  }
  let column_name = this.name()
  let matched = []
  for p in ancestor.expressions() {
    if p.output_name() == column_name {
      matched.push(if p.kind.is_a(Alias) { p.this_() } else { p })
    }
  }
  let m = if matched.length() == 1 { Some(matched[0]) } else { None }
  match m {
    Some(x) if x.kind.is_a(Column) &&
      x.text("table") == "" &&
      x.name() == column_name => None
    _ => m
  }
}

///|
pub fn Generator::ordered_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let desc_v = expression.get("desc")
  let desc = match desc_v {
    Some(v) => v.truthy()
    None => false
  }
  let asc = !desc
  let nulls_first = expression.has("nulls_first")
  let nulls_last = !nulls_first
  let null_ordering = self.dialect.cfg.null_ordering
  let nulls_are_large = null_ordering == "nulls_are_large"
  let nulls_are_small = null_ordering == "nulls_are_small"
  let nulls_are_last = null_ordering == "nulls_are_last"
  let mut this = self.sql_key(expression, "this")
  let sort_order = if desc {
    " DESC"
  } else if desc_v is Some(Bool(false)) {
    " ASC"
  } else {
    ""
  }
  let mut nulls_sort_change = ""
  if nulls_first &&
    ((asc && nulls_are_large) || (desc && nulls_are_small) || nulls_are_last) {
    nulls_sort_change = " NULLS FIRST"
  } else if nulls_last &&
    ((asc && nulls_are_small) || (desc && nulls_are_large)) &&
    !nulls_are_last {
    nulls_sort_change = " NULLS LAST"
  }
  let null_ordering_supported = self.cfg.null_ordering_supported
  if nulls_sort_change != "" && null_ordering_supported != Some(true) {
    let window = expression.find_ancestor([Window, Select])
    let (window_this, spec) = match window {
      Some(w) if w.kind.is_a(Window) => {
        let mut wt = w.this()
        match wt {
          Some(x) if x.kind.is_any([IgnoreNulls, RespectNulls]) => wt = x.this()
          _ => ()
        }
        (wt, w.arg("spec"))
      }
      _ => (None, None)
    }
    let supports = match window_this {
      Some(wt) =>
        wt.kind.is_any(self.cfg.window_funcs_with_null_ordering) &&
        (match spec {
          None => true
          Some(s) => py_upper(s.text("kind")) == "ROWS"
        })
      None => false
    }
    if !supports {
      if window_this is Some(wt) && spec is Some(_) {
        self.unsupported(
          "'\{py_strip(nulls_sort_change)}' translation not supported in window function \{wt.kind.sql_name()}",
        )
        nulls_sort_change = ""
      } else if null_ordering_supported == Some(false) &&
        (
          (asc && nulls_sort_change == " NULLS LAST") ||
          (desc && nulls_sort_change == " NULLS FIRST")
        ) {
        let mut ancestor = expression.find_ancestor([AggFunc, Window, Select])
        match ancestor {
          Some(a) if a.kind.is_a(Window) => ancestor = a.this()
          _ => ()
        }
        match ancestor {
          Some(a) if a.kind.is_a(AggFunc) => {
            self.unsupported(
              "'\{py_strip(nulls_sort_change)}' translation not supported for aggregate function \{a.kind.sql_name()} with \{sort_order} sort order",
            )
            nulls_sort_change = ""
          }
          _ => ()
        }
      } else if null_ordering_supported is None {
        let this_expr = expression.this_()
        if this_expr.is_int() {
          self.unsupported(
            "'\{py_strip(nulls_sort_change)}' translation not supported with positional ordering",
          )
        } else if !this_expr.kind.is_a(Rand) {
          let resolved = self.resolve_ordered_for_null_ordering_simulation(
            expression,
          )
          let target = match resolved {
            Some(r) => self.sql(Some(r))
            None => this
          }
          let null_sort_order = if nulls_sort_change == " NULLS FIRST" {
            " DESC"
          } else {
            ""
          }
          this = "CASE WHEN \{target} IS NULL THEN 1 ELSE 0 END\{null_sort_order}, \{target}"
        }
        nulls_sort_change = ""
      }
    }
  }
  let with_fill = g_pfx(" ", self.sql_key(expression, "with_fill"))
  this + sort_order + nulls_sort_change + with_fill
}

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

///|
pub fn Generator::matchrecognize_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let partition = self.partition_by_sql(expression)
  let order = self.sql_key(expression, "order")
  let measures = self.expressions(expression~, key="measures")
  let measures = if measures != "" {
    self.seg("MEASURES" + self.seg(measures))
  } else {
    ""
  }
  let rows = self.sql_key(expression, "rows")
  let rows = if rows != "" { self.seg(rows) } else { "" }
  let after = self.sql_key(expression, "after")
  let after = if after != "" { self.seg(after) } else { "" }
  let pattern = self.sql_key(expression, "pattern")
  let pattern = if pattern != "" {
    self.seg("PATTERN (" + pattern + ")")
  } else {
    ""
  }
  let definition_sqls = []
  for definition in expression.list("define") {
    definition_sqls.push(
      self.sql_key(definition, "alias") +
      " AS " +
      self.sql_key(definition, "this"),
    )
  }
  let definitions = self.expressions(sqls=g_strs(definition_sqls))
  let define_ = if definitions != "" {
    self.seg("DEFINE" + self.seg(definitions))
  } else {
    ""
  }
  let body = partition + order + measures + rows + after + pattern + define_
  let alias_ = g_pfx(" ", self.sql_key(expression, "alias"))
  self.seg("MATCH_RECOGNIZE") + " " + self.wrap_str(body) + alias_
}

///|
pub fn Generator::query_modifiers(
  self : Generator,
  expression : Expr,
  sqls : Array[String],
) -> String raise SqlglotError {
  let mut limit = expression.arg("limit")
  match limit {
    Some(l) if self.cfg.limit_fetch == "LIMIT" && l.kind.is_a(Fetch) => {
      let count = match l.arg("count") {
        Some(c) => c.copy()
        None => literal_int(1)
      }
      limit = Some(mk(Limit, [("expression", count)]))
    }
    Some(l) if self.cfg.limit_fetch == "FETCH" && l.kind.is_a(Limit) =>
      limit = Some(
        mk(Fetch, [
          ("direction", ("FIRST" : &IntoValue)),
          (
            "count",
            match l.arg("expression") {
              Some(e) => Some(e.copy())
              None => None
            },
          ),
        ]),
      )
    _ => ()
  }
  let parts = sqls.copy()
  for join in expression.list("joins") {
    parts.push(self.sql(Some(join)))
  }
  parts.push(self.sql_key(expression, "match"))
  for lateral in expression.list("laterals") {
    parts.push(self.sql(Some(lateral)))
  }
  parts.push(self.sql_key(expression, "prewhere"))
  parts.push(self.sql_key(expression, "where"))
  parts.push(self.sql_key(expression, "connect"))
  parts.push(self.sql_key(expression, "group"))
  parts.push(self.sql_key(expression, "having"))
  for gen in self.fns.after_having_modifier_transforms {
    parts.push((gen.1)(self, expression))
  }
  parts.push(self.sql_key(expression, "order"))
  let is_fetch = match limit {
    Some(l) => l.kind.is_a(Fetch)
    None => false
  }
  for s in self.offset_limit_modifiers(expression, is_fetch, limit) {
    parts.push(s)
  }
  for s in self.after_limit_modifiers(expression) {
    parts.push(s)
  }
  parts.push(self.sql_key(expression, "for_"))
  parts.push(self.options_modifier(expression))
  csv(parts, sep="")
}

///|
/// Virtual `options_modifier`.
pub fn Generator::options_modifier(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  match self.fns.hooks.options_modifier {
    Some(f) => f(self, expression)
    None => self.options_modifier_base(expression)
  }
}

///|
pub fn Generator::options_modifier_base(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  g_pfx(" ", self.expressions(expression~, key="options"))
}

///|
pub fn Generator::forclause_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let kind = py_str_value(expression.get("kind"))
  if kind == "BROWSE" {
    return self.sep() + "FOR BROWSE"
  }
  let options = self.expressions(expression~, key="expressions")
  if options == "" {
    return ""
  }
  self.sep() + "FOR " + kind + self.seg(options)
}

///|
pub fn Generator::queryoption_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  ignore(expression)
  self.unsupported("Unsupported query option.")
  ""
}

///|
/// Virtual `offset_limit_modifiers`.
pub fn Generator::offset_limit_modifiers(
  self : Generator,
  expression : Expr,
  fetch : Bool,
  limit : Expr?,
) -> Array[String] raise SqlglotError {
  match self.fns.hooks.offset_limit_modifiers {
    Some(f) => f(self, expression, fetch, limit)
    None => self.offset_limit_modifiers_base(expression, fetch, limit)
  }
}

///|
pub fn Generator::offset_limit_modifiers_base(
  self : Generator,
  expression : Expr,
  fetch : Bool,
  limit : Expr?,
) -> Array[String] raise SqlglotError {
  if fetch {
    [self.sql_key(expression, "offset"), self.sql(limit)]
  } else {
    [self.sql(limit), self.sql_key(expression, "offset")]
  }
}

///|
/// Virtual `after_limit_modifiers`.
pub fn Generator::after_limit_modifiers(
  self : Generator,
  expression : Expr,
) -> Array[String] raise SqlglotError {
  match self.fns.hooks.after_limit_modifiers {
    Some(f) => f(self, expression)
    None => self.after_limit_modifiers_base(expression)
  }
}

///|
pub fn Generator::after_limit_modifiers_base(
  self : Generator,
  expression : Expr,
) -> Array[String] raise SqlglotError {
  let locks = g_pfx(" ", self.expressions(expression~, key="locks", sep=" "))
  [locks, self.sql_key(expression, "sample")]
}

///|
pub fn Generator::select_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let into = expression.arg("into")
  if !self.cfg.supports_select_into {
    match into {
      Some(i) => i.pop() |> ignore
      None => ()
    }
  }
  let hint = self.sql_key(expression, "hint")
  let distinct = g_pfx(" ", self.sql_key(expression, "distinct"))
  let mut kind = self.sql_key(expression, "kind")
  let top = match expression.arg("limit") {
    Some(limit) if limit.kind.is_a(Limit) && self.cfg.limit_is_top => {
      let top = self.limit_sql(limit, top=true)
      limit.pop() |> ignore
      top
    }
    _ => ""
  }
  let mut expressions = self.expressions(expression~)
  if kind != "" {
    if self.cfg.select_kinds.contains(kind) {
      kind = " AS " + kind
    } else {
      if kind == "STRUCT" {
        let items = []
        for e in expression.expressions() {
          items.push(
            if e.kind.is_a(Alias) {
              mk(PropertyEQ, [
                ("this", e.get("alias")),
                ("expression", e.get("this")),
              ])
            } else {
              e
            },
          )
        }
        expressions = self.expressions(sqls=[
          Str(self.sql(Some(mk(Struct, [("expressions", items)])))),
        ])
      }
      kind = ""
    }
  }
  let operation_modifiers = g_pfx(
    self.sep(),
    self.expressions(expression~, key="operation_modifiers", sep=" "),
  )
  let exclude = expression.raw_list("exclude")
  if !self.cfg.star_exclude_requires_derived_table && !exclude.is_empty() {
    let exclude_sql = self.expressions(sqls=exclude, flat=true)
    expressions = expressions + self.seg("EXCLUDE") + " (" + exclude_sql + ")"
  }
  let top_distinct = if self.cfg.limit_is_top {
    distinct + hint + top
  } else {
    top + hint + distinct
  }
  let expressions = g_pfx(self.sep(), expressions)
  let mut sql = self.query_modifiers(expression, [
    "SELECT" + top_distinct + operation_modifiers + kind + expressions,
    self.sql_key(expression, "into"),
    self.sql_key(expression, "from_"),
  ])
  if expression.has("with_") {
    sql = self.maybe_comment(sql, expression~)
    expression.pop_comments() |> ignore
  }
  sql = self.prepend_ctes(expression, sql)
  if self.cfg.star_exclude_requires_derived_table && !exclude.is_empty() {
    expression.set("exclude", null_arg)
    let subquery = exp_subquery_of(expression, copy=false)
    let star = mk(Star, [("except_", exclude)])
    sql = self.sql(Some(exp_select_from([star], subquery)))
  }
  if !self.cfg.supports_select_into {
    match into {
      Some(into) => {
        let table_kind = if into.has("temporary") {
          " TEMPORARY"
        } else if self.cfg.supports_unlogged_tables && into.has("unlogged") {
          " UNLOGGED"
        } else {
          ""
        }
        sql = "CREATE" +
          table_kind +
          " TABLE " +
          self.sql(into.this()) +
          " AS " +
          sql
      }
      None => ()
    }
  }
  sql
}

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

///|
pub fn Generator::schema_columns_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if !expression.expressions().is_empty() {
    return "(" +
      self.sep(sep="") +
      self.expressions(expression~) +
      self.seg(")", sep="")
  }
  ""
}

///|
pub fn Generator::star_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let except_ = self.expressions(expression~, key="except_", flat=true)
  let except_ = if except_ != "" {
    self.seg(self.cfg.star_except) + " (" + except_ + ")"
  } else {
    ""
  }
  let replace = self.expressions(expression~, key="replace", flat=true)
  let replace = if replace != "" {
    self.seg("REPLACE") + " (" + replace + ")"
  } else {
    ""
  }
  let rename = self.expressions(expression~, key="rename", flat=true)
  let rename = if rename != "" {
    self.seg("RENAME") + " (" + rename + ")"
  } else {
    ""
  }
  let ilike = self.sql_key(expression, "ilike")
  let ilike = if ilike != "" { self.seg("ILIKE") + " " + ilike } else { "" }
  "*" + ilike + except_ + replace + rename
}

///|
pub fn Generator::subquery_sql(
  self : Generator,
  expression : Expr,
  sep? : String = " AS ",
) -> String raise SqlglotError {
  let mut alias_ = g_pfx(sep, self.sql_key(expression, "alias"))
  let sample = self.sql_key(expression, "sample")
  if self.dialect.cfg.alias_post_tablesample && sample != "" {
    alias_ = sample + alias_
    expression.set("sample", null_arg)
  }
  let pivots = self.expressions(expression~, key="pivots", sep="", flat=true)
  let sql = self.query_modifiers(expression, [
    self.wrap(expression),
    alias_,
    pivots,
  ])
  self.prepend_ctes(expression, sql)
}

///|
pub fn Generator::qualify_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.indent(self.sql_key(expression, "this"))
  self.seg("QUALIFY") + self.sep() + this
}

///|
pub fn Generator::unnest_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let args = self.expressions(expression~, flat=true)
  let alias_ = expression.arg("alias")
  let offset = expression.get("offset")
  if self.cfg.unnest_with_ordinality {
    match (alias_, offset) {
      (Some(a), Some(Node(off))) => {
        a.append("columns", off)
        expression.set("offset", null_arg)
      }
      _ => ()
    }
  }
  let alias_sql = match alias_ {
    Some(a) if self.dialect.cfg.unnest_column_only => {
      let columns = a.raw_list("columns")
      if columns.is_empty() {
        ""
      } else {
        self.sql_value(columns[0])
      }
    }
    _ => self.sql(alias_)
  }
  let alias_sql = g_pfx(" AS ", alias_sql)
  let offset_truthy = match offset {
    Some(v) => v.truthy()
    None => false
  }
  let suffix = if self.cfg.unnest_with_ordinality {
    if offset_truthy {
      " WITH ORDINALITY" + alias_sql
    } else {
      alias_sql
    }
  } else {
    match offset {
      Some(Node(off)) => alias_sql + " WITH OFFSET AS " + self.sql(Some(off))
      _ => if offset_truthy { alias_sql + " WITH OFFSET" } else { alias_sql }
    }
  }
  "UNNEST(" + args + ")" + suffix
}

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

///|
pub fn Generator::where_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.indent(self.sql_key(expression, "this"))
  self.seg("WHERE") + self.sep() + this
}

///|
pub fn Generator::window_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut this = self.sql_key(expression, "this")
  let partition = self.partition_by_sql(expression)
  let order = match expression.arg("order") {
    Some(o) => self.order_sql(o, flat=true)
    None => ""
  }
  let spec = self.sql_key(expression, "spec")
  let alias_ = self.sql_key(expression, "alias")
  let over = self.sql_key(expression, "over")
  let over = if over != "" { over } else { "OVER" }
  this = this +
    " " +
    (if expression.arg_key == Some("windows") { "AS" } else { over })
  let first = match expression.get("first") {
    None => ""
    Some(v) => if v.truthy() { "FIRST" } else { "LAST" }
  }
  if partition == "" && order == "" && spec == "" && alias_ != "" {
    return this + " " + alias_
  }
  let args : Array[&SqlArg] = []
  for arg in [alias_, first, partition, order, spec] {
    if arg != "" {
      args.push(arg)
    }
  }
  let args = self.format_args(args, sep=" ")
  this + " (" + args + ")"
}

///|
pub fn Generator::partition_by_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  g_pfx(
    "PARTITION BY ",
    self.expressions(expression~, key="partition_by", flat=true),
  )
}

///|
pub fn Generator::windowspec_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let kind = self.sql_key(expression, "kind")
  let start = csv(
    [self.sql_key(expression, "start"), self.sql_key(expression, "start_side")],
    sep=" ",
  )
  let end = csv(
    [self.sql_key(expression, "end"), self.sql_key(expression, "end_side")],
    sep=" ",
  )
  let end = if end != "" { end } else { "CURRENT ROW" }
  let mut window_spec = kind + " BETWEEN " + start + " AND " + end
  let exclude = self.sql_key(expression, "exclude")
  if exclude != "" {
    if self.cfg.supports_window_exclude {
      window_spec = window_spec + " EXCLUDE " + exclude
    } else {
      self.unsupported("EXCLUDE clause is not supported in the WINDOW clause")
    }
  }
  window_spec
}

///|
pub fn Generator::withingroup_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let expression_sql = g_str_from(self.sql_key(expression, "expression"), 1)
  this + " WITHIN GROUP (" + expression_sql + ")"
}

///|
pub fn Generator::delete_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let hint = self.sql_key(expression, "hint")
  let this = g_pfx(" FROM ", self.sql_key(expression, "this"))
  let using_ = g_pfx(" USING ", self.expressions(expression~, key="using"))
  let cluster = g_pfx(" ", self.sql_key(expression, "cluster"))
  let where_ = self.sql_key(expression, "where")
  let returning = self.sql_key(expression, "returning")
  let order = self.sql_key(expression, "order")
  let limit = self.sql_key(expression, "limit")
  let tables = g_pfx(" ", self.expressions(expression~, key="tables"))
  let expression_sql = if self.cfg.returning_end {
    this + using_ + cluster + where_ + returning + order + limit
  } else {
    returning + this + using_ + cluster + where_ + order + limit
  }
  self.prepend_ctes(expression, "DELETE" + hint + tables + expression_sql)
}

///|
pub fn Generator::insert_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let hint = self.sql_key(expression, "hint")
  let overwrite = expression.has("overwrite")
  let mut this = match expression.this() {
    Some(t) if t.kind.is_a(Directory) =>
      if overwrite {
        " OVERWRITE"
      } else {
        " INTO"
      }
    _ => if overwrite { self.cfg.insert_overwrite } else { " INTO" }
  }
  let stored = g_pfx(" ", self.sql_key(expression, "stored"))
  let alternative = match expression.get("alternative") {
    Some(v) if v.truthy() => " OR " + py_str_value(Some(v))
    _ => ""
  }
  let ignore_ = g_when(expression.has("ignore"), " IGNORE")
  if expression.has("is_function") {
    this = this + " FUNCTION"
  }
  this = this + " " + self.sql_key(expression, "this")
  let exists = g_when(expression.has("exists"), " IF EXISTS")
  let where_ = g_pfx(
    self.sep() + "REPLACE WHERE ",
    self.sql_key(expression, "where"),
  )
  let using_ = g_around(
    self.sep() + "REPLACE USING (",
    self.expressions(expression~, key="using", flat=true),
    ")",
  )
  let mut expression_sql = self.sep() + self.sql_key(expression, "expression")
  let on_conflict = g_pfx(" ", self.sql_key(expression, "conflict"))
  let by_name = g_when(expression.has("by_name"), " BY NAME")
  let default_values = g_when(expression.has("default"), "DEFAULT VALUES")
  let returning = self.sql_key(expression, "returning")
  if self.cfg.returning_end {
    expression_sql = expression_sql + on_conflict + default_values + returning
  } else {
    expression_sql = returning + expression_sql + on_conflict
  }
  let partition_by = g_pfx(" ", self.sql_key(expression, "partition"))
  let settings = g_pfx(" ", self.sql_key(expression, "settings"))
  let source = g_pfx("TABLE ", self.sql_key(expression, "source"))
  let sql = "INSERT" +
    hint +
    alternative +
    ignore_ +
    this +
    stored +
    by_name +
    exists +
    partition_by +
    settings +
    where_ +
    using_ +
    expression_sql +
    source
  self.prepend_ctes(expression, sql)
}

///|
pub fn Generator::onconflict_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let conflict = if expression.has("duplicate") {
    "ON DUPLICATE KEY"
  } else {
    "ON CONFLICT"
  }
  let constraint = g_pfx(
    " ON CONSTRAINT ",
    self.sql_key(expression, "constraint"),
  )
  let conflict_keys = g_around(
    "(",
    self.expressions(expression~, key="conflict_keys", flat=true),
    ")",
  )
  let index_predicate = self.sql_key(expression, "index_predicate")
  let conflict_keys = conflict_keys + index_predicate + " "
  let action = self.sql_key(expression, "action")
  let mut expressions = self.expressions(expression~, flat=true)
  if expressions != "" {
    let set_keyword = g_when(self.cfg.duplicate_key_update_with_set, "SET ")
    expressions = " " + set_keyword + expressions
  }
  let where_ = self.sql_key(expression, "where")
  conflict + constraint + conflict_keys + action + expressions + where_
}

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

///|
pub fn Generator::when_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let matched = if expression.has("matched") {
    "MATCHED"
  } else {
    "NOT MATCHED"
  }
  let source = g_when(
    self.cfg.matched_by_source && expression.has("source"),
    " BY SOURCE",
  )
  let condition = g_pfx(" AND ", self.sql_key(expression, "condition"))
  let then_expression = expression.arg("then")
  let mut then = match then_expression {
    Some(t) if t.kind.is_a(Insert) => {
      let this = self.sql_key(t, "this")
      let this = if this != "" { "INSERT " + this } else { "INSERT" }
      let then = self.sql_key(t, "expression")
      if then != "" {
        this + " VALUES " + then
      } else {
        this
      }
    }
    Some(t) if t.kind.is_a(Update) =>
      match t.get("expressions") {
        Some(Node(star)) if star.kind.is_a(Star) =>
          "UPDATE " + self.sql_key(t, "expressions")
        _ => {
          let expressions_sql = self.expressions(expression=t)
          if expressions_sql != "" {
            "UPDATE SET" + self.sep() + expressions_sql
          } else {
            "UPDATE"
          }
        }
      }
    _ => self.sql(then_expression)
  }
  match then_expression {
    Some(t) if t.kind.is_any([Insert, Update]) => {
      let mut where_ = self.sql_key(t, "where")
      if where_ != "" && !self.cfg.supports_merge_where {
        let kind = if t.kind.is_a(Insert) { "INSERT" } else { "UPDATE" }
        self.unsupported("WHERE clause in MERGE \{kind} is not supported")
        where_ = ""
      }
      then = then + where_
    }
    _ => ()
  }
  "WHEN " + matched + source + condition + " THEN " + then
}

///|
pub fn Generator::whens_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.expressions(expression~, sep=" ", indent=false)
}

///|
pub fn Generator::merge_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let table = expression.this_()
  let mut table_alias = ""
  let hints = table.list("hints")
  if !hints.is_empty() &&
    table.alias() != "" &&
    hints[0].kind.is_a(WithTableHint) {
    match table.arg("alias") {
      Some(a) => table_alias = " AS " + self.sql(Some(a.pop()))
      None => ()
    }
  }
  let this = self.sql(Some(table))
  let using_ = "USING " + self.sql_key(expression, "using")
  let mut whens = self.sql_key(expression, "whens")
  let mut on = g_pfx("ON ", self.sql_key(expression, "on"))
  if on == "" {
    on = g_around("USING (", self.expressions(expression~, key="using_cond"), ")")
  }
  let returning = self.sql_key(expression, "returning")
  if returning != "" {
    whens = whens + returning
  }
  let sep = self.sep()
  self.prepend_ctes(
    expression,
    "MERGE INTO " + this + table_alias + sep + using_ + sep + on + sep + whens,
  )
}