sqlspec 0.13.1__py3-none-any.whl → 0.16.2__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 (185) hide show
  1. sqlspec/__init__.py +71 -8
  2. sqlspec/__main__.py +12 -0
  3. sqlspec/__metadata__.py +1 -3
  4. sqlspec/_serialization.py +1 -2
  5. sqlspec/_sql.py +930 -136
  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 +116 -285
  10. sqlspec/adapters/adbc/driver.py +462 -340
  11. sqlspec/adapters/aiosqlite/__init__.py +18 -3
  12. sqlspec/adapters/aiosqlite/_types.py +13 -0
  13. sqlspec/adapters/aiosqlite/config.py +202 -150
  14. sqlspec/adapters/aiosqlite/driver.py +226 -247
  15. sqlspec/adapters/asyncmy/__init__.py +18 -3
  16. sqlspec/adapters/asyncmy/_types.py +12 -0
  17. sqlspec/adapters/asyncmy/config.py +80 -199
  18. sqlspec/adapters/asyncmy/driver.py +257 -215
  19. sqlspec/adapters/asyncpg/__init__.py +19 -4
  20. sqlspec/adapters/asyncpg/_types.py +17 -0
  21. sqlspec/adapters/asyncpg/config.py +81 -214
  22. sqlspec/adapters/asyncpg/driver.py +284 -359
  23. sqlspec/adapters/bigquery/__init__.py +17 -3
  24. sqlspec/adapters/bigquery/_types.py +12 -0
  25. sqlspec/adapters/bigquery/config.py +191 -299
  26. sqlspec/adapters/bigquery/driver.py +474 -634
  27. sqlspec/adapters/duckdb/__init__.py +14 -3
  28. sqlspec/adapters/duckdb/_types.py +12 -0
  29. sqlspec/adapters/duckdb/config.py +414 -397
  30. sqlspec/adapters/duckdb/driver.py +342 -393
  31. sqlspec/adapters/oracledb/__init__.py +19 -5
  32. sqlspec/adapters/oracledb/_types.py +14 -0
  33. sqlspec/adapters/oracledb/config.py +123 -458
  34. sqlspec/adapters/oracledb/driver.py +505 -531
  35. sqlspec/adapters/psqlpy/__init__.py +13 -3
  36. sqlspec/adapters/psqlpy/_types.py +11 -0
  37. sqlspec/adapters/psqlpy/config.py +93 -307
  38. sqlspec/adapters/psqlpy/driver.py +504 -213
  39. sqlspec/adapters/psycopg/__init__.py +19 -5
  40. sqlspec/adapters/psycopg/_types.py +17 -0
  41. sqlspec/adapters/psycopg/config.py +143 -472
  42. sqlspec/adapters/psycopg/driver.py +704 -825
  43. sqlspec/adapters/sqlite/__init__.py +14 -3
  44. sqlspec/adapters/sqlite/_types.py +11 -0
  45. sqlspec/adapters/sqlite/config.py +208 -142
  46. sqlspec/adapters/sqlite/driver.py +263 -278
  47. sqlspec/base.py +105 -9
  48. sqlspec/{statement/builder → builder}/__init__.py +12 -14
  49. sqlspec/{statement/builder/base.py → builder/_base.py} +184 -86
  50. sqlspec/{statement/builder/column.py → builder/_column.py} +97 -60
  51. sqlspec/{statement/builder/ddl.py → builder/_ddl.py} +61 -131
  52. sqlspec/{statement/builder → builder}/_ddl_utils.py +4 -10
  53. sqlspec/{statement/builder/delete.py → builder/_delete.py} +10 -30
  54. sqlspec/builder/_insert.py +421 -0
  55. sqlspec/builder/_merge.py +71 -0
  56. sqlspec/{statement/builder → builder}/_parsing_utils.py +49 -26
  57. sqlspec/builder/_select.py +170 -0
  58. sqlspec/{statement/builder/update.py → builder/_update.py} +16 -20
  59. sqlspec/builder/mixins/__init__.py +55 -0
  60. sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
  61. sqlspec/{statement/builder/mixins/_delete_from.py → builder/mixins/_delete_operations.py} +8 -1
  62. sqlspec/builder/mixins/_insert_operations.py +244 -0
  63. sqlspec/{statement/builder/mixins/_join.py → builder/mixins/_join_operations.py} +45 -13
  64. sqlspec/{statement/builder/mixins/_merge_clauses.py → builder/mixins/_merge_operations.py} +188 -30
  65. sqlspec/builder/mixins/_order_limit_operations.py +135 -0
  66. sqlspec/builder/mixins/_pivot_operations.py +153 -0
  67. sqlspec/builder/mixins/_select_operations.py +604 -0
  68. sqlspec/builder/mixins/_update_operations.py +202 -0
  69. sqlspec/builder/mixins/_where_clause.py +644 -0
  70. sqlspec/cli.py +247 -0
  71. sqlspec/config.py +183 -138
  72. sqlspec/core/__init__.py +63 -0
  73. sqlspec/core/cache.py +871 -0
  74. sqlspec/core/compiler.py +417 -0
  75. sqlspec/core/filters.py +830 -0
  76. sqlspec/core/hashing.py +310 -0
  77. sqlspec/core/parameters.py +1237 -0
  78. sqlspec/core/result.py +677 -0
  79. sqlspec/{statement → core}/splitter.py +321 -191
  80. sqlspec/core/statement.py +676 -0
  81. sqlspec/driver/__init__.py +7 -10
  82. sqlspec/driver/_async.py +422 -163
  83. sqlspec/driver/_common.py +545 -287
  84. sqlspec/driver/_sync.py +426 -160
  85. sqlspec/driver/mixins/__init__.py +2 -13
  86. sqlspec/driver/mixins/_result_tools.py +193 -0
  87. sqlspec/driver/mixins/_sql_translator.py +65 -14
  88. sqlspec/exceptions.py +5 -252
  89. sqlspec/extensions/aiosql/adapter.py +93 -96
  90. sqlspec/extensions/litestar/__init__.py +2 -1
  91. sqlspec/extensions/litestar/cli.py +48 -0
  92. sqlspec/extensions/litestar/config.py +0 -1
  93. sqlspec/extensions/litestar/handlers.py +15 -26
  94. sqlspec/extensions/litestar/plugin.py +21 -16
  95. sqlspec/extensions/litestar/providers.py +17 -52
  96. sqlspec/loader.py +423 -104
  97. sqlspec/migrations/__init__.py +35 -0
  98. sqlspec/migrations/base.py +414 -0
  99. sqlspec/migrations/commands.py +443 -0
  100. sqlspec/migrations/loaders.py +402 -0
  101. sqlspec/migrations/runner.py +213 -0
  102. sqlspec/migrations/tracker.py +140 -0
  103. sqlspec/migrations/utils.py +129 -0
  104. sqlspec/protocols.py +51 -186
  105. sqlspec/storage/__init__.py +1 -1
  106. sqlspec/storage/backends/base.py +37 -40
  107. sqlspec/storage/backends/fsspec.py +136 -112
  108. sqlspec/storage/backends/obstore.py +138 -160
  109. sqlspec/storage/capabilities.py +5 -4
  110. sqlspec/storage/registry.py +57 -106
  111. sqlspec/typing.py +136 -115
  112. sqlspec/utils/__init__.py +2 -2
  113. sqlspec/utils/correlation.py +0 -3
  114. sqlspec/utils/deprecation.py +6 -6
  115. sqlspec/utils/fixtures.py +6 -6
  116. sqlspec/utils/logging.py +0 -2
  117. sqlspec/utils/module_loader.py +7 -12
  118. sqlspec/utils/singleton.py +0 -1
  119. sqlspec/utils/sync_tools.py +17 -38
  120. sqlspec/utils/text.py +12 -51
  121. sqlspec/utils/type_guards.py +482 -235
  122. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/METADATA +7 -2
  123. sqlspec-0.16.2.dist-info/RECORD +134 -0
  124. sqlspec-0.16.2.dist-info/entry_points.txt +2 -0
  125. sqlspec/driver/connection.py +0 -207
  126. sqlspec/driver/mixins/_csv_writer.py +0 -91
  127. sqlspec/driver/mixins/_pipeline.py +0 -512
  128. sqlspec/driver/mixins/_result_utils.py +0 -140
  129. sqlspec/driver/mixins/_storage.py +0 -926
  130. sqlspec/driver/mixins/_type_coercion.py +0 -130
  131. sqlspec/driver/parameters.py +0 -138
  132. sqlspec/service/__init__.py +0 -4
  133. sqlspec/service/_util.py +0 -147
  134. sqlspec/service/base.py +0 -1131
  135. sqlspec/service/pagination.py +0 -26
  136. sqlspec/statement/__init__.py +0 -21
  137. sqlspec/statement/builder/insert.py +0 -288
  138. sqlspec/statement/builder/merge.py +0 -95
  139. sqlspec/statement/builder/mixins/__init__.py +0 -65
  140. sqlspec/statement/builder/mixins/_aggregate_functions.py +0 -250
  141. sqlspec/statement/builder/mixins/_case_builder.py +0 -91
  142. sqlspec/statement/builder/mixins/_common_table_expr.py +0 -90
  143. sqlspec/statement/builder/mixins/_from.py +0 -63
  144. sqlspec/statement/builder/mixins/_group_by.py +0 -118
  145. sqlspec/statement/builder/mixins/_having.py +0 -35
  146. sqlspec/statement/builder/mixins/_insert_from_select.py +0 -47
  147. sqlspec/statement/builder/mixins/_insert_into.py +0 -36
  148. sqlspec/statement/builder/mixins/_insert_values.py +0 -67
  149. sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
  150. sqlspec/statement/builder/mixins/_order_by.py +0 -46
  151. sqlspec/statement/builder/mixins/_pivot.py +0 -79
  152. sqlspec/statement/builder/mixins/_returning.py +0 -37
  153. sqlspec/statement/builder/mixins/_select_columns.py +0 -61
  154. sqlspec/statement/builder/mixins/_set_ops.py +0 -122
  155. sqlspec/statement/builder/mixins/_unpivot.py +0 -77
  156. sqlspec/statement/builder/mixins/_update_from.py +0 -55
  157. sqlspec/statement/builder/mixins/_update_set.py +0 -94
  158. sqlspec/statement/builder/mixins/_update_table.py +0 -29
  159. sqlspec/statement/builder/mixins/_where.py +0 -401
  160. sqlspec/statement/builder/mixins/_window_functions.py +0 -86
  161. sqlspec/statement/builder/select.py +0 -221
  162. sqlspec/statement/filters.py +0 -596
  163. sqlspec/statement/parameter_manager.py +0 -220
  164. sqlspec/statement/parameters.py +0 -867
  165. sqlspec/statement/pipelines/__init__.py +0 -210
  166. sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
  167. sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
  168. sqlspec/statement/pipelines/context.py +0 -115
  169. sqlspec/statement/pipelines/transformers/__init__.py +0 -7
  170. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
  171. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
  172. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
  173. sqlspec/statement/pipelines/validators/__init__.py +0 -23
  174. sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
  175. sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
  176. sqlspec/statement/pipelines/validators/_performance.py +0 -718
  177. sqlspec/statement/pipelines/validators/_security.py +0 -967
  178. sqlspec/statement/result.py +0 -435
  179. sqlspec/statement/sql.py +0 -1704
  180. sqlspec/statement/sql_compiler.py +0 -140
  181. sqlspec/utils/cached_property.py +0 -25
  182. sqlspec-0.13.1.dist-info/RECORD +0 -150
  183. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/WHEEL +0 -0
  184. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/LICENSE +0 -0
  185. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/NOTICE +0 -0
@@ -1,26 +0,0 @@
1
- from collections.abc import Sequence
2
- from dataclasses import dataclass
3
- from typing import Generic, TypeVar
4
-
5
- T = TypeVar("T")
6
-
7
- __all__ = ("OffsetPagination",)
8
-
9
-
10
- @dataclass
11
- class OffsetPagination(Generic[T]):
12
- """Container for data returned using limit/offset pagination."""
13
-
14
- __slots__ = ("items", "limit", "offset", "total")
15
-
16
- items: Sequence[T]
17
- """List of data being sent as part of the response."""
18
- limit: int
19
- """Maximal number of items to send."""
20
- offset: int
21
- """Offset from the beginning of the query.
22
-
23
- Identical to an index.
24
- """
25
- total: int
26
- """Total number of items."""
@@ -1,21 +0,0 @@
1
- """SQL utilities, validation, and parameter handling."""
2
-
3
- from sqlspec.statement import builder, filters, parameters, result, sql
4
- from sqlspec.statement.filters import StatementFilter
5
- from sqlspec.statement.result import ArrowResult, SQLResult, StatementResult
6
- from sqlspec.statement.sql import SQL, SQLConfig, Statement
7
-
8
- __all__ = (
9
- "SQL",
10
- "ArrowResult",
11
- "SQLConfig",
12
- "SQLResult",
13
- "Statement",
14
- "StatementFilter",
15
- "StatementResult",
16
- "builder",
17
- "filters",
18
- "parameters",
19
- "result",
20
- "sql",
21
- )
@@ -1,288 +0,0 @@
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, field
8
- from typing import TYPE_CHECKING, Any, Optional
9
-
10
- from sqlglot import exp
11
- from typing_extensions import Self
12
-
13
- 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
-
24
- if TYPE_CHECKING:
25
- from collections.abc import Mapping, Sequence
26
-
27
-
28
- __all__ = ("Insert",)
29
-
30
- ERR_MSG_TABLE_NOT_SET = "The target table must be set using .into() before adding values."
31
- ERR_MSG_VALUES_COLUMNS_MISMATCH = (
32
- "Number of values ({values_len}) does not match the number of specified columns ({columns_len})."
33
- )
34
- ERR_MSG_INTERNAL_EXPRESSION_TYPE = "Internal error: expression is not an Insert instance as expected."
35
- ERR_MSG_EXPRESSION_NOT_INITIALIZED = "Internal error: base expression not initialized."
36
-
37
-
38
- @dataclass(unsafe_hash=True)
39
- class Insert(QueryBuilder[RowT], ReturningClauseMixin, InsertValuesMixin, InsertFromSelectMixin, InsertIntoClauseMixin):
40
- """Builder for INSERT statements.
41
-
42
- This builder facilitates the construction of SQL INSERT queries
43
- 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
- """
91
-
92
- _table: "Optional[str]" = field(default=None, init=False)
93
- _columns: list[str] = field(default_factory=list, init=False)
94
- _values_added_count: int = field(default=0, init=False)
95
-
96
- def __init__(self, table: Optional[str] = None, **kwargs: Any) -> None:
97
- """Initialize INSERT with optional table.
98
-
99
- Args:
100
- table: Target table name
101
- **kwargs: Additional QueryBuilder arguments
102
- """
103
- super().__init__(**kwargs)
104
-
105
- # Initialize fields from dataclass
106
- self._table = None
107
- self._columns = []
108
- self._values_added_count = 0
109
-
110
- if table:
111
- self.into(table)
112
-
113
- def _create_base_expression(self) -> exp.Insert:
114
- """Create a base INSERT expression.
115
-
116
- This method is called by the base QueryBuilder during initialization.
117
-
118
- Returns:
119
- A new sqlglot Insert expression.
120
- """
121
- return exp.Insert()
122
-
123
- @property
124
- def _expected_result_type(self) -> "type[SQLResult[RowT]]":
125
- """Specifies the expected result type for an INSERT query.
126
-
127
- Returns:
128
- The type of result expected for INSERT operations.
129
- """
130
- return SQLResult[RowT]
131
-
132
- def _get_insert_expression(self) -> exp.Insert:
133
- """Safely gets and casts the internal expression to exp.Insert.
134
-
135
- Returns:
136
- The internal expression as exp.Insert.
137
-
138
- Raises:
139
- SQLBuilderError: If the expression is not initialized or is not an Insert.
140
- """
141
- if self._expression is None:
142
- raise SQLBuilderError(ERR_MSG_EXPRESSION_NOT_INITIALIZED)
143
- if not isinstance(self._expression, exp.Insert):
144
- raise SQLBuilderError(ERR_MSG_INTERNAL_EXPRESSION_TYPE)
145
- return self._expression
146
-
147
- def values(self, *values: Any) -> "Self":
148
- """Adds a row of values to the INSERT statement.
149
-
150
- This method can be called multiple times to insert multiple rows,
151
- resulting in a multi-row INSERT statement like `VALUES (...), (...)`.
152
-
153
- Args:
154
- *values: The values for the row to be inserted. The number of values
155
- must match the number of columns set by `columns()`, if `columns()` was called
156
- and specified any non-empty list of columns.
157
-
158
- Returns:
159
- The current builder instance for method chaining.
160
-
161
- Raises:
162
- SQLBuilderError: If `into()` has not been called to set the table,
163
- or if `columns()` was called with a non-empty list of columns
164
- and the number of values does not match the number of specified columns.
165
- """
166
- if not self._table:
167
- raise SQLBuilderError(ERR_MSG_TABLE_NOT_SET)
168
-
169
- insert_expr = self._get_insert_expression()
170
-
171
- if self._columns and len(values) != len(self._columns):
172
- msg = ERR_MSG_VALUES_COLUMNS_MISMATCH.format(values_len=len(values), columns_len=len(self._columns))
173
- raise SQLBuilderError(msg)
174
-
175
- param_names = [self._add_parameter(value) for value in values]
176
- value_placeholders = tuple(exp.var(name) for name in param_names)
177
-
178
- current_values_expression = insert_expr.args.get("expression")
179
-
180
- if self._values_added_count == 0:
181
- new_values_node = exp.Values(expressions=[exp.Tuple(expressions=list(value_placeholders))])
182
- insert_expr.set("expression", new_values_node)
183
- elif isinstance(current_values_expression, exp.Values):
184
- current_values_expression.expressions.append(exp.Tuple(expressions=list(value_placeholders)))
185
- 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
- new_values_node = exp.Values(expressions=[exp.Tuple(expressions=list(value_placeholders))])
189
- insert_expr.set("expression", new_values_node)
190
-
191
- self._values_added_count += 1
192
- return self
193
-
194
- def values_from_dict(self, data: "Mapping[str, Any]") -> "Self":
195
- """Adds a row of values from a dictionary.
196
-
197
- This is a convenience method that automatically sets columns based on
198
- the dictionary keys and values based on the dictionary values.
199
-
200
- Args:
201
- data: A mapping of column names to values.
202
-
203
- Returns:
204
- The current builder instance for method chaining.
205
-
206
- Raises:
207
- SQLBuilderError: If `into()` has not been called to set the table.
208
- """
209
- if not self._table:
210
- raise SQLBuilderError(ERR_MSG_TABLE_NOT_SET)
211
-
212
- if not self._columns:
213
- self.columns(*data.keys())
214
- elif set(self._columns) != set(data.keys()):
215
- # Verify that dictionary keys match existing columns
216
- msg = f"Dictionary keys {set(data.keys())} do not match existing columns {set(self._columns)}."
217
- raise SQLBuilderError(msg)
218
-
219
- return self.values(*[data[col] for col in self._columns])
220
-
221
- def values_from_dicts(self, data: "Sequence[Mapping[str, Any]]") -> "Self":
222
- """Adds multiple rows of values from a sequence of dictionaries.
223
-
224
- This is a convenience method for bulk inserts from structured data.
225
-
226
- Args:
227
- data: A sequence of mappings, each representing a row of data.
228
-
229
- Returns:
230
- The current builder instance for method chaining.
231
-
232
- Raises:
233
- SQLBuilderError: If `into()` has not been called to set the table,
234
- or if dictionaries have inconsistent keys.
235
- """
236
- if not data:
237
- return self
238
-
239
- first_dict = data[0]
240
- if not self._columns:
241
- self.columns(*first_dict.keys())
242
-
243
- expected_keys = set(self._columns)
244
- for i, row_dict in enumerate(data):
245
- if set(row_dict.keys()) != expected_keys:
246
- msg = (
247
- f"Dictionary at index {i} has keys {set(row_dict.keys())} "
248
- f"which do not match expected keys {expected_keys}."
249
- )
250
- raise SQLBuilderError(msg)
251
-
252
- for row_dict in data:
253
- self.values(*[row_dict[col] for col in self._columns])
254
-
255
- return self
256
-
257
- def on_conflict_do_nothing(self) -> "Self":
258
- """Adds an ON CONFLICT DO NOTHING clause (PostgreSQL syntax).
259
-
260
- This is used to ignore rows that would cause a conflict.
261
-
262
- Returns:
263
- The current builder instance for method chaining.
264
-
265
- Note:
266
- This is PostgreSQL-specific syntax. Different databases have different syntax.
267
- For a more general solution, you might need dialect-specific handling.
268
- """
269
- insert_expr = self._get_insert_expression()
270
- # Using sqlglot's OnConflict expression if available
271
- try:
272
- on_conflict = exp.OnConflict(this=None, expressions=[])
273
- insert_expr.set("on", on_conflict)
274
- except AttributeError:
275
- # Fallback for older sqlglot versions
276
- pass
277
- return self
278
-
279
- def on_duplicate_key_update(self, **set_values: Any) -> "Self":
280
- """Adds an ON DUPLICATE KEY UPDATE clause (MySQL syntax).
281
-
282
- Args:
283
- **set_values: Column-value pairs to update on duplicate key.
284
-
285
- Returns:
286
- The current builder instance for method chaining.
287
- """
288
- return self
@@ -1,95 +0,0 @@
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.statement.builder.base import QueryBuilder
12
- from sqlspec.statement.builder.mixins import (
13
- MergeIntoClauseMixin,
14
- MergeMatchedClauseMixin,
15
- MergeNotMatchedBySourceClauseMixin,
16
- MergeNotMatchedClauseMixin,
17
- MergeOnClauseMixin,
18
- MergeUsingClauseMixin,
19
- )
20
- from sqlspec.statement.result import SQLResult
21
- from sqlspec.typing import RowT
22
-
23
- __all__ = ("Merge",)
24
-
25
-
26
- @dataclass(unsafe_hash=True)
27
- class Merge(
28
- QueryBuilder[RowT],
29
- MergeUsingClauseMixin,
30
- MergeOnClauseMixin,
31
- MergeMatchedClauseMixin,
32
- MergeNotMatchedClauseMixin,
33
- MergeIntoClauseMixin,
34
- MergeNotMatchedBySourceClauseMixin,
35
- ):
36
- """Builder for MERGE statements.
37
-
38
- This builder provides a fluent interface for constructing SQL MERGE statements
39
- (also known as UPSERT in some databases) with automatic parameter binding and validation.
40
-
41
- Example:
42
- ```python
43
- # Basic MERGE statement
44
- merge_query = (
45
- Merge()
46
- .into("target_table")
47
- .using("source_table", "src")
48
- .on("target_table.id = src.id")
49
- .when_matched_then_update(
50
- {"name": "src.name", "updated_at": "NOW()"}
51
- )
52
- .when_not_matched_then_insert(
53
- columns=["id", "name", "created_at"],
54
- values=["src.id", "src.name", "NOW()"],
55
- )
56
- )
57
-
58
- # MERGE with subquery source
59
- source_query = (
60
- SelectBuilder()
61
- .select("id", "name", "email")
62
- .from_("temp_users")
63
- .where("status = 'pending'")
64
- )
65
-
66
- merge_query = (
67
- Merge()
68
- .into("users")
69
- .using(source_query, "src")
70
- .on("users.email = src.email")
71
- .when_matched_then_update({"name": "src.name"})
72
- .when_not_matched_then_insert(
73
- columns=["id", "name", "email"],
74
- values=["src.id", "src.name", "src.email"],
75
- )
76
- )
77
- ```
78
- """
79
-
80
- @property
81
- def _expected_result_type(self) -> "type[SQLResult[RowT]]":
82
- """Return the expected result type for this builder.
83
-
84
- Returns:
85
- The SQLResult type for MERGE statements.
86
- """
87
- return SQLResult[RowT]
88
-
89
- def _create_base_expression(self) -> "exp.Merge":
90
- """Create a base MERGE expression.
91
-
92
- Returns:
93
- A new sqlglot Merge expression with empty clauses.
94
- """
95
- return exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))
@@ -1,65 +0,0 @@
1
- """SQL statement builder mixins."""
2
-
3
- from sqlspec.statement.builder.mixins._aggregate_functions import AggregateFunctionsMixin
4
- from sqlspec.statement.builder.mixins._case_builder import CaseBuilderMixin
5
- from sqlspec.statement.builder.mixins._common_table_expr import CommonTableExpressionMixin
6
- from sqlspec.statement.builder.mixins._delete_from import DeleteFromClauseMixin
7
- from sqlspec.statement.builder.mixins._from import FromClauseMixin
8
- from sqlspec.statement.builder.mixins._group_by import GroupByClauseMixin
9
- from sqlspec.statement.builder.mixins._having import HavingClauseMixin
10
- from sqlspec.statement.builder.mixins._insert_from_select import InsertFromSelectMixin
11
- from sqlspec.statement.builder.mixins._insert_into import InsertIntoClauseMixin
12
- from sqlspec.statement.builder.mixins._insert_values import InsertValuesMixin
13
- from sqlspec.statement.builder.mixins._join import JoinClauseMixin
14
- from sqlspec.statement.builder.mixins._limit_offset import LimitOffsetClauseMixin
15
- from sqlspec.statement.builder.mixins._merge_clauses import (
16
- MergeIntoClauseMixin,
17
- MergeMatchedClauseMixin,
18
- MergeNotMatchedBySourceClauseMixin,
19
- MergeNotMatchedClauseMixin,
20
- MergeOnClauseMixin,
21
- MergeUsingClauseMixin,
22
- )
23
- from sqlspec.statement.builder.mixins._order_by import OrderByClauseMixin
24
- from sqlspec.statement.builder.mixins._pivot import PivotClauseMixin
25
- from sqlspec.statement.builder.mixins._returning import ReturningClauseMixin
26
- from sqlspec.statement.builder.mixins._select_columns import SelectColumnsMixin
27
- from sqlspec.statement.builder.mixins._set_ops import SetOperationMixin
28
- from sqlspec.statement.builder.mixins._unpivot import UnpivotClauseMixin
29
- from sqlspec.statement.builder.mixins._update_from import UpdateFromClauseMixin
30
- from sqlspec.statement.builder.mixins._update_set import UpdateSetClauseMixin
31
- from sqlspec.statement.builder.mixins._update_table import UpdateTableClauseMixin
32
- from sqlspec.statement.builder.mixins._where import WhereClauseMixin
33
- from sqlspec.statement.builder.mixins._window_functions import WindowFunctionsMixin
34
-
35
- __all__ = (
36
- "AggregateFunctionsMixin",
37
- "CaseBuilderMixin",
38
- "CommonTableExpressionMixin",
39
- "DeleteFromClauseMixin",
40
- "FromClauseMixin",
41
- "GroupByClauseMixin",
42
- "HavingClauseMixin",
43
- "InsertFromSelectMixin",
44
- "InsertIntoClauseMixin",
45
- "InsertValuesMixin",
46
- "JoinClauseMixin",
47
- "LimitOffsetClauseMixin",
48
- "MergeIntoClauseMixin",
49
- "MergeMatchedClauseMixin",
50
- "MergeNotMatchedBySourceClauseMixin",
51
- "MergeNotMatchedClauseMixin",
52
- "MergeOnClauseMixin",
53
- "MergeUsingClauseMixin",
54
- "OrderByClauseMixin",
55
- "PivotClauseMixin",
56
- "ReturningClauseMixin",
57
- "SelectColumnsMixin",
58
- "SetOperationMixin",
59
- "UnpivotClauseMixin",
60
- "UpdateFromClauseMixin",
61
- "UpdateSetClauseMixin",
62
- "UpdateTableClauseMixin",
63
- "WhereClauseMixin",
64
- "WindowFunctionsMixin",
65
- )