summaryrefslogtreecommitdiffstats
path: root/sqlglot/generator.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 05:35:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 05:35:55 +0000
commitfe979e8421c04c038353a0a2d07d81779516186a (patch)
treeefb70a52261e5cf4862a7eb69e1d7cd16356fcba /sqlglot/generator.py
parentReleasing debian version 23.13.7-1. (diff)
downloadsqlglot-fe979e8421c04c038353a0a2d07d81779516186a.tar.xz
sqlglot-fe979e8421c04c038353a0a2d07d81779516186a.zip
Merging upstream version 23.16.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sqlglot/generator.py')
-rw-r--r--sqlglot/generator.py189
1 files changed, 144 insertions, 45 deletions
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
index b2d537b..128d195 100644
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -8,7 +8,7 @@ from functools import reduce
from sqlglot import exp
from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages
-from sqlglot.helper import apply_index_offset, csv, seq_get
+from sqlglot.helper import apply_index_offset, csv, name_sequence, seq_get
from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS
from sqlglot.time import format_time
from sqlglot.tokens import TokenType
@@ -74,6 +74,8 @@ class Generator(metaclass=_Generator):
TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = {
**JSON_PATH_PART_TRANSFORMS,
+ exp.AllowedValuesProperty: lambda self,
+ e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}",
exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}",
exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}",
exp.CaseSpecificColumnConstraint: lambda _,
@@ -123,7 +125,9 @@ class Generator(metaclass=_Generator):
exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}",
exp.RemoteWithConnectionModelProperty: lambda self,
e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}",
- exp.ReturnsProperty: lambda self, e: self.naked_property(e),
+ exp.ReturnsProperty: lambda self, e: (
+ "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e)
+ ),
exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}",
exp.SetConfigProperty: lambda self, e: self.sql(e, "this"),
exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET",
@@ -133,6 +137,7 @@ class Generator(metaclass=_Generator):
exp.SqlSecurityProperty: lambda _,
e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}",
exp.StabilityProperty: lambda _, e: e.name,
+ exp.StrictProperty: lambda *_: "STRICT",
exp.TemporaryProperty: lambda *_: "TEMPORARY",
exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}",
exp.Timestamp: lambda self, e: self.func("TIMESTAMP", e.this, e.expression),
@@ -351,6 +356,15 @@ class Generator(metaclass=_Generator):
# Whether the conditional TRY(expression) function is supported
TRY_SUPPORTED = True
+ # The keyword to use when generating a star projection with excluded columns
+ STAR_EXCEPT = "EXCEPT"
+
+ # The HEX function name
+ HEX_FUNC = "HEX"
+
+ # The keywords to use when prefixing & separating WITH based properties
+ WITH_PROPERTIES_PREFIX = "WITH"
+
TYPE_MAPPING = {
exp.DataType.Type.NCHAR: "CHAR",
exp.DataType.Type.NVARCHAR: "VARCHAR",
@@ -364,11 +378,6 @@ class Generator(metaclass=_Generator):
exp.DataType.Type.ROWVERSION: "VARBINARY",
}
- STAR_MAPPING = {
- "except": "EXCEPT",
- "replace": "REPLACE",
- }
-
TIME_PART_SINGULARS = {
"MICROSECONDS": "MICROSECOND",
"SECONDS": "SECOND",
@@ -401,6 +410,7 @@ class Generator(metaclass=_Generator):
NAMED_PLACEHOLDER_TOKEN = ":"
PROPERTIES_LOCATION = {
+ exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,
exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,
exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,
exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,
@@ -413,6 +423,7 @@ class Generator(metaclass=_Generator):
exp.Cluster: exp.Properties.Location.POST_SCHEMA,
exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,
exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,
+ exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,
exp.DefinerProperty: exp.Properties.Location.POST_CREATE,
exp.DictRange: exp.Properties.Location.POST_SCHEMA,
exp.DictProperty: exp.Properties.Location.POST_SCHEMA,
@@ -466,6 +477,7 @@ class Generator(metaclass=_Generator):
exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,
exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE,
exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,
+ exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,
exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,
exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,
exp.TransientProperty: exp.Properties.Location.POST_CREATE,
@@ -539,6 +551,7 @@ class Generator(metaclass=_Generator):
"unsupported_messages",
"_escaped_quote_end",
"_escaped_identifier_end",
+ "_next_name",
)
def __init__(
@@ -584,6 +597,8 @@ class Generator(metaclass=_Generator):
self.dialect.tokenizer_class.IDENTIFIER_ESCAPES[0] + self.dialect.IDENTIFIER_END
)
+ self._next_name = name_sequence("_t")
+
def generate(self, expression: exp.Expression, copy: bool = True) -> str:
"""
Generates the SQL string corresponding to the given syntax tree.
@@ -687,15 +702,15 @@ class Generator(metaclass=_Generator):
return f"{sql} {comments_sql}"
def wrap(self, expression: exp.Expression | str) -> str:
- this_sql = self.indent(
- (
- self.sql(expression)
- if isinstance(expression, exp.UNWRAPPED_QUERIES)
- else self.sql(expression, "this")
- ),
- level=1,
- pad=0,
+ this_sql = (
+ self.sql(expression)
+ if isinstance(expression, exp.UNWRAPPED_QUERIES)
+ else self.sql(expression, "this")
)
+ if not this_sql:
+ return "()"
+
+ this_sql = self.indent(this_sql, level=1, pad=0)
return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:
@@ -720,7 +735,7 @@ class Generator(metaclass=_Generator):
skip_first: bool = False,
skip_last: bool = False,
) -> str:
- if not self.pretty:
+ if not self.pretty or not sql:
return sql
pad = self.pad if pad is None else pad
@@ -951,6 +966,12 @@ class Generator(metaclass=_Generator):
)
)
+ if properties_locs.get(exp.Properties.Location.POST_SCHEMA):
+ properties_sql = self.sep() + properties_sql
+ elif not self.pretty:
+ # Standalone POST_WITH properties need a leading whitespace in non-pretty mode
+ properties_sql = f" {properties_sql}"
+
begin = " BEGIN" if expression.args.get("begin") else ""
end = " END" if expression.args.get("end") else ""
@@ -1095,7 +1116,7 @@ class Generator(metaclass=_Generator):
self.unsupported("Named columns are not supported in table alias.")
if not alias and not self.dialect.UNNEST_COLUMN_ONLY:
- alias = "_t"
+ alias = self._next_name()
return f"{alias}{columns}"
@@ -1208,12 +1229,14 @@ class Generator(metaclass=_Generator):
expressions = f" ({expressions})" if expressions else ""
kind = expression.args["kind"]
exists_sql = " IF EXISTS " if expression.args.get("exists") else " "
+ on_cluster = self.sql(expression, "cluster")
+ on_cluster = f" {on_cluster}" if on_cluster else ""
temporary = " TEMPORARY" if expression.args.get("temporary") else ""
materialized = " MATERIALIZED" if expression.args.get("materialized") else ""
cascade = " CASCADE" if expression.args.get("cascade") else ""
constraints = " CONSTRAINTS" if expression.args.get("constraints") else ""
purge = " PURGE" if expression.args.get("purge") else ""
- return f"DROP{temporary}{materialized} {kind}{exists_sql}{this}{expressions}{cascade}{constraints}{purge}"
+ return f"DROP{temporary}{materialized} {kind}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
def except_sql(self, expression: exp.Except) -> str:
return self.set_operations(expression)
@@ -1296,6 +1319,19 @@ class Generator(metaclass=_Generator):
text = f"{self.dialect.IDENTIFIER_START}{text}{self.dialect.IDENTIFIER_END}"
return text
+ def hex_sql(self, expression: exp.Hex) -> str:
+ text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
+ if self.dialect.HEX_LOWERCASE:
+ text = self.func("LOWER", text)
+
+ return text
+
+ def lowerhex_sql(self, expression: exp.LowerHex) -> str:
+ text = self.func(self.HEX_FUNC, self.sql(expression, "this"))
+ if not self.dialect.HEX_LOWERCASE:
+ text = self.func("LOWER", text)
+ return text
+
def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:
input_format = self.sql(expression, "input_format")
input_format = f"INPUTFORMAT {input_format}" if input_format else ""
@@ -1321,13 +1357,17 @@ class Generator(metaclass=_Generator):
elif p_loc == exp.Properties.Location.POST_SCHEMA:
root_properties.append(p)
- return self.root_properties(
- exp.Properties(expressions=root_properties)
- ) + self.with_properties(exp.Properties(expressions=with_properties))
+ root_props = self.root_properties(exp.Properties(expressions=root_properties))
+ with_props = self.with_properties(exp.Properties(expressions=with_properties))
+
+ if root_props and with_props and not self.pretty:
+ with_props = " " + with_props
+
+ return root_props + with_props
def root_properties(self, properties: exp.Properties) -> str:
if properties.expressions:
- return self.sep() + self.expressions(properties, indent=False, sep=" ")
+ return self.expressions(properties, indent=False, sep=" ")
return ""
def properties(
@@ -1346,7 +1386,7 @@ class Generator(metaclass=_Generator):
return ""
def with_properties(self, properties: exp.Properties) -> str:
- return self.properties(properties, prefix=self.seg("WITH"))
+ return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=""))
def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:
properties_locs = defaultdict(list)
@@ -1514,19 +1554,25 @@ class Generator(metaclass=_Generator):
return f"{data_sql}{statistics_sql}"
def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:
- sql = "WITH(SYSTEM_VERSIONING=ON"
-
- if expression.this:
- history_table = self.sql(expression, "this")
- sql = f"{sql}(HISTORY_TABLE={history_table}"
+ this = self.sql(expression, "this")
+ this = f"HISTORY_TABLE={this}" if this else ""
+ data_consistency: t.Optional[str] = self.sql(expression, "data_consistency")
+ data_consistency = (
+ f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None
+ )
+ retention_period: t.Optional[str] = self.sql(expression, "retention_period")
+ retention_period = (
+ f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None
+ )
- if expression.expression:
- data_consistency_check = self.sql(expression, "expression")
- sql = f"{sql}, DATA_CONSISTENCY_CHECK={data_consistency_check}"
+ if this:
+ on_sql = self.func("ON", this, data_consistency, retention_period)
+ else:
+ on_sql = "ON" if expression.args.get("on") else "OFF"
- sql = f"{sql})"
+ sql = f"SYSTEM_VERSIONING={on_sql}"
- return f"{sql})"
+ return f"WITH({sql})" if expression.args.get("with") else sql
def insert_sql(self, expression: exp.Insert) -> str:
hint = self.sql(expression, "hint")
@@ -2300,10 +2346,12 @@ class Generator(metaclass=_Generator):
def star_sql(self, expression: exp.Star) -> str:
except_ = self.expressions(expression, key="except", flat=True)
- except_ = f"{self.seg(self.STAR_MAPPING['except'])} ({except_})" if except_ else ""
+ except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else ""
replace = self.expressions(expression, key="replace", flat=True)
- replace = f"{self.seg(self.STAR_MAPPING['replace'])} ({replace})" if replace else ""
- return f"*{except_}{replace}"
+ replace = f"{self.seg('REPLACE')} ({replace})" if replace else ""
+ rename = self.expressions(expression, key="rename", flat=True)
+ rename = f"{self.seg('RENAME')} ({rename})" if rename else ""
+ return f"*{except_}{replace}{rename}"
def parameter_sql(self, expression: exp.Parameter) -> str:
this = self.sql(expression, "this")
@@ -2843,9 +2891,10 @@ class Generator(metaclass=_Generator):
stack.append(self.expressions(expression, sep=f" {op} "))
else:
stack.append(expression.right)
- if expression.comments:
+ if expression.comments and self.comments:
for comment in expression.comments:
- op += f" /*{self.pad_comment(comment)}*/"
+ if comment:
+ op += f" /*{self.pad_comment(comment)}*/"
stack.extend((op, expression.left))
return op
@@ -2978,6 +3027,19 @@ class Generator(metaclass=_Generator):
return f"ALTER COLUMN {this} DROP DEFAULT"
+ def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:
+ this = self.sql(expression, "this")
+ if not isinstance(expression.this, exp.Var):
+ this = f"KEY DISTKEY {this}"
+ return f"ALTER DISTSTYLE {this}"
+
+ def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:
+ compound = " COMPOUND" if expression.args.get("compound") else ""
+ this = self.sql(expression, "this")
+ expressions = self.expressions(expression, flat=True)
+ expressions = f"({expressions})" if expressions else ""
+ return f"ALTER{compound} SORTKEY {this or expressions}"
+
def renametable_sql(self, expression: exp.RenameTable) -> str:
if not self.RENAME_TABLE_WITH_DB:
# Remove db from tables
@@ -2993,6 +3055,10 @@ class Generator(metaclass=_Generator):
new_column = self.sql(expression, "to")
return f"RENAME COLUMN{exists} {old_column} TO {new_column}"
+ def alterset_sql(self, expression: exp.AlterSet) -> str:
+ exprs = self.expressions(expression, flat=True)
+ return f"SET {exprs}"
+
def altertable_sql(self, expression: exp.AlterTable) -> str:
actions = expression.args["actions"]
@@ -3006,10 +3072,12 @@ class Generator(metaclass=_Generator):
actions = self.expressions(expression, key="actions", flat=True)
exists = " IF EXISTS" if expression.args.get("exists") else ""
+ on_cluster = self.sql(expression, "cluster")
+ on_cluster = f" {on_cluster}" if on_cluster else ""
only = " ONLY" if expression.args.get("only") else ""
options = self.expressions(expression, key="options")
options = f", {options}" if options else ""
- return f"ALTER TABLE{exists}{only} {self.sql(expression, 'this')} {actions}{options}"
+ return f"ALTER TABLE{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions}{options}"
def add_column_sql(self, expression: exp.AlterTable) -> str:
if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:
@@ -3781,6 +3849,11 @@ class Generator(metaclass=_Generator):
def copyparameter_sql(self, expression: exp.CopyParameter) -> str:
option = self.sql(expression, "this")
+
+ if option.upper() == "FILE_FORMAT":
+ values = self.expressions(expression, key="expression", flat=True, sep=" ")
+ return f"{option} = ({values})"
+
value = self.sql(expression, "expression")
if not value:
@@ -3802,7 +3875,6 @@ class Generator(metaclass=_Generator):
credentials = f"CREDENTIALS = ({credentials})" if credentials else ""
storage = self.sql(expression, "storage")
- storage = f" {storage}" if storage else ""
encryption = self.expressions(expression, key="encryption", flat=True, sep=" ")
encryption = f" ENCRYPTION = ({encryption})" if encryption else ""
@@ -3820,13 +3892,40 @@ class Generator(metaclass=_Generator):
this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}"
credentials = self.sql(expression, "credentials")
- credentials = f" {credentials}" if credentials else ""
- kind = " FROM " if expression.args.get("kind") else " TO "
+ credentials = self.seg(credentials) if credentials else ""
+ kind = self.seg("FROM" if expression.args.get("kind") else "TO")
files = self.expressions(expression, key="files", flat=True)
sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " "
- params = self.expressions(expression, key="params", flat=True, sep=sep)
+ params = self.expressions(
+ expression,
+ key="params",
+ sep=sep,
+ new_line=True,
+ skip_last=True,
+ skip_first=True,
+ indent=self.COPY_PARAMS_ARE_WRAPPED,
+ )
+
if params:
- params = f" WITH ({params})" if self.COPY_PARAMS_ARE_WRAPPED else f" {params}"
+ if self.COPY_PARAMS_ARE_WRAPPED:
+ params = f" WITH ({params})"
+ elif not self.pretty:
+ params = f" {params}"
+
+ return f"COPY{this}{kind} {files}{credentials}{params}"
+
+ def semicolon_sql(self, expression: exp.Semicolon) -> str:
+ return ""
+
+ def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:
+ on_sql = "ON" if expression.args.get("on") else "OFF"
+ filter_col: t.Optional[str] = self.sql(expression, "filter_column")
+ filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None
+ retention_period: t.Optional[str] = self.sql(expression, "retention_period")
+ retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None
+
+ if filter_col or retention_period:
+ on_sql = self.func("ON", filter_col, retention_period)
- return f"COPY{this}{kind}{files}{credentials}{params}"
+ return f"DATA_DELETION={on_sql}"