Skip to content
69 changes: 68 additions & 1 deletion sqlglot/dialects/exasol.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
build_date_delta,
)
from sqlglot.generator import unsupported_args
from sqlglot.helper import seq_get
from sqlglot.helper import seq_get, find_new_name
from sqlglot.tokens import TokenType
from sqlglot.optimizer.scope import build_scope

if t.TYPE_CHECKING:
from sqlglot.dialects.dialect import DialectType
Expand Down Expand Up @@ -167,6 +168,71 @@ def _substring_index_sql(self: Exasol.Generator, expression: exp.SubstringIndex)
return self.func("SUBSTR", haystack_sql, direction, length)


# https://docs.exasol.com/db/latest/sql/select.htm#:~:text=The%20select_list%20defines%20the%20columns%20of%20the%20result%20table.%20If%20*%20is%20used%2C%20all%20columns%20are%20listed.%20You%20can%20use%20an%20expression%20like%20t.*%20to%20list%20all%20columns%20of%20the%20table%20t%2C%20the%20view%20t%2C%20or%20the%20object%20with%20the%20table%20alias%20t.
def _qualify_unscoped_star(node: exp.Expression) -> exp.Expression:
"""
Exasol doesn't support a bare * alongside other select items, so we rewrite it
Rewrite: SELECT *, <other> FROM <Table>
Into: SELECT T.*, <other> FROM <Table> AS T
"""

if not isinstance(node, exp.Select):
return node
select_expressions = list(node.expressions or [])

has_bare_star = any(
isinstance(expr, exp.Star) and expr.this is None for expr in select_expressions
)

if not has_bare_star or len(select_expressions) <= 1:
return node

from_clause = node.args.get("from_")

base_source = from_clause.this if from_clause else None

if not base_source:
return node

table_sources: list[exp.Expression] = [base_source]

table_sources.extend(
join.this
for join in (node.args.get("joins") or [])
if isinstance(join, exp.Join) and join.this
)

if not table_sources:
return node

scope = build_scope(node)
used_alias_names = set(scope.sources.keys()) if scope else set()

qualifiers: list[exp.Identifier] = []

for src in table_sources:
alias = src.args.get("alias")
if isinstance(alias, (exp.TableAlias, exp.Alias)) and alias.name:
name = alias.name
else:
name = find_new_name(used_alias_names, base="T")
src.set("alias", exp.TableAlias(this=exp.to_identifier(name, quoted=False)))
used_alias_names.add(name)
qualifiers.append(exp.to_identifier(name, quoted=False))

star_columns = [
exp.Column(this=exp.Star(), table=alias_identifier) for alias_identifier in qualifiers
]

new_items: list[exp.Expression] = []
for select_expression in select_expressions:
new_items.extend(star_columns) if isinstance(
select_expression, exp.Star
) and select_expression.this is None else new_items.append(select_expression)
node.set("expressions", new_items)
return node


DATE_UNITS = {"DAY", "WEEK", "MONTH", "YEAR", "HOUR", "MINUTE", "SECOND"}


Expand Down Expand Up @@ -425,6 +491,7 @@ def datatype_sql(self, expression: exp.DataType) -> str:
exp.CommentColumnConstraint: lambda self, e: f"COMMENT IS {self.sql(e, 'this')}",
exp.Select: transforms.preprocess(
[
_qualify_unscoped_star,
_add_local_prefix_for_aliases,
]
),
Expand Down
18 changes: 18 additions & 0 deletions tests/dialects/test_exasol.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@ def test_exasol(self):
'SELECT 1 AS "x"',
)

def test_qualify_unscoped_star(self):
self.validate_identity(
"SELECT *, 1 FROM TEST",
"SELECT T.*, 1 FROM TEST AS T",
)
self.validate_identity(
"WITH t1 AS (SELECT 1 AS c1), t2 AS (SELECT 2 AS c2) SELECT *, 3 FROM t1, t2",
"WITH t1 AS (SELECT 1 AS c1), t2 AS (SELECT 2 AS c2) SELECT T.*, T_2.*, 3 FROM t1 AS T, t2 AS T_2",
)
self.validate_identity(
'SELECT *, 3 FROM "A" JOIN "B" ON 1=1',
'SELECT T.*, T_2.*, 3 FROM "A" AS T JOIN "B" AS T_2 ON 1 = 1',
)
self.validate_identity(
"SELECT *, 7 FROM (SELECT 1 AS x) s CROSS JOIN (SELECT 2 AS y) q",
"SELECT s.*, q.*, 7 FROM (SELECT 1 AS x) AS s CROSS JOIN (SELECT 2 AS y) AS q",
)

def test_type_mappings(self):
self.validate_identity("CAST(x AS BLOB)", "CAST(x AS VARCHAR)")
self.validate_identity("CAST(x AS LONGBLOB)", "CAST(x AS VARCHAR)")
Expand Down