diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 05:35:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 05:35:55 +0000 |
commit | fe979e8421c04c038353a0a2d07d81779516186a (patch) | |
tree | efb70a52261e5cf4862a7eb69e1d7cd16356fcba /sqlglot/generator.py | |
parent | Releasing debian version 23.13.7-1. (diff) | |
download | sqlglot-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.py | 189 |
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}" |