// Generator methods for DDL: CREATE/DROP/ALTER, columns, constraints, properties.

///|
pub fn Generator::uncache_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let table = self.sql_key(expression, "this")
  let exists_sql = g_when(expression.has("exists"), " IF EXISTS")
  "UNCACHE TABLE" + exists_sql + " " + table
}

///|
pub fn Generator::cache_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let lazy_ = g_when(expression.has("lazy"), " LAZY")
  let table = self.sql_key(expression, "this")
  let options = expression.raw_list("options")
  let options = if options.length() >= 2 {
    " OPTIONS(" +
    self.sql_value(options[0]) +
    " = " +
    self.sql_value(options[1]) +
    ")"
  } else {
    ""
  }
  let sql = self.sql_key(expression, "expression")
  let sql = g_pfx(" AS" + self.sep(), sql)
  let sql = "CACHE" + lazy_ + " TABLE " + table + options + sql
  self.prepend_ctes(expression, sql)
}

///|
pub fn Generator::characterset_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let default = g_when(expression.has("default"), "DEFAULT ")
  default + "CHARACTER SET=" + self.sql_key(expression, "this")
}

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

///|
pub fn Generator::column_parts_base(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if expression.has("shadow") &&
    self.dialect.cfg.projection_aliases_shadow_source_names {
    return self.sql_key(expression, "this")
  }
  let parts = []
  for k in ["catalog", "db", "table", "this"] {
    match expression.get(k) {
      Some(v) if v.truthy() => parts.push(self.sql_value(v))
      _ => ()
    }
  }
  parts.join(".")
}

///|
pub fn Generator::column_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut join_mark = g_when(expression.has("join_mark"), " (+)")
  if join_mark != "" && !self.dialect.cfg.supports_column_join_marks {
    join_mark = ""
    self.unsupported(
      "Outer join syntax using the (+) operator is not supported.",
    )
  }
  self.column_parts(expression) + join_mark
}

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

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

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

///|
pub fn Generator::columndef_sql(
  self : Generator,
  expression : Expr,
  sep? : String = " ",
) -> String raise SqlglotError {
  let column = self.sql_key(expression, "this")
  let kind = self.sql_key(expression, "kind")
  let constraints = self.expressions(
    expression~,
    key="constraints",
    sep=" ",
    flat=true,
  )
  let exists = g_when(expression.has("exists"), "IF NOT EXISTS ")
  let mut kind = g_pfx(sep, kind)
  let constraints = g_pfx(" ", constraints)
  let position = g_pfx(" ", self.sql_key(expression, "position"))
  if expression.find([ComputedColumnConstraint]) is Some(_) &&
    !self.cfg.computed_column_with_type {
    kind = ""
  }
  exists + column + kind + constraints + position
}

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

///|
pub fn Generator::computedcolumnconstraint_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let persisted = if expression.has("not_null") {
    " PERSISTED NOT NULL"
  } else if expression.has("persisted") {
    " PERSISTED"
  } else {
    ""
  }
  "AS " + this + persisted
}

///|
pub fn Generator::autoincrementcolumnconstraint_sql(
  self : Generator,
  expression : Expr,
) -> String {
  ignore(expression)
  self.token_sql(TokenType::AUTO_INCREMENT)
}

///|
pub fn Generator::compresscolumnconstraint_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = match expression.get("this") {
    Some(List(_)) =>
      self.wrap_str(self.expressions(expression~, key="this", flat=true))
    _ => self.sql_key(expression, "this")
  }
  "COMPRESS " + this
}

///|
pub fn Generator::generatedasidentitycolumnconstraint_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut this = ""
  match expression.get("this") {
    Some(v) => {
      let on_null = g_when(expression.has("on_null"), " ON NULL")
      this = if v.truthy() { " ALWAYS" } else { " BY DEFAULT" + on_null }
    }
    None => ()
  }
  let start = match expression.get("start") {
    Some(v) if v.truthy() => "START WITH " + py_str_value(Some(v))
    _ => ""
  }
  let increment = match expression.get("increment") {
    Some(v) if v.truthy() => " INCREMENT BY " + py_str_value(Some(v))
    _ => ""
  }
  let minvalue = match expression.get("minvalue") {
    Some(v) if v.truthy() => " MINVALUE " + py_str_value(Some(v))
    _ => ""
  }
  let maxvalue = match expression.get("maxvalue") {
    Some(v) if v.truthy() => " MAXVALUE " + py_str_value(Some(v))
    _ => ""
  }
  let mut cycle_sql = ""
  match expression.get("cycle") {
    Some(cycle) => {
      cycle_sql = (if !cycle.truthy() { " NO" } else { "" }) + " CYCLE"
      if start == "" && increment == "" {
        cycle_sql = py_strip(cycle_sql)
      }
    }
    None => ()
  }
  let mut sequence_opts = ""
  if start != "" || increment != "" || cycle_sql != "" {
    sequence_opts = start + increment + minvalue + maxvalue + cycle_sql
    sequence_opts = " (" + py_strip(sequence_opts) + ")"
  }
  let expr = self.sql_key(expression, "expression")
  let expr = if expr != "" { "(" + expr + ")" } else { "IDENTITY" }
  "GENERATED" + this + " AS " + expr + sequence_opts
}

///|
pub fn Generator::generatedasrowcolumnconstraint_sql(
  self : Generator,
  expression : Expr,
) -> String {
  ignore(self)
  let start = if expression.has("start") { "START" } else { "END" }
  let hidden = g_when(expression.has("hidden"), " HIDDEN")
  "GENERATED ALWAYS AS ROW " + start + hidden
}

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

///|
pub fn Generator::notnullcolumnconstraint_sql(
  self : Generator,
  expression : Expr,
) -> String {
  ignore(self)
  (if expression.has("allow_null") { "" } else { "NOT " }) + "NULL"
}

///|
pub fn Generator::primarykeycolumnconstraint_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  match expression.get("desc") {
    Some(desc) =>
      return "PRIMARY KEY" + (if desc.truthy() { " DESC" } else { " ASC" })
    None => ()
  }
  let options = g_pfx(
    " ",
    self.expressions(expression~, key="options", flat=true, sep=" "),
  )
  "PRIMARY KEY" + options
}

///|
pub fn Generator::uniquecolumnconstraint_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = g_pfx(" ", self.sql_key(expression, "this"))
  let index_type = match expression.get("index_type") {
    Some(v) if v.truthy() => " USING " + py_str_value(Some(v))
    _ => ""
  }
  let on_conflict = g_pfx(" ", self.sql_key(expression, "on_conflict"))
  let nulls_sql = g_when(expression.has("nulls"), " NULLS NOT DISTINCT")
  let options = g_pfx(
    " ",
    self.expressions(expression~, key="options", flat=true, sep=" "),
  )
  "UNIQUE" + nulls_sql + this + index_type + on_conflict + options
}

///|
pub fn Generator::inoutcolumnconstraint_sql(
  self : Generator,
  expression : Expr,
) -> String {
  let input_ = expression.has("input_")
  let output = expression.has("output")
  if expression.has("variadic") {
    return "VARIADIC"
  }
  if input_ && output {
    return "IN" + self.cfg.inout_separator + "OUT"
  }
  if input_ {
    return "IN"
  }
  if output {
    return "OUT"
  }
  ""
}

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

///|
pub fn Generator::createable_sql_base(
  self : Generator,
  expression : Expr,
  locations : PropLocs,
) -> String raise SqlglotError {
  ignore(locations)
  self.sql_key(expression, "this")
}

///|
fn g_locs_get(locs : PropLocs, loc : PropertiesLocation) -> Array[Expr] {
  match locs.get(loc) {
    Some(l) => l
    None => []
  }
}

///|
pub fn Generator::create_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut kind = self.sql_key(expression, "kind")
  match self.dialect.cfg.inverse_creatable_kind_mapping.get(kind) {
    Some(k) if k != "" => kind = k
    _ => ()
  }
  let properties = expression.arg("properties")
  match properties {
    Some(p) if kind == "TRIGGER" => {
      let exprs = p.expressions()
      if !exprs.is_empty() &&
        exprs[0].kind.is_a(TriggerProperties) &&
        exprs[0].has("constraint") {
        kind = "CONSTRAINT " + kind
      }
    }
    _ => ()
  }
  let properties_locs : PropLocs = match properties {
    Some(p) => self.locate_properties(p)
    None => Map([])
  }
  let this = self.createable_sql(expression, properties_locs)
  let mut properties_sql = ""
  let post_schema = g_locs_get(properties_locs, PostSchema)
  let post_with = g_locs_get(properties_locs, PostWith)
  if !post_schema.is_empty() || !post_with.is_empty() {
    let props_ast = mk(Properties, [("expressions", post_schema + post_with)])
    props_ast.parent = Some(expression)
    properties_sql = self.sql(Some(props_ast))
    if !post_schema.is_empty() {
      properties_sql = self.sep() + properties_sql
    } else if !self.pretty {
      properties_sql = " " + properties_sql
    }
  }
  let begin = g_when(expression.has("begin"), " BEGIN")
  let mut expression_sql = self.sql_key(expression, "expression")
  if expression_sql != "" {
    expression_sql = begin + self.sep() + expression_sql
    let expr_node = expression.expression()
    let is_macro = match expr_node {
      Some(x) => x.kind.is_a(MacroOverloads)
      None => false
    }
    let is_return = match expr_node {
      Some(x) => x.kind.is_a(Return)
      None => false
    }
    if !is_macro && (self.cfg.create_function_return_as || !is_return) {
      let mut postalias_props_sql = ""
      let post_alias = g_locs_get(properties_locs, PostAlias)
      if !post_alias.is_empty() {
        postalias_props_sql = self.properties(
          mk(Properties, [("expressions", post_alias)]),
          wrapped=false,
        )
      }
      let postalias_props_sql = g_pfx(" ", postalias_props_sql)
      expression_sql = " AS" + postalias_props_sql + expression_sql
    }
  }
  let mut postindex_props_sql = ""
  let post_index = g_locs_get(properties_locs, PostIndex)
  if !post_index.is_empty() {
    postindex_props_sql = self.properties(
      mk(Properties, [("expressions", post_index)]),
      wrapped=false,
      prefix=" ",
    )
  }
  let indexes = g_pfx(
    " ",
    self.expressions(expression~, key="indexes", indent=false, sep=" "),
  )
  let index_sql = indexes + postindex_props_sql
  let replace = g_when(expression.has("replace"), " OR REPLACE")
  let refresh = g_when(expression.has("refresh"), " OR REFRESH")
  let unique = g_when(expression.has("unique"), " UNIQUE")
  let clustered_sql = match expression.get("clustered") {
    None => ""
    Some(v) =>
      if v.truthy() {
        " CLUSTERED COLUMNSTORE"
      } else {
        " NONCLUSTERED COLUMNSTORE"
      }
  }
  let mut postcreate_props_sql = ""
  let post_create = g_locs_get(properties_locs, PostCreate)
  if !post_create.is_empty() {
    postcreate_props_sql = self.properties(
      mk(Properties, [("expressions", post_create)]),
      sep=" ",
      prefix=" ",
      wrapped=false,
    )
  }
  let modifiers = clustered_sql +
    replace +
    refresh +
    unique +
    postcreate_props_sql
  let mut postexpression_props_sql = ""
  let post_expression = g_locs_get(properties_locs, PostExpression)
  if !post_expression.is_empty() {
    postexpression_props_sql = self.properties(
      mk(Properties, [("expressions", post_expression)]),
      sep=" ",
      prefix=" ",
      wrapped=false,
    )
  }
  let concurrently = g_when(expression.has("concurrently"), " CONCURRENTLY")
  let exists_sql = g_when(expression.has("exists"), " IF NOT EXISTS")
  let no_schema_binding = g_when(
    expression.has("no_schema_binding"),
    " WITH NO SCHEMA BINDING",
  )
  let clone = g_pfx(" ", self.sql_key(expression, "clone"))
  let properties_expression = if self.cfg.expression_precedes_properties_creatables.contains(
      kind,
    ) {
    expression_sql + properties_sql
  } else {
    properties_sql + expression_sql
  }
  let expression_sql = "CREATE" +
    modifiers +
    " " +
    kind +
    concurrently +
    exists_sql +
    " " +
    this +
    properties_expression +
    postexpression_props_sql +
    index_sql +
    no_schema_binding +
    clone
  self.prepend_ctes(expression, expression_sql)
}

///|
pub fn Generator::sequenceproperties_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let start = g_pfx("START WITH ", self.sql_key(expression, "start"))
  let increment = g_pfx(" INCREMENT BY ", self.sql_key(expression, "increment"))
  let minvalue = g_pfx(" MINVALUE ", self.sql_key(expression, "minvalue"))
  let maxvalue = g_pfx(" MAXVALUE ", self.sql_key(expression, "maxvalue"))
  let owned = g_pfx(" OWNED BY ", self.sql_key(expression, "owned"))
  let cache_str = match expression.get("cache") {
    None => ""
    Some(Bool(true)) => " CACHE"
    Some(v) => " CACHE " + py_str_value(Some(v))
  }
  let options = g_pfx(
    " ",
    self.expressions(expression~, key="options", flat=true, sep=" "),
  )
  py_lstrip(
    start + increment + minvalue + maxvalue + cache_str + options + owned,
  )
}

///|
pub fn Generator::triggerproperties_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let timing = match expression.get("timing") {
    Some(v) => py_str_value(Some(v))
    None => ""
  }
  let event_sqls = []
  for event in expression.raw_list("events") {
    event_sqls.push(self.sql_value(event))
  }
  let events = event_sqls.join(" OR ")
  let timing_events = if timing != "" || events != "" {
    py_strip(timing + " " + events)
  } else {
    ""
  }
  let parts = [timing_events, "ON", self.sql_key(expression, "table")]
  match expression.get("referenced_table") {
    Some(v) if v.truthy() => {
      parts.push("FROM")
      parts.push(self.sql_value(v))
    }
    _ => ()
  }
  match expression.get("deferrable") {
    Some(v) if v.truthy() => parts.push(py_str_value(Some(v)))
    _ => ()
  }
  match expression.get("initially") {
    Some(v) if v.truthy() => parts.push("INITIALLY " + py_str_value(Some(v)))
    _ => ()
  }
  match expression.get("referencing") {
    Some(v) if v.truthy() => parts.push(self.sql_value(v))
    _ => ()
  }
  match expression.get("for_each") {
    Some(v) if v.truthy() => parts.push("FOR EACH " + py_str_value(Some(v)))
    _ => ()
  }
  match expression.get("when") {
    Some(v) if v.truthy() => parts.push("WHEN (" + self.sql_value(v) + ")")
    _ => ()
  }
  parts.push(self.sql_key(expression, "execute"))
  parts.join(self.sep())
}

///|
pub fn Generator::triggerreferencing_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let parts = []
  match expression.get("old") {
    Some(v) if v.truthy() => parts.push("OLD TABLE AS " + self.sql_value(v))
    _ => ()
  }
  match expression.get("new") {
    Some(v) if v.truthy() => parts.push("NEW TABLE AS " + self.sql_value(v))
    _ => ()
  }
  "REFERENCING " + parts.join(" ")
}

///|
pub fn Generator::triggerevent_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if expression.has("columns") {
    return py_str_value(expression.get("this")) +
      " OF " +
      self.expressions(expression~, key="columns", flat=true)
  }
  self.sql_key(expression, "this")
}

///|
pub fn Generator::clone_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let shallow = g_when(expression.has("shallow"), "SHALLOW ")
  let keyword = if expression.has("copy") && self.cfg.supports_table_copy {
    "COPY"
  } else {
    "CLONE"
  }
  shallow + keyword + " " + this
}

///|
pub fn Generator::describe_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let style = match expression.get("style") {
    Some(v) if v.truthy() => " " + py_str_value(Some(v))
    _ => ""
  }
  let partition = g_pfx(" ", self.sql_key(expression, "partition"))
  let format = g_pfx(" ", self.sql_key(expression, "format"))
  let as_json = g_when(expression.has("as_json"), " AS JSON")
  "DESCRIBE" +
  style +
  format +
  " " +
  self.sql_key(expression, "this") +
  partition +
  as_json
}

///|
pub fn Generator::drop_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let tables = self.expressions(expression~, key="tables", flat=true)
  let expressions = g_around(" (", self.expressions(expression~, flat=true), ")")
  let mut kind = py_str_value(expression.get("kind"))
  match self.dialect.cfg.inverse_creatable_kind_mapping.get(kind) {
    Some(k) if k != "" => kind = k
    _ => ()
  }
  let iceberg = g_when(
    expression.has("iceberg") && self.cfg.supports_drop_alter_iceberg_property,
    " ICEBERG",
  )
  let exists_sql = if expression.has("exists") { " IF EXISTS " } else { " " }
  let concurrently_sql = g_when(expression.has("concurrently"), " CONCURRENTLY")
  let on_cluster = g_pfx(" ", self.sql_key(expression, "cluster"))
  let temporary = g_when(expression.has("temporary"), " TEMPORARY")
  let materialized = g_when(expression.has("materialized"), " MATERIALIZED")
  let cascade = g_when(expression.has("cascade"), " CASCADE")
  let restrict = g_when(expression.has("restrict"), " RESTRICT")
  let constraints = g_when(expression.has("constraints"), " CONSTRAINTS")
  let purge = g_when(expression.has("purge"), " PURGE")
  let sync = g_when(expression.has("sync"), " SYNC")
  let force = g_when(expression.has("force"), " FORCE")
  "DROP" +
  temporary +
  materialized +
  iceberg +
  " " +
  kind +
  concurrently_sql +
  exists_sql +
  tables +
  on_cluster +
  expressions +
  cascade +
  restrict +
  constraints +
  purge +
  sync +
  force
}

///|
pub fn Generator::indexparameters_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let using_ = g_pfx(" USING ", self.sql_key(expression, "using"))
  let columns = g_around(
    "(",
    self.expressions(expression~, key="columns", flat=true),
    ")",
  )
  let partition_by = g_pfx(
    " PARTITION BY ",
    self.expressions(expression~, key="partition_by", flat=true),
  )
  let where_ = self.sql_key(expression, "where")
  let include_ = g_around(
    " INCLUDE (",
    self.expressions(expression~, key="include", flat=true),
    ")",
  )
  let with_storage = g_around(
    " WITH (",
    self.expressions(expression~, key="with_storage", flat=true),
    ")",
  )
  let tablespace = g_pfx(
    " USING INDEX TABLESPACE ",
    self.sql_key(expression, "tablespace"),
  )
  let on = g_pfx(" ON ", self.sql_key(expression, "on"))
  using_ +
  columns +
  include_ +
  with_storage +
  tablespace +
  partition_by +
  where_ +
  on
}

///|
pub fn Generator::index_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let unique = g_when(expression.has("unique"), "UNIQUE ")
  let primary = g_when(expression.has("primary"), "PRIMARY ")
  let amp = g_when(expression.has("amp"), "AMP ")
  let name = self.sql_key(expression, "this")
  let name = if name != "" { name + " " } else { "" }
  let table = g_pfx(self.cfg.index_on + " ", self.sql_key(expression, "table"))
  let index = if table == "" { "INDEX " } else { "" }
  let params = self.sql_key(expression, "params")
  unique + primary + amp + index + name + table + params
}

///|
pub fn Generator::inputoutputformat_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let input_format = g_pfx(
    "INPUTFORMAT ",
    self.sql_key(expression, "input_format"),
  )
  let output_format = g_pfx(
    "OUTPUTFORMAT ",
    self.sql_key(expression, "output_format"),
  )
  [input_format, output_format].join(self.sep())
}

///|
pub fn Generator::partition_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let partition_keyword = if expression.has("subpartition") {
    "SUBPARTITION"
  } else {
    "PARTITION"
  }
  partition_keyword + "(" + self.expressions(expression~, flat=true) + ")"
}

///|
pub fn Generator::property_location(
  self : Generator,
  p : Expr,
) -> PropertiesLocation raise SqlglotError {
  match self.cfg.properties_location.get(p.kind) {
    Some(loc) => loc
    None =>
      raise ValueError(
        "KeyError: \{p.kind.class_path()}",
      )
  }
}

///|
pub fn Generator::properties_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let root_properties = []
  let with_properties = []
  for p in expression.expressions() {
    let p_loc = self.property_location(p)
    if p_loc == PostWith {
      with_properties.push(p)
    } else if p_loc == PostSchema {
      root_properties.push(p)
    }
  }
  let root_props_ast = mk(Properties, [("expressions", root_properties)])
  root_props_ast.parent = expression.parent
  let with_props_ast = mk(Properties, [("expressions", with_properties)])
  with_props_ast.parent = expression.parent
  let root_props = self.root_properties(root_props_ast)
  let mut with_props = self.with_properties(with_props_ast)
  if root_props != "" && with_props != "" && !self.pretty {
    with_props = " " + with_props
  }
  root_props + with_props
}

///|
pub fn Generator::root_properties(
  self : Generator,
  properties : Expr,
) -> String raise SqlglotError {
  if !properties.expressions().is_empty() {
    return self.expressions(expression=properties, indent=false, sep=" ")
  }
  ""
}

///|
pub fn Generator::properties(
  self : Generator,
  properties : Expr,
  prefix? : String = "",
  sep? : String = ", ",
  suffix? : String = "",
  wrapped? : Bool = true,
) -> String raise SqlglotError {
  if !properties.expressions().is_empty() {
    let expressions = self.expressions(
      expression=properties,
      sep~,
      indent=false,
    )
    if expressions != "" {
      let expressions = if wrapped {
        self.wrap_str(expressions)
      } else {
        expressions
      }
      return prefix +
        (if py_strip(prefix) != "" { " " } else { "" }) +
        expressions +
        suffix
    }
  }
  ""
}

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

///|
pub fn Generator::with_properties_base(
  self : Generator,
  properties : Expr,
) -> String raise SqlglotError {
  self.properties(
    properties,
    prefix=self.seg(self.cfg.with_properties_prefix, sep=""),
  )
}

///|
/// Virtual `locate_properties`.
pub fn Generator::locate_properties(
  self : Generator,
  properties : Expr,
) -> PropLocs raise SqlglotError {
  match self.fns.hooks.locate_properties {
    Some(f) => f(self, properties)
    None => self.locate_properties_base(properties)
  }
}

///|
pub fn Generator::locate_properties_base(
  self : Generator,
  properties : Expr,
) -> PropLocs raise SqlglotError {
  let properties_locs : PropLocs = Map([])
  for p in properties.expressions() {
    let p_loc = self.property_location(p)
    if p_loc != Unsupported {
      match properties_locs.get(p_loc) {
        Some(l) => l.push(p)
        None => properties_locs[p_loc] = [p]
      }
    } else {
      self.unsupported("Unsupported property \{p.kind.key()}")
    }
  }
  properties_locs
}

///|
pub fn Generator::property_name(
  self : Generator,
  expression : Expr,
  string_key? : Bool = false,
) -> String raise SqlglotError {
  match expression.this() {
    Some(t) if t.kind.is_a(Dot) => return self.sql_key(expression, "this")
    _ => ()
  }
  if string_key {
    "'" + expression.name() + "'"
  } else {
    expression.name()
  }
}

///|
pub fn Generator::property_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if expression.kind == Property {
    return self.property_name(expression) +
      "=" +
      self.sql_key(expression, "value")
  }
  let property_name = match property_to_name.get(expression.kind) {
    Some(n) => n
    None => {
      self.unsupported("Unsupported property \{expression.kind.key()}")
      "None"
    }
  }
  property_name + "=" + self.sql_key(expression, "this")
}

///|
pub fn Generator::naked_property(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let property_name = match property_to_name.get(expression.kind) {
    Some(n) => n
    None => {
      self.unsupported("Unsupported property \{expression.kind.name()}")
      "None"
    }
  }
  property_name + " " + self.sql_key(expression, "this")
}

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

///|
pub fn Generator::likeproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if self.cfg.supports_create_table_like {
    let opts = []
    for e in expression.expressions() {
      opts.push(e.name() + " " + self.sql_key(e, "value"))
    }
    let options = g_pfx(" ", opts.join(" "))
    let mut like = "LIKE " + self.sql_key(expression, "this") + options
    if self.cfg.like_property_inside_schema &&
      !(match expression.parent {
        Some(p) => p.kind.is_a(Schema)
        None => false
      }) {
      like = "(" + like + ")"
    }
    return like
  }
  if !expression.expressions().is_empty() {
    self.unsupported("Transpilation of LIKE property options is unsupported")
  }
  let select = exp_select_from([mk0(Star)], expression.this_())
  select.set("limit", mk(Limit, [("expression", literal_int(0))]))
  "AS " + self.sql(Some(select))
}

///|
pub fn Generator::fallbackproperty_sql(
  self : Generator,
  expression : Expr,
) -> String {
  ignore(self)
  let no = g_when(expression.has("no"), "NO ")
  let protection = g_when(expression.has("protection"), " PROTECTION")
  no + "FALLBACK" + protection
}

///|
pub fn Generator::journalproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  ignore(self)
  let no = g_when(expression.has("no"), "NO ")
  let local_ = match expression.get("local") {
    Some(v) if v.truthy() => py_str_value(Some(v)) + " "
    _ => ""
  }
  let dual = g_when(expression.has("dual"), "DUAL ")
  let before = g_when(expression.has("before"), "BEFORE ")
  let after = g_when(expression.has("after"), "AFTER ")
  no + local_ + dual + before + after + "JOURNAL"
}

///|
pub fn Generator::freespaceproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let freespace = self.sql_key(expression, "this")
  let percent = g_when(expression.has("percent"), " PERCENT")
  "FREESPACE=" + freespace + percent
}

///|
pub fn Generator::checksumproperty_sql(
  self : Generator,
  expression : Expr,
) -> String {
  ignore(self)
  let property = if expression.has("default") {
    "DEFAULT"
  } else if expression.has("on") {
    "ON"
  } else {
    "OFF"
  }
  "CHECKSUM=" + property
}

///|
pub fn Generator::mergeblockratioproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  if expression.has("no") {
    return "NO MERGEBLOCKRATIO"
  }
  if expression.has("default") {
    return "DEFAULT MERGEBLOCKRATIO"
  }
  let percent = g_when(expression.has("percent"), " PERCENT")
  "MERGEBLOCKRATIO=" + self.sql_key(expression, "this") + percent
}

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

///|
pub fn Generator::datablocksizeproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let default = expression.has("default")
  let minimum = expression.has("minimum")
  let maximum = expression.has("maximum")
  if default || minimum || maximum {
    let prop = if default {
      "DEFAULT"
    } else if minimum {
      "MINIMUM"
    } else {
      "MAXIMUM"
    }
    return prop + " DATABLOCKSIZE"
  }
  let units = match expression.get("units") {
    Some(v) if v.truthy() => " " + py_str_value(Some(v))
    _ => ""
  }
  "DATABLOCKSIZE=" + self.sql_key(expression, "size") + units
}

///|
pub fn Generator::blockcompressionproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let prop = match expression.arg("autotemp") {
    Some(autotemp) => "AUTOTEMP(" + self.expressions(expression=autotemp) + ")"
    None =>
      if expression.has("always") {
        "ALWAYS"
      } else if expression.has("default") {
        "DEFAULT"
      } else if expression.has("manual") {
        "MANUAL"
      } else if expression.has("never") {
        "NEVER"
      } else {
        raise ValueError(
          "UnboundLocalError: cannot access local variable 'prop'",
        )
      }
  }
  "BLOCKCOMPRESSION=" + prop
}

///|
pub fn Generator::isolatedloadingproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let no = g_when(expression.has("no"), " NO")
  let concurrent = g_when(expression.has("concurrent"), " CONCURRENT")
  let target = g_pfx(" ", self.sql_key(expression, "target"))
  "WITH" + no + concurrent + " ISOLATED LOADING" + target
}

///|
pub fn Generator::partitionboundspec_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  match expression.get("this") {
    Some(List(_)) =>
      return "IN (" + self.expressions(expression~, key="this", flat=true) + ")"
    Some(v) if v.truthy() => {
      let modulus = self.sql_key(expression, "this")
      let remainder = self.sql_key(expression, "expression")
      return "WITH (MODULUS " + modulus + ", REMAINDER " + remainder + ")"
    }
    _ => ()
  }
  let from_expressions = self.expressions(
    expression~,
    key="from_expressions",
    flat=true,
  )
  let to_expressions = self.expressions(
    expression~,
    key="to_expressions",
    flat=true,
  )
  "FROM (" + from_expressions + ") TO (" + to_expressions + ")"
}

///|
pub fn Generator::partitionedofproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let for_values_or_default = match expression.expression() {
    Some(e) if e.kind.is_a(PartitionBoundSpec) =>
      " FOR VALUES " + self.sql(Some(e))
    _ => " DEFAULT"
  }
  "PARTITION OF " + this + for_values_or_default
}

///|
pub fn Generator::lockingproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let kind = py_str_value(expression.get("kind"))
  let this = if expression.has("this") {
    " " + self.sql_key(expression, "this")
  } else {
    ""
  }
  let for_or_in = match expression.get("for_or_in") {
    Some(v) if v.truthy() => " " + py_str_value(Some(v))
    _ => ""
  }
  let lock_type = py_str_value(expression.get("lock_type"))
  let override_ = g_when(expression.has("override"), " OVERRIDE")
  "LOCKING " + kind + this + for_or_in + " " + lock_type + override_
}

///|
pub fn Generator::withdataproperty_sql(
  self : Generator,
  expression : Expr,
) -> String {
  ignore(self)
  let data_sql = "WITH " + g_when(expression.has("no"), "NO ") + "DATA"
  let statistics_sql = match expression.get("statistics") {
    Some(v) => " AND " + (if !v.truthy() { "NO " } else { "" }) + "STATISTICS"
    None => ""
  }
  data_sql + statistics_sql
}

///|
pub fn Generator::withsystemversioningproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = g_pfx("HISTORY_TABLE=", self.sql_key(expression, "this"))
  let data_consistency = self.sql_key(expression, "data_consistency")
  let data_consistency : String? = if data_consistency != "" {
    Some("DATA_CONSISTENCY_CHECK=" + data_consistency)
  } else {
    None
  }
  let retention_period = self.sql_key(expression, "retention_period")
  let retention_period : String? = if retention_period != "" {
    Some("HISTORY_RETENTION_PERIOD=" + retention_period)
  } else {
    None
  }
  let on_sql = if this != "" {
    self.func("ON", [this, data_consistency, retention_period])
  } else if expression.has("on") {
    "ON"
  } else {
    "OFF"
  }
  let sql = "SYSTEM_VERSIONING=" + on_sql
  if expression.has("with_") {
    "WITH(" + sql + ")"
  } else {
    sql
  }
}

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

///|
pub fn Generator::kill_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let kind = g_pfx(" ", self.sql_key(expression, "kind"))
  let this = g_pfx(" ", self.sql_key(expression, "this"))
  "KILL" + kind + this
}

///|
pub fn Generator::rowformatdelimitedproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let fields = g_pfx(" FIELDS TERMINATED BY ", self.sql_key(expression, "fields"))
  let escaped = g_pfx(" ESCAPED BY ", self.sql_key(expression, "escaped"))
  let items = g_pfx(
    " COLLECTION ITEMS TERMINATED BY ",
    self.sql_key(expression, "collection_items"),
  )
  let keys = g_pfx(
    " MAP KEYS TERMINATED BY ",
    self.sql_key(expression, "map_keys"),
  )
  let lines = g_pfx(" LINES TERMINATED BY ", self.sql_key(expression, "lines"))
  let null = g_pfx(" NULL DEFINED AS ", self.sql_key(expression, "null"))
  "ROW FORMAT DELIMITED" + fields + escaped + items + keys + lines + null
}

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

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

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

///|
pub fn Generator::directory_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let local_ = g_when(expression.has("local"), "LOCAL ")
  let row_format = g_pfx(" ", self.sql_key(expression, "row_format"))
  local_ + "DIRECTORY " + self.sql_key(expression, "this") + row_format
}

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

///|
pub fn Generator::foreignkey_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let expressions = g_around(" (", self.expressions(expression~, flat=true), ")")
  let reference = g_pfx(" ", self.sql_key(expression, "reference"))
  let delete = g_pfx(" ON DELETE ", self.sql_key(expression, "delete"))
  let update = g_pfx(" ON UPDATE ", self.sql_key(expression, "update"))
  let options = g_pfx(
    " ",
    self.expressions(expression~, key="options", flat=true, sep=" "),
  )
  "FOREIGN KEY" + expressions + reference + delete + update + options
}

///|
pub fn Generator::primarykey_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = g_pfx(" ", self.sql_key(expression, "this"))
  let expressions = self.expressions(expression~, flat=true)
  let include_ = self.sql_key(expression, "include")
  let options = g_pfx(
    " ",
    self.expressions(expression~, key="options", flat=true, sep=" "),
  )
  "PRIMARY KEY" + this + " (" + expressions + ")" + include_ + options
}

///|
pub fn Generator::timeserieskey_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.unsupported("TIMESERIES primary key columns are not supported")
  self.sql_key(expression, "this")
}

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

///|
pub fn Generator::reference_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let expressions = g_around("(", self.expressions(expression~, flat=true), ")")
  let options = g_pfx(
    " ",
    self.expressions(expression~, key="options", flat=true, sep=" "),
  )
  "REFERENCES " + this + expressions + options
}

///|
pub fn Generator::comment_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let kind = py_str_value(expression.get("kind"))
  let materialized = g_when(expression.has("materialized"), " MATERIALIZED")
  let exists_sql = if expression.has("exists") { " IF EXISTS " } else { " " }
  let expression_sql = self.sql_key(expression, "expression")
  "COMMENT" +
  exists_sql +
  "ON" +
  materialized +
  " " +
  kind +
  " " +
  this +
  " IS " +
  expression_sql
}

///|
pub fn Generator::mergetreettlaction_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let delete = g_when(expression.has("delete"), " DELETE")
  let recompress = g_pfx(" RECOMPRESS ", self.sql_key(expression, "recompress"))
  let to_disk = g_pfx(" TO DISK ", self.sql_key(expression, "to_disk"))
  let to_volume = g_pfx(" TO VOLUME ", self.sql_key(expression, "to_volume"))
  this + delete + recompress + to_disk + to_volume
}

///|
pub fn Generator::mergetreettl_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let where_ = self.sql_key(expression, "where")
  let group = self.sql_key(expression, "group")
  let aggregates = self.expressions(expression~, key="aggregates")
  let aggregates = if aggregates != "" {
    self.seg("SET") + self.seg(aggregates)
  } else {
    ""
  }
  if where_ == "" &&
    group == "" &&
    aggregates == "" &&
    expression.expressions().length() == 1 {
    return "TTL " + self.expressions(expression~, flat=true)
  }
  "TTL" + self.seg(self.expressions(expression~)) + where_ + group + aggregates
}

///|
pub fn Generator::transaction_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let modes = g_pfx(" ", self.expressions(expression~, key="modes"))
  "BEGIN" + modes
}

///|
pub fn Generator::commit_sql(self : Generator, expression : Expr) -> String {
  ignore(self)
  let chain = match expression.get("chain") {
    Some(v) => if v.truthy() { " AND CHAIN" } else { " AND NO CHAIN" }
    None => ""
  }
  "COMMIT" + chain
}

///|
pub fn Generator::rollback_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  ignore(self)
  let savepoint = match expression.get("savepoint") {
    Some(v) if v.truthy() => " TO " + py_str_value(Some(v))
    _ => ""
  }
  "ROLLBACK" + savepoint
}

///|
pub fn Generator::altercolumn_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let mut exists = ""
  if expression.has("exists") {
    if self.cfg.supports_alter_column_if_exists {
      exists = " IF EXISTS"
    } else {
      self.unsupported(
        "ALTER COLUMN IF EXISTS is not supported by this dialect",
      )
    }
  }
  let dtype = self.sql_key(expression, "dtype")
  if dtype != "" {
    let collate = g_pfx(" COLLATE ", self.sql_key(expression, "collate"))
    let using_ = g_pfx(" USING ", self.sql_key(expression, "using"))
    let alter_set_type = g_pfx("", self.cfg.alter_set_type)
    let alter_set_type = if alter_set_type != "" {
      alter_set_type + " "
    } else {
      ""
    }
    let null_constraint = self.alter_column_null_constraint_sql(expression)
    return "ALTER COLUMN" +
      exists +
      " " +
      this +
      " " +
      alter_set_type +
      dtype +
      collate +
      using_ +
      null_constraint
  }
  let default = self.sql_key(expression, "default")
  if default != "" {
    return "ALTER COLUMN" + exists + " " + this + " SET DEFAULT " + default
  }
  let comment = self.sql_key(expression, "comment")
  if comment != "" {
    return "ALTER COLUMN" + exists + " " + this + " COMMENT " + comment
  }
  match expression.get("visible") {
    Some(v) if v.truthy() =>
      return "ALTER COLUMN" +
        exists +
        " " +
        this +
        " SET " +
        py_str_value(Some(v))
    _ => ()
  }
  let allow_null = expression.get("allow_null")
  let drop = expression.has("drop")
  let allow_null_truthy = match allow_null {
    Some(v) => v.truthy()
    None => false
  }
  if !drop && !allow_null_truthy {
    self.unsupported("Unsupported ALTER COLUMN syntax")
  }
  if allow_null is Some(_) {
    let keyword = if drop { "DROP" } else { "SET" }
    return "ALTER COLUMN" + exists + " " + this + " " + keyword + " NOT NULL"
  }
  "ALTER COLUMN" + exists + " " + this + " DROP DEFAULT"
}

///|
/// Python `_alter_column_null_constraint_sql`.
pub fn Generator::alter_column_null_constraint_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  match expression.get("allow_null") {
    None => ""
    Some(v) => {
      if !self.cfg.supports_alter_column_nullability {
        self.unsupported(
          "ALTER COLUMN cannot set nullability along with a type",
        )
        return ""
      }
      if v.truthy() {
        " NULL"
      } else {
        " NOT NULL"
      }
    }
  }
}

///|
pub fn Generator::modifycolumn_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let rename_from = self.sql_key(expression, "rename_from")
  if rename_from != "" {
    if !self.cfg.supports_change_column {
      self.unsupported("CHANGE COLUMN is not supported in this dialect")
    }
    return "CHANGE COLUMN " + rename_from + " " + this
  }
  if !self.cfg.supports_modify_column {
    self.unsupported("MODIFY COLUMN is not supported in this dialect")
  }
  "MODIFY COLUMN " + this
}

///|
pub fn Generator::alterindex_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let visible_sql = if expression.has("visible") {
    "VISIBLE"
  } else {
    "INVISIBLE"
  }
  "ALTER INDEX " + this + " " + visible_sql
}

///|
pub fn Generator::alterdiststyle_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut this = self.sql_key(expression, "this")
  match expression.this() {
    Some(t) if t.kind.is_a(Var) => ()
    _ => this = "KEY DISTKEY " + this
  }
  "ALTER DISTSTYLE " + this
}

///|
pub fn Generator::altersortkey_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let compound = g_when(expression.has("compound"), " COMPOUND")
  let this = self.sql_key(expression, "this")
  let expressions = g_around("(", self.expressions(expression~, flat=true), ")")
  "ALTER" +
  compound +
  " SORTKEY " +
  (if this != "" { this } else { expressions })
}

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

///|
pub fn Generator::alterrename_sql(
  self : Generator,
  expression : Expr,
  include_to? : Bool = true,
) -> String raise SqlglotError {
  let mut expression = expression
  if !self.cfg.rename_table_with_db {
    let mut err : SqlglotError? = None
    expression = expression.transform(n => {
      if n.kind.is_a(Table) {
        Some(exp_table_from_identifier(n.this())) catch {
          e => {
            err = Some(e)
            Some(n)
          }
        }
      } else {
        Some(n)
      }
    })
    match err {
      Some(e) => raise e
      None => ()
    }
  }
  let this = self.sql_key(expression, "this")
  let to_kw = g_when(include_to, " TO")
  "RENAME" + to_kw + " " + this
}

///|
pub fn Generator::renamecolumn_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let exists = g_when(expression.has("exists"), " IF EXISTS")
  let old_column = self.sql_key(expression, "this")
  let new_column = self.sql_key(expression, "to")
  "RENAME COLUMN" + exists + " " + old_column + " TO " + new_column
}

///|
pub fn Generator::alterset_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut exprs = self.expressions(expression~, flat=true)
  if self.cfg.alter_set_wrapped {
    exprs = "(" + exprs + ")"
  }
  "SET " + exprs
}

///|
pub fn Generator::alter_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let actions = expression.list("actions")
  let actions_sql = if !self.dialect.cfg.alter_table_add_required_for_each_column &&
    !actions.is_empty() &&
    actions[0].kind.is_a(ColumnDef) {
    "ADD " + self.expressions(expression~, key="actions", flat=true)
  } else {
    let actions_list : Array[&SqlArg] = []
    for action in actions {
      if action.kind.is_any([ColumnDef, Schema]) {
        actions_list.push(self.add_column_sql(action))
      } else {
        let mut action_sql = self.sql(Some(action))
        if action.kind.is_a(Query) {
          action_sql = "AS " + action_sql
        }
        actions_list.push(action_sql)
      }
    }
    lstrip_chars(self.format_args(actions_list), "\n")
  }
  let iceberg = g_when(
    expression.has("iceberg") && self.cfg.supports_drop_alter_iceberg_property,
    "ICEBERG ",
  )
  let exists = g_when(expression.has("exists"), " IF EXISTS")
  let on_cluster = g_pfx(" ", self.sql_key(expression, "cluster"))
  let only = g_when(expression.has("only"), " ONLY")
  let options = g_pfx(", ", self.expressions(expression~, key="options"))
  let kind = self.sql_key(expression, "kind")
  let not_valid = g_when(expression.has("not_valid"), " NOT VALID")
  let check = g_when(expression.has("check"), " WITH CHECK")
  let cascade = g_when(
    expression.has("cascade") && self.dialect.cfg.alter_table_supports_cascade,
    " CASCADE",
  )
  let this = g_pfx(" ", self.sql_key(expression, "this"))
  "ALTER " +
  iceberg +
  kind +
  exists +
  only +
  this +
  on_cluster +
  check +
  self.sep() +
  actions_sql +
  not_valid +
  options +
  cascade
}

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

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

///|
pub fn Generator::add_column_sql_base(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let sql = self.sql(Some(expression))
  let column_text = if expression.kind.is_a(Schema) {
    " COLUMNS"
  } else if expression.kind.is_a(ColumnDef) &&
    self.cfg.alter_table_include_column_keyword {
    " COLUMN"
  } else {
    ""
  }
  "ADD" + column_text + " " + sql
}

///|
pub fn Generator::droppartition_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let expressions = self.expressions(expression~)
  let exists = if expression.has("exists") { " IF EXISTS " } else { " " }
  "DROP" + exists + expressions
}

///|
pub fn Generator::dropprimarykey_sql(
  self : Generator,
  expression : Expr,
) -> String {
  ignore((self, expression))
  "DROP PRIMARY KEY"
}

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

///|
pub fn Generator::addpartition_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let exists = g_when(expression.has("exists"), "IF NOT EXISTS ")
  let location = g_pfx(" ", self.sql_key(expression, "location"))
  "ADD " + exists + self.sql(expression.this()) + location
}

///|
pub fn Generator::dictproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let kind = self.sql_key(expression, "kind")
  let settings_sql = self.expressions(expression~, key="settings", sep=" ")
  let args = if settings_sql != "" {
    "(" + self.sep(sep="") + settings_sql + self.seg(")", sep="")
  } else {
    "()"
  }
  this + "(" + kind + args + ")"
}

///|
pub fn Generator::dictrange_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let max = self.sql_key(expression, "max")
  let min = self.sql_key(expression, "min")
  this + "(MIN " + min + " MAX " + max + ")"
}

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

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

///|
/// Virtual `uniquekeyproperty_sql(expression, prefix)`.
pub fn Generator::uniquekeyproperty_sql_v(
  self : Generator,
  expression : Expr,
  prefix? : String = "UNIQUE KEY",
) -> String raise SqlglotError {
  match self.fns.hooks.uniquekeyproperty_sql {
    Some(f) => f(self, expression, prefix)
    None => self.uniquekeyproperty_sql(expression, prefix~)
  }
}

///|
pub fn Generator::uniquekeyproperty_sql(
  self : Generator,
  expression : Expr,
  prefix? : String = "UNIQUE KEY",
) -> String raise SqlglotError {
  prefix + " (" + self.expressions(expression~, flat=true) + ")"
}

///|
pub fn Generator::distributedbyproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let expressions = self.expressions(expression~, flat=true)
  let expressions = if expressions != "" {
    " " + self.wrap_str(expressions)
  } else {
    ""
  }
  let buckets = self.sql_key(expression, "buckets")
  let kind = self.sql_key(expression, "kind")
  let buckets = g_pfx(" BUCKETS ", buckets)
  let order = self.sql_key(expression, "order")
  "DISTRIBUTED BY " + kind + expressions + buckets + order
}

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

///|
pub fn Generator::clusteredbyproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let expressions = self.expressions(expression~, key="expressions", flat=true)
  let sorted_by = g_around(
    " SORTED BY (",
    self.expressions(expression~, key="sorted_by", flat=true),
    ")",
  )
  let buckets = self.sql_key(expression, "buckets")
  "CLUSTERED BY (" +
  expressions +
  ")" +
  sorted_by +
  " INTO " +
  buckets +
  " BUCKETS"
}

///|
pub fn Generator::indexconstraintoption_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let key_block_size = self.sql_key(expression, "key_block_size")
  if key_block_size != "" {
    return "KEY_BLOCK_SIZE = " + key_block_size
  }
  let using_ = self.sql_key(expression, "using")
  if using_ != "" {
    return "USING " + using_
  }
  let parser = self.sql_key(expression, "parser")
  if parser != "" {
    return "WITH PARSER " + parser
  }
  let comment = self.sql_key(expression, "comment")
  if comment != "" {
    return "COMMENT " + comment
  }
  match expression.get("visible") {
    Some(v) => return if v.truthy() { "VISIBLE" } else { "INVISIBLE" }
    None => ()
  }
  let engine_attr = self.sql_key(expression, "engine_attr")
  if engine_attr != "" {
    return "ENGINE_ATTRIBUTE = " + engine_attr
  }
  let secondary_engine_attr = self.sql_key(expression, "secondary_engine_attr")
  if secondary_engine_attr != "" {
    return "SECONDARY_ENGINE_ATTRIBUTE = " + secondary_engine_attr
  }
  self.unsupported("Unsupported index constraint option.")
  ""
}

///|
pub fn Generator::checkcolumnconstraint_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let enforced = g_when(expression.has("enforced"), " ENFORCED")
  "CHECK (" + self.sql_key(expression, "this") + ")" + enforced
}

///|
pub fn Generator::indexcolumnconstraint_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let kind = self.sql_key(expression, "kind")
  let kind = if kind != "" { kind + " INDEX" } else { "INDEX" }
  let this = g_pfx(" ", self.sql_key(expression, "this"))
  let index_type = g_pfx(" USING ", self.sql_key(expression, "index_type"))
  let expressions = g_around(" (", self.expressions(expression~, flat=true), ")")
  let options = g_pfx(" ", self.expressions(expression~, key="options", sep=" "))
  kind + this + index_type + expressions + options
}

///|
pub fn Generator::truncatetable_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let target = if expression.has("is_database") { "DATABASE" } else { "TABLE" }
  let tables = " " + self.expressions(expression~)
  let exists = g_when(expression.has("exists"), " IF EXISTS")
  let on_cluster = g_pfx(" ", self.sql_key(expression, "cluster"))
  let identity = g_around(" ", self.sql_key(expression, "identity"), " IDENTITY")
  let option = g_pfx(" ", self.sql_key(expression, "option"))
  let partition = g_pfx(" ", self.sql_key(expression, "partition"))
  "TRUNCATE " +
  target +
  exists +
  tables +
  on_cluster +
  identity +
  option +
  partition
}

///|
pub fn Generator::copyparameter_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let option = self.sql_key(expression, "this")
  if !expression.expressions().is_empty() {
    let upper = py_upper(option)
    let sep = if upper == "FILE_FORMAT" { " " } else { ", " }
    let op = if upper == "COPY_OPTIONS" || upper == "FORMAT_OPTIONS" {
      " "
    } else {
      " = "
    }
    let values = self.expressions(expression~, flat=true, sep~)
    return option + op + "(" + values + ")"
  }
  let value = self.sql_key(expression, "expression")
  if value == "" {
    return option
  }
  let op = if self.cfg.copy_params_eq_required { " = " } else { " " }
  option + op + value
}

///|
pub fn Generator::credentials_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let cred_expr = expression.get("credentials")
  let credentials = match cred_expr {
    Some(Node(c)) if c.kind.is_a(Literal) =>
      g_pfx("CREDENTIALS ", self.sql_key(expression, "credentials"))
    _ => {
      let credentials = self.expressions(
        expression~,
        key="credentials",
        flat=true,
        sep=" ",
      )
      if cred_expr is Some(_) {
        "CREDENTIALS = (" + credentials + ")"
      } else {
        ""
      }
    }
  }
  let storage = g_pfx(
    "STORAGE_INTEGRATION = ",
    self.sql_key(expression, "storage"),
  )
  let encryption = g_around(
    " ENCRYPTION = (",
    self.expressions(expression~, key="encryption", flat=true, sep=" "),
    ")",
  )
  let iam_role = g_pfx("IAM_ROLE ", self.sql_key(expression, "iam_role"))
  let region = g_pfx(" REGION ", self.sql_key(expression, "region"))
  credentials + storage + encryption + iam_role + region
}

///|
pub fn Generator::copy_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let this = if self.cfg.copy_has_into_keyword {
    " INTO " + this
  } else {
    " " + this
  }
  let credentials = self.sql_key(expression, "credentials")
  let credentials = if credentials != "" { self.seg(credentials) } else { "" }
  let files = self.expressions(expression~, key="files", flat=true)
  let kind = if files != "" {
    self.seg(if expression.has("kind") { "FROM" } else { "TO" })
  } else {
    ""
  }
  let sep = if self.dialect.cfg.copy_params_are_csv { ", " } else { " " }
  let mut params = self.expressions(
    expression~,
    key="params",
    sep~,
    new_line=true,
    skip_last=true,
    skip_first=true,
    indent=self.cfg.copy_params_are_wrapped,
  )
  if params != "" {
    if self.cfg.copy_params_are_wrapped {
      params = " WITH (" + params + ")"
    } else if !self.pretty && (files != "" || credentials != "") {
      params = " " + params
    }
  }
  "COPY" + this + kind + " " + files + credentials + params
}

///|
pub fn Generator::datadeletionproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut on_sql = if expression.has("on") { "ON" } else { "OFF" }
  let filter_col = self.sql_key(expression, "filter_column")
  let filter_col : String? = if filter_col != "" {
    Some("FILTER_COLUMN=" + filter_col)
  } else {
    None
  }
  let retention_period = self.sql_key(expression, "retention_period")
  let retention_period : String? = if retention_period != "" {
    Some("RETENTION_PERIOD=" + retention_period)
  } else {
    None
  }
  if filter_col is Some(_) || retention_period is Some(_) {
    on_sql = self.func("ON", [filter_col, retention_period])
  }
  "DATA_DELETION=" + on_sql
}

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

///|
pub fn Generator::attach_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let exists_sql = g_when(expression.has("exists"), " IF NOT EXISTS")
  let expressions = g_around(" (", self.expressions(expression~), ")")
  "ATTACH" + exists_sql + " " + this + expressions
}

///|
pub fn Generator::detach_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let mut kind = g_pfx(" ", self.sql_key(expression, "kind"))
  let exists = g_when(expression.has("exists"), " IF EXISTS")
  if exists != "" && kind == "" {
    kind = " DATABASE"
  }
  let this = g_pfx(" ", self.sql_key(expression, "this"))
  let cluster = g_pfx(" ", self.sql_key(expression, "cluster"))
  let permanent = g_when(expression.has("permanent"), " PERMANENTLY")
  let sync = g_when(expression.has("sync"), " SYNC")
  "DETACH" + kind + exists + this + cluster + permanent + sync
}

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

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

///|
pub fn Generator::encodeproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let encode = if expression.has("key") { "KEY ENCODE" } else { "ENCODE" }
  let mut encode = encode + " " + self.sql_key(expression, "this")
  match expression.arg("properties") {
    Some(p) => encode = encode + " " + self.properties(p)
    None => ()
  }
  encode
}

///|
pub fn Generator::includeproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let mut include_ = "INCLUDE " + this
  let column_def = self.sql_key(expression, "column_def")
  if column_def != "" {
    include_ = include_ + " " + column_def
  }
  let alias_ = self.sql_key(expression, "alias")
  if alias_ != "" {
    include_ = include_ + " AS " + alias_
  }
  include_
}

///|
pub fn Generator::partitionbyrangeproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let partitions = self.expressions(expression~, key="partition_expressions")
  let create = self.expressions(expression~, key="create_expressions")
  "PARTITION BY RANGE " +
  self.wrap_str(partitions) +
  " " +
  self.wrap_str(create)
}

///|
pub fn Generator::partitionbyrangepropertydynamic_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let start = self.sql_key(expression, "start")
  let end = self.sql_key(expression, "end")
  let every = expression.arg("every")
  match every {
    Some(ev) if ev.kind.is_a(Interval) =>
      match ev.this() {
        Some(t) if t.is_string() =>
          t.replace(Some(literal_number(ev.name()))) |> ignore
        _ => ()
      }
    _ => ()
  }
  "START " +
  self.wrap_str(start) +
  " END " +
  self.wrap_str(end) +
  " EVERY " +
  self.wrap_str(self.sql(every))
}

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

///|
pub fn Generator::analyzesample_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let kind = self.sql_key(expression, "kind")
  let sample = self.sql_key(expression, "sample")
  "SAMPLE " + sample + " " + kind
}

///|
pub fn Generator::analyzestatistics_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let kind = self.sql_key(expression, "kind")
  let option = g_pfx(" ", self.sql_key(expression, "option"))
  let this = g_pfx(" ", self.sql_key(expression, "this"))
  let columns = g_pfx(" ", self.expressions(expression~))
  kind + option + " STATISTICS" + this + columns
}

///|
pub fn Generator::analyzehistogram_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let columns = self.expressions(expression~)
  let inner_expression = g_pfx(" ", self.sql_key(expression, "expression"))
  let update_options = g_around(
    " ",
    self.sql_key(expression, "update_options"),
    " UPDATE",
  )
  this + " HISTOGRAM ON " + columns + inner_expression + update_options
}

///|
pub fn Generator::analyzedelete_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let kind = g_pfx(" ", self.sql_key(expression, "kind"))
  "DELETE" + kind + " STATISTICS"
}

///|
pub fn Generator::analyzelistchainedrows_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  "LIST CHAINED ROWS" + self.sql_key(expression, "expression")
}

///|
pub fn Generator::analyzevalidate_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let kind = self.sql_key(expression, "kind")
  let this = g_pfx(" ", self.sql_key(expression, "this"))
  let inner_expression = self.sql_key(expression, "expression")
  "VALIDATE " + kind + this + inner_expression
}

///|
pub fn Generator::analyze_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let options = g_pfx(" ", self.expressions(expression~, key="options", sep=" "))
  let kind = g_pfx(" ", self.sql_key(expression, "kind"))
  let tables = g_pfx(" ", self.expressions(expression~, key="tables", flat=true))
  let mode = g_pfx(" ", self.sql_key(expression, "mode"))
  let properties = g_pfx(" ", self.sql_key(expression, "properties"))
  let partition = g_pfx(" ", self.sql_key(expression, "partition"))
  let inner_expression = g_pfx(" ", self.sql_key(expression, "expression"))
  "ANALYZE" +
  options +
  kind +
  tables +
  partition +
  mode +
  inner_expression +
  properties
}

///|
pub fn Generator::declare_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let replace = g_when(expression.has("replace"), "OR REPLACE ")
  "DECLARE " + replace + self.expressions(expression~, flat=true)
}

///|
pub fn Generator::declareitem_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let variables = self.expressions(expression~, key="this")
  let default = g_pfx(
    " " + self.cfg.declare_default_assignment + " ",
    self.sql_key(expression, "default"),
  )
  let mut kind = self.sql_key(expression, "kind")
  match expression.arg("kind") {
    Some(k) if k.kind.is_a(Schema) => kind = "TABLE " + kind
    _ => ()
  }
  let kind = g_pfx(" ", kind)
  variables + kind + default
}

///|
pub fn Generator::recursivewithsearch_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let kind = self.sql_key(expression, "kind")
  let this = self.sql_key(expression, "this")
  let set = self.sql_key(expression, "expression")
  let using_ = g_pfx(" USING ", self.sql_key(expression, "using"))
  let kind_sql = if kind == "CYCLE" {
    kind
  } else {
    "SEARCH " + kind + " FIRST BY"
  }
  kind_sql + " " + this + " SET " + set + using_
}

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

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

///|
pub fn Generator::get_put_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let props_sql = match expression.arg("properties") {
    Some(p) => self.properties(p, prefix=" ", sep=" ", wrapped=false)
    None => ""
  }
  let this = self.sql_key(expression, "this")
  let target = self.sql_key(expression, "target")
  if expression.kind.is_a(Put) {
    "PUT " + this + " " + target + props_sql
  } else {
    "GET " + target + " " + this + props_sql
  }
}

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

///|
pub fn Generator::refreshtriggerproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let method_ = self.sql_key(expression, "method")
  let kind = match expression.get("kind") {
    Some(v) if v.truthy() => py_str_value(Some(v))
    _ => return "REFRESH " + method_
  }
  let every = self.sql_key(expression, "every")
  let unit = self.sql_key(expression, "unit")
  let every = if every != "" { " EVERY " + every + " " + unit } else { "" }
  let starts = g_pfx(" STARTS ", self.sql_key(expression, "starts"))
  "REFRESH " + method_ + " ON " + kind + every + starts
}

///|
pub fn Generator::altermodifysqlsecurity_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let props = self.expressions(expression~, sep=" ")
  "MODIFY " + props
}

///|
pub fn Generator::usingproperty_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let kind = py_str_value(expression.get("kind"))
  "USING " + kind + " " + self.sql_key(expression, "this")
}

///|
pub fn Generator::renameindex_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let to = self.sql_key(expression, "to")
  "RENAME INDEX " + this + " TO " + to
}

///|
pub fn Generator::export_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let this = self.sql_key(expression, "this")
  let connection = g_around(
    "WITH CONNECTION ",
    self.sql_key(expression, "connection"),
    " ",
  )
  let options = self.sql_key(expression, "options")
  "EXPORT DATA " + connection + options + " AS " + this
}

///|
pub fn Generator::use_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let kind = g_pfx(" ", self.sql_key(expression, "kind"))
  let this = self.sql_key(expression, "this")
  let this = if this != "" {
    this
  } else {
    self.expressions(expression~, flat=true)
  }
  let this = g_pfx(" ", this)
  "USE" + kind + this
}

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

///|
pub fn Generator::loaddata_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  let is_overwrite = expression.has("overwrite")
  let overwrite = g_when(is_overwrite, " OVERWRITE")
  let mut this = self.sql_key(expression, "this")
  match expression.arg("files") {
    Some(files) => {
      let files_sql = self.expressions(expression=files, flat=true)
      let files_sql = "FILES" + self.wrap_str(files_sql)
      if is_overwrite {
        this = " " + this
      } else if expression.has("temp") {
        this = " INTO TEMP TABLE " + this
      } else {
        this = " INTO TABLE " + this
      }
      return "LOAD DATA" + overwrite + this + " FROM " + files_sql
    }
    None => ()
  }
  let local_ = g_when(expression.has("local"), " LOCAL")
  let inpath = " INPATH " + self.sql_key(expression, "inpath")
  let this = " INTO TABLE " + this
  let partition = g_pfx(" ", self.sql_key(expression, "partition"))
  let input_format = g_pfx(
    " INPUTFORMAT ",
    self.sql_key(expression, "input_format"),
  )
  let serde = g_pfx(" SERDE ", self.sql_key(expression, "serde"))
  "LOAD DATA" +
  local_ +
  inpath +
  overwrite +
  this +
  partition +
  input_format +
  serde
}

///|
pub fn Generator::token_sql(self : Generator, token_type : TokenType) -> String {
  match self.cfg.token_mapping.get(token_type) {
    Some(s) => s
    None => token_type.name()
  }
}

///|
/// Python `_grant_or_revoke_sql`.
pub fn Generator::grant_or_revoke_sql(
  self : Generator,
  expression : Expr,
  keyword~ : String,
  preposition~ : String,
  grant_option_prefix? : String = "",
  grant_option_suffix? : String = "",
) -> String raise SqlglotError {
  let privileges_sql = self.expressions(
    expression~,
    key="privileges",
    flat=true,
  )
  let kind = g_pfx(" ", self.sql_key(expression, "kind"))
  let securable = g_pfx(" ", self.sql_key(expression, "securable"))
  let principals = self.expressions(expression~, key="principals", flat=true)
  let (grant_option_prefix, grant_option_suffix) = if !expression.has(
      "grant_option",
    ) {
    ("", "")
  } else {
    (grant_option_prefix, grant_option_suffix)
  }
  let cascade = g_pfx(" ", self.sql_key(expression, "cascade"))
  keyword +
  " " +
  grant_option_prefix +
  privileges_sql +
  " ON" +
  kind +
  securable +
  " " +
  preposition +
  " " +
  principals +
  grant_option_suffix +
  cascade
}

///|
pub fn Generator::grant_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.grant_or_revoke_sql(
    expression,
    keyword="GRANT",
    preposition="TO",
    grant_option_suffix=" WITH GRANT OPTION",
  )
}

///|
pub fn Generator::revoke_sql(
  self : Generator,
  expression : Expr,
) -> String raise SqlglotError {
  self.grant_or_revoke_sql(
    expression,
    keyword="REVOKE",
    preposition="FROM",
    grant_option_prefix="GRANT OPTION FOR ",
  )
}

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

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