// Port of sqlglot/parser.py: SELECT, WITH/CTE, aliases, subqueries, query modifiers.
///|
pub fn Parser::parse_select(
self : Parser,
nested? : Bool = false,
table? : Bool = false,
parse_subquery_alias? : Bool = true,
parse_set_operation? : Bool = true,
consume_pipe? : Bool = true,
from_? : Expr,
) -> Expr? raise SqlglotError {
let mut query = self.parse_select_query(
nested~,
table~,
parse_subquery_alias~,
parse_set_operation~,
)
if consume_pipe && self.match_(PIPE_GT, advance=false) {
if query is None && from_ is Some(f) {
query = Some(select_star_from(Some(f)))
}
match query {
Some(q) if q.kind.is_a(Query) => {
query = self.parse_pipe_syntax_query(q)
match query {
Some(q2) if table => query = Some(q2.subquery(copy=false))
_ => ()
}
}
_ => ()
}
}
query
}
///|
pub fn Parser::parse_select_query(
self : Parser,
nested? : Bool = false,
table? : Bool = false,
parse_subquery_alias? : Bool = true,
parse_set_operation? : Bool = true,
) -> Expr? raise SqlglotError {
let cte = self.parse_with()
match cte {
Some(cte) => {
let mut this = match self.parse_statement() {
Some(t) => t
None => {
self.raise_error("Failed to parse any statement following CTE")
return Some(cte)
}
}
while this.kind.is_a(Subquery) && this.is_wrapper() {
match this.this() {
Some(t) => this = t
None => break
}
}
if this.kind.has_arg("with_") {
match this.arg("with_") {
Some(inner_cte) => {
cte.set("expressions", cte.expressions() + inner_cte.expressions())
if inner_cte.has("recursive") {
cte.set("recursive", true)
}
}
None => ()
}
this.set("with_", cte)
} else {
self.raise_error("\{this.kind.key()} does not support CTE")
this = cte
}
return Some(this)
}
None => ()
}
let mut from_ = if self.match_(FROM, advance=false) {
self.parse_from(joins=true, consume_pipe=true)
} else {
None
}
let mut this : Expr? = None
if self.match_(SELECT) {
let comments = self.prev_comments
let hint = self.parse_hint()
let mut all_ : Bool? = None
let mut matched_distinct = false
if self.next.ok() && self.next.token_type != DOT {
all_ = Some(self.match_(ALL))
matched_distinct = self.match_set(self.cfg.distinct_tokens)
}
let kind = if self.match_(ALIAS) && self.match_texts(["STRUCT", "VALUE"]) {
Some(self.prev_upper())
} else {
None
}
let mut distinct : Expr? = if matched_distinct {
let on = if self.match_(ON) {
self.parse_value(values=false)
} else {
None
}
Some(self.expression(mk(Distinct, [("on", on)])))
} else {
None
}
let operation_modifiers = []
while self.curr.ok() && self.match_text_set(self.cfg.operation_modifiers) {
operation_modifiers.push(var_(self.prev_upper()))
}
let limit = self.parse_limit(top=true)
if limit is Some(_) && !matched_distinct && all_ != Some(true) {
matched_distinct = self.match_set(self.cfg.distinct_tokens)
if matched_distinct {
let on = if self.match_(ON) {
self.parse_value(values=false)
} else {
None
}
distinct = Some(self.expression(mk(Distinct, [("on", on)])))
} else {
all_ = Some(self.match_(ALL))
}
}
if all_ == Some(true) && distinct is Some(_) {
self.raise_error("Cannot specify both ALL and DISTINCT after SELECT")
}
let (projections, exclude) = self.parse_projections()
let sel = self.expression(
mk(Select, [
("kind", kind),
("hint", hint),
("distinct", distinct),
("expressions", projections),
("limit", limit),
("exclude", exclude),
(
"operation_modifiers",
if operation_modifiers.is_empty() {
None
} else {
Some(operation_modifiers)
},
),
]),
)
sel.comments = Some(comments)
match self.parse_into_clause() {
Some(into) => sel.set("into", into)
None => ()
}
if from_ is None {
from_ = self.parse_from()
}
match from_ {
Some(f) => sel.set("from_", f)
None => ()
}
this = self.parse_query_modifiers(Some(sel))
} else if (table || nested) && self.match_(L_PAREN) {
let comments = self.prev_comments
let this = self.parse_wrapped_select(table~)
match this {
Some(t) => t.add_comments(Some(comments), prepend=true)
None => ()
}
self.match_r_paren()
return self.parse_subquery(this, parse_alias=parse_subquery_alias)
} else if self.match_(VALUES, advance=false) {
this = self.parse_derived_table_values()
} else if from_ is Some(f) {
let sel = mk(Select, [("expressions", [mk0(Star)])])
sel.set("from_", mk1(From, f.this()))
this = self.parse_query_modifiers(Some(sel))
} else if self.match_(SUMMARIZE) {
let table = self.match_(TABLE)
let this = expr_or(self.parse_select(), () => {
expr_or(self.parse_string(), () => self.parse_table())
})
return Some(
self.expression(mk(Summarize, [("this", this), ("table", table)])),
)
} else if self.match_(DESCRIBE) {
this = self.parse_describe()
} else {
this = None
}
if parse_set_operation {
self.parse_set_operations(this)
} else {
this
}
}
///|
pub fn Parser::parse_recursive_with_search(
self : Parser,
) -> Expr? raise SqlglotError {
self.match_text("SEARCH") |> ignore
let kind = if self.match_text_set(self.cfg.recursive_cte_search_kind) {
self.prev_upper()
} else {
return None
}
self.match_text_seq(["FIRST", "BY"]) |> ignore
let this = self.parse_id_var()
let expression = self.andv(self.match_text("SET"), () => self.parse_id_var())
let using_ = self.andv(self.match_text("USING"), () => self.parse_id_var())
Some(
self.expression(
mk(RecursiveWithSearch, [
("kind", kind),
("this", this),
("expression", expression),
("using", using_),
]),
),
)
}
///|
pub fn Parser::parse_with(
self : Parser,
skip_with_token? : Bool = false,
) -> Expr? raise SqlglotError {
if !skip_with_token && !self.match_(WITH) {
return None
}
let comments = self.prev_comments
let mut recursive = self.match_(RECURSIVE)
let mut last_comments : Array[String]? = None
let expressions = []
let udfs = []
while true {
let cte = self.parse_cte()
match cte {
Some(c) => {
if c.kind == FunctionSpecification {
udfs.push(c)
} else {
expressions.push(c)
}
match last_comments {
Some(lc) if !lc.is_empty() => c.add_comments(Some(lc))
_ => ()
}
}
None => ()
}
if !self.match_(COMMA) && !self.match_(WITH) {
break
} else {
self.match_(WITH) |> ignore
recursive = self.match_(RECURSIVE) || recursive
}
last_comments = Some(self.prev_comments)
}
let search = self.parse_recursive_with_search()
Some(
self.expression(
mk(With, [
("expressions", expressions),
("recursive", if recursive { Some(true) } else { None }),
("search", search),
("udfs", if udfs.is_empty() { None } else { Some(udfs) }),
]),
comments~,
),
)
}
///|
pub fn Parser::parse_cte(self : Parser) -> Expr? raise SqlglotError {
match self.fns.hooks.parse_cte {
Some(f) => f(self)
None => self.parse_cte_base()
}
}
///|
pub fn Parser::parse_cte_base(self : Parser) -> Expr? raise SqlglotError {
let index = self.index
let alias = self.parse_table_alias(alias_tokens=self.cfg.id_var_tokens)
match alias {
Some(a) if a.this() is Some(_) => ()
_ => self.raise_error("Expected CTE to have alias")
}
let key_expressions = if self.match_text_seq(["USING", "KEY"]) {
Some(self.parse_wrapped_id_vars())
} else {
None
}
if !self.match_(ALIAS) && !self.cfg.optional_alias_token_cte {
self.retreat(index)
return None
}
let comments = self.prev_comments
let materialized : Bool? = if self.match_text_seq(["NOT", "MATERIALIZED"]) {
Some(false)
} else if self.match_text("MATERIALIZED") {
Some(true)
} else {
None
}
let this = self.parse_wrapped(() => self.parse_statement())
let cte = self.expression(
mk(CTE, [
("this", this),
("alias", alias),
("materialized", materialized),
("key_expressions", key_expressions),
]),
comments~,
)
match cte.this() {
Some(values) if values.kind == Values =>
cte.set("this", self.values_to_select(values))
_ => ()
}
Some(cte)
}
///|
pub fn Parser::values_to_select(self : Parser, values : Expr) -> Expr {
ignore(self)
if !values.alias().is_empty() {
return select_star_from(Some(values))
}
select_star_from(Some(alias_table(values, "_values")))
}
///|
pub fn Parser::parse_table_alias(
self : Parser,
alias_tokens? : TokenSet,
) -> Expr? raise SqlglotError {
if self.can_parse_limit_or_offset() {
return None
}
if py_upper(self.curr.text) == "START" && py_upper(self.next.text) == "WITH" {
return None
}
let any_token = self.match_(ALIAS)
let tokens = match alias_tokens {
Some(t) => t
None => self.cfg.table_alias_tokens
}
let alias = expr_or(self.parse_id_var(any_token~, tokens~), () => {
self.parse_string_as_identifier()
})
let index = self.index
let columns = if self.match_(L_PAREN) {
let columns = self.parse_csv(() => self.parse_function_parameter())
if !columns.is_empty() {
self.match_r_paren()
} else {
self.retreat(index)
}
Some(columns)
} else {
None
}
let has_columns = match columns {
Some(c) => !c.is_empty()
None => false
}
if alias is None && !has_columns {
return None
}
let table_alias = self.expression(
mk(TableAlias, [("this", alias), ("columns", columns)]),
)
match alias {
Some(a) if a.kind == Identifier =>
table_alias.add_comments(Some(a.pop_comments()))
_ => ()
}
Some(table_alias)
}
///|
pub fn Parser::parse_subquery(
self : Parser,
this : Expr?,
parse_alias? : Bool = true,
) -> Expr? raise SqlglotError {
let this = match this {
Some(t) => t
None => return None
}
let pivots = self.parse_pivots()
let alias = if parse_alias { self.parse_table_alias() } else { None }
let sample = self.parse_table_sample()
Some(
self.expression(
mk(Subquery, [
("this", this),
("pivots", pivots),
("alias", alias),
("sample", sample),
]),
),
)
}
///|
pub fn Parser::implicit_unnests_to_explicit(self : Parser, this : Expr) -> Expr {
let refs : @set.Set[String] = @set.Set::new()
match this.arg("from_") {
Some(f) =>
match f.this() {
Some(t) =>
refs.add(
normalize_identifiers(t.copy(), self.dialect).alias_or_name(),
)
None => ()
}
None => ()
}
for join in this.list("joins") {
let table = match join.this() {
Some(t) => t
None => continue
}
let normalized_table = table.copy()
normalized_table.get_meta()["maybe_column"] = Bool(true)
let normalized_table = normalize_identifiers(normalized_table, self.dialect)
if table.kind == Table && !join.has("on") {
let parts = normalized_table.parts()
if parts.length() > 1 && refs.contains(parts[0].name()) {
let table_as_column = table.to_column()
let unnest = mk(Unnest, [("expressions", [table_as_column])])
match table.arg("alias") {
Some(a) if a.kind == TableAlias => {
match table_as_column.this() {
Some(inner) => table_as_column.replace(Some(inner)) |> ignore
None => ()
}
let ta = mk1(TableAlias, null_arg)
match a.this() {
Some(at) => ta.append("columns", to_identifier_expr(at))
None => ()
}
unnest.set("alias", ta)
}
_ => ()
}
table.replace(Some(unnest)) |> ignore
}
}
refs.add(normalized_table.alias_or_name())
}
this
}
///|
pub fn Parser::parse_query_modifiers(
self : Parser,
this : Expr?,
) -> Expr? raise SqlglotError {
let this = match this {
Some(t) => t
None => return None
}
if this.kind.is_any(self.cfg.modifiables) {
for join in self.parse_joins() {
this.append("joins", join)
}
while self.parse_lateral() is Some(lateral) {
this.append("laterals", lateral)
}
while true {
if self.match_keys(self.fns.query_modifier_parsers, advance=false) {
let modifier_token = self.curr
if modifier_token.token_type == LIMIT ||
modifier_token.token_type == FETCH {
match this.arg("limit") {
Some(limit) if limit.meta_bool("top") => break
_ => ()
}
}
let parser = self.fns.query_modifier_parsers[modifier_token.token_type]
let (key, expression) = parser(self)
match expression {
Some(ev) if ev.truthy() => {
if this.has(key) {
self.raise_error(
"Found multiple '\{py_upper(modifier_token.text)}' clauses",
token=modifier_token,
)
}
this.set(key, ev)
if key == "limit" {
match ev {
Node(expr) => {
let offset = expr.arg("offset")
expr.set("offset", null_arg)
match offset {
Some(off) => {
if this.has("offset") {
self.raise_error(
"Found multiple 'OFFSET' clauses",
token=modifier_token,
)
}
let offset = mk(Offset, [("expression", off)])
this.set("offset", offset)
let limit_by_expressions = expr.expressions()
expr.set("expressions", null_arg)
offset.set("expressions", limit_by_expressions)
}
None => ()
}
}
_ => ()
}
}
continue
}
_ => ()
}
}
if py_upper(self.curr.text) == "START" {
let modifier_token = self.curr
match self.parse_connect() {
Some(connect) => {
if this.has("connect") {
self.raise_error(
"Found multiple 'START WITH' clauses",
token=modifier_token,
)
}
this.set("connect", connect)
continue
}
None => ()
}
}
break
}
}
if self.cfg.supports_implicit_unnest && this.has("from_") {
return Some(self.implicit_unnests_to_explicit(this))
}
Some(this)
}
///|
pub fn Parser::parse_hint_fallback_to_string(
self : Parser,
) -> Expr? raise SqlglotError {
let start = self.curr
while self.curr.ok() {
self.advance()
}
let end = self.tokens[self.index - 1]
Some(mk(Hint, [("expressions", [self.find_sql(start, end)])]))
}
///|
pub fn Parser::parse_hint_function_call(
self : Parser,
) -> Expr? raise SqlglotError {
match self.fns.hooks.parse_hint_function_call {
Some(f) => f(self)
None => self.parse_function_call()
}
}
///|
pub fn Parser::parse_hint_body(self : Parser) -> Expr? raise SqlglotError {
let start_index = self.index
let mut should_fallback_to_string = false
let hints = []
try {
while true {
let hint = self.parse_csv(() => {
expr_or(self.parse_hint_function_call(), () => {
self.parse_var(upper=true)
})
})
if hint.is_empty() {
break
}
hints.append(hint)
}
} catch {
ParseError(_, _) => should_fallback_to_string = true
e => raise e
}
if should_fallback_to_string || self.curr.ok() {
self.retreat(start_index)
return self.parse_hint_fallback_to_string()
}
Some(self.expression(mk(Hint, [("expressions", hints)])))
}
///|
pub fn Parser::parse_hint(self : Parser) -> Expr? raise SqlglotError {
if self.match_(HINT) && !self.prev_comments.is_empty() {
return Some(
parse_one(self.prev_comments[0], dialect=self.dialect, into=[Hint]),
)
}
None
}
///|
pub fn Parser::parse_into_clause(self : Parser) -> Expr? raise SqlglotError {
match self.fns.hooks.parse_into_clause {
Some(f) => f(self)
None => self.parse_into_clause_base()
}
}
///|
pub fn Parser::parse_into_clause_base(
self : Parser,
) -> Expr? raise SqlglotError {
if !self.match_(INTO) {
return None
}
let temp = self.match_(TEMPORARY)
let unlogged = self.match_text("UNLOGGED")
self.match_(TABLE) |> ignore
Some(
self.expression(
mk(Into, [
("this", self.parse_table(schema=true)),
("temporary", temp),
("unlogged", unlogged),
]),
),
)
}
///|
pub fn Parser::parse_from(
self : Parser,
joins? : Bool = false,
skip_from_token? : Bool = false,
consume_pipe? : Bool = false,
) -> Expr? raise SqlglotError {
match self.fns.hooks.parse_from {
Some(f) => f(self, joins, skip_from_token, consume_pipe)
None => self.parse_from_base(joins~, skip_from_token~, consume_pipe~)
}
}
///|
pub fn Parser::parse_from_base(
self : Parser,
joins? : Bool = false,
skip_from_token? : Bool = false,
consume_pipe? : Bool = false,
) -> Expr? raise SqlglotError {
if !skip_from_token && !self.match_(FROM) {
return None
}
let comments = self.prev_comments
Some(
self.expression(
mk1(From, self.parse_table(joins~, consume_pipe~)),
comments~,
),
)
}
///|
pub fn Parser::parse_match_recognize_measure(
self : Parser,
) -> Expr raise SqlglotError {
let window_frame : Value = if self.match_texts(["FINAL", "RUNNING"]) {
Str(self.prev_upper())
} else {
Bool(false)
}
self.expression(
mk(MatchRecognizeMeasure, [
("window_frame", window_frame),
("this", self.parse_expression()),
]),
)
}