sqlspec 0.14.1__py3-none-any.whl → 0.16.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 (159) hide show
  1. sqlspec/__init__.py +50 -25
  2. sqlspec/__main__.py +1 -1
  3. sqlspec/__metadata__.py +1 -3
  4. sqlspec/_serialization.py +1 -2
  5. sqlspec/_sql.py +480 -121
  6. sqlspec/_typing.py +278 -142
  7. sqlspec/adapters/adbc/__init__.py +4 -3
  8. sqlspec/adapters/adbc/_types.py +12 -0
  9. sqlspec/adapters/adbc/config.py +115 -260
  10. sqlspec/adapters/adbc/driver.py +462 -367
  11. sqlspec/adapters/aiosqlite/__init__.py +18 -3
  12. sqlspec/adapters/aiosqlite/_types.py +13 -0
  13. sqlspec/adapters/aiosqlite/config.py +199 -129
  14. sqlspec/adapters/aiosqlite/driver.py +230 -269
  15. sqlspec/adapters/asyncmy/__init__.py +18 -3
  16. sqlspec/adapters/asyncmy/_types.py +12 -0
  17. sqlspec/adapters/asyncmy/config.py +80 -168
  18. sqlspec/adapters/asyncmy/driver.py +260 -225
  19. sqlspec/adapters/asyncpg/__init__.py +19 -4
  20. sqlspec/adapters/asyncpg/_types.py +17 -0
  21. sqlspec/adapters/asyncpg/config.py +82 -181
  22. sqlspec/adapters/asyncpg/driver.py +285 -383
  23. sqlspec/adapters/bigquery/__init__.py +17 -3
  24. sqlspec/adapters/bigquery/_types.py +12 -0
  25. sqlspec/adapters/bigquery/config.py +191 -258
  26. sqlspec/adapters/bigquery/driver.py +474 -646
  27. sqlspec/adapters/duckdb/__init__.py +14 -3
  28. sqlspec/adapters/duckdb/_types.py +12 -0
  29. sqlspec/adapters/duckdb/config.py +415 -351
  30. sqlspec/adapters/duckdb/driver.py +343 -413
  31. sqlspec/adapters/oracledb/__init__.py +19 -5
  32. sqlspec/adapters/oracledb/_types.py +14 -0
  33. sqlspec/adapters/oracledb/config.py +123 -379
  34. sqlspec/adapters/oracledb/driver.py +507 -560
  35. sqlspec/adapters/psqlpy/__init__.py +13 -3
  36. sqlspec/adapters/psqlpy/_types.py +11 -0
  37. sqlspec/adapters/psqlpy/config.py +93 -254
  38. sqlspec/adapters/psqlpy/driver.py +505 -234
  39. sqlspec/adapters/psycopg/__init__.py +19 -5
  40. sqlspec/adapters/psycopg/_types.py +17 -0
  41. sqlspec/adapters/psycopg/config.py +143 -403
  42. sqlspec/adapters/psycopg/driver.py +706 -872
  43. sqlspec/adapters/sqlite/__init__.py +14 -3
  44. sqlspec/adapters/sqlite/_types.py +11 -0
  45. sqlspec/adapters/sqlite/config.py +202 -118
  46. sqlspec/adapters/sqlite/driver.py +264 -303
  47. sqlspec/base.py +105 -9
  48. sqlspec/{statement/builder → builder}/__init__.py +12 -14
  49. sqlspec/{statement/builder → builder}/_base.py +120 -55
  50. sqlspec/{statement/builder → builder}/_column.py +17 -6
  51. sqlspec/{statement/builder → builder}/_ddl.py +46 -79
  52. sqlspec/{statement/builder → builder}/_ddl_utils.py +5 -10
  53. sqlspec/{statement/builder → builder}/_delete.py +6 -25
  54. sqlspec/{statement/builder → builder}/_insert.py +18 -65
  55. sqlspec/builder/_merge.py +56 -0
  56. sqlspec/{statement/builder → builder}/_parsing_utils.py +8 -11
  57. sqlspec/{statement/builder → builder}/_select.py +11 -56
  58. sqlspec/{statement/builder → builder}/_update.py +12 -18
  59. sqlspec/{statement/builder → builder}/mixins/__init__.py +10 -14
  60. sqlspec/{statement/builder → builder}/mixins/_cte_and_set_ops.py +48 -59
  61. sqlspec/{statement/builder → builder}/mixins/_insert_operations.py +34 -18
  62. sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
  63. sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +19 -9
  64. sqlspec/{statement/builder → builder}/mixins/_order_limit_operations.py +3 -3
  65. sqlspec/{statement/builder → builder}/mixins/_pivot_operations.py +4 -8
  66. sqlspec/{statement/builder → builder}/mixins/_select_operations.py +25 -38
  67. sqlspec/{statement/builder → builder}/mixins/_update_operations.py +15 -16
  68. sqlspec/{statement/builder → builder}/mixins/_where_clause.py +210 -137
  69. sqlspec/cli.py +4 -5
  70. sqlspec/config.py +180 -133
  71. sqlspec/core/__init__.py +63 -0
  72. sqlspec/core/cache.py +873 -0
  73. sqlspec/core/compiler.py +396 -0
  74. sqlspec/core/filters.py +830 -0
  75. sqlspec/core/hashing.py +310 -0
  76. sqlspec/core/parameters.py +1209 -0
  77. sqlspec/core/result.py +664 -0
  78. sqlspec/{statement → core}/splitter.py +321 -191
  79. sqlspec/core/statement.py +666 -0
  80. sqlspec/driver/__init__.py +7 -10
  81. sqlspec/driver/_async.py +387 -176
  82. sqlspec/driver/_common.py +527 -289
  83. sqlspec/driver/_sync.py +390 -172
  84. sqlspec/driver/mixins/__init__.py +2 -19
  85. sqlspec/driver/mixins/_result_tools.py +164 -0
  86. sqlspec/driver/mixins/_sql_translator.py +6 -3
  87. sqlspec/exceptions.py +5 -252
  88. sqlspec/extensions/aiosql/adapter.py +93 -96
  89. sqlspec/extensions/litestar/cli.py +1 -1
  90. sqlspec/extensions/litestar/config.py +0 -1
  91. sqlspec/extensions/litestar/handlers.py +15 -26
  92. sqlspec/extensions/litestar/plugin.py +18 -16
  93. sqlspec/extensions/litestar/providers.py +17 -52
  94. sqlspec/loader.py +424 -105
  95. sqlspec/migrations/__init__.py +12 -0
  96. sqlspec/migrations/base.py +92 -68
  97. sqlspec/migrations/commands.py +24 -106
  98. sqlspec/migrations/loaders.py +402 -0
  99. sqlspec/migrations/runner.py +49 -51
  100. sqlspec/migrations/tracker.py +31 -44
  101. sqlspec/migrations/utils.py +64 -24
  102. sqlspec/protocols.py +7 -183
  103. sqlspec/storage/__init__.py +1 -1
  104. sqlspec/storage/backends/base.py +37 -40
  105. sqlspec/storage/backends/fsspec.py +136 -112
  106. sqlspec/storage/backends/obstore.py +138 -160
  107. sqlspec/storage/capabilities.py +5 -4
  108. sqlspec/storage/registry.py +57 -106
  109. sqlspec/typing.py +136 -115
  110. sqlspec/utils/__init__.py +2 -3
  111. sqlspec/utils/correlation.py +0 -3
  112. sqlspec/utils/deprecation.py +6 -6
  113. sqlspec/utils/fixtures.py +6 -6
  114. sqlspec/utils/logging.py +0 -2
  115. sqlspec/utils/module_loader.py +7 -12
  116. sqlspec/utils/singleton.py +0 -1
  117. sqlspec/utils/sync_tools.py +17 -38
  118. sqlspec/utils/text.py +12 -51
  119. sqlspec/utils/type_guards.py +443 -232
  120. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/METADATA +7 -2
  121. sqlspec-0.16.0.dist-info/RECORD +134 -0
  122. sqlspec/adapters/adbc/transformers.py +0 -108
  123. sqlspec/driver/connection.py +0 -207
  124. sqlspec/driver/mixins/_cache.py +0 -114
  125. sqlspec/driver/mixins/_csv_writer.py +0 -91
  126. sqlspec/driver/mixins/_pipeline.py +0 -508
  127. sqlspec/driver/mixins/_query_tools.py +0 -796
  128. sqlspec/driver/mixins/_result_utils.py +0 -138
  129. sqlspec/driver/mixins/_storage.py +0 -912
  130. sqlspec/driver/mixins/_type_coercion.py +0 -128
  131. sqlspec/driver/parameters.py +0 -138
  132. sqlspec/statement/__init__.py +0 -21
  133. sqlspec/statement/builder/_merge.py +0 -95
  134. sqlspec/statement/cache.py +0 -50
  135. sqlspec/statement/filters.py +0 -625
  136. sqlspec/statement/parameters.py +0 -956
  137. sqlspec/statement/pipelines/__init__.py +0 -210
  138. sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
  139. sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
  140. sqlspec/statement/pipelines/context.py +0 -109
  141. sqlspec/statement/pipelines/transformers/__init__.py +0 -7
  142. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
  143. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
  144. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
  145. sqlspec/statement/pipelines/validators/__init__.py +0 -23
  146. sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
  147. sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
  148. sqlspec/statement/pipelines/validators/_performance.py +0 -714
  149. sqlspec/statement/pipelines/validators/_security.py +0 -967
  150. sqlspec/statement/result.py +0 -435
  151. sqlspec/statement/sql.py +0 -1774
  152. sqlspec/utils/cached_property.py +0 -25
  153. sqlspec/utils/statement_hashing.py +0 -203
  154. sqlspec-0.14.1.dist-info/RECORD +0 -145
  155. /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
  156. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/WHEEL +0 -0
  157. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/entry_points.txt +0 -0
  158. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/LICENSE +0 -0
  159. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/NOTICE +0 -0
@@ -1,4 +1,4 @@
1
- # DDL builders for SQLSpec: DROP, CREATE INDEX, TRUNCATE, etc.
1
+ """DDL builders for SQLSpec: DROP, CREATE INDEX, TRUNCATE, etc."""
2
2
 
3
3
  from dataclasses import dataclass, field
4
4
  from typing import TYPE_CHECKING, Any, Optional, Union
@@ -7,13 +7,13 @@ from sqlglot import exp
7
7
  from sqlglot.dialects.dialect import DialectType
8
8
  from typing_extensions import Self
9
9
 
10
- from sqlspec.statement.builder._base import QueryBuilder, SafeQuery
11
- from sqlspec.statement.builder._ddl_utils import build_column_expression, build_constraint_expression
12
- from sqlspec.statement.result import SQLResult
10
+ from sqlspec.builder._base import QueryBuilder, SafeQuery
11
+ from sqlspec.builder._ddl_utils import build_column_expression, build_constraint_expression
12
+ from sqlspec.core.result import SQLResult
13
13
 
14
14
  if TYPE_CHECKING:
15
- from sqlspec.statement.builder._column import ColumnExpression
16
- from sqlspec.statement.sql import SQL, SQLConfig
15
+ from sqlspec.builder._column import ColumnExpression
16
+ from sqlspec.core.statement import SQL, StatementConfig
17
17
 
18
18
  __all__ = (
19
19
  "AlterOperation",
@@ -33,19 +33,18 @@ __all__ = (
33
33
  "DropTable",
34
34
  "DropView",
35
35
  "RenameTable",
36
- "TruncateTable",
36
+ "Truncate",
37
37
  )
38
38
 
39
39
 
40
40
  @dataclass
41
- class DDLBuilder(QueryBuilder[Any]):
41
+ class DDLBuilder(QueryBuilder):
42
42
  """Base class for DDL builders (CREATE, DROP, ALTER, etc)."""
43
43
 
44
44
  dialect: DialectType = None
45
45
  _expression: Optional[exp.Expression] = field(default=None, init=False, repr=False, compare=False, hash=False)
46
46
 
47
47
  def __post_init__(self) -> None:
48
- # Override to prevent QueryBuilder from calling _create_base_expression prematurely
49
48
  pass
50
49
 
51
50
  def _create_base_expression(self) -> exp.Expression:
@@ -53,8 +52,7 @@ class DDLBuilder(QueryBuilder[Any]):
53
52
  raise NotImplementedError(msg)
54
53
 
55
54
  @property
56
- def _expected_result_type(self) -> "type[SQLResult[Any]]":
57
- # DDL typically returns no rows; use object for now.
55
+ def _expected_result_type(self) -> "type[SQLResult]":
58
56
  return SQLResult
59
57
 
60
58
  def build(self) -> "SafeQuery":
@@ -62,7 +60,7 @@ class DDLBuilder(QueryBuilder[Any]):
62
60
  self._expression = self._create_base_expression()
63
61
  return super().build()
64
62
 
65
- def to_statement(self, config: "Optional[SQLConfig]" = None) -> "SQL":
63
+ def to_statement(self, config: "Optional[StatementConfig]" = None) -> "SQL":
66
64
  return super().to_statement(config=config)
67
65
 
68
66
 
@@ -209,10 +207,8 @@ class CreateTable(DDLBuilder):
209
207
 
210
208
  def primary_key_constraint(self, columns: "Union[str, list[str]]", name: "Optional[str]" = None) -> "Self":
211
209
  """Add a primary key constraint."""
212
- # Normalize column list
213
210
  col_list = [columns] if isinstance(columns, str) else list(columns)
214
211
 
215
- # Validation
216
212
  if not col_list:
217
213
  self._raise_sql_builder_error("Primary key must include at least one column")
218
214
 
@@ -362,16 +358,11 @@ class CreateTable(DDLBuilder):
362
358
  if self._tablespace:
363
359
  props.append(exp.Property(this=exp.to_identifier("TABLESPACE"), value=exp.to_identifier(self._tablespace)))
364
360
  if self._partition_by:
365
- props.append(
366
- exp.Property(this=exp.to_identifier("PARTITION BY"), value=exp.Literal.string(self._partition_by))
367
- )
361
+ props.append(exp.Property(this=exp.to_identifier("PARTITION BY"), value=exp.convert(self._partition_by)))
368
362
 
369
363
  for key, value in self._table_options.items():
370
364
  if key != "engine": # Skip already handled options
371
- if isinstance(value, str):
372
- props.append(exp.Property(this=exp.to_identifier(key.upper()), value=exp.Literal.string(value)))
373
- else:
374
- props.append(exp.Property(this=exp.to_identifier(key.upper()), value=exp.Literal.number(value)))
365
+ props.append(exp.Property(this=exp.to_identifier(key.upper()), value=exp.convert(value)))
375
366
 
376
367
  properties_node = exp.Properties(expressions=props) if props else None
377
368
 
@@ -651,7 +642,7 @@ class CreateIndex(DDLBuilder):
651
642
 
652
643
  # --- TRUNCATE TABLE ---
653
644
  @dataclass
654
- class TruncateTable(DDLBuilder):
645
+ class Truncate(DDLBuilder):
655
646
  """Builder for TRUNCATE TABLE ... [CASCADE|RESTRICT] [RESTART IDENTITY|CONTINUE IDENTITY]."""
656
647
 
657
648
  _table_name: Optional[str] = None
@@ -791,16 +782,16 @@ class CreateTableAsSelect(DDLBuilder):
791
782
  self._raise_sql_builder_error("SELECT query must be set for CREATE TABLE AS SELECT.")
792
783
 
793
784
  select_expr = None
794
- select_params = None
795
- from sqlspec.statement.builder._select import Select
796
- from sqlspec.statement.sql import SQL
785
+ select_parameters = None
786
+ from sqlspec.builder._select import Select
787
+ from sqlspec.core.statement import SQL
797
788
 
798
789
  if isinstance(self._select_query, SQL):
799
790
  select_expr = self._select_query.expression
800
- select_params = getattr(self._select_query, "parameters", None)
791
+ select_parameters = getattr(self._select_query, "parameters", None)
801
792
  elif isinstance(self._select_query, Select):
802
793
  select_expr = getattr(self._select_query, "_expression", None)
803
- select_params = getattr(self._select_query, "_parameters", None)
794
+ select_parameters = getattr(self._select_query, "_parameters", None)
804
795
 
805
796
  with_ctes = getattr(self._select_query, "_with_ctes", {})
806
797
  if with_ctes and select_expr and isinstance(select_expr, exp.Select):
@@ -813,15 +804,15 @@ class CreateTableAsSelect(DDLBuilder):
813
804
  )
814
805
  elif isinstance(self._select_query, str):
815
806
  select_expr = exp.maybe_parse(self._select_query)
816
- select_params = None
807
+ select_parameters = None
817
808
  else:
818
809
  self._raise_sql_builder_error("Unsupported type for SELECT query in CTAS.")
819
810
  if select_expr is None:
820
811
  self._raise_sql_builder_error("SELECT query must be a valid SELECT expression.")
821
812
 
822
813
  # Merge parameters from SELECT if present
823
- if select_params:
824
- for p_name, p_value in select_params.items():
814
+ if select_parameters:
815
+ for p_name, p_value in select_parameters.items():
825
816
  # Always preserve the original parameter name
826
817
  # The SELECT query already has unique parameter names
827
818
  self._parameters[p_name] = p_value
@@ -908,27 +899,27 @@ class CreateMaterializedView(DDLBuilder):
908
899
  self._raise_sql_builder_error("SELECT query must be set for CREATE MATERIALIZED VIEW.")
909
900
 
910
901
  select_expr = None
911
- select_params = None
912
- from sqlspec.statement.builder._select import Select
913
- from sqlspec.statement.sql import SQL
902
+ select_parameters = None
903
+ from sqlspec.builder._select import Select
904
+ from sqlspec.core.statement import SQL
914
905
 
915
906
  if isinstance(self._select_query, SQL):
916
907
  select_expr = self._select_query.expression
917
- select_params = getattr(self._select_query, "parameters", None)
908
+ select_parameters = getattr(self._select_query, "parameters", None)
918
909
  elif isinstance(self._select_query, Select):
919
910
  select_expr = getattr(self._select_query, "_expression", None)
920
- select_params = getattr(self._select_query, "_parameters", None)
911
+ select_parameters = getattr(self._select_query, "_parameters", None)
921
912
  elif isinstance(self._select_query, str):
922
913
  select_expr = exp.maybe_parse(self._select_query)
923
- select_params = None
914
+ select_parameters = None
924
915
  else:
925
916
  self._raise_sql_builder_error("Unsupported type for SELECT query in materialized view.")
926
917
  if select_expr is None or not isinstance(select_expr, exp.Select):
927
918
  self._raise_sql_builder_error("SELECT query must be a valid SELECT expression.")
928
919
 
929
920
  # Merge parameters from SELECT if present
930
- if select_params:
931
- for p_name, p_value in select_params.items():
921
+ if select_parameters:
922
+ for p_name, p_value in select_parameters.items():
932
923
  # Always preserve the original parameter name
933
924
  # The SELECT query already has unique parameter names
934
925
  self._parameters[p_name] = p_value
@@ -939,9 +930,7 @@ class CreateMaterializedView(DDLBuilder):
939
930
 
940
931
  props: list[exp.Property] = []
941
932
  if self._refresh_mode:
942
- props.append(
943
- exp.Property(this=exp.to_identifier("REFRESH_MODE"), value=exp.Literal.string(self._refresh_mode))
944
- )
933
+ props.append(exp.Property(this=exp.to_identifier("REFRESH_MODE"), value=exp.convert(self._refresh_mode)))
945
934
  if self._tablespace:
946
935
  props.append(exp.Property(this=exp.to_identifier("TABLESPACE"), value=exp.to_identifier(self._tablespace)))
947
936
  if self._using_index:
@@ -949,12 +938,10 @@ class CreateMaterializedView(DDLBuilder):
949
938
  exp.Property(this=exp.to_identifier("USING_INDEX"), value=exp.to_identifier(self._using_index))
950
939
  )
951
940
  for k, v in self._storage_parameters.items():
952
- props.append(exp.Property(this=exp.to_identifier(k), value=exp.Literal.string(str(v))))
941
+ props.append(exp.Property(this=exp.to_identifier(k), value=exp.convert(str(v))))
953
942
  if self._with_data is not None:
954
943
  props.append(exp.Property(this=exp.to_identifier("WITH_DATA" if self._with_data else "NO_DATA")))
955
- props.extend(
956
- exp.Property(this=exp.to_identifier("HINT"), value=exp.Literal.string(hint)) for hint in self._hints
957
- )
944
+ props.extend(exp.Property(this=exp.to_identifier("HINT"), value=exp.convert(hint)) for hint in self._hints)
958
945
  properties_node = exp.Properties(expressions=props) if props else None
959
946
 
960
947
  return exp.Create(
@@ -1007,27 +994,27 @@ class CreateView(DDLBuilder):
1007
994
  self._raise_sql_builder_error("SELECT query must be set for CREATE VIEW.")
1008
995
 
1009
996
  select_expr = None
1010
- select_params = None
1011
- from sqlspec.statement.builder._select import Select
1012
- from sqlspec.statement.sql import SQL
997
+ select_parameters = None
998
+ from sqlspec.builder._select import Select
999
+ from sqlspec.core.statement import SQL
1013
1000
 
1014
1001
  if isinstance(self._select_query, SQL):
1015
1002
  select_expr = self._select_query.expression
1016
- select_params = getattr(self._select_query, "parameters", None)
1003
+ select_parameters = getattr(self._select_query, "parameters", None)
1017
1004
  elif isinstance(self._select_query, Select):
1018
1005
  select_expr = getattr(self._select_query, "_expression", None)
1019
- select_params = getattr(self._select_query, "_parameters", None)
1006
+ select_parameters = getattr(self._select_query, "_parameters", None)
1020
1007
  elif isinstance(self._select_query, str):
1021
1008
  select_expr = exp.maybe_parse(self._select_query)
1022
- select_params = None
1009
+ select_parameters = None
1023
1010
  else:
1024
1011
  self._raise_sql_builder_error("Unsupported type for SELECT query in view.")
1025
1012
  if select_expr is None or not isinstance(select_expr, exp.Select):
1026
1013
  self._raise_sql_builder_error("SELECT query must be a valid SELECT expression.")
1027
1014
 
1028
1015
  # Merge parameters from SELECT if present
1029
- if select_params:
1030
- for p_name, p_value in select_params.items():
1016
+ if select_parameters:
1017
+ for p_name, p_value in select_parameters.items():
1031
1018
  # Always preserve the original parameter name
1032
1019
  # The SELECT query already has unique parameter names
1033
1020
  self._parameters[p_name] = p_value
@@ -1037,7 +1024,7 @@ class CreateView(DDLBuilder):
1037
1024
  schema_expr = exp.Schema(expressions=[exp.column(c) for c in self._columns])
1038
1025
 
1039
1026
  props: list[exp.Property] = [
1040
- exp.Property(this=exp.to_identifier("HINT"), value=exp.Literal.string(h)) for h in self._hints
1027
+ exp.Property(this=exp.to_identifier("HINT"), value=exp.convert(h)) for h in self._hints
1041
1028
  ]
1042
1029
  properties_node = exp.Properties(expressions=props) if props else None
1043
1030
 
@@ -1238,24 +1225,6 @@ class AlterTable(DDLBuilder):
1238
1225
  self._operations.append(operation)
1239
1226
  return self
1240
1227
 
1241
- def set_default(self, column: str, default: "Any") -> "Self":
1242
- """Set default value for a column."""
1243
- operation = AlterOperation(
1244
- operation_type="ALTER COLUMN SET DEFAULT",
1245
- column_name=column,
1246
- column_definition=ColumnDefinition(name=column, dtype="", default=default),
1247
- )
1248
-
1249
- self._operations.append(operation)
1250
- return self
1251
-
1252
- def drop_default(self, column: str) -> "Self":
1253
- """Remove default value from a column."""
1254
- operation = AlterOperation(operation_type="ALTER COLUMN DROP DEFAULT", column_name=column)
1255
-
1256
- self._operations.append(operation)
1257
- return self
1258
-
1259
1228
  def _create_base_expression(self) -> "exp.Expression":
1260
1229
  """Create the SQLGlot expression for ALTER TABLE."""
1261
1230
  if not self._operations:
@@ -1327,15 +1296,15 @@ class AlterTable(DDLBuilder):
1327
1296
  if default_val.upper() in {"CURRENT_TIMESTAMP", "CURRENT_DATE", "CURRENT_TIME"} or "(" in default_val:
1328
1297
  default_expr = exp.maybe_parse(default_val)
1329
1298
  else:
1330
- default_expr = exp.Literal.string(default_val)
1299
+ default_expr = exp.convert(default_val)
1331
1300
  elif isinstance(default_val, (int, float)):
1332
- default_expr = exp.Literal.number(default_val)
1301
+ default_expr = exp.convert(default_val)
1333
1302
  elif default_val is True:
1334
1303
  default_expr = exp.true()
1335
1304
  elif default_val is False:
1336
1305
  default_expr = exp.false()
1337
1306
  else:
1338
- default_expr = exp.Literal.string(str(default_val))
1307
+ default_expr = exp.convert(str(default_val))
1339
1308
  return exp.AlterColumn(this=exp.to_identifier(op.column_name), default=default_expr)
1340
1309
 
1341
1310
  if op_type == "ALTER COLUMN DROP DEFAULT":
@@ -1375,14 +1344,12 @@ class CommentOn(DDLBuilder):
1375
1344
 
1376
1345
  def _create_base_expression(self) -> exp.Expression:
1377
1346
  if self._target_type == "TABLE" and self._table and self._comment is not None:
1378
- return exp.Comment(
1379
- this=exp.to_table(self._table), kind="TABLE", expression=exp.Literal.string(self._comment)
1380
- )
1347
+ return exp.Comment(this=exp.to_table(self._table), kind="TABLE", expression=exp.convert(self._comment))
1381
1348
  if self._target_type == "COLUMN" and self._table and self._column and self._comment is not None:
1382
1349
  return exp.Comment(
1383
1350
  this=exp.Column(table=self._table, this=self._column),
1384
1351
  kind="COLUMN",
1385
- expression=exp.Literal.string(self._comment),
1352
+ expression=exp.convert(self._comment),
1386
1353
  )
1387
1354
  self._raise_sql_builder_error("Must specify target and comment for COMMENT ON statement.")
1388
1355
  raise AssertionError # This line is unreachable but satisfies the linter
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Optional
5
5
  from sqlglot import exp
6
6
 
7
7
  if TYPE_CHECKING:
8
- from sqlspec.statement.builder._ddl import ColumnDefinition, ConstraintDefinition
8
+ from sqlspec.builder._ddl import ColumnDefinition, ConstraintDefinition
9
9
 
10
10
  __all__ = ("build_column_expression", "build_constraint_expression")
11
11
 
@@ -31,15 +31,10 @@ def build_column_expression(col: "ColumnDefinition") -> "exp.Expression":
31
31
  if col.default.upper() in {"CURRENT_TIMESTAMP", "CURRENT_DATE", "CURRENT_TIME"} or "(" in col.default:
32
32
  default_expr = exp.maybe_parse(col.default)
33
33
  else:
34
- default_expr = exp.Literal.string(col.default)
35
- elif isinstance(col.default, (int, float)):
36
- default_expr = exp.Literal.number(col.default)
37
- elif col.default is True:
38
- default_expr = exp.true()
39
- elif col.default is False:
40
- default_expr = exp.false()
34
+ default_expr = exp.convert(col.default)
41
35
  else:
42
- default_expr = exp.Literal.string(str(col.default))
36
+ # Use exp.convert for all other types (int, float, bool, None, etc.)
37
+ default_expr = exp.convert(col.default)
43
38
 
44
39
  constraints.append(exp.ColumnConstraint(kind=default_expr))
45
40
 
@@ -48,7 +43,7 @@ def build_column_expression(col: "ColumnDefinition") -> "exp.Expression":
48
43
  constraints.append(exp.ColumnConstraint(kind=check_expr))
49
44
 
50
45
  if col.comment:
51
- constraints.append(exp.ColumnConstraint(kind=exp.CommentColumnConstraint(this=exp.Literal.string(col.comment))))
46
+ constraints.append(exp.ColumnConstraint(kind=exp.CommentColumnConstraint(this=exp.convert(col.comment))))
52
47
 
53
48
  if col.generated:
54
49
  generated_expr = exp.GeneratedAsIdentityColumnConstraint(this=exp.maybe_parse(col.generated))
@@ -9,38 +9,20 @@ from typing import Any, Optional
9
9
 
10
10
  from sqlglot import exp
11
11
 
12
- from sqlspec.statement.builder._base import QueryBuilder, SafeQuery
13
- from sqlspec.statement.builder.mixins import DeleteFromClauseMixin, ReturningClauseMixin, WhereClauseMixin
14
- from sqlspec.statement.result import SQLResult
15
- from sqlspec.typing import RowT
12
+ from sqlspec.builder._base import QueryBuilder, SafeQuery
13
+ from sqlspec.builder.mixins import DeleteFromClauseMixin, ReturningClauseMixin, WhereClauseMixin
14
+ from sqlspec.core.result import SQLResult
16
15
 
17
16
  __all__ = ("Delete",)
18
17
 
19
18
 
20
19
  @dataclass(unsafe_hash=True)
21
- class Delete(QueryBuilder[RowT], WhereClauseMixin, ReturningClauseMixin, DeleteFromClauseMixin):
20
+ class Delete(QueryBuilder, WhereClauseMixin, ReturningClauseMixin, DeleteFromClauseMixin):
22
21
  """Builder for DELETE statements.
23
22
 
24
23
  This builder provides a fluent interface for constructing SQL DELETE statements
25
24
  with automatic parameter binding and validation. It does not support JOIN
26
25
  operations to maintain cross-dialect compatibility and safety.
27
-
28
- Example:
29
- ```python
30
- # Basic DELETE
31
- delete_query = Delete().from_("users").where("age < 18")
32
-
33
- # Even more concise with constructor
34
- delete_query = Delete("users").where("age < 18")
35
-
36
- # DELETE with parameterized conditions
37
- delete_query = (
38
- Delete()
39
- .from_("users")
40
- .where_eq("status", "inactive")
41
- .where_in("category", ["test", "demo"])
42
- )
43
- ```
44
26
  """
45
27
 
46
28
  _table: "Optional[str]" = field(default=None, init=False)
@@ -54,20 +36,19 @@ class Delete(QueryBuilder[RowT], WhereClauseMixin, ReturningClauseMixin, DeleteF
54
36
  """
55
37
  super().__init__(**kwargs)
56
38
 
57
- # Initialize fields from dataclass
58
39
  self._table = None
59
40
 
60
41
  if table:
61
42
  self.from_(table)
62
43
 
63
44
  @property
64
- def _expected_result_type(self) -> "type[SQLResult[RowT]]":
45
+ def _expected_result_type(self) -> "type[SQLResult]":
65
46
  """Get the expected result type for DELETE operations.
66
47
 
67
48
  Returns:
68
49
  The ExecuteResult type for DELETE statements.
69
50
  """
70
- return SQLResult[RowT]
51
+ return SQLResult
71
52
 
72
53
  def _create_base_expression(self) -> "exp.Delete":
73
54
  """Create a new sqlglot Delete expression.
@@ -10,16 +10,10 @@ from typing import TYPE_CHECKING, Any, Optional
10
10
  from sqlglot import exp
11
11
  from typing_extensions import Self
12
12
 
13
+ from sqlspec.builder._base import QueryBuilder
14
+ from sqlspec.builder.mixins import InsertFromSelectMixin, InsertIntoClauseMixin, InsertValuesMixin, ReturningClauseMixin
15
+ from sqlspec.core.result import SQLResult
13
16
  from sqlspec.exceptions import SQLBuilderError
14
- from sqlspec.statement.builder._base import QueryBuilder
15
- from sqlspec.statement.builder.mixins import (
16
- InsertFromSelectMixin,
17
- InsertIntoClauseMixin,
18
- InsertValuesMixin,
19
- ReturningClauseMixin,
20
- )
21
- from sqlspec.statement.result import SQLResult
22
- from sqlspec.typing import RowT
23
17
 
24
18
  if TYPE_CHECKING:
25
19
  from collections.abc import Mapping, Sequence
@@ -36,57 +30,11 @@ ERR_MSG_EXPRESSION_NOT_INITIALIZED = "Internal error: base expression not initia
36
30
 
37
31
 
38
32
  @dataclass(unsafe_hash=True)
39
- class Insert(QueryBuilder[RowT], ReturningClauseMixin, InsertValuesMixin, InsertFromSelectMixin, InsertIntoClauseMixin):
33
+ class Insert(QueryBuilder, ReturningClauseMixin, InsertValuesMixin, InsertFromSelectMixin, InsertIntoClauseMixin):
40
34
  """Builder for INSERT statements.
41
35
 
42
36
  This builder facilitates the construction of SQL INSERT queries
43
37
  in a safe and dialect-agnostic manner with automatic parameter binding.
44
-
45
- Example:
46
- ```python
47
- # Basic INSERT with values
48
- insert_query = (
49
- Insert()
50
- .into("users")
51
- .columns("name", "email", "age")
52
- .values("John Doe", "john@example.com", 30)
53
- )
54
-
55
- # Even more concise with constructor
56
- insert_query = Insert("users").values(
57
- {"name": "John", "age": 30}
58
- )
59
-
60
- # Multi-row INSERT
61
- insert_query = (
62
- Insert()
63
- .into("users")
64
- .columns("name", "email")
65
- .values("John", "john@example.com")
66
- .values("Jane", "jane@example.com")
67
- )
68
-
69
- # INSERT from dictionary
70
- insert_query = (
71
- Insert()
72
- .into("users")
73
- .values_from_dict(
74
- {"name": "John", "email": "john@example.com"}
75
- )
76
- )
77
-
78
- # INSERT from SELECT
79
- insert_query = (
80
- Insert()
81
- .into("users_backup")
82
- .from_select(
83
- Select()
84
- .select("name", "email")
85
- .from_("users")
86
- .where("active = true")
87
- )
88
- )
89
- ```
90
38
  """
91
39
 
92
40
  _table: "Optional[str]" = field(default=None, init=False)
@@ -102,7 +50,6 @@ class Insert(QueryBuilder[RowT], ReturningClauseMixin, InsertValuesMixin, Insert
102
50
  """
103
51
  super().__init__(**kwargs)
104
52
 
105
- # Initialize fields from dataclass
106
53
  self._table = None
107
54
  self._columns = []
108
55
  self._values_added_count = 0
@@ -121,13 +68,13 @@ class Insert(QueryBuilder[RowT], ReturningClauseMixin, InsertValuesMixin, Insert
121
68
  return exp.Insert()
122
69
 
123
70
  @property
124
- def _expected_result_type(self) -> "type[SQLResult[RowT]]":
71
+ def _expected_result_type(self) -> "type[SQLResult]":
125
72
  """Specifies the expected result type for an INSERT query.
126
73
 
127
74
  Returns:
128
75
  The type of result expected for INSERT operations.
129
76
  """
130
- return SQLResult[RowT]
77
+ return SQLResult
131
78
 
132
79
  def _get_insert_expression(self) -> exp.Insert:
133
80
  """Safely gets and casts the internal expression to exp.Insert.
@@ -172,7 +119,18 @@ class Insert(QueryBuilder[RowT], ReturningClauseMixin, InsertValuesMixin, Insert
172
119
  msg = ERR_MSG_VALUES_COLUMNS_MISMATCH.format(values_len=len(values), columns_len=len(self._columns))
173
120
  raise SQLBuilderError(msg)
174
121
 
175
- param_names = [self._add_parameter(value) for value in values]
122
+ param_names = []
123
+ for i, value in enumerate(values):
124
+ # Try to use column name if available, otherwise use position-based name
125
+ if self._columns and i < len(self._columns):
126
+ column_name = (
127
+ str(self._columns[i]).split(".")[-1] if "." in str(self._columns[i]) else str(self._columns[i])
128
+ )
129
+ param_name = self._generate_unique_parameter_name(column_name)
130
+ else:
131
+ param_name = self._generate_unique_parameter_name(f"value_{i + 1}")
132
+ _, param_name = self.add_parameter(value, name=param_name)
133
+ param_names.append(param_name)
176
134
  value_placeholders = tuple(exp.var(name) for name in param_names)
177
135
 
178
136
  current_values_expression = insert_expr.args.get("expression")
@@ -183,8 +141,6 @@ class Insert(QueryBuilder[RowT], ReturningClauseMixin, InsertValuesMixin, Insert
183
141
  elif isinstance(current_values_expression, exp.Values):
184
142
  current_values_expression.expressions.append(exp.Tuple(expressions=list(value_placeholders)))
185
143
  else:
186
- # This case should ideally not be reached if logic is correct:
187
- # means _values_added_count > 0 but expression is not exp.Values.
188
144
  new_values_node = exp.Values(expressions=[exp.Tuple(expressions=list(value_placeholders))])
189
145
  insert_expr.set("expression", new_values_node)
190
146
 
@@ -212,7 +168,6 @@ class Insert(QueryBuilder[RowT], ReturningClauseMixin, InsertValuesMixin, Insert
212
168
  if not self._columns:
213
169
  self.columns(*data.keys())
214
170
  elif set(self._columns) != set(data.keys()):
215
- # Verify that dictionary keys match existing columns
216
171
  msg = f"Dictionary keys {set(data.keys())} do not match existing columns {set(self._columns)}."
217
172
  raise SQLBuilderError(msg)
218
173
 
@@ -267,12 +222,10 @@ class Insert(QueryBuilder[RowT], ReturningClauseMixin, InsertValuesMixin, Insert
267
222
  For a more general solution, you might need dialect-specific handling.
268
223
  """
269
224
  insert_expr = self._get_insert_expression()
270
- # Using sqlglot's OnConflict expression if available
271
225
  try:
272
226
  on_conflict = exp.OnConflict(this=None, expressions=[])
273
227
  insert_expr.set("on", on_conflict)
274
228
  except AttributeError:
275
- # Fallback for older sqlglot versions
276
229
  pass
277
230
  return self
278
231
 
@@ -0,0 +1,56 @@
1
+ """Safe SQL query builder with validation and parameter binding.
2
+
3
+ This module provides a fluent interface for building SQL queries safely,
4
+ with automatic parameter binding and validation.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+
9
+ from sqlglot import exp
10
+
11
+ from sqlspec.builder._base import QueryBuilder
12
+ from sqlspec.builder.mixins import (
13
+ MergeIntoClauseMixin,
14
+ MergeMatchedClauseMixin,
15
+ MergeNotMatchedBySourceClauseMixin,
16
+ MergeNotMatchedClauseMixin,
17
+ MergeOnClauseMixin,
18
+ MergeUsingClauseMixin,
19
+ )
20
+ from sqlspec.core.result import SQLResult
21
+
22
+ __all__ = ("Merge",)
23
+
24
+
25
+ @dataclass(unsafe_hash=True)
26
+ class Merge(
27
+ QueryBuilder,
28
+ MergeUsingClauseMixin,
29
+ MergeOnClauseMixin,
30
+ MergeMatchedClauseMixin,
31
+ MergeNotMatchedClauseMixin,
32
+ MergeIntoClauseMixin,
33
+ MergeNotMatchedBySourceClauseMixin,
34
+ ):
35
+ """Builder for MERGE statements.
36
+
37
+ This builder provides a fluent interface for constructing SQL MERGE statements
38
+ (also known as UPSERT in some databases) with automatic parameter binding and validation.
39
+ """
40
+
41
+ @property
42
+ def _expected_result_type(self) -> "type[SQLResult]":
43
+ """Return the expected result type for this builder.
44
+
45
+ Returns:
46
+ The SQLResult type for MERGE statements.
47
+ """
48
+ return SQLResult
49
+
50
+ def _create_base_expression(self) -> "exp.Merge":
51
+ """Create a base MERGE expression.
52
+
53
+ Returns:
54
+ A new sqlglot Merge expression with empty clauses.
55
+ """
56
+ return exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))