// Port of sqlglot/parser.py: MATCH_RECOGNIZE, LATERAL, joins, indexes, tables,
// UNNEST, VALUES, TABLESAMPLE and PIVOT.

///|
pub fn Parser::parse_match_recognize(self : Parser) -> Expr? raise SqlglotError {
  if !self.match_(MATCH_RECOGNIZE) {
    return None
  }
  self.match_l_paren()
  let partition = self.parse_partition_by()
  let order = self.parse_order()
  let measures = if self.match_text("MEASURES") {
    Some(self.parse_csv(() => Some(self.parse_match_recognize_measure())))
  } else {
    None
  }
  let rows = if self.match_text_seq(["ONE", "ROW", "PER", "MATCH"]) {
    Some(var_("ONE ROW PER MATCH"))
  } else if self.match_text_seq(["ALL", "ROWS", "PER", "MATCH"]) {
    let mut text = "ALL ROWS PER MATCH"
    if self.match_text_seq(["SHOW", "EMPTY", "MATCHES"]) {
      text += " SHOW EMPTY MATCHES"
    } else if self.match_text_seq(["OMIT", "EMPTY", "MATCHES"]) {
      text += " OMIT EMPTY MATCHES"
    } else if self.match_text_seq(["WITH", "UNMATCHED", "ROWS"]) {
      text += " WITH UNMATCHED ROWS"
    }
    Some(var_(text))
  } else {
    None
  }
  let after = if self.match_text_seq(["AFTER", "MATCH", "SKIP"]) {
    let mut text = "AFTER MATCH SKIP"
    if self.match_text_seq(["PAST", "LAST", "ROW"]) {
      text += " PAST LAST ROW"
    } else if self.match_text_seq(["TO", "NEXT", "ROW"]) {
      text += " TO NEXT ROW"
    } else if self.match_text_seq(["TO", "FIRST"]) ||
      self.match_text_seq(["TO", "LAST"]) {
      let direction = self.prev_upper()
      let pattern_var = self.advance_any()
      if pattern_var is None {
        self.raise_error(
          "Expecting pattern variable after AFTER MATCH SKIP TO \{direction}",
        )
      }
      let pv = match pattern_var {
        Some(t) => t.text
        None => ""
      }
      text += " TO \{direction} \{pv}"
    }
    Some(var_(text))
  } else {
    None
  }
  let pattern = if self.match_text("PATTERN") {
    self.match_l_paren()
    if !self.curr.ok() {
      self.raise_error("Expecting )", token=self.curr)
    }
    let mut paren = 1
    let start = self.curr
    let mut end = self.prev
    while self.curr.ok() && paren > 0 {
      if self.curr.token_type == L_PAREN {
        paren += 1
      }
      if self.curr.token_type == R_PAREN {
        paren -= 1
      }
      end = self.prev
      self.advance()
    }
    if paren > 0 {
      self.raise_error("Expecting )", token=self.curr)
    }
    Some(var_(self.find_sql(start, end)))
  } else {
    None
  }
  let define = if self.match_text("DEFINE") {
    Some(self.parse_csv(() => self.parse_name_as_expression()))
  } else {
    None
  }
  self.match_r_paren()
  let alias = self.parse_table_alias()
  Some(
    self.expression(
      mk(MatchRecognize, [
        ("partition_by", partition),
        ("order", order),
        ("measures", measures),
        ("rows", rows),
        ("after", after),
        ("pattern", pattern),
        ("define", define),
        ("alias", alias),
      ]),
    ),
  )
}

///|
pub fn Parser::parse_lateral(self : Parser) -> Expr? raise SqlglotError {
  match self.fns.hooks.parse_lateral {
    Some(f) => f(self)
    None => self.parse_lateral_base()
  }
}

///|
pub fn Parser::parse_lateral_base(self : Parser) -> Expr? raise SqlglotError {
  let cross_apply : Bool? = if self.match_pair(CROSS, APPLY) {
    Some(true)
  } else if self.match_pair(OUTER, APPLY) {
    Some(false)
  } else {
    None
  }
  let mut this : Expr? = None
  let mut view : Bool? = None
  let mut outer : Bool? = None
  if cross_apply is Some(_) {
    this = self.parse_select(table=true)
  } else if self.match_(LATERAL) {
    this = self.parse_select(table=true)
    view = Some(self.match_(VIEW))
    outer = Some(self.match_(OUTER))
  } else {
    return None
  }
  if this is None {
    this = expr_or(self.parse_unnest(), () => {
      expr_or(self.parse_function(), () => self.parse_id_var(any_token=false))
    })
    while self.match_(DOT) {
      let expression = expr_or(self.parse_function(), () => {
        self.parse_id_var(any_token=false)
      })
      this = Some(mk(Dot, [("this", this), ("expression", expression)]))
    }
  }
  let mut ordinality : Bool? = None
  let mut table_alias : Expr? = None
  if view == Some(true) {
    let table = self.parse_id_var(any_token=false)
    let columns = if self.match_(ALIAS) {
      self.parse_csv(() => self.parse_id_var())
    } else {
      []
    }
    table_alias = Some(
      self.expression(mk(TableAlias, [("this", table), ("columns", columns)])),
    )
  } else {
    match this {
      Some(t) if t.kind.is_any([Subquery, Unnest]) && !t.alias().is_empty() =>
        table_alias = Some(t.arg("alias").unwrap().pop())
      _ => {
        ordinality = Some(self.match_pair(WITH, ORDINALITY))
        table_alias = self.parse_table_alias()
      }
    }
  }
  Some(
    self.expression(
      mk(Lateral, [
        ("this", this),
        ("view", view),
        ("outer", outer),
        ("alias", table_alias),
        ("cross_apply", cross_apply),
        ("ordinality", ordinality),
      ]),
    ),
  )
}

///|
pub fn Parser::parse_stream(self : Parser) -> Expr? raise SqlglotError {
  let index = self.index
  if self.match_(STREAM) {
    match self.try_parse(() => self.parse_table()) {
      Some(this) => return Some(self.expression(mk1(Stream, this)))
      None => ()
    }
    self.retreat(index)
  }
  None
}

///|
pub fn Parser::parse_join_parts(
  self : Parser,
) -> (Token?, Token?, Token?) raise SqlglotError {
  match self.fns.hooks.parse_join_parts {
    Some(f) => f(self)
    None => self.parse_join_parts_base()
  }
}

///|
pub fn Parser::parse_join_parts_base(self : Parser) -> (Token?, Token?, Token?) {
  let method = if self.match_set(self.cfg.join_methods) {
    Some(self.prev)
  } else {
    None
  }
  let side = if self.match_set(self.cfg.join_sides) {
    Some(self.prev)
  } else {
    None
  }
  let kind = if self.match_set(self.cfg.join_kinds) {
    Some(self.prev)
  } else {
    None
  }
  (method, side, kind)
}

///|
pub fn Parser::parse_using_identifiers(
  self : Parser,
) -> Array[Expr] raise SqlglotError {
  fn parse_column_as_identifier() -> Expr? raise SqlglotError {
    let this = self.parse_column()
    match this {
      Some(t) if t.kind.is_a(Column) => t.this()
      _ => this
    }
  }

  self.parse_wrapped_csv(parse_column_as_identifier, optional=true)
}

///|
pub fn Parser::parse_join(
  self : Parser,
  skip_join_token? : Bool = false,
  parse_bracket? : Bool = false,
  alias_tokens? : TokenSet,
) -> Expr? raise SqlglotError {
  match self.fns.hooks.parse_join {
    Some(f) => f(self, skip_join_token, parse_bracket, alias_tokens)
    None =>
      self.parse_join_base(skip_join_token~, parse_bracket~, alias_tokens?)
  }
}

///|
pub fn Parser::parse_join_base(
  self : Parser,
  skip_join_token? : Bool = false,
  parse_bracket? : Bool = false,
  alias_tokens? : TokenSet,
) -> Expr? raise SqlglotError {
  if self.match_(COMMA) {
    let table = self.try_parse(() => self.parse_table(alias_tokens?))
    let cross_join = match table {
      Some(t) => Some(self.expression(mk1(Join, t)))
      None => None
    }
    match cross_join {
      Some(cj) if self.cfg.joins_have_equal_precedence =>
        cj.set("kind", "CROSS")
      _ => ()
    }
    return cross_join
  }
  let index = self.index
  let (method0, side0, kind0) = self.parse_join_parts()
  let mut method = method0
  let mut side = side0
  let mut kind = kind0
  let directed = self.match_text("DIRECTED")
  let hint = if self.match_text_set(self.cfg.join_hints) {
    Some(self.prev.text)
  } else {
    None
  }
  let join = self.match_(JOIN) ||
    (match kind {
      Some(k) => k.token_type == STRAIGHT_JOIN
      None => false
    })
  let join_comments = self.prev_comments
  if !skip_join_token && !join {
    self.retreat(index)
    kind = None
    method = None
    side = None
  }
  let outer_apply = self.match_pair(OUTER, APPLY, advance=false)
  let cross_apply = self.match_pair(CROSS, APPLY, advance=false)
  if !skip_join_token && !join && !outer_apply && !cross_apply {
    return None
  }
  let this = self.parse_table(parse_bracket~, alias_tokens?)
  let j = mk1(Join, this)
  match kind {
    Some(k) if k.token_type == ARRAY && self.match_(COMMA) =>
      j.set(
        "expressions",
        self.parse_csv(() => self.parse_table(parse_bracket~, alias_tokens?)),
      )
    _ => ()
  }
  match method {
    Some(m) => j.set("method", py_upper(m.text))
    None => ()
  }
  match side {
    Some(s) => j.set("side", py_upper(s.text))
    None => ()
  }
  match kind {
    Some(k) => j.set("kind", py_upper(k.text))
    None => ()
  }
  match hint {
    Some(h) if !h.is_empty() => j.set("hint", h)
    _ => ()
  }
  if self.match_(MATCH_CONDITION) {
    j.set("match_condition", self.parse_wrapped(() => self.parse_comparison()))
  }
  if self.match_(ON) {
    j.set("on", self.parse_disjunction())
  } else if self.match_(USING) {
    j.set("using", self.parse_using_identifiers())
  } else if method is None &&
    !(outer_apply || cross_apply) &&
    !(match this {
      Some(t) => t.kind.is_a(Unnest)
      None => false
    }) &&
    !(match kind {
      Some(k) => k.token_type == CROSS || k.token_type == ARRAY
      None => false
    }) {
    let index = self.index
    let mut joins : Array[Expr]? = Some(self.parse_joins(alias_tokens?))
    let has_joins = match joins {
      Some(js) => !js.is_empty()
      None => false
    }
    if has_joins && self.match_(ON) {
      j.set("on", self.parse_disjunction())
    } else if has_joins && self.match_(USING) {
      j.set("using", self.parse_using_identifiers())
    } else {
      joins = None
      self.retreat(index)
    }
    match this {
      Some(t) =>
        t.set(
          "joins",
          match joins {
            Some(js) if !js.is_empty() => Some(js)
            _ => None
          },
        )
      None => ()
    }
  }
  j.set("pivots", self.parse_pivots())
  let comments = join_comments.copy()
  for token in [method, side, kind] {
    match token {
      Some(t) => comments.append(t.comments)
      None => ()
    }
  }
  if self.cfg.add_join_on_true &&
    !j.has("on") &&
    !j.has("using") &&
    !j.has("method") &&
    (match j.str_arg("kind") {
      None => true
      Some("INNER" | "OUTER") => true
      _ => false
    }) {
    j.set("on", true_())
  }
  if directed {
    j.set("directed", directed)
  }
  Some(self.expression(j, comments~))
}

///|
pub fn Parser::parse_opclass(self : Parser) -> Expr? raise SqlglotError {
  let this = self.parse_disjunction()
  if self.match_text_set(self.cfg.opclass_follow_keywords, advance=false) {
    return this
  }
  if !self.match_set(self.cfg.optype_follow_tokens, advance=false) {
    return Some(
      self.expression(
        mk(Opclass, [("this", this), ("expression", self.parse_table_parts())]),
      ),
    )
  }
  this
}

///|
pub fn Parser::parse_index_params(self : Parser) -> Expr? raise SqlglotError {
  match self.fns.hooks.parse_index_params {
    Some(f) => f(self)
    None => self.parse_index_params_base()
  }
}

///|
pub fn Parser::parse_index_params_base(
  self : Parser,
) -> Expr? raise SqlglotError {
  let using_ = if self.match_(USING) {
    self.parse_var(any_token=true)
  } else {
    None
  }
  let columns = if self.match_(L_PAREN, advance=false) {
    Some(self.parse_wrapped_csv(() => self.parse_with_operator()))
  } else {
    None
  }
  let include = if self.match_text("INCLUDE") {
    Some(self.parse_wrapped_id_vars())
  } else {
    None
  }
  let partition_by = self.parse_partition_by()
  let with_storage = self.andl(self.match_(WITH), () => {
    self.parse_wrapped_properties()
  })
  let tablespace = if self.match_text_seq(["USING", "INDEX", "TABLESPACE"]) {
    self.parse_var(any_token=true)
  } else {
    None
  }
  let where_ = self.parse_where()
  let on = if self.match_(ON) { self.parse_field() } else { None }
  Some(
    self.expression(
      mk(IndexParameters, [
        ("using", using_),
        ("columns", columns),
        ("include", include),
        ("partition_by", partition_by),
        ("where", where_),
        ("with_storage", with_storage),
        ("tablespace", tablespace),
        ("on", on),
      ]),
    ),
  )
}

///|
pub fn Parser::parse_index(
  self : Parser,
  index? : Expr?,
  anonymous? : Bool = false,
) -> Expr? raise SqlglotError {
  let mut index : Expr? = match index {
    Some(i) => i
    None => None
  }
  let mut unique : Bool? = None
  let mut primary : Bool? = None
  let mut amp : Bool? = None
  let mut table : Expr? = None
  if index is Some(_) || anonymous {
    self.match_(ON) |> ignore
    self.match_(TABLE) |> ignore
    table = self.parse_table_parts(schema=true)
  } else {
    unique = Some(self.match_(UNIQUE))
    primary = Some(self.match_text("PRIMARY"))
    amp = Some(self.match_text("AMP"))
    if !self.match_(INDEX) {
      return None
    }
    index = self.parse_id_var()
    table = None
  }
  let params = self.parse_index_params()
  Some(
    self.expression(
      mk(Index, [
        ("this", index),
        ("table", table),
        ("unique", unique),
        ("primary", primary),
        ("amp", amp),
        ("params", params),
      ]),
    ),
  )
}

///|
pub fn Parser::parse_table_hints(
  self : Parser,
) -> Array[Expr]? raise SqlglotError {
  match self.fns.hooks.parse_table_hints {
    Some(f) => f(self)
    None => self.parse_table_hints_base()
  }
}

///|
pub fn Parser::parse_table_hints_base(
  self : Parser,
) -> Array[Expr]? raise SqlglotError {
  let hints = []
  if self.match_pair(WITH, L_PAREN) {
    hints.push(
      self.expression(
        mk(WithTableHint, [
          (
            "expressions",
            self.parse_csv(() => {
              expr_or(self.parse_function(), () => {
                self.parse_var(any_token=true)
              })
            }),
          ),
        ]),
      ),
    )
    self.match_r_paren()
  } else {
    while self.match_set(self.cfg.table_index_hint_tokens) {
      let hint = mk1(IndexTableHint, self.prev_upper())
      self.match_any([INDEX, KEY]) |> ignore
      if self.match_(FOR) {
        match self.advance_any() {
          Some(_) => hint.set("target", self.prev_upper())
          None => hint.set("target", false)
        }
      }
      hint.set("expressions", self.parse_wrapped_id_vars())
      hints.push(hint)
    }
  }
  if hints.is_empty() {
    None
  } else {
    Some(hints)
  }
}

///|
pub fn Parser::parse_table_part(
  self : Parser,
  schema? : Bool = false,
) -> Expr? raise SqlglotError {
  match self.fns.hooks.parse_table_part {
    Some(f) => f(self, schema)
    None => self.parse_table_part_base(schema~)
  }
}

///|
pub fn Parser::parse_table_part_base(
  self : Parser,
  schema? : Bool = false,
) -> Expr? raise SqlglotError {
  let f = if !schema {
    self.parse_function(optional_parens=false)
  } else {
    None
  }
  match f {
    Some(_) => f
    None =>
      expr_or(self.parse_id_var(any_token=false), () => {
        expr_or(self.parse_string_as_identifier(), () => {
          self.parse_placeholder()
        })
      })
  }
}

///|
pub fn Parser::parse_table_parts_fast(
  self : Parser,
) -> Expr? raise SqlglotError {
  let index = self.index
  let mut parts : Array[Expr]? = None
  let mut all_comments : Array[String]? = None
  while self.match_set(self.cfg.identifier_tokens) {
    let token = self.prev
    let comments = self.prev_comments
    let has_dot = self.match_(DOT)
    let curr_tt = self.curr.token_type
    if !has_dot {
      if self.cfg.table_postfix_tokens.contains(curr_tt) {
        self.retreat(index)
        return None
      }
    } else if !self.cfg.identifier_tokens.contains(curr_tt) {
      self.retreat(index)
      return None
    }
    if parts is None {
      parts = Some([])
    }
    if !comments.is_empty() {
      if all_comments is None {
        all_comments = Some([])
      }
      all_comments.unwrap().append(comments)
      self.prev_comments = []
    }
    parts
    .unwrap()
    .push(
      self.expression(
        mk(Identifier, [
          ("this", token.text),
          ("quoted", token.token_type == IDENTIFIER),
        ]),
        token~,
      ),
    )
    if !has_dot {
      break
    }
  }
  let parts = match parts {
    Some(p) => p
    None => return None
  }
  let n = parts.length()
  let table = if n == 1 {
    mk1(Table, parts[0])
  } else if n == 2 {
    mk(Table, [("this", parts[1]), ("db", parts[0])])
  } else {
    let mut this = parts[2]
    for i in 3.. table.add_comments(Some(c))
    _ => ()
  }
  Some(table)
}

///|
pub fn Parser::parse_table_parts(
  self : Parser,
  schema? : Bool = false,
  is_db_reference? : Bool = false,
  wildcard? : Bool = false,
  fast? : Bool = false,
) -> Expr? raise SqlglotError {
  match self.fns.hooks.parse_table_parts {
    Some(f) => f(self, schema, is_db_reference, wildcard, fast)
    None =>
      self.parse_table_parts_base(schema~, is_db_reference~, wildcard~, fast~)
  }
}

///|
pub fn Parser::parse_table_parts_base(
  self : Parser,
  schema? : Bool = false,
  is_db_reference? : Bool = false,
  wildcard? : Bool = false,
  fast? : Bool = false,
) -> Expr? raise SqlglotError {
  if fast {
    return self.parse_table_parts_fast()
  }
  let mut catalog : Value? = None
  let mut db : Value? = None
  let mut table : Value? = self.parse_table_part(schema~).map(x => Node(x))
  while self.match_(DOT) {
    if catalog is Some(c) && c.truthy() {
      let expression = self.parse_table_part(schema~)
      table = Some(
        Node(
          self.expression(
            mk(Dot, [("this", table), ("expression", expression)]),
          ),
        ),
      )
    } else {
      catalog = db
      db = table
      table = match self.parse_table_part(schema~) {
        Some(t) => Some(Node(t))
        None => Some(Str(""))
      }
    }
  }
  let table_truthy = match table {
    Some(v) => v.truthy()
    None => false
  }
  let table_is_ident = match table {
    Some(Node(t)) => t.kind == Identifier
    _ => false
  }
  if wildcard &&
    self.is_connected() &&
    (table_is_ident || !table_truthy) &&
    self.match_(STAR) {
    match table {
      Some(Node(t)) if t.kind == Identifier =>
        t.args["this"] = Str(t.text("this") + "*")
      _ => table = Some(Node(mk1(Identifier, "*")))
    }
  }
  if is_db_reference {
    catalog = db
    db = table
    table = None
  }
  let table_truthy = match table {
    Some(v) => v.truthy()
    None => false
  }
  let db_truthy = match db {
    Some(v) => v.truthy()
    None => false
  }
  if !table_truthy && !is_db_reference {
    self.raise_error("Expected table name but got \{token_repr(self.curr)}")
  }
  if !db_truthy && is_db_reference {
    self.raise_error("Expected database name but got \{token_repr(self.curr)}")
  }
  let table = self.expression(
    mk(Table, [("this", table), ("db", db), ("catalog", catalog)]),
  )
  let comments = []
  for part in table.parts() {
    let pc = part.pop_comments()
    if !pc.is_empty() {
      comments.append(pc)
    }
  }
  if !comments.is_empty() {
    table.add_comments(Some(comments))
  }
  match self.parse_changes() {
    Some(c) => table.set("changes", c)
    None => ()
  }
  match self.parse_historical_data() {
    Some(at_before) => table.set("when", at_before)
    None => ()
  }
  match self.parse_pivots() {
    Some(p) => table.set("pivots", p)
    None => ()
  }
  Some(table)
}

///|
/// Python `repr(token)`.
pub fn token_repr(t : Token) -> String {
  let comments = "[" + t.comments.map(c => py_repr_str(c)).join(", ") + "]"
  ""
}

///|
pub fn Parser::parse_table(
  self : Parser,
  schema? : Bool = false,
  joins? : Bool = false,
  alias_tokens? : TokenSet,
  parse_bracket? : Bool = false,
  is_db_reference? : Bool = false,
  parse_partition? : Bool = false,
  consume_pipe? : Bool = false,
) -> Expr? raise SqlglotError {
  let args : TableArgs = {
    schema,
    joins,
    alias_tokens,
    parse_bracket,
    is_db_reference,
    parse_partition,
    consume_pipe,
  }
  match self.fns.hooks.parse_table {
    Some(f) => f(self, args)
    None => self.parse_table_base(args)
  }
}

///|
pub fn Parser::parse_table_base(
  self : Parser,
  args : TableArgs,
) -> Expr? raise SqlglotError {
  let {
    schema,
    joins,
    alias_tokens,
    parse_bracket,
    is_db_reference,
    parse_partition,
    consume_pipe,
  } = args
  let tokens = match alias_tokens {
    Some(t) => t
    None => self.cfg.table_alias_tokens
  }
  if !schema && !is_db_reference && !consume_pipe && !joins {
    let index = self.index
    match self.parse_table_parts(fast=true) {
      Some(table) => {
        let curr_tt = self.curr.token_type
        let next_tt = self.next.token_type
        let fast_terminators = self.cfg.table_terminators
        if fast_terminators.contains(curr_tt) && next_tt != MATCH_CONDITION {
          return Some(table)
        }
        let postfix_tokens = self.cfg.table_postfix_tokens
        if !postfix_tokens.contains(curr_tt) &&
          !postfix_tokens.contains(next_tt) {
          match self.parse_table_alias(alias_tokens=tokens) {
            Some(alias) => table.set("alias", alias)
            None => ()
          }
          if fast_terminators.contains(self.curr.token_type) {
            return Some(table)
          }
        }
        self.retreat(index)
      }
      None => ()
    }
  }
  match self.parse_stream() {
    Some(s) => return Some(s)
    None => ()
  }
  match self.parse_lateral() {
    Some(l) => return Some(l)
    None => ()
  }
  match self.parse_unnest() {
    Some(u) => return Some(u)
    None => ()
  }
  match self.parse_derived_table_values() {
    Some(v) => return Some(v)
    None => ()
  }
  match self.parse_select(table=true, consume_pipe~) {
    Some(subquery) => {
      if !subquery.has("pivots") {
        subquery.set("pivots", self.parse_pivots())
      }
      if joins {
        for join in self.parse_joins() {
          subquery.append("joins", join)
        }
      }
      return Some(subquery)
    }
    None => ()
  }
  let bracket = if parse_bracket { self.parse_bracket(None) } else { None }
  let bracket = match bracket {
    Some(b) => Some(self.expression(mk1(Table, b)))
    None => None
  }
  let rows_from_tables = if self.match_text_seq(["ROWS", "FROM"]) {
    Some(self.parse_wrapped_csv(() => self.parse_table()))
  } else {
    None
  }
  let rows_from = match rows_from_tables {
    Some(t) if !t.is_empty() =>
      Some(self.expression(mk(Table, [("rows_from", t)])))
    _ => None
  }
  let only = self.match_(ONLY)
  let mut this = match bracket {
    Some(b) => b
    None =>
      match rows_from {
        Some(r) => r
        None =>
          match
            self.parse_bracket(
              self.parse_table_parts(schema~, is_db_reference~),
            ) {
            Some(t) => t
            None => return None
          }
      }
  }
  if only {
    this.set("only", only)
  }
  self.match_(STAR) |> ignore
  let parse_partition = parse_partition || self.cfg.supports_partition_selection
  if parse_partition && self.match_(PARTITION, advance=false) {
    this.set("partition", self.parse_partition())
  }
  if schema {
    return self.parse_schema(this=Some(this))
  }
  if self.dialect.cfg.alias_post_version {
    this.set("version", self.parse_version())
  }
  if self.dialect.cfg.alias_post_tablesample {
    this.set("sample", self.parse_table_sample())
  }
  match self.parse_table_alias(alias_tokens=tokens) {
    Some(alias) => {
      this.set("alias", alias)
      if this.kind == Table && !this.has("when") {
        this.set("when", self.parse_historical_data())
      }
    }
    None => ()
  }
  if self.match_(INDEXED_BY) {
    this.set("indexed", self.parse_table_parts())
  } else if self.match_text_seq(["NOT", "INDEXED"]) {
    this.set("indexed", false)
  }
  if this.kind == Table && self.match_text("AT") {
    let col = this.to_column(copy=false)
    return Some(
      self.expression(
        mk(AtIndex, [("this", col), ("expression", self.parse_id_var())]),
      ),
    )
  }
  this.set("hints", self.parse_table_hints())
  if !this.has("pivots") {
    this.set("pivots", self.parse_pivots())
  }
  if !self.dialect.cfg.alias_post_tablesample {
    this.set("sample", self.parse_table_sample())
  }
  if !self.dialect.cfg.alias_post_version {
    this.set("version", self.parse_version())
  }
  if joins {
    for join in self.parse_joins(alias_tokens?) {
      this.append("joins", join)
    }
  }
  if self.match_pair(WITH, ORDINALITY) {
    this.set("ordinality", true)
    this.set("alias", self.parse_table_alias())
  }
  match this.this() {
    Some(table_from_rows) if this.kind == Table &&
      table_from_rows.kind == TableFromRows => {
      for kv in TableFromRows.arg_types() {
        let arg = kv.0
        if arg != "this" {
          table_from_rows.set(arg, this.args.get(arg))
        }
      }
      this = table_from_rows
    }
    _ => ()
  }
  Some(this)
}

///|
pub fn Parser::parse_version(self : Parser) -> Expr? raise SqlglotError {
  let mut this : String? = None
  for pv in self.cfg.version_phrases {
    if self.match_text_seq(pv.0) {
      this = Some(pv.1)
      break
    }
  }
  let this = match this {
    Some(t) => t
    None => return None
  }
  let mut kind = ""
  let mut expression : Expr? = None
  if self.match_any([FROM, BETWEEN]) {
    kind = self.prev_upper()
    let start = self.parse_bitwise()
    self.match_texts(["TO", "AND"]) |> ignore
    let end = self.parse_bitwise()
    expression = Some(
      self.expression(
        mk(Tuple, [("expressions", [start, end].filter_map(x => x))]),
      ),
    )
  } else if self.match_text_seq(["CONTAINED", "IN"]) {
    kind = "CONTAINED IN"
    expression = Some(
      self.expression(
        mk(Tuple, [
          ("expressions", self.parse_wrapped_csv(() => self.parse_bitwise())),
        ]),
      ),
    )
  } else if self.match_(ALL) {
    kind = "ALL"
    expression = None
  } else {
    self.match_text_seq(["AS", "OF"]) |> ignore
    kind = "AS OF"
    expression = self.parse_type()
  }
  Some(
    self.expression(
      mk(Version, [("this", this), ("expression", expression), ("kind", kind)]),
    ),
  )
}

///|
pub fn Parser::parse_historical_data(self : Parser) -> Expr? raise SqlglotError {
  let index = self.index
  let mut historical_data : Expr? = None
  if self.match_text_set(self.cfg.historical_data_prefix) {
    let this = self.prev_upper()
    let kind : Value = if self.match_(L_PAREN) &&
      self.match_text_set(self.cfg.historical_data_kind) {
      Str(self.prev_upper())
    } else {
      Bool(false)
    }
    let expression = if self.match_(FARROW) {
      self.parse_bitwise()
    } else {
      None
    }
    match expression {
      Some(e) => {
        self.match_r_paren()
        historical_data = Some(
          self.expression(
            mk(HistoricalData, [
              ("this", this),
              ("kind", kind),
              ("expression", e),
            ]),
          ),
        )
      }
      None => self.retreat(index)
    }
  }
  historical_data
}

///|
pub fn Parser::parse_changes(self : Parser) -> Expr? raise SqlglotError {
  if !self.match_text_seq(["CHANGES", "(", "INFORMATION", "=>"]) {
    return None
  }
  let information = self.parse_var(any_token=true)
  self.match_r_paren()
  let at_before = self.parse_historical_data()
  let end = self.parse_historical_data()
  Some(
    self.expression(
      mk(Changes, [
        ("information", information),
        ("at_before", at_before),
        ("end", end),
      ]),
    ),
  )
}

///|
pub fn Parser::parse_unnest(
  self : Parser,
  with_alias? : Bool = true,
) -> Expr? raise SqlglotError {
  match self.fns.hooks.parse_unnest {
    Some(f) => f(self, with_alias)
    None => self.parse_unnest_base(with_alias~)
  }
}

///|
pub fn Parser::parse_unnest_base(
  self : Parser,
  with_alias? : Bool = true,
) -> Expr? raise SqlglotError {
  if !self.match_pair(UNNEST, L_PAREN, advance=false) {
    return None
  }
  self.advance()
  let expressions = self.parse_wrapped_csv(() => self.parse_equality())
  let mut offset : Value? = Some(Bool(self.match_pair(WITH, ORDINALITY)))
  let alias = if with_alias { self.parse_table_alias() } else { None }
  match alias {
    Some(alias) => {
      if self.dialect.cfg.unnest_column_only {
        if alias.has("columns") {
          self.raise_error("Unexpected extra column alias in unnest.")
        }
        alias.set("columns", [alias.this()].filter_map(x => x))
        alias.set("this", null_arg)
      }
      let columns = alias.list("columns")
      if offset is Some(Bool(true)) && expressions.length() < columns.length() {
        let last = columns[columns.length() - 1]
        let raw = alias.raw_list("columns")
        raw.pop() |> ignore
        offset = Some(Node(last))
      }
    }
    None => ()
  }
  let offset_truthy = match offset {
    Some(v) => v.truthy()
    None => false
  }
  if !offset_truthy && self.match_pair(WITH, OFFSET) {
    self.match_(ALIAS) |> ignore
    offset = match
      self.parse_id_var(
        any_token=false,
        tokens=self.cfg.unnest_offset_alias_tokens,
      ) {
      Some(o) => Some(Node(o))
      None => Some(Node(to_identifier("offset")))
    }
  }
  Some(
    self.expression(
      mk(Unnest, [
        ("expressions", expressions),
        ("alias", alias),
        ("offset", offset),
      ]),
    ),
  )
}

///|
pub fn Parser::parse_derived_table_values(
  self : Parser,
  allow_value_synonym? : Bool = false,
) -> Expr? raise SqlglotError {
  let is_derived = self.match_pair(L_PAREN, VALUES)
  if !is_derived &&
    !(self.match_text("VALUES") ||
    self.match_text_seq(["FORMAT", "VALUES"]) ||
    (allow_value_synonym && self.match_text("VALUE"))) {
    return None
  }
  let expressions = self.parse_csv(() => self.parse_value())
  let alias = self.parse_table_alias()
  if is_derived {
    self.match_r_paren()
  }
  let alias = expr_or(alias, () => self.parse_table_alias())
  Some(
    self.expression(
      mk(Values, [("expressions", expressions), ("alias", alias)]),
    ),
  )
}

///|
pub fn Parser::parse_table_sample(
  self : Parser,
  as_modifier? : Bool = false,
) -> Expr? raise SqlglotError {
  match self.fns.hooks.parse_table_sample {
    Some(f) => f(self, as_modifier)
    None => self.parse_table_sample_base(as_modifier~)
  }
}

///|
pub fn Parser::parse_table_sample_base(
  self : Parser,
  as_modifier? : Bool = false,
) -> Expr? raise SqlglotError {
  if !self.match_(TABLE_SAMPLE) &&
    !(as_modifier && self.match_text_seq(["USING", "SAMPLE"])) {
    return None
  }
  let mut bucket_numerator : Expr? = None
  let mut bucket_denominator : Expr? = None
  let mut bucket_field : Expr? = None
  let mut percent : Expr? = None
  let mut size : Expr? = None
  let mut seed : Value? = None
  let mut method = self.parse_var(tokens=TokenSet::new([ROW]), upper=true)
  let matched_l_paren = self.match_(L_PAREN)
  let mut num : Expr? = None
  let mut expressions : Array[Expr]? = None
  if self.cfg.tablesample_csv {
    expressions = Some(self.parse_csv(() => self.parse_primary()))
  } else {
    num = if self.match_(NUMBER, advance=false) {
      self.parse_factor(parse_mod=false)
    } else {
      expr_or(self.parse_primary(), () => self.parse_placeholder())
    }
  }
  if self.match_text("BUCKET") {
    bucket_numerator = self.parse_number()
    self.match_text_seq(["OUT", "OF"]) |> ignore
    bucket_denominator = self.parse_number()
    self.match_(ON) |> ignore
    bucket_field = self.parse_field()
  } else if self.match_any([PERCENT, MOD]) {
    percent = num
  } else if self.match_(ROWS) || !self.dialect.cfg.tablesample_size_is_percent {
    size = num
  } else {
    percent = num
  }
  if matched_l_paren {
    self.match_r_paren()
  }
  if self.match_(L_PAREN) {
    method = self.parse_var(upper=true)
    seed = self.andv(self.match_(COMMA), () => self.parse_number())
    self.match_r_paren()
  } else if self.match_texts(["SEED", "REPEATABLE"]) {
    seed = self.parse_wrapped(() => self.parse_number()).map(x => Node(x))
  }
  if method is None {
    match self.cfg.default_sampling_method {
      Some(m) if !m.is_empty() => method = Some(var_(m))
      _ => ()
    }
  }
  Some(
    self.expression(
      mk(TableSample, [
        ("expressions", expressions),
        ("method", method),
        ("bucket_numerator", bucket_numerator),
        ("bucket_denominator", bucket_denominator),
        ("bucket_field", bucket_field),
        ("percent", percent),
        ("size", size),
        ("seed", seed),
      ]),
    ),
  )
}

///|
pub fn Parser::parse_pivots(self : Parser) -> Array[Expr]? raise SqlglotError {
  if self.curr.token_type != PIVOT && self.curr.token_type != UNPIVOT {
    return None
  }
  let out = []
  while self.parse_pivot() is Some(p) {
    out.push(p)
  }
  if out.is_empty() {
    None
  } else {
    Some(out)
  }
}

///|
pub fn Parser::parse_joins(
  self : Parser,
  alias_tokens? : TokenSet,
) -> Array[Expr] raise SqlglotError {
  let out = []
  while self.parse_join(alias_tokens?) is Some(j) {
    out.push(j)
  }
  out
}

///|
pub fn Parser::parse_unpivot_columns(self : Parser) -> Expr? raise SqlglotError {
  if !self.match_(INTO) {
    return None
  }
  let this = self.andv(self.match_text("NAME"), () => self.parse_column())
  let expressions = self.andl(self.match_text("VALUE"), () => {
    self.parse_csv(() => self.parse_column())
  })
  Some(
    self.expression(
      mk(UnpivotColumns, [("this", this), ("expressions", expressions)]),
    ),
  )
}

///|
pub fn Parser::parse_simplified_pivot(
  self : Parser,
  is_unpivot? : Bool,
) -> Expr? raise SqlglotError {
  fn parse_on() -> Expr? raise SqlglotError {
    let this = self.parse_bitwise()
    if self.match_(IN) {
      return self.parse_in(this)
    }
    if self.match_(ALIAS, advance=false) {
      return self.parse_alias(this)
    }
    this
  }

  let this = self.parse_table()
  let expressions = self.andl(self.match_(ON), () => self.parse_csv(parse_on))
  let into = self.parse_unpivot_columns()
  let using_ = self.andl(self.match_(USING), () => {
    self.parse_csv(() => self.parse_alias(self.parse_column()))
  })
  let group = self.parse_group()
  Some(
    self.expression(
      mk(Pivot, [
        ("this", this),
        ("expressions", expressions),
        ("using", using_),
        ("group", group),
        ("unpivot", is_unpivot),
        ("into", into),
      ]),
    ),
  )
}

///|
pub fn Parser::parse_pivot_in(self : Parser) -> Expr? raise SqlglotError {
  fn parse_aliased_expression() -> Expr? raise SqlglotError {
    let this = self.parse_select_or_expression()
    self.match_(ALIAS) |> ignore
    let alias = self.parse_bitwise()
    match alias {
      Some(a) => {
        let a = if a.kind.is_a(Column) && a.db().is_empty() {
          match a.this() {
            Some(t) => t
            None => a
          }
        } else {
          a
        }
        Some(self.expression(mk(PivotAlias, [("this", this), ("alias", a)])))
      }
      None => this
    }
  }

  let value = self.parse_column()
  if !self.match_(IN) {
    self.raise_error("Expecting IN")
  }
  if self.match_(L_PAREN) {
    let exprs = if self.match_(ANY) {
      [mk1(PivotAny, self.parse_order())]
    } else {
      self.parse_csv(parse_aliased_expression)
    }
    self.match_r_paren()
    return Some(
      self.expression(mk(In, [("this", value), ("expressions", exprs)])),
    )
  }
  Some(
    self.expression(mk(In, [("this", value), ("field", self.parse_id_var())])),
  )
}

///|
pub fn Parser::parse_pivot_aggregation(
  self : Parser,
) -> Expr? raise SqlglotError {
  match self.fns.hooks.parse_pivot_aggregation {
    Some(f) => f(self)
    None => self.parse_pivot_aggregation_base()
  }
}

///|
pub fn Parser::parse_pivot_aggregation_base(
  self : Parser,
) -> Expr? raise SqlglotError {
  let func = self.parse_function()
  if func is None {
    if self.prev.token_type == COMMA {
      return None
    }
    self.raise_error("Expecting an aggregation function in PIVOT")
  }
  self.parse_alias(func)
}

///|
fn unpivot_target(e : Expr) -> Expr {
  if e.kind.is_a(Column) && e.table_name().is_empty() {
    match e.this() {
      Some(t) => return t
      None => ()
    }
  }
  if e.kind == Tuple {
    e.set("expressions", e.expressions().map(unpivot_target))
  }
  e
}

///|
fn cartesian_product(lists : Array[Array[String]]) -> Array[Array[String]] {
  let mut result : Array[Array[String]] = [[]]
  for l in lists {
    let next = []
    for prefix in result {
      for x in l {
        next.push(prefix + [x])
      }
    }
    result = next
  }
  result
}

///|
pub fn Parser::parse_pivot(self : Parser) -> Expr? raise SqlglotError {
  let index = self.index
  let mut include_nulls : Bool? = None
  let mut unpivot = false
  if self.match_(PIVOT) {
    unpivot = false
  } else if self.match_(UNPIVOT) {
    unpivot = true
    if self.match_text_seq(["INCLUDE", "NULLS"]) {
      include_nulls = Some(true)
    } else if self.match_text_seq(["EXCLUDE", "NULLS"]) {
      include_nulls = Some(false)
    }
  } else {
    return None
  }
  if !self.match_(L_PAREN) {
    self.retreat(index)
    return None
  }
  let expressions = if unpivot {
    self.parse_csv(() => self.parse_column())
  } else {
    self.parse_csv(() => self.parse_pivot_aggregation())
  }
  if expressions.is_empty() {
    self.raise_error("Failed to parse PIVOT's aggregation list")
  }
  if !self.match_(FOR) {
    self.raise_error("Expecting FOR")
  }
  let fields = []
  while true {
    match self.try_parse(() => self.parse_pivot_in()) {
      Some(f) => fields.push(f)
      None => break
    }
  }
  let default_on_null = self.andv(
    self.match_text_seq(["DEFAULT", "ON", "NULL"]),
    () => self.parse_wrapped(() => self.parse_bitwise()),
  )
  let group = self.parse_group()
  self.match_r_paren()
  let pivot = self.expression(
    mk(Pivot, [
      ("expressions", expressions),
      ("fields", fields),
      ("unpivot", unpivot),
      ("include_nulls", include_nulls),
      ("default_on_null", default_on_null),
      ("group", group),
    ]),
  )
  if unpivot {
    pivot.set("expressions", pivot.expressions().map(unpivot_target))
    for pivot_field in pivot.list("fields") {
      if pivot_field.kind == In {
        match pivot_field.this() {
          Some(t) => pivot_field.set("this", unpivot_target(t))
          None => ()
        }
      }
    }
    pivot.set("value_columns_first", self.cfg.unpivot_value_columns_first)
  }
  if !self.match_any([PIVOT, UNPIVOT], advance=false) {
    pivot.set("alias", self.parse_table_alias())
  }
  if !unpivot {
    let names = self.pivot_column_names(expressions)
    let columns = []
    let all_fields : Array[Array[String]] = []
    for pivot_field in pivot.list("fields") {
      let pivot_field_expressions = pivot_field.expressions()
      if !pivot_field_expressions.is_empty() &&
        pivot_field_expressions[0].kind == PivotAny {
        continue
      }
      all_fields.push(
        pivot_field_expressions.map(fld => {
          if self.cfg.identify_pivot_strings && fld.kind != PivotAlias {
            expr_to_sql(fld)
          } else {
            fld.alias_or_name()
          }
        }),
      )
    }
    if !all_fields.is_empty() {
      if !names.is_empty() {
        all_fields.push(names)
      }
      for fld_parts in cartesian_product(all_fields) {
        let fld_parts = fld_parts.copy()
        if !names.is_empty() && self.cfg.prefixed_pivot_columns {
          let last = fld_parts.pop().unwrap()
          fld_parts.insert(0, last)
        }
        columns.push(to_identifier(fld_parts.join("_")))
      }
    }
    pivot.set("columns", columns)
    pivot.set("identify_pivot_strings", self.cfg.identify_pivot_strings)
    pivot.set("prefixed_pivot_columns", self.cfg.prefixed_pivot_columns)
    pivot.set("pivot_column_naming", self.cfg.pivot_column_naming)
  }
  Some(pivot)
}

///|
pub fn Parser::pivot_column_names(
  self : Parser,
  aggregations : Array[Expr],
) -> Array[String] raise SqlglotError {
  match self.fns.hooks.pivot_column_names {
    Some(f) => f(self, aggregations)
    None => {
      let out = []
      for agg in aggregations {
        let a = agg.alias()
        if !a.is_empty() {
          out.push(a)
        }
      }
      out
    }
  }
}