sqlspec 0.14.1__py3-none-any.whl → 0.16.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sqlspec might be problematic. Click here for more details.
- sqlspec/__init__.py +50 -25
- sqlspec/__main__.py +1 -1
- sqlspec/__metadata__.py +1 -3
- sqlspec/_serialization.py +1 -2
- sqlspec/_sql.py +480 -121
- sqlspec/_typing.py +278 -142
- sqlspec/adapters/adbc/__init__.py +4 -3
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +115 -260
- sqlspec/adapters/adbc/driver.py +462 -367
- sqlspec/adapters/aiosqlite/__init__.py +18 -3
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +199 -129
- sqlspec/adapters/aiosqlite/driver.py +230 -269
- sqlspec/adapters/asyncmy/__init__.py +18 -3
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +80 -168
- sqlspec/adapters/asyncmy/driver.py +260 -225
- sqlspec/adapters/asyncpg/__init__.py +19 -4
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +82 -181
- sqlspec/adapters/asyncpg/driver.py +285 -383
- sqlspec/adapters/bigquery/__init__.py +17 -3
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +191 -258
- sqlspec/adapters/bigquery/driver.py +474 -646
- sqlspec/adapters/duckdb/__init__.py +14 -3
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +415 -351
- sqlspec/adapters/duckdb/driver.py +343 -413
- sqlspec/adapters/oracledb/__init__.py +19 -5
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +123 -379
- sqlspec/adapters/oracledb/driver.py +507 -560
- sqlspec/adapters/psqlpy/__init__.py +13 -3
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +93 -254
- sqlspec/adapters/psqlpy/driver.py +505 -234
- sqlspec/adapters/psycopg/__init__.py +19 -5
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +143 -403
- sqlspec/adapters/psycopg/driver.py +706 -872
- sqlspec/adapters/sqlite/__init__.py +14 -3
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +202 -118
- sqlspec/adapters/sqlite/driver.py +264 -303
- sqlspec/base.py +105 -9
- sqlspec/{statement/builder → builder}/__init__.py +12 -14
- sqlspec/{statement/builder → builder}/_base.py +120 -55
- sqlspec/{statement/builder → builder}/_column.py +17 -6
- sqlspec/{statement/builder → builder}/_ddl.py +46 -79
- sqlspec/{statement/builder → builder}/_ddl_utils.py +5 -10
- sqlspec/{statement/builder → builder}/_delete.py +6 -25
- sqlspec/{statement/builder → builder}/_insert.py +18 -65
- sqlspec/builder/_merge.py +56 -0
- sqlspec/{statement/builder → builder}/_parsing_utils.py +8 -11
- sqlspec/{statement/builder → builder}/_select.py +11 -56
- sqlspec/{statement/builder → builder}/_update.py +12 -18
- sqlspec/{statement/builder → builder}/mixins/__init__.py +10 -14
- sqlspec/{statement/builder → builder}/mixins/_cte_and_set_ops.py +48 -59
- sqlspec/{statement/builder → builder}/mixins/_insert_operations.py +34 -18
- sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
- sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +19 -9
- sqlspec/{statement/builder → builder}/mixins/_order_limit_operations.py +3 -3
- sqlspec/{statement/builder → builder}/mixins/_pivot_operations.py +4 -8
- sqlspec/{statement/builder → builder}/mixins/_select_operations.py +25 -38
- sqlspec/{statement/builder → builder}/mixins/_update_operations.py +15 -16
- sqlspec/{statement/builder → builder}/mixins/_where_clause.py +210 -137
- sqlspec/cli.py +4 -5
- sqlspec/config.py +180 -133
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.py +873 -0
- sqlspec/core/compiler.py +396 -0
- sqlspec/core/filters.py +830 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.py +1209 -0
- sqlspec/core/result.py +664 -0
- sqlspec/{statement → core}/splitter.py +321 -191
- sqlspec/core/statement.py +666 -0
- sqlspec/driver/__init__.py +7 -10
- sqlspec/driver/_async.py +387 -176
- sqlspec/driver/_common.py +527 -289
- sqlspec/driver/_sync.py +390 -172
- sqlspec/driver/mixins/__init__.py +2 -19
- sqlspec/driver/mixins/_result_tools.py +164 -0
- sqlspec/driver/mixins/_sql_translator.py +6 -3
- sqlspec/exceptions.py +5 -252
- sqlspec/extensions/aiosql/adapter.py +93 -96
- sqlspec/extensions/litestar/cli.py +1 -1
- sqlspec/extensions/litestar/config.py +0 -1
- sqlspec/extensions/litestar/handlers.py +15 -26
- sqlspec/extensions/litestar/plugin.py +18 -16
- sqlspec/extensions/litestar/providers.py +17 -52
- sqlspec/loader.py +424 -105
- sqlspec/migrations/__init__.py +12 -0
- sqlspec/migrations/base.py +92 -68
- sqlspec/migrations/commands.py +24 -106
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +49 -51
- sqlspec/migrations/tracker.py +31 -44
- sqlspec/migrations/utils.py +64 -24
- sqlspec/protocols.py +7 -183
- sqlspec/storage/__init__.py +1 -1
- sqlspec/storage/backends/base.py +37 -40
- sqlspec/storage/backends/fsspec.py +136 -112
- sqlspec/storage/backends/obstore.py +138 -160
- sqlspec/storage/capabilities.py +5 -4
- sqlspec/storage/registry.py +57 -106
- sqlspec/typing.py +136 -115
- sqlspec/utils/__init__.py +2 -3
- sqlspec/utils/correlation.py +0 -3
- sqlspec/utils/deprecation.py +6 -6
- sqlspec/utils/fixtures.py +6 -6
- sqlspec/utils/logging.py +0 -2
- sqlspec/utils/module_loader.py +7 -12
- sqlspec/utils/singleton.py +0 -1
- sqlspec/utils/sync_tools.py +17 -38
- sqlspec/utils/text.py +12 -51
- sqlspec/utils/type_guards.py +443 -232
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/METADATA +7 -2
- sqlspec-0.16.0.dist-info/RECORD +134 -0
- sqlspec/adapters/adbc/transformers.py +0 -108
- sqlspec/driver/connection.py +0 -207
- sqlspec/driver/mixins/_cache.py +0 -114
- sqlspec/driver/mixins/_csv_writer.py +0 -91
- sqlspec/driver/mixins/_pipeline.py +0 -508
- sqlspec/driver/mixins/_query_tools.py +0 -796
- sqlspec/driver/mixins/_result_utils.py +0 -138
- sqlspec/driver/mixins/_storage.py +0 -912
- sqlspec/driver/mixins/_type_coercion.py +0 -128
- sqlspec/driver/parameters.py +0 -138
- sqlspec/statement/__init__.py +0 -21
- sqlspec/statement/builder/_merge.py +0 -95
- sqlspec/statement/cache.py +0 -50
- sqlspec/statement/filters.py +0 -625
- sqlspec/statement/parameters.py +0 -956
- sqlspec/statement/pipelines/__init__.py +0 -210
- sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
- sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
- sqlspec/statement/pipelines/context.py +0 -109
- sqlspec/statement/pipelines/transformers/__init__.py +0 -7
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
- sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
- sqlspec/statement/pipelines/validators/__init__.py +0 -23
- sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
- sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
- sqlspec/statement/pipelines/validators/_performance.py +0 -714
- sqlspec/statement/pipelines/validators/_security.py +0 -967
- sqlspec/statement/result.py +0 -435
- sqlspec/statement/sql.py +0 -1774
- sqlspec/utils/cached_property.py +0 -25
- sqlspec/utils/statement_hashing.py +0 -203
- sqlspec-0.14.1.dist-info/RECORD +0 -145
- /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -3,8 +3,8 @@ from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
|
3
3
|
from sqlglot import exp
|
|
4
4
|
from typing_extensions import Self
|
|
5
5
|
|
|
6
|
+
from sqlspec.builder._parsing_utils import parse_table_expression
|
|
6
7
|
from sqlspec.exceptions import SQLBuilderError
|
|
7
|
-
from sqlspec.statement.builder._parsing_utils import parse_table_expression
|
|
8
8
|
from sqlspec.utils.type_guards import has_query_builder_parameters
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
@@ -33,7 +33,6 @@ class JoinClauseMixin:
|
|
|
33
33
|
if isinstance(table, str):
|
|
34
34
|
table_expr = parse_table_expression(table, alias)
|
|
35
35
|
elif has_query_builder_parameters(table):
|
|
36
|
-
# Work directly with AST when possible to avoid string parsing
|
|
37
36
|
if hasattr(table, "_expression") and getattr(table, "_expression", None) is not None:
|
|
38
37
|
table_expr_value = getattr(table, "_expression", None)
|
|
39
38
|
if table_expr_value is not None:
|
|
@@ -46,7 +45,6 @@ class JoinClauseMixin:
|
|
|
46
45
|
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
47
46
|
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(builder, "dialect", None)))
|
|
48
47
|
table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
49
|
-
# Parameter merging logic can be added here if needed
|
|
50
48
|
else:
|
|
51
49
|
table_expr = table
|
|
52
50
|
on_expr: Optional[exp.Expression] = None
|
|
@@ -70,10 +70,9 @@ class MergeUsingClauseMixin:
|
|
|
70
70
|
if isinstance(source, str):
|
|
71
71
|
source_expr = exp.to_table(source, alias=alias)
|
|
72
72
|
elif has_query_builder_parameters(source) and hasattr(source, "_expression"):
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
for p_name, p_value in subquery_builder_params.items():
|
|
73
|
+
subquery_builder_parameters = source.parameters
|
|
74
|
+
if subquery_builder_parameters:
|
|
75
|
+
for p_name, p_value in subquery_builder_parameters.items():
|
|
77
76
|
self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
|
|
78
77
|
|
|
79
78
|
subquery_exp = exp.paren(getattr(source, "_expression", exp.select()))
|
|
@@ -173,7 +172,11 @@ class MergeMatchedClauseMixin:
|
|
|
173
172
|
"""
|
|
174
173
|
update_expressions: list[exp.EQ] = []
|
|
175
174
|
for col, val in set_values.items():
|
|
176
|
-
|
|
175
|
+
column_name = col if isinstance(col, str) else str(col)
|
|
176
|
+
if "." in column_name:
|
|
177
|
+
column_name = column_name.split(".")[-1]
|
|
178
|
+
param_name = self._generate_unique_parameter_name(column_name) # type: ignore[attr-defined]
|
|
179
|
+
param_name = self.add_parameter(val, name=param_name)[1] # type: ignore[attr-defined]
|
|
177
180
|
update_expressions.append(exp.EQ(this=exp.column(col), expression=exp.var(param_name)))
|
|
178
181
|
|
|
179
182
|
when_args: dict[str, Any] = {"matched": True, "then": exp.Update(expressions=update_expressions)}
|
|
@@ -271,8 +274,12 @@ class MergeNotMatchedClauseMixin:
|
|
|
271
274
|
raise SQLBuilderError(msg)
|
|
272
275
|
|
|
273
276
|
parameterized_values: list[exp.Expression] = []
|
|
274
|
-
for val in values:
|
|
275
|
-
|
|
277
|
+
for i, val in enumerate(values):
|
|
278
|
+
column_name = columns[i] if isinstance(columns[i], str) else str(columns[i])
|
|
279
|
+
if "." in column_name:
|
|
280
|
+
column_name = column_name.split(".")[-1]
|
|
281
|
+
param_name = self._generate_unique_parameter_name(column_name) # type: ignore[attr-defined]
|
|
282
|
+
param_name = self.add_parameter(val, name=param_name)[1] # type: ignore[attr-defined]
|
|
276
283
|
parameterized_values.append(exp.var(param_name))
|
|
277
284
|
|
|
278
285
|
insert_args["this"] = exp.Tuple(expressions=[exp.column(c) for c in columns])
|
|
@@ -281,7 +288,6 @@ class MergeNotMatchedClauseMixin:
|
|
|
281
288
|
msg = "Specifying columns without values for INSERT action is complex and not fully supported yet. Consider providing full expressions."
|
|
282
289
|
raise SQLBuilderError(msg)
|
|
283
290
|
elif not columns and not values:
|
|
284
|
-
# INSERT DEFAULT VALUES case
|
|
285
291
|
pass
|
|
286
292
|
else:
|
|
287
293
|
msg = "Cannot specify values without columns for INSERT action."
|
|
@@ -338,7 +344,11 @@ class MergeNotMatchedBySourceClauseMixin:
|
|
|
338
344
|
"""
|
|
339
345
|
update_expressions: list[exp.EQ] = []
|
|
340
346
|
for col, val in set_values.items():
|
|
341
|
-
|
|
347
|
+
column_name = col if isinstance(col, str) else str(col)
|
|
348
|
+
if "." in column_name:
|
|
349
|
+
column_name = column_name.split(".")[-1]
|
|
350
|
+
param_name = self._generate_unique_parameter_name(column_name) # type: ignore[attr-defined]
|
|
351
|
+
param_name = self.add_parameter(val, name=param_name)[1] # type: ignore[attr-defined]
|
|
342
352
|
update_expressions.append(exp.EQ(this=exp.column(col), expression=exp.var(param_name)))
|
|
343
353
|
|
|
344
354
|
when_args: dict[str, Any] = {
|
|
@@ -5,8 +5,8 @@ from typing import TYPE_CHECKING, Optional, Union, cast
|
|
|
5
5
|
from sqlglot import exp
|
|
6
6
|
from typing_extensions import Self
|
|
7
7
|
|
|
8
|
+
from sqlspec.builder._parsing_utils import parse_order_expression
|
|
8
9
|
from sqlspec.exceptions import SQLBuilderError
|
|
9
|
-
from sqlspec.statement.builder._parsing_utils import parse_order_expression
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from sqlspec.protocols import SQLBuilderProtocol
|
|
@@ -71,7 +71,7 @@ class LimitOffsetClauseMixin:
|
|
|
71
71
|
if not isinstance(builder._expression, exp.Select):
|
|
72
72
|
msg = "LIMIT is only supported for SELECT statements."
|
|
73
73
|
raise SQLBuilderError(msg)
|
|
74
|
-
builder._expression = builder._expression.limit(exp.
|
|
74
|
+
builder._expression = builder._expression.limit(exp.convert(value), copy=False)
|
|
75
75
|
return cast("Self", builder)
|
|
76
76
|
|
|
77
77
|
def offset(self, value: int) -> Self:
|
|
@@ -90,7 +90,7 @@ class LimitOffsetClauseMixin:
|
|
|
90
90
|
if not isinstance(builder._expression, exp.Select):
|
|
91
91
|
msg = "OFFSET is only supported for SELECT statements."
|
|
92
92
|
raise SQLBuilderError(msg)
|
|
93
|
-
builder._expression = builder._expression.offset(exp.
|
|
93
|
+
builder._expression = builder._expression.offset(exp.convert(value), copy=False)
|
|
94
94
|
return cast("Self", builder)
|
|
95
95
|
|
|
96
96
|
|
|
@@ -7,7 +7,7 @@ from sqlglot import exp
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from sqlglot.dialects.dialect import DialectType
|
|
9
9
|
|
|
10
|
-
from sqlspec.
|
|
10
|
+
from sqlspec.builder._select import Select
|
|
11
11
|
|
|
12
12
|
__all__ = ("PivotClauseMixin", "UnpivotClauseMixin")
|
|
13
13
|
|
|
@@ -56,12 +56,10 @@ class PivotClauseMixin:
|
|
|
56
56
|
for val in pivot_values:
|
|
57
57
|
if isinstance(val, exp.Expression):
|
|
58
58
|
pivot_value_exprs.append(val)
|
|
59
|
-
elif isinstance(val, str):
|
|
60
|
-
pivot_value_exprs.append(exp.
|
|
61
|
-
elif isinstance(val, (int, float)):
|
|
62
|
-
pivot_value_exprs.append(exp.Literal.number(val))
|
|
59
|
+
elif isinstance(val, (str, int, float)):
|
|
60
|
+
pivot_value_exprs.append(exp.convert(val))
|
|
63
61
|
else:
|
|
64
|
-
pivot_value_exprs.append(exp.
|
|
62
|
+
pivot_value_exprs.append(exp.convert(str(val)))
|
|
65
63
|
|
|
66
64
|
in_expr = exp.In(this=pivot_col_expr, expressions=pivot_value_exprs)
|
|
67
65
|
|
|
@@ -113,7 +111,6 @@ class UnpivotClauseMixin:
|
|
|
113
111
|
"""
|
|
114
112
|
current_expr = self._expression
|
|
115
113
|
if not isinstance(current_expr, exp.Select):
|
|
116
|
-
# SelectBuilder's __init__ ensures _expression is exp.Select.
|
|
117
114
|
msg = "Unpivot can only be applied to a Select expression managed by Select."
|
|
118
115
|
raise TypeError(msg)
|
|
119
116
|
|
|
@@ -127,7 +124,6 @@ class UnpivotClauseMixin:
|
|
|
127
124
|
elif isinstance(col_name_or_expr, str):
|
|
128
125
|
unpivot_cols_exprs.append(exp.column(col_name_or_expr))
|
|
129
126
|
else:
|
|
130
|
-
# Fallback for other types, should ideally be an error or more specific handling
|
|
131
127
|
unpivot_cols_exprs.append(exp.column(str(col_name_or_expr)))
|
|
132
128
|
|
|
133
129
|
in_expr = exp.In(this=name_col_ident, expressions=unpivot_cols_exprs)
|
|
@@ -6,15 +6,14 @@ from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
|
6
6
|
from sqlglot import exp
|
|
7
7
|
from typing_extensions import Self
|
|
8
8
|
|
|
9
|
+
from sqlspec.builder._parsing_utils import parse_column_expression, parse_table_expression
|
|
9
10
|
from sqlspec.exceptions import SQLBuilderError
|
|
10
|
-
from sqlspec.statement.builder._parsing_utils import parse_column_expression, parse_table_expression
|
|
11
11
|
from sqlspec.utils.type_guards import has_query_builder_parameters, is_expression
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
|
+
from sqlspec.builder._base import QueryBuilder
|
|
15
|
+
from sqlspec.builder._column import Column, FunctionColumn
|
|
14
16
|
from sqlspec.protocols import SelectBuilderProtocol, SQLBuilderProtocol
|
|
15
|
-
from sqlspec.statement.builder._base import QueryBuilder
|
|
16
|
-
from sqlspec.statement.builder._column import Column, FunctionColumn
|
|
17
|
-
from sqlspec.typing import RowT
|
|
18
17
|
|
|
19
18
|
__all__ = ("CaseBuilder", "SelectClauseMixin")
|
|
20
19
|
|
|
@@ -24,7 +23,6 @@ class SelectClauseMixin:
|
|
|
24
23
|
|
|
25
24
|
_expression: Optional[exp.Expression] = None
|
|
26
25
|
|
|
27
|
-
# SELECT and DISTINCT methods
|
|
28
26
|
def select(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn"]) -> Self:
|
|
29
27
|
"""Add columns to SELECT clause.
|
|
30
28
|
|
|
@@ -69,7 +67,6 @@ class SelectClauseMixin:
|
|
|
69
67
|
builder._expression.set("distinct", exp.Distinct(expressions=distinct_columns))
|
|
70
68
|
return cast("Self", builder)
|
|
71
69
|
|
|
72
|
-
# FROM clause method
|
|
73
70
|
def from_(self, table: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
|
|
74
71
|
"""Add FROM clause.
|
|
75
72
|
|
|
@@ -93,30 +90,27 @@ class SelectClauseMixin:
|
|
|
93
90
|
if isinstance(table, str):
|
|
94
91
|
from_expr = parse_table_expression(table, alias)
|
|
95
92
|
elif is_expression(table):
|
|
96
|
-
# Direct sqlglot expression - use as is
|
|
97
93
|
from_expr = exp.alias_(table, alias) if alias else table
|
|
98
94
|
elif has_query_builder_parameters(table):
|
|
99
|
-
# Query builder with build() method
|
|
100
95
|
subquery = table.build()
|
|
101
96
|
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
102
97
|
subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(builder, "dialect", None)))
|
|
103
98
|
from_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
parameters=
|
|
110
|
-
args=
|
|
111
|
-
kwargs=
|
|
99
|
+
current_parameters = getattr(builder, "_parameters", None)
|
|
100
|
+
merged_parameters = getattr(type(builder), "ParameterConverter", None)
|
|
101
|
+
if merged_parameters and hasattr(subquery, "parameters"):
|
|
102
|
+
subquery_parameters = getattr(subquery, "parameters", {})
|
|
103
|
+
merged_parameters = merged_parameters.merge_parameters(
|
|
104
|
+
parameters=subquery_parameters,
|
|
105
|
+
args=current_parameters if isinstance(current_parameters, list) else None,
|
|
106
|
+
kwargs=current_parameters if isinstance(current_parameters, dict) else {},
|
|
112
107
|
)
|
|
113
|
-
setattr(builder, "_parameters",
|
|
108
|
+
setattr(builder, "_parameters", merged_parameters)
|
|
114
109
|
else:
|
|
115
110
|
from_expr = table
|
|
116
111
|
builder._expression = builder._expression.from_(from_expr, copy=False)
|
|
117
112
|
return cast("Self", builder)
|
|
118
113
|
|
|
119
|
-
# GROUP BY methods
|
|
120
114
|
def group_by(self, *columns: Union[str, exp.Expression]) -> Self:
|
|
121
115
|
"""Add GROUP BY clause.
|
|
122
116
|
|
|
@@ -149,7 +143,6 @@ class SelectClauseMixin:
|
|
|
149
143
|
|
|
150
144
|
Example:
|
|
151
145
|
```python
|
|
152
|
-
# GROUP BY ROLLUP(product, region)
|
|
153
146
|
query = (
|
|
154
147
|
sql.select("product", "region", sql.sum("sales"))
|
|
155
148
|
.from_("sales_data")
|
|
@@ -174,7 +167,6 @@ class SelectClauseMixin:
|
|
|
174
167
|
|
|
175
168
|
Example:
|
|
176
169
|
```python
|
|
177
|
-
# GROUP BY CUBE(product, region)
|
|
178
170
|
query = (
|
|
179
171
|
sql.select("product", "region", sql.sum("sales"))
|
|
180
172
|
.from_("sales_data")
|
|
@@ -200,7 +192,6 @@ class SelectClauseMixin:
|
|
|
200
192
|
|
|
201
193
|
Example:
|
|
202
194
|
```python
|
|
203
|
-
# GROUP BY GROUPING SETS ((product), (region), ())
|
|
204
195
|
query = (
|
|
205
196
|
sql.select("product", "region", sql.sum("sales"))
|
|
206
197
|
.from_("sales_data")
|
|
@@ -217,13 +208,11 @@ class SelectClauseMixin:
|
|
|
217
208
|
columns = [exp.column(col) for col in column_set]
|
|
218
209
|
set_expressions.append(exp.Tuple(expressions=columns))
|
|
219
210
|
else:
|
|
220
|
-
# Single column
|
|
221
211
|
set_expressions.append(exp.column(column_set))
|
|
222
212
|
|
|
223
213
|
grouping_sets_expr = exp.GroupingSets(expressions=set_expressions)
|
|
224
214
|
return self.group_by(grouping_sets_expr)
|
|
225
215
|
|
|
226
|
-
# Aggregate function methods
|
|
227
216
|
def count_(self, column: "Union[str, exp.Expression]" = "*", alias: Optional[str] = None) -> Self:
|
|
228
217
|
"""Add COUNT function to SELECT clause.
|
|
229
218
|
|
|
@@ -440,8 +429,7 @@ class SelectClauseMixin:
|
|
|
440
429
|
"""
|
|
441
430
|
builder = cast("SelectBuilderProtocol", self)
|
|
442
431
|
col_expr = exp.column(column) if isinstance(column, str) else column
|
|
443
|
-
|
|
444
|
-
string_agg_expr = exp.GroupConcat(this=col_expr, separator=exp.Literal.string(separator))
|
|
432
|
+
string_agg_expr = exp.GroupConcat(this=col_expr, separator=exp.convert(separator))
|
|
445
433
|
select_expr = exp.alias_(string_agg_expr, alias) if alias else string_agg_expr
|
|
446
434
|
return cast("Self", builder.select(select_expr))
|
|
447
435
|
|
|
@@ -461,7 +449,6 @@ class SelectClauseMixin:
|
|
|
461
449
|
select_expr = exp.alias_(json_agg_expr, alias) if alias else json_agg_expr
|
|
462
450
|
return cast("Self", builder.select(select_expr))
|
|
463
451
|
|
|
464
|
-
# Window function method
|
|
465
452
|
def window(
|
|
466
453
|
self,
|
|
467
454
|
function_expr: Union[str, exp.Expression],
|
|
@@ -501,20 +488,19 @@ class SelectClauseMixin:
|
|
|
501
488
|
else:
|
|
502
489
|
func_expr_parsed = function_expr
|
|
503
490
|
|
|
504
|
-
over_args: dict[str, Any] = {}
|
|
491
|
+
over_args: dict[str, Any] = {}
|
|
505
492
|
if partition_by:
|
|
506
493
|
if isinstance(partition_by, str):
|
|
507
494
|
over_args["partition_by"] = [exp.column(partition_by)]
|
|
508
|
-
elif isinstance(partition_by, list):
|
|
495
|
+
elif isinstance(partition_by, list):
|
|
509
496
|
over_args["partition_by"] = [exp.column(col) if isinstance(col, str) else col for col in partition_by]
|
|
510
|
-
elif isinstance(partition_by, exp.Expression):
|
|
497
|
+
elif isinstance(partition_by, exp.Expression):
|
|
511
498
|
over_args["partition_by"] = [partition_by]
|
|
512
499
|
|
|
513
500
|
if order_by:
|
|
514
501
|
if isinstance(order_by, str):
|
|
515
502
|
over_args["order"] = exp.column(order_by).asc()
|
|
516
503
|
elif isinstance(order_by, list):
|
|
517
|
-
# Properly handle multiple ORDER BY columns using Order expression
|
|
518
504
|
order_expressions: list[Union[exp.Expression, exp.Column]] = []
|
|
519
505
|
for col in order_by:
|
|
520
506
|
if isinstance(col, str):
|
|
@@ -534,7 +520,6 @@ class SelectClauseMixin:
|
|
|
534
520
|
self._expression.select(exp.alias_(window_expr, alias) if alias else window_expr, copy=False)
|
|
535
521
|
return self
|
|
536
522
|
|
|
537
|
-
# CASE expression method
|
|
538
523
|
def case_(self, alias: "Optional[str]" = None) -> "CaseBuilder":
|
|
539
524
|
"""Create a CASE expression for the SELECT clause.
|
|
540
525
|
|
|
@@ -544,7 +529,7 @@ class SelectClauseMixin:
|
|
|
544
529
|
Returns:
|
|
545
530
|
CaseBuilder: A CaseBuilder instance for building the CASE expression.
|
|
546
531
|
"""
|
|
547
|
-
builder = cast("QueryBuilder
|
|
532
|
+
builder = cast("QueryBuilder", self) # pyright: ignore
|
|
548
533
|
return CaseBuilder(builder, alias)
|
|
549
534
|
|
|
550
535
|
|
|
@@ -552,11 +537,11 @@ class SelectClauseMixin:
|
|
|
552
537
|
class CaseBuilder:
|
|
553
538
|
"""Builder for CASE expressions."""
|
|
554
539
|
|
|
555
|
-
_parent: "QueryBuilder
|
|
540
|
+
_parent: "QueryBuilder" # pyright: ignore
|
|
556
541
|
_alias: Optional[str]
|
|
557
542
|
_case_expr: exp.Case
|
|
558
543
|
|
|
559
|
-
def __init__(self, parent: "QueryBuilder
|
|
544
|
+
def __init__(self, parent: "QueryBuilder", alias: "Optional[str]" = None) -> None:
|
|
560
545
|
"""Initialize CaseBuilder.
|
|
561
546
|
|
|
562
547
|
Args:
|
|
@@ -578,7 +563,8 @@ class CaseBuilder:
|
|
|
578
563
|
CaseBuilder: The current builder instance for method chaining.
|
|
579
564
|
"""
|
|
580
565
|
cond_expr = exp.condition(condition) if isinstance(condition, str) else condition
|
|
581
|
-
param_name = self._parent.
|
|
566
|
+
param_name = self._parent._generate_unique_parameter_name("case_when_value")
|
|
567
|
+
param_name = self._parent.add_parameter(value, name=param_name)[1]
|
|
582
568
|
value_expr = exp.Placeholder(this=param_name)
|
|
583
569
|
|
|
584
570
|
when_clause = exp.When(this=cond_expr, then=value_expr)
|
|
@@ -597,16 +583,17 @@ class CaseBuilder:
|
|
|
597
583
|
Returns:
|
|
598
584
|
CaseBuilder: The current builder instance for method chaining.
|
|
599
585
|
"""
|
|
600
|
-
param_name = self._parent.
|
|
586
|
+
param_name = self._parent._generate_unique_parameter_name("case_else_value")
|
|
587
|
+
param_name = self._parent.add_parameter(value, name=param_name)[1]
|
|
601
588
|
value_expr = exp.Placeholder(this=param_name)
|
|
602
589
|
self._case_expr.set("default", value_expr)
|
|
603
590
|
return self
|
|
604
591
|
|
|
605
|
-
def end(self) -> "QueryBuilder
|
|
592
|
+
def end(self) -> "QueryBuilder":
|
|
606
593
|
"""Finalize the CASE expression and add it to the SELECT clause.
|
|
607
594
|
|
|
608
595
|
Returns:
|
|
609
596
|
The parent builder instance.
|
|
610
597
|
"""
|
|
611
598
|
select_expr = exp.alias_(self._case_expr, self._alias) if self._alias else self._case_expr
|
|
612
|
-
return cast("QueryBuilder
|
|
599
|
+
return cast("QueryBuilder", self._parent.select(select_expr)) # type: ignore[attr-defined]
|
|
@@ -68,52 +68,51 @@ class UpdateSetClauseMixin:
|
|
|
68
68
|
msg = "Cannot add SET clause to non-UPDATE expression."
|
|
69
69
|
raise SQLBuilderError(msg)
|
|
70
70
|
assignments = []
|
|
71
|
-
# (column, value) signature
|
|
72
71
|
if len(args) == MIN_SET_ARGS and not kwargs:
|
|
73
72
|
col, val = args
|
|
74
73
|
col_expr = col if isinstance(col, exp.Column) else exp.column(col)
|
|
75
|
-
# If value is an expression, use it directly
|
|
76
74
|
if isinstance(val, exp.Expression):
|
|
77
75
|
value_expr = val
|
|
78
76
|
elif has_query_builder_parameters(val):
|
|
79
|
-
# It's a builder (like SelectBuilder), convert to subquery
|
|
80
77
|
subquery = val.build()
|
|
81
|
-
# Parse the SQL and use as expression
|
|
82
78
|
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
83
79
|
value_expr = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect", None)))
|
|
84
|
-
# Merge parameters from subquery
|
|
85
80
|
if has_query_builder_parameters(val):
|
|
86
81
|
for p_name, p_value in val.parameters.items():
|
|
87
82
|
self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
|
|
88
83
|
else:
|
|
89
|
-
|
|
84
|
+
column_name = col if isinstance(col, str) else str(col)
|
|
85
|
+
# Extract just the column part if table.column format
|
|
86
|
+
if "." in column_name:
|
|
87
|
+
column_name = column_name.split(".")[-1]
|
|
88
|
+
param_name = self._generate_unique_parameter_name(column_name) # type: ignore[attr-defined]
|
|
89
|
+
param_name = self.add_parameter(val, name=param_name)[1] # type: ignore[attr-defined]
|
|
90
90
|
value_expr = exp.Placeholder(this=param_name)
|
|
91
91
|
assignments.append(exp.EQ(this=col_expr, expression=value_expr))
|
|
92
|
-
# mapping and/or kwargs
|
|
93
92
|
elif (len(args) == 1 and isinstance(args[0], Mapping)) or kwargs:
|
|
94
93
|
all_values = dict(args[0] if args else {}, **kwargs)
|
|
95
94
|
for col, val in all_values.items():
|
|
96
|
-
# If value is an expression, use it directly
|
|
97
95
|
if isinstance(val, exp.Expression):
|
|
98
96
|
value_expr = val
|
|
99
97
|
elif has_query_builder_parameters(val):
|
|
100
|
-
# It's a builder (like SelectBuilder), convert to subquery
|
|
101
98
|
subquery = val.build()
|
|
102
|
-
# Parse the SQL and use as expression
|
|
103
99
|
sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
104
100
|
value_expr = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect", None)))
|
|
105
|
-
# Merge parameters from subquery
|
|
106
101
|
if has_query_builder_parameters(val):
|
|
107
102
|
for p_name, p_value in val.parameters.items():
|
|
108
103
|
self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
|
|
109
104
|
else:
|
|
110
|
-
|
|
105
|
+
# Extract column name for parameter naming
|
|
106
|
+
column_name = col if isinstance(col, str) else str(col)
|
|
107
|
+
if "." in column_name:
|
|
108
|
+
column_name = column_name.split(".")[-1]
|
|
109
|
+
param_name = self._generate_unique_parameter_name(column_name) # type: ignore[attr-defined]
|
|
110
|
+
param_name = self.add_parameter(val, name=param_name)[1] # type: ignore[attr-defined]
|
|
111
111
|
value_expr = exp.Placeholder(this=param_name)
|
|
112
112
|
assignments.append(exp.EQ(this=exp.column(col), expression=value_expr))
|
|
113
113
|
else:
|
|
114
114
|
msg = "Invalid arguments for set(): use (column, value), mapping, or kwargs."
|
|
115
115
|
raise SQLBuilderError(msg)
|
|
116
|
-
# Append to existing expressions instead of replacing
|
|
117
116
|
existing = self._expression.args.get("expressions", [])
|
|
118
117
|
self._expression.set("expressions", existing + assignments)
|
|
119
118
|
return self
|
|
@@ -142,9 +141,9 @@ class UpdateFromClauseMixin:
|
|
|
142
141
|
if isinstance(table, str):
|
|
143
142
|
table_expr = exp.to_table(table, alias=alias)
|
|
144
143
|
elif has_query_builder_parameters(table):
|
|
145
|
-
|
|
146
|
-
if
|
|
147
|
-
for p_name, p_value in
|
|
144
|
+
subquery_builder_parameters = getattr(table, "_parameters", None)
|
|
145
|
+
if subquery_builder_parameters:
|
|
146
|
+
for p_name, p_value in subquery_builder_parameters.items():
|
|
148
147
|
self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
|
|
149
148
|
subquery_exp = exp.paren(getattr(table, "_expression", exp.select()))
|
|
150
149
|
table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|