Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions sqlspec/builder/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from typing_extensions import Self

from sqlspec.builder._locking import register_lock_generator
from sqlspec.builder._parsing_utils import _resolve_dialect
from sqlspec.builder._vector_distance import has_vector_distance_ancestor
from sqlspec.core import (
SQL,
Expand Down Expand Up @@ -647,7 +648,9 @@ def build(self, dialect: DialectType = None) -> "BuiltQuery":
err_msg = f"Error generating SQL from expression: {e!s}"
self._raise_builder_error(err_msg, e)

return BuiltQuery(sql=sql_string, parameters=self._parameters.copy(), dialect=dialect or self.dialect)
return BuiltQuery(
sql=sql_string, parameters=self._parameters.copy(), dialect=_resolve_dialect(dialect, self.dialect)
)

def to_sql(self, show_parameters: bool = False, dialect: DialectType = None) -> str:
"""Return SQL string with optional parameter substitution.
Expand Down Expand Up @@ -988,7 +991,9 @@ def build_static_expression(
identify = self._should_identify(target_dialect)
sql_string = expr.sql(dialect=target_dialect, pretty=True, identify=identify)
return BuiltQuery(
sql=sql_string, parameters=parameters.copy() if parameters else {}, dialect=dialect or self.dialect
sql=sql_string,
parameters=parameters.copy() if parameters else {},
dialect=_resolve_dialect(dialect, self.dialect),
)


Expand Down
10 changes: 7 additions & 3 deletions sqlspec/builder/_column.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,11 @@ def alias(self, alias_name: str) -> exp.Expr:

def asc(self) -> exp.Ordered:
"""Create an ASC ordering expression."""
return exp.Ordered(this=self._expression, desc=False)
return exp.Ordered(this=self._expression, desc=False, nulls_first=False)

def desc(self) -> exp.Ordered:
"""Create a DESC ordering expression."""
return exp.Ordered(this=self._expression, desc=True)
return exp.Ordered(this=self._expression, desc=True, nulls_first=False)

def as_(self, alias: str) -> exp.Alias:
"""Create an aliased expression."""
Expand Down Expand Up @@ -248,7 +248,11 @@ def substring(self, start: int, length: int | None = None) -> "FunctionColumn":
def coalesce(self, *values: Any) -> "FunctionColumn":
"""SQL COALESCE() function."""
expressions = [self._expression] + [self._convert_value(v) for v in values]
return FunctionColumn(exp.Coalesce(expressions=expressions))
return FunctionColumn(
exp.Coalesce(this=expressions[0], expressions=expressions[1:])
if expressions
else exp.Coalesce(this=exp.Var(this=""))
)

def count(self) -> "FunctionColumn":
"""SQL COUNT() function."""
Expand Down
74 changes: 36 additions & 38 deletions sqlspec/builder/_ddl.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from sqlglot.dialects.dialect import DialectType

from sqlspec.builder._column import ColumnExpression
from sqlspec.core import StatementConfig

__all__ = (
"AlterOperation",
Expand Down Expand Up @@ -90,7 +89,7 @@ def build_column_expression(col: "ColumnDefinition") -> "exp.Expr":
constraints.append(exp.ColumnConstraint(kind=exp.UniqueColumnConstraint()))

if col.auto_increment:
constraints.append(exp.ColumnConstraint(kind=exp.AutoIncrementColumnConstraint()))
constraints.append(exp.ColumnConstraint(kind=exp.AutoIncrementColumnConstraint(this=False)))

if col.default is not None:
default_expr: exp.Expr | None = None
Expand Down Expand Up @@ -204,6 +203,10 @@ def _create_base_expression(self) -> exp.Expr:
msg = "Subclasses must implement _create_base_expression."
raise NotImplementedError(msg)

def _require(self, value: object, message: str) -> None:
if not value:
self._raise_builder_error(message)

def _resolve_select_query(self, query: object, context: str, *, require_select_type: bool = True) -> exp.Expr:
select_parameters: dict[str, Any] | None = None

Expand Down Expand Up @@ -238,9 +241,6 @@ def build(self, dialect: "DialectType" = None) -> "BuiltQuery":
self._expression = self._create_base_expression()
return super().build(dialect=dialect)

def to_statement(self, config: "StatementConfig | None" = None) -> "SQL":
return super().to_statement(config=config)


@trait
class _IfExistsDDLMixin:
Expand Down Expand Up @@ -589,8 +589,7 @@ def with_option(self, key: str, value: "Any") -> "Self":

def _create_base_expression(self) -> "exp.Expr":
"""Create the SQLGlot expression for CREATE TABLE."""
if not self._columns and not self._like_table:
self._raise_builder_error("Table must have at least one column or use LIKE clause")
self._require(self._columns or self._like_table, "Table must have at least one column or use LIKE clause")

column_defs: list[exp.Expr] = []
for col in self._columns:
Expand Down Expand Up @@ -632,7 +631,7 @@ def _create_base_expression(self) -> "exp.Expr":
props.append(exp.TemporaryProperty())
if self._like_table:
props.append(exp.LikeProperty(this=exp.to_table(self._like_table)))
properties_node = exp.Properties(expressions=props) if props else None
properties_node = _wrap_properties(props)

create_target: exp.Expr = table_identifier if self._like_table and not column_defs else schema_expr
return exp.Create(kind="TABLE", this=create_target, exists=self._if_not_exists, properties=properties_node)
Expand Down Expand Up @@ -686,8 +685,7 @@ def _drop_expression_args(self) -> "dict[str, Any]":
return {}

def _create_base_expression(self) -> exp.Expr:
if not self._name:
self._raise_builder_error(f"{self._object_label} name must be set for DROP {self._drop_kind}.")
self._require(self._name, f"{self._object_label} name must be set for DROP {self._drop_kind}.")
return exp.Drop(
kind=self._drop_kind,
this=self._build_drop_this(),
Expand Down Expand Up @@ -874,8 +872,7 @@ def _create_base_expression(self) -> exp.Expr:
Columns are turned into raw expressions (not ``Ordered``) to preserve natural NULL ordering,
string ``where`` clauses become expressions, and the final ``exp.Index`` is wrapped in an ``exp.Create`` with the configured flags.
"""
if not self._index_name or not self._table_name:
self._raise_builder_error("Index name and table name must be set for CREATE INDEX.")
self._require(self._index_name and self._table_name, "Index name and table name must be set for CREATE INDEX.")

cols: list[exp.Expr] = []
for col in self._columns:
Expand All @@ -901,7 +898,9 @@ def _create_base_expression(self) -> exp.Expr:
index_params.set("where", exp.Where(this=where_expr))

index_expr = exp.Index(
this=exp.to_identifier(self._index_name), table=exp.to_table(self._table_name), params=index_params
this=exp.to_identifier(self._index_name),
table=exp.to_table(cast("str", self._table_name)),
params=index_params,
)

return exp.Create(kind="INDEX", this=index_expr, unique=self._unique, exists=self._if_not_exists)
Expand Down Expand Up @@ -937,8 +936,7 @@ def continue_identity(self) -> Self:
return self

def _create_base_expression(self) -> exp.Expr:
if not self._table_name:
self._raise_builder_error("Table name must be set for TRUNCATE TABLE.")
self._require(self._table_name, "Table name must be set for TRUNCATE TABLE.")
identity_expr = exp.Var(this=self._identity) if self._identity else None
option_expr = exp.Var(this="CASCADE") if self._cascade else None
return exp.TruncateTable(
Expand Down Expand Up @@ -1013,14 +1011,13 @@ def authorization(self, user_name: str) -> Self:
return self

def _create_base_expression(self) -> exp.Expr:
if not self._schema_name:
self._raise_builder_error("Schema name must be set for CREATE SCHEMA.")
self._require(self._schema_name, "Schema name must be set for CREATE SCHEMA.")
props: list[exp.Property] = []
if self._authorization:
props.append(
exp.Property(this=exp.to_identifier("AUTHORIZATION"), value=exp.to_identifier(self._authorization))
)
properties_node = exp.Properties(expressions=props) if props else None
properties_node = _wrap_properties(props)
return exp.Create(
kind="SCHEMA",
this=exp.to_identifier(self._schema_name),
Expand Down Expand Up @@ -1061,8 +1058,7 @@ def as_select(self, select_query: "str | exp.Expr") -> Self:
return self

def _create_base_expression(self) -> exp.Expr:
if not self._table_name:
self._raise_builder_error("Table name must be set for CREATE TABLE AS SELECT.")
self._require(self._table_name, "Table name must be set for CREATE TABLE AS SELECT.")
if self._select_query is None:
self._raise_builder_error("SELECT query must be set for CREATE TABLE AS SELECT.")

Expand All @@ -1074,7 +1070,7 @@ def _create_base_expression(self) -> exp.Expr:
if has_with_method(select_expr):
select_expr = select_expr.with_(cte.this, as_=alias, copy=False)

create_target: exp.Expr = exp.to_table(self._table_name)
create_target: exp.Expr = exp.to_table(cast("str", self._table_name))
if self._columns:
create_target = exp.Schema(this=create_target, expressions=[exp.to_identifier(c) for c in self._columns])

Expand Down Expand Up @@ -1157,16 +1153,13 @@ def with_hint(self, hint: str) -> Self:
return self

def _create_base_expression(self) -> exp.Expr:
if not self._view_name:
self._raise_builder_error("View name must be set for CREATE MATERIALIZED VIEW.")
self._require(self._view_name, "View name must be set for CREATE MATERIALIZED VIEW.")
if self._select_query is None:
self._raise_builder_error("SELECT query must be set for CREATE MATERIALIZED VIEW.")

select_expr = self._resolve_select_query(self._select_query, "materialized view")

schema_expr = None
if self._columns:
schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
schema_expr = _build_optional_column_schema(self._columns)

props: list[exp.Property] = []
if self._refresh_mode:
Expand All @@ -1180,9 +1173,9 @@ def _create_base_expression(self) -> exp.Expr:
for k, v in self._storage_parameters.items():
props.append(exp.Property(this=exp.to_identifier(k), value=exp.convert(str(v))))
if self._with_data is not None:
props.append(exp.Property(this=exp.to_identifier("WITH_DATA" if self._with_data else "NO_DATA")))
props.append(exp.WithDataProperty(no=not self._with_data))
props.extend(exp.Property(this=exp.to_identifier("HINT"), value=exp.convert(hint)) for hint in self._hints)
properties_node = exp.Properties(expressions=props) if props else None
properties_node = _wrap_properties(props)

create_target: exp.Expr = exp.to_table(self._view_name)
if schema_expr is not None:
Expand Down Expand Up @@ -1233,21 +1226,18 @@ def with_hint(self, hint: str) -> Self:
return self

def _create_base_expression(self) -> exp.Expr:
if not self._view_name:
self._raise_builder_error("View name must be set for CREATE VIEW.")
self._require(self._view_name, "View name must be set for CREATE VIEW.")
if self._select_query is None:
self._raise_builder_error("SELECT query must be set for CREATE VIEW.")

select_expr = self._resolve_select_query(self._select_query, "view")

schema_expr = None
if self._columns:
schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
schema_expr = _build_optional_column_schema(self._columns)

props: list[exp.Property] = [
exp.Property(this=exp.to_identifier("HINT"), value=exp.convert(h)) for h in self._hints
]
properties_node = exp.Properties(expressions=props) if props else None
properties_node = _wrap_properties(props)

create_target: exp.Expr = exp.to_table(self._view_name)
if schema_expr is not None:
Expand Down Expand Up @@ -1453,8 +1443,7 @@ def drop_column_default(self, column: str) -> "Self":

def _create_base_expression(self) -> "exp.Expr":
"""Create the SQLGlot expression for ALTER TABLE."""
if not self._operations:
self._raise_builder_error("At least one operation must be specified for ALTER TABLE")
self._require(self._operations, "At least one operation must be specified for ALTER TABLE")

if self._schema:
table = exp.Table(this=exp.to_identifier(self._table_name), db=exp.to_identifier(self._schema))
Expand Down Expand Up @@ -1616,10 +1605,19 @@ def to(self, new_name: str) -> Self:
return self

def _create_base_expression(self) -> exp.Expr:
if not self._old_name or not self._new_name:
self._raise_builder_error("Both old and new table names must be set for RENAME TABLE.")
self._require(self._old_name and self._new_name, "Both old and new table names must be set for RENAME TABLE.")
return exp.Alter(
this=exp.to_table(self._old_name),
kind="TABLE",
actions=[exp.AlterRename(this=exp.to_identifier(self._new_name))],
)


def _build_optional_column_schema(columns: list[str]) -> exp.Schema | None:
if not columns:
return None
return exp.Schema(expressions=[exp.column(column) for column in columns])


def _wrap_properties(properties: list[exp.Property]) -> exp.Properties | None:
return exp.Properties(expressions=properties) if properties else None
Loading
Loading