sqlspec 0.12.1__py3-none-any.whl → 0.13.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sqlspec might be problematic. Click here for more details.

Files changed (113) hide show
  1. sqlspec/_sql.py +21 -180
  2. sqlspec/adapters/adbc/config.py +10 -12
  3. sqlspec/adapters/adbc/driver.py +120 -118
  4. sqlspec/adapters/aiosqlite/config.py +3 -3
  5. sqlspec/adapters/aiosqlite/driver.py +116 -141
  6. sqlspec/adapters/asyncmy/config.py +3 -4
  7. sqlspec/adapters/asyncmy/driver.py +123 -135
  8. sqlspec/adapters/asyncpg/config.py +3 -7
  9. sqlspec/adapters/asyncpg/driver.py +98 -140
  10. sqlspec/adapters/bigquery/config.py +4 -5
  11. sqlspec/adapters/bigquery/driver.py +231 -181
  12. sqlspec/adapters/duckdb/config.py +3 -6
  13. sqlspec/adapters/duckdb/driver.py +132 -124
  14. sqlspec/adapters/oracledb/config.py +6 -5
  15. sqlspec/adapters/oracledb/driver.py +242 -259
  16. sqlspec/adapters/psqlpy/config.py +3 -7
  17. sqlspec/adapters/psqlpy/driver.py +118 -93
  18. sqlspec/adapters/psycopg/config.py +34 -30
  19. sqlspec/adapters/psycopg/driver.py +342 -214
  20. sqlspec/adapters/sqlite/config.py +3 -3
  21. sqlspec/adapters/sqlite/driver.py +150 -104
  22. sqlspec/config.py +0 -4
  23. sqlspec/driver/_async.py +89 -98
  24. sqlspec/driver/_common.py +52 -17
  25. sqlspec/driver/_sync.py +81 -105
  26. sqlspec/driver/connection.py +207 -0
  27. sqlspec/driver/mixins/_csv_writer.py +91 -0
  28. sqlspec/driver/mixins/_pipeline.py +38 -49
  29. sqlspec/driver/mixins/_result_utils.py +27 -9
  30. sqlspec/driver/mixins/_storage.py +149 -216
  31. sqlspec/driver/mixins/_type_coercion.py +3 -4
  32. sqlspec/driver/parameters.py +138 -0
  33. sqlspec/exceptions.py +10 -2
  34. sqlspec/extensions/aiosql/adapter.py +0 -10
  35. sqlspec/extensions/litestar/handlers.py +0 -1
  36. sqlspec/extensions/litestar/plugin.py +0 -3
  37. sqlspec/extensions/litestar/providers.py +0 -14
  38. sqlspec/loader.py +31 -118
  39. sqlspec/protocols.py +542 -0
  40. sqlspec/service/__init__.py +3 -2
  41. sqlspec/service/_util.py +147 -0
  42. sqlspec/service/base.py +1116 -9
  43. sqlspec/statement/builder/__init__.py +42 -32
  44. sqlspec/statement/builder/_ddl_utils.py +0 -10
  45. sqlspec/statement/builder/_parsing_utils.py +10 -4
  46. sqlspec/statement/builder/base.py +70 -23
  47. sqlspec/statement/builder/column.py +283 -0
  48. sqlspec/statement/builder/ddl.py +102 -65
  49. sqlspec/statement/builder/delete.py +23 -7
  50. sqlspec/statement/builder/insert.py +29 -15
  51. sqlspec/statement/builder/merge.py +4 -4
  52. sqlspec/statement/builder/mixins/_aggregate_functions.py +113 -14
  53. sqlspec/statement/builder/mixins/_common_table_expr.py +0 -1
  54. sqlspec/statement/builder/mixins/_delete_from.py +1 -1
  55. sqlspec/statement/builder/mixins/_from.py +10 -8
  56. sqlspec/statement/builder/mixins/_group_by.py +0 -1
  57. sqlspec/statement/builder/mixins/_insert_from_select.py +0 -1
  58. sqlspec/statement/builder/mixins/_insert_values.py +0 -2
  59. sqlspec/statement/builder/mixins/_join.py +20 -13
  60. sqlspec/statement/builder/mixins/_limit_offset.py +3 -3
  61. sqlspec/statement/builder/mixins/_merge_clauses.py +3 -4
  62. sqlspec/statement/builder/mixins/_order_by.py +2 -2
  63. sqlspec/statement/builder/mixins/_pivot.py +4 -7
  64. sqlspec/statement/builder/mixins/_select_columns.py +6 -5
  65. sqlspec/statement/builder/mixins/_unpivot.py +6 -9
  66. sqlspec/statement/builder/mixins/_update_from.py +2 -1
  67. sqlspec/statement/builder/mixins/_update_set.py +11 -8
  68. sqlspec/statement/builder/mixins/_where.py +61 -34
  69. sqlspec/statement/builder/select.py +32 -17
  70. sqlspec/statement/builder/update.py +25 -11
  71. sqlspec/statement/filters.py +39 -14
  72. sqlspec/statement/parameter_manager.py +220 -0
  73. sqlspec/statement/parameters.py +210 -79
  74. sqlspec/statement/pipelines/__init__.py +166 -23
  75. sqlspec/statement/pipelines/analyzers/_analyzer.py +22 -25
  76. sqlspec/statement/pipelines/context.py +35 -39
  77. sqlspec/statement/pipelines/transformers/__init__.py +2 -3
  78. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +19 -187
  79. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +667 -43
  80. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +76 -0
  81. sqlspec/statement/pipelines/validators/_dml_safety.py +33 -18
  82. sqlspec/statement/pipelines/validators/_parameter_style.py +87 -14
  83. sqlspec/statement/pipelines/validators/_performance.py +38 -23
  84. sqlspec/statement/pipelines/validators/_security.py +39 -62
  85. sqlspec/statement/result.py +37 -129
  86. sqlspec/statement/splitter.py +0 -12
  87. sqlspec/statement/sql.py +885 -379
  88. sqlspec/statement/sql_compiler.py +140 -0
  89. sqlspec/storage/__init__.py +10 -2
  90. sqlspec/storage/backends/fsspec.py +82 -35
  91. sqlspec/storage/backends/obstore.py +66 -49
  92. sqlspec/storage/capabilities.py +101 -0
  93. sqlspec/storage/registry.py +56 -83
  94. sqlspec/typing.py +6 -434
  95. sqlspec/utils/cached_property.py +25 -0
  96. sqlspec/utils/correlation.py +0 -2
  97. sqlspec/utils/logging.py +0 -6
  98. sqlspec/utils/sync_tools.py +0 -4
  99. sqlspec/utils/text.py +0 -5
  100. sqlspec/utils/type_guards.py +892 -0
  101. {sqlspec-0.12.1.dist-info → sqlspec-0.13.0.dist-info}/METADATA +1 -1
  102. sqlspec-0.13.0.dist-info/RECORD +150 -0
  103. sqlspec/statement/builder/protocols.py +0 -20
  104. sqlspec/statement/pipelines/base.py +0 -315
  105. sqlspec/statement/pipelines/result_types.py +0 -41
  106. sqlspec/statement/pipelines/transformers/_remove_comments.py +0 -66
  107. sqlspec/statement/pipelines/transformers/_remove_hints.py +0 -81
  108. sqlspec/statement/pipelines/validators/base.py +0 -67
  109. sqlspec/storage/protocol.py +0 -170
  110. sqlspec-0.12.1.dist-info/RECORD +0 -145
  111. {sqlspec-0.12.1.dist-info → sqlspec-0.13.0.dist-info}/WHEEL +0 -0
  112. {sqlspec-0.12.1.dist-info → sqlspec-0.13.0.dist-info}/licenses/LICENSE +0 -0
  113. {sqlspec-0.12.1.dist-info → sqlspec-0.13.0.dist-info}/licenses/NOTICE +0 -0
@@ -12,25 +12,28 @@ from sqlspec.statement.builder.base import QueryBuilder, SafeQuery
12
12
  from sqlspec.statement.result import SQLResult
13
13
 
14
14
  if TYPE_CHECKING:
15
+ from sqlspec.statement.builder.column import ColumnExpression
15
16
  from sqlspec.statement.sql import SQL, SQLConfig
16
17
 
17
18
  __all__ = (
18
19
  "AlterOperation",
19
- "AlterTableBuilder",
20
+ "AlterTable",
20
21
  "ColumnDefinition",
21
- "CommentOnBuilder",
22
+ "CommentOn",
22
23
  "ConstraintDefinition",
23
- "CreateIndexBuilder",
24
- "CreateSchemaBuilder",
25
- "CreateTableAsSelectBuilder",
26
- "CreateTableBuilder",
24
+ "CreateIndex",
25
+ "CreateMaterializedView",
26
+ "CreateSchema",
27
+ "CreateTable",
28
+ "CreateTableAsSelect",
29
+ "CreateView",
27
30
  "DDLBuilder",
28
- "DropIndexBuilder",
29
- "DropSchemaBuilder",
30
- "DropTableBuilder",
31
- "DropViewBuilder",
32
- "RenameTableBuilder",
33
- "TruncateTableBuilder",
31
+ "DropIndex",
32
+ "DropSchema",
33
+ "DropTable",
34
+ "DropView",
35
+ "RenameTable",
36
+ "TruncateTable",
34
37
  )
35
38
 
36
39
 
@@ -99,12 +102,12 @@ class ConstraintDefinition:
99
102
 
100
103
  # --- CREATE TABLE ---
101
104
  @dataclass
102
- class CreateTableBuilder(DDLBuilder):
105
+ class CreateTable(DDLBuilder):
103
106
  """Builder for CREATE TABLE statements with columns and constraints.
104
107
 
105
108
  Example:
106
109
  builder = (
107
- CreateTableBuilder("users")
110
+ CreateTable("users")
108
111
  .column("id", "SERIAL", primary_key=True)
109
112
  .column("email", "VARCHAR(255)", not_null=True, unique=True)
110
113
  .column("created_at", "TIMESTAMP", default="CURRENT_TIMESTAMP")
@@ -179,11 +182,9 @@ class CreateTableBuilder(DDLBuilder):
179
182
  if not dtype:
180
183
  self._raise_sql_builder_error("Column type must be a non-empty string")
181
184
 
182
- # Check for duplicate column names
183
185
  if any(col.name == name for col in self._columns):
184
186
  self._raise_sql_builder_error(f"Column '{name}' already defined")
185
187
 
186
- # Create column definition
187
188
  column_def = ColumnDefinition(
188
189
  name=name,
189
190
  dtype=dtype,
@@ -215,15 +216,12 @@ class CreateTableBuilder(DDLBuilder):
215
216
  if not col_list:
216
217
  self._raise_sql_builder_error("Primary key must include at least one column")
217
218
 
218
- # Check if primary key already exists
219
219
  existing_pk = next((c for c in self._constraints if c.constraint_type == "PRIMARY KEY"), None)
220
220
  if existing_pk:
221
- # Update existing primary key to include new columns
222
221
  for col in col_list:
223
222
  if col not in existing_pk.columns:
224
223
  existing_pk.columns.append(col)
225
224
  else:
226
- # Create new primary key constraint
227
225
  constraint = ConstraintDefinition(constraint_type="PRIMARY KEY", name=name, columns=col_list)
228
226
  self._constraints.append(constraint)
229
227
 
@@ -250,7 +248,6 @@ class CreateTableBuilder(DDLBuilder):
250
248
  if len(col_list) != len(ref_col_list):
251
249
  self._raise_sql_builder_error("Foreign key columns and referenced columns must have same length")
252
250
 
253
- # Validate actions
254
251
  valid_actions = {"CASCADE", "SET NULL", "SET DEFAULT", "RESTRICT", "NO ACTION", None}
255
252
  if on_delete and on_delete.upper() not in valid_actions:
256
253
  self._raise_sql_builder_error(f"Invalid ON DELETE action: {on_delete}")
@@ -285,12 +282,21 @@ class CreateTableBuilder(DDLBuilder):
285
282
  self._constraints.append(constraint)
286
283
  return self
287
284
 
288
- def check_constraint(self, condition: str, name: "Optional[str]" = None) -> "Self":
285
+ def check_constraint(self, condition: Union[str, "ColumnExpression"], name: "Optional[str]" = None) -> "Self":
289
286
  """Add a check constraint."""
290
287
  if not condition:
291
288
  self._raise_sql_builder_error("Check constraint must have a condition")
292
289
 
293
- constraint = ConstraintDefinition(constraint_type="CHECK", name=name, condition=condition)
290
+ condition_str: str
291
+ if hasattr(condition, "sqlglot_expression"):
292
+ # This is a ColumnExpression - render as raw SQL for DDL (no parameters)
293
+ sqlglot_expr = getattr(condition, "sqlglot_expression", None)
294
+ condition_str = sqlglot_expr.sql(dialect=self.dialect) if sqlglot_expr else str(condition)
295
+ else:
296
+ # String condition - use as-is
297
+ condition_str = str(condition)
298
+
299
+ constraint = ConstraintDefinition(constraint_type="CHECK", name=name, condition=condition_str)
294
300
 
295
301
  self._constraints.append(constraint)
296
302
  return self
@@ -325,19 +331,16 @@ class CreateTableBuilder(DDLBuilder):
325
331
  if not self._columns and not self._like_table:
326
332
  self._raise_sql_builder_error("Table must have at least one column or use LIKE clause")
327
333
 
328
- # Build table identifier with schema if provided
329
334
  if self._schema:
330
335
  table = exp.Table(this=exp.to_identifier(self._table_name), db=exp.to_identifier(self._schema))
331
336
  else:
332
337
  table = exp.to_table(self._table_name)
333
338
 
334
- # Build column expressions
335
339
  column_defs: list[exp.Expression] = []
336
340
  for col in self._columns:
337
341
  col_expr = build_column_expression(col)
338
342
  column_defs.append(col_expr)
339
343
 
340
- # Build constraint expressions
341
344
  for constraint in self._constraints:
342
345
  # Skip PRIMARY KEY constraints that are already defined on columns
343
346
  if constraint.constraint_type == "PRIMARY KEY" and len(constraint.columns) == 1:
@@ -349,7 +352,6 @@ class CreateTableBuilder(DDLBuilder):
349
352
  if constraint_expr:
350
353
  column_defs.append(constraint_expr)
351
354
 
352
- # Build properties for table options
353
355
  props: list[exp.Property] = []
354
356
  if self._table_options.get("engine"):
355
357
  props.append(
@@ -364,7 +366,6 @@ class CreateTableBuilder(DDLBuilder):
364
366
  exp.Property(this=exp.to_identifier("PARTITION BY"), value=exp.Literal.string(self._partition_by))
365
367
  )
366
368
 
367
- # Add other table options
368
369
  for key, value in self._table_options.items():
369
370
  if key != "engine": # Skip already handled options
370
371
  if isinstance(value, str):
@@ -374,15 +375,12 @@ class CreateTableBuilder(DDLBuilder):
374
375
 
375
376
  properties_node = exp.Properties(expressions=props) if props else None
376
377
 
377
- # Build schema expression
378
378
  schema_expr = exp.Schema(expressions=column_defs) if column_defs else None
379
379
 
380
- # Handle LIKE clause
381
380
  like_expr = None
382
381
  if self._like_table:
383
382
  like_expr = exp.to_table(self._like_table)
384
383
 
385
- # Create the CREATE expression
386
384
  return exp.Create(
387
385
  kind="TABLE",
388
386
  this=table,
@@ -406,13 +404,23 @@ class CreateTableBuilder(DDLBuilder):
406
404
 
407
405
  # --- DROP TABLE ---
408
406
  @dataclass
409
- class DropTableBuilder(DDLBuilder):
407
+ class DropTable(DDLBuilder):
410
408
  """Builder for DROP TABLE [IF EXISTS] ... [CASCADE|RESTRICT]."""
411
409
 
412
410
  _table_name: Optional[str] = None
413
411
  _if_exists: bool = False
414
412
  _cascade: Optional[bool] = None # True: CASCADE, False: RESTRICT, None: not set
415
413
 
414
+ def __init__(self, table_name: str, **kwargs: Any) -> None:
415
+ """Initialize DROP TABLE with table name.
416
+
417
+ Args:
418
+ table_name: Name of the table to drop
419
+ **kwargs: Additional DDLBuilder arguments
420
+ """
421
+ super().__init__(**kwargs)
422
+ self._table_name = table_name
423
+
416
424
  def table(self, name: str) -> Self:
417
425
  self._table_name = name
418
426
  return self
@@ -439,7 +447,7 @@ class DropTableBuilder(DDLBuilder):
439
447
 
440
448
  # --- DROP INDEX ---
441
449
  @dataclass
442
- class DropIndexBuilder(DDLBuilder):
450
+ class DropIndex(DDLBuilder):
443
451
  """Builder for DROP INDEX [IF EXISTS] ... [ON table] [CASCADE|RESTRICT]."""
444
452
 
445
453
  _index_name: Optional[str] = None
@@ -447,6 +455,16 @@ class DropIndexBuilder(DDLBuilder):
447
455
  _if_exists: bool = False
448
456
  _cascade: Optional[bool] = None
449
457
 
458
+ def __init__(self, index_name: str, **kwargs: Any) -> None:
459
+ """Initialize DROP INDEX with index name.
460
+
461
+ Args:
462
+ index_name: Name of the index to drop
463
+ **kwargs: Additional DDLBuilder arguments
464
+ """
465
+ super().__init__(**kwargs)
466
+ self._index_name = index_name
467
+
450
468
  def name(self, index_name: str) -> Self:
451
469
  self._index_name = index_name
452
470
  return self
@@ -481,7 +499,7 @@ class DropIndexBuilder(DDLBuilder):
481
499
 
482
500
  # --- DROP VIEW ---
483
501
  @dataclass
484
- class DropViewBuilder(DDLBuilder):
502
+ class DropView(DDLBuilder):
485
503
  """Builder for DROP VIEW [IF EXISTS] ... [CASCADE|RESTRICT]."""
486
504
 
487
505
  _view_name: Optional[str] = None
@@ -514,7 +532,7 @@ class DropViewBuilder(DDLBuilder):
514
532
 
515
533
  # --- DROP SCHEMA ---
516
534
  @dataclass
517
- class DropSchemaBuilder(DDLBuilder):
535
+ class DropSchema(DDLBuilder):
518
536
  """Builder for DROP SCHEMA [IF EXISTS] ... [CASCADE|RESTRICT]."""
519
537
 
520
538
  _schema_name: Optional[str] = None
@@ -547,7 +565,7 @@ class DropSchemaBuilder(DDLBuilder):
547
565
 
548
566
  # --- CREATE INDEX ---
549
567
  @dataclass
550
- class CreateIndexBuilder(DDLBuilder):
568
+ class CreateIndex(DDLBuilder):
551
569
  """Builder for CREATE [UNIQUE] INDEX [IF NOT EXISTS] ... ON ... (...).
552
570
 
553
571
  Supports columns, expressions, ordering, using, and where.
@@ -561,6 +579,19 @@ class CreateIndexBuilder(DDLBuilder):
561
579
  _using: Optional[str] = None
562
580
  _where: Optional[Union[str, exp.Expression]] = None
563
581
 
582
+ def __init__(self, index_name: str, **kwargs: Any) -> None:
583
+ """Initialize CREATE INDEX with index name.
584
+
585
+ Args:
586
+ index_name: Name of the index to create
587
+ **kwargs: Additional DDLBuilder arguments
588
+ """
589
+ super().__init__(**kwargs)
590
+ self._index_name = index_name
591
+ # Initialize dataclass fields that may not be set by super().__init__
592
+ if not hasattr(self, "_columns"):
593
+ self._columns = []
594
+
564
595
  def name(self, index_name: str) -> Self:
565
596
  self._index_name = index_name
566
597
  return self
@@ -620,7 +651,7 @@ class CreateIndexBuilder(DDLBuilder):
620
651
 
621
652
  # --- TRUNCATE TABLE ---
622
653
  @dataclass
623
- class TruncateTableBuilder(DDLBuilder):
654
+ class TruncateTable(DDLBuilder):
624
655
  """Builder for TRUNCATE TABLE ... [CASCADE|RESTRICT] [RESTART IDENTITY|CONTINUE IDENTITY]."""
625
656
 
626
657
  _table_name: Optional[str] = None
@@ -673,7 +704,7 @@ class AlterOperation:
673
704
 
674
705
  # --- CREATE SCHEMA ---
675
706
  @dataclass
676
- class CreateSchemaBuilder(DDLBuilder):
707
+ class CreateSchema(DDLBuilder):
677
708
  """Builder for CREATE SCHEMA [IF NOT EXISTS] schema_name [AUTHORIZATION user_name]."""
678
709
 
679
710
  _schema_name: Optional[str] = None
@@ -710,7 +741,7 @@ class CreateSchemaBuilder(DDLBuilder):
710
741
 
711
742
 
712
743
  @dataclass
713
- class CreateTableAsSelectBuilder(DDLBuilder):
744
+ class CreateTableAsSelect(DDLBuilder):
714
745
  """Builder for CREATE TABLE [IF NOT EXISTS] ... AS SELECT ... (CTAS).
715
746
 
716
747
  Supports optional column list and parameterized SELECT sources.
@@ -759,24 +790,33 @@ class CreateTableAsSelectBuilder(DDLBuilder):
759
790
  if self._select_query is None:
760
791
  self._raise_sql_builder_error("SELECT query must be set for CREATE TABLE AS SELECT.")
761
792
 
762
- # Determine the SELECT expression and parameters
763
793
  select_expr = None
764
794
  select_params = None
765
- from sqlspec.statement.builder.select import SelectBuilder
795
+ from sqlspec.statement.builder.select import Select
766
796
  from sqlspec.statement.sql import SQL
767
797
 
768
798
  if isinstance(self._select_query, SQL):
769
799
  select_expr = self._select_query.expression
770
800
  select_params = getattr(self._select_query, "parameters", None)
771
- elif isinstance(self._select_query, SelectBuilder):
801
+ elif isinstance(self._select_query, Select):
772
802
  select_expr = getattr(self._select_query, "_expression", None)
773
803
  select_params = getattr(self._select_query, "_parameters", None)
804
+
805
+ with_ctes = getattr(self._select_query, "_with_ctes", {})
806
+ if with_ctes and select_expr and isinstance(select_expr, exp.Select):
807
+ for alias, cte in with_ctes.items():
808
+ if hasattr(select_expr, "with_"):
809
+ select_expr = select_expr.with_(
810
+ cte.this, # The CTE's SELECT expression
811
+ as_=alias,
812
+ copy=False,
813
+ )
774
814
  elif isinstance(self._select_query, str):
775
815
  select_expr = exp.maybe_parse(self._select_query)
776
816
  select_params = None
777
817
  else:
778
818
  self._raise_sql_builder_error("Unsupported type for SELECT query in CTAS.")
779
- if select_expr is None or not isinstance(select_expr, exp.Select):
819
+ if select_expr is None:
780
820
  self._raise_sql_builder_error("SELECT query must be a valid SELECT expression.")
781
821
 
782
822
  # Merge parameters from SELECT if present
@@ -786,7 +826,6 @@ class CreateTableAsSelectBuilder(DDLBuilder):
786
826
  # The SELECT query already has unique parameter names
787
827
  self._parameters[p_name] = p_value
788
828
 
789
- # Build schema/column list if provided
790
829
  schema_expr = None
791
830
  if self._columns:
792
831
  schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
@@ -801,7 +840,7 @@ class CreateTableAsSelectBuilder(DDLBuilder):
801
840
 
802
841
 
803
842
  @dataclass
804
- class CreateMaterializedViewBuilder(DDLBuilder):
843
+ class CreateMaterializedView(DDLBuilder):
805
844
  """Builder for CREATE MATERIALIZED VIEW [IF NOT EXISTS] ... AS SELECT ...
806
845
 
807
846
  Supports optional column list, parameterized SELECT sources, and dialect-specific options.
@@ -868,16 +907,15 @@ class CreateMaterializedViewBuilder(DDLBuilder):
868
907
  if self._select_query is None:
869
908
  self._raise_sql_builder_error("SELECT query must be set for CREATE MATERIALIZED VIEW.")
870
909
 
871
- # Determine the SELECT expression and parameters
872
910
  select_expr = None
873
911
  select_params = None
874
- from sqlspec.statement.builder.select import SelectBuilder
912
+ from sqlspec.statement.builder.select import Select
875
913
  from sqlspec.statement.sql import SQL
876
914
 
877
915
  if isinstance(self._select_query, SQL):
878
916
  select_expr = self._select_query.expression
879
917
  select_params = getattr(self._select_query, "parameters", None)
880
- elif isinstance(self._select_query, SelectBuilder):
918
+ elif isinstance(self._select_query, Select):
881
919
  select_expr = getattr(self._select_query, "_expression", None)
882
920
  select_params = getattr(self._select_query, "_parameters", None)
883
921
  elif isinstance(self._select_query, str):
@@ -895,12 +933,10 @@ class CreateMaterializedViewBuilder(DDLBuilder):
895
933
  # The SELECT query already has unique parameter names
896
934
  self._parameters[p_name] = p_value
897
935
 
898
- # Build schema/column list if provided
899
936
  schema_expr = None
900
937
  if self._columns:
901
938
  schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
902
939
 
903
- # Build properties for dialect-specific options
904
940
  props: list[exp.Property] = []
905
941
  if self._refresh_mode:
906
942
  props.append(
@@ -932,7 +968,7 @@ class CreateMaterializedViewBuilder(DDLBuilder):
932
968
 
933
969
 
934
970
  @dataclass
935
- class CreateViewBuilder(DDLBuilder):
971
+ class CreateView(DDLBuilder):
936
972
  """Builder for CREATE VIEW [IF NOT EXISTS] ... AS SELECT ...
937
973
 
938
974
  Supports optional column list, parameterized SELECT sources, and hints.
@@ -970,16 +1006,15 @@ class CreateViewBuilder(DDLBuilder):
970
1006
  if self._select_query is None:
971
1007
  self._raise_sql_builder_error("SELECT query must be set for CREATE VIEW.")
972
1008
 
973
- # Determine the SELECT expression and parameters
974
1009
  select_expr = None
975
1010
  select_params = None
976
- from sqlspec.statement.builder.select import SelectBuilder
1011
+ from sqlspec.statement.builder.select import Select
977
1012
  from sqlspec.statement.sql import SQL
978
1013
 
979
1014
  if isinstance(self._select_query, SQL):
980
1015
  select_expr = self._select_query.expression
981
1016
  select_params = getattr(self._select_query, "parameters", None)
982
- elif isinstance(self._select_query, SelectBuilder):
1017
+ elif isinstance(self._select_query, Select):
983
1018
  select_expr = getattr(self._select_query, "_expression", None)
984
1019
  select_params = getattr(self._select_query, "_parameters", None)
985
1020
  elif isinstance(self._select_query, str):
@@ -997,12 +1032,10 @@ class CreateViewBuilder(DDLBuilder):
997
1032
  # The SELECT query already has unique parameter names
998
1033
  self._parameters[p_name] = p_value
999
1034
 
1000
- # Build schema/column list if provided
1001
1035
  schema_expr = None
1002
1036
  if self._columns:
1003
1037
  schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
1004
1038
 
1005
- # Build properties for hints
1006
1039
  props: list[exp.Property] = [
1007
1040
  exp.Property(this=exp.to_identifier("HINT"), value=exp.Literal.string(h)) for h in self._hints
1008
1041
  ]
@@ -1019,7 +1052,7 @@ class CreateViewBuilder(DDLBuilder):
1019
1052
 
1020
1053
 
1021
1054
  @dataclass
1022
- class AlterTableBuilder(DDLBuilder):
1055
+ class AlterTable(DDLBuilder):
1023
1056
  """Builder for ALTER TABLE with granular operations.
1024
1057
 
1025
1058
  Supports column operations (add, drop, alter type, rename) and constraint operations.
@@ -1124,7 +1157,7 @@ class AlterTableBuilder(DDLBuilder):
1124
1157
  name: "Optional[str]" = None,
1125
1158
  references_table: "Optional[str]" = None,
1126
1159
  references_columns: "Optional[Union[str, list[str]]]" = None,
1127
- condition: "Optional[str]" = None,
1160
+ condition: "Optional[Union[str, ColumnExpression]]" = None,
1128
1161
  on_delete: "Optional[str]" = None,
1129
1162
  on_update: "Optional[str]" = None,
1130
1163
  ) -> "Self":
@@ -1154,13 +1187,22 @@ class AlterTableBuilder(DDLBuilder):
1154
1187
  if references_columns is not None:
1155
1188
  ref_col_list = [references_columns] if isinstance(references_columns, str) else list(references_columns)
1156
1189
 
1190
+ # Handle ColumnExpression for CHECK constraints
1191
+ condition_str: Optional[str] = None
1192
+ if condition is not None:
1193
+ if hasattr(condition, "sqlglot_expression"):
1194
+ sqlglot_expr = getattr(condition, "sqlglot_expression", None)
1195
+ condition_str = sqlglot_expr.sql(dialect=self.dialect) if sqlglot_expr else str(condition)
1196
+ else:
1197
+ condition_str = str(condition)
1198
+
1157
1199
  constraint_def = ConstraintDefinition(
1158
1200
  constraint_type=constraint_type.upper(),
1159
1201
  name=name,
1160
1202
  columns=col_list or [],
1161
1203
  references_table=references_table,
1162
1204
  references_columns=ref_col_list or [],
1163
- condition=condition,
1205
+ condition=condition_str,
1164
1206
  on_delete=on_delete,
1165
1207
  on_update=on_update,
1166
1208
  )
@@ -1280,10 +1322,8 @@ class AlterTableBuilder(DDLBuilder):
1280
1322
  if not op.column_definition or op.column_definition.default is None:
1281
1323
  self._raise_sql_builder_error("Default value required for SET DEFAULT")
1282
1324
  default_val = op.column_definition.default
1283
- # Handle different default value types
1284
1325
  default_expr: Optional[exp.Expression]
1285
1326
  if isinstance(default_val, str):
1286
- # Check if it's a function/expression or a literal string
1287
1327
  if default_val.upper() in {"CURRENT_TIMESTAMP", "CURRENT_DATE", "CURRENT_TIME"} or "(" in default_val:
1288
1328
  default_expr = exp.maybe_parse(default_val)
1289
1329
  else:
@@ -1306,7 +1346,7 @@ class AlterTableBuilder(DDLBuilder):
1306
1346
 
1307
1347
 
1308
1348
  @dataclass
1309
- class CommentOnBuilder(DDLBuilder):
1349
+ class CommentOn(DDLBuilder):
1310
1350
  """Builder for COMMENT ON ... IS ... statements.
1311
1351
 
1312
1352
  Supports COMMENT ON TABLE and COMMENT ON COLUMN.
@@ -1335,12 +1375,10 @@ class CommentOnBuilder(DDLBuilder):
1335
1375
 
1336
1376
  def _create_base_expression(self) -> exp.Expression:
1337
1377
  if self._target_type == "TABLE" and self._table and self._comment is not None:
1338
- # Create a proper Comment expression
1339
1378
  return exp.Comment(
1340
1379
  this=exp.to_table(self._table), kind="TABLE", expression=exp.Literal.string(self._comment)
1341
1380
  )
1342
1381
  if self._target_type == "COLUMN" and self._table and self._column and self._comment is not None:
1343
- # Create a proper Comment expression for column
1344
1382
  return exp.Comment(
1345
1383
  this=exp.Column(table=self._table, this=self._column),
1346
1384
  kind="COLUMN",
@@ -1351,7 +1389,7 @@ class CommentOnBuilder(DDLBuilder):
1351
1389
 
1352
1390
 
1353
1391
  @dataclass
1354
- class RenameTableBuilder(DDLBuilder):
1392
+ class RenameTable(DDLBuilder):
1355
1393
  """Builder for ALTER TABLE ... RENAME TO ... statements.
1356
1394
 
1357
1395
  Supports renaming a table.
@@ -1371,7 +1409,6 @@ class RenameTableBuilder(DDLBuilder):
1371
1409
  def _create_base_expression(self) -> exp.Expression:
1372
1410
  if not self._old_name or not self._new_name:
1373
1411
  self._raise_sql_builder_error("Both old and new table names must be set for RENAME TABLE.")
1374
- # Create ALTER TABLE with RENAME TO action
1375
1412
  return exp.Alter(
1376
1413
  this=exp.to_table(self._old_name),
1377
1414
  kind="TABLE",
@@ -5,7 +5,7 @@ with automatic parameter binding and validation.
5
5
  """
6
6
 
7
7
  from dataclasses import dataclass, field
8
- from typing import Optional
8
+ from typing import Any, Optional
9
9
 
10
10
  from sqlglot import exp
11
11
 
@@ -14,11 +14,11 @@ from sqlspec.statement.builder.mixins import DeleteFromClauseMixin, ReturningCla
14
14
  from sqlspec.statement.result import SQLResult
15
15
  from sqlspec.typing import RowT
16
16
 
17
- __all__ = ("DeleteBuilder",)
17
+ __all__ = ("Delete",)
18
18
 
19
19
 
20
20
  @dataclass(unsafe_hash=True)
21
- class DeleteBuilder(QueryBuilder[RowT], WhereClauseMixin, ReturningClauseMixin, DeleteFromClauseMixin):
21
+ class Delete(QueryBuilder[RowT], WhereClauseMixin, ReturningClauseMixin, DeleteFromClauseMixin):
22
22
  """Builder for DELETE statements.
23
23
 
24
24
  This builder provides a fluent interface for constructing SQL DELETE statements
@@ -28,13 +28,14 @@ class DeleteBuilder(QueryBuilder[RowT], WhereClauseMixin, ReturningClauseMixin,
28
28
  Example:
29
29
  ```python
30
30
  # Basic DELETE
31
- delete_query = (
32
- DeleteBuilder().from_("users").where("age < 18")
33
- )
31
+ delete_query = Delete().from_("users").where("age < 18")
32
+
33
+ # Even more concise with constructor
34
+ delete_query = Delete("users").where("age < 18")
34
35
 
35
36
  # DELETE with parameterized conditions
36
37
  delete_query = (
37
- DeleteBuilder()
38
+ Delete()
38
39
  .from_("users")
39
40
  .where_eq("status", "inactive")
40
41
  .where_in("category", ["test", "demo"])
@@ -44,6 +45,21 @@ class DeleteBuilder(QueryBuilder[RowT], WhereClauseMixin, ReturningClauseMixin,
44
45
 
45
46
  _table: "Optional[str]" = field(default=None, init=False)
46
47
 
48
+ def __init__(self, table: Optional[str] = None, **kwargs: Any) -> None:
49
+ """Initialize DELETE with optional table.
50
+
51
+ Args:
52
+ table: Target table name
53
+ **kwargs: Additional QueryBuilder arguments
54
+ """
55
+ super().__init__(**kwargs)
56
+
57
+ # Initialize fields from dataclass
58
+ self._table = None
59
+
60
+ if table:
61
+ self.from_(table)
62
+
47
63
  @property
48
64
  def _expected_result_type(self) -> "type[SQLResult[RowT]]":
49
65
  """Get the expected result type for DELETE operations.