sqlspec 0.25.0__py3-none-any.whl → 0.27.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 (199) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +256 -24
  3. sqlspec/_typing.py +71 -52
  4. sqlspec/adapters/adbc/_types.py +1 -1
  5. sqlspec/adapters/adbc/adk/__init__.py +5 -0
  6. sqlspec/adapters/adbc/adk/store.py +870 -0
  7. sqlspec/adapters/adbc/config.py +69 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +340 -0
  9. sqlspec/adapters/adbc/driver.py +266 -58
  10. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  11. sqlspec/adapters/adbc/litestar/store.py +504 -0
  12. sqlspec/adapters/adbc/type_converter.py +153 -0
  13. sqlspec/adapters/aiosqlite/_types.py +1 -1
  14. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  15. sqlspec/adapters/aiosqlite/adk/store.py +527 -0
  16. sqlspec/adapters/aiosqlite/config.py +88 -15
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
  18. sqlspec/adapters/aiosqlite/driver.py +143 -40
  19. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  20. sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
  21. sqlspec/adapters/aiosqlite/pool.py +7 -7
  22. sqlspec/adapters/asyncmy/__init__.py +7 -1
  23. sqlspec/adapters/asyncmy/_types.py +2 -2
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +493 -0
  26. sqlspec/adapters/asyncmy/config.py +68 -23
  27. sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
  28. sqlspec/adapters/asyncmy/driver.py +313 -58
  29. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  30. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  31. sqlspec/adapters/asyncpg/__init__.py +2 -1
  32. sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
  33. sqlspec/adapters/asyncpg/_types.py +11 -7
  34. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  35. sqlspec/adapters/asyncpg/adk/store.py +450 -0
  36. sqlspec/adapters/asyncpg/config.py +59 -35
  37. sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
  38. sqlspec/adapters/asyncpg/driver.py +170 -25
  39. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  40. sqlspec/adapters/asyncpg/litestar/store.py +253 -0
  41. sqlspec/adapters/bigquery/_types.py +1 -1
  42. sqlspec/adapters/bigquery/adk/__init__.py +5 -0
  43. sqlspec/adapters/bigquery/adk/store.py +576 -0
  44. sqlspec/adapters/bigquery/config.py +27 -10
  45. sqlspec/adapters/bigquery/data_dictionary.py +149 -0
  46. sqlspec/adapters/bigquery/driver.py +368 -142
  47. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  48. sqlspec/adapters/bigquery/litestar/store.py +327 -0
  49. sqlspec/adapters/bigquery/type_converter.py +125 -0
  50. sqlspec/adapters/duckdb/_types.py +1 -1
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +553 -0
  53. sqlspec/adapters/duckdb/config.py +80 -20
  54. sqlspec/adapters/duckdb/data_dictionary.py +163 -0
  55. sqlspec/adapters/duckdb/driver.py +167 -45
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +4 -4
  59. sqlspec/adapters/duckdb/type_converter.py +133 -0
  60. sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
  61. sqlspec/adapters/oracledb/_types.py +20 -2
  62. sqlspec/adapters/oracledb/adk/__init__.py +5 -0
  63. sqlspec/adapters/oracledb/adk/store.py +1745 -0
  64. sqlspec/adapters/oracledb/config.py +122 -32
  65. sqlspec/adapters/oracledb/data_dictionary.py +509 -0
  66. sqlspec/adapters/oracledb/driver.py +353 -91
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +767 -0
  69. sqlspec/adapters/oracledb/migrations.py +348 -73
  70. sqlspec/adapters/oracledb/type_converter.py +207 -0
  71. sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
  72. sqlspec/adapters/psqlpy/_types.py +2 -1
  73. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  74. sqlspec/adapters/psqlpy/adk/store.py +482 -0
  75. sqlspec/adapters/psqlpy/config.py +46 -17
  76. sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
  77. sqlspec/adapters/psqlpy/driver.py +123 -209
  78. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  79. sqlspec/adapters/psqlpy/litestar/store.py +272 -0
  80. sqlspec/adapters/psqlpy/type_converter.py +102 -0
  81. sqlspec/adapters/psycopg/_type_handlers.py +80 -0
  82. sqlspec/adapters/psycopg/_types.py +2 -1
  83. sqlspec/adapters/psycopg/adk/__init__.py +5 -0
  84. sqlspec/adapters/psycopg/adk/store.py +944 -0
  85. sqlspec/adapters/psycopg/config.py +69 -35
  86. sqlspec/adapters/psycopg/data_dictionary.py +331 -0
  87. sqlspec/adapters/psycopg/driver.py +238 -81
  88. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  89. sqlspec/adapters/psycopg/litestar/store.py +554 -0
  90. sqlspec/adapters/sqlite/__init__.py +2 -1
  91. sqlspec/adapters/sqlite/_type_handlers.py +86 -0
  92. sqlspec/adapters/sqlite/_types.py +1 -1
  93. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  94. sqlspec/adapters/sqlite/adk/store.py +572 -0
  95. sqlspec/adapters/sqlite/config.py +87 -15
  96. sqlspec/adapters/sqlite/data_dictionary.py +149 -0
  97. sqlspec/adapters/sqlite/driver.py +137 -54
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +18 -9
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +162 -89
  104. sqlspec/builder/_column.py +62 -29
  105. sqlspec/builder/_ddl.py +180 -121
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +53 -94
  109. sqlspec/builder/_insert.py +32 -131
  110. sqlspec/builder/_join.py +375 -0
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +111 -17
  113. sqlspec/builder/_select.py +1457 -24
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +307 -194
  116. sqlspec/config.py +252 -67
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +17 -17
  119. sqlspec/core/compiler.py +62 -9
  120. sqlspec/core/filters.py +37 -37
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +83 -48
  123. sqlspec/core/result.py +102 -46
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +36 -30
  126. sqlspec/core/type_conversion.py +235 -0
  127. sqlspec/driver/__init__.py +7 -6
  128. sqlspec/driver/_async.py +188 -151
  129. sqlspec/driver/_common.py +285 -80
  130. sqlspec/driver/_sync.py +188 -152
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +75 -7
  134. sqlspec/extensions/adk/__init__.py +53 -0
  135. sqlspec/extensions/adk/_types.py +51 -0
  136. sqlspec/extensions/adk/converters.py +172 -0
  137. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
  138. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  139. sqlspec/extensions/adk/service.py +181 -0
  140. sqlspec/extensions/adk/store.py +536 -0
  141. sqlspec/extensions/aiosql/adapter.py +73 -53
  142. sqlspec/extensions/litestar/__init__.py +21 -4
  143. sqlspec/extensions/litestar/cli.py +54 -10
  144. sqlspec/extensions/litestar/config.py +59 -266
  145. sqlspec/extensions/litestar/handlers.py +46 -17
  146. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  147. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  148. sqlspec/extensions/litestar/plugin.py +324 -223
  149. sqlspec/extensions/litestar/providers.py +25 -25
  150. sqlspec/extensions/litestar/store.py +265 -0
  151. sqlspec/loader.py +30 -49
  152. sqlspec/migrations/__init__.py +4 -3
  153. sqlspec/migrations/base.py +302 -39
  154. sqlspec/migrations/commands.py +611 -144
  155. sqlspec/migrations/context.py +142 -0
  156. sqlspec/migrations/fix.py +199 -0
  157. sqlspec/migrations/loaders.py +68 -23
  158. sqlspec/migrations/runner.py +543 -107
  159. sqlspec/migrations/tracker.py +237 -21
  160. sqlspec/migrations/utils.py +51 -3
  161. sqlspec/migrations/validation.py +177 -0
  162. sqlspec/protocols.py +66 -36
  163. sqlspec/storage/_utils.py +98 -0
  164. sqlspec/storage/backends/fsspec.py +134 -106
  165. sqlspec/storage/backends/local.py +78 -51
  166. sqlspec/storage/backends/obstore.py +278 -162
  167. sqlspec/storage/registry.py +75 -39
  168. sqlspec/typing.py +16 -84
  169. sqlspec/utils/config_resolver.py +153 -0
  170. sqlspec/utils/correlation.py +4 -5
  171. sqlspec/utils/data_transformation.py +3 -2
  172. sqlspec/utils/deprecation.py +9 -8
  173. sqlspec/utils/fixtures.py +4 -4
  174. sqlspec/utils/logging.py +46 -6
  175. sqlspec/utils/module_loader.py +2 -2
  176. sqlspec/utils/schema.py +288 -0
  177. sqlspec/utils/serializers.py +50 -2
  178. sqlspec/utils/sync_tools.py +21 -17
  179. sqlspec/utils/text.py +1 -2
  180. sqlspec/utils/type_guards.py +111 -20
  181. sqlspec/utils/version.py +433 -0
  182. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
  183. sqlspec-0.27.0.dist-info/RECORD +207 -0
  184. sqlspec/builder/mixins/__init__.py +0 -55
  185. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
  186. sqlspec/builder/mixins/_delete_operations.py +0 -50
  187. sqlspec/builder/mixins/_insert_operations.py +0 -282
  188. sqlspec/builder/mixins/_join_operations.py +0 -389
  189. sqlspec/builder/mixins/_merge_operations.py +0 -592
  190. sqlspec/builder/mixins/_order_limit_operations.py +0 -152
  191. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  192. sqlspec/builder/mixins/_select_operations.py +0 -936
  193. sqlspec/builder/mixins/_update_operations.py +0 -218
  194. sqlspec/builder/mixins/_where_clause.py +0 -1304
  195. sqlspec-0.25.0.dist-info/RECORD +0 -139
  196. sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
  197. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
  198. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
  199. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
sqlspec/builder/_ddl.py CHANGED
@@ -4,19 +4,21 @@ Provides builders for DDL operations including CREATE, DROP, ALTER,
4
4
  TRUNCATE, and other schema manipulation statements.
5
5
  """
6
6
 
7
- from typing import TYPE_CHECKING, Any, Optional, Union
7
+ from typing import TYPE_CHECKING, Any, Union
8
8
 
9
9
  from sqlglot import exp
10
10
  from sqlglot.dialects.dialect import DialectType
11
11
  from typing_extensions import Self
12
12
 
13
13
  from sqlspec.builder._base import QueryBuilder, SafeQuery
14
+ from sqlspec.builder._select import Select
14
15
  from sqlspec.core.result import SQLResult
16
+ from sqlspec.core.statement import SQL
15
17
  from sqlspec.utils.type_guards import has_sqlglot_expression, has_with_method
16
18
 
17
19
  if TYPE_CHECKING:
18
20
  from sqlspec.builder._column import ColumnExpression
19
- from sqlspec.core.statement import SQL, StatementConfig
21
+ from sqlspec.core.statement import StatementConfig
20
22
 
21
23
  __all__ = (
22
24
  "AlterOperation",
@@ -39,6 +41,37 @@ __all__ = (
39
41
  "Truncate",
40
42
  )
41
43
 
44
+ CONSTRAINT_TYPE_PRIMARY_KEY = "PRIMARY KEY"
45
+ CONSTRAINT_TYPE_FOREIGN_KEY = "FOREIGN KEY"
46
+ CONSTRAINT_TYPE_UNIQUE = "UNIQUE"
47
+ CONSTRAINT_TYPE_CHECK = "CHECK"
48
+
49
+ FOREIGN_KEY_ACTION_CASCADE = "CASCADE"
50
+ FOREIGN_KEY_ACTION_SET_NULL = "SET NULL"
51
+ FOREIGN_KEY_ACTION_SET_DEFAULT = "SET DEFAULT"
52
+ FOREIGN_KEY_ACTION_RESTRICT = "RESTRICT"
53
+ FOREIGN_KEY_ACTION_NO_ACTION = "NO ACTION"
54
+
55
+ VALID_FOREIGN_KEY_ACTIONS = {
56
+ FOREIGN_KEY_ACTION_CASCADE,
57
+ FOREIGN_KEY_ACTION_SET_NULL,
58
+ FOREIGN_KEY_ACTION_SET_DEFAULT,
59
+ FOREIGN_KEY_ACTION_RESTRICT,
60
+ FOREIGN_KEY_ACTION_NO_ACTION,
61
+ None,
62
+ }
63
+
64
+ VALID_CONSTRAINT_TYPES = {
65
+ CONSTRAINT_TYPE_PRIMARY_KEY,
66
+ CONSTRAINT_TYPE_FOREIGN_KEY,
67
+ CONSTRAINT_TYPE_UNIQUE,
68
+ CONSTRAINT_TYPE_CHECK,
69
+ }
70
+
71
+ CURRENT_TIMESTAMP_KEYWORD = "CURRENT_TIMESTAMP"
72
+ CURRENT_DATE_KEYWORD = "CURRENT_DATE"
73
+ CURRENT_TIME_KEYWORD = "CURRENT_TIME"
74
+
42
75
 
43
76
  def build_column_expression(col: "ColumnDefinition") -> "exp.Expression":
44
77
  """Build SQLGlot expression for a column definition."""
@@ -56,14 +89,14 @@ def build_column_expression(col: "ColumnDefinition") -> "exp.Expression":
56
89
  constraints.append(exp.ColumnConstraint(kind=exp.UniqueColumnConstraint()))
57
90
 
58
91
  if col.default is not None:
59
- default_expr: Optional[exp.Expression] = None
92
+ default_expr: exp.Expression | None = None
60
93
  if isinstance(col.default, str):
61
94
  default_upper = col.default.upper()
62
- if default_upper == "CURRENT_TIMESTAMP":
95
+ if default_upper == CURRENT_TIMESTAMP_KEYWORD:
63
96
  default_expr = exp.CurrentTimestamp()
64
- elif default_upper == "CURRENT_DATE":
97
+ elif default_upper == CURRENT_DATE_KEYWORD:
65
98
  default_expr = exp.CurrentDate()
66
- elif default_upper == "CURRENT_TIME":
99
+ elif default_upper == CURRENT_TIME_KEYWORD:
67
100
  default_expr = exp.CurrentTime()
68
101
  elif "(" in col.default:
69
102
  default_expr = exp.maybe_parse(col.default)
@@ -94,16 +127,16 @@ def build_column_expression(col: "ColumnDefinition") -> "exp.Expression":
94
127
  return col_def
95
128
 
96
129
 
97
- def build_constraint_expression(constraint: "ConstraintDefinition") -> "Optional[exp.Expression]":
130
+ def build_constraint_expression(constraint: "ConstraintDefinition") -> "exp.Expression | None":
98
131
  """Build SQLGlot expression for a table constraint."""
99
- if constraint.constraint_type == "PRIMARY KEY":
132
+ if constraint.constraint_type == CONSTRAINT_TYPE_PRIMARY_KEY:
100
133
  pk_constraint = exp.PrimaryKey(expressions=[exp.to_identifier(col) for col in constraint.columns])
101
134
 
102
135
  if constraint.name:
103
136
  return exp.Constraint(this=exp.to_identifier(constraint.name), expression=pk_constraint)
104
137
  return pk_constraint
105
138
 
106
- if constraint.constraint_type == "FOREIGN KEY":
139
+ if constraint.constraint_type == CONSTRAINT_TYPE_FOREIGN_KEY:
107
140
  fk_constraint = exp.ForeignKey(
108
141
  expressions=[exp.to_identifier(col) for col in constraint.columns],
109
142
  reference=exp.Reference(
@@ -118,14 +151,14 @@ def build_constraint_expression(constraint: "ConstraintDefinition") -> "Optional
118
151
  return exp.Constraint(this=exp.to_identifier(constraint.name), expression=fk_constraint)
119
152
  return fk_constraint
120
153
 
121
- if constraint.constraint_type == "UNIQUE":
154
+ if constraint.constraint_type == CONSTRAINT_TYPE_UNIQUE:
122
155
  unique_constraint = exp.UniqueKeyProperty(expressions=[exp.to_identifier(col) for col in constraint.columns])
123
156
 
124
157
  if constraint.name:
125
158
  return exp.Constraint(this=exp.to_identifier(constraint.name), expression=unique_constraint)
126
159
  return unique_constraint
127
160
 
128
- if constraint.constraint_type == "CHECK":
161
+ if constraint.constraint_type == CONSTRAINT_TYPE_CHECK:
129
162
  check_expr = exp.Check(this=exp.maybe_parse(constraint.condition) if constraint.condition else None)
130
163
 
131
164
  if constraint.name:
@@ -142,7 +175,7 @@ class DDLBuilder(QueryBuilder):
142
175
 
143
176
  def __init__(self, dialect: DialectType = None) -> None:
144
177
  super().__init__(dialect=dialect)
145
- self._expression: Optional[exp.Expression] = None
178
+ self._expression: exp.Expression | None = None
146
179
 
147
180
  def _create_base_expression(self) -> exp.Expression:
148
181
  msg = "Subclasses must implement _create_base_expression."
@@ -157,7 +190,7 @@ class DDLBuilder(QueryBuilder):
157
190
  self._expression = self._create_base_expression()
158
191
  return super().build()
159
192
 
160
- def to_statement(self, config: "Optional[StatementConfig]" = None) -> "SQL":
193
+ def to_statement(self, config: "StatementConfig | None" = None) -> "SQL":
161
194
  return super().to_statement(config=config)
162
195
 
163
196
 
@@ -182,15 +215,15 @@ class ColumnDefinition:
182
215
  self,
183
216
  name: str,
184
217
  dtype: str,
185
- default: "Optional[Any]" = None,
218
+ default: "Any | None" = None,
186
219
  not_null: bool = False,
187
220
  primary_key: bool = False,
188
221
  unique: bool = False,
189
222
  auto_increment: bool = False,
190
- comment: "Optional[str]" = None,
191
- check: "Optional[str]" = None,
192
- generated: "Optional[str]" = None,
193
- collate: "Optional[str]" = None,
223
+ comment: "str | None" = None,
224
+ check: "str | None" = None,
225
+ generated: "str | None" = None,
226
+ collate: "str | None" = None,
194
227
  ) -> None:
195
228
  self.name = name
196
229
  self.dtype = dtype
@@ -224,13 +257,13 @@ class ConstraintDefinition:
224
257
  def __init__(
225
258
  self,
226
259
  constraint_type: str,
227
- name: "Optional[str]" = None,
228
- columns: "Optional[list[str]]" = None,
229
- references_table: "Optional[str]" = None,
230
- references_columns: "Optional[list[str]]" = None,
231
- condition: "Optional[str]" = None,
232
- on_delete: "Optional[str]" = None,
233
- on_update: "Optional[str]" = None,
260
+ name: "str | None" = None,
261
+ columns: "list[str] | None" = None,
262
+ references_table: "str | None" = None,
263
+ references_columns: "list[str] | None" = None,
264
+ condition: "str | None" = None,
265
+ on_delete: "str | None" = None,
266
+ on_update: "str | None" = None,
234
267
  deferrable: bool = False,
235
268
  initially_deferred: bool = False,
236
269
  ) -> None:
@@ -281,10 +314,10 @@ class CreateTable(DDLBuilder):
281
314
  self._columns: list[ColumnDefinition] = []
282
315
  self._constraints: list[ConstraintDefinition] = []
283
316
  self._table_options: dict[str, Any] = {}
284
- self._schema: Optional[str] = None
285
- self._tablespace: Optional[str] = None
286
- self._like_table: Optional[str] = None
287
- self._partition_by: Optional[str] = None
317
+ self._schema: str | None = None
318
+ self._tablespace: str | None = None
319
+ self._like_table: str | None = None
320
+ self._partition_by: str | None = None
288
321
 
289
322
  def in_schema(self, schema_name: str) -> "Self":
290
323
  """Set the schema for the table."""
@@ -316,19 +349,28 @@ class CreateTable(DDLBuilder):
316
349
  self._partition_by = partition_spec
317
350
  return self
318
351
 
352
+ @property
353
+ def columns(self) -> "list[ColumnDefinition]":
354
+ """Get the list of column definitions for this table.
355
+
356
+ Returns:
357
+ List of ColumnDefinition objects.
358
+ """
359
+ return self._columns
360
+
319
361
  def column(
320
362
  self,
321
363
  name: str,
322
364
  dtype: str,
323
- default: "Optional[Any]" = None,
365
+ default: "Any | None" = None,
324
366
  not_null: bool = False,
325
367
  primary_key: bool = False,
326
368
  unique: bool = False,
327
369
  auto_increment: bool = False,
328
- comment: "Optional[str]" = None,
329
- check: "Optional[str]" = None,
330
- generated: "Optional[str]" = None,
331
- collate: "Optional[str]" = None,
370
+ comment: "str | None" = None,
371
+ check: "str | None" = None,
372
+ generated: "str | None" = None,
373
+ collate: "str | None" = None,
332
374
  ) -> "Self":
333
375
  """Add a column definition to the table."""
334
376
  if not name:
@@ -356,37 +398,37 @@ class CreateTable(DDLBuilder):
356
398
 
357
399
  self._columns.append(column_def)
358
400
 
359
- if primary_key and not any(c.constraint_type == "PRIMARY KEY" for c in self._constraints):
401
+ if primary_key and not self._has_primary_key_constraint():
360
402
  self.primary_key_constraint([name])
361
403
 
362
404
  return self
363
405
 
364
- def primary_key_constraint(self, columns: "Union[str, list[str]]", name: "Optional[str]" = None) -> "Self":
406
+ def primary_key_constraint(self, columns: "str | list[str]", name: "str | None" = None) -> "Self":
365
407
  """Add a primary key constraint."""
366
408
  col_list = [columns] if isinstance(columns, str) else list(columns)
367
409
 
368
410
  if not col_list:
369
411
  self._raise_sql_builder_error("Primary key must include at least one column")
370
412
 
371
- existing_pk = next((c for c in self._constraints if c.constraint_type == "PRIMARY KEY"), None)
413
+ existing_pk = self._find_primary_key_constraint()
372
414
  if existing_pk:
373
415
  for col in col_list:
374
416
  if col not in existing_pk.columns:
375
417
  existing_pk.columns.append(col)
376
418
  else:
377
- constraint = ConstraintDefinition(constraint_type="PRIMARY KEY", name=name, columns=col_list)
419
+ constraint = ConstraintDefinition(constraint_type=CONSTRAINT_TYPE_PRIMARY_KEY, name=name, columns=col_list)
378
420
  self._constraints.append(constraint)
379
421
 
380
422
  return self
381
423
 
382
424
  def foreign_key_constraint(
383
425
  self,
384
- columns: "Union[str, list[str]]",
426
+ columns: "str | list[str]",
385
427
  references_table: str,
386
- references_columns: "Union[str, list[str]]",
387
- name: "Optional[str]" = None,
388
- on_delete: "Optional[str]" = None,
389
- on_update: "Optional[str]" = None,
428
+ references_columns: "str | list[str]",
429
+ name: "str | None" = None,
430
+ on_delete: "str | None" = None,
431
+ on_update: "str | None" = None,
390
432
  deferrable: bool = False,
391
433
  initially_deferred: bool = False,
392
434
  ) -> "Self":
@@ -398,14 +440,11 @@ class CreateTable(DDLBuilder):
398
440
  if len(col_list) != len(ref_col_list):
399
441
  self._raise_sql_builder_error("Foreign key columns and referenced columns must have same length")
400
442
 
401
- valid_actions = {"CASCADE", "SET NULL", "SET DEFAULT", "RESTRICT", "NO ACTION", None}
402
- if on_delete and on_delete.upper() not in valid_actions:
403
- self._raise_sql_builder_error(f"Invalid ON DELETE action: {on_delete}")
404
- if on_update and on_update.upper() not in valid_actions:
405
- self._raise_sql_builder_error(f"Invalid ON UPDATE action: {on_update}")
443
+ self._validate_foreign_key_action(on_delete, "ON DELETE")
444
+ self._validate_foreign_key_action(on_update, "ON UPDATE")
406
445
 
407
446
  constraint = ConstraintDefinition(
408
- constraint_type="FOREIGN KEY",
447
+ constraint_type=CONSTRAINT_TYPE_FOREIGN_KEY,
409
448
  name=name,
410
449
  columns=col_list,
411
450
  references_table=references_table,
@@ -419,19 +458,19 @@ class CreateTable(DDLBuilder):
419
458
  self._constraints.append(constraint)
420
459
  return self
421
460
 
422
- def unique_constraint(self, columns: "Union[str, list[str]]", name: "Optional[str]" = None) -> "Self":
461
+ def unique_constraint(self, columns: "str | list[str]", name: "str | None" = None) -> "Self":
423
462
  """Add a unique constraint."""
424
463
  col_list = [columns] if isinstance(columns, str) else list(columns)
425
464
 
426
465
  if not col_list:
427
466
  self._raise_sql_builder_error("Unique constraint must include at least one column")
428
467
 
429
- constraint = ConstraintDefinition(constraint_type="UNIQUE", name=name, columns=col_list)
468
+ constraint = ConstraintDefinition(constraint_type=CONSTRAINT_TYPE_UNIQUE, name=name, columns=col_list)
430
469
 
431
470
  self._constraints.append(constraint)
432
471
  return self
433
472
 
434
- def check_constraint(self, condition: Union[str, "ColumnExpression"], name: "Optional[str]" = None) -> "Self":
473
+ def check_constraint(self, condition: Union[str, "ColumnExpression"], name: "str | None" = None) -> "Self":
435
474
  """Add a check constraint."""
436
475
  if not condition:
437
476
  self._raise_sql_builder_error("Check constraint must have a condition")
@@ -443,7 +482,7 @@ class CreateTable(DDLBuilder):
443
482
  else:
444
483
  condition_str = str(condition)
445
484
 
446
- constraint = ConstraintDefinition(constraint_type="CHECK", name=name, condition=condition_str)
485
+ constraint = ConstraintDefinition(constraint_type=CONSTRAINT_TYPE_CHECK, name=name, condition=condition_str)
447
486
 
448
487
  self._constraints.append(constraint)
449
488
  return self
@@ -484,10 +523,8 @@ class CreateTable(DDLBuilder):
484
523
  column_defs.append(col_expr)
485
524
 
486
525
  for constraint in self._constraints:
487
- if constraint.constraint_type == "PRIMARY KEY" and len(constraint.columns) == 1:
488
- col_name = constraint.columns[0]
489
- if any(c.name == col_name and c.primary_key for c in self._columns):
490
- continue
526
+ if self._is_redundant_single_column_primary_key(constraint):
527
+ continue
491
528
 
492
529
  constraint_expr = build_constraint_expression(constraint)
493
530
  if constraint_expr:
@@ -531,6 +568,27 @@ class CreateTable(DDLBuilder):
531
568
  like=like_expr,
532
569
  )
533
570
 
571
+ def _has_primary_key_constraint(self) -> bool:
572
+ """Check if table already has a primary key constraint."""
573
+ return any(c.constraint_type == CONSTRAINT_TYPE_PRIMARY_KEY for c in self._constraints)
574
+
575
+ def _find_primary_key_constraint(self) -> "ConstraintDefinition | None":
576
+ """Find existing primary key constraint."""
577
+ return next((c for c in self._constraints if c.constraint_type == CONSTRAINT_TYPE_PRIMARY_KEY), None)
578
+
579
+ def _validate_foreign_key_action(self, action: "str | None", action_type: str) -> None:
580
+ """Validate foreign key action (ON DELETE or ON UPDATE)."""
581
+ if action and action.upper() not in VALID_FOREIGN_KEY_ACTIONS:
582
+ self._raise_sql_builder_error(f"Invalid {action_type} action: {action}")
583
+
584
+ def _is_redundant_single_column_primary_key(self, constraint: "ConstraintDefinition") -> bool:
585
+ """Check if constraint is a redundant single-column primary key."""
586
+ if constraint.constraint_type != CONSTRAINT_TYPE_PRIMARY_KEY or len(constraint.columns) != 1:
587
+ return False
588
+
589
+ col_name = constraint.columns[0]
590
+ return any(c.name == col_name and c.primary_key for c in self._columns)
591
+
534
592
 
535
593
  class DropTable(DDLBuilder):
536
594
  """Builder for DROP TABLE [IF EXISTS] ... [CASCADE|RESTRICT]."""
@@ -547,7 +605,7 @@ class DropTable(DDLBuilder):
547
605
  super().__init__(dialect=dialect)
548
606
  self._table_name = table_name
549
607
  self._if_exists = False
550
- self._cascade: Optional[bool] = None
608
+ self._cascade: bool | None = None
551
609
 
552
610
  def table(self, name: str) -> Self:
553
611
  self._table_name = name
@@ -587,9 +645,9 @@ class DropIndex(DDLBuilder):
587
645
  """
588
646
  super().__init__(dialect=dialect)
589
647
  self._index_name = index_name
590
- self._table_name: Optional[str] = None
648
+ self._table_name: str | None = None
591
649
  self._if_exists = False
592
- self._cascade: Optional[bool] = None
650
+ self._cascade: bool | None = None
593
651
 
594
652
  def name(self, index_name: str) -> Self:
595
653
  self._index_name = index_name
@@ -638,7 +696,7 @@ class DropView(DDLBuilder):
638
696
  super().__init__(dialect=dialect)
639
697
  self._view_name = view_name
640
698
  self._if_exists = False
641
- self._cascade: Optional[bool] = None
699
+ self._cascade: bool | None = None
642
700
 
643
701
  def name(self, view_name: str) -> Self:
644
702
  self._view_name = view_name
@@ -679,7 +737,7 @@ class DropSchema(DDLBuilder):
679
737
  super().__init__(dialect=dialect)
680
738
  self._schema_name = schema_name
681
739
  self._if_exists = False
682
- self._cascade: Optional[bool] = None
740
+ self._cascade: bool | None = None
683
741
 
684
742
  def name(self, schema_name: str) -> Self:
685
743
  self._schema_name = schema_name
@@ -719,12 +777,12 @@ class CreateIndex(DDLBuilder):
719
777
  """
720
778
  super().__init__(dialect=dialect)
721
779
  self._index_name = index_name
722
- self._table_name: Optional[str] = None
723
- self._columns: list[Union[str, exp.Ordered, exp.Expression]] = []
780
+ self._table_name: str | None = None
781
+ self._columns: list[str | exp.Ordered | exp.Expression] = []
724
782
  self._unique = False
725
783
  self._if_not_exists = False
726
- self._using: Optional[str] = None
727
- self._where: Optional[Union[str, exp.Expression]] = None
784
+ self._using: str | None = None
785
+ self._where: str | exp.Expression | None = None
728
786
 
729
787
  def name(self, index_name: str) -> Self:
730
788
  self._index_name = index_name
@@ -734,11 +792,11 @@ class CreateIndex(DDLBuilder):
734
792
  self._table_name = table_name
735
793
  return self
736
794
 
737
- def columns(self, *cols: Union[str, exp.Ordered, exp.Expression]) -> Self:
795
+ def columns(self, *cols: str | exp.Ordered | exp.Expression) -> Self:
738
796
  self._columns.extend(cols)
739
797
  return self
740
798
 
741
- def expressions(self, *exprs: Union[str, exp.Expression]) -> Self:
799
+ def expressions(self, *exprs: str | exp.Expression) -> Self:
742
800
  self._columns.extend(exprs)
743
801
  return self
744
802
 
@@ -754,7 +812,7 @@ class CreateIndex(DDLBuilder):
754
812
  self._using = method
755
813
  return self
756
814
 
757
- def where(self, condition: Union[str, exp.Expression]) -> Self:
815
+ def where(self, condition: str | exp.Expression) -> Self:
758
816
  self._where = condition
759
817
  return self
760
818
 
@@ -796,8 +854,8 @@ class Truncate(DDLBuilder):
796
854
  """
797
855
  super().__init__(dialect=dialect)
798
856
  self._table_name = table_name
799
- self._cascade: Optional[bool] = None
800
- self._identity: Optional[str] = None
857
+ self._cascade: bool | None = None
858
+ self._identity: str | None = None
801
859
 
802
860
  def table(self, name: str) -> Self:
803
861
  self._table_name = name
@@ -845,15 +903,15 @@ class AlterOperation:
845
903
  def __init__(
846
904
  self,
847
905
  operation_type: str,
848
- column_name: "Optional[str]" = None,
849
- column_definition: "Optional[ColumnDefinition]" = None,
850
- constraint_name: "Optional[str]" = None,
851
- constraint_definition: "Optional[ConstraintDefinition]" = None,
852
- new_type: "Optional[str]" = None,
853
- new_name: "Optional[str]" = None,
854
- after_column: "Optional[str]" = None,
906
+ column_name: "str | None" = None,
907
+ column_definition: "ColumnDefinition | None" = None,
908
+ constraint_name: "str | None" = None,
909
+ constraint_definition: "ConstraintDefinition | None" = None,
910
+ new_type: "str | None" = None,
911
+ new_name: "str | None" = None,
912
+ after_column: "str | None" = None,
855
913
  first: bool = False,
856
- using_expression: "Optional[str]" = None,
914
+ using_expression: "str | None" = None,
857
915
  ) -> None:
858
916
  self.operation_type = operation_type
859
917
  self.column_name = column_name
@@ -882,7 +940,7 @@ class CreateSchema(DDLBuilder):
882
940
  super().__init__(dialect=dialect)
883
941
  self._schema_name = schema_name
884
942
  self._if_not_exists = False
885
- self._authorization: Optional[str] = None
943
+ self._authorization: str | None = None
886
944
 
887
945
  def name(self, schema_name: str) -> Self:
888
946
  self._schema_name = schema_name
@@ -937,10 +995,10 @@ class CreateTableAsSelect(DDLBuilder):
937
995
 
938
996
  def __init__(self, dialect: DialectType = None) -> None:
939
997
  super().__init__(dialect=dialect)
940
- self._table_name: Optional[str] = None
998
+ self._table_name: str | None = None
941
999
  self._if_not_exists = False
942
1000
  self._columns: list[str] = []
943
- self._select_query: Optional[object] = None
1001
+ self._select_query: object | None = None
944
1002
 
945
1003
  def name(self, table_name: str) -> Self:
946
1004
  self._table_name = table_name
@@ -954,7 +1012,7 @@ class CreateTableAsSelect(DDLBuilder):
954
1012
  self._columns = list(cols)
955
1013
  return self
956
1014
 
957
- def as_select(self, select_query: "Union[str, exp.Expression]") -> Self:
1015
+ def as_select(self, select_query: "str | exp.Expression") -> Self:
958
1016
  self._select_query = select_query
959
1017
  return self
960
1018
 
@@ -966,8 +1024,6 @@ class CreateTableAsSelect(DDLBuilder):
966
1024
 
967
1025
  select_expr = None
968
1026
  select_parameters = None
969
- from sqlspec.builder._select import Select
970
- from sqlspec.core.statement import SQL
971
1027
 
972
1028
  if isinstance(self._select_query, SQL):
973
1029
  select_expr = self._select_query.expression
@@ -1033,12 +1089,12 @@ class CreateMaterializedView(DDLBuilder):
1033
1089
  self._view_name = view_name
1034
1090
  self._if_not_exists = False
1035
1091
  self._columns: list[str] = []
1036
- self._select_query: Optional[Union[str, exp.Expression]] = None
1037
- self._with_data: Optional[bool] = None
1038
- self._refresh_mode: Optional[str] = None
1092
+ self._select_query: str | exp.Expression | None = None
1093
+ self._with_data: bool | None = None
1094
+ self._refresh_mode: str | None = None
1039
1095
  self._storage_parameters: dict[str, Any] = {}
1040
- self._tablespace: Optional[str] = None
1041
- self._using_index: Optional[str] = None
1096
+ self._tablespace: str | None = None
1097
+ self._using_index: str | None = None
1042
1098
  self._hints: list[str] = []
1043
1099
 
1044
1100
  def name(self, view_name: str) -> Self:
@@ -1053,7 +1109,7 @@ class CreateMaterializedView(DDLBuilder):
1053
1109
  self._columns = list(cols)
1054
1110
  return self
1055
1111
 
1056
- def as_select(self, select_query: "Union[str, exp.Expression]") -> Self:
1112
+ def as_select(self, select_query: "str | exp.Expression") -> Self:
1057
1113
  self._select_query = select_query
1058
1114
  return self
1059
1115
 
@@ -1093,8 +1149,6 @@ class CreateMaterializedView(DDLBuilder):
1093
1149
 
1094
1150
  select_expr = None
1095
1151
  select_parameters = None
1096
- from sqlspec.builder._select import Select
1097
- from sqlspec.core.statement import SQL
1098
1152
 
1099
1153
  if isinstance(self._select_query, SQL):
1100
1154
  select_expr = self._select_query.expression
@@ -1160,7 +1214,7 @@ class CreateView(DDLBuilder):
1160
1214
  self._view_name = view_name
1161
1215
  self._if_not_exists = False
1162
1216
  self._columns: list[str] = []
1163
- self._select_query: Optional[Union[str, exp.Expression]] = None
1217
+ self._select_query: str | exp.Expression | None = None
1164
1218
  self._hints: list[str] = []
1165
1219
 
1166
1220
  def name(self, view_name: str) -> Self:
@@ -1175,7 +1229,7 @@ class CreateView(DDLBuilder):
1175
1229
  self._columns = list(cols)
1176
1230
  return self
1177
1231
 
1178
- def as_select(self, select_query: "Union[str, exp.Expression]") -> Self:
1232
+ def as_select(self, select_query: "str | exp.Expression") -> Self:
1179
1233
  self._select_query = select_query
1180
1234
  return self
1181
1235
 
@@ -1191,8 +1245,6 @@ class CreateView(DDLBuilder):
1191
1245
 
1192
1246
  select_expr = None
1193
1247
  select_parameters = None
1194
- from sqlspec.builder._select import Select
1195
- from sqlspec.core.statement import SQL
1196
1248
 
1197
1249
  if isinstance(self._select_query, SQL):
1198
1250
  select_expr = self._select_query.expression
@@ -1249,7 +1301,7 @@ class AlterTable(DDLBuilder):
1249
1301
  super().__init__(dialect=dialect)
1250
1302
  self._table_name = table_name
1251
1303
  self._operations: list[AlterOperation] = []
1252
- self._schema: Optional[str] = None
1304
+ self._schema: str | None = None
1253
1305
  self._if_exists = False
1254
1306
 
1255
1307
  def if_exists(self) -> "Self":
@@ -1261,11 +1313,11 @@ class AlterTable(DDLBuilder):
1261
1313
  self,
1262
1314
  name: str,
1263
1315
  dtype: str,
1264
- default: "Optional[Any]" = None,
1316
+ default: "Any | None" = None,
1265
1317
  not_null: bool = False,
1266
1318
  unique: bool = False,
1267
- comment: "Optional[str]" = None,
1268
- after: "Optional[str]" = None,
1319
+ comment: "str | None" = None,
1320
+ after: "str | None" = None,
1269
1321
  first: bool = False,
1270
1322
  ) -> "Self":
1271
1323
  """Add a new column to the table."""
@@ -1296,7 +1348,7 @@ class AlterTable(DDLBuilder):
1296
1348
  self._operations.append(operation)
1297
1349
  return self
1298
1350
 
1299
- def alter_column_type(self, name: str, new_type: str, using: "Optional[str]" = None) -> "Self":
1351
+ def alter_column_type(self, name: str, new_type: str, using: "str | None" = None) -> "Self":
1300
1352
  """Change the type of an existing column."""
1301
1353
  if not name:
1302
1354
  self._raise_sql_builder_error("Column name must be a non-empty string")
@@ -1327,13 +1379,13 @@ class AlterTable(DDLBuilder):
1327
1379
  def add_constraint(
1328
1380
  self,
1329
1381
  constraint_type: str,
1330
- columns: "Optional[Union[str, list[str]]]" = None,
1331
- name: "Optional[str]" = None,
1332
- references_table: "Optional[str]" = None,
1333
- references_columns: "Optional[Union[str, list[str]]]" = None,
1334
- condition: "Optional[Union[str, ColumnExpression]]" = None,
1335
- on_delete: "Optional[str]" = None,
1336
- on_update: "Optional[str]" = None,
1382
+ columns: "str | list[str] | None" = None,
1383
+ name: "str | None" = None,
1384
+ references_table: "str | None" = None,
1385
+ references_columns: "str | list[str] | None" = None,
1386
+ condition: "str | ColumnExpression | None" = None,
1387
+ on_delete: "str | None" = None,
1388
+ on_update: "str | None" = None,
1337
1389
  ) -> "Self":
1338
1390
  """Add a constraint to the table.
1339
1391
 
@@ -1347,8 +1399,7 @@ class AlterTable(DDLBuilder):
1347
1399
  on_delete: Foreign key ON DELETE action
1348
1400
  on_update: Foreign key ON UPDATE action
1349
1401
  """
1350
- valid_types = {"PRIMARY KEY", "FOREIGN KEY", "UNIQUE", "CHECK"}
1351
- if constraint_type.upper() not in valid_types:
1402
+ if constraint_type.upper() not in VALID_CONSTRAINT_TYPES:
1352
1403
  self._raise_sql_builder_error(f"Invalid constraint type: {constraint_type}")
1353
1404
 
1354
1405
  col_list = None
@@ -1359,10 +1410,10 @@ class AlterTable(DDLBuilder):
1359
1410
  if references_columns is not None:
1360
1411
  ref_col_list = [references_columns] if isinstance(references_columns, str) else list(references_columns)
1361
1412
 
1362
- condition_str: Optional[str] = None
1413
+ condition_str: str | None = None
1363
1414
  if condition is not None:
1364
- if hasattr(condition, "sqlglot_expression"):
1365
- sqlglot_expr = getattr(condition, "sqlglot_expression", None)
1415
+ if has_sqlglot_expression(condition):
1416
+ sqlglot_expr = condition.sqlglot_expression
1366
1417
  condition_str = sqlglot_expr.sql(dialect=self.dialect) if sqlglot_expr else str(condition)
1367
1418
  else:
1368
1419
  condition_str = str(condition)
@@ -1472,9 +1523,9 @@ class AlterTable(DDLBuilder):
1472
1523
  if not op.column_definition or op.column_definition.default is None:
1473
1524
  self._raise_sql_builder_error("Default value required for SET DEFAULT")
1474
1525
  default_val = op.column_definition.default
1475
- default_expr: Optional[exp.Expression]
1526
+ default_expr: exp.Expression | None
1476
1527
  if isinstance(default_val, str):
1477
- if default_val.upper() in {"CURRENT_TIMESTAMP", "CURRENT_DATE", "CURRENT_TIME"} or "(" in default_val:
1528
+ if self._is_sql_function_default(default_val):
1478
1529
  default_expr = exp.maybe_parse(default_val)
1479
1530
  else:
1480
1531
  default_expr = exp.convert(default_val)
@@ -1494,6 +1545,14 @@ class AlterTable(DDLBuilder):
1494
1545
  self._raise_sql_builder_error(f"Unknown operation type: {op.operation_type}")
1495
1546
  raise AssertionError
1496
1547
 
1548
+ def _is_sql_function_default(self, default_val: str) -> bool:
1549
+ """Check if default value is a SQL function or expression."""
1550
+ default_upper = default_val.upper()
1551
+ return (
1552
+ default_upper in {CURRENT_TIMESTAMP_KEYWORD, CURRENT_DATE_KEYWORD, CURRENT_TIME_KEYWORD}
1553
+ or "(" in default_val
1554
+ )
1555
+
1497
1556
 
1498
1557
  class CommentOn(DDLBuilder):
1499
1558
  """Builder for COMMENT ON ... IS ... statements."""
@@ -1507,10 +1566,10 @@ class CommentOn(DDLBuilder):
1507
1566
  dialect: SQL dialect to use
1508
1567
  """
1509
1568
  super().__init__(dialect=dialect)
1510
- self._target_type: Optional[str] = None
1511
- self._table: Optional[str] = None
1512
- self._column: Optional[str] = None
1513
- self._comment: Optional[str] = None
1569
+ self._target_type: str | None = None
1570
+ self._table: str | None = None
1571
+ self._column: str | None = None
1572
+ self._comment: str | None = None
1514
1573
 
1515
1574
  def on_table(self, table: str) -> Self:
1516
1575
  self._target_type = "TABLE"
@@ -1555,7 +1614,7 @@ class RenameTable(DDLBuilder):
1555
1614
  """
1556
1615
  super().__init__(dialect=dialect)
1557
1616
  self._old_name = old_name
1558
- self._new_name: Optional[str] = None
1617
+ self._new_name: str | None = None
1559
1618
 
1560
1619
  def table(self, old_name: str) -> Self:
1561
1620
  self._old_name = old_name