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
@@ -4,16 +4,16 @@ Provides statement builders (select, insert, update, etc.) and column expression
4
4
  """
5
5
 
6
6
  import logging
7
- from typing import TYPE_CHECKING, Any, Optional, Union
7
+ from typing import TYPE_CHECKING, Any, Union
8
8
 
9
9
  import sqlglot
10
10
  from sqlglot import exp
11
11
  from sqlglot.dialects.dialect import DialectType
12
12
  from sqlglot.errors import ParseError as SQLGlotParseError
13
13
 
14
- from sqlspec.builder import (
14
+ from sqlspec.builder._column import Column
15
+ from sqlspec.builder._ddl import (
15
16
  AlterTable,
16
- Column,
17
17
  CommentOn,
18
18
  CreateIndex,
19
19
  CreateMaterializedView,
@@ -21,18 +21,14 @@ from sqlspec.builder import (
21
21
  CreateTable,
22
22
  CreateTableAsSelect,
23
23
  CreateView,
24
- Delete,
25
24
  DropIndex,
26
25
  DropSchema,
27
26
  DropTable,
28
27
  DropView,
29
- Insert,
30
- Merge,
31
28
  RenameTable,
32
- Select,
33
29
  Truncate,
34
- Update,
35
30
  )
31
+ from sqlspec.builder._delete import Delete
36
32
  from sqlspec.builder._expression_wrappers import (
37
33
  AggregateExpression,
38
34
  ConversionExpression,
@@ -40,8 +36,12 @@ from sqlspec.builder._expression_wrappers import (
40
36
  MathExpression,
41
37
  StringExpression,
42
38
  )
43
- from sqlspec.builder.mixins._join_operations import JoinBuilder
44
- from sqlspec.builder.mixins._select_operations import Case, SubqueryBuilder, WindowFunctionBuilder
39
+ from sqlspec.builder._insert import Insert
40
+ from sqlspec.builder._join import JoinBuilder
41
+ from sqlspec.builder._merge import Merge
42
+ from sqlspec.builder._parsing_utils import extract_expression, to_expression
43
+ from sqlspec.builder._select import Case, Select, SubqueryBuilder, WindowFunctionBuilder
44
+ from sqlspec.builder._update import Update
45
45
  from sqlspec.core.statement import SQL
46
46
  from sqlspec.exceptions import SQLBuilderError
47
47
 
@@ -204,7 +204,7 @@ class SQLFactory:
204
204
  select_builder.select(*columns_or_sql)
205
205
  return select_builder
206
206
 
207
- def insert(self, table_or_sql: Optional[str] = None, dialect: DialectType = None) -> "Insert":
207
+ def insert(self, table_or_sql: str | None = None, dialect: DialectType = None) -> "Insert":
208
208
  builder_dialect = dialect or self.dialect
209
209
  builder = Insert(dialect=builder_dialect)
210
210
  if table_or_sql:
@@ -221,7 +221,7 @@ class SQLFactory:
221
221
  return builder.into(table_or_sql)
222
222
  return builder
223
223
 
224
- def update(self, table_or_sql: Optional[str] = None, dialect: DialectType = None) -> "Update":
224
+ def update(self, table_or_sql: str | None = None, dialect: DialectType = None) -> "Update":
225
225
  builder_dialect = dialect or self.dialect
226
226
  builder = Update(dialect=builder_dialect)
227
227
  if table_or_sql:
@@ -237,7 +237,7 @@ class SQLFactory:
237
237
  return builder.table(table_or_sql)
238
238
  return builder
239
239
 
240
- def delete(self, table_or_sql: Optional[str] = None, dialect: DialectType = None) -> "Delete":
240
+ def delete(self, table_or_sql: str | None = None, dialect: DialectType = None) -> "Delete":
241
241
  builder_dialect = dialect or self.dialect
242
242
  builder = Delete(dialect=builder_dialect)
243
243
  if table_or_sql and self._looks_like_sql(table_or_sql):
@@ -251,7 +251,7 @@ class SQLFactory:
251
251
  return self._populate_delete_from_sql(builder, table_or_sql)
252
252
  return builder
253
253
 
254
- def merge(self, table_or_sql: Optional[str] = None, dialect: DialectType = None) -> "Merge":
254
+ def merge(self, table_or_sql: str | None = None, dialect: DialectType = None) -> "Merge":
255
255
  builder_dialect = dialect or self.dialect
256
256
  builder = Merge(dialect=builder_dialect)
257
257
  if table_or_sql:
@@ -422,7 +422,7 @@ class SQLFactory:
422
422
  return CommentOn(dialect=dialect or self.dialect)
423
423
 
424
424
  @staticmethod
425
- def _looks_like_sql(candidate: str, expected_type: Optional[str] = None) -> bool:
425
+ def _looks_like_sql(candidate: str, expected_type: str | None = None) -> bool:
426
426
  """Determine if a string looks like SQL.
427
427
 
428
428
  Args:
@@ -524,7 +524,7 @@ class SQLFactory:
524
524
  logger.warning("Failed to parse MERGE SQL, falling back to traditional mode: %s", e)
525
525
  return builder
526
526
 
527
- def column(self, name: str, table: Optional[str] = None) -> Column:
527
+ def column(self, name: str, table: str | None = None) -> Column:
528
528
  """Create a column reference.
529
529
 
530
530
  Args:
@@ -682,7 +682,7 @@ class SQLFactory:
682
682
  return Column(name)
683
683
 
684
684
  @staticmethod
685
- def raw(sql_fragment: str, **parameters: Any) -> "Union[exp.Expression, SQL]":
685
+ def raw(sql_fragment: str, **parameters: Any) -> "exp.Expression | SQL":
686
686
  """Create a raw SQL expression from a string fragment with optional parameters.
687
687
 
688
688
  Args:
@@ -746,7 +746,7 @@ class SQLFactory:
746
746
  if isinstance(column, str) and column == "*":
747
747
  expr = exp.Count(this=exp.Star(), distinct=distinct)
748
748
  else:
749
- col_expr = self._extract_expression(column)
749
+ col_expr = extract_expression(column)
750
750
  expr = exp.Count(this=col_expr, distinct=distinct)
751
751
  return AggregateExpression(expr)
752
752
 
@@ -774,7 +774,7 @@ class SQLFactory:
774
774
  Returns:
775
775
  SUM expression.
776
776
  """
777
- col_expr = SQLFactory._extract_expression(column)
777
+ col_expr = extract_expression(column)
778
778
  return AggregateExpression(exp.Sum(this=col_expr, distinct=distinct))
779
779
 
780
780
  @staticmethod
@@ -787,7 +787,7 @@ class SQLFactory:
787
787
  Returns:
788
788
  AVG expression.
789
789
  """
790
- col_expr = SQLFactory._extract_expression(column)
790
+ col_expr = extract_expression(column)
791
791
  return AggregateExpression(exp.Avg(this=col_expr))
792
792
 
793
793
  @staticmethod
@@ -800,7 +800,7 @@ class SQLFactory:
800
800
  Returns:
801
801
  MAX expression.
802
802
  """
803
- col_expr = SQLFactory._extract_expression(column)
803
+ col_expr = extract_expression(column)
804
804
  return AggregateExpression(exp.Max(this=col_expr))
805
805
 
806
806
  @staticmethod
@@ -813,11 +813,11 @@ class SQLFactory:
813
813
  Returns:
814
814
  MIN expression.
815
815
  """
816
- col_expr = SQLFactory._extract_expression(column)
816
+ col_expr = extract_expression(column)
817
817
  return AggregateExpression(exp.Min(this=col_expr))
818
818
 
819
819
  @staticmethod
820
- def rollup(*columns: Union[str, exp.Expression]) -> FunctionExpression:
820
+ def rollup(*columns: str | exp.Expression) -> FunctionExpression:
821
821
  """Create a ROLLUP expression for GROUP BY clauses.
822
822
 
823
823
  Args:
@@ -839,7 +839,7 @@ class SQLFactory:
839
839
  return FunctionExpression(exp.Rollup(expressions=column_exprs))
840
840
 
841
841
  @staticmethod
842
- def cube(*columns: Union[str, exp.Expression]) -> FunctionExpression:
842
+ def cube(*columns: str | exp.Expression) -> FunctionExpression:
843
843
  """Create a CUBE expression for GROUP BY clauses.
844
844
 
845
845
  Args:
@@ -861,7 +861,7 @@ class SQLFactory:
861
861
  return FunctionExpression(exp.Cube(expressions=column_exprs))
862
862
 
863
863
  @staticmethod
864
- def grouping_sets(*column_sets: Union[tuple[str, ...], list[str]]) -> FunctionExpression:
864
+ def grouping_sets(*column_sets: tuple[str, ...] | list[str]) -> FunctionExpression:
865
865
  """Create a GROUPING SETS expression for GROUP BY clauses.
866
866
 
867
867
  Args:
@@ -895,7 +895,7 @@ class SQLFactory:
895
895
  return FunctionExpression(exp.GroupingSets(expressions=set_expressions))
896
896
 
897
897
  @staticmethod
898
- def any(values: Union[list[Any], exp.Expression, str]) -> FunctionExpression:
898
+ def any(values: list[Any] | exp.Expression | str) -> FunctionExpression:
899
899
  """Create an ANY expression for use with comparison operators.
900
900
 
901
901
  Args:
@@ -923,7 +923,7 @@ class SQLFactory:
923
923
  return FunctionExpression(exp.Any(this=values))
924
924
 
925
925
  @staticmethod
926
- def not_any_(values: Union[list[Any], exp.Expression, str]) -> FunctionExpression:
926
+ def not_any_(values: list[Any] | exp.Expression | str) -> FunctionExpression:
927
927
  """Create a NOT ANY expression for use with comparison operators.
928
928
 
929
929
  Args:
@@ -945,7 +945,7 @@ class SQLFactory:
945
945
  return SQLFactory.any(values)
946
946
 
947
947
  @staticmethod
948
- def concat(*expressions: Union[str, exp.Expression]) -> StringExpression:
948
+ def concat(*expressions: str | exp.Expression) -> StringExpression:
949
949
  """Create a CONCAT expression.
950
950
 
951
951
  Args:
@@ -958,7 +958,7 @@ class SQLFactory:
958
958
  return StringExpression(exp.Concat(expressions=exprs))
959
959
 
960
960
  @staticmethod
961
- def upper(column: Union[str, exp.Expression]) -> StringExpression:
961
+ def upper(column: str | exp.Expression) -> StringExpression:
962
962
  """Create an UPPER expression.
963
963
 
964
964
  Args:
@@ -971,7 +971,7 @@ class SQLFactory:
971
971
  return StringExpression(exp.Upper(this=col_expr))
972
972
 
973
973
  @staticmethod
974
- def lower(column: Union[str, exp.Expression]) -> StringExpression:
974
+ def lower(column: str | exp.Expression) -> StringExpression:
975
975
  """Create a LOWER expression.
976
976
 
977
977
  Args:
@@ -984,7 +984,7 @@ class SQLFactory:
984
984
  return StringExpression(exp.Lower(this=col_expr))
985
985
 
986
986
  @staticmethod
987
- def length(column: Union[str, exp.Expression]) -> StringExpression:
987
+ def length(column: str | exp.Expression) -> StringExpression:
988
988
  """Create a LENGTH expression.
989
989
 
990
990
  Args:
@@ -997,7 +997,7 @@ class SQLFactory:
997
997
  return StringExpression(exp.Length(this=col_expr))
998
998
 
999
999
  @staticmethod
1000
- def round(column: Union[str, exp.Expression], decimals: int = 0) -> MathExpression:
1000
+ def round(column: str | exp.Expression, decimals: int = 0) -> MathExpression:
1001
1001
  """Create a ROUND expression.
1002
1002
 
1003
1003
  Args:
@@ -1035,46 +1035,7 @@ class SQLFactory:
1035
1035
  return FunctionExpression(exp.convert(value))
1036
1036
 
1037
1037
  @staticmethod
1038
- def _to_expression(value: Any) -> exp.Expression:
1039
- """Convert a Python value to a raw SQLGlot expression.
1040
-
1041
- Args:
1042
- value: Python value or SQLGlot expression to convert.
1043
-
1044
- Returns:
1045
- Raw SQLGlot expression.
1046
- """
1047
- if isinstance(value, exp.Expression):
1048
- return value
1049
- return exp.convert(value)
1050
-
1051
- @staticmethod
1052
- def _extract_expression(value: Any) -> exp.Expression:
1053
- """Extract SQLGlot expression from value, handling our wrapper types.
1054
-
1055
- Args:
1056
- value: String, SQLGlot expression, or our wrapper type.
1057
-
1058
- Returns:
1059
- Raw SQLGlot expression.
1060
- """
1061
- from sqlspec.builder._expression_wrappers import ExpressionWrapper
1062
- from sqlspec.builder.mixins._select_operations import Case
1063
-
1064
- if isinstance(value, str):
1065
- return exp.column(value)
1066
- if isinstance(value, Column):
1067
- return value.sqlglot_expression
1068
- if isinstance(value, ExpressionWrapper):
1069
- return value.expression
1070
- if isinstance(value, Case):
1071
- return exp.Case(ifs=value.conditions, default=value.default)
1072
- if isinstance(value, exp.Expression):
1073
- return value
1074
- return exp.convert(value)
1075
-
1076
- @staticmethod
1077
- def decode(column: Union[str, exp.Expression], *args: Union[str, exp.Expression, Any]) -> FunctionExpression:
1038
+ def decode(column: str | exp.Expression, *args: str | exp.Expression | Any) -> FunctionExpression:
1078
1039
  """Create a DECODE expression (Oracle-style conditional logic).
1079
1040
 
1080
1041
  DECODE compares column to each search value and returns the corresponding result.
@@ -1109,14 +1070,14 @@ class SQLFactory:
1109
1070
 
1110
1071
  for i in range(0, len(args) - 1, 2):
1111
1072
  if i + 1 >= len(args):
1112
- default = SQLFactory._to_expression(args[i])
1073
+ default = to_expression(args[i])
1113
1074
  break
1114
1075
 
1115
1076
  search_val = args[i]
1116
1077
  result_val = args[i + 1]
1117
1078
 
1118
- search_expr = SQLFactory._to_expression(search_val)
1119
- result_expr = SQLFactory._to_expression(result_val)
1079
+ search_expr = to_expression(search_val)
1080
+ result_expr = to_expression(result_val)
1120
1081
 
1121
1082
  condition = exp.EQ(this=col_expr, expression=search_expr)
1122
1083
  conditions.append(exp.If(this=condition, true=result_expr))
@@ -1124,7 +1085,7 @@ class SQLFactory:
1124
1085
  return FunctionExpression(exp.Case(ifs=conditions, default=default))
1125
1086
 
1126
1087
  @staticmethod
1127
- def cast(column: Union[str, exp.Expression], data_type: str) -> ConversionExpression:
1088
+ def cast(column: str | exp.Expression, data_type: str) -> ConversionExpression:
1128
1089
  """Create a CAST expression for type conversion.
1129
1090
 
1130
1091
  Args:
@@ -1138,7 +1099,7 @@ class SQLFactory:
1138
1099
  return ConversionExpression(exp.Cast(this=col_expr, to=exp.DataType.build(data_type)))
1139
1100
 
1140
1101
  @staticmethod
1141
- def coalesce(*expressions: Union[str, exp.Expression]) -> ConversionExpression:
1102
+ def coalesce(*expressions: str | exp.Expression) -> ConversionExpression:
1142
1103
  """Create a COALESCE expression.
1143
1104
 
1144
1105
  Args:
@@ -1151,9 +1112,7 @@ class SQLFactory:
1151
1112
  return ConversionExpression(exp.Coalesce(expressions=exprs))
1152
1113
 
1153
1114
  @staticmethod
1154
- def nvl(
1155
- column: Union[str, exp.Expression], substitute_value: Union[str, exp.Expression, Any]
1156
- ) -> ConversionExpression:
1115
+ def nvl(column: str | exp.Expression, substitute_value: str | exp.Expression | Any) -> ConversionExpression:
1157
1116
  """Create an NVL (Oracle-style) expression using COALESCE.
1158
1117
 
1159
1118
  Args:
@@ -1164,14 +1123,14 @@ class SQLFactory:
1164
1123
  COALESCE expression equivalent to NVL.
1165
1124
  """
1166
1125
  col_expr = exp.column(column) if isinstance(column, str) else column
1167
- sub_expr = SQLFactory._to_expression(substitute_value)
1126
+ sub_expr = to_expression(substitute_value)
1168
1127
  return ConversionExpression(exp.Coalesce(expressions=[col_expr, sub_expr]))
1169
1128
 
1170
1129
  @staticmethod
1171
1130
  def nvl2(
1172
- column: Union[str, exp.Expression],
1173
- value_if_not_null: Union[str, exp.Expression, Any],
1174
- value_if_null: Union[str, exp.Expression, Any],
1131
+ column: str | exp.Expression,
1132
+ value_if_not_null: str | exp.Expression | Any,
1133
+ value_if_null: str | exp.Expression | Any,
1175
1134
  ) -> ConversionExpression:
1176
1135
  """Create an NVL2 (Oracle-style) expression using CASE.
1177
1136
 
@@ -1192,8 +1151,8 @@ class SQLFactory:
1192
1151
  ```
1193
1152
  """
1194
1153
  col_expr = exp.column(column) if isinstance(column, str) else column
1195
- not_null_expr = SQLFactory._to_expression(value_if_not_null)
1196
- null_expr = SQLFactory._to_expression(value_if_null)
1154
+ not_null_expr = to_expression(value_if_not_null)
1155
+ null_expr = to_expression(value_if_null)
1197
1156
 
1198
1157
  is_null = exp.Is(this=col_expr, expression=exp.Null())
1199
1158
  condition = exp.Not(this=is_null)
@@ -1284,8 +1243,8 @@ class SQLFactory:
1284
1243
 
1285
1244
  def row_number(
1286
1245
  self,
1287
- partition_by: Optional[Union[str, list[str], exp.Expression]] = None,
1288
- order_by: Optional[Union[str, list[str], exp.Expression]] = None,
1246
+ partition_by: str | list[str] | exp.Expression | None = None,
1247
+ order_by: str | list[str] | exp.Expression | None = None,
1289
1248
  ) -> FunctionExpression:
1290
1249
  """Create a ROW_NUMBER() window function.
1291
1250
 
@@ -1300,8 +1259,8 @@ class SQLFactory:
1300
1259
 
1301
1260
  def rank(
1302
1261
  self,
1303
- partition_by: Optional[Union[str, list[str], exp.Expression]] = None,
1304
- order_by: Optional[Union[str, list[str], exp.Expression]] = None,
1262
+ partition_by: str | list[str] | exp.Expression | None = None,
1263
+ order_by: str | list[str] | exp.Expression | None = None,
1305
1264
  ) -> FunctionExpression:
1306
1265
  """Create a RANK() window function.
1307
1266
 
@@ -1316,8 +1275,8 @@ class SQLFactory:
1316
1275
 
1317
1276
  def dense_rank(
1318
1277
  self,
1319
- partition_by: Optional[Union[str, list[str], exp.Expression]] = None,
1320
- order_by: Optional[Union[str, list[str], exp.Expression]] = None,
1278
+ partition_by: str | list[str] | exp.Expression | None = None,
1279
+ order_by: str | list[str] | exp.Expression | None = None,
1321
1280
  ) -> FunctionExpression:
1322
1281
  """Create a DENSE_RANK() window function.
1323
1282
 
@@ -1334,8 +1293,8 @@ class SQLFactory:
1334
1293
  def _create_window_function(
1335
1294
  func_name: str,
1336
1295
  func_args: list[exp.Expression],
1337
- partition_by: Optional[Union[str, list[str], exp.Expression]] = None,
1338
- order_by: Optional[Union[str, list[str], exp.Expression]] = None,
1296
+ partition_by: str | list[str] | exp.Expression | None = None,
1297
+ order_by: str | list[str] | exp.Expression | None = None,
1339
1298
  ) -> FunctionExpression:
1340
1299
  """Helper to create window function expressions.
1341
1300
 
@@ -4,13 +4,15 @@ Provides a fluent interface for building SQL INSERT queries with
4
4
  parameter binding and validation.
5
5
  """
6
6
 
7
- from typing import TYPE_CHECKING, Any, Final, Optional
7
+ from typing import TYPE_CHECKING, Any, Final
8
8
 
9
9
  from sqlglot import exp
10
10
  from typing_extensions import Self
11
11
 
12
12
  from sqlspec.builder._base import QueryBuilder
13
- from sqlspec.builder.mixins import InsertFromSelectMixin, InsertIntoClauseMixin, InsertValuesMixin, ReturningClauseMixin
13
+ from sqlspec.builder._dml import InsertFromSelectMixin, InsertIntoClauseMixin, InsertValuesMixin
14
+ from sqlspec.builder._parsing_utils import extract_sql_object_expression
15
+ from sqlspec.builder._select import ReturningClauseMixin
14
16
  from sqlspec.core.result import SQLResult
15
17
  from sqlspec.exceptions import SQLBuilderError
16
18
  from sqlspec.utils.type_guards import has_expression_and_sql
@@ -22,9 +24,6 @@ if TYPE_CHECKING:
22
24
  __all__ = ("Insert",)
23
25
 
24
26
  ERR_MSG_TABLE_NOT_SET: Final[str] = "The target table must be set using .into() before adding values."
25
- ERR_MSG_VALUES_COLUMNS_MISMATCH: Final[str] = (
26
- "Number of values ({values_len}) does not match the number of specified columns ({columns_len})."
27
- )
28
27
  ERR_MSG_INTERNAL_EXPRESSION_TYPE: Final[str] = "Internal error: expression is not an Insert instance as expected."
29
28
  ERR_MSG_EXPRESSION_NOT_INITIALIZED: Final[str] = "Internal error: base expression not initialized."
30
29
 
@@ -37,7 +36,7 @@ class Insert(QueryBuilder, ReturningClauseMixin, InsertValuesMixin, InsertFromSe
37
36
 
38
37
  __slots__ = ("_columns", "_table", "_values_added_count")
39
38
 
40
- def __init__(self, table: Optional[str] = None, **kwargs: Any) -> None:
39
+ def __init__(self, table: str | None = None, **kwargs: Any) -> None:
41
40
  """Initialize INSERT with optional table.
42
41
 
43
42
  Args:
@@ -46,8 +45,7 @@ class Insert(QueryBuilder, ReturningClauseMixin, InsertValuesMixin, InsertFromSe
46
45
  """
47
46
  super().__init__(**kwargs)
48
47
 
49
- # Initialize Insert-specific attributes
50
- self._table: Optional[str] = None
48
+ self._table: str | None = None
51
49
  self._columns: list[str] = []
52
50
  self._values_added_count: int = 0
53
51
 
@@ -94,95 +92,6 @@ class Insert(QueryBuilder, ReturningClauseMixin, InsertValuesMixin, InsertFromSe
94
92
  """Get the insert expression (public API)."""
95
93
  return self._get_insert_expression()
96
94
 
97
- def values(self, *values: Any, **kwargs: Any) -> "Self":
98
- """Adds a row of values to the INSERT statement.
99
-
100
- This method can be called multiple times to insert multiple rows,
101
- resulting in a multi-row INSERT statement like `VALUES (...), (...)`.
102
-
103
- Supports:
104
- - values(val1, val2, val3)
105
- - values(col1=val1, col2=val2)
106
- - values(mapping)
107
-
108
- Args:
109
- *values: The values for the row to be inserted. The number of values
110
- must match the number of columns set by `columns()`, if `columns()` was called
111
- and specified any non-empty list of columns.
112
- **kwargs: Column-value pairs for named values.
113
-
114
- Returns:
115
- The current builder instance for method chaining.
116
-
117
- Raises:
118
- SQLBuilderError: If `into()` has not been called to set the table,
119
- or if `columns()` was called with a non-empty list of columns
120
- and the number of values does not match the number of specified columns.
121
- """
122
- if not self._table:
123
- raise SQLBuilderError(ERR_MSG_TABLE_NOT_SET)
124
-
125
- if kwargs:
126
- if values:
127
- msg = "Cannot mix positional values with keyword values."
128
- raise SQLBuilderError(msg)
129
- return self.values_from_dict(kwargs)
130
-
131
- if len(values) == 1:
132
- values_0 = values[0]
133
- if hasattr(values_0, "items") and hasattr(values_0, "keys"):
134
- return self.values_from_dict(values_0)
135
-
136
- insert_expr = self.get_insert_expression()
137
-
138
- if self._columns and len(values) != len(self._columns):
139
- msg = ERR_MSG_VALUES_COLUMNS_MISMATCH.format(values_len=len(values), columns_len=len(self._columns))
140
- raise SQLBuilderError(msg)
141
-
142
- value_placeholders: list[exp.Expression] = []
143
- for i, value in enumerate(values):
144
- if isinstance(value, exp.Expression):
145
- value_placeholders.append(value)
146
- elif has_expression_and_sql(value):
147
- # Handle SQL objects (from sql.raw with parameters)
148
- expression = getattr(value, "expression", None)
149
- if expression is not None and isinstance(expression, exp.Expression):
150
- # Merge parameters from SQL object into builder
151
- self._merge_sql_object_parameters(value)
152
- value_placeholders.append(expression)
153
- else:
154
- # If expression is None, fall back to parsing the raw SQL
155
- sql_text = getattr(value, "sql", "")
156
- # Merge parameters even when parsing raw SQL
157
- self._merge_sql_object_parameters(value)
158
- # Check if sql_text is callable (like Expression.sql method)
159
- if callable(sql_text):
160
- sql_text = str(value)
161
- value_expr = exp.maybe_parse(sql_text) or exp.convert(str(sql_text))
162
- value_placeholders.append(value_expr)
163
- else:
164
- if self._columns and i < len(self._columns):
165
- column_str = str(self._columns[i])
166
- column_name = column_str.rsplit(".", maxsplit=1)[-1] if "." in column_str else column_str
167
- param_name = self.generate_unique_parameter_name(column_name)
168
- else:
169
- param_name = self.generate_unique_parameter_name(f"value_{i + 1}")
170
- _, param_name = self.add_parameter(value, name=param_name)
171
- value_placeholders.append(exp.Placeholder(this=param_name))
172
-
173
- tuple_expr = exp.Tuple(expressions=value_placeholders)
174
- if self._values_added_count == 0:
175
- insert_expr.set("expression", exp.Values(expressions=[tuple_expr]))
176
- else:
177
- current_values = insert_expr.args.get("expression")
178
- if isinstance(current_values, exp.Values):
179
- current_values.expressions.append(tuple_expr)
180
- else:
181
- insert_expr.set("expression", exp.Values(expressions=[tuple_expr]))
182
-
183
- self._values_added_count += 1
184
- return self
185
-
186
95
  def values_from_dict(self, data: "Mapping[str, Any]") -> "Self":
187
96
  """Adds a row of values from a dictionary.
188
97
 
@@ -258,17 +167,14 @@ class Insert(QueryBuilder, ReturningClauseMixin, InsertValuesMixin, InsertFromSe
258
167
 
259
168
  Example:
260
169
  ```python
261
- # ON CONFLICT (id) DO NOTHING
262
170
  sql.insert("users").values(id=1, name="John").on_conflict(
263
171
  "id"
264
172
  ).do_nothing()
265
173
 
266
- # ON CONFLICT (email, username) DO UPDATE SET updated_at = NOW()
267
174
  sql.insert("users").values(...).on_conflict(
268
175
  "email", "username"
269
176
  ).do_update(updated_at=sql.raw("NOW()"))
270
177
 
271
- # ON CONFLICT DO NOTHING (catches all conflicts)
272
178
  sql.insert("users").values(...).on_conflict().do_nothing()
273
179
  ```
274
180
  """
@@ -290,22 +196,41 @@ class Insert(QueryBuilder, ReturningClauseMixin, InsertValuesMixin, InsertFromSe
290
196
  return self.on_conflict(*columns).do_nothing()
291
197
 
292
198
  def on_duplicate_key_update(self, **kwargs: Any) -> "Insert":
293
- """Adds conflict resolution using the ON CONFLICT syntax (cross-database compatible).
199
+ """Adds MySQL-style ON DUPLICATE KEY UPDATE clause.
294
200
 
295
201
  Args:
296
- **kwargs: Column-value pairs to update on conflict.
202
+ **kwargs: Column-value pairs to update on duplicate key.
297
203
 
298
204
  Returns:
299
205
  The current builder instance for method chaining.
300
206
 
301
207
  Note:
302
- This method uses PostgreSQL-style ON CONFLICT syntax but SQLGlot will
303
- transpile it to the appropriate syntax for each database (MySQL's
304
- ON DUPLICATE KEY UPDATE, etc.).
208
+ This method creates MySQL-specific ON DUPLICATE KEY UPDATE syntax.
209
+ For PostgreSQL, use on_conflict() instead.
305
210
  """
306
211
  if not kwargs:
307
212
  return self
308
- return self.on_conflict().do_update(**kwargs)
213
+
214
+ insert_expr = self._get_insert_expression()
215
+
216
+ set_expressions = []
217
+ for col, val in kwargs.items():
218
+ if has_expression_and_sql(val):
219
+ value_expr = extract_sql_object_expression(val, builder=self)
220
+ elif isinstance(val, exp.Expression):
221
+ value_expr = val
222
+ else:
223
+ param_name = self.generate_unique_parameter_name(col)
224
+ _, param_name = self.add_parameter(val, name=param_name)
225
+ value_expr = exp.Placeholder(this=param_name)
226
+
227
+ set_expressions.append(exp.EQ(this=exp.column(col), expression=value_expr))
228
+
229
+ on_conflict = exp.OnConflict(duplicate=True, action=exp.var("UPDATE"), expressions=set_expressions or None)
230
+
231
+ insert_expr.set("conflict", on_conflict)
232
+
233
+ return self
309
234
 
310
235
 
311
236
  class ConflictBuilder:
@@ -342,7 +267,6 @@ class ConflictBuilder:
342
267
  """
343
268
  insert_expr = self._insert_builder.get_insert_expression()
344
269
 
345
- # Create ON CONFLICT with proper structure
346
270
  conflict_keys = [exp.to_identifier(col) for col in self._columns] if self._columns else None
347
271
  on_conflict = exp.OnConflict(conflict_keys=conflict_keys, action=exp.var("DO NOTHING"))
348
272
 
@@ -369,42 +293,19 @@ class ConflictBuilder:
369
293
  """
370
294
  insert_expr = self._insert_builder.get_insert_expression()
371
295
 
372
- # Create SET expressions for the UPDATE
373
296
  set_expressions = []
374
297
  for col, val in kwargs.items():
375
298
  if has_expression_and_sql(val):
376
- # Handle SQL objects (from sql.raw with parameters)
377
- expression = getattr(val, "expression", None)
378
- if expression is not None and isinstance(expression, exp.Expression):
379
- # Merge parameters from SQL object into builder
380
- if hasattr(val, "parameters"):
381
- sql_parameters = getattr(val, "parameters", {})
382
- for param_name, param_value in sql_parameters.items():
383
- self._insert_builder.add_parameter(param_value, name=param_name)
384
- value_expr = expression
385
- else:
386
- # If expression is None, fall back to parsing the raw SQL
387
- sql_text = getattr(val, "sql", "")
388
- # Merge parameters even when parsing raw SQL
389
- if hasattr(val, "parameters"):
390
- sql_parameters = getattr(val, "parameters", {})
391
- for param_name, param_value in sql_parameters.items():
392
- self._insert_builder.add_parameter(param_value, name=param_name)
393
- # Check if sql_text is callable (like Expression.sql method)
394
- if callable(sql_text):
395
- sql_text = str(val)
396
- value_expr = exp.maybe_parse(sql_text) or exp.convert(str(sql_text))
299
+ value_expr = extract_sql_object_expression(val, builder=self._insert_builder)
397
300
  elif isinstance(val, exp.Expression):
398
301
  value_expr = val
399
302
  else:
400
- # Create parameter for regular values
401
303
  param_name = self._insert_builder.generate_unique_parameter_name(col)
402
304
  _, param_name = self._insert_builder.add_parameter(val, name=param_name)
403
305
  value_expr = exp.Placeholder(this=param_name)
404
306
 
405
307
  set_expressions.append(exp.EQ(this=exp.column(col), expression=value_expr))
406
308
 
407
- # Create ON CONFLICT with proper structure
408
309
  conflict_keys = [exp.to_identifier(col) for col in self._columns] if self._columns else None
409
310
  on_conflict = exp.OnConflict(
410
311
  conflict_keys=conflict_keys, action=exp.var("DO UPDATE"), expressions=set_expressions or None