sqlspec 0.24.1__py3-none-any.whl → 0.26.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.
- sqlspec/_serialization.py +223 -21
- sqlspec/_sql.py +20 -62
- sqlspec/_typing.py +11 -0
- sqlspec/adapters/adbc/config.py +8 -1
- sqlspec/adapters/adbc/data_dictionary.py +290 -0
- sqlspec/adapters/adbc/driver.py +129 -20
- sqlspec/adapters/adbc/type_converter.py +159 -0
- sqlspec/adapters/aiosqlite/config.py +3 -0
- sqlspec/adapters/aiosqlite/data_dictionary.py +117 -0
- sqlspec/adapters/aiosqlite/driver.py +17 -3
- sqlspec/adapters/asyncmy/_types.py +1 -1
- sqlspec/adapters/asyncmy/config.py +11 -8
- sqlspec/adapters/asyncmy/data_dictionary.py +122 -0
- sqlspec/adapters/asyncmy/driver.py +31 -7
- sqlspec/adapters/asyncpg/config.py +3 -0
- sqlspec/adapters/asyncpg/data_dictionary.py +134 -0
- sqlspec/adapters/asyncpg/driver.py +19 -4
- sqlspec/adapters/bigquery/config.py +3 -0
- sqlspec/adapters/bigquery/data_dictionary.py +109 -0
- sqlspec/adapters/bigquery/driver.py +21 -3
- sqlspec/adapters/bigquery/type_converter.py +93 -0
- sqlspec/adapters/duckdb/_types.py +1 -1
- sqlspec/adapters/duckdb/config.py +2 -0
- sqlspec/adapters/duckdb/data_dictionary.py +124 -0
- sqlspec/adapters/duckdb/driver.py +32 -5
- sqlspec/adapters/duckdb/pool.py +1 -1
- sqlspec/adapters/duckdb/type_converter.py +103 -0
- sqlspec/adapters/oracledb/config.py +6 -0
- sqlspec/adapters/oracledb/data_dictionary.py +442 -0
- sqlspec/adapters/oracledb/driver.py +68 -9
- sqlspec/adapters/oracledb/migrations.py +51 -67
- sqlspec/adapters/oracledb/type_converter.py +132 -0
- sqlspec/adapters/psqlpy/config.py +3 -0
- sqlspec/adapters/psqlpy/data_dictionary.py +133 -0
- sqlspec/adapters/psqlpy/driver.py +23 -179
- sqlspec/adapters/psqlpy/type_converter.py +73 -0
- sqlspec/adapters/psycopg/config.py +8 -4
- sqlspec/adapters/psycopg/data_dictionary.py +257 -0
- sqlspec/adapters/psycopg/driver.py +40 -5
- sqlspec/adapters/sqlite/config.py +3 -0
- sqlspec/adapters/sqlite/data_dictionary.py +117 -0
- sqlspec/adapters/sqlite/driver.py +18 -3
- sqlspec/adapters/sqlite/pool.py +13 -4
- sqlspec/base.py +3 -4
- sqlspec/builder/_base.py +130 -48
- sqlspec/builder/_column.py +66 -24
- sqlspec/builder/_ddl.py +91 -41
- sqlspec/builder/_insert.py +40 -58
- sqlspec/builder/_parsing_utils.py +127 -12
- sqlspec/builder/_select.py +147 -2
- sqlspec/builder/_update.py +1 -1
- sqlspec/builder/mixins/_cte_and_set_ops.py +31 -23
- sqlspec/builder/mixins/_delete_operations.py +12 -7
- sqlspec/builder/mixins/_insert_operations.py +50 -36
- sqlspec/builder/mixins/_join_operations.py +15 -30
- sqlspec/builder/mixins/_merge_operations.py +210 -78
- sqlspec/builder/mixins/_order_limit_operations.py +4 -10
- sqlspec/builder/mixins/_pivot_operations.py +1 -0
- sqlspec/builder/mixins/_select_operations.py +44 -22
- sqlspec/builder/mixins/_update_operations.py +30 -37
- sqlspec/builder/mixins/_where_clause.py +52 -70
- sqlspec/cli.py +246 -140
- sqlspec/config.py +33 -19
- sqlspec/core/__init__.py +3 -2
- sqlspec/core/cache.py +298 -352
- sqlspec/core/compiler.py +61 -4
- sqlspec/core/filters.py +246 -213
- sqlspec/core/hashing.py +9 -11
- sqlspec/core/parameters.py +27 -10
- sqlspec/core/statement.py +72 -12
- sqlspec/core/type_conversion.py +234 -0
- sqlspec/driver/__init__.py +6 -3
- sqlspec/driver/_async.py +108 -5
- sqlspec/driver/_common.py +186 -17
- sqlspec/driver/_sync.py +108 -5
- sqlspec/driver/mixins/_result_tools.py +60 -7
- sqlspec/exceptions.py +5 -0
- sqlspec/loader.py +8 -9
- sqlspec/migrations/__init__.py +4 -3
- sqlspec/migrations/base.py +153 -14
- sqlspec/migrations/commands.py +34 -96
- sqlspec/migrations/context.py +145 -0
- sqlspec/migrations/loaders.py +25 -8
- sqlspec/migrations/runner.py +352 -82
- sqlspec/storage/backends/fsspec.py +1 -0
- sqlspec/typing.py +4 -0
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/serializers.py +50 -2
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/METADATA +1 -1
- sqlspec-0.26.0.dist-info/RECORD +157 -0
- sqlspec-0.24.1.dist-info/RECORD +0 -139
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.24.1.dist-info → sqlspec-0.26.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# pyright: reportPrivateUsage=false
|
|
1
2
|
"""UPDATE operation mixins.
|
|
2
3
|
|
|
3
4
|
Provides mixins for UPDATE statement functionality including
|
|
@@ -5,12 +6,13 @@ table specification, SET clauses, and FROM clauses.
|
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
8
|
from collections.abc import Mapping
|
|
8
|
-
from typing import Any, Optional, Union
|
|
9
|
+
from typing import Any, Optional, Union
|
|
9
10
|
|
|
10
11
|
from mypy_extensions import trait
|
|
11
12
|
from sqlglot import exp
|
|
12
13
|
from typing_extensions import Self
|
|
13
14
|
|
|
15
|
+
from sqlspec.builder._parsing_utils import extract_sql_object_expression
|
|
14
16
|
from sqlspec.exceptions import SQLBuilderError
|
|
15
17
|
from sqlspec.utils.type_guards import has_query_builder_parameters
|
|
16
18
|
|
|
@@ -25,8 +27,8 @@ class UpdateTableClauseMixin:
|
|
|
25
27
|
|
|
26
28
|
__slots__ = ()
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
31
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
30
32
|
|
|
31
33
|
def table(self, table_name: str, alias: Optional[str] = None) -> Self:
|
|
32
34
|
"""Set the table to update.
|
|
@@ -38,10 +40,14 @@ class UpdateTableClauseMixin:
|
|
|
38
40
|
Returns:
|
|
39
41
|
The current builder instance for method chaining.
|
|
40
42
|
"""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
current_expr = self.get_expression()
|
|
44
|
+
if current_expr is None or not isinstance(current_expr, exp.Update):
|
|
45
|
+
self.set_expression(exp.Update(this=None, expressions=[], joins=[]))
|
|
46
|
+
current_expr = self.get_expression()
|
|
47
|
+
|
|
48
|
+
assert current_expr is not None
|
|
43
49
|
table_expr: exp.Expression = exp.to_table(table_name, alias=alias)
|
|
44
|
-
|
|
50
|
+
current_expr.set("this", table_expr)
|
|
45
51
|
setattr(self, "_table", table_name)
|
|
46
52
|
return self
|
|
47
53
|
|
|
@@ -52,8 +58,8 @@ class UpdateSetClauseMixin:
|
|
|
52
58
|
|
|
53
59
|
__slots__ = ()
|
|
54
60
|
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
62
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
57
63
|
|
|
58
64
|
def add_parameter(self, value: Any, name: Optional[str] = None) -> tuple[Any, str]:
|
|
59
65
|
"""Add parameter - provided by QueryBuilder."""
|
|
@@ -86,24 +92,7 @@ class UpdateSetClauseMixin:
|
|
|
86
92
|
self.add_parameter(p_value, name=p_name)
|
|
87
93
|
return value_expr
|
|
88
94
|
if hasattr(val, "expression") and hasattr(val, "sql"):
|
|
89
|
-
|
|
90
|
-
expression = getattr(val, "expression", None)
|
|
91
|
-
if expression is not None and isinstance(expression, exp.Expression):
|
|
92
|
-
# Merge parameters from SQL object into builder
|
|
93
|
-
if hasattr(val, "parameters"):
|
|
94
|
-
sql_parameters = getattr(val, "parameters", {})
|
|
95
|
-
for param_name, param_value in sql_parameters.items():
|
|
96
|
-
self.add_parameter(param_value, name=param_name)
|
|
97
|
-
return cast("exp.Expression", expression)
|
|
98
|
-
# If expression is None, fall back to parsing the raw SQL
|
|
99
|
-
sql_text = getattr(val, "sql", "")
|
|
100
|
-
# Merge parameters even when parsing raw SQL
|
|
101
|
-
if hasattr(val, "parameters"):
|
|
102
|
-
sql_parameters = getattr(val, "parameters", {})
|
|
103
|
-
for param_name, param_value in sql_parameters.items():
|
|
104
|
-
self.add_parameter(param_value, name=param_name)
|
|
105
|
-
parsed_expr = exp.maybe_parse(sql_text)
|
|
106
|
-
return parsed_expr if parsed_expr is not None else exp.convert(str(sql_text))
|
|
95
|
+
return extract_sql_object_expression(val, builder=self)
|
|
107
96
|
column_name = col if isinstance(col, str) else str(col)
|
|
108
97
|
if "." in column_name:
|
|
109
98
|
column_name = column_name.split(".")[-1]
|
|
@@ -130,9 +119,12 @@ class UpdateSetClauseMixin:
|
|
|
130
119
|
Returns:
|
|
131
120
|
The current builder instance for method chaining.
|
|
132
121
|
"""
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
122
|
+
current_expr = self.get_expression()
|
|
123
|
+
if current_expr is None:
|
|
124
|
+
self.set_expression(exp.Update())
|
|
125
|
+
current_expr = self.get_expression()
|
|
126
|
+
|
|
127
|
+
if not isinstance(current_expr, exp.Update):
|
|
136
128
|
msg = "Cannot add SET clause to non-UPDATE expression."
|
|
137
129
|
raise SQLBuilderError(msg)
|
|
138
130
|
assignments = []
|
|
@@ -149,8 +141,8 @@ class UpdateSetClauseMixin:
|
|
|
149
141
|
else:
|
|
150
142
|
msg = "Invalid arguments for set(): use (column, value), mapping, or kwargs."
|
|
151
143
|
raise SQLBuilderError(msg)
|
|
152
|
-
existing =
|
|
153
|
-
|
|
144
|
+
existing = current_expr.args.get("expressions", [])
|
|
145
|
+
current_expr.set("expressions", existing + assignments)
|
|
154
146
|
return self
|
|
155
147
|
|
|
156
148
|
|
|
@@ -160,8 +152,8 @@ class UpdateFromClauseMixin:
|
|
|
160
152
|
|
|
161
153
|
__slots__ = ()
|
|
162
154
|
|
|
163
|
-
|
|
164
|
-
|
|
155
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
156
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
165
157
|
|
|
166
158
|
def from_(self, table: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
|
|
167
159
|
"""Add a FROM clause to the UPDATE statement.
|
|
@@ -176,7 +168,8 @@ class UpdateFromClauseMixin:
|
|
|
176
168
|
Raises:
|
|
177
169
|
SQLBuilderError: If the current expression is not an UPDATE statement.
|
|
178
170
|
"""
|
|
179
|
-
|
|
171
|
+
current_expr = self.get_expression()
|
|
172
|
+
if current_expr is None or not isinstance(current_expr, exp.Update):
|
|
180
173
|
msg = "Cannot add FROM clause to non-UPDATE expression. Set the main table first."
|
|
181
174
|
raise SQLBuilderError(msg)
|
|
182
175
|
table_expr: exp.Expression
|
|
@@ -194,9 +187,9 @@ class UpdateFromClauseMixin:
|
|
|
194
187
|
else:
|
|
195
188
|
msg = f"Unsupported table type for FROM clause: {type(table)}"
|
|
196
189
|
raise SQLBuilderError(msg)
|
|
197
|
-
if
|
|
198
|
-
|
|
199
|
-
from_clause =
|
|
190
|
+
if current_expr.args.get("from") is None:
|
|
191
|
+
current_expr.set("from", exp.From(expressions=[]))
|
|
192
|
+
from_clause = current_expr.args["from"]
|
|
200
193
|
if hasattr(from_clause, "append"):
|
|
201
194
|
from_clause.append("expressions", table_expr)
|
|
202
195
|
else:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# ruff: noqa: PLR2004
|
|
2
|
+
# pyright: reportPrivateUsage=false, reportPrivateImportUsage=false
|
|
2
3
|
"""WHERE and HAVING clause mixins.
|
|
3
4
|
|
|
4
5
|
Provides mixins for WHERE and HAVING clause functionality with
|
|
@@ -14,7 +15,9 @@ from mypy_extensions import trait
|
|
|
14
15
|
from sqlglot import exp
|
|
15
16
|
from typing_extensions import Self
|
|
16
17
|
|
|
17
|
-
from sqlspec.builder._parsing_utils import parse_column_expression, parse_condition_expression
|
|
18
|
+
from sqlspec.builder._parsing_utils import extract_column_name, parse_column_expression, parse_condition_expression
|
|
19
|
+
from sqlspec.core.parameters import ParameterStyle, ParameterValidator
|
|
20
|
+
from sqlspec.core.statement import SQL
|
|
18
21
|
from sqlspec.exceptions import SQLBuilderError
|
|
19
22
|
from sqlspec.utils.type_guards import (
|
|
20
23
|
has_expression_and_parameters,
|
|
@@ -24,31 +27,6 @@ from sqlspec.utils.type_guards import (
|
|
|
24
27
|
is_iterable_parameters,
|
|
25
28
|
)
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
def _extract_column_name(column: Union[str, exp.Column]) -> str:
|
|
29
|
-
"""Extract column name from column expression for parameter naming.
|
|
30
|
-
|
|
31
|
-
Args:
|
|
32
|
-
column: Column expression (string or SQLGlot Column)
|
|
33
|
-
|
|
34
|
-
Returns:
|
|
35
|
-
Column name as string for use as parameter name
|
|
36
|
-
"""
|
|
37
|
-
if isinstance(column, str):
|
|
38
|
-
# Handle simple column names and table.column references
|
|
39
|
-
if "." in column:
|
|
40
|
-
return column.split(".")[-1] # Return just the column part
|
|
41
|
-
return column
|
|
42
|
-
if isinstance(column, exp.Column):
|
|
43
|
-
# Extract the column name from SQLGlot Column expression
|
|
44
|
-
try:
|
|
45
|
-
return str(column.this.this)
|
|
46
|
-
except AttributeError:
|
|
47
|
-
return str(column.this) if column.this else "column"
|
|
48
|
-
# Fallback for any unexpected types (defensive programming)
|
|
49
|
-
return "column"
|
|
50
|
-
|
|
51
|
-
|
|
52
30
|
if TYPE_CHECKING:
|
|
53
31
|
from sqlspec.builder._column import ColumnExpression
|
|
54
32
|
from sqlspec.protocols import SQLBuilderProtocol
|
|
@@ -62,8 +40,9 @@ class WhereClauseMixin:
|
|
|
62
40
|
|
|
63
41
|
__slots__ = ()
|
|
64
42
|
|
|
65
|
-
# Type
|
|
66
|
-
|
|
43
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
44
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
45
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
67
46
|
|
|
68
47
|
def _create_parameterized_condition(
|
|
69
48
|
self,
|
|
@@ -82,7 +61,7 @@ class WhereClauseMixin:
|
|
|
82
61
|
The created condition expression
|
|
83
62
|
"""
|
|
84
63
|
builder = cast("SQLBuilderProtocol", self)
|
|
85
|
-
column_name =
|
|
64
|
+
column_name = extract_column_name(column)
|
|
86
65
|
param_name = builder._generate_unique_parameter_name(column_name)
|
|
87
66
|
_, param_name = builder.add_parameter(value, name=param_name)
|
|
88
67
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
@@ -118,18 +97,20 @@ class WhereClauseMixin:
|
|
|
118
97
|
Self with OR condition applied
|
|
119
98
|
"""
|
|
120
99
|
# Create a temporary clone to capture the condition
|
|
121
|
-
original_expr = self.
|
|
100
|
+
original_expr = self.get_expression()
|
|
122
101
|
|
|
123
102
|
# Apply the where method to get the condition
|
|
124
103
|
where_method(*args, **kwargs)
|
|
125
104
|
|
|
126
105
|
# Get the last condition added by extracting it from the modified expression
|
|
127
|
-
|
|
128
|
-
|
|
106
|
+
current_expr = self.get_expression()
|
|
107
|
+
if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)) and original_expr != current_expr:
|
|
108
|
+
last_where = current_expr.find(exp.Where)
|
|
129
109
|
if last_where and last_where.this:
|
|
130
110
|
condition = last_where.this
|
|
131
111
|
# Restore original expression
|
|
132
|
-
|
|
112
|
+
if original_expr is not None:
|
|
113
|
+
self.set_expression(original_expr)
|
|
133
114
|
# Apply as OR
|
|
134
115
|
return self.or_where(condition)
|
|
135
116
|
|
|
@@ -236,7 +217,7 @@ class WhereClauseMixin:
|
|
|
236
217
|
column_name_raw, operator, value = condition
|
|
237
218
|
operator = str(operator).upper()
|
|
238
219
|
column_exp = parse_column_expression(column_name_raw)
|
|
239
|
-
column_name =
|
|
220
|
+
column_name = extract_column_name(column_name_raw)
|
|
240
221
|
|
|
241
222
|
# Simple operators that use direct parameterization
|
|
242
223
|
simple_operators = {
|
|
@@ -299,16 +280,17 @@ class WhereClauseMixin:
|
|
|
299
280
|
Returns:
|
|
300
281
|
The current builder instance for method chaining.
|
|
301
282
|
"""
|
|
302
|
-
|
|
283
|
+
current_expr = self.get_expression()
|
|
284
|
+
if self.__class__.__name__ == "Update" and not isinstance(current_expr, exp.Update):
|
|
303
285
|
msg = "Cannot add WHERE clause to non-UPDATE expression"
|
|
304
286
|
raise SQLBuilderError(msg)
|
|
305
287
|
|
|
306
288
|
builder = cast("SQLBuilderProtocol", self)
|
|
307
|
-
if
|
|
289
|
+
if current_expr is None:
|
|
308
290
|
msg = "Cannot add WHERE clause: expression is not initialized."
|
|
309
291
|
raise SQLBuilderError(msg)
|
|
310
292
|
|
|
311
|
-
if isinstance(
|
|
293
|
+
if isinstance(current_expr, exp.Delete) and not current_expr.args.get("this"):
|
|
312
294
|
msg = "WHERE clause requires a table to be set. Use from() to set the table first."
|
|
313
295
|
raise SQLBuilderError(msg)
|
|
314
296
|
|
|
@@ -319,15 +301,11 @@ class WhereClauseMixin:
|
|
|
319
301
|
raise SQLBuilderError(msg)
|
|
320
302
|
|
|
321
303
|
# Check if condition contains parameter placeholders
|
|
322
|
-
from sqlspec.core.parameters import ParameterStyle, ParameterValidator
|
|
323
|
-
|
|
324
304
|
validator = ParameterValidator()
|
|
325
305
|
param_info = validator.extract_parameters(condition)
|
|
326
306
|
|
|
327
307
|
if param_info:
|
|
328
308
|
# String condition with placeholders - create SQL object with parameters
|
|
329
|
-
from sqlspec import sql as sql_factory
|
|
330
|
-
|
|
331
309
|
# Create parameter mapping based on the detected parameter info
|
|
332
310
|
param_dict = dict(kwargs) # Start with named parameters
|
|
333
311
|
|
|
@@ -347,7 +325,7 @@ class WhereClauseMixin:
|
|
|
347
325
|
param_dict[f"param_{i}"] = value
|
|
348
326
|
|
|
349
327
|
# Create SQL object with parameters that will be processed correctly
|
|
350
|
-
condition =
|
|
328
|
+
condition = SQL(condition, param_dict)
|
|
351
329
|
# Fall through to existing SQL object handling logic
|
|
352
330
|
|
|
353
331
|
elif len(values) == 1 and not kwargs:
|
|
@@ -357,10 +335,11 @@ class WhereClauseMixin:
|
|
|
357
335
|
else:
|
|
358
336
|
where_expr = self._process_tuple_condition((condition, values[0]))
|
|
359
337
|
# Process this condition and skip the rest
|
|
360
|
-
if isinstance(
|
|
361
|
-
|
|
338
|
+
if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)):
|
|
339
|
+
updated_expr = current_expr.where(where_expr, copy=False)
|
|
340
|
+
self.set_expression(updated_expr)
|
|
362
341
|
else:
|
|
363
|
-
msg = f"WHERE clause not supported for {type(
|
|
342
|
+
msg = f"WHERE clause not supported for {type(current_expr).__name__}"
|
|
364
343
|
raise SQLBuilderError(msg)
|
|
365
344
|
return self
|
|
366
345
|
else:
|
|
@@ -400,10 +379,11 @@ class WhereClauseMixin:
|
|
|
400
379
|
msg = f"Unsupported condition type: {type(condition).__name__}"
|
|
401
380
|
raise SQLBuilderError(msg)
|
|
402
381
|
|
|
403
|
-
if isinstance(
|
|
404
|
-
|
|
382
|
+
if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)):
|
|
383
|
+
updated_expr = current_expr.where(where_expr, copy=False)
|
|
384
|
+
self.set_expression(updated_expr)
|
|
405
385
|
else:
|
|
406
|
-
msg = f"WHERE clause not supported for {type(
|
|
386
|
+
msg = f"WHERE clause not supported for {type(current_expr).__name__}"
|
|
407
387
|
raise SQLBuilderError(msg)
|
|
408
388
|
return self
|
|
409
389
|
|
|
@@ -448,7 +428,7 @@ class WhereClauseMixin:
|
|
|
448
428
|
def where_between(self, column: Union[str, exp.Column], low: Any, high: Any) -> Self:
|
|
449
429
|
"""Add WHERE column BETWEEN low AND high clause."""
|
|
450
430
|
builder = cast("SQLBuilderProtocol", self)
|
|
451
|
-
column_name =
|
|
431
|
+
column_name = extract_column_name(column)
|
|
452
432
|
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
453
433
|
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
454
434
|
_, low_param = builder.add_parameter(low, name=low_param)
|
|
@@ -460,7 +440,7 @@ class WhereClauseMixin:
|
|
|
460
440
|
def where_like(self, column: Union[str, exp.Column], pattern: str, escape: Optional[str] = None) -> Self:
|
|
461
441
|
"""Add WHERE column LIKE pattern clause."""
|
|
462
442
|
builder = cast("SQLBuilderProtocol", self)
|
|
463
|
-
column_name =
|
|
443
|
+
column_name = extract_column_name(column)
|
|
464
444
|
param_name = builder._generate_unique_parameter_name(column_name)
|
|
465
445
|
_, param_name = builder.add_parameter(pattern, name=param_name)
|
|
466
446
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
@@ -519,7 +499,7 @@ class WhereClauseMixin:
|
|
|
519
499
|
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
520
500
|
msg = "Unsupported type for 'values' in WHERE IN"
|
|
521
501
|
raise SQLBuilderError(msg)
|
|
522
|
-
column_name =
|
|
502
|
+
column_name = extract_column_name(column)
|
|
523
503
|
parameters = []
|
|
524
504
|
for i, v in enumerate(values):
|
|
525
505
|
if len(values) == 1:
|
|
@@ -548,7 +528,7 @@ class WhereClauseMixin:
|
|
|
548
528
|
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
549
529
|
msg = "Values for where_not_in must be a non-string iterable or subquery."
|
|
550
530
|
raise SQLBuilderError(msg)
|
|
551
|
-
column_name =
|
|
531
|
+
column_name = extract_column_name(column)
|
|
552
532
|
parameters = []
|
|
553
533
|
for i, v in enumerate(values):
|
|
554
534
|
if len(values) == 1:
|
|
@@ -638,7 +618,7 @@ class WhereClauseMixin:
|
|
|
638
618
|
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
639
619
|
msg = "Unsupported type for 'values' in WHERE ANY"
|
|
640
620
|
raise SQLBuilderError(msg)
|
|
641
|
-
column_name =
|
|
621
|
+
column_name = extract_column_name(column)
|
|
642
622
|
parameters = []
|
|
643
623
|
for i, v in enumerate(values):
|
|
644
624
|
if len(values) == 1:
|
|
@@ -678,7 +658,7 @@ class WhereClauseMixin:
|
|
|
678
658
|
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
679
659
|
msg = "Unsupported type for 'values' in WHERE NOT ANY"
|
|
680
660
|
raise SQLBuilderError(msg)
|
|
681
|
-
column_name =
|
|
661
|
+
column_name = extract_column_name(column)
|
|
682
662
|
parameters = []
|
|
683
663
|
for i, v in enumerate(values):
|
|
684
664
|
if len(values) == 1:
|
|
@@ -837,15 +817,11 @@ class WhereClauseMixin:
|
|
|
837
817
|
raise SQLBuilderError(msg)
|
|
838
818
|
|
|
839
819
|
# Check if condition contains parameter placeholders
|
|
840
|
-
from sqlspec.core.parameters import ParameterStyle, ParameterValidator
|
|
841
|
-
|
|
842
820
|
validator = ParameterValidator()
|
|
843
821
|
param_info = validator.extract_parameters(condition)
|
|
844
822
|
|
|
845
823
|
if param_info:
|
|
846
824
|
# String condition with placeholders - create SQL object with parameters
|
|
847
|
-
from sqlspec import sql as sql_factory
|
|
848
|
-
|
|
849
825
|
# Create parameter mapping based on the detected parameter info
|
|
850
826
|
param_dict = dict(kwargs) # Start with named parameters
|
|
851
827
|
|
|
@@ -865,7 +841,7 @@ class WhereClauseMixin:
|
|
|
865
841
|
param_dict[f"param_{i}"] = value
|
|
866
842
|
|
|
867
843
|
# Create SQL object with parameters that will be processed correctly
|
|
868
|
-
condition =
|
|
844
|
+
condition = SQL(condition, param_dict)
|
|
869
845
|
# Fall through to existing SQL object handling logic
|
|
870
846
|
|
|
871
847
|
elif len(values) == 1 and not kwargs:
|
|
@@ -975,7 +951,7 @@ class WhereClauseMixin:
|
|
|
975
951
|
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
976
952
|
msg = "Unsupported type for 'values' in OR WHERE IN"
|
|
977
953
|
raise SQLBuilderError(msg)
|
|
978
|
-
column_name =
|
|
954
|
+
column_name = extract_column_name(column)
|
|
979
955
|
parameters = []
|
|
980
956
|
for i, v in enumerate(values):
|
|
981
957
|
if len(values) == 1:
|
|
@@ -990,7 +966,7 @@ class WhereClauseMixin:
|
|
|
990
966
|
def or_where_like(self, column: Union[str, exp.Column], pattern: str, escape: Optional[str] = None) -> Self:
|
|
991
967
|
"""Add OR column LIKE pattern clause."""
|
|
992
968
|
builder = cast("SQLBuilderProtocol", self)
|
|
993
|
-
column_name =
|
|
969
|
+
column_name = extract_column_name(column)
|
|
994
970
|
param_name = builder._generate_unique_parameter_name(column_name)
|
|
995
971
|
_, param_name = builder.add_parameter(pattern, name=param_name)
|
|
996
972
|
col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
|
|
@@ -1044,7 +1020,7 @@ class WhereClauseMixin:
|
|
|
1044
1020
|
def or_where_between(self, column: Union[str, exp.Column], low: Any, high: Any) -> Self:
|
|
1045
1021
|
"""Add OR column BETWEEN low AND high clause."""
|
|
1046
1022
|
builder = cast("SQLBuilderProtocol", self)
|
|
1047
|
-
column_name =
|
|
1023
|
+
column_name = extract_column_name(column)
|
|
1048
1024
|
low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
|
|
1049
1025
|
high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
|
|
1050
1026
|
_, low_param = builder.add_parameter(low, name=low_param)
|
|
@@ -1092,7 +1068,7 @@ class WhereClauseMixin:
|
|
|
1092
1068
|
if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
|
|
1093
1069
|
msg = "Values for or_where_not_in must be a non-string iterable or subquery."
|
|
1094
1070
|
raise SQLBuilderError(msg)
|
|
1095
|
-
column_name =
|
|
1071
|
+
column_name = extract_column_name(column)
|
|
1096
1072
|
parameters = []
|
|
1097
1073
|
for i, v in enumerate(values):
|
|
1098
1074
|
if len(values) == 1:
|
|
@@ -1216,7 +1192,7 @@ class WhereClauseMixin:
|
|
|
1216
1192
|
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
1217
1193
|
msg = "Unsupported type for 'values' in OR WHERE ANY"
|
|
1218
1194
|
raise SQLBuilderError(msg)
|
|
1219
|
-
column_name =
|
|
1195
|
+
column_name = extract_column_name(column)
|
|
1220
1196
|
parameters = []
|
|
1221
1197
|
for i, v in enumerate(values):
|
|
1222
1198
|
if len(values) == 1:
|
|
@@ -1272,7 +1248,7 @@ class WhereClauseMixin:
|
|
|
1272
1248
|
if not is_iterable_parameters(values) or isinstance(values, bytes):
|
|
1273
1249
|
msg = "Unsupported type for 'values' in OR WHERE NOT ANY"
|
|
1274
1250
|
raise SQLBuilderError(msg)
|
|
1275
|
-
column_name =
|
|
1251
|
+
column_name = extract_column_name(column)
|
|
1276
1252
|
parameters = []
|
|
1277
1253
|
for i, v in enumerate(values):
|
|
1278
1254
|
if len(values) == 1:
|
|
@@ -1292,7 +1268,9 @@ class HavingClauseMixin:
|
|
|
1292
1268
|
|
|
1293
1269
|
__slots__ = ()
|
|
1294
1270
|
|
|
1295
|
-
|
|
1271
|
+
# Type annotations for PyRight - these will be provided by the base class
|
|
1272
|
+
def get_expression(self) -> Optional[exp.Expression]: ...
|
|
1273
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
1296
1274
|
|
|
1297
1275
|
def having(self, condition: Union[str, exp.Expression]) -> Self:
|
|
1298
1276
|
"""Add HAVING clause.
|
|
@@ -1306,11 +1284,15 @@ class HavingClauseMixin:
|
|
|
1306
1284
|
Returns:
|
|
1307
1285
|
The current builder instance for method chaining.
|
|
1308
1286
|
"""
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1287
|
+
current_expr = self.get_expression()
|
|
1288
|
+
if current_expr is None:
|
|
1289
|
+
self.set_expression(exp.Select())
|
|
1290
|
+
current_expr = self.get_expression()
|
|
1291
|
+
|
|
1292
|
+
if not isinstance(current_expr, exp.Select):
|
|
1312
1293
|
msg = "Cannot add HAVING to a non-SELECT expression."
|
|
1313
1294
|
raise SQLBuilderError(msg)
|
|
1314
1295
|
having_expr = exp.condition(condition) if isinstance(condition, str) else condition
|
|
1315
|
-
|
|
1296
|
+
updated_expr = current_expr.having(having_expr, copy=False)
|
|
1297
|
+
self.set_expression(updated_expr)
|
|
1316
1298
|
return self
|