2025-02-13 15:01:55 +01:00
|
|
|
"""
|
2025-02-13 15:07:05 +01:00
|
|
|
## Expressions
|
|
|
|
|
|
|
|
Every AST node in SQLGlot is represented by a subclass of `Expression`.
|
|
|
|
|
|
|
|
This module contains the implementation of all supported `Expression` types. Additionally,
|
|
|
|
it exposes a number of helper functions, which are mainly used to programmatically build
|
|
|
|
SQL expressions, such as `sqlglot.expressions.select`.
|
2025-02-13 15:23:26 +01:00
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
----
|
2025-02-13 15:01:55 +01:00
|
|
|
"""
|
|
|
|
|
2025-02-13 14:53:05 +01:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2025-02-13 14:46:58 +01:00
|
|
|
import datetime
|
2025-02-13 15:01:55 +01:00
|
|
|
import math
|
2025-02-13 14:31:47 +01:00
|
|
|
import numbers
|
2025-02-13 06:15:54 +01:00
|
|
|
import re
|
2025-02-13 14:53:05 +01:00
|
|
|
import typing as t
|
2025-02-13 06:15:54 +01:00
|
|
|
from collections import deque
|
|
|
|
from copy import deepcopy
|
|
|
|
from enum import auto
|
2025-02-13 21:01:12 +01:00
|
|
|
from functools import reduce
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
from sqlglot._typing import E
|
2025-02-13 06:15:54 +01:00
|
|
|
from sqlglot.errors import ParseError
|
2025-02-13 14:40:43 +01:00
|
|
|
from sqlglot.helper import (
|
|
|
|
AutoName,
|
|
|
|
camel_to_snake_case,
|
2025-02-13 14:53:05 +01:00
|
|
|
ensure_collection,
|
2025-02-13 15:48:10 +01:00
|
|
|
ensure_list,
|
2025-02-13 14:53:05 +01:00
|
|
|
seq_get,
|
2025-02-13 14:40:43 +01:00
|
|
|
subclasses,
|
|
|
|
)
|
2025-02-13 15:03:38 +01:00
|
|
|
from sqlglot.tokens import Token
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 14:53:05 +01:00
|
|
|
if t.TYPE_CHECKING:
|
2025-02-13 15:09:58 +01:00
|
|
|
from sqlglot.dialects.dialect import DialectType
|
2025-02-13 15:07:05 +01:00
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
class _Expression(type):
|
|
|
|
def __new__(cls, clsname, bases, attrs):
|
|
|
|
klass = super().__new__(cls, clsname, bases, attrs)
|
2025-02-13 15:07:05 +01:00
|
|
|
|
|
|
|
# When an Expression class is created, its key is automatically set to be
|
|
|
|
# the lowercase version of the class' name.
|
2025-02-13 06:15:54 +01:00
|
|
|
klass.key = clsname.lower()
|
2025-02-13 15:07:05 +01:00
|
|
|
|
|
|
|
# This is so that docstrings are not inherited in pdoc
|
|
|
|
klass.__doc__ = klass.__doc__ or ""
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
return klass
|
|
|
|
|
|
|
|
|
2025-02-13 21:04:58 +01:00
|
|
|
SQLGLOT_META = "sqlglot.meta"
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Expression(metaclass=_Expression):
|
|
|
|
"""
|
2025-02-13 15:07:05 +01:00
|
|
|
The base class for all expressions in a syntax tree. Each Expression encapsulates any necessary
|
|
|
|
context, such as its child expressions, their names (arg keys), and whether a given child expression
|
|
|
|
is optional or not.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Attributes:
|
2025-02-13 15:07:05 +01:00
|
|
|
key: a unique key for each class in the Expression hierarchy. This is useful for hashing
|
|
|
|
and representing expressions as strings.
|
|
|
|
arg_types: determines what arguments (child nodes) are supported by an expression. It
|
|
|
|
maps arg keys to booleans that indicate whether the corresponding args are optional.
|
2025-02-13 15:53:39 +01:00
|
|
|
parent: a reference to the parent expression (or None, in case of root expressions).
|
|
|
|
arg_key: the arg key an expression is associated with, i.e. the name its parent expression
|
|
|
|
uses to refer to it.
|
|
|
|
comments: a list of comments that are associated with a given expression. This is used in
|
|
|
|
order to preserve comments when transpiling SQL code.
|
2025-02-13 20:48:36 +01:00
|
|
|
type: the `sqlglot.expressions.DataType` type of an expression. This is inferred by the
|
2025-02-13 15:53:39 +01:00
|
|
|
optimizer, in order to enable some transformations that require type information.
|
2025-02-13 20:48:36 +01:00
|
|
|
meta: a dictionary that can be used to store useful metadata for a given expression.
|
2025-02-13 15:07:05 +01:00
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> class Foo(Expression):
|
|
|
|
... arg_types = {"this": True, "expression": False}
|
|
|
|
|
|
|
|
The above definition informs us that Foo is an Expression that requires an argument called
|
|
|
|
"this" and may also optionally receive an argument called "expression".
|
|
|
|
|
|
|
|
Args:
|
|
|
|
args: a mapping used for retrieving the arguments of an expression, given their arg keys.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
key = "expression"
|
2025-02-13 06:15:54 +01:00
|
|
|
arg_types = {"this": True}
|
2025-02-13 15:48:10 +01:00
|
|
|
__slots__ = ("args", "parent", "arg_key", "comments", "_type", "_meta", "_hash")
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
def __init__(self, **args: t.Any):
|
|
|
|
self.args: t.Dict[str, t.Any] = args
|
|
|
|
self.parent: t.Optional[Expression] = None
|
|
|
|
self.arg_key: t.Optional[str] = None
|
|
|
|
self.comments: t.Optional[t.List[str]] = None
|
2025-02-13 14:58:37 +01:00
|
|
|
self._type: t.Optional[DataType] = None
|
2025-02-13 15:40:23 +01:00
|
|
|
self._meta: t.Optional[t.Dict[str, t.Any]] = None
|
2025-02-13 15:48:10 +01:00
|
|
|
self._hash: t.Optional[int] = None
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
for arg_key, value in self.args.items():
|
|
|
|
self._set_parent(arg_key, value)
|
|
|
|
|
2025-02-13 14:53:05 +01:00
|
|
|
def __eq__(self, other) -> bool:
|
2025-02-13 15:48:10 +01:00
|
|
|
return type(self) is type(other) and hash(self) == hash(other)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:48:10 +01:00
|
|
|
@property
|
|
|
|
def hashable_args(self) -> t.Any:
|
2025-02-13 20:43:05 +01:00
|
|
|
return frozenset(
|
|
|
|
(k, tuple(_norm_arg(a) for a in v) if type(v) is list else _norm_arg(v))
|
|
|
|
for k, v in self.args.items()
|
|
|
|
if not (v is None or v is False or (type(v) is list and not v))
|
2025-02-13 06:15:54 +01:00
|
|
|
)
|
|
|
|
|
2025-02-13 15:48:10 +01:00
|
|
|
def __hash__(self) -> int:
|
|
|
|
if self._hash is not None:
|
|
|
|
return self._hash
|
|
|
|
|
|
|
|
return hash((self.__class__, self.hashable_args))
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
@property
|
|
|
|
def this(self):
|
2025-02-13 15:07:05 +01:00
|
|
|
"""
|
|
|
|
Retrieves the argument with key "this".
|
|
|
|
"""
|
2025-02-13 06:15:54 +01:00
|
|
|
return self.args.get("this")
|
|
|
|
|
|
|
|
@property
|
|
|
|
def expression(self):
|
2025-02-13 15:07:05 +01:00
|
|
|
"""
|
|
|
|
Retrieves the argument with key "expression".
|
|
|
|
"""
|
2025-02-13 06:15:54 +01:00
|
|
|
return self.args.get("expression")
|
|
|
|
|
|
|
|
@property
|
|
|
|
def expressions(self):
|
2025-02-13 15:07:05 +01:00
|
|
|
"""
|
|
|
|
Retrieves the argument with key "expressions".
|
|
|
|
"""
|
2025-02-13 06:15:54 +01:00
|
|
|
return self.args.get("expressions") or []
|
|
|
|
|
2025-02-13 15:26:26 +01:00
|
|
|
def text(self, key) -> str:
|
2025-02-13 15:07:05 +01:00
|
|
|
"""
|
|
|
|
Returns a textual representation of the argument corresponding to "key". This can only be used
|
|
|
|
for args that are strings or leaf Expression instances, such as identifiers and literals.
|
|
|
|
"""
|
2025-02-13 06:15:54 +01:00
|
|
|
field = self.args.get(key)
|
|
|
|
if isinstance(field, str):
|
|
|
|
return field
|
|
|
|
if isinstance(field, (Identifier, Literal, Var)):
|
|
|
|
return field.this
|
2025-02-13 15:23:26 +01:00
|
|
|
if isinstance(field, (Star, Null)):
|
|
|
|
return field.name
|
2025-02-13 06:15:54 +01:00
|
|
|
return ""
|
|
|
|
|
|
|
|
@property
|
2025-02-13 15:26:26 +01:00
|
|
|
def is_string(self) -> bool:
|
2025-02-13 15:07:05 +01:00
|
|
|
"""
|
|
|
|
Checks whether a Literal expression is a string.
|
|
|
|
"""
|
2025-02-13 06:15:54 +01:00
|
|
|
return isinstance(self, Literal) and self.args["is_string"]
|
|
|
|
|
|
|
|
@property
|
2025-02-13 15:26:26 +01:00
|
|
|
def is_number(self) -> bool:
|
2025-02-13 15:07:05 +01:00
|
|
|
"""
|
|
|
|
Checks whether a Literal expression is a number.
|
|
|
|
"""
|
2025-02-13 06:15:54 +01:00
|
|
|
return isinstance(self, Literal) and not self.args["is_string"]
|
|
|
|
|
|
|
|
@property
|
2025-02-13 15:26:26 +01:00
|
|
|
def is_int(self) -> bool:
|
2025-02-13 15:07:05 +01:00
|
|
|
"""
|
|
|
|
Checks whether a Literal expression is an integer.
|
|
|
|
"""
|
2025-02-13 06:15:54 +01:00
|
|
|
if self.is_number:
|
|
|
|
try:
|
|
|
|
int(self.name)
|
|
|
|
return True
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
return False
|
|
|
|
|
|
|
|
@property
|
2025-02-13 15:26:26 +01:00
|
|
|
def is_star(self) -> bool:
|
|
|
|
"""Checks whether an expression is a star."""
|
|
|
|
return isinstance(self, Star) or (isinstance(self, Column) and isinstance(self.this, Star))
|
|
|
|
|
|
|
|
@property
|
|
|
|
def alias(self) -> str:
|
2025-02-13 15:07:05 +01:00
|
|
|
"""
|
|
|
|
Returns the alias of the expression, or an empty string if it's not aliased.
|
|
|
|
"""
|
2025-02-13 06:15:54 +01:00
|
|
|
if isinstance(self.args.get("alias"), TableAlias):
|
|
|
|
return self.args["alias"].name
|
|
|
|
return self.text("alias")
|
|
|
|
|
2025-02-13 20:51:40 +01:00
|
|
|
@property
|
|
|
|
def alias_column_names(self) -> t.List[str]:
|
|
|
|
table_alias = self.args.get("alias")
|
|
|
|
if not table_alias:
|
|
|
|
return []
|
|
|
|
return [c.name for c in table_alias.args.get("columns") or []]
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
@property
|
2025-02-13 15:23:26 +01:00
|
|
|
def name(self) -> str:
|
2025-02-13 06:15:54 +01:00
|
|
|
return self.text("this")
|
|
|
|
|
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def alias_or_name(self) -> str:
|
2025-02-13 06:15:54 +01:00
|
|
|
return self.alias or self.name
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def output_name(self) -> str:
|
2025-02-13 15:07:05 +01:00
|
|
|
"""
|
|
|
|
Name of the output column if this expression is a selection.
|
|
|
|
|
|
|
|
If the Expression has no output name, an empty string is returned.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> from sqlglot import parse_one
|
|
|
|
>>> parse_one("SELECT a").expressions[0].output_name
|
|
|
|
'a'
|
|
|
|
>>> parse_one("SELECT b AS c").expressions[0].output_name
|
|
|
|
'c'
|
|
|
|
>>> parse_one("SELECT 1 + 2").expressions[0].output_name
|
|
|
|
''
|
|
|
|
"""
|
|
|
|
return ""
|
|
|
|
|
2025-02-13 14:58:37 +01:00
|
|
|
@property
|
|
|
|
def type(self) -> t.Optional[DataType]:
|
|
|
|
return self._type
|
|
|
|
|
|
|
|
@type.setter
|
|
|
|
def type(self, dtype: t.Optional[DataType | DataType.Type | str]) -> None:
|
|
|
|
if dtype and not isinstance(dtype, DataType):
|
|
|
|
dtype = DataType.build(dtype)
|
|
|
|
self._type = dtype # type: ignore
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
@property
|
|
|
|
def meta(self) -> t.Dict[str, t.Any]:
|
|
|
|
if self._meta is None:
|
|
|
|
self._meta = {}
|
|
|
|
return self._meta
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
def __deepcopy__(self, memo):
|
2025-02-13 14:53:05 +01:00
|
|
|
copy = self.__class__(**deepcopy(self.args))
|
2025-02-13 15:40:23 +01:00
|
|
|
if self.comments is not None:
|
|
|
|
copy.comments = deepcopy(self.comments)
|
|
|
|
|
|
|
|
if self._type is not None:
|
|
|
|
copy._type = self._type.copy()
|
|
|
|
|
|
|
|
if self._meta is not None:
|
|
|
|
copy._meta = deepcopy(self._meta)
|
|
|
|
|
2025-02-13 14:53:05 +01:00
|
|
|
return copy
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
def copy(self):
|
2025-02-13 15:07:05 +01:00
|
|
|
"""
|
|
|
|
Returns a deep copy of the expression.
|
|
|
|
"""
|
2025-02-13 06:15:54 +01:00
|
|
|
new = deepcopy(self)
|
2025-02-13 15:08:15 +01:00
|
|
|
new.parent = self.parent
|
2025-02-13 06:15:54 +01:00
|
|
|
return new
|
|
|
|
|
2025-02-13 15:53:39 +01:00
|
|
|
def add_comments(self, comments: t.Optional[t.List[str]]) -> None:
|
|
|
|
if self.comments is None:
|
|
|
|
self.comments = []
|
|
|
|
if comments:
|
2025-02-13 21:04:58 +01:00
|
|
|
for comment in comments:
|
|
|
|
_, *meta = comment.split(SQLGLOT_META)
|
|
|
|
if meta:
|
|
|
|
for kv in "".join(meta).split(","):
|
|
|
|
k, *v = kv.split("=")
|
|
|
|
value = v[0].strip() if v else True
|
|
|
|
self.meta[k.strip()] = value
|
|
|
|
self.comments.append(comment)
|
2025-02-13 15:53:39 +01:00
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def append(self, arg_key: str, value: t.Any) -> None:
|
2025-02-13 08:04:41 +01:00
|
|
|
"""
|
|
|
|
Appends value to arg_key if it's a list or sets it as a new list.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
arg_key (str): name of the list expression arg
|
|
|
|
value (Any): value to append to the list
|
|
|
|
"""
|
|
|
|
if not isinstance(self.args.get(arg_key), list):
|
|
|
|
self.args[arg_key] = []
|
|
|
|
self.args[arg_key].append(value)
|
|
|
|
self._set_parent(arg_key, value)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def set(self, arg_key: str, value: t.Any) -> None:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
2025-02-13 20:44:18 +01:00
|
|
|
Sets arg_key to value.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Args:
|
2025-02-13 20:44:18 +01:00
|
|
|
arg_key: name of the expression arg.
|
2025-02-13 06:15:54 +01:00
|
|
|
value: value to set the arg to.
|
|
|
|
"""
|
2025-02-13 20:44:18 +01:00
|
|
|
if value is None:
|
|
|
|
self.args.pop(arg_key, None)
|
|
|
|
return
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
self.args[arg_key] = value
|
|
|
|
self._set_parent(arg_key, value)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def _set_parent(self, arg_key: str, value: t.Any) -> None:
|
2025-02-13 15:48:10 +01:00
|
|
|
if hasattr(value, "parent"):
|
2025-02-13 06:15:54 +01:00
|
|
|
value.parent = self
|
|
|
|
value.arg_key = arg_key
|
2025-02-13 15:48:10 +01:00
|
|
|
elif type(value) is list:
|
2025-02-13 06:15:54 +01:00
|
|
|
for v in value:
|
2025-02-13 15:48:10 +01:00
|
|
|
if hasattr(v, "parent"):
|
2025-02-13 06:15:54 +01:00
|
|
|
v.parent = self
|
|
|
|
v.arg_key = arg_key
|
|
|
|
|
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def depth(self) -> int:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Returns the depth of this tree.
|
|
|
|
"""
|
|
|
|
if self.parent:
|
|
|
|
return self.parent.depth + 1
|
|
|
|
return 0
|
|
|
|
|
2025-02-13 15:48:10 +01:00
|
|
|
def iter_expressions(self) -> t.Iterator[t.Tuple[str, Expression]]:
|
|
|
|
"""Yields the key and expression for all arguments, exploding list args."""
|
|
|
|
for k, vs in self.args.items():
|
|
|
|
if type(vs) is list:
|
|
|
|
for v in vs:
|
|
|
|
if hasattr(v, "parent"):
|
|
|
|
yield k, v
|
|
|
|
else:
|
|
|
|
if hasattr(vs, "parent"):
|
|
|
|
yield k, vs
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def find(self, *expression_types: t.Type[E], bfs: bool = True) -> t.Optional[E]:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Returns the first node in this tree which matches at least one of
|
|
|
|
the specified types.
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:46:19 +01:00
|
|
|
expression_types: the expression type(s) to match.
|
2025-02-13 15:57:23 +01:00
|
|
|
bfs: whether to search the AST using the BFS algorithm (DFS is used if false).
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:07:05 +01:00
|
|
|
The node which matches the criteria or None if no such node was found.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
return next(self.find_all(*expression_types, bfs=bfs), None)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def find_all(self, *expression_types: t.Type[E], bfs: bool = True) -> t.Iterator[E]:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Returns a generator object which visits all nodes in this tree and only
|
|
|
|
yields those that match at least one of the specified expression types.
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:46:19 +01:00
|
|
|
expression_types: the expression type(s) to match.
|
2025-02-13 15:57:23 +01:00
|
|
|
bfs: whether to search the AST using the BFS algorithm (DFS is used if false).
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:07:05 +01:00
|
|
|
The generator object.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
2025-02-13 15:48:10 +01:00
|
|
|
for expression, *_ in self.walk(bfs=bfs):
|
2025-02-13 06:15:54 +01:00
|
|
|
if isinstance(expression, expression_types):
|
|
|
|
yield expression
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def find_ancestor(self, *expression_types: t.Type[E]) -> t.Optional[E]:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Returns a nearest parent matching expression_types.
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:46:19 +01:00
|
|
|
expression_types: the expression type(s) to match.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:07:05 +01:00
|
|
|
The parent node.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
ancestor = self.parent
|
|
|
|
while ancestor and not isinstance(ancestor, expression_types):
|
|
|
|
ancestor = ancestor.parent
|
2025-02-13 15:46:19 +01:00
|
|
|
return t.cast(E, ancestor)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def parent_select(self) -> t.Optional[Select]:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Returns the parent select statement.
|
|
|
|
"""
|
|
|
|
return self.find_ancestor(Select)
|
|
|
|
|
2025-02-13 15:48:10 +01:00
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def same_parent(self) -> bool:
|
2025-02-13 15:48:10 +01:00
|
|
|
"""Returns if the parent is the same class as itself."""
|
|
|
|
return type(self.parent) is self.__class__
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
def root(self) -> Expression:
|
|
|
|
"""
|
|
|
|
Returns the root expression of this tree.
|
|
|
|
"""
|
|
|
|
expression = self
|
|
|
|
while expression.parent:
|
|
|
|
expression = expression.parent
|
|
|
|
return expression
|
|
|
|
|
2025-02-13 14:43:32 +01:00
|
|
|
def walk(self, bfs=True, prune=None):
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Returns a generator object which visits all nodes in this tree.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
bfs (bool): if set to True the BFS traversal order will be applied,
|
|
|
|
otherwise the DFS traversal will be used instead.
|
2025-02-13 14:43:32 +01:00
|
|
|
prune ((node, parent, arg_key) -> bool): callable that returns True if
|
|
|
|
the generator should stop traversing this branch of the tree.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
the generator object.
|
|
|
|
"""
|
|
|
|
if bfs:
|
2025-02-13 14:43:32 +01:00
|
|
|
yield from self.bfs(prune=prune)
|
2025-02-13 06:15:54 +01:00
|
|
|
else:
|
2025-02-13 14:43:32 +01:00
|
|
|
yield from self.dfs(prune=prune)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
def dfs(self, parent=None, key=None, prune=None):
|
|
|
|
"""
|
|
|
|
Returns a generator object which visits all nodes in this tree in
|
|
|
|
the DFS (Depth-first) order.
|
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:07:05 +01:00
|
|
|
The generator object.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
parent = parent or self.parent
|
|
|
|
yield self, parent, key
|
|
|
|
if prune and prune(self, parent, key):
|
|
|
|
return
|
|
|
|
|
2025-02-13 15:48:10 +01:00
|
|
|
for k, v in self.iter_expressions():
|
|
|
|
yield from v.dfs(self, k, prune)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
def bfs(self, prune=None):
|
|
|
|
"""
|
|
|
|
Returns a generator object which visits all nodes in this tree in
|
|
|
|
the BFS (Breadth-first) order.
|
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:07:05 +01:00
|
|
|
The generator object.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
queue = deque([(self, self.parent, None)])
|
|
|
|
|
|
|
|
while queue:
|
|
|
|
item, parent, key = queue.popleft()
|
|
|
|
|
|
|
|
yield item, parent, key
|
|
|
|
if prune and prune(item, parent, key):
|
|
|
|
continue
|
|
|
|
|
2025-02-13 15:48:10 +01:00
|
|
|
for k, v in item.iter_expressions():
|
|
|
|
queue.append((v, item, k))
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
def unnest(self):
|
|
|
|
"""
|
|
|
|
Returns the first non parenthesis child or self.
|
|
|
|
"""
|
|
|
|
expression = self
|
2025-02-13 15:48:10 +01:00
|
|
|
while type(expression) is Paren:
|
2025-02-13 06:15:54 +01:00
|
|
|
expression = expression.this
|
|
|
|
return expression
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
def unalias(self):
|
|
|
|
"""
|
|
|
|
Returns the inner expression if this is an Alias.
|
|
|
|
"""
|
|
|
|
if isinstance(self, Alias):
|
|
|
|
return self.this
|
|
|
|
return self
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
def unnest_operands(self):
|
|
|
|
"""
|
|
|
|
Returns unnested operands as a tuple.
|
|
|
|
"""
|
2025-02-13 15:48:10 +01:00
|
|
|
return tuple(arg.unnest() for _, arg in self.iter_expressions())
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
def flatten(self, unnest=True):
|
|
|
|
"""
|
|
|
|
Returns a generator which yields child nodes who's parents are the same class.
|
|
|
|
|
|
|
|
A AND B AND C -> [A, B, C]
|
|
|
|
"""
|
2025-02-13 15:48:10 +01:00
|
|
|
for node, _, _ in self.dfs(prune=lambda n, p, *_: p and not type(n) is self.__class__):
|
|
|
|
if not type(node) is self.__class__:
|
2025-02-13 06:15:54 +01:00
|
|
|
yield node.unnest() if unnest else node
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __str__(self) -> str:
|
2025-02-13 06:15:54 +01:00
|
|
|
return self.sql()
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __repr__(self) -> str:
|
2025-02-13 15:07:05 +01:00
|
|
|
return self._to_s()
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:09:58 +01:00
|
|
|
def sql(self, dialect: DialectType = None, **opts) -> str:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Returns SQL string representation of this tree.
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
Args:
|
|
|
|
dialect: the dialect of the output SQL string (eg. "spark", "hive", "presto", "mysql").
|
|
|
|
opts: other `sqlglot.generator.Generator` options.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
Returns:
|
|
|
|
The SQL string.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
from sqlglot.dialects import Dialect
|
|
|
|
|
|
|
|
return Dialect.get_or_raise(dialect)().generate(self, **opts)
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
def _to_s(self, hide_missing: bool = True, level: int = 0) -> str:
|
2025-02-13 06:15:54 +01:00
|
|
|
indent = "" if not level else "\n"
|
|
|
|
indent += "".join([" "] * level)
|
|
|
|
left = f"({self.key.upper()} "
|
|
|
|
|
2025-02-13 14:58:37 +01:00
|
|
|
args: t.Dict[str, t.Any] = {
|
2025-02-13 06:15:54 +01:00
|
|
|
k: ", ".join(
|
2025-02-13 15:07:05 +01:00
|
|
|
v._to_s(hide_missing=hide_missing, level=level + 1)
|
|
|
|
if hasattr(v, "_to_s")
|
|
|
|
else str(v)
|
2025-02-13 15:48:10 +01:00
|
|
|
for v in ensure_list(vs)
|
2025-02-13 06:15:54 +01:00
|
|
|
if v is not None
|
|
|
|
)
|
|
|
|
for k, vs in self.args.items()
|
|
|
|
}
|
2025-02-13 14:56:25 +01:00
|
|
|
args["comments"] = self.comments
|
2025-02-13 14:53:05 +01:00
|
|
|
args["type"] = self.type
|
2025-02-13 06:15:54 +01:00
|
|
|
args = {k: v for k, v in args.items() if v or not hide_missing}
|
|
|
|
|
|
|
|
right = ", ".join(f"{k}: {v}" for k, v in args.items())
|
|
|
|
right += ")"
|
|
|
|
|
|
|
|
return indent + left + right
|
|
|
|
|
|
|
|
def transform(self, fun, *args, copy=True, **kwargs):
|
|
|
|
"""
|
|
|
|
Recursively visits all tree nodes (excluding already transformed ones)
|
|
|
|
and applies the given transformation function to each node.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
fun (function): a function which takes a node as an argument and returns a
|
2025-02-13 14:31:47 +01:00
|
|
|
new transformed node or the same node without modifications. If the function
|
|
|
|
returns None, then the corresponding node will be removed from the syntax tree.
|
2025-02-13 06:15:54 +01:00
|
|
|
copy (bool): if set to True a new tree instance is constructed, otherwise the tree is
|
|
|
|
modified in place.
|
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:07:05 +01:00
|
|
|
The transformed tree.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
node = self.copy() if copy else self
|
|
|
|
new_node = fun(node, *args, **kwargs)
|
|
|
|
|
2025-02-13 14:31:47 +01:00
|
|
|
if new_node is None or not isinstance(new_node, Expression):
|
2025-02-13 06:15:54 +01:00
|
|
|
return new_node
|
|
|
|
if new_node is not node:
|
|
|
|
new_node.parent = node.parent
|
|
|
|
return new_node
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
replace_children(new_node, lambda child: child.transform(fun, *args, copy=False, **kwargs))
|
2025-02-13 06:15:54 +01:00
|
|
|
return new_node
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
@t.overload
|
|
|
|
def replace(self, expression: E) -> E:
|
|
|
|
...
|
|
|
|
|
|
|
|
@t.overload
|
|
|
|
def replace(self, expression: None) -> None:
|
|
|
|
...
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
def replace(self, expression):
|
|
|
|
"""
|
|
|
|
Swap out this expression with a new expression.
|
|
|
|
|
|
|
|
For example::
|
|
|
|
|
|
|
|
>>> tree = Select().select("x").from_("tbl")
|
|
|
|
>>> tree.find(Column).replace(Column(this="y"))
|
|
|
|
(COLUMN this: y)
|
|
|
|
>>> tree.sql()
|
|
|
|
'SELECT y FROM tbl'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
expression: new node
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
Returns:
|
|
|
|
The new expression or expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
if not self.parent:
|
|
|
|
return expression
|
|
|
|
|
|
|
|
parent = self.parent
|
|
|
|
self.parent = None
|
|
|
|
|
|
|
|
replace_children(parent, lambda child: expression if child is self else child)
|
|
|
|
return expression
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def pop(self: E) -> E:
|
2025-02-13 14:40:43 +01:00
|
|
|
"""
|
|
|
|
Remove this expression from its AST.
|
2025-02-13 15:46:19 +01:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
The popped expression.
|
2025-02-13 14:40:43 +01:00
|
|
|
"""
|
|
|
|
self.replace(None)
|
2025-02-13 15:46:19 +01:00
|
|
|
return self
|
2025-02-13 14:40:43 +01:00
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def assert_is(self, type_: t.Type[E]) -> E:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Assert that this `Expression` is an instance of `type_`.
|
|
|
|
|
|
|
|
If it is NOT an instance of `type_`, this raises an assertion error.
|
|
|
|
Otherwise, this returns this expression.
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
This is useful for type security in chained expressions:
|
|
|
|
|
|
|
|
>>> import sqlglot
|
|
|
|
>>> sqlglot.parse_one("SELECT x from y").assert_is(Select).select("z").sql()
|
|
|
|
'SELECT x, z FROM y'
|
|
|
|
"""
|
|
|
|
assert isinstance(self, type_)
|
|
|
|
return self
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
def error_messages(self, args: t.Optional[t.Sequence] = None) -> t.List[str]:
|
|
|
|
"""
|
|
|
|
Checks if this expression is valid (e.g. all mandatory args are set).
|
|
|
|
|
|
|
|
Args:
|
|
|
|
args: a sequence of values that were used to instantiate a Func expression. This is used
|
|
|
|
to check that the provided arguments don't exceed the function argument limit.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
A list of error messages for all possible errors that were found.
|
|
|
|
"""
|
|
|
|
errors: t.List[str] = []
|
|
|
|
|
|
|
|
for k in self.args:
|
|
|
|
if k not in self.arg_types:
|
|
|
|
errors.append(f"Unexpected keyword: '{k}' for {self.__class__}")
|
|
|
|
for k, mandatory in self.arg_types.items():
|
|
|
|
v = self.args.get(k)
|
|
|
|
if mandatory and (v is None or (isinstance(v, list) and not v)):
|
|
|
|
errors.append(f"Required keyword: '{k}' missing for {self.__class__}")
|
|
|
|
|
|
|
|
if (
|
|
|
|
args
|
|
|
|
and isinstance(self, Func)
|
|
|
|
and len(args) > len(self.arg_types)
|
|
|
|
and not self.is_var_len_args
|
|
|
|
):
|
|
|
|
errors.append(
|
|
|
|
f"The number of provided arguments ({len(args)}) is greater than "
|
|
|
|
f"the maximum number of supported arguments ({len(self.arg_types)})"
|
|
|
|
)
|
|
|
|
|
|
|
|
return errors
|
|
|
|
|
2025-02-13 15:03:38 +01:00
|
|
|
def dump(self):
|
|
|
|
"""
|
|
|
|
Dump this Expression to a JSON-serializable dict.
|
|
|
|
"""
|
|
|
|
from sqlglot.serde import dump
|
|
|
|
|
|
|
|
return dump(self)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def load(cls, obj):
|
|
|
|
"""
|
|
|
|
Load a dict (as returned by `Expression.dump`) into an Expression instance.
|
|
|
|
"""
|
|
|
|
from sqlglot.serde import load
|
|
|
|
|
|
|
|
return load(obj)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def and_(
|
|
|
|
self,
|
|
|
|
*expressions: t.Optional[ExpOrStr],
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Condition:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
AND this condition with one or multiple expressions.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> condition("x=1").and_("y=1").sql()
|
|
|
|
'x = 1 AND y = 1'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
*expressions: the SQL code strings to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
copy: whether or not to copy the involved expressions (only applies to Expressions).
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The new And condition.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
2025-02-13 15:53:39 +01:00
|
|
|
return and_(self, *expressions, dialect=dialect, copy=copy, **opts)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def or_(
|
|
|
|
self,
|
|
|
|
*expressions: t.Optional[ExpOrStr],
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Condition:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
OR this condition with one or multiple expressions.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> condition("x=1").or_("y=1").sql()
|
|
|
|
'x = 1 OR y = 1'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
*expressions: the SQL code strings to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
copy: whether or not to copy the involved expressions (only applies to Expressions).
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The new Or condition.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
2025-02-13 15:53:39 +01:00
|
|
|
return or_(self, *expressions, dialect=dialect, copy=copy, **opts)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def not_(self, copy: bool = True):
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Wrap this condition with NOT.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> condition("x=1").not_().sql()
|
|
|
|
'NOT x = 1'
|
|
|
|
|
2025-02-13 15:53:39 +01:00
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
copy: whether or not to copy this object.
|
2025-02-13 15:53:39 +01:00
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The new Not instance.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
2025-02-13 15:53:39 +01:00
|
|
|
return not_(self, copy=copy)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def as_(
|
|
|
|
self,
|
|
|
|
alias: str | Identifier,
|
|
|
|
quoted: t.Optional[bool] = None,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Alias:
|
|
|
|
return alias_(self, alias, quoted=quoted, dialect=dialect, copy=copy, **opts)
|
|
|
|
|
|
|
|
def _binop(self, klass: t.Type[E], other: t.Any, reverse: bool = False) -> E:
|
2025-02-13 15:53:39 +01:00
|
|
|
this = self.copy()
|
|
|
|
other = convert(other, copy=True)
|
2025-02-13 15:52:09 +01:00
|
|
|
if not isinstance(this, klass) and not isinstance(other, klass):
|
|
|
|
this = _wrap(this, Binary)
|
|
|
|
other = _wrap(other, Binary)
|
|
|
|
if reverse:
|
|
|
|
return klass(this=other, expression=this)
|
|
|
|
return klass(this=this, expression=other)
|
|
|
|
|
2025-02-13 21:03:38 +01:00
|
|
|
def __getitem__(self, other: ExpOrStr | t.Tuple[ExpOrStr]) -> Bracket:
|
2025-02-13 15:53:39 +01:00
|
|
|
return Bracket(
|
|
|
|
this=self.copy(), expressions=[convert(e, copy=True) for e in ensure_list(other)]
|
|
|
|
)
|
2025-02-13 15:52:09 +01:00
|
|
|
|
2025-02-13 21:03:38 +01:00
|
|
|
def __iter__(self) -> t.Iterator:
|
|
|
|
if "expressions" in self.arg_types:
|
|
|
|
return iter(self.args.get("expressions") or [])
|
|
|
|
# We define this because __getitem__ converts Expression into an iterable, which is
|
|
|
|
# problematic because one can hit infinite loops if they do "for x in some_expr: ..."
|
|
|
|
# See: https://peps.python.org/pep-0234/
|
|
|
|
raise TypeError(f"'{self.__class__.__name__}' object is not iterable")
|
|
|
|
|
2025-02-13 15:53:39 +01:00
|
|
|
def isin(
|
2025-02-13 20:46:55 +01:00
|
|
|
self,
|
|
|
|
*expressions: t.Any,
|
|
|
|
query: t.Optional[ExpOrStr] = None,
|
|
|
|
unnest: t.Optional[ExpOrStr] | t.Collection[ExpOrStr] = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
2025-02-13 15:53:39 +01:00
|
|
|
) -> In:
|
2025-02-13 15:52:09 +01:00
|
|
|
return In(
|
2025-02-13 20:48:36 +01:00
|
|
|
this=maybe_copy(self, copy),
|
2025-02-13 15:53:39 +01:00
|
|
|
expressions=[convert(e, copy=copy) for e in expressions],
|
|
|
|
query=maybe_parse(query, copy=copy, **opts) if query else None,
|
2025-02-13 20:46:55 +01:00
|
|
|
unnest=Unnest(
|
|
|
|
expressions=[
|
|
|
|
maybe_parse(t.cast(ExpOrStr, e), copy=copy, **opts) for e in ensure_list(unnest)
|
|
|
|
]
|
|
|
|
)
|
|
|
|
if unnest
|
|
|
|
else None,
|
2025-02-13 15:53:39 +01:00
|
|
|
)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def between(self, low: t.Any, high: t.Any, copy: bool = True, **opts) -> Between:
|
2025-02-13 15:53:39 +01:00
|
|
|
return Between(
|
2025-02-13 20:48:36 +01:00
|
|
|
this=maybe_copy(self, copy),
|
2025-02-13 15:53:39 +01:00
|
|
|
low=convert(low, copy=copy, **opts),
|
|
|
|
high=convert(high, copy=copy, **opts),
|
2025-02-13 15:52:09 +01:00
|
|
|
)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def is_(self, other: ExpOrStr) -> Is:
|
|
|
|
return self._binop(Is, other)
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
def like(self, other: ExpOrStr) -> Like:
|
|
|
|
return self._binop(Like, other)
|
|
|
|
|
|
|
|
def ilike(self, other: ExpOrStr) -> ILike:
|
|
|
|
return self._binop(ILike, other)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def eq(self, other: t.Any) -> EQ:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(EQ, other)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def neq(self, other: t.Any) -> NEQ:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(NEQ, other)
|
|
|
|
|
|
|
|
def rlike(self, other: ExpOrStr) -> RegexpLike:
|
|
|
|
return self._binop(RegexpLike, other)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __lt__(self, other: t.Any) -> LT:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(LT, other)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __le__(self, other: t.Any) -> LTE:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(LTE, other)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __gt__(self, other: t.Any) -> GT:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(GT, other)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __ge__(self, other: t.Any) -> GTE:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(GTE, other)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __add__(self, other: t.Any) -> Add:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(Add, other)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __radd__(self, other: t.Any) -> Add:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(Add, other, reverse=True)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __sub__(self, other: t.Any) -> Sub:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(Sub, other)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __rsub__(self, other: t.Any) -> Sub:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(Sub, other, reverse=True)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __mul__(self, other: t.Any) -> Mul:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(Mul, other)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __rmul__(self, other: t.Any) -> Mul:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(Mul, other, reverse=True)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __truediv__(self, other: t.Any) -> Div:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(Div, other)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __rtruediv__(self, other: t.Any) -> Div:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(Div, other, reverse=True)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __floordiv__(self, other: t.Any) -> IntDiv:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(IntDiv, other)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __rfloordiv__(self, other: t.Any) -> IntDiv:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(IntDiv, other, reverse=True)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __mod__(self, other: t.Any) -> Mod:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(Mod, other)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __rmod__(self, other: t.Any) -> Mod:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(Mod, other, reverse=True)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __pow__(self, other: t.Any) -> Pow:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(Pow, other)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __rpow__(self, other: t.Any) -> Pow:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(Pow, other, reverse=True)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __and__(self, other: t.Any) -> And:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(And, other)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __rand__(self, other: t.Any) -> And:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(And, other, reverse=True)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __or__(self, other: t.Any) -> Or:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(Or, other)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def __ror__(self, other: t.Any) -> Or:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self._binop(Or, other, reverse=True)
|
|
|
|
|
|
|
|
def __neg__(self) -> Neg:
|
2025-02-13 15:53:39 +01:00
|
|
|
return Neg(this=_wrap(self.copy(), Binary))
|
2025-02-13 15:52:09 +01:00
|
|
|
|
|
|
|
def __invert__(self) -> Not:
|
2025-02-13 15:53:39 +01:00
|
|
|
return not_(self.copy())
|
2025-02-13 15:52:09 +01:00
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 21:03:38 +01:00
|
|
|
IntoType = t.Union[
|
|
|
|
str,
|
|
|
|
t.Type[Expression],
|
|
|
|
t.Collection[t.Union[str, t.Type[Expression]]],
|
|
|
|
]
|
|
|
|
ExpOrStr = t.Union[str, Expression]
|
|
|
|
|
|
|
|
|
|
|
|
class Condition(Expression):
|
|
|
|
"""Logical conditions like x AND y, or simply x"""
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Predicate(Condition):
|
|
|
|
"""Relationships like x = y, x > 1, x >= y."""
|
|
|
|
|
|
|
|
|
|
|
|
class DerivedTable(Expression):
|
|
|
|
@property
|
2025-02-13 20:45:52 +01:00
|
|
|
def selects(self) -> t.List[Expression]:
|
2025-02-13 15:53:39 +01:00
|
|
|
return self.this.selects if isinstance(self.this, Subqueryable) else []
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
@property
|
2025-02-13 20:45:52 +01:00
|
|
|
def named_selects(self) -> t.List[str]:
|
2025-02-13 15:07:05 +01:00
|
|
|
return [select.output_name for select in self.selects]
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 14:46:58 +01:00
|
|
|
class Unionable(Expression):
|
2025-02-13 15:57:23 +01:00
|
|
|
def union(
|
|
|
|
self, expression: ExpOrStr, distinct: bool = True, dialect: DialectType = None, **opts
|
|
|
|
) -> Unionable:
|
2025-02-13 14:45:11 +01:00
|
|
|
"""
|
|
|
|
Builds a UNION expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> import sqlglot
|
|
|
|
>>> sqlglot.parse_one("SELECT * FROM foo").union("SELECT * FROM bla").sql()
|
|
|
|
'SELECT * FROM foo UNION SELECT * FROM bla'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
expression: the SQL code string.
|
2025-02-13 14:45:11 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
distinct: set the DISTINCT flag if and only if this is true.
|
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
opts: other options to use to parse the input expressions.
|
|
|
|
|
2025-02-13 14:45:11 +01:00
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The new Union expression.
|
2025-02-13 14:45:11 +01:00
|
|
|
"""
|
|
|
|
return union(left=self, right=expression, distinct=distinct, dialect=dialect, **opts)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def intersect(
|
|
|
|
self, expression: ExpOrStr, distinct: bool = True, dialect: DialectType = None, **opts
|
|
|
|
) -> Unionable:
|
2025-02-13 14:45:11 +01:00
|
|
|
"""
|
|
|
|
Builds an INTERSECT expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> import sqlglot
|
|
|
|
>>> sqlglot.parse_one("SELECT * FROM foo").intersect("SELECT * FROM bla").sql()
|
|
|
|
'SELECT * FROM foo INTERSECT SELECT * FROM bla'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
expression: the SQL code string.
|
2025-02-13 14:45:11 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
distinct: set the DISTINCT flag if and only if this is true.
|
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
opts: other options to use to parse the input expressions.
|
|
|
|
|
2025-02-13 14:45:11 +01:00
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The new Intersect expression.
|
2025-02-13 14:45:11 +01:00
|
|
|
"""
|
|
|
|
return intersect(left=self, right=expression, distinct=distinct, dialect=dialect, **opts)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def except_(
|
|
|
|
self, expression: ExpOrStr, distinct: bool = True, dialect: DialectType = None, **opts
|
|
|
|
) -> Unionable:
|
2025-02-13 14:45:11 +01:00
|
|
|
"""
|
|
|
|
Builds an EXCEPT expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> import sqlglot
|
|
|
|
>>> sqlglot.parse_one("SELECT * FROM foo").except_("SELECT * FROM bla").sql()
|
|
|
|
'SELECT * FROM foo EXCEPT SELECT * FROM bla'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
expression: the SQL code string.
|
2025-02-13 14:45:11 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
distinct: set the DISTINCT flag if and only if this is true.
|
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
opts: other options to use to parse the input expressions.
|
|
|
|
|
2025-02-13 14:45:11 +01:00
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The new Except expression.
|
2025-02-13 14:45:11 +01:00
|
|
|
"""
|
|
|
|
return except_(left=self, right=expression, distinct=distinct, dialect=dialect, **opts)
|
|
|
|
|
|
|
|
|
|
|
|
class UDTF(DerivedTable, Unionable):
|
2025-02-13 15:53:39 +01:00
|
|
|
@property
|
2025-02-13 20:45:52 +01:00
|
|
|
def selects(self) -> t.List[Expression]:
|
2025-02-13 15:53:39 +01:00
|
|
|
alias = self.args.get("alias")
|
|
|
|
return alias.columns if alias else []
|
2025-02-13 14:43:32 +01:00
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Cache(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"with": False,
|
|
|
|
"this": True,
|
|
|
|
"lazy": False,
|
|
|
|
"options": False,
|
|
|
|
"expression": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class Uncache(Expression):
|
|
|
|
arg_types = {"this": True, "exists": False}
|
|
|
|
|
|
|
|
|
2025-02-13 20:48:36 +01:00
|
|
|
class DDL(Expression):
|
|
|
|
@property
|
|
|
|
def ctes(self):
|
|
|
|
with_ = self.args.get("with")
|
|
|
|
if not with_:
|
|
|
|
return []
|
|
|
|
return with_.expressions
|
|
|
|
|
|
|
|
@property
|
|
|
|
def named_selects(self) -> t.List[str]:
|
|
|
|
if isinstance(self.expression, Subqueryable):
|
|
|
|
return self.expression.named_selects
|
|
|
|
return []
|
|
|
|
|
|
|
|
@property
|
|
|
|
def selects(self) -> t.List[Expression]:
|
|
|
|
if isinstance(self.expression, Subqueryable):
|
|
|
|
return self.expression.selects
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
class Create(DDL):
|
2025-02-13 06:15:54 +01:00
|
|
|
arg_types = {
|
|
|
|
"with": False,
|
|
|
|
"this": True,
|
|
|
|
"kind": True,
|
|
|
|
"expression": False,
|
|
|
|
"exists": False,
|
|
|
|
"properties": False,
|
|
|
|
"replace": False,
|
|
|
|
"unique": False,
|
2025-02-13 15:03:38 +01:00
|
|
|
"indexes": False,
|
2025-02-13 15:05:06 +01:00
|
|
|
"no_schema_binding": False,
|
2025-02-13 15:07:05 +01:00
|
|
|
"begin": False,
|
2025-02-13 21:04:58 +01:00
|
|
|
"end": False,
|
2025-02-13 15:57:23 +01:00
|
|
|
"clone": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# https://docs.snowflake.com/en/sql-reference/sql/create-clone
|
2025-02-13 21:04:58 +01:00
|
|
|
# https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_table_clone_statement
|
|
|
|
# https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_table_copy
|
2025-02-13 15:57:23 +01:00
|
|
|
class Clone(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"when": False,
|
|
|
|
"kind": False,
|
2025-02-13 20:58:22 +01:00
|
|
|
"shallow": False,
|
2025-02-13 15:57:23 +01:00
|
|
|
"expression": False,
|
2025-02-13 21:04:58 +01:00
|
|
|
"copy": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 14:46:58 +01:00
|
|
|
class Describe(Expression):
|
2025-02-13 20:58:22 +01:00
|
|
|
arg_types = {"this": True, "kind": False, "expressions": False}
|
2025-02-13 14:46:58 +01:00
|
|
|
|
|
|
|
|
2025-02-13 21:03:38 +01:00
|
|
|
class Kill(Expression):
|
|
|
|
arg_types = {"this": True, "kind": False}
|
|
|
|
|
|
|
|
|
2025-02-13 15:48:10 +01:00
|
|
|
class Pragma(Expression):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 14:53:05 +01:00
|
|
|
class Set(Expression):
|
2025-02-13 20:21:40 +01:00
|
|
|
arg_types = {"expressions": False, "unset": False, "tag": False}
|
2025-02-13 14:53:05 +01:00
|
|
|
|
|
|
|
|
|
|
|
class SetItem(Expression):
|
|
|
|
arg_types = {
|
2025-02-13 14:54:32 +01:00
|
|
|
"this": False,
|
|
|
|
"expressions": False,
|
2025-02-13 14:53:05 +01:00
|
|
|
"kind": False,
|
|
|
|
"collate": False, # MySQL SET NAMES statement
|
2025-02-13 14:54:32 +01:00
|
|
|
"global": False,
|
2025-02-13 14:53:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class Show(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"target": False,
|
|
|
|
"offset": False,
|
|
|
|
"limit": False,
|
|
|
|
"like": False,
|
|
|
|
"where": False,
|
|
|
|
"db": False,
|
2025-02-13 20:58:22 +01:00
|
|
|
"scope": False,
|
|
|
|
"scope_kind": False,
|
2025-02-13 14:53:05 +01:00
|
|
|
"full": False,
|
|
|
|
"mutex": False,
|
|
|
|
"query": False,
|
|
|
|
"channel": False,
|
|
|
|
"global": False,
|
|
|
|
"log": False,
|
|
|
|
"position": False,
|
|
|
|
"types": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 14:40:43 +01:00
|
|
|
class UserDefinedFunction(Expression):
|
2025-02-13 15:07:05 +01:00
|
|
|
arg_types = {"this": True, "expressions": False, "wrapped": False}
|
2025-02-13 14:40:43 +01:00
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class CharacterSet(Expression):
|
|
|
|
arg_types = {"this": True, "default": False}
|
|
|
|
|
|
|
|
|
|
|
|
class With(Expression):
|
|
|
|
arg_types = {"expressions": True, "recursive": False}
|
|
|
|
|
2025-02-13 15:01:55 +01:00
|
|
|
@property
|
|
|
|
def recursive(self) -> bool:
|
|
|
|
return bool(self.args.get("recursive"))
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
class WithinGroup(Expression):
|
|
|
|
arg_types = {"this": True, "expression": False}
|
|
|
|
|
|
|
|
|
|
|
|
class CTE(DerivedTable):
|
|
|
|
arg_types = {"this": True, "alias": True}
|
|
|
|
|
|
|
|
|
|
|
|
class TableAlias(Expression):
|
|
|
|
arg_types = {"this": False, "columns": False}
|
|
|
|
|
|
|
|
@property
|
|
|
|
def columns(self):
|
|
|
|
return self.args.get("columns") or []
|
|
|
|
|
|
|
|
|
|
|
|
class BitString(Condition):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
class HexString(Condition):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 14:48:46 +01:00
|
|
|
class ByteString(Condition):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
class RawString(Condition):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Column(Condition):
|
2025-02-13 15:42:13 +01:00
|
|
|
arg_types = {"this": True, "table": False, "db": False, "catalog": False, "join_mark": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
@property
|
2025-02-13 15:26:26 +01:00
|
|
|
def table(self) -> str:
|
2025-02-13 06:15:54 +01:00
|
|
|
return self.text("table")
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
@property
|
2025-02-13 15:26:26 +01:00
|
|
|
def db(self) -> str:
|
|
|
|
return self.text("db")
|
|
|
|
|
|
|
|
@property
|
|
|
|
def catalog(self) -> str:
|
|
|
|
return self.text("catalog")
|
|
|
|
|
|
|
|
@property
|
|
|
|
def output_name(self) -> str:
|
2025-02-13 15:07:05 +01:00
|
|
|
return self.name
|
|
|
|
|
2025-02-13 15:46:19 +01:00
|
|
|
@property
|
|
|
|
def parts(self) -> t.List[Identifier]:
|
|
|
|
"""Return the parts of a column in order catalog, db, table, name."""
|
2025-02-13 15:57:23 +01:00
|
|
|
return [
|
|
|
|
t.cast(Identifier, self.args[part])
|
|
|
|
for part in ("catalog", "db", "table", "this")
|
|
|
|
if self.args.get(part)
|
|
|
|
]
|
2025-02-13 15:46:19 +01:00
|
|
|
|
2025-02-13 21:03:38 +01:00
|
|
|
def to_dot(self) -> Dot | Identifier:
|
2025-02-13 15:46:19 +01:00
|
|
|
"""Converts the column into a dot expression."""
|
|
|
|
parts = self.parts
|
|
|
|
parent = self.parent
|
|
|
|
|
|
|
|
while parent:
|
|
|
|
if isinstance(parent, Dot):
|
|
|
|
parts.append(parent.expression)
|
|
|
|
parent = parent.parent
|
|
|
|
|
2025-02-13 21:03:38 +01:00
|
|
|
return Dot.build(deepcopy(parts)) if len(parts) > 1 else parts[0]
|
2025-02-13 15:46:19 +01:00
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:50:57 +01:00
|
|
|
class ColumnPosition(Expression):
|
|
|
|
arg_types = {"this": False, "position": True}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class ColumnDef(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
2025-02-13 15:03:38 +01:00
|
|
|
"kind": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
"constraints": False,
|
2025-02-13 15:01:55 +01:00
|
|
|
"exists": False,
|
2025-02-13 15:50:57 +01:00
|
|
|
"position": False,
|
2025-02-13 15:01:55 +01:00
|
|
|
}
|
|
|
|
|
2025-02-13 15:53:39 +01:00
|
|
|
@property
|
|
|
|
def constraints(self) -> t.List[ColumnConstraint]:
|
|
|
|
return self.args.get("constraints") or []
|
|
|
|
|
2025-02-13 15:01:55 +01:00
|
|
|
|
|
|
|
class AlterColumn(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"dtype": False,
|
|
|
|
"collate": False,
|
|
|
|
"using": False,
|
|
|
|
"default": False,
|
|
|
|
"drop": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 15:05:06 +01:00
|
|
|
class RenameTable(Expression):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:42:13 +01:00
|
|
|
class Comment(Expression):
|
|
|
|
arg_types = {"this": True, "kind": True, "expression": True, "exists": False}
|
|
|
|
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
class Comprehension(Expression):
|
|
|
|
arg_types = {"this": True, "expression": True, "iterator": True, "condition": False}
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
# https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl
|
|
|
|
class MergeTreeTTLAction(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"delete": False,
|
|
|
|
"recompress": False,
|
|
|
|
"to_disk": False,
|
|
|
|
"to_volume": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl
|
|
|
|
class MergeTreeTTL(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"expressions": True,
|
|
|
|
"where": False,
|
|
|
|
"group": False,
|
|
|
|
"aggregates": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 20:48:36 +01:00
|
|
|
# https://dev.mysql.com/doc/refman/8.0/en/create-table.html
|
|
|
|
class IndexConstraintOption(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"key_block_size": False,
|
|
|
|
"using": False,
|
|
|
|
"parser": False,
|
|
|
|
"comment": False,
|
|
|
|
"visible": False,
|
|
|
|
"engine_attr": False,
|
|
|
|
"secondary_engine_attr": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class ColumnConstraint(Expression):
|
|
|
|
arg_types = {"this": False, "kind": True}
|
|
|
|
|
2025-02-13 15:53:39 +01:00
|
|
|
@property
|
|
|
|
def kind(self) -> ColumnConstraintKind:
|
|
|
|
return self.args["kind"]
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
class ColumnConstraintKind(Expression):
|
2025-02-13 06:15:54 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
class AutoIncrementColumnConstraint(ColumnConstraintKind):
|
2025-02-13 06:15:54 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:26:26 +01:00
|
|
|
class CaseSpecificColumnConstraint(ColumnConstraintKind):
|
|
|
|
arg_types = {"not_": True}
|
|
|
|
|
|
|
|
|
|
|
|
class CharacterSetColumnConstraint(ColumnConstraintKind):
|
|
|
|
arg_types = {"this": True}
|
|
|
|
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
class CheckColumnConstraint(ColumnConstraintKind):
|
2025-02-13 06:15:54 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
class ClusteredColumnConstraint(ColumnConstraintKind):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
class CollateColumnConstraint(ColumnConstraintKind):
|
2025-02-13 06:15:54 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
class CommentColumnConstraint(ColumnConstraintKind):
|
2025-02-13 06:15:54 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:30:09 +01:00
|
|
|
class CompressColumnConstraint(ColumnConstraintKind):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:26:26 +01:00
|
|
|
class DateFormatColumnConstraint(ColumnConstraintKind):
|
|
|
|
arg_types = {"this": True}
|
|
|
|
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
class DefaultColumnConstraint(ColumnConstraintKind):
|
2025-02-13 06:15:54 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 14:58:37 +01:00
|
|
|
class EncodeColumnConstraint(ColumnConstraintKind):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
class GeneratedAsIdentityColumnConstraint(ColumnConstraintKind):
|
|
|
|
# this: True -> ALWAYS, this: False -> BY DEFAULT
|
2025-02-13 15:26:26 +01:00
|
|
|
arg_types = {
|
|
|
|
"this": False,
|
2025-02-13 15:57:23 +01:00
|
|
|
"expression": False,
|
|
|
|
"on_null": False,
|
2025-02-13 15:26:26 +01:00
|
|
|
"start": False,
|
|
|
|
"increment": False,
|
|
|
|
"minvalue": False,
|
|
|
|
"maxvalue": False,
|
|
|
|
"cycle": False,
|
|
|
|
}
|
2025-02-13 08:04:41 +01:00
|
|
|
|
|
|
|
|
2025-02-13 20:48:36 +01:00
|
|
|
# https://dev.mysql.com/doc/refman/8.0/en/create-table.html
|
|
|
|
class IndexColumnConstraint(ColumnConstraintKind):
|
2025-02-13 21:02:36 +01:00
|
|
|
arg_types = {
|
|
|
|
"this": False,
|
|
|
|
"schema": True,
|
|
|
|
"kind": False,
|
|
|
|
"index_type": False,
|
|
|
|
"options": False,
|
|
|
|
}
|
2025-02-13 20:48:36 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:30:09 +01:00
|
|
|
class InlineLengthColumnConstraint(ColumnConstraintKind):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
class NonClusteredColumnConstraint(ColumnConstraintKind):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class NotForReplicationColumnConstraint(ColumnConstraintKind):
|
|
|
|
arg_types = {}
|
|
|
|
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
class NotNullColumnConstraint(ColumnConstraintKind):
|
2025-02-13 14:58:37 +01:00
|
|
|
arg_types = {"allow_null": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
# https://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html
|
|
|
|
class OnUpdateColumnConstraint(ColumnConstraintKind):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
class PrimaryKeyColumnConstraint(ColumnConstraintKind):
|
2025-02-13 14:56:25 +01:00
|
|
|
arg_types = {"desc": False}
|
2025-02-13 08:04:41 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:26:26 +01:00
|
|
|
class TitleColumnConstraint(ColumnConstraintKind):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
class UniqueColumnConstraint(ColumnConstraintKind):
|
2025-02-13 21:02:36 +01:00
|
|
|
arg_types = {"this": False, "index_type": False}
|
2025-02-13 15:26:26 +01:00
|
|
|
|
|
|
|
|
|
|
|
class UppercaseColumnConstraint(ColumnConstraintKind):
|
|
|
|
arg_types: t.Dict[str, t.Any] = {}
|
|
|
|
|
|
|
|
|
|
|
|
class PathColumnConstraint(ColumnConstraintKind):
|
2025-02-13 06:15:54 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
# computed column expression
|
|
|
|
# https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql?view=sql-server-ver16
|
|
|
|
class ComputedColumnConstraint(ColumnConstraintKind):
|
|
|
|
arg_types = {"this": True, "persisted": False, "not_null": False}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Constraint(Expression):
|
|
|
|
arg_types = {"this": True, "expressions": True}
|
|
|
|
|
|
|
|
|
|
|
|
class Delete(Expression):
|
2025-02-13 20:04:59 +01:00
|
|
|
arg_types = {
|
|
|
|
"with": False,
|
|
|
|
"this": False,
|
|
|
|
"using": False,
|
|
|
|
"where": False,
|
|
|
|
"returning": False,
|
|
|
|
"limit": False,
|
2025-02-13 20:43:05 +01:00
|
|
|
"tables": False, # Multiple-Table Syntax (MySQL)
|
2025-02-13 20:04:59 +01:00
|
|
|
}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:46:19 +01:00
|
|
|
def delete(
|
|
|
|
self,
|
|
|
|
table: ExpOrStr,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Delete:
|
|
|
|
"""
|
|
|
|
Create a DELETE expression or replace the table on an existing DELETE expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> delete("tbl").sql()
|
|
|
|
'DELETE FROM tbl'
|
|
|
|
|
|
|
|
Args:
|
|
|
|
table: the table from which to delete.
|
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Delete: the modified expression.
|
|
|
|
"""
|
|
|
|
return _apply_builder(
|
|
|
|
expression=table,
|
|
|
|
instance=self,
|
|
|
|
arg="this",
|
|
|
|
dialect=dialect,
|
|
|
|
into=Table,
|
|
|
|
copy=copy,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
|
|
|
def where(
|
|
|
|
self,
|
2025-02-13 15:57:23 +01:00
|
|
|
*expressions: t.Optional[ExpOrStr],
|
2025-02-13 15:46:19 +01:00
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Delete:
|
|
|
|
"""
|
|
|
|
Append to or set the WHERE expressions.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> delete("tbl").where("x = 'a' OR x < 'b'").sql()
|
|
|
|
"DELETE FROM tbl WHERE x = 'a' OR x < 'b'"
|
|
|
|
|
|
|
|
Args:
|
|
|
|
*expressions: the SQL code strings to parse.
|
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
|
|
|
Multiple expressions are combined with an AND operator.
|
|
|
|
append: if `True`, AND the new expressions to any existing expression.
|
|
|
|
Otherwise, this resets the expression.
|
|
|
|
dialect: the dialect used to parse the input expressions.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Delete: the modified expression.
|
|
|
|
"""
|
|
|
|
return _apply_conjunction_builder(
|
|
|
|
*expressions,
|
|
|
|
instance=self,
|
|
|
|
arg="where",
|
|
|
|
append=append,
|
|
|
|
into=Where,
|
|
|
|
dialect=dialect,
|
|
|
|
copy=copy,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
|
|
|
def returning(
|
|
|
|
self,
|
|
|
|
expression: ExpOrStr,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Delete:
|
|
|
|
"""
|
|
|
|
Set the RETURNING expression. Not supported by all dialects.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> delete("tbl").returning("*", dialect="postgres").sql()
|
|
|
|
'DELETE FROM tbl RETURNING *'
|
|
|
|
|
|
|
|
Args:
|
|
|
|
expression: the SQL code strings to parse.
|
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
|
|
|
dialect: the dialect used to parse the input expressions.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Delete: the modified expression.
|
|
|
|
"""
|
|
|
|
return _apply_builder(
|
|
|
|
expression=expression,
|
|
|
|
instance=self,
|
|
|
|
arg="returning",
|
|
|
|
prefix="RETURNING",
|
|
|
|
dialect=dialect,
|
|
|
|
copy=copy,
|
|
|
|
into=Returning,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
class Drop(Expression):
|
2025-02-13 14:43:32 +01:00
|
|
|
arg_types = {
|
|
|
|
"this": False,
|
|
|
|
"kind": False,
|
|
|
|
"exists": False,
|
|
|
|
"temporary": False,
|
|
|
|
"materialized": False,
|
2025-02-13 14:54:32 +01:00
|
|
|
"cascade": False,
|
2025-02-13 15:48:10 +01:00
|
|
|
"constraints": False,
|
2025-02-13 15:52:09 +01:00
|
|
|
"purge": False,
|
2025-02-13 14:43:32 +01:00
|
|
|
}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Filter(Expression):
|
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
|
|
|
|
|
|
|
|
class Check(Expression):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
# https://docs.snowflake.com/en/sql-reference/constructs/connect-by
|
|
|
|
class Connect(Expression):
|
|
|
|
arg_types = {"start": False, "connect": True}
|
|
|
|
|
|
|
|
|
|
|
|
class Prior(Expression):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 14:46:58 +01:00
|
|
|
class Directory(Expression):
|
|
|
|
# https://spark.apache.org/docs/3.0.0-preview/sql-ref-syntax-dml-insert-overwrite-directory-hive.html
|
|
|
|
arg_types = {"this": True, "local": False, "row_format": False}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class ForeignKey(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"expressions": True,
|
|
|
|
"reference": False,
|
|
|
|
"delete": False,
|
|
|
|
"update": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 21:01:12 +01:00
|
|
|
class ColumnPrefix(Expression):
|
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
class PrimaryKey(Expression):
|
|
|
|
arg_types = {"expressions": True, "options": False}
|
|
|
|
|
|
|
|
|
2025-02-13 14:56:25 +01:00
|
|
|
# https://www.postgresql.org/docs/9.1/sql-selectinto.html
|
|
|
|
# https://docs.aws.amazon.com/redshift/latest/dg/r_SELECT_INTO.html#r_SELECT_INTO-examples
|
|
|
|
class Into(Expression):
|
|
|
|
arg_types = {"this": True, "temporary": False, "unlogged": False}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class From(Expression):
|
2025-02-13 15:57:23 +01:00
|
|
|
@property
|
|
|
|
def name(self) -> str:
|
|
|
|
return self.this.name
|
|
|
|
|
|
|
|
@property
|
|
|
|
def alias_or_name(self) -> str:
|
|
|
|
return self.this.alias_or_name
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Having(Expression):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Hint(Expression):
|
|
|
|
arg_types = {"expressions": True}
|
|
|
|
|
|
|
|
|
2025-02-13 14:45:11 +01:00
|
|
|
class JoinHint(Expression):
|
|
|
|
arg_types = {"this": True, "expressions": True}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Identifier(Expression):
|
2025-02-13 20:48:36 +01:00
|
|
|
arg_types = {"this": True, "quoted": False, "global": False, "temporary": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def quoted(self) -> bool:
|
2025-02-13 06:15:54 +01:00
|
|
|
return bool(self.args.get("quoted"))
|
|
|
|
|
2025-02-13 15:48:10 +01:00
|
|
|
@property
|
|
|
|
def hashable_args(self) -> t.Any:
|
2025-02-13 20:43:05 +01:00
|
|
|
return (self.this, self.quoted)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def output_name(self) -> str:
|
2025-02-13 15:07:05 +01:00
|
|
|
return self.name
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 21:04:58 +01:00
|
|
|
# https://www.postgresql.org/docs/current/indexes-opclass.html
|
|
|
|
class Opclass(Expression):
|
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Index(Expression):
|
2025-02-13 15:03:38 +01:00
|
|
|
arg_types = {
|
|
|
|
"this": False,
|
|
|
|
"table": False,
|
2025-02-13 16:00:51 +01:00
|
|
|
"using": False,
|
2025-02-13 15:03:38 +01:00
|
|
|
"where": False,
|
|
|
|
"columns": False,
|
|
|
|
"unique": False,
|
|
|
|
"primary": False,
|
|
|
|
"amp": False, # teradata
|
2025-02-13 15:57:23 +01:00
|
|
|
"partition_by": False, # teradata
|
2025-02-13 21:03:38 +01:00
|
|
|
"where": False, # postgres partial indexes
|
2025-02-13 15:03:38 +01:00
|
|
|
}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 20:48:36 +01:00
|
|
|
class Insert(DDL):
|
2025-02-13 06:15:54 +01:00
|
|
|
arg_types = {
|
|
|
|
"with": False,
|
|
|
|
"this": True,
|
2025-02-13 14:58:37 +01:00
|
|
|
"expression": False,
|
2025-02-13 15:52:09 +01:00
|
|
|
"conflict": False,
|
2025-02-13 15:43:32 +01:00
|
|
|
"returning": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
"overwrite": False,
|
|
|
|
"exists": False,
|
|
|
|
"partition": False,
|
2025-02-13 15:26:26 +01:00
|
|
|
"alternative": False,
|
2025-02-13 20:15:27 +01:00
|
|
|
"where": False,
|
2025-02-13 20:43:05 +01:00
|
|
|
"ignore": False,
|
2025-02-13 20:58:22 +01:00
|
|
|
"by_name": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
}
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def with_(
|
|
|
|
self,
|
|
|
|
alias: ExpOrStr,
|
|
|
|
as_: ExpOrStr,
|
|
|
|
recursive: t.Optional[bool] = None,
|
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Insert:
|
|
|
|
"""
|
|
|
|
Append to or set the common table expressions.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> insert("SELECT x FROM cte", "t").with_("cte", as_="SELECT * FROM tbl").sql()
|
|
|
|
'WITH cte AS (SELECT * FROM tbl) INSERT INTO t SELECT x FROM cte'
|
|
|
|
|
|
|
|
Args:
|
|
|
|
alias: the SQL code string to parse as the table name.
|
|
|
|
If an `Expression` instance is passed, this is used as-is.
|
|
|
|
as_: the SQL code string to parse as the table expression.
|
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
|
|
|
recursive: set the RECURSIVE part of the expression. Defaults to `False`.
|
|
|
|
append: if `True`, add to any existing expressions.
|
|
|
|
Otherwise, this resets the expressions.
|
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The modified expression.
|
|
|
|
"""
|
|
|
|
return _apply_cte_builder(
|
|
|
|
self, alias, as_, recursive=recursive, append=append, dialect=dialect, copy=copy, **opts
|
|
|
|
)
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
class OnConflict(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"duplicate": False,
|
|
|
|
"expressions": False,
|
|
|
|
"nothing": False,
|
|
|
|
"key": False,
|
|
|
|
"constraint": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 15:43:32 +01:00
|
|
|
class Returning(Expression):
|
2025-02-13 20:45:52 +01:00
|
|
|
arg_types = {"expressions": True, "into": False}
|
2025-02-13 15:43:32 +01:00
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
# https://dev.mysql.com/doc/refman/8.0/en/charset-introducer.html
|
|
|
|
class Introducer(Expression):
|
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
|
|
|
|
|
2025-02-13 15:01:55 +01:00
|
|
|
# national char, like n'utf8'
|
|
|
|
class National(Expression):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 14:46:58 +01:00
|
|
|
class LoadData(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"local": False,
|
|
|
|
"overwrite": False,
|
|
|
|
"inpath": True,
|
|
|
|
"partition": False,
|
|
|
|
"input_format": False,
|
|
|
|
"serde": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Partition(Expression):
|
2025-02-13 15:08:15 +01:00
|
|
|
arg_types = {"expressions": True}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Fetch(Expression):
|
2025-02-13 15:52:09 +01:00
|
|
|
arg_types = {
|
|
|
|
"direction": False,
|
|
|
|
"count": False,
|
|
|
|
"percent": False,
|
|
|
|
"with_ties": False,
|
|
|
|
}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Group(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"expressions": False,
|
|
|
|
"grouping_sets": False,
|
|
|
|
"cube": False,
|
|
|
|
"rollup": False,
|
2025-02-13 15:57:23 +01:00
|
|
|
"totals": False,
|
2025-02-13 20:43:05 +01:00
|
|
|
"all": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class Lambda(Expression):
|
|
|
|
arg_types = {"this": True, "expressions": True}
|
|
|
|
|
|
|
|
|
|
|
|
class Limit(Expression):
|
2025-02-13 16:00:51 +01:00
|
|
|
arg_types = {"this": False, "expression": True, "offset": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Literal(Condition):
|
|
|
|
arg_types = {"this": True, "is_string": True}
|
|
|
|
|
2025-02-13 15:48:10 +01:00
|
|
|
@property
|
|
|
|
def hashable_args(self) -> t.Any:
|
|
|
|
return (self.this, self.args.get("is_string"))
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
@classmethod
|
2025-02-13 14:53:05 +01:00
|
|
|
def number(cls, number) -> Literal:
|
2025-02-13 06:15:54 +01:00
|
|
|
return cls(this=str(number), is_string=False)
|
|
|
|
|
|
|
|
@classmethod
|
2025-02-13 14:53:05 +01:00
|
|
|
def string(cls, string) -> Literal:
|
2025-02-13 06:15:54 +01:00
|
|
|
return cls(this=str(string), is_string=True)
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def output_name(self) -> str:
|
2025-02-13 15:07:05 +01:00
|
|
|
return self.name
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
class Join(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"on": False,
|
|
|
|
"side": False,
|
|
|
|
"kind": False,
|
|
|
|
"using": False,
|
2025-02-13 15:58:40 +01:00
|
|
|
"method": False,
|
2025-02-13 15:57:23 +01:00
|
|
|
"global": False,
|
2025-02-13 15:52:09 +01:00
|
|
|
"hint": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
}
|
|
|
|
|
2025-02-13 15:58:40 +01:00
|
|
|
@property
|
|
|
|
def method(self) -> str:
|
|
|
|
return self.text("method").upper()
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def kind(self) -> str:
|
2025-02-13 06:15:54 +01:00
|
|
|
return self.text("kind").upper()
|
|
|
|
|
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def side(self) -> str:
|
2025-02-13 06:15:54 +01:00
|
|
|
return self.text("side").upper()
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def hint(self) -> str:
|
2025-02-13 15:52:09 +01:00
|
|
|
return self.text("hint").upper()
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def alias_or_name(self) -> str:
|
2025-02-13 08:04:41 +01:00
|
|
|
return self.this.alias_or_name
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def on(
|
|
|
|
self,
|
|
|
|
*expressions: t.Optional[ExpOrStr],
|
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Join:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Append to or set the ON expressions.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> import sqlglot
|
|
|
|
>>> sqlglot.parse_one("JOIN x", into=Join).on("y = 1").sql()
|
|
|
|
'JOIN x ON y = 1'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
*expressions: the SQL code strings to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
|
|
|
Multiple expressions are combined with an AND operator.
|
2025-02-13 15:57:23 +01:00
|
|
|
append: if `True`, AND the new expressions to any existing expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
Otherwise, this resets the expression.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expressions.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The modified Join expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
join = _apply_conjunction_builder(
|
|
|
|
*expressions,
|
|
|
|
instance=self,
|
|
|
|
arg="on",
|
|
|
|
append=append,
|
|
|
|
dialect=dialect,
|
|
|
|
copy=copy,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
|
|
|
if join.kind == "CROSS":
|
|
|
|
join.set("kind", None)
|
|
|
|
|
|
|
|
return join
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def using(
|
|
|
|
self,
|
|
|
|
*expressions: t.Optional[ExpOrStr],
|
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Join:
|
2025-02-13 14:51:47 +01:00
|
|
|
"""
|
|
|
|
Append to or set the USING expressions.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> import sqlglot
|
|
|
|
>>> sqlglot.parse_one("JOIN x", into=Join).using("foo", "bla").sql()
|
|
|
|
'JOIN x USING (foo, bla)'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
*expressions: the SQL code strings to parse.
|
2025-02-13 14:51:47 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
append: if `True`, concatenate the new expressions to the existing "using" list.
|
2025-02-13 14:51:47 +01:00
|
|
|
Otherwise, this resets the expression.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expressions.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 14:51:47 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The modified Join expression.
|
2025-02-13 14:51:47 +01:00
|
|
|
"""
|
|
|
|
join = _apply_list_builder(
|
|
|
|
*expressions,
|
|
|
|
instance=self,
|
|
|
|
arg="using",
|
|
|
|
append=append,
|
|
|
|
dialect=dialect,
|
|
|
|
copy=copy,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
|
|
|
if join.kind == "CROSS":
|
|
|
|
join.set("kind", None)
|
|
|
|
|
|
|
|
return join
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 14:43:32 +01:00
|
|
|
class Lateral(UDTF):
|
2025-02-13 14:51:47 +01:00
|
|
|
arg_types = {"this": True, "view": False, "outer": False, "alias": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:08:15 +01:00
|
|
|
class MatchRecognize(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"partition_by": False,
|
|
|
|
"order": False,
|
|
|
|
"measures": False,
|
|
|
|
"rows": False,
|
|
|
|
"after": False,
|
|
|
|
"pattern": False,
|
|
|
|
"define": False,
|
2025-02-13 15:52:09 +01:00
|
|
|
"alias": False,
|
2025-02-13 15:08:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
# Clickhouse FROM FINAL modifier
|
|
|
|
# https://clickhouse.com/docs/en/sql-reference/statements/select/from/#final-modifier
|
|
|
|
class Final(Expression):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Offset(Expression):
|
|
|
|
arg_types = {"this": False, "expression": True}
|
|
|
|
|
|
|
|
|
|
|
|
class Order(Expression):
|
|
|
|
arg_types = {"this": False, "expressions": True}
|
|
|
|
|
|
|
|
|
|
|
|
# hive specific sorts
|
|
|
|
# https://cwiki.apache.org/confluence/display/Hive/LanguageManual+SortBy
|
|
|
|
class Cluster(Order):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Distribute(Order):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Sort(Order):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Ordered(Expression):
|
2025-02-13 21:03:38 +01:00
|
|
|
arg_types = {"this": True, "desc": False, "nulls_first": True}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Property(Expression):
|
|
|
|
arg_types = {"this": True, "value": True}
|
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class AlgorithmProperty(Property):
|
2025-02-13 15:09:58 +01:00
|
|
|
arg_types = {"this": True}
|
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class AutoIncrementProperty(Property):
|
|
|
|
arg_types = {"this": True}
|
2025-02-13 15:09:58 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class BlockCompressionProperty(Property):
|
|
|
|
arg_types = {"autotemp": False, "always": False, "default": True, "manual": True, "never": True}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class CharacterSetProperty(Property):
|
|
|
|
arg_types = {"this": True, "default": True}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class ChecksumProperty(Property):
|
|
|
|
arg_types = {"on": False, "default": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class CollateProperty(Property):
|
2025-02-13 14:56:25 +01:00
|
|
|
arg_types = {"this": True}
|
2025-02-13 14:54:32 +01:00
|
|
|
|
|
|
|
|
2025-02-13 20:04:59 +01:00
|
|
|
class CopyGrantsProperty(Property):
|
|
|
|
arg_types = {}
|
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class DataBlocksizeProperty(Property):
|
2025-02-13 15:57:23 +01:00
|
|
|
arg_types = {
|
|
|
|
"size": False,
|
|
|
|
"units": False,
|
|
|
|
"minimum": False,
|
|
|
|
"maximum": False,
|
|
|
|
"default": False,
|
|
|
|
}
|
2025-02-13 14:54:32 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class DefinerProperty(Property):
|
2025-02-13 14:56:25 +01:00
|
|
|
arg_types = {"this": True}
|
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class DistKeyProperty(Property):
|
|
|
|
arg_types = {"this": True}
|
2025-02-13 14:54:32 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class DistStyleProperty(Property):
|
2025-02-13 14:56:25 +01:00
|
|
|
arg_types = {"this": True}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class EngineProperty(Property):
|
2025-02-13 14:56:25 +01:00
|
|
|
arg_types = {"this": True}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 20:48:36 +01:00
|
|
|
class HeapProperty(Property):
|
|
|
|
arg_types = {}
|
|
|
|
|
|
|
|
|
2025-02-13 16:00:51 +01:00
|
|
|
class ToTableProperty(Property):
|
|
|
|
arg_types = {"this": True}
|
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class ExecuteAsProperty(Property):
|
2025-02-13 14:56:25 +01:00
|
|
|
arg_types = {"this": True}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class ExternalProperty(Property):
|
|
|
|
arg_types = {"this": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class FallbackProperty(Property):
|
|
|
|
arg_types = {"no": True, "protection": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class FileFormatProperty(Property):
|
2025-02-13 14:56:25 +01:00
|
|
|
arg_types = {"this": True}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class FreespaceProperty(Property):
|
|
|
|
arg_types = {"this": True, "percent": False}
|
|
|
|
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
class InputOutputFormat(Expression):
|
|
|
|
arg_types = {"input_format": False, "output_format": False}
|
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class IsolatedLoadingProperty(Property):
|
|
|
|
arg_types = {
|
|
|
|
"no": True,
|
|
|
|
"concurrent": True,
|
|
|
|
"for_all": True,
|
|
|
|
"for_insert": True,
|
|
|
|
"for_none": True,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class JournalProperty(Property):
|
2025-02-13 15:57:23 +01:00
|
|
|
arg_types = {
|
|
|
|
"no": False,
|
|
|
|
"dual": False,
|
|
|
|
"before": False,
|
|
|
|
"local": False,
|
|
|
|
"after": False,
|
|
|
|
}
|
2025-02-13 14:40:43 +01:00
|
|
|
|
|
|
|
|
|
|
|
class LanguageProperty(Property):
|
2025-02-13 14:56:25 +01:00
|
|
|
arg_types = {"this": True}
|
2025-02-13 14:40:43 +01:00
|
|
|
|
|
|
|
|
2025-02-13 20:21:40 +01:00
|
|
|
# spark ddl
|
|
|
|
class ClusteredByProperty(Property):
|
|
|
|
arg_types = {"expressions": True, "sorted_by": False, "buckets": True}
|
|
|
|
|
|
|
|
|
2025-02-13 15:58:40 +01:00
|
|
|
class DictProperty(Property):
|
|
|
|
arg_types = {"this": True, "kind": True, "settings": False}
|
|
|
|
|
|
|
|
|
|
|
|
class DictSubProperty(Property):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class DictRange(Property):
|
|
|
|
arg_types = {"this": True, "min": True, "max": True}
|
|
|
|
|
|
|
|
|
|
|
|
# Clickhouse CREATE ... ON CLUSTER modifier
|
|
|
|
# https://clickhouse.com/docs/en/sql-reference/distributed-ddl
|
|
|
|
class OnCluster(Property):
|
|
|
|
arg_types = {"this": True}
|
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class LikeProperty(Property):
|
|
|
|
arg_types = {"this": True, "expressions": False}
|
|
|
|
|
|
|
|
|
|
|
|
class LocationProperty(Property):
|
2025-02-13 14:56:25 +01:00
|
|
|
arg_types = {"this": True}
|
2025-02-13 14:43:32 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class LockingProperty(Property):
|
|
|
|
arg_types = {
|
|
|
|
"this": False,
|
|
|
|
"kind": True,
|
|
|
|
"for_or_in": True,
|
|
|
|
"lock_type": True,
|
|
|
|
"override": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class LogProperty(Property):
|
|
|
|
arg_types = {"no": True}
|
|
|
|
|
|
|
|
|
|
|
|
class MaterializedProperty(Property):
|
|
|
|
arg_types = {"this": False}
|
|
|
|
|
|
|
|
|
|
|
|
class MergeBlockRatioProperty(Property):
|
|
|
|
arg_types = {"this": False, "no": False, "default": False, "percent": False}
|
|
|
|
|
|
|
|
|
|
|
|
class NoPrimaryIndexProperty(Property):
|
2025-02-13 15:57:23 +01:00
|
|
|
arg_types = {}
|
2025-02-13 15:40:23 +01:00
|
|
|
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
class OnProperty(Property):
|
|
|
|
arg_types = {"this": True}
|
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class OnCommitProperty(Property):
|
2025-02-13 20:58:22 +01:00
|
|
|
arg_types = {"delete": False}
|
2025-02-13 15:40:23 +01:00
|
|
|
|
|
|
|
|
|
|
|
class PartitionedByProperty(Property):
|
2025-02-13 14:43:32 +01:00
|
|
|
arg_types = {"this": True}
|
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class ReturnsProperty(Property):
|
|
|
|
arg_types = {"this": True, "is_table": False, "table": False}
|
|
|
|
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
class RowFormatProperty(Property):
|
|
|
|
arg_types = {"this": True}
|
|
|
|
|
|
|
|
|
2025-02-13 14:58:37 +01:00
|
|
|
class RowFormatDelimitedProperty(Property):
|
|
|
|
# https://cwiki.apache.org/confluence/display/hive/languagemanual+dml
|
|
|
|
arg_types = {
|
|
|
|
"fields": False,
|
|
|
|
"escaped": False,
|
|
|
|
"collection_items": False,
|
|
|
|
"map_keys": False,
|
|
|
|
"lines": False,
|
|
|
|
"null": False,
|
|
|
|
"serde": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class RowFormatSerdeProperty(Property):
|
2025-02-13 20:46:55 +01:00
|
|
|
arg_types = {"this": True, "serde_properties": False}
|
|
|
|
|
|
|
|
|
|
|
|
# https://spark.apache.org/docs/3.1.2/sql-ref-syntax-qry-select-transform.html
|
|
|
|
class QueryTransform(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"expressions": True,
|
|
|
|
"command_script": True,
|
|
|
|
"schema": False,
|
|
|
|
"row_format_before": False,
|
|
|
|
"record_writer": False,
|
|
|
|
"row_format_after": False,
|
|
|
|
"record_reader": False,
|
|
|
|
}
|
2025-02-13 14:58:37 +01:00
|
|
|
|
|
|
|
|
2025-02-13 21:04:58 +01:00
|
|
|
class SampleProperty(Property):
|
|
|
|
arg_types = {"this": True}
|
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class SchemaCommentProperty(Property):
|
2025-02-13 15:08:15 +01:00
|
|
|
arg_types = {"this": True}
|
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class SerdeProperties(Property):
|
|
|
|
arg_types = {"expressions": True}
|
2025-02-13 15:08:15 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class SetProperty(Property):
|
|
|
|
arg_types = {"multi": True}
|
2025-02-13 15:08:15 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
class SettingsProperty(Property):
|
|
|
|
arg_types = {"expressions": True}
|
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class SortKeyProperty(Property):
|
|
|
|
arg_types = {"this": True, "compound": False}
|
2025-02-13 15:08:15 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class SqlSecurityProperty(Property):
|
|
|
|
arg_types = {"definer": True}
|
2025-02-13 15:08:15 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
class StabilityProperty(Property):
|
|
|
|
arg_types = {"this": True}
|
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class TemporaryProperty(Property):
|
2025-02-13 15:57:23 +01:00
|
|
|
arg_types = {}
|
2025-02-13 15:08:15 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class TransientProperty(Property):
|
|
|
|
arg_types = {"this": False}
|
2025-02-13 15:08:15 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
class VolatileProperty(Property):
|
|
|
|
arg_types = {"this": False}
|
2025-02-13 15:08:15 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class WithDataProperty(Property):
|
|
|
|
arg_types = {"no": True, "statistics": False}
|
2025-02-13 15:08:15 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
class WithJournalTableProperty(Property):
|
|
|
|
arg_types = {"this": True}
|
2025-02-13 15:26:26 +01:00
|
|
|
|
|
|
|
|
2025-02-13 14:31:47 +01:00
|
|
|
class Properties(Expression):
|
2025-02-13 15:09:58 +01:00
|
|
|
arg_types = {"expressions": True}
|
2025-02-13 14:31:47 +01:00
|
|
|
|
2025-02-13 14:56:25 +01:00
|
|
|
NAME_TO_PROPERTY = {
|
2025-02-13 15:09:58 +01:00
|
|
|
"ALGORITHM": AlgorithmProperty,
|
2025-02-13 14:31:47 +01:00
|
|
|
"AUTO_INCREMENT": AutoIncrementProperty,
|
2025-02-13 14:56:25 +01:00
|
|
|
"CHARACTER SET": CharacterSetProperty,
|
2025-02-13 20:21:40 +01:00
|
|
|
"CLUSTERED_BY": ClusteredByProperty,
|
2025-02-13 14:31:47 +01:00
|
|
|
"COLLATE": CollateProperty,
|
|
|
|
"COMMENT": SchemaCommentProperty,
|
2025-02-13 15:09:58 +01:00
|
|
|
"DEFINER": DefinerProperty,
|
2025-02-13 14:56:25 +01:00
|
|
|
"DISTKEY": DistKeyProperty,
|
|
|
|
"DISTSTYLE": DistStyleProperty,
|
2025-02-13 14:31:47 +01:00
|
|
|
"ENGINE": EngineProperty,
|
2025-02-13 14:56:25 +01:00
|
|
|
"EXECUTE AS": ExecuteAsProperty,
|
2025-02-13 14:31:47 +01:00
|
|
|
"FORMAT": FileFormatProperty,
|
2025-02-13 14:56:25 +01:00
|
|
|
"LANGUAGE": LanguageProperty,
|
2025-02-13 14:31:47 +01:00
|
|
|
"LOCATION": LocationProperty,
|
|
|
|
"PARTITIONED_BY": PartitionedByProperty,
|
2025-02-13 14:56:25 +01:00
|
|
|
"RETURNS": ReturnsProperty,
|
2025-02-13 15:52:09 +01:00
|
|
|
"ROW_FORMAT": RowFormatProperty,
|
2025-02-13 14:54:32 +01:00
|
|
|
"SORTKEY": SortKeyProperty,
|
2025-02-13 14:31:47 +01:00
|
|
|
}
|
|
|
|
|
2025-02-13 14:56:25 +01:00
|
|
|
PROPERTY_TO_NAME = {v: k for k, v in NAME_TO_PROPERTY.items()}
|
|
|
|
|
2025-02-13 15:26:26 +01:00
|
|
|
# CREATE property locations
|
|
|
|
# Form: schema specified
|
|
|
|
# create [POST_CREATE]
|
|
|
|
# table a [POST_NAME]
|
|
|
|
# (b int) [POST_SCHEMA]
|
|
|
|
# with ([POST_WITH])
|
|
|
|
# index (b) [POST_INDEX]
|
|
|
|
#
|
|
|
|
# Form: alias selection
|
|
|
|
# create [POST_CREATE]
|
|
|
|
# table a [POST_NAME]
|
2025-02-13 15:40:23 +01:00
|
|
|
# as [POST_ALIAS] (select * from b) [POST_EXPRESSION]
|
2025-02-13 15:26:26 +01:00
|
|
|
# index (c) [POST_INDEX]
|
2025-02-13 15:09:58 +01:00
|
|
|
class Location(AutoName):
|
|
|
|
POST_CREATE = auto()
|
2025-02-13 15:26:26 +01:00
|
|
|
POST_NAME = auto()
|
|
|
|
POST_SCHEMA = auto()
|
|
|
|
POST_WITH = auto()
|
|
|
|
POST_ALIAS = auto()
|
2025-02-13 15:40:23 +01:00
|
|
|
POST_EXPRESSION = auto()
|
2025-02-13 15:09:58 +01:00
|
|
|
POST_INDEX = auto()
|
|
|
|
UNSUPPORTED = auto()
|
|
|
|
|
2025-02-13 14:31:47 +01:00
|
|
|
@classmethod
|
2025-02-13 15:57:23 +01:00
|
|
|
def from_dict(cls, properties_dict: t.Dict) -> Properties:
|
2025-02-13 14:31:47 +01:00
|
|
|
expressions = []
|
|
|
|
for key, value in properties_dict.items():
|
2025-02-13 14:56:25 +01:00
|
|
|
property_cls = cls.NAME_TO_PROPERTY.get(key.upper())
|
|
|
|
if property_cls:
|
|
|
|
expressions.append(property_cls(this=convert(value)))
|
|
|
|
else:
|
|
|
|
expressions.append(Property(this=Literal.string(key), value=convert(value)))
|
|
|
|
|
2025-02-13 14:31:47 +01:00
|
|
|
return cls(expressions=expressions)
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Qualify(Expression):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
# https://www.ibm.com/docs/en/ias?topic=procedures-return-statement-in-sql
|
|
|
|
class Return(Expression):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Reference(Expression):
|
2025-02-13 15:07:05 +01:00
|
|
|
arg_types = {"this": True, "expressions": False, "options": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Tuple(Expression):
|
|
|
|
arg_types = {"expressions": False}
|
|
|
|
|
2025-02-13 15:53:39 +01:00
|
|
|
def isin(
|
2025-02-13 20:46:55 +01:00
|
|
|
self,
|
|
|
|
*expressions: t.Any,
|
|
|
|
query: t.Optional[ExpOrStr] = None,
|
|
|
|
unnest: t.Optional[ExpOrStr] | t.Collection[ExpOrStr] = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
2025-02-13 15:53:39 +01:00
|
|
|
) -> In:
|
|
|
|
return In(
|
2025-02-13 20:48:36 +01:00
|
|
|
this=maybe_copy(self, copy),
|
2025-02-13 15:53:39 +01:00
|
|
|
expressions=[convert(e, copy=copy) for e in expressions],
|
|
|
|
query=maybe_parse(query, copy=copy, **opts) if query else None,
|
2025-02-13 20:46:55 +01:00
|
|
|
unnest=Unnest(
|
|
|
|
expressions=[
|
|
|
|
maybe_parse(t.cast(ExpOrStr, e), copy=copy, **opts) for e in ensure_list(unnest)
|
|
|
|
]
|
|
|
|
)
|
|
|
|
if unnest
|
|
|
|
else None,
|
2025-02-13 15:53:39 +01:00
|
|
|
)
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 14:45:11 +01:00
|
|
|
class Subqueryable(Unionable):
|
2025-02-13 15:57:23 +01:00
|
|
|
def subquery(self, alias: t.Optional[ExpOrStr] = None, copy: bool = True) -> Subquery:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Convert this expression to an aliased expression that can be used as a Subquery.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> subquery = Select().select("x").from_("tbl").subquery()
|
|
|
|
>>> Select().select("x").from_(subquery).sql()
|
|
|
|
'SELECT x FROM (SELECT x FROM tbl)'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 14:51:47 +01:00
|
|
|
alias (str | Identifier): an optional alias for the subquery
|
2025-02-13 06:15:54 +01:00
|
|
|
copy (bool): if `False`, modify this expression instance in-place.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Alias: the subquery
|
|
|
|
"""
|
2025-02-13 20:48:36 +01:00
|
|
|
instance = maybe_copy(self, copy)
|
2025-02-13 15:57:23 +01:00
|
|
|
if not isinstance(alias, Expression):
|
|
|
|
alias = TableAlias(this=to_identifier(alias)) if alias else None
|
|
|
|
|
|
|
|
return Subquery(this=instance, alias=alias)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def limit(
|
|
|
|
self, expression: ExpOrStr | int, dialect: DialectType = None, copy: bool = True, **opts
|
|
|
|
) -> Select:
|
2025-02-13 14:58:37 +01:00
|
|
|
raise NotImplementedError
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
@property
|
|
|
|
def ctes(self):
|
|
|
|
with_ = self.args.get("with")
|
|
|
|
if not with_:
|
|
|
|
return []
|
|
|
|
return with_.expressions
|
|
|
|
|
2025-02-13 14:46:58 +01:00
|
|
|
@property
|
2025-02-13 20:45:52 +01:00
|
|
|
def selects(self) -> t.List[Expression]:
|
2025-02-13 14:46:58 +01:00
|
|
|
raise NotImplementedError("Subqueryable objects must implement `selects`")
|
|
|
|
|
|
|
|
@property
|
2025-02-13 20:45:52 +01:00
|
|
|
def named_selects(self) -> t.List[str]:
|
2025-02-13 14:46:58 +01:00
|
|
|
raise NotImplementedError("Subqueryable objects must implement `named_selects`")
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
def select(
|
|
|
|
self,
|
|
|
|
*expressions: t.Optional[ExpOrStr],
|
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Subqueryable:
|
|
|
|
raise NotImplementedError("Subqueryable objects must implement `select`")
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
def with_(
|
|
|
|
self,
|
2025-02-13 15:57:23 +01:00
|
|
|
alias: ExpOrStr,
|
|
|
|
as_: ExpOrStr,
|
|
|
|
recursive: t.Optional[bool] = None,
|
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
2025-02-13 06:15:54 +01:00
|
|
|
**opts,
|
2025-02-13 15:57:23 +01:00
|
|
|
) -> Subqueryable:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Append to or set the common table expressions.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> Select().with_("tbl2", as_="SELECT * FROM tbl").select("x").from_("tbl2").sql()
|
|
|
|
'WITH tbl2 AS (SELECT * FROM tbl) SELECT x FROM tbl2'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
alias: the SQL code string to parse as the table name.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an `Expression` instance is passed, this is used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
as_: the SQL code string to parse as the table expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
recursive: set the RECURSIVE part of the expression. Defaults to `False`.
|
|
|
|
append: if `True`, add to any existing expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
Otherwise, this resets the expressions.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The modified expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
2025-02-13 15:57:23 +01:00
|
|
|
return _apply_cte_builder(
|
|
|
|
self, alias, as_, recursive=recursive, append=append, dialect=dialect, copy=copy, **opts
|
2025-02-13 06:15:54 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
QUERY_MODIFIERS = {
|
2025-02-13 15:08:15 +01:00
|
|
|
"match": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
"laterals": False,
|
|
|
|
"joins": False,
|
2025-02-13 20:58:22 +01:00
|
|
|
"connect": False,
|
2025-02-13 14:40:43 +01:00
|
|
|
"pivots": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
"where": False,
|
|
|
|
"group": False,
|
|
|
|
"having": False,
|
|
|
|
"qualify": False,
|
2025-02-13 15:01:55 +01:00
|
|
|
"windows": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
"distribute": False,
|
|
|
|
"sort": False,
|
|
|
|
"cluster": False,
|
|
|
|
"order": False,
|
|
|
|
"limit": False,
|
|
|
|
"offset": False,
|
2025-02-13 15:57:23 +01:00
|
|
|
"locks": False,
|
2025-02-13 15:43:32 +01:00
|
|
|
"sample": False,
|
2025-02-13 15:57:23 +01:00
|
|
|
"settings": False,
|
|
|
|
"format": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 20:04:59 +01:00
|
|
|
# https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16
|
|
|
|
class WithTableHint(Expression):
|
|
|
|
arg_types = {"expressions": True}
|
|
|
|
|
|
|
|
|
|
|
|
# https://dev.mysql.com/doc/refman/8.0/en/index-hints.html
|
|
|
|
class IndexTableHint(Expression):
|
|
|
|
arg_types = {"this": True, "expressions": False, "target": False}
|
|
|
|
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
class Table(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
2025-02-13 14:48:46 +01:00
|
|
|
"alias": False,
|
2025-02-13 08:04:41 +01:00
|
|
|
"db": False,
|
|
|
|
"catalog": False,
|
|
|
|
"laterals": False,
|
|
|
|
"joins": False,
|
2025-02-13 14:40:43 +01:00
|
|
|
"pivots": False,
|
2025-02-13 15:03:38 +01:00
|
|
|
"hints": False,
|
2025-02-13 15:07:05 +01:00
|
|
|
"system_time": False,
|
2025-02-13 20:58:22 +01:00
|
|
|
"version": False,
|
2025-02-13 21:04:58 +01:00
|
|
|
"format": False,
|
|
|
|
"pattern": False,
|
2025-02-13 15:07:05 +01:00
|
|
|
}
|
|
|
|
|
2025-02-13 20:21:40 +01:00
|
|
|
@property
|
|
|
|
def name(self) -> str:
|
|
|
|
if isinstance(self.this, Func):
|
|
|
|
return ""
|
|
|
|
return self.this.name
|
|
|
|
|
2025-02-13 15:26:26 +01:00
|
|
|
@property
|
|
|
|
def db(self) -> str:
|
|
|
|
return self.text("db")
|
|
|
|
|
|
|
|
@property
|
|
|
|
def catalog(self) -> str:
|
|
|
|
return self.text("catalog")
|
|
|
|
|
2025-02-13 20:45:52 +01:00
|
|
|
@property
|
|
|
|
def selects(self) -> t.List[Expression]:
|
|
|
|
return []
|
|
|
|
|
|
|
|
@property
|
|
|
|
def named_selects(self) -> t.List[str]:
|
|
|
|
return []
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
@property
|
2025-02-13 21:04:58 +01:00
|
|
|
def parts(self) -> t.List[Expression]:
|
2025-02-13 15:57:23 +01:00
|
|
|
"""Return the parts of a table in order catalog, db, table."""
|
2025-02-13 21:04:58 +01:00
|
|
|
parts: t.List[Expression] = []
|
2025-02-13 20:45:52 +01:00
|
|
|
|
|
|
|
for arg in ("catalog", "db", "this"):
|
|
|
|
part = self.args.get(arg)
|
|
|
|
|
2025-02-13 21:04:58 +01:00
|
|
|
if isinstance(part, Dot):
|
2025-02-13 20:45:52 +01:00
|
|
|
parts.extend(part.flatten())
|
2025-02-13 21:04:58 +01:00
|
|
|
elif isinstance(part, Expression):
|
|
|
|
parts.append(part)
|
2025-02-13 20:45:52 +01:00
|
|
|
|
|
|
|
return parts
|
2025-02-13 15:57:23 +01:00
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
|
2025-02-13 14:46:58 +01:00
|
|
|
class Union(Subqueryable):
|
2025-02-13 06:15:54 +01:00
|
|
|
arg_types = {
|
|
|
|
"with": False,
|
|
|
|
"this": True,
|
|
|
|
"expression": True,
|
|
|
|
"distinct": False,
|
2025-02-13 20:58:22 +01:00
|
|
|
"by_name": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
**QUERY_MODIFIERS,
|
|
|
|
}
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def limit(
|
|
|
|
self, expression: ExpOrStr | int, dialect: DialectType = None, copy: bool = True, **opts
|
|
|
|
) -> Select:
|
2025-02-13 14:58:37 +01:00
|
|
|
"""
|
|
|
|
Set the LIMIT expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> select("1").union(select("1")).limit(1).sql()
|
2025-02-13 15:01:55 +01:00
|
|
|
'SELECT * FROM (SELECT 1 UNION SELECT 1) AS _l_0 LIMIT 1'
|
2025-02-13 14:58:37 +01:00
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
expression: the SQL code string to parse.
|
2025-02-13 14:58:37 +01:00
|
|
|
This can also be an integer.
|
|
|
|
If a `Limit` instance is passed, this is used as-is.
|
|
|
|
If another `Expression` instance is passed, it will be wrapped in a `Limit`.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 14:58:37 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The limited subqueryable.
|
2025-02-13 14:58:37 +01:00
|
|
|
"""
|
|
|
|
return (
|
|
|
|
select("*")
|
|
|
|
.from_(self.subquery(alias="_l_0", copy=copy))
|
|
|
|
.limit(expression, dialect=dialect, copy=False, **opts)
|
|
|
|
)
|
|
|
|
|
2025-02-13 15:26:26 +01:00
|
|
|
def select(
|
|
|
|
self,
|
2025-02-13 15:57:23 +01:00
|
|
|
*expressions: t.Optional[ExpOrStr],
|
2025-02-13 15:26:26 +01:00
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Union:
|
|
|
|
"""Append to or set the SELECT of the union recursively.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> from sqlglot import parse_one
|
|
|
|
>>> parse_one("select a from x union select a from y union select a from z").select("b").sql()
|
|
|
|
'SELECT a, b FROM x UNION SELECT a, b FROM y UNION SELECT a, b FROM z'
|
|
|
|
|
|
|
|
Args:
|
|
|
|
*expressions: the SQL code strings to parse.
|
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
|
|
|
append: if `True`, add to any existing expressions.
|
|
|
|
Otherwise, this resets the expressions.
|
|
|
|
dialect: the dialect used to parse the input expressions.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Union: the modified expression.
|
|
|
|
"""
|
|
|
|
this = self.copy() if copy else self
|
|
|
|
this.this.unnest().select(*expressions, append=append, dialect=dialect, copy=False, **opts)
|
|
|
|
this.expression.unnest().select(
|
|
|
|
*expressions, append=append, dialect=dialect, copy=False, **opts
|
|
|
|
)
|
|
|
|
return this
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
@property
|
2025-02-13 20:45:52 +01:00
|
|
|
def named_selects(self) -> t.List[str]:
|
2025-02-13 14:46:58 +01:00
|
|
|
return self.this.unnest().named_selects
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
@property
|
|
|
|
def is_star(self) -> bool:
|
|
|
|
return self.this.is_star or self.expression.is_star
|
|
|
|
|
2025-02-13 14:46:58 +01:00
|
|
|
@property
|
2025-02-13 20:45:52 +01:00
|
|
|
def selects(self) -> t.List[Expression]:
|
2025-02-13 14:46:58 +01:00
|
|
|
return self.this.unnest().selects
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def left(self):
|
|
|
|
return self.this
|
|
|
|
|
|
|
|
@property
|
|
|
|
def right(self):
|
|
|
|
return self.expression
|
|
|
|
|
|
|
|
|
|
|
|
class Except(Union):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Intersect(Union):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 14:43:32 +01:00
|
|
|
class Unnest(UDTF):
|
2025-02-13 06:15:54 +01:00
|
|
|
arg_types = {
|
|
|
|
"expressions": True,
|
|
|
|
"alias": False,
|
2025-02-13 15:05:06 +01:00
|
|
|
"offset": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class Update(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"with": False,
|
2025-02-13 14:58:37 +01:00
|
|
|
"this": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
"expressions": True,
|
|
|
|
"from": False,
|
|
|
|
"where": False,
|
2025-02-13 15:43:32 +01:00
|
|
|
"returning": False,
|
2025-02-13 20:58:22 +01:00
|
|
|
"order": False,
|
2025-02-13 20:04:59 +01:00
|
|
|
"limit": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 14:43:32 +01:00
|
|
|
class Values(UDTF):
|
|
|
|
arg_types = {
|
|
|
|
"expressions": True,
|
|
|
|
"ordinality": False,
|
|
|
|
"alias": False,
|
|
|
|
}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Var(Expression):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
class Version(Expression):
|
|
|
|
"""
|
|
|
|
Time travel, iceberg, bigquery etc
|
|
|
|
https://trino.io/docs/current/connector/iceberg.html?highlight=snapshot#using-snapshots
|
|
|
|
https://www.databricks.com/blog/2019/02/04/introducing-delta-time-travel-for-large-scale-data-lakes.html
|
|
|
|
https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#for_system_time_as_of
|
|
|
|
https://learn.microsoft.com/en-us/sql/relational-databases/tables/querying-data-in-a-system-versioned-temporal-table?view=sql-server-ver16
|
|
|
|
this is either TIMESTAMP or VERSION
|
|
|
|
kind is ("AS OF", "BETWEEN")
|
|
|
|
"""
|
|
|
|
|
|
|
|
arg_types = {"this": True, "kind": True, "expression": False}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Schema(Expression):
|
2025-02-13 14:54:32 +01:00
|
|
|
arg_types = {"this": False, "expressions": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
# https://dev.mysql.com/doc/refman/8.0/en/select.html
|
|
|
|
# https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/SELECT.html
|
2025-02-13 15:09:58 +01:00
|
|
|
class Lock(Expression):
|
2025-02-13 15:57:23 +01:00
|
|
|
arg_types = {"update": True, "expressions": False, "wait": False}
|
2025-02-13 15:09:58 +01:00
|
|
|
|
|
|
|
|
2025-02-13 14:46:58 +01:00
|
|
|
class Select(Subqueryable):
|
2025-02-13 06:15:54 +01:00
|
|
|
arg_types = {
|
|
|
|
"with": False,
|
2025-02-13 15:48:10 +01:00
|
|
|
"kind": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
"expressions": False,
|
|
|
|
"hint": False,
|
|
|
|
"distinct": False,
|
2025-02-13 14:56:25 +01:00
|
|
|
"into": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
"from": False,
|
|
|
|
**QUERY_MODIFIERS,
|
|
|
|
}
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def from_(
|
|
|
|
self, expression: ExpOrStr, dialect: DialectType = None, copy: bool = True, **opts
|
|
|
|
) -> Select:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Set the FROM expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> Select().from_("tbl").select("x").sql()
|
|
|
|
'SELECT x FROM tbl'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
expression : the SQL code strings to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If a `From` instance is passed, this is used as-is.
|
|
|
|
If another `Expression` instance is passed, it will be wrapped in a `From`.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The modified Select expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
2025-02-13 15:57:23 +01:00
|
|
|
return _apply_builder(
|
|
|
|
expression=expression,
|
2025-02-13 06:15:54 +01:00
|
|
|
instance=self,
|
|
|
|
arg="from",
|
|
|
|
into=From,
|
2025-02-13 15:57:23 +01:00
|
|
|
prefix="FROM",
|
2025-02-13 06:15:54 +01:00
|
|
|
dialect=dialect,
|
2025-02-13 15:57:23 +01:00
|
|
|
copy=copy,
|
2025-02-13 06:15:54 +01:00
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def group_by(
|
|
|
|
self,
|
|
|
|
*expressions: t.Optional[ExpOrStr],
|
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Select:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Set the GROUP BY expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> Select().from_("tbl").select("x", "COUNT(1)").group_by("x").sql()
|
|
|
|
'SELECT x, COUNT(1) FROM tbl GROUP BY x'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
*expressions: the SQL code strings to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If a `Group` instance is passed, this is used as-is.
|
|
|
|
If another `Expression` instance is passed, it will be wrapped in a `Group`.
|
2025-02-13 14:48:46 +01:00
|
|
|
If nothing is passed in then a group by is not applied to the expression
|
2025-02-13 15:57:23 +01:00
|
|
|
append: if `True`, add to any existing expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
Otherwise, this flattens all the `Group` expression into a single expression.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The modified Select expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
2025-02-13 14:48:46 +01:00
|
|
|
if not expressions:
|
|
|
|
return self if not copy else self.copy()
|
2025-02-13 15:57:23 +01:00
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
return _apply_child_list_builder(
|
|
|
|
*expressions,
|
|
|
|
instance=self,
|
|
|
|
arg="group",
|
|
|
|
append=append,
|
|
|
|
copy=copy,
|
|
|
|
prefix="GROUP BY",
|
|
|
|
into=Group,
|
|
|
|
dialect=dialect,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def order_by(
|
|
|
|
self,
|
|
|
|
*expressions: t.Optional[ExpOrStr],
|
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Select:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Set the ORDER BY expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> Select().from_("tbl").select("x").order_by("x DESC").sql()
|
|
|
|
'SELECT x FROM tbl ORDER BY x DESC'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
*expressions: the SQL code strings to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If a `Group` instance is passed, this is used as-is.
|
|
|
|
If another `Expression` instance is passed, it will be wrapped in a `Order`.
|
2025-02-13 15:57:23 +01:00
|
|
|
append: if `True`, add to any existing expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
Otherwise, this flattens all the `Order` expression into a single expression.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The modified Select expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
return _apply_child_list_builder(
|
|
|
|
*expressions,
|
|
|
|
instance=self,
|
|
|
|
arg="order",
|
|
|
|
append=append,
|
|
|
|
copy=copy,
|
|
|
|
prefix="ORDER BY",
|
|
|
|
into=Order,
|
|
|
|
dialect=dialect,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def sort_by(
|
|
|
|
self,
|
|
|
|
*expressions: t.Optional[ExpOrStr],
|
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Select:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Set the SORT BY expression.
|
|
|
|
|
|
|
|
Example:
|
2025-02-13 15:57:23 +01:00
|
|
|
>>> Select().from_("tbl").select("x").sort_by("x DESC").sql(dialect="hive")
|
2025-02-13 06:15:54 +01:00
|
|
|
'SELECT x FROM tbl SORT BY x DESC'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
*expressions: the SQL code strings to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If a `Group` instance is passed, this is used as-is.
|
|
|
|
If another `Expression` instance is passed, it will be wrapped in a `SORT`.
|
2025-02-13 15:57:23 +01:00
|
|
|
append: if `True`, add to any existing expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
Otherwise, this flattens all the `Order` expression into a single expression.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The modified Select expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
return _apply_child_list_builder(
|
|
|
|
*expressions,
|
|
|
|
instance=self,
|
|
|
|
arg="sort",
|
|
|
|
append=append,
|
|
|
|
copy=copy,
|
|
|
|
prefix="SORT BY",
|
|
|
|
into=Sort,
|
|
|
|
dialect=dialect,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def cluster_by(
|
|
|
|
self,
|
|
|
|
*expressions: t.Optional[ExpOrStr],
|
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Select:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Set the CLUSTER BY expression.
|
|
|
|
|
|
|
|
Example:
|
2025-02-13 15:57:23 +01:00
|
|
|
>>> Select().from_("tbl").select("x").cluster_by("x DESC").sql(dialect="hive")
|
2025-02-13 06:15:54 +01:00
|
|
|
'SELECT x FROM tbl CLUSTER BY x DESC'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
*expressions: the SQL code strings to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If a `Group` instance is passed, this is used as-is.
|
|
|
|
If another `Expression` instance is passed, it will be wrapped in a `Cluster`.
|
2025-02-13 15:57:23 +01:00
|
|
|
append: if `True`, add to any existing expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
Otherwise, this flattens all the `Order` expression into a single expression.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The modified Select expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
return _apply_child_list_builder(
|
|
|
|
*expressions,
|
|
|
|
instance=self,
|
|
|
|
arg="cluster",
|
|
|
|
append=append,
|
|
|
|
copy=copy,
|
|
|
|
prefix="CLUSTER BY",
|
|
|
|
into=Cluster,
|
|
|
|
dialect=dialect,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def limit(
|
|
|
|
self, expression: ExpOrStr | int, dialect: DialectType = None, copy: bool = True, **opts
|
|
|
|
) -> Select:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Set the LIMIT expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> Select().from_("tbl").select("x").limit(10).sql()
|
|
|
|
'SELECT x FROM tbl LIMIT 10'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
expression: the SQL code string to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
This can also be an integer.
|
|
|
|
If a `Limit` instance is passed, this is used as-is.
|
|
|
|
If another `Expression` instance is passed, it will be wrapped in a `Limit`.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
Select: the modified expression.
|
|
|
|
"""
|
|
|
|
return _apply_builder(
|
|
|
|
expression=expression,
|
|
|
|
instance=self,
|
|
|
|
arg="limit",
|
|
|
|
into=Limit,
|
|
|
|
prefix="LIMIT",
|
|
|
|
dialect=dialect,
|
|
|
|
copy=copy,
|
2025-02-13 21:03:38 +01:00
|
|
|
into_arg="expression",
|
2025-02-13 06:15:54 +01:00
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def offset(
|
|
|
|
self, expression: ExpOrStr | int, dialect: DialectType = None, copy: bool = True, **opts
|
|
|
|
) -> Select:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Set the OFFSET expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> Select().from_("tbl").select("x").offset(10).sql()
|
|
|
|
'SELECT x FROM tbl OFFSET 10'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
expression: the SQL code string to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
This can also be an integer.
|
|
|
|
If a `Offset` instance is passed, this is used as-is.
|
|
|
|
If another `Expression` instance is passed, it will be wrapped in a `Offset`.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The modified Select expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
return _apply_builder(
|
|
|
|
expression=expression,
|
|
|
|
instance=self,
|
|
|
|
arg="offset",
|
|
|
|
into=Offset,
|
|
|
|
prefix="OFFSET",
|
|
|
|
dialect=dialect,
|
|
|
|
copy=copy,
|
2025-02-13 21:04:58 +01:00
|
|
|
into_arg="expression",
|
2025-02-13 06:15:54 +01:00
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
2025-02-13 15:26:26 +01:00
|
|
|
def select(
|
|
|
|
self,
|
2025-02-13 15:57:23 +01:00
|
|
|
*expressions: t.Optional[ExpOrStr],
|
2025-02-13 15:26:26 +01:00
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Select:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Append to or set the SELECT expressions.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> Select().select("x", "y").sql()
|
|
|
|
'SELECT x, y'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:26:26 +01:00
|
|
|
*expressions: the SQL code strings to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:26:26 +01:00
|
|
|
append: if `True`, add to any existing expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
Otherwise, this resets the expressions.
|
2025-02-13 15:26:26 +01:00
|
|
|
dialect: the dialect used to parse the input expressions.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The modified Select expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
return _apply_list_builder(
|
|
|
|
*expressions,
|
|
|
|
instance=self,
|
|
|
|
arg="expressions",
|
|
|
|
append=append,
|
|
|
|
dialect=dialect,
|
|
|
|
copy=copy,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def lateral(
|
|
|
|
self,
|
|
|
|
*expressions: t.Optional[ExpOrStr],
|
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Select:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Append to or set the LATERAL expressions.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> Select().select("x").lateral("OUTER explode(y) tbl2 AS z").from_("tbl").sql()
|
|
|
|
'SELECT x FROM tbl LATERAL VIEW OUTER EXPLODE(y) tbl2 AS z'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
*expressions: the SQL code strings to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
append: if `True`, add to any existing expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
Otherwise, this resets the expressions.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expressions.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The modified Select expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
return _apply_list_builder(
|
|
|
|
*expressions,
|
|
|
|
instance=self,
|
|
|
|
arg="laterals",
|
|
|
|
append=append,
|
|
|
|
into=Lateral,
|
|
|
|
prefix="LATERAL VIEW",
|
|
|
|
dialect=dialect,
|
|
|
|
copy=copy,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
|
|
|
def join(
|
|
|
|
self,
|
2025-02-13 15:57:23 +01:00
|
|
|
expression: ExpOrStr,
|
|
|
|
on: t.Optional[ExpOrStr] = None,
|
2025-02-13 20:48:36 +01:00
|
|
|
using: t.Optional[ExpOrStr | t.Collection[ExpOrStr]] = None,
|
2025-02-13 15:57:23 +01:00
|
|
|
append: bool = True,
|
|
|
|
join_type: t.Optional[str] = None,
|
|
|
|
join_alias: t.Optional[Identifier | str] = None,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
2025-02-13 06:15:54 +01:00
|
|
|
**opts,
|
2025-02-13 14:53:05 +01:00
|
|
|
) -> Select:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Append to or set the JOIN expressions.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> Select().select("*").from_("tbl").join("tbl2", on="tbl1.y = tbl2.y").sql()
|
|
|
|
'SELECT * FROM tbl JOIN tbl2 ON tbl1.y = tbl2.y'
|
|
|
|
|
2025-02-13 14:51:47 +01:00
|
|
|
>>> Select().select("1").from_("a").join("b", using=["x", "y", "z"]).sql()
|
|
|
|
'SELECT 1 FROM a JOIN b USING (x, y, z)'
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
Use `join_type` to change the type of join:
|
|
|
|
|
|
|
|
>>> Select().select("*").from_("tbl").join("tbl2", on="tbl1.y = tbl2.y", join_type="left outer").sql()
|
|
|
|
'SELECT * FROM tbl LEFT OUTER JOIN tbl2 ON tbl1.y = tbl2.y'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
expression: the SQL code string to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
on: optionally specify the join "on" criteria as a SQL string.
|
2025-02-13 14:51:47 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
using: optionally specify the join "using" criteria as a SQL string.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
append: if `True`, add to any existing expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
Otherwise, this resets the expressions.
|
2025-02-13 15:57:23 +01:00
|
|
|
join_type: if set, alter the parsed join type.
|
|
|
|
join_alias: an optional alias for the joined source.
|
|
|
|
dialect: the dialect used to parse the input expressions.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
Select: the modified expression.
|
|
|
|
"""
|
2025-02-13 15:58:40 +01:00
|
|
|
parse_args: t.Dict[str, t.Any] = {"dialect": dialect, **opts}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
try:
|
2025-02-13 15:58:40 +01:00
|
|
|
expression = maybe_parse(expression, into=Join, prefix="JOIN", **parse_args)
|
2025-02-13 06:15:54 +01:00
|
|
|
except ParseError:
|
2025-02-13 15:58:40 +01:00
|
|
|
expression = maybe_parse(expression, into=(Join, Expression), **parse_args)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
join = expression if isinstance(expression, Join) else Join(this=expression)
|
|
|
|
|
|
|
|
if isinstance(join.this, Select):
|
|
|
|
join.this.replace(join.this.subquery())
|
|
|
|
|
|
|
|
if join_type:
|
2025-02-13 15:58:40 +01:00
|
|
|
method: t.Optional[Token]
|
2025-02-13 15:03:38 +01:00
|
|
|
side: t.Optional[Token]
|
|
|
|
kind: t.Optional[Token]
|
|
|
|
|
2025-02-13 15:58:40 +01:00
|
|
|
method, side, kind = maybe_parse(join_type, into="JOIN_TYPE", **parse_args) # type: ignore
|
2025-02-13 15:03:38 +01:00
|
|
|
|
2025-02-13 15:58:40 +01:00
|
|
|
if method:
|
|
|
|
join.set("method", method.text)
|
2025-02-13 06:15:54 +01:00
|
|
|
if side:
|
|
|
|
join.set("side", side.text)
|
|
|
|
if kind:
|
|
|
|
join.set("kind", kind.text)
|
|
|
|
|
|
|
|
if on:
|
2025-02-13 15:57:23 +01:00
|
|
|
on = and_(*ensure_list(on), dialect=dialect, copy=copy, **opts)
|
2025-02-13 06:15:54 +01:00
|
|
|
join.set("on", on)
|
|
|
|
|
2025-02-13 14:51:47 +01:00
|
|
|
if using:
|
|
|
|
join = _apply_list_builder(
|
2025-02-13 15:57:23 +01:00
|
|
|
*ensure_list(using),
|
2025-02-13 14:51:47 +01:00
|
|
|
instance=join,
|
|
|
|
arg="using",
|
|
|
|
append=append,
|
|
|
|
copy=copy,
|
2025-02-13 20:48:36 +01:00
|
|
|
into=Identifier,
|
2025-02-13 14:51:47 +01:00
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
if join_alias:
|
2025-02-13 14:54:32 +01:00
|
|
|
join.set("this", alias_(join.this, join_alias, table=True))
|
2025-02-13 15:57:23 +01:00
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
return _apply_list_builder(
|
|
|
|
join,
|
|
|
|
instance=self,
|
|
|
|
arg="joins",
|
|
|
|
append=append,
|
|
|
|
copy=copy,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def where(
|
|
|
|
self,
|
|
|
|
*expressions: t.Optional[ExpOrStr],
|
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Select:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Append to or set the WHERE expressions.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> Select().select("x").from_("tbl").where("x = 'a' OR x < 'b'").sql()
|
|
|
|
"SELECT x FROM tbl WHERE x = 'a' OR x < 'b'"
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
*expressions: the SQL code strings to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
|
|
|
Multiple expressions are combined with an AND operator.
|
2025-02-13 15:57:23 +01:00
|
|
|
append: if `True`, AND the new expressions to any existing expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
Otherwise, this resets the expression.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expressions.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
Select: the modified expression.
|
|
|
|
"""
|
|
|
|
return _apply_conjunction_builder(
|
|
|
|
*expressions,
|
|
|
|
instance=self,
|
|
|
|
arg="where",
|
|
|
|
append=append,
|
|
|
|
into=Where,
|
|
|
|
dialect=dialect,
|
|
|
|
copy=copy,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def having(
|
|
|
|
self,
|
|
|
|
*expressions: t.Optional[ExpOrStr],
|
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Select:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Append to or set the HAVING expressions.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> Select().select("x", "COUNT(y)").from_("tbl").group_by("x").having("COUNT(y) > 3").sql()
|
|
|
|
'SELECT x, COUNT(y) FROM tbl GROUP BY x HAVING COUNT(y) > 3'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
*expressions: the SQL code strings to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
|
|
|
Multiple expressions are combined with an AND operator.
|
2025-02-13 15:57:23 +01:00
|
|
|
append: if `True`, AND the new expressions to any existing expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
Otherwise, this resets the expression.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expressions.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input expressions.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The modified Select expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
return _apply_conjunction_builder(
|
|
|
|
*expressions,
|
|
|
|
instance=self,
|
|
|
|
arg="having",
|
|
|
|
append=append,
|
|
|
|
into=Having,
|
|
|
|
dialect=dialect,
|
|
|
|
copy=copy,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def window(
|
|
|
|
self,
|
|
|
|
*expressions: t.Optional[ExpOrStr],
|
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Select:
|
2025-02-13 15:01:55 +01:00
|
|
|
return _apply_list_builder(
|
|
|
|
*expressions,
|
|
|
|
instance=self,
|
|
|
|
arg="windows",
|
|
|
|
append=append,
|
|
|
|
into=Window,
|
|
|
|
dialect=dialect,
|
|
|
|
copy=copy,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def qualify(
|
|
|
|
self,
|
|
|
|
*expressions: t.Optional[ExpOrStr],
|
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Select:
|
2025-02-13 15:43:32 +01:00
|
|
|
return _apply_conjunction_builder(
|
|
|
|
*expressions,
|
|
|
|
instance=self,
|
|
|
|
arg="qualify",
|
|
|
|
append=append,
|
|
|
|
into=Qualify,
|
|
|
|
dialect=dialect,
|
|
|
|
copy=copy,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def distinct(
|
|
|
|
self, *ons: t.Optional[ExpOrStr], distinct: bool = True, copy: bool = True
|
|
|
|
) -> Select:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Set the OFFSET expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> Select().from_("tbl").select("x").distinct().sql()
|
|
|
|
'SELECT DISTINCT x FROM tbl'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:53:39 +01:00
|
|
|
ons: the expressions to distinct on
|
|
|
|
distinct: whether the Select should be distinct
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
Select: the modified expression.
|
|
|
|
"""
|
2025-02-13 20:48:36 +01:00
|
|
|
instance = maybe_copy(self, copy)
|
2025-02-13 15:57:23 +01:00
|
|
|
on = Tuple(expressions=[maybe_parse(on, copy=copy) for on in ons if on]) if ons else None
|
2025-02-13 15:53:39 +01:00
|
|
|
instance.set("distinct", Distinct(on=on) if distinct else None)
|
2025-02-13 06:15:54 +01:00
|
|
|
return instance
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def ctas(
|
|
|
|
self,
|
|
|
|
table: ExpOrStr,
|
|
|
|
properties: t.Optional[t.Dict] = None,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Create:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Convert this expression to a CREATE TABLE AS statement.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> Select().select("*").from_("tbl").ctas("x").sql()
|
|
|
|
'CREATE TABLE x AS SELECT * FROM tbl'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
table: the SQL code string to parse as the table name.
|
2025-02-13 06:15:54 +01:00
|
|
|
If another `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
properties: an optional mapping of table properties
|
|
|
|
dialect: the dialect used to parse the input table.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
opts: other options to use to parse the input table.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The new Create expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
2025-02-13 20:48:36 +01:00
|
|
|
instance = maybe_copy(self, copy)
|
2025-02-13 06:15:54 +01:00
|
|
|
table_expression = maybe_parse(
|
|
|
|
table,
|
|
|
|
into=Table,
|
|
|
|
dialect=dialect,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
properties_expression = None
|
|
|
|
if properties:
|
2025-02-13 14:31:47 +01:00
|
|
|
properties_expression = Properties.from_dict(properties)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
return Create(
|
|
|
|
this=table_expression,
|
|
|
|
kind="table",
|
|
|
|
expression=instance,
|
|
|
|
properties=properties_expression,
|
|
|
|
)
|
|
|
|
|
2025-02-13 15:09:58 +01:00
|
|
|
def lock(self, update: bool = True, copy: bool = True) -> Select:
|
|
|
|
"""
|
|
|
|
Set the locking read mode for this expression.
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
>>> Select().select("x").from_("tbl").where("x = 'a'").lock().sql("mysql")
|
|
|
|
"SELECT x FROM tbl WHERE x = 'a' FOR UPDATE"
|
|
|
|
|
|
|
|
>>> Select().select("x").from_("tbl").where("x = 'a'").lock(update=False).sql("mysql")
|
|
|
|
"SELECT x FROM tbl WHERE x = 'a' FOR SHARE"
|
|
|
|
|
|
|
|
Args:
|
|
|
|
update: if `True`, the locking type will be `FOR UPDATE`, else it will be `FOR SHARE`.
|
|
|
|
copy: if `False`, modify this expression instance in-place.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The modified expression.
|
|
|
|
"""
|
2025-02-13 20:48:36 +01:00
|
|
|
inst = maybe_copy(self, copy)
|
2025-02-13 15:57:23 +01:00
|
|
|
inst.set("locks", [Lock(update=update)])
|
2025-02-13 15:09:58 +01:00
|
|
|
|
|
|
|
return inst
|
|
|
|
|
2025-02-13 16:00:51 +01:00
|
|
|
def hint(self, *hints: ExpOrStr, dialect: DialectType = None, copy: bool = True) -> Select:
|
|
|
|
"""
|
|
|
|
Set hints for this expression.
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
>>> Select().select("x").from_("tbl").hint("BROADCAST(y)").sql(dialect="spark")
|
|
|
|
'SELECT /*+ BROADCAST(y) */ x FROM tbl'
|
|
|
|
|
|
|
|
Args:
|
|
|
|
hints: The SQL code strings to parse as the hints.
|
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
|
|
|
dialect: The dialect used to parse the hints.
|
|
|
|
copy: If `False`, modify this expression instance in-place.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The modified expression.
|
|
|
|
"""
|
2025-02-13 20:48:36 +01:00
|
|
|
inst = maybe_copy(self, copy)
|
2025-02-13 16:00:51 +01:00
|
|
|
inst.set(
|
|
|
|
"hint", Hint(expressions=[maybe_parse(h, copy=copy, dialect=dialect) for h in hints])
|
|
|
|
)
|
|
|
|
|
|
|
|
return inst
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
@property
|
2025-02-13 14:53:05 +01:00
|
|
|
def named_selects(self) -> t.List[str]:
|
2025-02-13 15:07:05 +01:00
|
|
|
return [e.output_name for e in self.expressions if e.alias_or_name]
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
@property
|
|
|
|
def is_star(self) -> bool:
|
|
|
|
return any(expression.is_star for expression in self.expressions)
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
@property
|
2025-02-13 14:53:05 +01:00
|
|
|
def selects(self) -> t.List[Expression]:
|
2025-02-13 06:15:54 +01:00
|
|
|
return self.expressions
|
|
|
|
|
|
|
|
|
2025-02-13 14:45:11 +01:00
|
|
|
class Subquery(DerivedTable, Unionable):
|
2025-02-13 06:15:54 +01:00
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"alias": False,
|
2025-02-13 14:54:32 +01:00
|
|
|
"with": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
**QUERY_MODIFIERS,
|
|
|
|
}
|
|
|
|
|
|
|
|
def unnest(self):
|
|
|
|
"""
|
|
|
|
Returns the first non subquery.
|
|
|
|
"""
|
|
|
|
expression = self
|
|
|
|
while isinstance(expression, Subquery):
|
|
|
|
expression = expression.this
|
|
|
|
return expression
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
def unwrap(self) -> Subquery:
|
|
|
|
expression = self
|
|
|
|
while expression.same_parent and expression.is_wrapper:
|
|
|
|
expression = t.cast(Subquery, expression.parent)
|
|
|
|
return expression
|
|
|
|
|
|
|
|
@property
|
|
|
|
def is_wrapper(self) -> bool:
|
|
|
|
"""
|
|
|
|
Whether this Subquery acts as a simple wrapper around another expression.
|
|
|
|
|
|
|
|
SELECT * FROM (((SELECT * FROM t)))
|
|
|
|
^
|
|
|
|
This corresponds to a "wrapper" Subquery node
|
|
|
|
"""
|
|
|
|
return all(v is None for k, v in self.args.items() if k != "this")
|
|
|
|
|
2025-02-13 15:40:23 +01:00
|
|
|
@property
|
|
|
|
def is_star(self) -> bool:
|
|
|
|
return self.this.is_star
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def output_name(self) -> str:
|
2025-02-13 15:07:05 +01:00
|
|
|
return self.alias
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
class TableSample(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"this": False,
|
2025-02-13 20:59:47 +01:00
|
|
|
"expressions": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
"method": False,
|
|
|
|
"bucket_numerator": False,
|
|
|
|
"bucket_denominator": False,
|
|
|
|
"bucket_field": False,
|
|
|
|
"percent": False,
|
|
|
|
"rows": False,
|
|
|
|
"size": False,
|
2025-02-13 14:40:43 +01:00
|
|
|
"seed": False,
|
2025-02-13 15:43:32 +01:00
|
|
|
"kind": False,
|
2025-02-13 14:40:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
class Tag(Expression):
|
|
|
|
"""Tags are used for generating arbitrary sql like SELECT <span>x</span>."""
|
|
|
|
|
|
|
|
arg_types = {
|
|
|
|
"this": False,
|
|
|
|
"prefix": False,
|
|
|
|
"postfix": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
# Represents both the standard SQL PIVOT operator and DuckDB's "simplified" PIVOT syntax
|
|
|
|
# https://duckdb.org/docs/sql/statements/pivot
|
2025-02-13 14:40:43 +01:00
|
|
|
class Pivot(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"this": False,
|
2025-02-13 15:40:23 +01:00
|
|
|
"alias": False,
|
2025-02-13 14:40:43 +01:00
|
|
|
"expressions": True,
|
2025-02-13 15:57:23 +01:00
|
|
|
"field": False,
|
|
|
|
"unpivot": False,
|
|
|
|
"using": False,
|
|
|
|
"group": False,
|
2025-02-13 15:52:09 +01:00
|
|
|
"columns": False,
|
2025-02-13 20:55:29 +01:00
|
|
|
"include_nulls": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
class Window(Condition):
|
2025-02-13 06:15:54 +01:00
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"partition_by": False,
|
|
|
|
"order": False,
|
|
|
|
"spec": False,
|
|
|
|
"alias": False,
|
2025-02-13 15:52:09 +01:00
|
|
|
"over": False,
|
|
|
|
"first": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class WindowSpec(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"kind": False,
|
|
|
|
"start": False,
|
|
|
|
"start_side": False,
|
|
|
|
"end": False,
|
|
|
|
"end_side": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class Where(Expression):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Star(Expression):
|
|
|
|
arg_types = {"except": False, "replace": False}
|
|
|
|
|
|
|
|
@property
|
2025-02-13 15:23:26 +01:00
|
|
|
def name(self) -> str:
|
2025-02-13 06:15:54 +01:00
|
|
|
return "*"
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def output_name(self) -> str:
|
2025-02-13 15:07:05 +01:00
|
|
|
return self.name
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 20:04:59 +01:00
|
|
|
class Parameter(Condition):
|
2025-02-13 15:26:26 +01:00
|
|
|
arg_types = {"this": True, "wrapped": False}
|
2025-02-13 14:31:47 +01:00
|
|
|
|
|
|
|
|
2025-02-13 20:04:59 +01:00
|
|
|
class SessionParameter(Condition):
|
2025-02-13 14:53:05 +01:00
|
|
|
arg_types = {"this": True, "kind": False}
|
|
|
|
|
|
|
|
|
2025-02-13 20:04:59 +01:00
|
|
|
class Placeholder(Condition):
|
2025-02-13 15:57:23 +01:00
|
|
|
arg_types = {"this": False, "kind": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Null(Condition):
|
2025-02-13 14:53:05 +01:00
|
|
|
arg_types: t.Dict[str, t.Any] = {}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:23:26 +01:00
|
|
|
@property
|
|
|
|
def name(self) -> str:
|
|
|
|
return "NULL"
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
class Boolean(Condition):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
class DataTypeParam(Expression):
|
2025-02-13 15:57:23 +01:00
|
|
|
arg_types = {"this": True, "expression": False}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class DataType(Expression):
|
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"expressions": False,
|
|
|
|
"nested": False,
|
2025-02-13 15:05:06 +01:00
|
|
|
"values": False,
|
2025-02-13 15:26:26 +01:00
|
|
|
"prefix": False,
|
2025-02-13 20:58:22 +01:00
|
|
|
"kind": False,
|
2025-02-13 06:15:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
class Type(AutoName):
|
2025-02-13 15:57:23 +01:00
|
|
|
ARRAY = auto()
|
2025-02-13 15:52:09 +01:00
|
|
|
BIGDECIMAL = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
BIGINT = auto()
|
|
|
|
BIGSERIAL = auto()
|
|
|
|
BINARY = auto()
|
2025-02-13 15:44:58 +01:00
|
|
|
BIT = auto()
|
2025-02-13 06:15:54 +01:00
|
|
|
BOOLEAN = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
CHAR = auto()
|
2025-02-13 06:15:54 +01:00
|
|
|
DATE = auto()
|
2025-02-13 20:55:29 +01:00
|
|
|
DATEMULTIRANGE = auto()
|
|
|
|
DATERANGE = auto()
|
2025-02-13 06:15:54 +01:00
|
|
|
DATETIME = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
DATETIME64 = auto()
|
|
|
|
DECIMAL = auto()
|
|
|
|
DOUBLE = auto()
|
2025-02-13 20:55:29 +01:00
|
|
|
ENUM = auto()
|
|
|
|
ENUM8 = auto()
|
|
|
|
ENUM16 = auto()
|
|
|
|
FIXEDSTRING = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
FLOAT = auto()
|
2025-02-13 06:15:54 +01:00
|
|
|
GEOGRAPHY = auto()
|
2025-02-13 08:04:41 +01:00
|
|
|
GEOMETRY = auto()
|
|
|
|
HLLSKETCH = auto()
|
2025-02-13 14:56:25 +01:00
|
|
|
HSTORE = auto()
|
2025-02-13 14:31:47 +01:00
|
|
|
IMAGE = auto()
|
2025-02-13 15:40:23 +01:00
|
|
|
INET = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
INT = auto()
|
|
|
|
INT128 = auto()
|
|
|
|
INT256 = auto()
|
2025-02-13 20:55:29 +01:00
|
|
|
INT4MULTIRANGE = auto()
|
|
|
|
INT4RANGE = auto()
|
|
|
|
INT8MULTIRANGE = auto()
|
|
|
|
INT8RANGE = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
INTERVAL = auto()
|
2025-02-13 20:55:29 +01:00
|
|
|
IPADDRESS = auto()
|
|
|
|
IPPREFIX = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
JSON = auto()
|
|
|
|
JSONB = auto()
|
|
|
|
LONGBLOB = auto()
|
|
|
|
LONGTEXT = auto()
|
2025-02-13 20:55:29 +01:00
|
|
|
LOWCARDINALITY = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
MAP = auto()
|
|
|
|
MEDIUMBLOB = auto()
|
2025-02-13 20:58:22 +01:00
|
|
|
MEDIUMINT = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
MEDIUMTEXT = auto()
|
|
|
|
MONEY = auto()
|
|
|
|
NCHAR = auto()
|
2025-02-13 20:55:29 +01:00
|
|
|
NESTED = auto()
|
2025-02-13 14:45:11 +01:00
|
|
|
NULL = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
NULLABLE = auto()
|
2025-02-13 20:55:29 +01:00
|
|
|
NUMMULTIRANGE = auto()
|
|
|
|
NUMRANGE = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
NVARCHAR = auto()
|
|
|
|
OBJECT = auto()
|
|
|
|
ROWVERSION = auto()
|
|
|
|
SERIAL = auto()
|
2025-02-13 16:00:51 +01:00
|
|
|
SET = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
SMALLINT = auto()
|
|
|
|
SMALLMONEY = auto()
|
|
|
|
SMALLSERIAL = auto()
|
|
|
|
STRUCT = auto()
|
|
|
|
SUPER = auto()
|
|
|
|
TEXT = auto()
|
2025-02-13 21:01:12 +01:00
|
|
|
TINYBLOB = auto()
|
|
|
|
TINYTEXT = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
TIME = auto()
|
2025-02-13 20:55:29 +01:00
|
|
|
TIMETZ = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
TIMESTAMP = auto()
|
|
|
|
TIMESTAMPLTZ = auto()
|
2025-02-13 20:55:29 +01:00
|
|
|
TIMESTAMPTZ = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
TINYINT = auto()
|
2025-02-13 20:55:29 +01:00
|
|
|
TSMULTIRANGE = auto()
|
|
|
|
TSRANGE = auto()
|
|
|
|
TSTZMULTIRANGE = auto()
|
|
|
|
TSTZRANGE = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
UBIGINT = auto()
|
|
|
|
UINT = auto()
|
|
|
|
UINT128 = auto()
|
|
|
|
UINT256 = auto()
|
2025-02-13 20:59:47 +01:00
|
|
|
UMEDIUMINT = auto()
|
2025-02-13 21:04:58 +01:00
|
|
|
UDECIMAL = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
UNIQUEIDENTIFIER = auto()
|
2025-02-13 20:55:29 +01:00
|
|
|
UNKNOWN = auto() # Sentinel value, useful for type annotation
|
2025-02-13 20:04:59 +01:00
|
|
|
USERDEFINED = "USER-DEFINED"
|
2025-02-13 20:55:29 +01:00
|
|
|
USMALLINT = auto()
|
|
|
|
UTINYINT = auto()
|
2025-02-13 15:57:23 +01:00
|
|
|
UUID = auto()
|
|
|
|
VARBINARY = auto()
|
|
|
|
VARCHAR = auto()
|
|
|
|
VARIANT = auto()
|
|
|
|
XML = auto()
|
2025-02-13 20:58:22 +01:00
|
|
|
YEAR = auto()
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 14:54:32 +01:00
|
|
|
TEXT_TYPES = {
|
|
|
|
Type.CHAR,
|
|
|
|
Type.NCHAR,
|
|
|
|
Type.VARCHAR,
|
|
|
|
Type.NVARCHAR,
|
|
|
|
Type.TEXT,
|
|
|
|
}
|
|
|
|
|
2025-02-13 14:58:37 +01:00
|
|
|
INTEGER_TYPES = {
|
2025-02-13 14:54:32 +01:00
|
|
|
Type.INT,
|
|
|
|
Type.TINYINT,
|
|
|
|
Type.SMALLINT,
|
|
|
|
Type.BIGINT,
|
2025-02-13 15:53:39 +01:00
|
|
|
Type.INT128,
|
|
|
|
Type.INT256,
|
2025-02-13 14:58:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
FLOAT_TYPES = {
|
2025-02-13 14:54:32 +01:00
|
|
|
Type.FLOAT,
|
|
|
|
Type.DOUBLE,
|
|
|
|
}
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
NUMERIC_TYPES = {
|
|
|
|
*INTEGER_TYPES,
|
|
|
|
*FLOAT_TYPES,
|
|
|
|
}
|
2025-02-13 14:58:37 +01:00
|
|
|
|
2025-02-13 14:54:32 +01:00
|
|
|
TEMPORAL_TYPES = {
|
2025-02-13 16:00:51 +01:00
|
|
|
Type.TIME,
|
2025-02-13 20:55:29 +01:00
|
|
|
Type.TIMETZ,
|
2025-02-13 14:54:32 +01:00
|
|
|
Type.TIMESTAMP,
|
|
|
|
Type.TIMESTAMPTZ,
|
|
|
|
Type.TIMESTAMPLTZ,
|
|
|
|
Type.DATE,
|
|
|
|
Type.DATETIME,
|
2025-02-13 15:57:23 +01:00
|
|
|
Type.DATETIME64,
|
2025-02-13 14:54:32 +01:00
|
|
|
}
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
@classmethod
|
2025-02-13 15:03:38 +01:00
|
|
|
def build(
|
2025-02-13 20:58:22 +01:00
|
|
|
cls,
|
|
|
|
dtype: str | DataType | DataType.Type,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
udt: bool = False,
|
|
|
|
**kwargs,
|
2025-02-13 15:03:38 +01:00
|
|
|
) -> DataType:
|
2025-02-13 20:58:22 +01:00
|
|
|
"""
|
|
|
|
Constructs a DataType object.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
dtype: the data type of interest.
|
|
|
|
dialect: the dialect to use for parsing `dtype`, in case it's a string.
|
|
|
|
udt: when set to True, `dtype` will be used as-is if it can't be parsed into a
|
|
|
|
DataType, thus creating a user-defined type.
|
|
|
|
kawrgs: additional arguments to pass in the constructor of DataType.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The constructed DataType object.
|
|
|
|
"""
|
2025-02-13 15:03:38 +01:00
|
|
|
from sqlglot import parse_one
|
|
|
|
|
|
|
|
if isinstance(dtype, str):
|
2025-02-13 20:58:22 +01:00
|
|
|
if dtype.upper() == "UNKNOWN":
|
|
|
|
return DataType(this=DataType.Type.UNKNOWN, **kwargs)
|
2025-02-13 15:57:23 +01:00
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
try:
|
|
|
|
data_type_exp = parse_one(dtype, read=dialect, into=DataType)
|
|
|
|
except ParseError:
|
|
|
|
if udt:
|
|
|
|
return DataType(this=DataType.Type.USERDEFINED, kind=dtype, **kwargs)
|
|
|
|
raise
|
2025-02-13 15:03:38 +01:00
|
|
|
elif isinstance(dtype, DataType.Type):
|
|
|
|
data_type_exp = DataType(this=dtype)
|
2025-02-13 15:09:58 +01:00
|
|
|
elif isinstance(dtype, DataType):
|
|
|
|
return dtype
|
2025-02-13 15:03:38 +01:00
|
|
|
else:
|
|
|
|
raise ValueError(f"Invalid data type: {type(dtype)}. Expected str or DataType.Type")
|
2025-02-13 15:57:23 +01:00
|
|
|
|
2025-02-13 15:03:38 +01:00
|
|
|
return DataType(**{**data_type_exp.args, **kwargs})
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:58:40 +01:00
|
|
|
def is_type(self, *dtypes: str | DataType | DataType.Type) -> bool:
|
2025-02-13 20:58:22 +01:00
|
|
|
"""
|
|
|
|
Checks whether this DataType matches one of the provided data types. Nested types or precision
|
|
|
|
will be compared using "structural equivalence" semantics, so e.g. array<int> != array<float>.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
dtypes: the data types to compare this DataType to.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
True, if and only if there is a type in `dtypes` which is equal to this DataType.
|
|
|
|
"""
|
|
|
|
for dtype in dtypes:
|
|
|
|
other = DataType.build(dtype, udt=True)
|
|
|
|
|
|
|
|
if (
|
|
|
|
other.expressions
|
|
|
|
or self.this == DataType.Type.USERDEFINED
|
|
|
|
or other.this == DataType.Type.USERDEFINED
|
|
|
|
):
|
|
|
|
matches = self == other
|
|
|
|
else:
|
|
|
|
matches = self.this == other.this
|
|
|
|
|
|
|
|
if matches:
|
|
|
|
return True
|
|
|
|
return False
|
2025-02-13 15:09:58 +01:00
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 15:01:55 +01:00
|
|
|
# https://www.postgresql.org/docs/15/datatype-pseudo.html
|
2025-02-13 21:04:58 +01:00
|
|
|
class PseudoType(DataType):
|
|
|
|
arg_types = {"this": True}
|
2025-02-13 15:01:55 +01:00
|
|
|
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
# https://www.postgresql.org/docs/15/datatype-oid.html
|
2025-02-13 21:04:58 +01:00
|
|
|
class ObjectIdentifier(DataType):
|
|
|
|
arg_types = {"this": True}
|
2025-02-13 20:58:22 +01:00
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
# WHERE x <OP> EXISTS|ALL|ANY|SOME(SELECT ...)
|
|
|
|
class SubqueryPredicate(Predicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class All(SubqueryPredicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Any(SubqueryPredicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Exists(SubqueryPredicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 14:54:32 +01:00
|
|
|
# Commands to interact with the databases or engines. For most of the command
|
|
|
|
# expressions we parse whatever comes after the command's name as a string.
|
2025-02-13 06:15:54 +01:00
|
|
|
class Command(Expression):
|
|
|
|
arg_types = {"this": True, "expression": False}
|
|
|
|
|
|
|
|
|
2025-02-13 15:01:55 +01:00
|
|
|
class Transaction(Expression):
|
2025-02-13 20:46:55 +01:00
|
|
|
arg_types = {"this": False, "modes": False, "mark": False}
|
2025-02-13 14:54:32 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:01:55 +01:00
|
|
|
class Commit(Expression):
|
2025-02-13 20:46:55 +01:00
|
|
|
arg_types = {"chain": False, "this": False, "durability": False}
|
2025-02-13 14:54:32 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:01:55 +01:00
|
|
|
class Rollback(Expression):
|
2025-02-13 20:46:55 +01:00
|
|
|
arg_types = {"savepoint": False, "this": False}
|
2025-02-13 14:54:32 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:01:55 +01:00
|
|
|
class AlterTable(Expression):
|
2025-02-13 20:59:47 +01:00
|
|
|
arg_types = {"this": True, "actions": True, "exists": False, "only": False}
|
2025-02-13 15:07:05 +01:00
|
|
|
|
|
|
|
|
|
|
|
class AddConstraint(Expression):
|
|
|
|
arg_types = {"this": False, "expression": False, "enforced": False}
|
2025-02-13 15:01:55 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:08:15 +01:00
|
|
|
class DropPartition(Expression):
|
|
|
|
arg_types = {"expressions": True, "exists": False}
|
|
|
|
|
|
|
|
|
2025-02-13 14:54:32 +01:00
|
|
|
# Binary expressions like (ADD a b)
|
2025-02-13 15:52:09 +01:00
|
|
|
class Binary(Condition):
|
2025-02-13 06:15:54 +01:00
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
|
|
|
|
@property
|
|
|
|
def left(self):
|
|
|
|
return self.this
|
|
|
|
|
|
|
|
@property
|
|
|
|
def right(self):
|
|
|
|
return self.expression
|
|
|
|
|
|
|
|
|
|
|
|
class Add(Binary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
class Connector(Binary):
|
2025-02-13 06:15:54 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class And(Connector):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Or(Connector):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class BitwiseAnd(Binary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class BitwiseLeftShift(Binary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class BitwiseOr(Binary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class BitwiseRightShift(Binary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class BitwiseXor(Binary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Div(Binary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:42:13 +01:00
|
|
|
class Overlaps(Binary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Dot(Binary):
|
2025-02-13 15:23:26 +01:00
|
|
|
@property
|
|
|
|
def name(self) -> str:
|
|
|
|
return self.expression.name
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 16:00:51 +01:00
|
|
|
@property
|
|
|
|
def output_name(self) -> str:
|
|
|
|
return self.name
|
|
|
|
|
2025-02-13 15:46:19 +01:00
|
|
|
@classmethod
|
|
|
|
def build(self, expressions: t.Sequence[Expression]) -> Dot:
|
|
|
|
"""Build a Dot object with a sequence of expressions."""
|
|
|
|
if len(expressions) < 2:
|
|
|
|
raise ValueError(f"Dot requires >= 2 expressions.")
|
|
|
|
|
2025-02-13 21:01:12 +01:00
|
|
|
return t.cast(Dot, reduce(lambda x, y: Dot(this=x, expression=y), expressions))
|
2025-02-13 15:46:19 +01:00
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
class DPipe(Binary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 16:00:51 +01:00
|
|
|
class SafeDPipe(DPipe):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class EQ(Binary, Predicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 14:53:05 +01:00
|
|
|
class NullSafeEQ(Binary, Predicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class NullSafeNEQ(Binary, Predicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Distance(Binary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Escape(Binary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:08:15 +01:00
|
|
|
class Glob(Binary, Predicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class GT(Binary, Predicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class GTE(Binary, Predicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class ILike(Binary, Predicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:26:26 +01:00
|
|
|
class ILikeAny(Binary, Predicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class IntDiv(Binary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Is(Binary, Predicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 14:53:05 +01:00
|
|
|
class Kwarg(Binary):
|
|
|
|
"""Kwarg in special functions like func(kwarg => y)."""
|
2025-02-13 08:04:41 +01:00
|
|
|
|
|
|
|
|
2025-02-13 14:53:05 +01:00
|
|
|
class Like(Binary, Predicate):
|
2025-02-13 08:04:41 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:26:26 +01:00
|
|
|
class LikeAny(Binary, Predicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class LT(Binary, Predicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class LTE(Binary, Predicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Mod(Binary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Mul(Binary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class NEQ(Binary, Predicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 14:53:05 +01:00
|
|
|
class SimilarTo(Binary, Predicate):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:01:55 +01:00
|
|
|
class Slice(Binary):
|
|
|
|
arg_types = {"this": False, "expression": False}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Sub(Binary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:44:58 +01:00
|
|
|
class ArrayOverlaps(Binary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
# Unary Expressions
|
|
|
|
# (NOT a)
|
2025-02-13 15:52:09 +01:00
|
|
|
class Unary(Condition):
|
2025-02-13 06:15:54 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class BitwiseNot(Unary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
class Not(Unary):
|
2025-02-13 06:15:54 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
class Paren(Unary):
|
2025-02-13 14:54:32 +01:00
|
|
|
arg_types = {"this": True, "with": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 16:00:51 +01:00
|
|
|
@property
|
|
|
|
def output_name(self) -> str:
|
|
|
|
return self.this.name
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
class Neg(Unary):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Alias(Expression):
|
|
|
|
arg_types = {"this": True, "alias": False}
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def output_name(self) -> str:
|
2025-02-13 15:07:05 +01:00
|
|
|
return self.alias
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
class Aliases(Expression):
|
|
|
|
arg_types = {"this": True, "expressions": True}
|
|
|
|
|
|
|
|
@property
|
|
|
|
def aliases(self):
|
|
|
|
return self.expressions
|
|
|
|
|
|
|
|
|
|
|
|
class AtTimeZone(Expression):
|
|
|
|
arg_types = {"this": True, "zone": True}
|
|
|
|
|
|
|
|
|
|
|
|
class Between(Predicate):
|
|
|
|
arg_types = {"this": True, "low": True, "high": True}
|
|
|
|
|
|
|
|
|
|
|
|
class Bracket(Condition):
|
|
|
|
arg_types = {"this": True, "expressions": True}
|
|
|
|
|
2025-02-13 21:01:12 +01:00
|
|
|
@property
|
|
|
|
def output_name(self) -> str:
|
|
|
|
if len(self.expressions) == 1:
|
|
|
|
return self.expressions[0].output_name
|
|
|
|
|
|
|
|
return super().output_name
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 20:21:40 +01:00
|
|
|
class SafeBracket(Bracket):
|
|
|
|
"""Represents array lookup where OOB index yields NULL instead of causing a failure."""
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Distinct(Expression):
|
2025-02-13 14:46:58 +01:00
|
|
|
arg_types = {"expressions": False, "on": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class In(Predicate):
|
2025-02-13 14:53:05 +01:00
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"expressions": False,
|
|
|
|
"query": False,
|
|
|
|
"unnest": False,
|
|
|
|
"field": False,
|
2025-02-13 15:03:38 +01:00
|
|
|
"is_global": False,
|
2025-02-13 14:53:05 +01:00
|
|
|
}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class TimeUnit(Expression):
|
|
|
|
"""Automatically converts unit arg into a var."""
|
|
|
|
|
|
|
|
arg_types = {"unit": False}
|
|
|
|
|
|
|
|
def __init__(self, **args):
|
|
|
|
unit = args.get("unit")
|
2025-02-13 15:46:19 +01:00
|
|
|
if isinstance(unit, (Column, Literal)):
|
2025-02-13 06:15:54 +01:00
|
|
|
args["unit"] = Var(this=unit.name)
|
|
|
|
elif isinstance(unit, Week):
|
|
|
|
unit.set("this", Var(this=unit.this.name))
|
2025-02-13 15:57:23 +01:00
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
super().__init__(**args)
|
|
|
|
|
2025-02-13 21:03:38 +01:00
|
|
|
@property
|
|
|
|
def unit(self) -> t.Optional[Var]:
|
|
|
|
return self.args.get("unit")
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 21:04:58 +01:00
|
|
|
class IntervalOp(TimeUnit):
|
|
|
|
arg_types = {"unit": True, "expression": True}
|
|
|
|
|
|
|
|
def interval(self):
|
|
|
|
return Interval(
|
|
|
|
this=self.expression.copy(),
|
|
|
|
unit=self.unit.copy(),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2025-02-13 20:55:29 +01:00
|
|
|
# https://www.oracletutorial.com/oracle-basics/oracle-interval/
|
|
|
|
# https://trino.io/docs/current/language/types.html#interval-day-to-second
|
2025-02-13 20:59:47 +01:00
|
|
|
# https://docs.databricks.com/en/sql/language-manual/data-types/interval-type.html
|
2025-02-13 21:04:58 +01:00
|
|
|
class IntervalSpan(DataType):
|
2025-02-13 20:59:47 +01:00
|
|
|
arg_types = {"this": True, "expression": True}
|
2025-02-13 20:55:29 +01:00
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Interval(TimeUnit):
|
2025-02-13 15:01:55 +01:00
|
|
|
arg_types = {"this": False, "unit": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class IgnoreNulls(Expression):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
class RespectNulls(Expression):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
# Functions
|
|
|
|
class Func(Condition):
|
|
|
|
"""
|
|
|
|
The base class for all function expressions.
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
Attributes:
|
|
|
|
is_var_len_args (bool): if set to True the last argument defined in arg_types will be
|
|
|
|
treated as a variable length argument and the argument's value will be stored as a list.
|
|
|
|
_sql_names (list): determines the SQL name (1st item in the list) and aliases (subsequent items)
|
|
|
|
for this function expression. These values are used to map this node to a name during parsing
|
|
|
|
as well as to provide the function's name during SQL string generation. By default the SQL
|
|
|
|
name is set to the expression's class name transformed to snake case.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
is_var_len_args = False
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_arg_list(cls, args):
|
2025-02-13 14:43:32 +01:00
|
|
|
if cls.is_var_len_args:
|
|
|
|
all_arg_keys = list(cls.arg_types)
|
|
|
|
# If this function supports variable length argument treat the last argument as such.
|
|
|
|
non_var_len_arg_keys = all_arg_keys[:-1] if cls.is_var_len_args else all_arg_keys
|
|
|
|
num_non_var = len(non_var_len_arg_keys)
|
|
|
|
|
|
|
|
args_dict = {arg_key: arg for arg, arg_key in zip(args, non_var_len_arg_keys)}
|
|
|
|
args_dict[all_arg_keys[-1]] = args[num_non_var:]
|
|
|
|
else:
|
|
|
|
args_dict = {arg_key: arg for arg, arg_key in zip(args, cls.arg_types)}
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
return cls(**args_dict)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def sql_names(cls):
|
|
|
|
if cls is Func:
|
2025-02-13 14:53:05 +01:00
|
|
|
raise NotImplementedError(
|
|
|
|
"SQL name is only supported by concrete function implementations"
|
|
|
|
)
|
2025-02-13 15:07:05 +01:00
|
|
|
if "_sql_names" not in cls.__dict__:
|
2025-02-13 06:15:54 +01:00
|
|
|
cls._sql_names = [camel_to_snake_case(cls.__name__)]
|
|
|
|
return cls._sql_names
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def sql_name(cls):
|
|
|
|
return cls.sql_names()[0]
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def default_parser_mappings(cls):
|
|
|
|
return {name: cls.from_arg_list for name in cls.sql_names()}
|
|
|
|
|
|
|
|
|
|
|
|
class AggFunc(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
class ParameterizedAgg(AggFunc):
|
|
|
|
arg_types = {"this": True, "expressions": True, "params": True}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Abs(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 20:46:55 +01:00
|
|
|
# https://spark.apache.org/docs/latest/api/sql/index.html#transform
|
|
|
|
class Transform(Func):
|
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Anonymous(Func):
|
|
|
|
arg_types = {"this": True, "expressions": False}
|
|
|
|
is_var_len_args = True
|
|
|
|
|
|
|
|
|
2025-02-13 15:50:57 +01:00
|
|
|
# https://docs.snowflake.com/en/sql-reference/functions/hll
|
|
|
|
# https://docs.aws.amazon.com/redshift/latest/dg/r_HLL_function.html
|
|
|
|
class Hll(AggFunc):
|
|
|
|
arg_types = {"this": True, "expressions": False}
|
|
|
|
is_var_len_args = True
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class ApproxDistinct(AggFunc):
|
|
|
|
arg_types = {"this": True, "accuracy": False}
|
2025-02-13 15:57:23 +01:00
|
|
|
_sql_names = ["APPROX_DISTINCT", "APPROX_COUNT_DISTINCT"]
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Array(Func):
|
|
|
|
arg_types = {"expressions": False}
|
|
|
|
is_var_len_args = True
|
|
|
|
|
|
|
|
|
2025-02-13 15:44:58 +01:00
|
|
|
# https://docs.snowflake.com/en/sql-reference/functions/to_char
|
|
|
|
class ToChar(Func):
|
|
|
|
arg_types = {"this": True, "format": False}
|
|
|
|
|
|
|
|
|
2025-02-13 15:09:58 +01:00
|
|
|
class GenerateSeries(Func):
|
|
|
|
arg_types = {"start": True, "end": True, "step": False}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class ArrayAgg(AggFunc):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class ArrayAll(Func):
|
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
|
|
|
|
|
|
|
|
class ArrayAny(Func):
|
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
|
|
|
|
|
2025-02-13 14:48:46 +01:00
|
|
|
class ArrayConcat(Func):
|
2025-02-13 20:58:22 +01:00
|
|
|
_sql_names = ["ARRAY_CONCAT", "ARRAY_CAT"]
|
2025-02-13 14:48:46 +01:00
|
|
|
arg_types = {"this": True, "expressions": False}
|
|
|
|
is_var_len_args = True
|
|
|
|
|
|
|
|
|
2025-02-13 15:44:58 +01:00
|
|
|
class ArrayContains(Binary, Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class ArrayContained(Binary):
|
|
|
|
pass
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class ArrayFilter(Func):
|
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
_sql_names = ["FILTER", "ARRAY_FILTER"]
|
|
|
|
|
|
|
|
|
2025-02-13 15:42:13 +01:00
|
|
|
class ArrayJoin(Func):
|
|
|
|
arg_types = {"this": True, "expression": True, "null": False}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class ArraySize(Func):
|
2025-02-13 14:56:25 +01:00
|
|
|
arg_types = {"this": True, "expression": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class ArraySort(Func):
|
|
|
|
arg_types = {"this": True, "expression": False}
|
|
|
|
|
|
|
|
|
|
|
|
class ArraySum(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class ArrayUnionAgg(AggFunc):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Avg(AggFunc):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class AnyValue(AggFunc):
|
2025-02-13 20:58:22 +01:00
|
|
|
arg_types = {"this": True, "having": False, "max": False, "ignore_nulls": False}
|
|
|
|
|
|
|
|
|
|
|
|
class First(Func):
|
|
|
|
arg_types = {"this": True, "ignore_nulls": False}
|
|
|
|
|
|
|
|
|
|
|
|
class Last(Func):
|
|
|
|
arg_types = {"this": True, "ignore_nulls": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Case(Func):
|
|
|
|
arg_types = {"this": False, "ifs": True, "default": False}
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
def when(self, condition: ExpOrStr, then: ExpOrStr, copy: bool = True, **opts) -> Case:
|
2025-02-13 20:48:36 +01:00
|
|
|
instance = maybe_copy(self, copy)
|
2025-02-13 15:53:39 +01:00
|
|
|
instance.append(
|
|
|
|
"ifs",
|
|
|
|
If(
|
|
|
|
this=maybe_parse(condition, copy=copy, **opts),
|
|
|
|
true=maybe_parse(then, copy=copy, **opts),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
return instance
|
2025-02-13 15:52:09 +01:00
|
|
|
|
|
|
|
def else_(self, condition: ExpOrStr, copy: bool = True, **opts) -> Case:
|
2025-02-13 20:48:36 +01:00
|
|
|
instance = maybe_copy(self, copy)
|
2025-02-13 15:53:39 +01:00
|
|
|
instance.set("default", maybe_parse(condition, copy=copy, **opts))
|
|
|
|
return instance
|
2025-02-13 15:52:09 +01:00
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
class Cast(Func):
|
2025-02-13 20:21:40 +01:00
|
|
|
arg_types = {"this": True, "to": True, "format": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 14:46:58 +01:00
|
|
|
@property
|
2025-02-13 15:23:26 +01:00
|
|
|
def name(self) -> str:
|
2025-02-13 14:46:58 +01:00
|
|
|
return self.this.name
|
|
|
|
|
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def to(self) -> DataType:
|
2025-02-13 14:46:58 +01:00
|
|
|
return self.args["to"]
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
@property
|
2025-02-13 15:57:23 +01:00
|
|
|
def output_name(self) -> str:
|
2025-02-13 15:07:05 +01:00
|
|
|
return self.name
|
|
|
|
|
2025-02-13 15:58:40 +01:00
|
|
|
def is_type(self, *dtypes: str | DataType | DataType.Type) -> bool:
|
2025-02-13 20:58:22 +01:00
|
|
|
"""
|
|
|
|
Checks whether this Cast's DataType matches one of the provided data types. Nested types
|
|
|
|
like arrays or structs will be compared using "structural equivalence" semantics, so e.g.
|
|
|
|
array<int> != array<float>.
|
2025-02-13 15:09:58 +01:00
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
Args:
|
|
|
|
dtypes: the data types to compare this Cast's DataType to.
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
Returns:
|
|
|
|
True, if and only if there is a type in `dtypes` which is equal to this Cast's DataType.
|
|
|
|
"""
|
|
|
|
return self.to.is_type(*dtypes)
|
2025-02-13 15:57:23 +01:00
|
|
|
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
class TryCast(Cast):
|
2025-02-13 14:54:32 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
class CastToStrType(Func):
|
|
|
|
arg_types = {"this": True, "to": True}
|
|
|
|
|
|
|
|
|
2025-02-13 21:04:58 +01:00
|
|
|
class Collate(Binary, Func):
|
2025-02-13 06:15:54 +01:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Ceil(Func):
|
2025-02-13 14:40:43 +01:00
|
|
|
arg_types = {"this": True, "decimals": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
_sql_names = ["CEIL", "CEILING"]
|
|
|
|
|
|
|
|
|
|
|
|
class Coalesce(Func):
|
|
|
|
arg_types = {"this": True, "expressions": False}
|
|
|
|
is_var_len_args = True
|
2025-02-13 16:00:51 +01:00
|
|
|
_sql_names = ["COALESCE", "IFNULL", "NVL"]
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 21:04:58 +01:00
|
|
|
class Chr(Func):
|
|
|
|
arg_types = {"this": True, "charset": False, "expressions": False}
|
|
|
|
is_var_len_args = True
|
|
|
|
_sql_names = ["CHR", "CHAR"]
|
|
|
|
|
|
|
|
|
2025-02-13 14:54:32 +01:00
|
|
|
class Concat(Func):
|
|
|
|
arg_types = {"expressions": True}
|
2025-02-13 06:15:54 +01:00
|
|
|
is_var_len_args = True
|
|
|
|
|
|
|
|
|
2025-02-13 16:00:51 +01:00
|
|
|
class SafeConcat(Concat):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 14:54:32 +01:00
|
|
|
class ConcatWs(Concat):
|
|
|
|
_sql_names = ["CONCAT_WS"]
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Count(AggFunc):
|
2025-02-13 16:00:51 +01:00
|
|
|
arg_types = {"this": False, "expressions": False}
|
|
|
|
is_var_len_args = True
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:46:19 +01:00
|
|
|
class CountIf(AggFunc):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class CurrentDate(Func):
|
|
|
|
arg_types = {"this": False}
|
|
|
|
|
|
|
|
|
|
|
|
class CurrentDatetime(Func):
|
|
|
|
arg_types = {"this": False}
|
|
|
|
|
|
|
|
|
|
|
|
class CurrentTime(Func):
|
|
|
|
arg_types = {"this": False}
|
|
|
|
|
|
|
|
|
|
|
|
class CurrentTimestamp(Func):
|
|
|
|
arg_types = {"this": False}
|
|
|
|
|
|
|
|
|
2025-02-13 15:50:57 +01:00
|
|
|
class CurrentUser(Func):
|
|
|
|
arg_types = {"this": False}
|
|
|
|
|
|
|
|
|
2025-02-13 21:04:58 +01:00
|
|
|
class DateAdd(Func, IntervalOp):
|
2025-02-13 06:15:54 +01:00
|
|
|
arg_types = {"this": True, "expression": True, "unit": False}
|
|
|
|
|
|
|
|
|
2025-02-13 21:04:58 +01:00
|
|
|
class DateSub(Func, IntervalOp):
|
2025-02-13 06:15:54 +01:00
|
|
|
arg_types = {"this": True, "expression": True, "unit": False}
|
|
|
|
|
|
|
|
|
|
|
|
class DateDiff(Func, TimeUnit):
|
2025-02-13 15:44:58 +01:00
|
|
|
_sql_names = ["DATEDIFF", "DATE_DIFF"]
|
2025-02-13 06:15:54 +01:00
|
|
|
arg_types = {"this": True, "expression": True, "unit": False}
|
|
|
|
|
|
|
|
|
2025-02-13 14:53:05 +01:00
|
|
|
class DateTrunc(Func):
|
2025-02-13 15:26:26 +01:00
|
|
|
arg_types = {"unit": True, "this": True, "zone": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 21:03:38 +01:00
|
|
|
@property
|
|
|
|
def unit(self) -> Expression:
|
|
|
|
return self.args["unit"]
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 21:04:58 +01:00
|
|
|
class DatetimeAdd(Func, IntervalOp):
|
2025-02-13 06:15:54 +01:00
|
|
|
arg_types = {"this": True, "expression": True, "unit": False}
|
|
|
|
|
|
|
|
|
2025-02-13 21:04:58 +01:00
|
|
|
class DatetimeSub(Func, IntervalOp):
|
2025-02-13 06:15:54 +01:00
|
|
|
arg_types = {"this": True, "expression": True, "unit": False}
|
|
|
|
|
|
|
|
|
|
|
|
class DatetimeDiff(Func, TimeUnit):
|
|
|
|
arg_types = {"this": True, "expression": True, "unit": False}
|
|
|
|
|
|
|
|
|
|
|
|
class DatetimeTrunc(Func, TimeUnit):
|
|
|
|
arg_types = {"this": True, "unit": True, "zone": False}
|
|
|
|
|
|
|
|
|
2025-02-13 15:09:58 +01:00
|
|
|
class DayOfWeek(Func):
|
|
|
|
_sql_names = ["DAY_OF_WEEK", "DAYOFWEEK"]
|
|
|
|
|
|
|
|
|
|
|
|
class DayOfMonth(Func):
|
|
|
|
_sql_names = ["DAY_OF_MONTH", "DAYOFMONTH"]
|
|
|
|
|
|
|
|
|
|
|
|
class DayOfYear(Func):
|
|
|
|
_sql_names = ["DAY_OF_YEAR", "DAYOFYEAR"]
|
|
|
|
|
|
|
|
|
2025-02-13 21:04:58 +01:00
|
|
|
class ToDays(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:09:58 +01:00
|
|
|
class WeekOfYear(Func):
|
|
|
|
_sql_names = ["WEEK_OF_YEAR", "WEEKOFYEAR"]
|
|
|
|
|
|
|
|
|
2025-02-13 20:46:55 +01:00
|
|
|
class MonthsBetween(Func):
|
|
|
|
arg_types = {"this": True, "expression": True, "roundoff": False}
|
|
|
|
|
|
|
|
|
2025-02-13 15:03:38 +01:00
|
|
|
class LastDateOfMonth(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Extract(Func):
|
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
|
|
|
|
|
2025-02-13 21:02:36 +01:00
|
|
|
class Timestamp(Func):
|
|
|
|
arg_types = {"this": False, "expression": False}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class TimestampAdd(Func, TimeUnit):
|
|
|
|
arg_types = {"this": True, "expression": True, "unit": False}
|
|
|
|
|
|
|
|
|
|
|
|
class TimestampSub(Func, TimeUnit):
|
|
|
|
arg_types = {"this": True, "expression": True, "unit": False}
|
|
|
|
|
|
|
|
|
|
|
|
class TimestampDiff(Func, TimeUnit):
|
|
|
|
arg_types = {"this": True, "expression": True, "unit": False}
|
|
|
|
|
|
|
|
|
|
|
|
class TimestampTrunc(Func, TimeUnit):
|
|
|
|
arg_types = {"this": True, "unit": True, "zone": False}
|
|
|
|
|
|
|
|
|
|
|
|
class TimeAdd(Func, TimeUnit):
|
|
|
|
arg_types = {"this": True, "expression": True, "unit": False}
|
|
|
|
|
|
|
|
|
|
|
|
class TimeSub(Func, TimeUnit):
|
|
|
|
arg_types = {"this": True, "expression": True, "unit": False}
|
|
|
|
|
|
|
|
|
|
|
|
class TimeDiff(Func, TimeUnit):
|
|
|
|
arg_types = {"this": True, "expression": True, "unit": False}
|
|
|
|
|
|
|
|
|
|
|
|
class TimeTrunc(Func, TimeUnit):
|
|
|
|
arg_types = {"this": True, "unit": True, "zone": False}
|
|
|
|
|
|
|
|
|
2025-02-13 14:50:31 +01:00
|
|
|
class DateFromParts(Func):
|
|
|
|
_sql_names = ["DATEFROMPARTS"]
|
|
|
|
arg_types = {"year": True, "month": True, "day": True}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class DateStrToDate(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class DateToDateStr(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class DateToDi(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 20:43:05 +01:00
|
|
|
# https://cloud.google.com/bigquery/docs/reference/standard-sql/date_functions#date
|
2025-02-13 16:00:51 +01:00
|
|
|
class Date(Func):
|
2025-02-13 21:03:38 +01:00
|
|
|
arg_types = {"this": False, "zone": False, "expressions": False}
|
|
|
|
is_var_len_args = True
|
2025-02-13 16:00:51 +01:00
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Day(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 14:54:32 +01:00
|
|
|
class Decode(Func):
|
2025-02-13 15:05:06 +01:00
|
|
|
arg_types = {"this": True, "charset": True, "replace": False}
|
2025-02-13 14:54:32 +01:00
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class DiToDate(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 14:54:32 +01:00
|
|
|
class Encode(Func):
|
|
|
|
arg_types = {"this": True, "charset": True}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Exp(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Explode(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Floor(Func):
|
2025-02-13 14:40:43 +01:00
|
|
|
arg_types = {"this": True, "decimals": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:53:39 +01:00
|
|
|
class FromBase64(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class ToBase64(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Greatest(Func):
|
2025-02-13 15:05:06 +01:00
|
|
|
arg_types = {"this": True, "expressions": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
is_var_len_args = True
|
|
|
|
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
class GroupConcat(AggFunc):
|
2025-02-13 08:04:41 +01:00
|
|
|
arg_types = {"this": True, "separator": False}
|
|
|
|
|
|
|
|
|
2025-02-13 14:54:32 +01:00
|
|
|
class Hex(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 20:46:55 +01:00
|
|
|
class Xor(Connector, Func):
|
|
|
|
arg_types = {"this": False, "expression": False, "expressions": False}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class If(Func):
|
|
|
|
arg_types = {"this": True, "true": True, "false": False}
|
|
|
|
|
|
|
|
|
|
|
|
class Initcap(Func):
|
2025-02-13 15:57:23 +01:00
|
|
|
arg_types = {"this": True, "expression": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 20:48:36 +01:00
|
|
|
class IsNan(Func):
|
|
|
|
_sql_names = ["IS_NAN", "ISNAN"]
|
|
|
|
|
|
|
|
|
2025-02-13 21:01:12 +01:00
|
|
|
class FormatJson(Expression):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:48:10 +01:00
|
|
|
class JSONKeyValue(Expression):
|
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
|
|
|
|
|
|
|
|
class JSONObject(Func):
|
|
|
|
arg_types = {
|
|
|
|
"expressions": False,
|
|
|
|
"null_handling": False,
|
|
|
|
"unique_keys": False,
|
|
|
|
"return_type": False,
|
|
|
|
"encoding": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 21:01:12 +01:00
|
|
|
# https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/JSON_ARRAY.html
|
|
|
|
class JSONArray(Func):
|
|
|
|
arg_types = {
|
|
|
|
"expressions": True,
|
|
|
|
"null_handling": False,
|
|
|
|
"return_type": False,
|
|
|
|
"strict": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/JSON_ARRAYAGG.html
|
|
|
|
class JSONArrayAgg(Func):
|
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"order": False,
|
|
|
|
"null_handling": False,
|
|
|
|
"return_type": False,
|
|
|
|
"strict": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/JSON_TABLE.html
|
|
|
|
# Note: parsing of JSON column definitions is currently incomplete.
|
|
|
|
class JSONColumnDef(Expression):
|
|
|
|
arg_types = {"this": True, "kind": False, "path": False}
|
|
|
|
|
|
|
|
|
|
|
|
# # https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/JSON_TABLE.html
|
|
|
|
class JSONTable(Func):
|
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"expressions": True,
|
|
|
|
"path": False,
|
|
|
|
"error_handling": False,
|
|
|
|
"empty_handling": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
class OpenJSONColumnDef(Expression):
|
|
|
|
arg_types = {"this": True, "kind": True, "path": False, "as_json": False}
|
|
|
|
|
|
|
|
|
|
|
|
class OpenJSON(Func):
|
|
|
|
arg_types = {"this": True, "path": False, "expressions": False}
|
|
|
|
|
|
|
|
|
2025-02-13 15:01:55 +01:00
|
|
|
class JSONBContains(Binary):
|
|
|
|
_sql_names = ["JSONB_CONTAINS"]
|
|
|
|
|
|
|
|
|
|
|
|
class JSONExtract(Binary, Func):
|
2025-02-13 06:15:54 +01:00
|
|
|
_sql_names = ["JSON_EXTRACT"]
|
|
|
|
|
|
|
|
|
|
|
|
class JSONExtractScalar(JSONExtract):
|
|
|
|
_sql_names = ["JSON_EXTRACT_SCALAR"]
|
|
|
|
|
|
|
|
|
|
|
|
class JSONBExtract(JSONExtract):
|
|
|
|
_sql_names = ["JSONB_EXTRACT"]
|
|
|
|
|
|
|
|
|
|
|
|
class JSONBExtractScalar(JSONExtract):
|
|
|
|
_sql_names = ["JSONB_EXTRACT_SCALAR"]
|
|
|
|
|
|
|
|
|
2025-02-13 15:50:57 +01:00
|
|
|
class JSONFormat(Func):
|
|
|
|
arg_types = {"this": False, "options": False}
|
|
|
|
_sql_names = ["JSON_FORMAT"]
|
|
|
|
|
|
|
|
|
2025-02-13 20:43:05 +01:00
|
|
|
# https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html#operator_member-of
|
|
|
|
class JSONArrayContains(Binary, Predicate, Func):
|
|
|
|
_sql_names = ["JSON_ARRAY_CONTAINS"]
|
|
|
|
|
|
|
|
|
2025-02-13 21:02:36 +01:00
|
|
|
class ParseJSON(Func):
|
|
|
|
# BigQuery, Snowflake have PARSE_JSON, Presto has JSON_PARSE
|
|
|
|
_sql_names = ["PARSE_JSON", "JSON_PARSE"]
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Least(Func):
|
2025-02-13 20:44:18 +01:00
|
|
|
arg_types = {"this": True, "expressions": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
is_var_len_args = True
|
|
|
|
|
|
|
|
|
2025-02-13 15:58:40 +01:00
|
|
|
class Left(Func):
|
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
|
|
|
|
|
|
|
|
class Right(Func):
|
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Length(Func):
|
2025-02-13 15:58:40 +01:00
|
|
|
_sql_names = ["LENGTH", "LEN"]
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Levenshtein(Func):
|
2025-02-13 15:03:38 +01:00
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"expression": False,
|
|
|
|
"ins_cost": False,
|
|
|
|
"del_cost": False,
|
|
|
|
"sub_cost": False,
|
|
|
|
}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Ln(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Log(Func):
|
|
|
|
arg_types = {"this": True, "expression": False}
|
|
|
|
|
|
|
|
|
|
|
|
class Log2(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Log10(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:01:55 +01:00
|
|
|
class LogicalOr(AggFunc):
|
2025-02-13 15:46:19 +01:00
|
|
|
_sql_names = ["LOGICAL_OR", "BOOL_OR", "BOOLOR_AGG"]
|
|
|
|
|
|
|
|
|
|
|
|
class LogicalAnd(AggFunc):
|
|
|
|
_sql_names = ["LOGICAL_AND", "BOOL_AND", "BOOLAND_AGG"]
|
2025-02-13 15:01:55 +01:00
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Lower(Func):
|
2025-02-13 14:54:32 +01:00
|
|
|
_sql_names = ["LOWER", "LCASE"]
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Map(Func):
|
2025-02-13 15:05:06 +01:00
|
|
|
arg_types = {"keys": False, "values": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 20:21:40 +01:00
|
|
|
class MapFromEntries(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
class StarMap(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 14:45:11 +01:00
|
|
|
class VarMap(Func):
|
|
|
|
arg_types = {"keys": True, "values": True}
|
|
|
|
is_var_len_args = True
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
@property
|
|
|
|
def keys(self) -> t.List[Expression]:
|
|
|
|
return self.args["keys"].expressions
|
|
|
|
|
|
|
|
@property
|
|
|
|
def values(self) -> t.List[Expression]:
|
|
|
|
return self.args["values"].expressions
|
|
|
|
|
2025-02-13 14:45:11 +01:00
|
|
|
|
2025-02-13 15:50:57 +01:00
|
|
|
# https://dev.mysql.com/doc/refman/8.0/en/fulltext-search.html
|
|
|
|
class MatchAgainst(Func):
|
|
|
|
arg_types = {"this": True, "expressions": True, "modifier": False}
|
2025-02-13 14:56:25 +01:00
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Max(AggFunc):
|
2025-02-13 15:43:32 +01:00
|
|
|
arg_types = {"this": True, "expressions": False}
|
|
|
|
is_var_len_args = True
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
class MD5(Func):
|
|
|
|
_sql_names = ["MD5"]
|
|
|
|
|
|
|
|
|
2025-02-13 20:44:18 +01:00
|
|
|
# Represents the variant of the MD5 function that returns a binary value
|
|
|
|
class MD5Digest(Func):
|
|
|
|
_sql_names = ["MD5_DIGEST"]
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Min(AggFunc):
|
2025-02-13 15:43:32 +01:00
|
|
|
arg_types = {"this": True, "expressions": False}
|
|
|
|
is_var_len_args = True
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Month(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Nvl2(Func):
|
|
|
|
arg_types = {"this": True, "true": True, "false": False}
|
|
|
|
|
|
|
|
|
|
|
|
class Posexplode(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:08:15 +01:00
|
|
|
class Pow(Binary, Func):
|
2025-02-13 06:15:54 +01:00
|
|
|
_sql_names = ["POWER", "POW"]
|
|
|
|
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
class PercentileCont(AggFunc):
|
2025-02-13 15:53:39 +01:00
|
|
|
arg_types = {"this": True, "expression": False}
|
2025-02-13 15:07:05 +01:00
|
|
|
|
|
|
|
|
|
|
|
class PercentileDisc(AggFunc):
|
2025-02-13 15:53:39 +01:00
|
|
|
arg_types = {"this": True, "expression": False}
|
2025-02-13 15:07:05 +01:00
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Quantile(AggFunc):
|
|
|
|
arg_types = {"this": True, "quantile": True}
|
|
|
|
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
class ApproxQuantile(Quantile):
|
2025-02-13 15:05:06 +01:00
|
|
|
arg_types = {"this": True, "quantile": True, "accuracy": False, "weight": False}
|
2025-02-13 08:04:41 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:42:13 +01:00
|
|
|
class RangeN(Func):
|
|
|
|
arg_types = {"this": True, "expressions": True, "each": False}
|
|
|
|
|
|
|
|
|
2025-02-13 14:54:32 +01:00
|
|
|
class ReadCSV(Func):
|
|
|
|
_sql_names = ["READ_CSV"]
|
|
|
|
is_var_len_args = True
|
|
|
|
arg_types = {"this": True, "expressions": False}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Reduce(Func):
|
2025-02-13 15:09:58 +01:00
|
|
|
arg_types = {"this": True, "initial": True, "merge": True, "finish": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:26:26 +01:00
|
|
|
class RegexpExtract(Func):
|
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"expression": True,
|
|
|
|
"position": False,
|
|
|
|
"occurrence": False,
|
2025-02-13 20:45:52 +01:00
|
|
|
"parameters": False,
|
2025-02-13 15:26:26 +01:00
|
|
|
"group": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 20:46:55 +01:00
|
|
|
class RegexpReplace(Func):
|
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"expression": True,
|
|
|
|
"replacement": True,
|
|
|
|
"position": False,
|
|
|
|
"occurrence": False,
|
|
|
|
"parameters": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class RegexpLike(Binary, Func):
|
2025-02-13 14:40:43 +01:00
|
|
|
arg_types = {"this": True, "expression": True, "flag": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:01:55 +01:00
|
|
|
class RegexpILike(Func):
|
|
|
|
arg_types = {"this": True, "expression": True, "flag": False}
|
|
|
|
|
|
|
|
|
2025-02-13 15:48:10 +01:00
|
|
|
# https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.split.html
|
|
|
|
# limit is the number of times a pattern is applied
|
2025-02-13 06:15:54 +01:00
|
|
|
class RegexpSplit(Func):
|
2025-02-13 15:48:10 +01:00
|
|
|
arg_types = {"this": True, "expression": True, "limit": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 14:50:31 +01:00
|
|
|
class Repeat(Func):
|
|
|
|
arg_types = {"this": True, "times": True}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Round(Func):
|
|
|
|
arg_types = {"this": True, "decimals": False}
|
|
|
|
|
|
|
|
|
2025-02-13 14:56:25 +01:00
|
|
|
class RowNumber(Func):
|
|
|
|
arg_types: t.Dict[str, t.Any] = {}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class SafeDivide(Func):
|
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
|
|
|
|
|
|
|
|
class SetAgg(AggFunc):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
class SHA(Func):
|
|
|
|
_sql_names = ["SHA", "SHA1"]
|
|
|
|
|
|
|
|
|
|
|
|
class SHA2(Func):
|
|
|
|
_sql_names = ["SHA2"]
|
|
|
|
arg_types = {"this": True, "length": False}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class SortArray(Func):
|
|
|
|
arg_types = {"this": True, "asc": False}
|
|
|
|
|
|
|
|
|
|
|
|
class Split(Func):
|
2025-02-13 14:48:46 +01:00
|
|
|
arg_types = {"this": True, "expression": True, "limit": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
# Start may be omitted in the case of postgres
|
|
|
|
# https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6
|
2025-02-13 06:15:54 +01:00
|
|
|
class Substring(Func):
|
2025-02-13 08:04:41 +01:00
|
|
|
arg_types = {"this": True, "start": False, "length": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
class StandardHash(Func):
|
|
|
|
arg_types = {"this": True, "expression": False}
|
|
|
|
|
|
|
|
|
2025-02-13 20:48:36 +01:00
|
|
|
class StartsWith(Func):
|
|
|
|
_sql_names = ["STARTS_WITH", "STARTSWITH"]
|
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class StrPosition(Func):
|
2025-02-13 15:05:06 +01:00
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"substr": True,
|
|
|
|
"position": False,
|
|
|
|
"instance": False,
|
|
|
|
}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class StrToDate(Func):
|
|
|
|
arg_types = {"this": True, "format": True}
|
|
|
|
|
|
|
|
|
|
|
|
class StrToTime(Func):
|
2025-02-13 20:43:05 +01:00
|
|
|
arg_types = {"this": True, "format": True, "zone": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:03:38 +01:00
|
|
|
# Spark allows unix_timestamp()
|
|
|
|
# https://spark.apache.org/docs/3.1.3/api/python/reference/api/pyspark.sql.functions.unix_timestamp.html
|
2025-02-13 06:15:54 +01:00
|
|
|
class StrToUnix(Func):
|
2025-02-13 15:03:38 +01:00
|
|
|
arg_types = {"this": False, "format": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
# https://prestodb.io/docs/current/functions/string.html
|
|
|
|
# https://spark.apache.org/docs/latest/api/sql/index.html#str_to_map
|
|
|
|
class StrToMap(Func):
|
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"pair_delim": False,
|
|
|
|
"key_value_delim": False,
|
|
|
|
"duplicate_resolution_callback": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 14:51:47 +01:00
|
|
|
class NumberToStr(Func):
|
2025-02-13 20:58:22 +01:00
|
|
|
arg_types = {"this": True, "format": True, "culture": False}
|
2025-02-13 14:51:47 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:58:40 +01:00
|
|
|
class FromBase(Func):
|
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Struct(Func):
|
|
|
|
arg_types = {"expressions": True}
|
|
|
|
is_var_len_args = True
|
|
|
|
|
|
|
|
|
|
|
|
class StructExtract(Func):
|
|
|
|
arg_types = {"this": True, "expression": True}
|
|
|
|
|
|
|
|
|
2025-02-13 20:58:22 +01:00
|
|
|
# https://learn.microsoft.com/en-us/sql/t-sql/functions/stuff-transact-sql?view=sql-server-ver16
|
|
|
|
# https://docs.snowflake.com/en/sql-reference/functions/insert
|
|
|
|
class Stuff(Func):
|
|
|
|
_sql_names = ["STUFF", "INSERT"]
|
|
|
|
arg_types = {"this": True, "start": True, "length": True, "expression": True}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Sum(AggFunc):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Sqrt(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Stddev(AggFunc):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class StddevPop(AggFunc):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class StddevSamp(AggFunc):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class TimeToStr(Func):
|
2025-02-13 20:58:22 +01:00
|
|
|
arg_types = {"this": True, "format": True, "culture": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class TimeToTimeStr(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class TimeToUnix(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class TimeStrToDate(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class TimeStrToTime(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class TimeStrToUnix(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 08:04:41 +01:00
|
|
|
class Trim(Func):
|
|
|
|
arg_types = {
|
|
|
|
"this": True,
|
|
|
|
"expression": False,
|
2025-02-13 14:54:32 +01:00
|
|
|
"position": False,
|
2025-02-13 08:04:41 +01:00
|
|
|
"collation": False,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class TsOrDsAdd(Func, TimeUnit):
|
|
|
|
arg_types = {"this": True, "expression": True, "unit": False}
|
|
|
|
|
|
|
|
|
|
|
|
class TsOrDsToDateStr(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class TsOrDsToDate(Func):
|
|
|
|
arg_types = {"this": True, "format": False}
|
|
|
|
|
|
|
|
|
|
|
|
class TsOrDiToDi(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 14:54:32 +01:00
|
|
|
class Unhex(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class UnixToStr(Func):
|
2025-02-13 14:50:31 +01:00
|
|
|
arg_types = {"this": True, "format": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:05:06 +01:00
|
|
|
# https://prestodb.io/docs/current/functions/datetime.html
|
|
|
|
# presto has weird zone/hours/minutes
|
2025-02-13 06:15:54 +01:00
|
|
|
class UnixToTime(Func):
|
2025-02-13 15:05:06 +01:00
|
|
|
arg_types = {"this": True, "scale": False, "zone": False, "hours": False, "minutes": False}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
SECONDS = Literal.string("seconds")
|
|
|
|
MILLIS = Literal.string("millis")
|
|
|
|
MICROS = Literal.string("micros")
|
|
|
|
|
|
|
|
|
|
|
|
class UnixToTimeStr(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Upper(Func):
|
2025-02-13 14:54:32 +01:00
|
|
|
_sql_names = ["UPPER", "UCASE"]
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Variance(AggFunc):
|
|
|
|
_sql_names = ["VARIANCE", "VARIANCE_SAMP", "VAR_SAMP"]
|
|
|
|
|
|
|
|
|
|
|
|
class VariancePop(AggFunc):
|
|
|
|
_sql_names = ["VARIANCE_POP", "VAR_POP"]
|
|
|
|
|
|
|
|
|
|
|
|
class Week(Func):
|
|
|
|
arg_types = {"this": True, "mode": False}
|
|
|
|
|
|
|
|
|
2025-02-13 15:26:26 +01:00
|
|
|
class XMLTable(Func):
|
|
|
|
arg_types = {"this": True, "passing": False, "columns": False, "by_ref": False}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
class Year(Func):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2025-02-13 14:53:05 +01:00
|
|
|
class Use(Expression):
|
2025-02-13 15:08:15 +01:00
|
|
|
arg_types = {"this": True, "kind": False}
|
2025-02-13 14:53:05 +01:00
|
|
|
|
|
|
|
|
2025-02-13 14:58:37 +01:00
|
|
|
class Merge(Expression):
|
|
|
|
arg_types = {"this": True, "using": True, "on": True, "expressions": True}
|
|
|
|
|
|
|
|
|
|
|
|
class When(Func):
|
2025-02-13 15:43:32 +01:00
|
|
|
arg_types = {"matched": True, "source": False, "condition": False, "then": True}
|
2025-02-13 14:58:37 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
# https://docs.oracle.com/javadb/10.8.3.0/ref/rrefsqljnextvaluefor.html
|
|
|
|
# https://learn.microsoft.com/en-us/sql/t-sql/functions/next-value-for-transact-sql?view=sql-server-ver16
|
|
|
|
class NextValueFor(Func):
|
|
|
|
arg_types = {"this": True, "order": False}
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
def _norm_arg(arg):
|
2025-02-13 15:48:10 +01:00
|
|
|
return arg.lower() if type(arg) is str else arg
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 14:40:43 +01:00
|
|
|
ALL_FUNCTIONS = subclasses(__name__, Func, (AggFunc, Anonymous, Func))
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
# Helpers
|
2025-02-13 15:52:09 +01:00
|
|
|
@t.overload
|
|
|
|
def maybe_parse(
|
|
|
|
sql_or_expression: ExpOrStr,
|
|
|
|
*,
|
|
|
|
into: t.Type[E],
|
|
|
|
dialect: DialectType = None,
|
|
|
|
prefix: t.Optional[str] = None,
|
|
|
|
copy: bool = False,
|
|
|
|
**opts,
|
|
|
|
) -> E:
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
@t.overload
|
|
|
|
def maybe_parse(
|
|
|
|
sql_or_expression: str | E,
|
|
|
|
*,
|
|
|
|
into: t.Optional[IntoType] = None,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
prefix: t.Optional[str] = None,
|
|
|
|
copy: bool = False,
|
|
|
|
**opts,
|
|
|
|
) -> E:
|
|
|
|
...
|
|
|
|
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
def maybe_parse(
|
2025-02-13 15:46:19 +01:00
|
|
|
sql_or_expression: ExpOrStr,
|
2025-02-13 06:15:54 +01:00
|
|
|
*,
|
2025-02-13 15:07:05 +01:00
|
|
|
into: t.Optional[IntoType] = None,
|
2025-02-13 15:09:58 +01:00
|
|
|
dialect: DialectType = None,
|
2025-02-13 15:07:05 +01:00
|
|
|
prefix: t.Optional[str] = None,
|
2025-02-13 15:26:26 +01:00
|
|
|
copy: bool = False,
|
2025-02-13 06:15:54 +01:00
|
|
|
**opts,
|
2025-02-13 15:03:38 +01:00
|
|
|
) -> Expression:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""Gracefully handle a possible string or expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> maybe_parse("1")
|
|
|
|
(LITERAL this: 1, is_string: False)
|
|
|
|
>>> maybe_parse(to_identifier("x"))
|
|
|
|
(IDENTIFIER this: x, quoted: False)
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:07:05 +01:00
|
|
|
sql_or_expression: the SQL code string or an expression
|
|
|
|
into: the SQLGlot Expression to parse into
|
|
|
|
dialect: the dialect used to parse the input expressions (in the case that an
|
2025-02-13 06:15:54 +01:00
|
|
|
input expression is a SQL string).
|
2025-02-13 15:07:05 +01:00
|
|
|
prefix: a string to prefix the sql with before it gets parsed
|
2025-02-13 06:15:54 +01:00
|
|
|
(automatically includes a space)
|
2025-02-13 15:26:26 +01:00
|
|
|
copy: whether or not to copy the expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
**opts: other options to use to parse the input expressions (again, in the case
|
|
|
|
that an input expression is a SQL string).
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Expression: the parsed or given expression.
|
|
|
|
"""
|
|
|
|
if isinstance(sql_or_expression, Expression):
|
2025-02-13 15:26:26 +01:00
|
|
|
if copy:
|
|
|
|
return sql_or_expression.copy()
|
2025-02-13 06:15:54 +01:00
|
|
|
return sql_or_expression
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
if sql_or_expression is None:
|
|
|
|
raise ParseError(f"SQL cannot be None")
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
import sqlglot
|
|
|
|
|
|
|
|
sql = str(sql_or_expression)
|
|
|
|
if prefix:
|
|
|
|
sql = f"{prefix} {sql}"
|
2025-02-13 15:57:23 +01:00
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
return sqlglot.parse_one(sql, read=dialect, into=into, **opts)
|
|
|
|
|
|
|
|
|
2025-02-13 20:51:40 +01:00
|
|
|
@t.overload
|
|
|
|
def maybe_copy(instance: None, copy: bool = True) -> None:
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
@t.overload
|
2025-02-13 20:48:36 +01:00
|
|
|
def maybe_copy(instance: E, copy: bool = True) -> E:
|
2025-02-13 20:51:40 +01:00
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
def maybe_copy(instance, copy=True):
|
|
|
|
return instance.copy() if copy and instance else instance
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
def _is_wrong_expression(expression, into):
|
|
|
|
return isinstance(expression, Expression) and not isinstance(expression, into)
|
|
|
|
|
|
|
|
|
|
|
|
def _apply_builder(
|
|
|
|
expression,
|
|
|
|
instance,
|
|
|
|
arg,
|
|
|
|
copy=True,
|
|
|
|
prefix=None,
|
|
|
|
into=None,
|
|
|
|
dialect=None,
|
2025-02-13 21:03:38 +01:00
|
|
|
into_arg="this",
|
2025-02-13 06:15:54 +01:00
|
|
|
**opts,
|
|
|
|
):
|
|
|
|
if _is_wrong_expression(expression, into):
|
2025-02-13 21:03:38 +01:00
|
|
|
expression = into(**{into_arg: expression})
|
2025-02-13 20:48:36 +01:00
|
|
|
instance = maybe_copy(instance, copy)
|
2025-02-13 06:15:54 +01:00
|
|
|
expression = maybe_parse(
|
|
|
|
sql_or_expression=expression,
|
|
|
|
prefix=prefix,
|
|
|
|
into=into,
|
|
|
|
dialect=dialect,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
instance.set(arg, expression)
|
|
|
|
return instance
|
|
|
|
|
|
|
|
|
|
|
|
def _apply_child_list_builder(
|
|
|
|
*expressions,
|
|
|
|
instance,
|
|
|
|
arg,
|
|
|
|
append=True,
|
|
|
|
copy=True,
|
|
|
|
prefix=None,
|
|
|
|
into=None,
|
|
|
|
dialect=None,
|
|
|
|
properties=None,
|
|
|
|
**opts,
|
|
|
|
):
|
2025-02-13 20:48:36 +01:00
|
|
|
instance = maybe_copy(instance, copy)
|
2025-02-13 06:15:54 +01:00
|
|
|
parsed = []
|
|
|
|
for expression in expressions:
|
2025-02-13 15:57:23 +01:00
|
|
|
if expression is not None:
|
|
|
|
if _is_wrong_expression(expression, into):
|
|
|
|
expression = into(expressions=[expression])
|
|
|
|
|
|
|
|
expression = maybe_parse(
|
|
|
|
expression,
|
|
|
|
into=into,
|
|
|
|
dialect=dialect,
|
|
|
|
prefix=prefix,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
parsed.extend(expression.expressions)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
existing = instance.args.get(arg)
|
|
|
|
if append and existing:
|
|
|
|
parsed = existing.expressions + parsed
|
|
|
|
|
|
|
|
child = into(expressions=parsed)
|
|
|
|
for k, v in (properties or {}).items():
|
|
|
|
child.set(k, v)
|
|
|
|
instance.set(arg, child)
|
2025-02-13 15:57:23 +01:00
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
return instance
|
|
|
|
|
|
|
|
|
|
|
|
def _apply_list_builder(
|
|
|
|
*expressions,
|
|
|
|
instance,
|
|
|
|
arg,
|
|
|
|
append=True,
|
|
|
|
copy=True,
|
|
|
|
prefix=None,
|
|
|
|
into=None,
|
|
|
|
dialect=None,
|
|
|
|
**opts,
|
|
|
|
):
|
2025-02-13 20:48:36 +01:00
|
|
|
inst = maybe_copy(instance, copy)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
expressions = [
|
|
|
|
maybe_parse(
|
|
|
|
sql_or_expression=expression,
|
|
|
|
into=into,
|
|
|
|
prefix=prefix,
|
|
|
|
dialect=dialect,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
for expression in expressions
|
2025-02-13 15:57:23 +01:00
|
|
|
if expression is not None
|
2025-02-13 06:15:54 +01:00
|
|
|
]
|
|
|
|
|
|
|
|
existing_expressions = inst.args.get(arg)
|
|
|
|
if append and existing_expressions:
|
|
|
|
expressions = existing_expressions + expressions
|
|
|
|
|
|
|
|
inst.set(arg, expressions)
|
|
|
|
return inst
|
|
|
|
|
|
|
|
|
|
|
|
def _apply_conjunction_builder(
|
|
|
|
*expressions,
|
|
|
|
instance,
|
|
|
|
arg,
|
|
|
|
into=None,
|
|
|
|
append=True,
|
|
|
|
copy=True,
|
|
|
|
dialect=None,
|
|
|
|
**opts,
|
|
|
|
):
|
|
|
|
expressions = [exp for exp in expressions if exp is not None and exp != ""]
|
|
|
|
if not expressions:
|
|
|
|
return instance
|
|
|
|
|
2025-02-13 20:48:36 +01:00
|
|
|
inst = maybe_copy(instance, copy)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
existing = inst.args.get(arg)
|
|
|
|
if append and existing is not None:
|
|
|
|
expressions = [existing.this if into else existing] + list(expressions)
|
|
|
|
|
2025-02-13 15:53:39 +01:00
|
|
|
node = and_(*expressions, dialect=dialect, copy=copy, **opts)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
inst.set(arg, into(this=node) if into else node)
|
|
|
|
return inst
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def _apply_cte_builder(
|
|
|
|
instance: E,
|
|
|
|
alias: ExpOrStr,
|
|
|
|
as_: ExpOrStr,
|
|
|
|
recursive: t.Optional[bool] = None,
|
|
|
|
append: bool = True,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> E:
|
|
|
|
alias_expression = maybe_parse(alias, dialect=dialect, into=TableAlias, **opts)
|
|
|
|
as_expression = maybe_parse(as_, dialect=dialect, **opts)
|
|
|
|
cte = CTE(this=as_expression, alias=alias_expression)
|
|
|
|
return _apply_child_list_builder(
|
|
|
|
cte,
|
|
|
|
instance=instance,
|
|
|
|
arg="with",
|
|
|
|
append=append,
|
|
|
|
copy=copy,
|
|
|
|
into=With,
|
|
|
|
properties={"recursive": recursive or False},
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def _combine(
|
|
|
|
expressions: t.Sequence[t.Optional[ExpOrStr]],
|
|
|
|
operator: t.Type[Connector],
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Expression:
|
|
|
|
conditions = [
|
|
|
|
condition(expression, dialect=dialect, copy=copy, **opts)
|
|
|
|
for expression in expressions
|
|
|
|
if expression is not None
|
2025-02-13 15:53:39 +01:00
|
|
|
]
|
2025-02-13 15:57:23 +01:00
|
|
|
|
|
|
|
this, *rest = conditions
|
|
|
|
if rest:
|
2025-02-13 15:52:09 +01:00
|
|
|
this = _wrap(this, Connector)
|
2025-02-13 15:57:23 +01:00
|
|
|
for expression in rest:
|
2025-02-13 15:52:09 +01:00
|
|
|
this = operator(this=this, expression=_wrap(expression, Connector))
|
2025-02-13 15:57:23 +01:00
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
return this
|
|
|
|
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
def _wrap(expression: E, kind: t.Type[Expression]) -> E | Paren:
|
2025-02-13 15:57:23 +01:00
|
|
|
return Paren(this=expression) if isinstance(expression, kind) else expression
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def union(
|
|
|
|
left: ExpOrStr, right: ExpOrStr, distinct: bool = True, dialect: DialectType = None, **opts
|
|
|
|
) -> Union:
|
2025-02-13 14:45:11 +01:00
|
|
|
"""
|
|
|
|
Initializes a syntax tree from one UNION expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> union("SELECT * FROM foo", "SELECT * FROM bla").sql()
|
|
|
|
'SELECT * FROM foo UNION SELECT * FROM bla'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
left: the SQL code string corresponding to the left-hand side.
|
2025-02-13 14:45:11 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
right: the SQL code string corresponding to the right-hand side.
|
2025-02-13 14:45:11 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
distinct: set the DISTINCT flag if and only if this is true.
|
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
opts: other options to use to parse the input expressions.
|
|
|
|
|
2025-02-13 14:45:11 +01:00
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The new Union instance.
|
2025-02-13 14:45:11 +01:00
|
|
|
"""
|
|
|
|
left = maybe_parse(sql_or_expression=left, dialect=dialect, **opts)
|
|
|
|
right = maybe_parse(sql_or_expression=right, dialect=dialect, **opts)
|
|
|
|
|
|
|
|
return Union(this=left, expression=right, distinct=distinct)
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def intersect(
|
|
|
|
left: ExpOrStr, right: ExpOrStr, distinct: bool = True, dialect: DialectType = None, **opts
|
|
|
|
) -> Intersect:
|
2025-02-13 14:45:11 +01:00
|
|
|
"""
|
|
|
|
Initializes a syntax tree from one INTERSECT expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> intersect("SELECT * FROM foo", "SELECT * FROM bla").sql()
|
|
|
|
'SELECT * FROM foo INTERSECT SELECT * FROM bla'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
left: the SQL code string corresponding to the left-hand side.
|
2025-02-13 14:45:11 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
right: the SQL code string corresponding to the right-hand side.
|
2025-02-13 14:45:11 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
distinct: set the DISTINCT flag if and only if this is true.
|
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
opts: other options to use to parse the input expressions.
|
|
|
|
|
2025-02-13 14:45:11 +01:00
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The new Intersect instance.
|
2025-02-13 14:45:11 +01:00
|
|
|
"""
|
|
|
|
left = maybe_parse(sql_or_expression=left, dialect=dialect, **opts)
|
|
|
|
right = maybe_parse(sql_or_expression=right, dialect=dialect, **opts)
|
|
|
|
|
|
|
|
return Intersect(this=left, expression=right, distinct=distinct)
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def except_(
|
|
|
|
left: ExpOrStr, right: ExpOrStr, distinct: bool = True, dialect: DialectType = None, **opts
|
|
|
|
) -> Except:
|
2025-02-13 14:45:11 +01:00
|
|
|
"""
|
|
|
|
Initializes a syntax tree from one EXCEPT expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> except_("SELECT * FROM foo", "SELECT * FROM bla").sql()
|
|
|
|
'SELECT * FROM foo EXCEPT SELECT * FROM bla'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
left: the SQL code string corresponding to the left-hand side.
|
2025-02-13 14:45:11 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
right: the SQL code string corresponding to the right-hand side.
|
2025-02-13 14:45:11 +01:00
|
|
|
If an `Expression` instance is passed, it will be used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
distinct: set the DISTINCT flag if and only if this is true.
|
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
opts: other options to use to parse the input expressions.
|
|
|
|
|
2025-02-13 14:45:11 +01:00
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The new Except instance.
|
2025-02-13 14:45:11 +01:00
|
|
|
"""
|
|
|
|
left = maybe_parse(sql_or_expression=left, dialect=dialect, **opts)
|
|
|
|
right = maybe_parse(sql_or_expression=right, dialect=dialect, **opts)
|
|
|
|
|
|
|
|
return Except(this=left, expression=right, distinct=distinct)
|
|
|
|
|
|
|
|
|
2025-02-13 15:46:19 +01:00
|
|
|
def select(*expressions: ExpOrStr, dialect: DialectType = None, **opts) -> Select:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Initializes a syntax tree from one or multiple SELECT expressions.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> select("col1", "col2").from_("tbl").sql()
|
|
|
|
'SELECT col1, col2 FROM tbl'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:26:26 +01:00
|
|
|
*expressions: the SQL code string to parse as the expressions of a
|
2025-02-13 06:15:54 +01:00
|
|
|
SELECT statement. If an Expression instance is passed, this is used as-is.
|
2025-02-13 15:26:26 +01:00
|
|
|
dialect: the dialect used to parse the input expressions (in the case that an
|
2025-02-13 06:15:54 +01:00
|
|
|
input expression is a SQL string).
|
|
|
|
**opts: other options to use to parse the input expressions (again, in the case
|
|
|
|
that an input expression is a SQL string).
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Select: the syntax tree for the SELECT statement.
|
|
|
|
"""
|
|
|
|
return Select().select(*expressions, dialect=dialect, **opts)
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def from_(expression: ExpOrStr, dialect: DialectType = None, **opts) -> Select:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Initializes a syntax tree from a FROM expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> from_("tbl").select("col1", "col2").sql()
|
|
|
|
'SELECT col1, col2 FROM tbl'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
*expression: the SQL code string to parse as the FROM expressions of a
|
2025-02-13 06:15:54 +01:00
|
|
|
SELECT statement. If an Expression instance is passed, this is used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expression (in the case that the
|
2025-02-13 06:15:54 +01:00
|
|
|
input expression is a SQL string).
|
|
|
|
**opts: other options to use to parse the input expressions (again, in the case
|
|
|
|
that the input expression is a SQL string).
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Select: the syntax tree for the SELECT statement.
|
|
|
|
"""
|
2025-02-13 15:57:23 +01:00
|
|
|
return Select().from_(expression, dialect=dialect, **opts)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:46:19 +01:00
|
|
|
def update(
|
|
|
|
table: str | Table,
|
|
|
|
properties: dict,
|
|
|
|
where: t.Optional[ExpOrStr] = None,
|
|
|
|
from_: t.Optional[ExpOrStr] = None,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
**opts,
|
|
|
|
) -> Update:
|
2025-02-13 14:40:43 +01:00
|
|
|
"""
|
|
|
|
Creates an update statement.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> update("my_table", {"x": 1, "y": "2", "z": None}, from_="baz", where="id > 1").sql()
|
|
|
|
"UPDATE my_table SET x = 1, y = '2', z = NULL FROM baz WHERE id > 1"
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:46:19 +01:00
|
|
|
*properties: dictionary of properties to set which are
|
2025-02-13 14:40:43 +01:00
|
|
|
auto converted to sql objects eg None -> NULL
|
2025-02-13 15:46:19 +01:00
|
|
|
where: sql conditional parsed into a WHERE statement
|
|
|
|
from_: sql statement parsed into a FROM statement
|
|
|
|
dialect: the dialect used to parse the input expressions.
|
2025-02-13 14:40:43 +01:00
|
|
|
**opts: other options to use to parse the input expressions.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Update: the syntax tree for the UPDATE statement.
|
|
|
|
"""
|
2025-02-13 15:46:19 +01:00
|
|
|
update_expr = Update(this=maybe_parse(table, into=Table, dialect=dialect))
|
|
|
|
update_expr.set(
|
2025-02-13 14:40:43 +01:00
|
|
|
"expressions",
|
2025-02-13 14:53:05 +01:00
|
|
|
[
|
|
|
|
EQ(this=maybe_parse(k, dialect=dialect, **opts), expression=convert(v))
|
|
|
|
for k, v in properties.items()
|
|
|
|
],
|
2025-02-13 14:40:43 +01:00
|
|
|
)
|
|
|
|
if from_:
|
2025-02-13 15:46:19 +01:00
|
|
|
update_expr.set(
|
2025-02-13 15:01:55 +01:00
|
|
|
"from",
|
|
|
|
maybe_parse(from_, into=From, dialect=dialect, prefix="FROM", **opts),
|
|
|
|
)
|
2025-02-13 14:50:31 +01:00
|
|
|
if isinstance(where, Condition):
|
|
|
|
where = Where(this=where)
|
2025-02-13 14:40:43 +01:00
|
|
|
if where:
|
2025-02-13 15:46:19 +01:00
|
|
|
update_expr.set(
|
2025-02-13 15:01:55 +01:00
|
|
|
"where",
|
|
|
|
maybe_parse(where, into=Where, dialect=dialect, prefix="WHERE", **opts),
|
|
|
|
)
|
2025-02-13 15:46:19 +01:00
|
|
|
return update_expr
|
2025-02-13 14:40:43 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:46:19 +01:00
|
|
|
def delete(
|
|
|
|
table: ExpOrStr,
|
|
|
|
where: t.Optional[ExpOrStr] = None,
|
|
|
|
returning: t.Optional[ExpOrStr] = None,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
**opts,
|
|
|
|
) -> Delete:
|
2025-02-13 14:46:58 +01:00
|
|
|
"""
|
|
|
|
Builds a delete statement.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> delete("my_table", where="id > 1").sql()
|
|
|
|
'DELETE FROM my_table WHERE id > 1'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:46:19 +01:00
|
|
|
where: sql conditional parsed into a WHERE statement
|
|
|
|
returning: sql conditional parsed into a RETURNING statement
|
|
|
|
dialect: the dialect used to parse the input expressions.
|
2025-02-13 14:46:58 +01:00
|
|
|
**opts: other options to use to parse the input expressions.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Delete: the syntax tree for the DELETE statement.
|
|
|
|
"""
|
2025-02-13 15:46:19 +01:00
|
|
|
delete_expr = Delete().delete(table, dialect=dialect, copy=False, **opts)
|
|
|
|
if where:
|
|
|
|
delete_expr = delete_expr.where(where, dialect=dialect, copy=False, **opts)
|
|
|
|
if returning:
|
|
|
|
delete_expr = delete_expr.returning(returning, dialect=dialect, copy=False, **opts)
|
|
|
|
return delete_expr
|
2025-02-13 14:46:58 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def insert(
|
|
|
|
expression: ExpOrStr,
|
|
|
|
into: ExpOrStr,
|
|
|
|
columns: t.Optional[t.Sequence[ExpOrStr]] = None,
|
|
|
|
overwrite: t.Optional[bool] = None,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
copy: bool = True,
|
|
|
|
**opts,
|
|
|
|
) -> Insert:
|
|
|
|
"""
|
|
|
|
Builds an INSERT statement.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> insert("VALUES (1, 2, 3)", "tbl").sql()
|
|
|
|
'INSERT INTO tbl VALUES (1, 2, 3)'
|
|
|
|
|
|
|
|
Args:
|
|
|
|
expression: the sql string or expression of the INSERT statement
|
|
|
|
into: the tbl to insert data to.
|
|
|
|
columns: optionally the table's column names.
|
|
|
|
overwrite: whether to INSERT OVERWRITE or not.
|
|
|
|
dialect: the dialect used to parse the input expressions.
|
|
|
|
copy: whether or not to copy the expression.
|
|
|
|
**opts: other options to use to parse the input expressions.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Insert: the syntax tree for the INSERT statement.
|
|
|
|
"""
|
|
|
|
expr = maybe_parse(expression, dialect=dialect, copy=copy, **opts)
|
|
|
|
this: Table | Schema = maybe_parse(into, into=Table, dialect=dialect, copy=copy, **opts)
|
|
|
|
|
|
|
|
if columns:
|
|
|
|
this = _apply_list_builder(
|
|
|
|
*columns,
|
|
|
|
instance=Schema(this=this),
|
|
|
|
arg="expressions",
|
|
|
|
into=Identifier,
|
|
|
|
copy=False,
|
|
|
|
dialect=dialect,
|
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
|
|
|
return Insert(this=this, expression=expr, overwrite=overwrite)
|
|
|
|
|
|
|
|
|
|
|
|
def condition(
|
|
|
|
expression: ExpOrStr, dialect: DialectType = None, copy: bool = True, **opts
|
|
|
|
) -> Condition:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Initialize a logical condition expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> condition("x=1").sql()
|
|
|
|
'x = 1'
|
|
|
|
|
|
|
|
This is helpful for composing larger logical syntax trees:
|
|
|
|
>>> where = condition("x=1")
|
|
|
|
>>> where = where.and_("y=1")
|
|
|
|
>>> Select().from_("tbl").select("*").where(where).sql()
|
|
|
|
'SELECT * FROM tbl WHERE x = 1 AND y = 1'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
*expression: the SQL code string to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an Expression instance is passed, this is used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expression (in the case that the
|
2025-02-13 06:15:54 +01:00
|
|
|
input expression is a SQL string).
|
2025-02-13 15:57:23 +01:00
|
|
|
copy: Whether or not to copy `expression` (only applies to expressions).
|
2025-02-13 06:15:54 +01:00
|
|
|
**opts: other options to use to parse the input expressions (again, in the case
|
|
|
|
that the input expression is a SQL string).
|
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The new Condition instance
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
2025-02-13 15:57:23 +01:00
|
|
|
return maybe_parse(
|
2025-02-13 06:15:54 +01:00
|
|
|
expression,
|
|
|
|
into=Condition,
|
|
|
|
dialect=dialect,
|
2025-02-13 15:53:39 +01:00
|
|
|
copy=copy,
|
2025-02-13 06:15:54 +01:00
|
|
|
**opts,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def and_(
|
|
|
|
*expressions: t.Optional[ExpOrStr], dialect: DialectType = None, copy: bool = True, **opts
|
|
|
|
) -> Condition:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Combine multiple conditions with an AND logical operator.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> and_("x=1", and_("y=1", "z=1")).sql()
|
|
|
|
'x = 1 AND (y = 1 AND z = 1)'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
*expressions: the SQL code strings to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an Expression instance is passed, this is used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
copy: whether or not to copy `expressions` (only applies to Expressions).
|
2025-02-13 06:15:54 +01:00
|
|
|
**opts: other options to use to parse the input expressions.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
And: the new condition
|
|
|
|
"""
|
2025-02-13 15:57:23 +01:00
|
|
|
return t.cast(Condition, _combine(expressions, And, dialect, copy=copy, **opts))
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def or_(
|
|
|
|
*expressions: t.Optional[ExpOrStr], dialect: DialectType = None, copy: bool = True, **opts
|
|
|
|
) -> Condition:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Combine multiple conditions with an OR logical operator.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> or_("x=1", or_("y=1", "z=1")).sql()
|
|
|
|
'x = 1 OR (y = 1 OR z = 1)'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
*expressions: the SQL code strings to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an Expression instance is passed, this is used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
copy: whether or not to copy `expressions` (only applies to Expressions).
|
2025-02-13 06:15:54 +01:00
|
|
|
**opts: other options to use to parse the input expressions.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Or: the new condition
|
|
|
|
"""
|
2025-02-13 15:57:23 +01:00
|
|
|
return t.cast(Condition, _combine(expressions, Or, dialect, copy=copy, **opts))
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def not_(expression: ExpOrStr, dialect: DialectType = None, copy: bool = True, **opts) -> Not:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Wrap a condition with a NOT operator.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> not_("this_suit='black'").sql()
|
|
|
|
"NOT this_suit = 'black'"
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
expression: the SQL code string to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an Expression instance is passed, this is used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the dialect used to parse the input expression.
|
|
|
|
copy: whether to copy the expression or not.
|
2025-02-13 06:15:54 +01:00
|
|
|
**opts: other options to use to parse the input expressions.
|
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The new condition.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
this = condition(
|
|
|
|
expression,
|
|
|
|
dialect=dialect,
|
2025-02-13 15:53:39 +01:00
|
|
|
copy=copy,
|
2025-02-13 06:15:54 +01:00
|
|
|
**opts,
|
|
|
|
)
|
2025-02-13 15:52:09 +01:00
|
|
|
return Not(this=_wrap(this, Connector))
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def paren(expression: ExpOrStr, copy: bool = True) -> Paren:
|
|
|
|
"""
|
|
|
|
Wrap an expression in parentheses.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> paren("5 + 3").sql()
|
|
|
|
'(5 + 3)'
|
|
|
|
|
|
|
|
Args:
|
|
|
|
expression: the SQL code string to parse.
|
|
|
|
If an Expression instance is passed, this is used as-is.
|
|
|
|
copy: whether to copy the expression or not.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The wrapped expression.
|
|
|
|
"""
|
|
|
|
return Paren(this=maybe_parse(expression, copy=copy))
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:01:55 +01:00
|
|
|
SAFE_IDENTIFIER_RE = re.compile(r"^[_a-zA-Z][\w]*$")
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:23:26 +01:00
|
|
|
@t.overload
|
2025-02-13 15:57:23 +01:00
|
|
|
def to_identifier(name: None, quoted: t.Optional[bool] = None, copy: bool = True) -> None:
|
2025-02-13 15:23:26 +01:00
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
@t.overload
|
2025-02-13 15:57:23 +01:00
|
|
|
def to_identifier(
|
|
|
|
name: str | Identifier, quoted: t.Optional[bool] = None, copy: bool = True
|
|
|
|
) -> Identifier:
|
2025-02-13 15:23:26 +01:00
|
|
|
...
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def to_identifier(name, quoted=None, copy=True):
|
2025-02-13 15:23:26 +01:00
|
|
|
"""Builds an identifier.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name: The name to turn into an identifier.
|
|
|
|
quoted: Whether or not force quote the identifier.
|
2025-02-13 15:57:23 +01:00
|
|
|
copy: Whether or not to copy a passed in Identefier node.
|
2025-02-13 15:23:26 +01:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
The identifier ast node.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if name is None:
|
2025-02-13 06:15:54 +01:00
|
|
|
return None
|
2025-02-13 15:23:26 +01:00
|
|
|
|
|
|
|
if isinstance(name, Identifier):
|
2025-02-13 20:48:36 +01:00
|
|
|
identifier = maybe_copy(name, copy)
|
2025-02-13 15:23:26 +01:00
|
|
|
elif isinstance(name, str):
|
|
|
|
identifier = Identifier(
|
|
|
|
this=name,
|
2025-02-13 15:48:10 +01:00
|
|
|
quoted=not SAFE_IDENTIFIER_RE.match(name) if quoted is None else quoted,
|
2025-02-13 15:23:26 +01:00
|
|
|
)
|
2025-02-13 06:15:54 +01:00
|
|
|
else:
|
2025-02-13 15:23:26 +01:00
|
|
|
raise ValueError(f"Name needs to be a string or an Identifier, got: {name.__class__}")
|
2025-02-13 06:15:54 +01:00
|
|
|
return identifier
|
|
|
|
|
|
|
|
|
2025-02-13 15:09:58 +01:00
|
|
|
INTERVAL_STRING_RE = re.compile(r"\s*([0-9]+)\s*([a-zA-Z]+)\s*")
|
|
|
|
|
|
|
|
|
|
|
|
def to_interval(interval: str | Literal) -> Interval:
|
|
|
|
"""Builds an interval expression from a string like '1 day' or '5 months'."""
|
|
|
|
if isinstance(interval, Literal):
|
|
|
|
if not interval.is_string:
|
|
|
|
raise ValueError("Invalid interval string.")
|
|
|
|
|
|
|
|
interval = interval.this
|
|
|
|
|
|
|
|
interval_parts = INTERVAL_STRING_RE.match(interval) # type: ignore
|
|
|
|
|
|
|
|
if not interval_parts:
|
|
|
|
raise ValueError("Invalid interval string.")
|
|
|
|
|
|
|
|
return Interval(
|
|
|
|
this=Literal.string(interval_parts.group(1)),
|
|
|
|
unit=Var(this=interval_parts.group(2)),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2025-02-13 15:05:06 +01:00
|
|
|
@t.overload
|
|
|
|
def to_table(sql_path: str | Table, **kwargs) -> Table:
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
@t.overload
|
|
|
|
def to_table(sql_path: None, **kwargs) -> None:
|
|
|
|
...
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def to_table(
|
|
|
|
sql_path: t.Optional[str | Table], dialect: DialectType = None, **kwargs
|
|
|
|
) -> t.Optional[Table]:
|
2025-02-13 14:46:58 +01:00
|
|
|
"""
|
|
|
|
Create a table expression from a `[catalog].[schema].[table]` sql path. Catalog and schema are optional.
|
2025-02-13 14:48:46 +01:00
|
|
|
If a table is passed in then that table is returned.
|
2025-02-13 14:46:58 +01:00
|
|
|
|
|
|
|
Args:
|
2025-02-13 14:53:05 +01:00
|
|
|
sql_path: a `[catalog].[schema].[table]` string.
|
2025-02-13 15:57:23 +01:00
|
|
|
dialect: the source dialect according to which the table name will be parsed.
|
|
|
|
kwargs: the kwargs to instantiate the resulting `Table` expression with.
|
2025-02-13 14:53:05 +01:00
|
|
|
|
2025-02-13 14:46:58 +01:00
|
|
|
Returns:
|
2025-02-13 14:53:05 +01:00
|
|
|
A table expression.
|
2025-02-13 14:46:58 +01:00
|
|
|
"""
|
2025-02-13 14:48:46 +01:00
|
|
|
if sql_path is None or isinstance(sql_path, Table):
|
|
|
|
return sql_path
|
|
|
|
if not isinstance(sql_path, str):
|
|
|
|
raise ValueError(f"Invalid type provided for a table: {type(sql_path)}")
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
table = maybe_parse(sql_path, into=Table, dialect=dialect)
|
|
|
|
if table:
|
|
|
|
for k, v in kwargs.items():
|
|
|
|
table.set(k, v)
|
|
|
|
|
|
|
|
return table
|
2025-02-13 14:46:58 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:03:38 +01:00
|
|
|
def to_column(sql_path: str | Column, **kwargs) -> Column:
|
2025-02-13 14:48:46 +01:00
|
|
|
"""
|
|
|
|
Create a column from a `[table].[column]` sql path. Schema is optional.
|
|
|
|
|
|
|
|
If a column is passed in then that column is returned.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
sql_path: `[table].[column]` string
|
|
|
|
Returns:
|
|
|
|
Table: A column expression
|
|
|
|
"""
|
|
|
|
if sql_path is None or isinstance(sql_path, Column):
|
|
|
|
return sql_path
|
|
|
|
if not isinstance(sql_path, str):
|
|
|
|
raise ValueError(f"Invalid type provided for column: {type(sql_path)}")
|
2025-02-13 15:48:10 +01:00
|
|
|
return column(*reversed(sql_path.split(".")), **kwargs) # type: ignore
|
2025-02-13 14:48:46 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:23:26 +01:00
|
|
|
def alias_(
|
2025-02-13 15:46:19 +01:00
|
|
|
expression: ExpOrStr,
|
2025-02-13 15:23:26 +01:00
|
|
|
alias: str | Identifier,
|
|
|
|
table: bool | t.Sequence[str | Identifier] = False,
|
|
|
|
quoted: t.Optional[bool] = None,
|
|
|
|
dialect: DialectType = None,
|
2025-02-13 15:57:23 +01:00
|
|
|
copy: bool = True,
|
2025-02-13 15:23:26 +01:00
|
|
|
**opts,
|
|
|
|
):
|
|
|
|
"""Create an Alias expression.
|
|
|
|
|
2025-02-13 14:48:46 +01:00
|
|
|
Example:
|
2025-02-13 06:15:54 +01:00
|
|
|
>>> alias_('foo', 'bar').sql()
|
|
|
|
'foo AS bar'
|
|
|
|
|
2025-02-13 15:23:26 +01:00
|
|
|
>>> alias_('(select 1, 2)', 'bar', table=['a', 'b']).sql()
|
|
|
|
'(SELECT 1, 2) AS bar(a, b)'
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
Args:
|
2025-02-13 15:23:26 +01:00
|
|
|
expression: the SQL code strings to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an Expression instance is passed, this is used as-is.
|
2025-02-13 15:23:26 +01:00
|
|
|
alias: the alias name to use. If the name has
|
2025-02-13 06:15:54 +01:00
|
|
|
special characters it is quoted.
|
2025-02-13 15:23:26 +01:00
|
|
|
table: Whether or not to create a table alias, can also be a list of columns.
|
|
|
|
quoted: whether or not to quote the alias
|
|
|
|
dialect: the dialect used to parse the input expression.
|
2025-02-13 15:57:23 +01:00
|
|
|
copy: Whether or not to copy the expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
**opts: other options to use to parse the input expressions.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Alias: the aliased expression
|
|
|
|
"""
|
2025-02-13 15:57:23 +01:00
|
|
|
exp = maybe_parse(expression, dialect=dialect, copy=copy, **opts)
|
2025-02-13 06:15:54 +01:00
|
|
|
alias = to_identifier(alias, quoted=quoted)
|
2025-02-13 14:48:46 +01:00
|
|
|
|
|
|
|
if table:
|
2025-02-13 15:23:26 +01:00
|
|
|
table_alias = TableAlias(this=alias)
|
|
|
|
exp.set("alias", table_alias)
|
|
|
|
|
|
|
|
if not isinstance(table, bool):
|
|
|
|
for column in table:
|
|
|
|
table_alias.append("columns", to_identifier(column, quoted=quoted))
|
|
|
|
|
|
|
|
return exp
|
2025-02-13 14:48:46 +01:00
|
|
|
|
|
|
|
# We don't set the "alias" arg for Window expressions, because that would add an IDENTIFIER node in
|
|
|
|
# the AST, representing a "named_window" [1] construct (eg. bigquery). What we want is an ALIAS node
|
|
|
|
# for the complete Window expression.
|
|
|
|
#
|
|
|
|
# [1]: https://cloud.google.com/bigquery/docs/reference/standard-sql/window-function-calls
|
2025-02-13 06:15:54 +01:00
|
|
|
|
2025-02-13 14:45:11 +01:00
|
|
|
if "alias" in exp.arg_types and not isinstance(exp, Window):
|
2025-02-13 06:15:54 +01:00
|
|
|
exp.set("alias", alias)
|
|
|
|
return exp
|
|
|
|
return Alias(this=exp, alias=alias)
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def subquery(
|
|
|
|
expression: ExpOrStr,
|
|
|
|
alias: t.Optional[Identifier | str] = None,
|
|
|
|
dialect: DialectType = None,
|
|
|
|
**opts,
|
|
|
|
) -> Select:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Build a subquery expression.
|
2025-02-13 15:09:58 +01:00
|
|
|
|
|
|
|
Example:
|
2025-02-13 06:15:54 +01:00
|
|
|
>>> subquery('select x from tbl', 'bar').select('x').sql()
|
|
|
|
'SELECT x FROM (SELECT x FROM tbl) AS bar'
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
expression: the SQL code strings to parse.
|
2025-02-13 06:15:54 +01:00
|
|
|
If an Expression instance is passed, this is used as-is.
|
2025-02-13 15:57:23 +01:00
|
|
|
alias: the alias name to use.
|
|
|
|
dialect: the dialect used to parse the input expression.
|
2025-02-13 06:15:54 +01:00
|
|
|
**opts: other options to use to parse the input expressions.
|
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
A new Select instance with the subquery expression included.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
expression = maybe_parse(expression, dialect=dialect, **opts).subquery(alias)
|
|
|
|
return Select().from_(expression, dialect=dialect, **opts)
|
|
|
|
|
|
|
|
|
2025-02-13 15:26:26 +01:00
|
|
|
def column(
|
|
|
|
col: str | Identifier,
|
|
|
|
table: t.Optional[str | Identifier] = None,
|
2025-02-13 15:48:10 +01:00
|
|
|
db: t.Optional[str | Identifier] = None,
|
|
|
|
catalog: t.Optional[str | Identifier] = None,
|
2025-02-13 15:26:26 +01:00
|
|
|
quoted: t.Optional[bool] = None,
|
|
|
|
) -> Column:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Build a Column.
|
2025-02-13 15:09:58 +01:00
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
col: Column name.
|
|
|
|
table: Table name.
|
|
|
|
db: Database name.
|
|
|
|
catalog: Catalog name.
|
|
|
|
quoted: Whether to force quotes on the column's identifiers.
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The new Column instance.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
return Column(
|
|
|
|
this=to_identifier(col, quoted=quoted),
|
|
|
|
table=to_identifier(table, quoted=quoted),
|
2025-02-13 15:48:10 +01:00
|
|
|
db=to_identifier(db, quoted=quoted),
|
|
|
|
catalog=to_identifier(catalog, quoted=quoted),
|
2025-02-13 06:15:54 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2025-02-13 15:46:19 +01:00
|
|
|
def cast(expression: ExpOrStr, to: str | DataType | DataType.Type, **opts) -> Cast:
|
2025-02-13 15:09:58 +01:00
|
|
|
"""Cast an expression to a data type.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> cast('x + 1', 'int').sql()
|
|
|
|
'CAST(x + 1 AS INT)'
|
|
|
|
|
|
|
|
Args:
|
|
|
|
expression: The expression to cast.
|
|
|
|
to: The datatype to cast to.
|
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The new Cast instance.
|
2025-02-13 15:09:58 +01:00
|
|
|
"""
|
|
|
|
expression = maybe_parse(expression, **opts)
|
2025-02-13 21:03:38 +01:00
|
|
|
data_type = DataType.build(to, **opts)
|
|
|
|
expression = Cast(this=expression, to=data_type)
|
|
|
|
expression.type = data_type
|
|
|
|
return expression
|
2025-02-13 15:09:58 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def table_(
|
|
|
|
table: Identifier | str,
|
|
|
|
db: t.Optional[Identifier | str] = None,
|
|
|
|
catalog: t.Optional[Identifier | str] = None,
|
|
|
|
quoted: t.Optional[bool] = None,
|
|
|
|
alias: t.Optional[Identifier | str] = None,
|
|
|
|
) -> Table:
|
2025-02-13 14:40:43 +01:00
|
|
|
"""Build a Table.
|
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
table: Table name.
|
|
|
|
db: Database name.
|
|
|
|
catalog: Catalog name.
|
|
|
|
quote: Whether to force quotes on the table's identifiers.
|
|
|
|
alias: Table's alias.
|
2025-02-13 14:40:43 +01:00
|
|
|
|
2025-02-13 06:15:54 +01:00
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
The new Table instance.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
return Table(
|
2025-02-13 20:58:22 +01:00
|
|
|
this=to_identifier(table, quoted=quoted) if table else None,
|
|
|
|
db=to_identifier(db, quoted=quoted) if db else None,
|
|
|
|
catalog=to_identifier(catalog, quoted=quoted) if catalog else None,
|
2025-02-13 14:48:46 +01:00
|
|
|
alias=TableAlias(this=to_identifier(alias)) if alias else None,
|
2025-02-13 06:15:54 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2025-02-13 15:00:13 +01:00
|
|
|
def values(
|
|
|
|
values: t.Iterable[t.Tuple[t.Any, ...]],
|
|
|
|
alias: t.Optional[str] = None,
|
2025-02-13 15:03:38 +01:00
|
|
|
columns: t.Optional[t.Iterable[str] | t.Dict[str, DataType]] = None,
|
2025-02-13 15:00:13 +01:00
|
|
|
) -> Values:
|
2025-02-13 14:46:58 +01:00
|
|
|
"""Build VALUES statement.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> values([(1, '2')]).sql()
|
|
|
|
"VALUES (1, '2')"
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:00:13 +01:00
|
|
|
values: values statements that will be converted to SQL
|
|
|
|
alias: optional alias
|
2025-02-13 15:03:38 +01:00
|
|
|
columns: Optional list of ordered column names or ordered dictionary of column names to types.
|
|
|
|
If either are provided then an alias is also required.
|
2025-02-13 14:46:58 +01:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
Values: the Values expression object
|
|
|
|
"""
|
2025-02-13 15:00:13 +01:00
|
|
|
if columns and not alias:
|
|
|
|
raise ValueError("Alias is required when providing columns")
|
2025-02-13 15:53:39 +01:00
|
|
|
|
2025-02-13 14:46:58 +01:00
|
|
|
return Values(
|
2025-02-13 15:53:39 +01:00
|
|
|
expressions=[convert(tup) for tup in values],
|
|
|
|
alias=(
|
|
|
|
TableAlias(this=to_identifier(alias), columns=[to_identifier(x) for x in columns])
|
|
|
|
if columns
|
|
|
|
else (TableAlias(this=to_identifier(alias)) if alias else None)
|
|
|
|
),
|
2025-02-13 14:46:58 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2025-02-13 15:46:19 +01:00
|
|
|
def var(name: t.Optional[ExpOrStr]) -> Var:
|
2025-02-13 15:26:26 +01:00
|
|
|
"""Build a SQL variable.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> repr(var('x'))
|
|
|
|
'(VAR this: x)'
|
|
|
|
|
|
|
|
>>> repr(var(column('x', table='y')))
|
|
|
|
'(VAR this: x)'
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name: The name of the var or an expression who's name will become the var.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The new variable node.
|
|
|
|
"""
|
|
|
|
if not name:
|
2025-02-13 15:46:19 +01:00
|
|
|
raise ValueError("Cannot convert empty name into var.")
|
2025-02-13 15:26:26 +01:00
|
|
|
|
|
|
|
if isinstance(name, Expression):
|
|
|
|
name = name.name
|
|
|
|
return Var(this=name)
|
|
|
|
|
|
|
|
|
2025-02-13 15:05:06 +01:00
|
|
|
def rename_table(old_name: str | Table, new_name: str | Table) -> AlterTable:
|
|
|
|
"""Build ALTER TABLE... RENAME... expression
|
|
|
|
|
|
|
|
Args:
|
|
|
|
old_name: The old name of the table
|
|
|
|
new_name: The new name of the table
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Alter table expression
|
|
|
|
"""
|
|
|
|
old_table = to_table(old_name)
|
|
|
|
new_table = to_table(new_name)
|
|
|
|
return AlterTable(
|
|
|
|
this=old_table,
|
|
|
|
actions=[
|
|
|
|
RenameTable(this=new_table),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2025-02-13 15:53:39 +01:00
|
|
|
def convert(value: t.Any, copy: bool = False) -> Expression:
|
2025-02-13 14:40:43 +01:00
|
|
|
"""Convert a python value into an expression object.
|
|
|
|
|
|
|
|
Raises an error if a conversion is not possible.
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:53:39 +01:00
|
|
|
value: A python object.
|
|
|
|
copy: Whether or not to copy `value` (only applies to Expressions and collections).
|
2025-02-13 14:40:43 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:53:39 +01:00
|
|
|
Expression: the equivalent expression object.
|
2025-02-13 14:40:43 +01:00
|
|
|
"""
|
|
|
|
if isinstance(value, Expression):
|
2025-02-13 20:48:36 +01:00
|
|
|
return maybe_copy(value, copy)
|
2025-02-13 14:40:43 +01:00
|
|
|
if isinstance(value, str):
|
|
|
|
return Literal.string(value)
|
2025-02-13 15:52:09 +01:00
|
|
|
if isinstance(value, bool):
|
|
|
|
return Boolean(this=value)
|
|
|
|
if value is None or (isinstance(value, float) and math.isnan(value)):
|
2025-02-13 15:01:55 +01:00
|
|
|
return NULL
|
2025-02-13 14:40:43 +01:00
|
|
|
if isinstance(value, numbers.Number):
|
|
|
|
return Literal.number(value)
|
2025-02-13 15:52:09 +01:00
|
|
|
if isinstance(value, datetime.datetime):
|
|
|
|
datetime_literal = Literal.string(
|
|
|
|
(value if value.tzinfo else value.replace(tzinfo=datetime.timezone.utc)).isoformat()
|
|
|
|
)
|
|
|
|
return TimeStrToTime(this=datetime_literal)
|
|
|
|
if isinstance(value, datetime.date):
|
|
|
|
date_literal = Literal.string(value.strftime("%Y-%m-%d"))
|
|
|
|
return DateStrToDate(this=date_literal)
|
2025-02-13 14:40:43 +01:00
|
|
|
if isinstance(value, tuple):
|
2025-02-13 15:53:39 +01:00
|
|
|
return Tuple(expressions=[convert(v, copy=copy) for v in value])
|
2025-02-13 14:40:43 +01:00
|
|
|
if isinstance(value, list):
|
2025-02-13 15:53:39 +01:00
|
|
|
return Array(expressions=[convert(v, copy=copy) for v in value])
|
2025-02-13 14:40:43 +01:00
|
|
|
if isinstance(value, dict):
|
|
|
|
return Map(
|
2025-02-13 20:58:22 +01:00
|
|
|
keys=Array(expressions=[convert(k, copy=copy) for k in value]),
|
|
|
|
values=Array(expressions=[convert(v, copy=copy) for v in value.values()]),
|
2025-02-13 14:40:43 +01:00
|
|
|
)
|
|
|
|
raise ValueError(f"Cannot convert {value}")
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def replace_children(expression: Expression, fun: t.Callable, *args, **kwargs) -> None:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Replace children of an expression with the result of a lambda fun(child) -> exp.
|
|
|
|
"""
|
|
|
|
for k, v in expression.args.items():
|
2025-02-13 15:48:10 +01:00
|
|
|
is_list_arg = type(v) is list
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
child_nodes = v if is_list_arg else [v]
|
|
|
|
new_child_nodes = []
|
|
|
|
|
|
|
|
for cn in child_nodes:
|
|
|
|
if isinstance(cn, Expression):
|
2025-02-13 15:46:19 +01:00
|
|
|
for child_node in ensure_collection(fun(cn, *args, **kwargs)):
|
2025-02-13 06:15:54 +01:00
|
|
|
new_child_nodes.append(child_node)
|
|
|
|
child_node.parent = expression
|
|
|
|
child_node.arg_key = k
|
|
|
|
else:
|
|
|
|
new_child_nodes.append(cn)
|
|
|
|
|
2025-02-13 14:53:05 +01:00
|
|
|
expression.args[k] = new_child_nodes if is_list_arg else seq_get(new_child_nodes, 0)
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 16:00:51 +01:00
|
|
|
def column_table_names(expression: Expression, exclude: str = "") -> t.Set[str]:
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
|
|
|
Return all table names referenced through columns in an expression.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
>>> import sqlglot
|
2025-02-13 16:00:51 +01:00
|
|
|
>>> sorted(column_table_names(sqlglot.parse_one("a.b AND c.d AND c.e")))
|
|
|
|
['a', 'c']
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
expression: expression to find table names.
|
2025-02-13 16:00:51 +01:00
|
|
|
exclude: a table name to exclude
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:57:23 +01:00
|
|
|
A list of unique names.
|
2025-02-13 06:15:54 +01:00
|
|
|
"""
|
2025-02-13 16:00:51 +01:00
|
|
|
return {
|
|
|
|
table
|
|
|
|
for table in (column.table for column in expression.find_all(Column))
|
|
|
|
if table and table != exclude
|
|
|
|
}
|
2025-02-13 06:15:54 +01:00
|
|
|
|
|
|
|
|
2025-02-13 20:15:27 +01:00
|
|
|
def table_name(table: Table | str, dialect: DialectType = None) -> str:
|
2025-02-13 14:45:11 +01:00
|
|
|
"""Get the full name of a table as a string.
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 20:15:27 +01:00
|
|
|
table: Table expression node or string.
|
|
|
|
dialect: The dialect to generate the table name for.
|
2025-02-13 14:45:11 +01:00
|
|
|
|
|
|
|
Examples:
|
|
|
|
>>> from sqlglot import exp, parse_one
|
|
|
|
>>> table_name(parse_one("select * from a.b.c").find(exp.Table))
|
|
|
|
'a.b.c'
|
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:07:05 +01:00
|
|
|
The table name.
|
2025-02-13 14:45:11 +01:00
|
|
|
"""
|
|
|
|
|
2025-02-13 21:04:58 +01:00
|
|
|
table = maybe_parse(table, into=Table, dialect=dialect)
|
2025-02-13 14:45:11 +01:00
|
|
|
|
2025-02-13 14:53:05 +01:00
|
|
|
if not table:
|
|
|
|
raise ValueError(f"Cannot parse {table}")
|
|
|
|
|
2025-02-13 20:15:27 +01:00
|
|
|
return ".".join(
|
2025-02-13 20:45:52 +01:00
|
|
|
part.sql(dialect=dialect, identify=True)
|
|
|
|
if not SAFE_IDENTIFIER_RE.match(part.name)
|
|
|
|
else part.name
|
2025-02-13 20:15:27 +01:00
|
|
|
for part in table.parts
|
|
|
|
)
|
2025-02-13 14:45:11 +01:00
|
|
|
|
|
|
|
|
2025-02-13 16:00:51 +01:00
|
|
|
def replace_tables(expression: E, mapping: t.Dict[str, str], copy: bool = True) -> E:
|
2025-02-13 14:45:11 +01:00
|
|
|
"""Replace all tables in expression according to the mapping.
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
expression: expression node to be transformed and replaced.
|
|
|
|
mapping: mapping of table names.
|
2025-02-13 16:00:51 +01:00
|
|
|
copy: whether or not to copy the expression.
|
2025-02-13 14:45:11 +01:00
|
|
|
|
|
|
|
Examples:
|
|
|
|
>>> from sqlglot import exp, parse_one
|
|
|
|
>>> replace_tables(parse_one("select * from a.b"), {"a.b": "c"}).sql()
|
2025-02-13 14:48:46 +01:00
|
|
|
'SELECT * FROM c'
|
2025-02-13 14:45:11 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:07:05 +01:00
|
|
|
The mapped expression.
|
2025-02-13 14:45:11 +01:00
|
|
|
"""
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def _replace_tables(node: Expression) -> Expression:
|
2025-02-13 14:45:11 +01:00
|
|
|
if isinstance(node, Table):
|
|
|
|
new_name = mapping.get(table_name(node))
|
|
|
|
if new_name:
|
2025-02-13 14:48:46 +01:00
|
|
|
return to_table(
|
|
|
|
new_name,
|
|
|
|
**{k: v for k, v in node.args.items() if k not in ("this", "db", "catalog")},
|
|
|
|
)
|
2025-02-13 14:45:11 +01:00
|
|
|
return node
|
|
|
|
|
2025-02-13 16:00:51 +01:00
|
|
|
return expression.transform(_replace_tables, copy=copy)
|
2025-02-13 14:45:11 +01:00
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def replace_placeholders(expression: Expression, *args, **kwargs) -> Expression:
|
2025-02-13 14:50:31 +01:00
|
|
|
"""Replace placeholders in an expression.
|
|
|
|
|
|
|
|
Args:
|
2025-02-13 15:57:23 +01:00
|
|
|
expression: expression node to be transformed and replaced.
|
2025-02-13 15:07:05 +01:00
|
|
|
args: positional names that will substitute unnamed placeholders in the given order.
|
|
|
|
kwargs: keyword arguments that will substitute named placeholders.
|
2025-02-13 14:50:31 +01:00
|
|
|
|
|
|
|
Examples:
|
|
|
|
>>> from sqlglot import exp, parse_one
|
|
|
|
>>> replace_placeholders(
|
2025-02-13 15:50:57 +01:00
|
|
|
... parse_one("select * from :tbl where ? = ?"),
|
|
|
|
... exp.to_identifier("str_col"), "b", tbl=exp.to_identifier("foo")
|
2025-02-13 14:50:31 +01:00
|
|
|
... ).sql()
|
2025-02-13 15:50:57 +01:00
|
|
|
"SELECT * FROM foo WHERE str_col = 'b'"
|
2025-02-13 14:50:31 +01:00
|
|
|
|
|
|
|
Returns:
|
2025-02-13 15:07:05 +01:00
|
|
|
The mapped expression.
|
2025-02-13 14:50:31 +01:00
|
|
|
"""
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def _replace_placeholders(node: Expression, args, **kwargs) -> Expression:
|
2025-02-13 14:50:31 +01:00
|
|
|
if isinstance(node, Placeholder):
|
|
|
|
if node.name:
|
|
|
|
new_name = kwargs.get(node.name)
|
|
|
|
if new_name:
|
2025-02-13 15:50:57 +01:00
|
|
|
return convert(new_name)
|
2025-02-13 14:50:31 +01:00
|
|
|
else:
|
|
|
|
try:
|
2025-02-13 15:50:57 +01:00
|
|
|
return convert(next(args))
|
2025-02-13 14:50:31 +01:00
|
|
|
except StopIteration:
|
|
|
|
pass
|
|
|
|
return node
|
|
|
|
|
|
|
|
return expression.transform(_replace_placeholders, iter(args), **kwargs)
|
|
|
|
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
def expand(
|
|
|
|
expression: Expression, sources: t.Dict[str, Subqueryable], copy: bool = True
|
|
|
|
) -> Expression:
|
2025-02-13 15:07:05 +01:00
|
|
|
"""Transforms an expression by expanding all referenced sources into subqueries.
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
>>> from sqlglot import parse_one
|
|
|
|
>>> expand(parse_one("select * from x AS z"), {"x": parse_one("select * from y")}).sql()
|
|
|
|
'SELECT * FROM (SELECT * FROM y) AS z /* source: x */'
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
>>> expand(parse_one("select * from x AS z"), {"x": parse_one("select * from y"), "y": parse_one("select * from z")}).sql()
|
|
|
|
'SELECT * FROM (SELECT * FROM (SELECT * FROM z) AS y /* source: y */) AS z /* source: x */'
|
|
|
|
|
2025-02-13 15:07:05 +01:00
|
|
|
Args:
|
|
|
|
expression: The expression to expand.
|
|
|
|
sources: A dictionary of name to Subqueryables.
|
|
|
|
copy: Whether or not to copy the expression during transformation. Defaults to True.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The transformed expression.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def _expand(node: Expression):
|
|
|
|
if isinstance(node, Table):
|
|
|
|
name = table_name(node)
|
|
|
|
source = sources.get(name)
|
|
|
|
if source:
|
|
|
|
subquery = source.subquery(node.alias or name)
|
|
|
|
subquery.comments = [f"source: {name}"]
|
2025-02-13 15:52:09 +01:00
|
|
|
return subquery.transform(_expand, copy=False)
|
2025-02-13 15:07:05 +01:00
|
|
|
return node
|
|
|
|
|
|
|
|
return expression.transform(_expand, copy=copy)
|
|
|
|
|
|
|
|
|
2025-02-13 15:09:58 +01:00
|
|
|
def func(name: str, *args, dialect: DialectType = None, **kwargs) -> Func:
|
2025-02-13 15:07:05 +01:00
|
|
|
"""
|
|
|
|
Returns a Func expression.
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
>>> func("abs", 5).sql()
|
|
|
|
'ABS(5)'
|
|
|
|
|
|
|
|
>>> func("cast", this=5, to=DataType.build("DOUBLE")).sql()
|
|
|
|
'CAST(5 AS DOUBLE)'
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name: the name of the function to build.
|
|
|
|
args: the args used to instantiate the function of interest.
|
|
|
|
dialect: the source dialect.
|
|
|
|
kwargs: the kwargs used to instantiate the function of interest.
|
|
|
|
|
|
|
|
Note:
|
|
|
|
The arguments `args` and `kwargs` are mutually exclusive.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
An instance of the function of interest, or an anonymous function, if `name` doesn't
|
|
|
|
correspond to an existing `sqlglot.expressions.Func` class.
|
|
|
|
"""
|
|
|
|
if args and kwargs:
|
|
|
|
raise ValueError("Can't use both args and kwargs to instantiate a function.")
|
|
|
|
|
|
|
|
from sqlglot.dialects.dialect import Dialect
|
|
|
|
|
2025-02-13 15:52:09 +01:00
|
|
|
converted: t.List[Expression] = [maybe_parse(arg, dialect=dialect) for arg in args]
|
|
|
|
kwargs = {key: maybe_parse(value, dialect=dialect) for key, value in kwargs.items()}
|
2025-02-13 15:07:05 +01:00
|
|
|
|
|
|
|
parser = Dialect.get_or_raise(dialect)().parser()
|
|
|
|
from_args_list = parser.FUNCTIONS.get(name.upper())
|
|
|
|
|
|
|
|
if from_args_list:
|
2025-02-13 15:44:58 +01:00
|
|
|
function = from_args_list(converted) if converted else from_args_list.__self__(**kwargs) # type: ignore
|
2025-02-13 15:07:05 +01:00
|
|
|
else:
|
2025-02-13 15:44:58 +01:00
|
|
|
kwargs = kwargs or {"expressions": converted}
|
2025-02-13 15:07:05 +01:00
|
|
|
function = Anonymous(this=name, **kwargs)
|
|
|
|
|
2025-02-13 15:44:58 +01:00
|
|
|
for error_message in function.error_messages(converted):
|
2025-02-13 15:07:05 +01:00
|
|
|
raise ValueError(error_message)
|
|
|
|
|
|
|
|
return function
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def true() -> Boolean:
|
2025-02-13 15:07:05 +01:00
|
|
|
"""
|
|
|
|
Returns a true Boolean expression.
|
|
|
|
"""
|
2025-02-13 14:54:32 +01:00
|
|
|
return Boolean(this=True)
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def false() -> Boolean:
|
2025-02-13 15:07:05 +01:00
|
|
|
"""
|
|
|
|
Returns a false Boolean expression.
|
|
|
|
"""
|
2025-02-13 14:54:32 +01:00
|
|
|
return Boolean(this=False)
|
|
|
|
|
|
|
|
|
2025-02-13 15:57:23 +01:00
|
|
|
def null() -> Null:
|
2025-02-13 15:07:05 +01:00
|
|
|
"""
|
|
|
|
Returns a Null expression.
|
|
|
|
"""
|
2025-02-13 14:54:32 +01:00
|
|
|
return Null()
|
|
|
|
|
|
|
|
|
|
|
|
# TODO: deprecate this
|
2025-02-13 06:15:54 +01:00
|
|
|
TRUE = Boolean(this=True)
|
|
|
|
FALSE = Boolean(this=False)
|
|
|
|
NULL = Null()
|