sqlspec 0.12.2__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 +100 -130
  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 +125 -167
  12. sqlspec/adapters/duckdb/config.py +3 -6
  13. sqlspec/adapters/duckdb/driver.py +114 -111
  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 +18 -31
  19. sqlspec/adapters/psycopg/driver.py +283 -236
  20. sqlspec/adapters/sqlite/config.py +3 -3
  21. sqlspec/adapters/sqlite/driver.py +103 -97
  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 +67 -181
  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 +25 -90
  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 +67 -22
  47. sqlspec/statement/builder/column.py +283 -0
  48. sqlspec/statement/builder/ddl.py +91 -67
  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 +21 -20
  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 +628 -58
  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 +863 -391
  88. sqlspec/statement/sql_compiler.py +140 -0
  89. sqlspec/storage/__init__.py +10 -2
  90. sqlspec/storage/backends/fsspec.py +53 -8
  91. sqlspec/storage/backends/obstore.py +15 -19
  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.2.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 -173
  110. sqlspec-0.12.2.dist-info/RECORD +0 -145
  111. {sqlspec-0.12.2.dist-info → sqlspec-0.13.0.dist-info}/WHEEL +0 -0
  112. {sqlspec-0.12.2.dist-info → sqlspec-0.13.0.dist-info}/licenses/LICENSE +0 -0
  113. {sqlspec-0.12.2.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,20 @@ 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):
772
- # Get the expression and parameters directly
801
+ elif isinstance(self._select_query, Select):
773
802
  select_expr = getattr(self._select_query, "_expression", None)
774
803
  select_params = getattr(self._select_query, "_parameters", None)
775
804
 
776
- # Apply CTEs if present
777
805
  with_ctes = getattr(self._select_query, "_with_ctes", {})
778
806
  if with_ctes and select_expr and isinstance(select_expr, exp.Select):
779
- # Apply CTEs directly to the SELECT expression using sqlglot's with_ method
780
807
  for alias, cte in with_ctes.items():
781
808
  if hasattr(select_expr, "with_"):
782
809
  select_expr = select_expr.with_(
@@ -799,7 +826,6 @@ class CreateTableAsSelectBuilder(DDLBuilder):
799
826
  # The SELECT query already has unique parameter names
800
827
  self._parameters[p_name] = p_value
801
828
 
802
- # Build schema/column list if provided
803
829
  schema_expr = None
804
830
  if self._columns:
805
831
  schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
@@ -814,7 +840,7 @@ class CreateTableAsSelectBuilder(DDLBuilder):
814
840
 
815
841
 
816
842
  @dataclass
817
- class CreateMaterializedViewBuilder(DDLBuilder):
843
+ class CreateMaterializedView(DDLBuilder):
818
844
  """Builder for CREATE MATERIALIZED VIEW [IF NOT EXISTS] ... AS SELECT ...
819
845
 
820
846
  Supports optional column list, parameterized SELECT sources, and dialect-specific options.
@@ -881,16 +907,15 @@ class CreateMaterializedViewBuilder(DDLBuilder):
881
907
  if self._select_query is None:
882
908
  self._raise_sql_builder_error("SELECT query must be set for CREATE MATERIALIZED VIEW.")
883
909
 
884
- # Determine the SELECT expression and parameters
885
910
  select_expr = None
886
911
  select_params = None
887
- from sqlspec.statement.builder.select import SelectBuilder
912
+ from sqlspec.statement.builder.select import Select
888
913
  from sqlspec.statement.sql import SQL
889
914
 
890
915
  if isinstance(self._select_query, SQL):
891
916
  select_expr = self._select_query.expression
892
917
  select_params = getattr(self._select_query, "parameters", None)
893
- elif isinstance(self._select_query, SelectBuilder):
918
+ elif isinstance(self._select_query, Select):
894
919
  select_expr = getattr(self._select_query, "_expression", None)
895
920
  select_params = getattr(self._select_query, "_parameters", None)
896
921
  elif isinstance(self._select_query, str):
@@ -908,12 +933,10 @@ class CreateMaterializedViewBuilder(DDLBuilder):
908
933
  # The SELECT query already has unique parameter names
909
934
  self._parameters[p_name] = p_value
910
935
 
911
- # Build schema/column list if provided
912
936
  schema_expr = None
913
937
  if self._columns:
914
938
  schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
915
939
 
916
- # Build properties for dialect-specific options
917
940
  props: list[exp.Property] = []
918
941
  if self._refresh_mode:
919
942
  props.append(
@@ -945,7 +968,7 @@ class CreateMaterializedViewBuilder(DDLBuilder):
945
968
 
946
969
 
947
970
  @dataclass
948
- class CreateViewBuilder(DDLBuilder):
971
+ class CreateView(DDLBuilder):
949
972
  """Builder for CREATE VIEW [IF NOT EXISTS] ... AS SELECT ...
950
973
 
951
974
  Supports optional column list, parameterized SELECT sources, and hints.
@@ -983,16 +1006,15 @@ class CreateViewBuilder(DDLBuilder):
983
1006
  if self._select_query is None:
984
1007
  self._raise_sql_builder_error("SELECT query must be set for CREATE VIEW.")
985
1008
 
986
- # Determine the SELECT expression and parameters
987
1009
  select_expr = None
988
1010
  select_params = None
989
- from sqlspec.statement.builder.select import SelectBuilder
1011
+ from sqlspec.statement.builder.select import Select
990
1012
  from sqlspec.statement.sql import SQL
991
1013
 
992
1014
  if isinstance(self._select_query, SQL):
993
1015
  select_expr = self._select_query.expression
994
1016
  select_params = getattr(self._select_query, "parameters", None)
995
- elif isinstance(self._select_query, SelectBuilder):
1017
+ elif isinstance(self._select_query, Select):
996
1018
  select_expr = getattr(self._select_query, "_expression", None)
997
1019
  select_params = getattr(self._select_query, "_parameters", None)
998
1020
  elif isinstance(self._select_query, str):
@@ -1010,12 +1032,10 @@ class CreateViewBuilder(DDLBuilder):
1010
1032
  # The SELECT query already has unique parameter names
1011
1033
  self._parameters[p_name] = p_value
1012
1034
 
1013
- # Build schema/column list if provided
1014
1035
  schema_expr = None
1015
1036
  if self._columns:
1016
1037
  schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
1017
1038
 
1018
- # Build properties for hints
1019
1039
  props: list[exp.Property] = [
1020
1040
  exp.Property(this=exp.to_identifier("HINT"), value=exp.Literal.string(h)) for h in self._hints
1021
1041
  ]
@@ -1032,7 +1052,7 @@ class CreateViewBuilder(DDLBuilder):
1032
1052
 
1033
1053
 
1034
1054
  @dataclass
1035
- class AlterTableBuilder(DDLBuilder):
1055
+ class AlterTable(DDLBuilder):
1036
1056
  """Builder for ALTER TABLE with granular operations.
1037
1057
 
1038
1058
  Supports column operations (add, drop, alter type, rename) and constraint operations.
@@ -1137,7 +1157,7 @@ class AlterTableBuilder(DDLBuilder):
1137
1157
  name: "Optional[str]" = None,
1138
1158
  references_table: "Optional[str]" = None,
1139
1159
  references_columns: "Optional[Union[str, list[str]]]" = None,
1140
- condition: "Optional[str]" = None,
1160
+ condition: "Optional[Union[str, ColumnExpression]]" = None,
1141
1161
  on_delete: "Optional[str]" = None,
1142
1162
  on_update: "Optional[str]" = None,
1143
1163
  ) -> "Self":
@@ -1167,13 +1187,22 @@ class AlterTableBuilder(DDLBuilder):
1167
1187
  if references_columns is not None:
1168
1188
  ref_col_list = [references_columns] if isinstance(references_columns, str) else list(references_columns)
1169
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
+
1170
1199
  constraint_def = ConstraintDefinition(
1171
1200
  constraint_type=constraint_type.upper(),
1172
1201
  name=name,
1173
1202
  columns=col_list or [],
1174
1203
  references_table=references_table,
1175
1204
  references_columns=ref_col_list or [],
1176
- condition=condition,
1205
+ condition=condition_str,
1177
1206
  on_delete=on_delete,
1178
1207
  on_update=on_update,
1179
1208
  )
@@ -1293,10 +1322,8 @@ class AlterTableBuilder(DDLBuilder):
1293
1322
  if not op.column_definition or op.column_definition.default is None:
1294
1323
  self._raise_sql_builder_error("Default value required for SET DEFAULT")
1295
1324
  default_val = op.column_definition.default
1296
- # Handle different default value types
1297
1325
  default_expr: Optional[exp.Expression]
1298
1326
  if isinstance(default_val, str):
1299
- # Check if it's a function/expression or a literal string
1300
1327
  if default_val.upper() in {"CURRENT_TIMESTAMP", "CURRENT_DATE", "CURRENT_TIME"} or "(" in default_val:
1301
1328
  default_expr = exp.maybe_parse(default_val)
1302
1329
  else:
@@ -1319,7 +1346,7 @@ class AlterTableBuilder(DDLBuilder):
1319
1346
 
1320
1347
 
1321
1348
  @dataclass
1322
- class CommentOnBuilder(DDLBuilder):
1349
+ class CommentOn(DDLBuilder):
1323
1350
  """Builder for COMMENT ON ... IS ... statements.
1324
1351
 
1325
1352
  Supports COMMENT ON TABLE and COMMENT ON COLUMN.
@@ -1348,12 +1375,10 @@ class CommentOnBuilder(DDLBuilder):
1348
1375
 
1349
1376
  def _create_base_expression(self) -> exp.Expression:
1350
1377
  if self._target_type == "TABLE" and self._table and self._comment is not None:
1351
- # Create a proper Comment expression
1352
1378
  return exp.Comment(
1353
1379
  this=exp.to_table(self._table), kind="TABLE", expression=exp.Literal.string(self._comment)
1354
1380
  )
1355
1381
  if self._target_type == "COLUMN" and self._table and self._column and self._comment is not None:
1356
- # Create a proper Comment expression for column
1357
1382
  return exp.Comment(
1358
1383
  this=exp.Column(table=self._table, this=self._column),
1359
1384
  kind="COLUMN",
@@ -1364,7 +1389,7 @@ class CommentOnBuilder(DDLBuilder):
1364
1389
 
1365
1390
 
1366
1391
  @dataclass
1367
- class RenameTableBuilder(DDLBuilder):
1392
+ class RenameTable(DDLBuilder):
1368
1393
  """Builder for ALTER TABLE ... RENAME TO ... statements.
1369
1394
 
1370
1395
  Supports renaming a table.
@@ -1384,7 +1409,6 @@ class RenameTableBuilder(DDLBuilder):
1384
1409
  def _create_base_expression(self) -> exp.Expression:
1385
1410
  if not self._old_name or not self._new_name:
1386
1411
  self._raise_sql_builder_error("Both old and new table names must be set for RENAME TABLE.")
1387
- # Create ALTER TABLE with RENAME TO action
1388
1412
  return exp.Alter(
1389
1413
  this=exp.to_table(self._old_name),
1390
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.
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
25
25
  from collections.abc import Mapping, Sequence
26
26
 
27
27
 
28
- __all__ = ("InsertBuilder",)
28
+ __all__ = ("Insert",)
29
29
 
30
30
  ERR_MSG_TABLE_NOT_SET = "The target table must be set using .into() before adding values."
31
31
  ERR_MSG_VALUES_COLUMNS_MISMATCH = (
@@ -36,9 +36,7 @@ ERR_MSG_EXPRESSION_NOT_INITIALIZED = "Internal error: base expression not initia
36
36
 
37
37
 
38
38
  @dataclass(unsafe_hash=True)
39
- class InsertBuilder(
40
- QueryBuilder[RowT], ReturningClauseMixin, InsertValuesMixin, InsertFromSelectMixin, InsertIntoClauseMixin
41
- ):
39
+ class Insert(QueryBuilder[RowT], ReturningClauseMixin, InsertValuesMixin, InsertFromSelectMixin, InsertIntoClauseMixin):
42
40
  """Builder for INSERT statements.
43
41
 
44
42
  This builder facilitates the construction of SQL INSERT queries
@@ -48,15 +46,20 @@ class InsertBuilder(
48
46
  ```python
49
47
  # Basic INSERT with values
50
48
  insert_query = (
51
- InsertBuilder()
49
+ Insert()
52
50
  .into("users")
53
51
  .columns("name", "email", "age")
54
52
  .values("John Doe", "john@example.com", 30)
55
53
  )
56
54
 
55
+ # Even more concise with constructor
56
+ insert_query = Insert("users").values(
57
+ {"name": "John", "age": 30}
58
+ )
59
+
57
60
  # Multi-row INSERT
58
61
  insert_query = (
59
- InsertBuilder()
62
+ Insert()
60
63
  .into("users")
61
64
  .columns("name", "email")
62
65
  .values("John", "john@example.com")
@@ -65,7 +68,7 @@ class InsertBuilder(
65
68
 
66
69
  # INSERT from dictionary
67
70
  insert_query = (
68
- InsertBuilder()
71
+ Insert()
69
72
  .into("users")
70
73
  .values_from_dict(
71
74
  {"name": "John", "email": "john@example.com"}
@@ -74,10 +77,10 @@ class InsertBuilder(
74
77
 
75
78
  # INSERT from SELECT
76
79
  insert_query = (
77
- InsertBuilder()
80
+ Insert()
78
81
  .into("users_backup")
79
82
  .from_select(
80
- SelectBuilder()
83
+ Select()
81
84
  .select("name", "email")
82
85
  .from_("users")
83
86
  .where("active = true")
@@ -90,6 +93,23 @@ class InsertBuilder(
90
93
  _columns: list[str] = field(default_factory=list, init=False)
91
94
  _values_added_count: int = field(default=0, init=False)
92
95
 
96
+ def __init__(self, table: Optional[str] = None, **kwargs: Any) -> None:
97
+ """Initialize INSERT with optional table.
98
+
99
+ Args:
100
+ table: Target table name
101
+ **kwargs: Additional QueryBuilder arguments
102
+ """
103
+ super().__init__(**kwargs)
104
+
105
+ # Initialize fields from dataclass
106
+ self._table = None
107
+ self._columns = []
108
+ self._values_added_count = 0
109
+
110
+ if table:
111
+ self.into(table)
112
+
93
113
  def _create_base_expression(self) -> exp.Insert:
94
114
  """Create a base INSERT expression.
95
115
 
@@ -165,7 +185,6 @@ class InsertBuilder(
165
185
  else:
166
186
  # This case should ideally not be reached if logic is correct:
167
187
  # means _values_added_count > 0 but expression is not exp.Values.
168
- # Fallback to creating a new Values node, though this might indicate an issue.
169
188
  new_values_node = exp.Values(expressions=[exp.Tuple(expressions=list(value_placeholders))])
170
189
  insert_expr.set("expression", new_values_node)
171
190
 
@@ -191,14 +210,12 @@ class InsertBuilder(
191
210
  raise SQLBuilderError(ERR_MSG_TABLE_NOT_SET)
192
211
 
193
212
  if not self._columns:
194
- # Set columns from dictionary keys if not already set
195
213
  self.columns(*data.keys())
196
214
  elif set(self._columns) != set(data.keys()):
197
215
  # Verify that dictionary keys match existing columns
198
216
  msg = f"Dictionary keys {set(data.keys())} do not match existing columns {set(self._columns)}."
199
217
  raise SQLBuilderError(msg)
200
218
 
201
- # Add values in the same order as columns
202
219
  return self.values(*[data[col] for col in self._columns])
203
220
 
204
221
  def values_from_dicts(self, data: "Sequence[Mapping[str, Any]]") -> "Self":
@@ -219,12 +236,10 @@ class InsertBuilder(
219
236
  if not data:
220
237
  return self
221
238
 
222
- # Use the first dictionary to establish columns
223
239
  first_dict = data[0]
224
240
  if not self._columns:
225
241
  self.columns(*first_dict.keys())
226
242
 
227
- # Validate that all dictionaries have the same keys
228
243
  expected_keys = set(self._columns)
229
244
  for i, row_dict in enumerate(data):
230
245
  if set(row_dict.keys()) != expected_keys:
@@ -234,7 +249,6 @@ class InsertBuilder(
234
249
  )
235
250
  raise SQLBuilderError(msg)
236
251
 
237
- # Add each row
238
252
  for row_dict in data:
239
253
  self.values(*[row_dict[col] for col in self._columns])
240
254