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.
- sqlspec/__init__.py +7 -15
- sqlspec/_serialization.py +256 -24
- sqlspec/_typing.py +71 -52
- sqlspec/adapters/adbc/_types.py +1 -1
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +870 -0
- sqlspec/adapters/adbc/config.py +69 -12
- sqlspec/adapters/adbc/data_dictionary.py +340 -0
- sqlspec/adapters/adbc/driver.py +266 -58
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +504 -0
- sqlspec/adapters/adbc/type_converter.py +153 -0
- sqlspec/adapters/aiosqlite/_types.py +1 -1
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +527 -0
- sqlspec/adapters/aiosqlite/config.py +88 -15
- sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
- sqlspec/adapters/aiosqlite/driver.py +143 -40
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
- sqlspec/adapters/aiosqlite/pool.py +7 -7
- sqlspec/adapters/asyncmy/__init__.py +7 -1
- sqlspec/adapters/asyncmy/_types.py +2 -2
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +493 -0
- sqlspec/adapters/asyncmy/config.py +68 -23
- sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
- sqlspec/adapters/asyncmy/driver.py +313 -58
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +2 -1
- sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
- sqlspec/adapters/asyncpg/_types.py +11 -7
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +450 -0
- sqlspec/adapters/asyncpg/config.py +59 -35
- sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
- sqlspec/adapters/asyncpg/driver.py +170 -25
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +253 -0
- sqlspec/adapters/bigquery/_types.py +1 -1
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +576 -0
- sqlspec/adapters/bigquery/config.py +27 -10
- sqlspec/adapters/bigquery/data_dictionary.py +149 -0
- sqlspec/adapters/bigquery/driver.py +368 -142
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +327 -0
- sqlspec/adapters/bigquery/type_converter.py +125 -0
- sqlspec/adapters/duckdb/_types.py +1 -1
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +553 -0
- sqlspec/adapters/duckdb/config.py +80 -20
- sqlspec/adapters/duckdb/data_dictionary.py +163 -0
- sqlspec/adapters/duckdb/driver.py +167 -45
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +4 -4
- sqlspec/adapters/duckdb/type_converter.py +133 -0
- sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
- sqlspec/adapters/oracledb/_types.py +20 -2
- sqlspec/adapters/oracledb/adk/__init__.py +5 -0
- sqlspec/adapters/oracledb/adk/store.py +1745 -0
- sqlspec/adapters/oracledb/config.py +122 -32
- sqlspec/adapters/oracledb/data_dictionary.py +509 -0
- sqlspec/adapters/oracledb/driver.py +353 -91
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +767 -0
- sqlspec/adapters/oracledb/migrations.py +348 -73
- sqlspec/adapters/oracledb/type_converter.py +207 -0
- sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
- sqlspec/adapters/psqlpy/_types.py +2 -1
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +482 -0
- sqlspec/adapters/psqlpy/config.py +46 -17
- sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
- sqlspec/adapters/psqlpy/driver.py +123 -209
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +272 -0
- sqlspec/adapters/psqlpy/type_converter.py +102 -0
- sqlspec/adapters/psycopg/_type_handlers.py +80 -0
- sqlspec/adapters/psycopg/_types.py +2 -1
- sqlspec/adapters/psycopg/adk/__init__.py +5 -0
- sqlspec/adapters/psycopg/adk/store.py +944 -0
- sqlspec/adapters/psycopg/config.py +69 -35
- sqlspec/adapters/psycopg/data_dictionary.py +331 -0
- sqlspec/adapters/psycopg/driver.py +238 -81
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/sqlite/__init__.py +2 -1
- sqlspec/adapters/sqlite/_type_handlers.py +86 -0
- sqlspec/adapters/sqlite/_types.py +1 -1
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +572 -0
- sqlspec/adapters/sqlite/config.py +87 -15
- sqlspec/adapters/sqlite/data_dictionary.py +149 -0
- sqlspec/adapters/sqlite/driver.py +137 -54
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +18 -9
- sqlspec/base.py +45 -26
- sqlspec/builder/__init__.py +73 -4
- sqlspec/builder/_base.py +162 -89
- sqlspec/builder/_column.py +62 -29
- sqlspec/builder/_ddl.py +180 -121
- sqlspec/builder/_delete.py +5 -4
- sqlspec/builder/_dml.py +388 -0
- sqlspec/{_sql.py → builder/_factory.py} +53 -94
- sqlspec/builder/_insert.py +32 -131
- sqlspec/builder/_join.py +375 -0
- sqlspec/builder/_merge.py +446 -11
- sqlspec/builder/_parsing_utils.py +111 -17
- sqlspec/builder/_select.py +1457 -24
- sqlspec/builder/_update.py +11 -42
- sqlspec/cli.py +307 -194
- sqlspec/config.py +252 -67
- sqlspec/core/__init__.py +5 -4
- sqlspec/core/cache.py +17 -17
- sqlspec/core/compiler.py +62 -9
- sqlspec/core/filters.py +37 -37
- sqlspec/core/hashing.py +9 -9
- sqlspec/core/parameters.py +83 -48
- sqlspec/core/result.py +102 -46
- sqlspec/core/splitter.py +16 -17
- sqlspec/core/statement.py +36 -30
- sqlspec/core/type_conversion.py +235 -0
- sqlspec/driver/__init__.py +7 -6
- sqlspec/driver/_async.py +188 -151
- sqlspec/driver/_common.py +285 -80
- sqlspec/driver/_sync.py +188 -152
- sqlspec/driver/mixins/_result_tools.py +20 -236
- sqlspec/driver/mixins/_sql_translator.py +4 -4
- sqlspec/exceptions.py +75 -7
- sqlspec/extensions/adk/__init__.py +53 -0
- sqlspec/extensions/adk/_types.py +51 -0
- sqlspec/extensions/adk/converters.py +172 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +181 -0
- sqlspec/extensions/adk/store.py +536 -0
- sqlspec/extensions/aiosql/adapter.py +73 -53
- sqlspec/extensions/litestar/__init__.py +21 -4
- sqlspec/extensions/litestar/cli.py +54 -10
- sqlspec/extensions/litestar/config.py +59 -266
- sqlspec/extensions/litestar/handlers.py +46 -17
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +324 -223
- sqlspec/extensions/litestar/providers.py +25 -25
- sqlspec/extensions/litestar/store.py +265 -0
- sqlspec/loader.py +30 -49
- sqlspec/migrations/__init__.py +4 -3
- sqlspec/migrations/base.py +302 -39
- sqlspec/migrations/commands.py +611 -144
- sqlspec/migrations/context.py +142 -0
- sqlspec/migrations/fix.py +199 -0
- sqlspec/migrations/loaders.py +68 -23
- sqlspec/migrations/runner.py +543 -107
- sqlspec/migrations/tracker.py +237 -21
- sqlspec/migrations/utils.py +51 -3
- sqlspec/migrations/validation.py +177 -0
- sqlspec/protocols.py +66 -36
- sqlspec/storage/_utils.py +98 -0
- sqlspec/storage/backends/fsspec.py +134 -106
- sqlspec/storage/backends/local.py +78 -51
- sqlspec/storage/backends/obstore.py +278 -162
- sqlspec/storage/registry.py +75 -39
- sqlspec/typing.py +16 -84
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/correlation.py +4 -5
- sqlspec/utils/data_transformation.py +3 -2
- sqlspec/utils/deprecation.py +9 -8
- sqlspec/utils/fixtures.py +4 -4
- sqlspec/utils/logging.py +46 -6
- sqlspec/utils/module_loader.py +2 -2
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +50 -2
- sqlspec/utils/sync_tools.py +21 -17
- sqlspec/utils/text.py +1 -2
- sqlspec/utils/type_guards.py +111 -20
- sqlspec/utils/version.py +433 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
- sqlspec-0.27.0.dist-info/RECORD +207 -0
- sqlspec/builder/mixins/__init__.py +0 -55
- sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
- sqlspec/builder/mixins/_delete_operations.py +0 -50
- sqlspec/builder/mixins/_insert_operations.py +0 -282
- sqlspec/builder/mixins/_join_operations.py +0 -389
- sqlspec/builder/mixins/_merge_operations.py +0 -592
- sqlspec/builder/mixins/_order_limit_operations.py +0 -152
- sqlspec/builder/mixins/_pivot_operations.py +0 -157
- sqlspec/builder/mixins/_select_operations.py +0 -936
- sqlspec/builder/mixins/_update_operations.py +0 -218
- sqlspec/builder/mixins/_where_clause.py +0 -1304
- sqlspec-0.25.0.dist-info/RECORD +0 -139
- sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
sqlspec/builder/_delete.py
CHANGED
|
@@ -4,12 +4,13 @@ Provides a fluent interface for building SQL DELETE queries with
|
|
|
4
4
|
parameter binding and validation.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any
|
|
8
8
|
|
|
9
9
|
from sqlglot import exp
|
|
10
10
|
|
|
11
11
|
from sqlspec.builder._base import QueryBuilder, SafeQuery
|
|
12
|
-
from sqlspec.builder.
|
|
12
|
+
from sqlspec.builder._dml import DeleteFromClauseMixin
|
|
13
|
+
from sqlspec.builder._select import ReturningClauseMixin, WhereClauseMixin
|
|
13
14
|
from sqlspec.core.result import SQLResult
|
|
14
15
|
from sqlspec.exceptions import SQLBuilderError
|
|
15
16
|
|
|
@@ -24,9 +25,9 @@ class Delete(QueryBuilder, WhereClauseMixin, ReturningClauseMixin, DeleteFromCla
|
|
|
24
25
|
"""
|
|
25
26
|
|
|
26
27
|
__slots__ = ("_table",)
|
|
27
|
-
_expression:
|
|
28
|
+
_expression: exp.Expression | None
|
|
28
29
|
|
|
29
|
-
def __init__(self, table:
|
|
30
|
+
def __init__(self, table: str | None = None, **kwargs: Any) -> None:
|
|
30
31
|
"""Initialize DELETE with optional table.
|
|
31
32
|
|
|
32
33
|
Args:
|
sqlspec/builder/_dml.py
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"""Reusable mixins for INSERT/UPDATE/DELETE builders."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping, Sequence
|
|
4
|
+
from typing import Any, cast
|
|
5
|
+
|
|
6
|
+
from mypy_extensions import trait
|
|
7
|
+
from sqlglot import exp
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
from sqlspec.builder._parsing_utils import extract_sql_object_expression
|
|
11
|
+
from sqlspec.exceptions import SQLBuilderError
|
|
12
|
+
from sqlspec.protocols import SQLBuilderProtocol
|
|
13
|
+
from sqlspec.utils.type_guards import has_expression_and_sql, has_query_builder_parameters
|
|
14
|
+
|
|
15
|
+
__all__ = (
|
|
16
|
+
"DeleteFromClauseMixin",
|
|
17
|
+
"InsertFromSelectMixin",
|
|
18
|
+
"InsertIntoClauseMixin",
|
|
19
|
+
"InsertValuesMixin",
|
|
20
|
+
"UpdateFromClauseMixin",
|
|
21
|
+
"UpdateSetClauseMixin",
|
|
22
|
+
"UpdateTableClauseMixin",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
ARG_PAIR_COUNT = 2
|
|
26
|
+
SINGLE_VALUE_COUNT = 1
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# DELETE helpers
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@trait
|
|
35
|
+
class DeleteFromClauseMixin:
|
|
36
|
+
"""Mixin providing FROM clause support for DELETE builders."""
|
|
37
|
+
|
|
38
|
+
__slots__ = ()
|
|
39
|
+
|
|
40
|
+
def get_expression(self) -> exp.Expression | None: ...
|
|
41
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
42
|
+
|
|
43
|
+
def from_(self, table: str) -> Self:
|
|
44
|
+
current_expr = self.get_expression()
|
|
45
|
+
if current_expr is None:
|
|
46
|
+
self.set_expression(exp.Delete())
|
|
47
|
+
current_expr = self.get_expression()
|
|
48
|
+
|
|
49
|
+
if not isinstance(current_expr, exp.Delete):
|
|
50
|
+
msg = f"Base expression for Delete is {type(current_expr).__name__}, expected Delete."
|
|
51
|
+
raise SQLBuilderError(msg)
|
|
52
|
+
|
|
53
|
+
assert current_expr is not None
|
|
54
|
+
setattr(self, "_table", table)
|
|
55
|
+
current_expr.set("this", exp.to_table(table))
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# INSERT helpers
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@trait
|
|
65
|
+
class InsertIntoClauseMixin:
|
|
66
|
+
__slots__ = ()
|
|
67
|
+
|
|
68
|
+
def get_expression(self) -> exp.Expression | None: ...
|
|
69
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
70
|
+
|
|
71
|
+
def into(self, table: str) -> Self:
|
|
72
|
+
current_expr = self.get_expression()
|
|
73
|
+
if current_expr is None:
|
|
74
|
+
self.set_expression(exp.Insert())
|
|
75
|
+
current_expr = self.get_expression()
|
|
76
|
+
|
|
77
|
+
if not isinstance(current_expr, exp.Insert):
|
|
78
|
+
msg = "Cannot set target table on a non-INSERT expression."
|
|
79
|
+
raise SQLBuilderError(msg)
|
|
80
|
+
|
|
81
|
+
assert current_expr is not None
|
|
82
|
+
setattr(self, "_table", table)
|
|
83
|
+
current_expr.set("this", exp.to_table(table))
|
|
84
|
+
return self
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@trait
|
|
88
|
+
class InsertValuesMixin:
|
|
89
|
+
__slots__ = ()
|
|
90
|
+
|
|
91
|
+
def get_expression(self) -> exp.Expression | None: ...
|
|
92
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
93
|
+
|
|
94
|
+
_columns: Any
|
|
95
|
+
|
|
96
|
+
def add_parameter(self, value: Any, name: str | None = None) -> tuple[Any, str]:
|
|
97
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
98
|
+
raise NotImplementedError(msg)
|
|
99
|
+
|
|
100
|
+
def _generate_unique_parameter_name(self, base_name: str) -> str:
|
|
101
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
102
|
+
raise NotImplementedError(msg)
|
|
103
|
+
|
|
104
|
+
def columns(self, *columns: str | exp.Expression) -> Self:
|
|
105
|
+
current_expr = self.get_expression()
|
|
106
|
+
if current_expr is None:
|
|
107
|
+
self.set_expression(exp.Insert())
|
|
108
|
+
current_expr = self.get_expression()
|
|
109
|
+
|
|
110
|
+
if not isinstance(current_expr, exp.Insert):
|
|
111
|
+
msg = "Cannot set columns on a non-INSERT expression."
|
|
112
|
+
raise SQLBuilderError(msg)
|
|
113
|
+
|
|
114
|
+
assert current_expr is not None
|
|
115
|
+
current_this = current_expr.args.get("this")
|
|
116
|
+
if current_this is None:
|
|
117
|
+
msg = "Table must be set using .into() before setting columns."
|
|
118
|
+
raise SQLBuilderError(msg)
|
|
119
|
+
|
|
120
|
+
if columns:
|
|
121
|
+
identifiers = [exp.to_identifier(col) if isinstance(col, str) else col for col in columns]
|
|
122
|
+
table_name = current_this.this
|
|
123
|
+
current_expr.set("this", exp.Schema(this=table_name, expressions=identifiers))
|
|
124
|
+
elif isinstance(current_this, exp.Schema):
|
|
125
|
+
table_name = current_this.this
|
|
126
|
+
current_expr.set("this", exp.Table(this=table_name))
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
cols = self._columns
|
|
130
|
+
if not columns:
|
|
131
|
+
cols.clear()
|
|
132
|
+
else:
|
|
133
|
+
cols[:] = [col if isinstance(col, str) else str(col) for col in columns]
|
|
134
|
+
except AttributeError:
|
|
135
|
+
pass
|
|
136
|
+
return self
|
|
137
|
+
|
|
138
|
+
def values(self, *values: Any, **kwargs: Any) -> Self:
|
|
139
|
+
current_expr = self.get_expression()
|
|
140
|
+
if current_expr is None:
|
|
141
|
+
self.set_expression(exp.Insert())
|
|
142
|
+
current_expr = self.get_expression()
|
|
143
|
+
|
|
144
|
+
if not isinstance(current_expr, exp.Insert):
|
|
145
|
+
msg = "Cannot add values to a non-INSERT expression."
|
|
146
|
+
raise SQLBuilderError(msg)
|
|
147
|
+
|
|
148
|
+
assert current_expr is not None
|
|
149
|
+
table_name = cast("str | None", self._table) # type: ignore[attr-defined]
|
|
150
|
+
if not table_name:
|
|
151
|
+
msg = "The target table must be set using .into() before adding values."
|
|
152
|
+
raise SQLBuilderError(msg)
|
|
153
|
+
|
|
154
|
+
positional_values = list(values)
|
|
155
|
+
if len(positional_values) == SINGLE_VALUE_COUNT and hasattr(positional_values[0], "items") and not kwargs:
|
|
156
|
+
kwargs = cast("dict[str, Any]", positional_values[0])
|
|
157
|
+
positional_values = []
|
|
158
|
+
|
|
159
|
+
if kwargs and positional_values:
|
|
160
|
+
msg = "Cannot mix positional values with keyword values."
|
|
161
|
+
raise SQLBuilderError(msg)
|
|
162
|
+
|
|
163
|
+
row_expressions: list[exp.Expression] = []
|
|
164
|
+
column_defs = cast("list[str]", self._columns or [])
|
|
165
|
+
|
|
166
|
+
if kwargs:
|
|
167
|
+
if not column_defs:
|
|
168
|
+
self.columns(*kwargs.keys())
|
|
169
|
+
column_defs = cast("list[str]", self._columns)
|
|
170
|
+
for col, val in kwargs.items():
|
|
171
|
+
if isinstance(val, exp.Expression):
|
|
172
|
+
row_expressions.append(val)
|
|
173
|
+
continue
|
|
174
|
+
if has_expression_and_sql(val):
|
|
175
|
+
row_expressions.append(extract_sql_object_expression(val, builder=self))
|
|
176
|
+
continue
|
|
177
|
+
column_name = str(col).split(".")[-1]
|
|
178
|
+
unique_name = self._generate_unique_parameter_name(column_name)
|
|
179
|
+
_, unique_name = self.add_parameter(val, name=unique_name)
|
|
180
|
+
row_expressions.append(exp.Placeholder(this=unique_name))
|
|
181
|
+
else:
|
|
182
|
+
if column_defs and len(positional_values) != len(column_defs):
|
|
183
|
+
msg = (
|
|
184
|
+
f"Number of values ({len(positional_values)}) does not match the number of specified columns "
|
|
185
|
+
f"({len(column_defs)})."
|
|
186
|
+
)
|
|
187
|
+
raise SQLBuilderError(msg)
|
|
188
|
+
|
|
189
|
+
for index, raw_value in enumerate(positional_values):
|
|
190
|
+
if isinstance(raw_value, exp.Expression):
|
|
191
|
+
row_expressions.append(raw_value)
|
|
192
|
+
elif has_expression_and_sql(raw_value):
|
|
193
|
+
row_expressions.append(extract_sql_object_expression(raw_value, builder=self))
|
|
194
|
+
else:
|
|
195
|
+
if column_defs and index < len(column_defs):
|
|
196
|
+
column_token = column_defs[index]
|
|
197
|
+
column_name = column_token.rsplit(".", maxsplit=1)[-1]
|
|
198
|
+
else:
|
|
199
|
+
column_name = f"value_{index + 1}"
|
|
200
|
+
unique_name = self._generate_unique_parameter_name(column_name)
|
|
201
|
+
_, unique_name = self.add_parameter(raw_value, name=unique_name)
|
|
202
|
+
row_expressions.append(exp.Placeholder(this=unique_name))
|
|
203
|
+
|
|
204
|
+
values_node = current_expr.args.get("expression")
|
|
205
|
+
tuple_expression = exp.Tuple(expressions=row_expressions)
|
|
206
|
+
if isinstance(values_node, exp.Values):
|
|
207
|
+
values_node.expressions.append(tuple_expression)
|
|
208
|
+
else:
|
|
209
|
+
current_expr.set("expression", exp.Values(expressions=[tuple_expression]))
|
|
210
|
+
return self
|
|
211
|
+
|
|
212
|
+
def add_values(self, values: Sequence[Any]) -> Self:
|
|
213
|
+
return self.values(*values)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@trait
|
|
217
|
+
class InsertFromSelectMixin:
|
|
218
|
+
__slots__ = ()
|
|
219
|
+
|
|
220
|
+
def get_expression(self) -> exp.Expression | None: ...
|
|
221
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
222
|
+
|
|
223
|
+
_table: Any
|
|
224
|
+
|
|
225
|
+
def add_parameter(self, value: Any, name: str | None = None) -> tuple[Any, str]:
|
|
226
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
227
|
+
raise NotImplementedError(msg)
|
|
228
|
+
|
|
229
|
+
def from_select(self, select_builder: SQLBuilderProtocol) -> Self:
|
|
230
|
+
if not getattr(self, "_table", None):
|
|
231
|
+
msg = "The target table must be set using .into() before adding values."
|
|
232
|
+
raise SQLBuilderError(msg)
|
|
233
|
+
|
|
234
|
+
current_expr = self.get_expression()
|
|
235
|
+
if current_expr is None:
|
|
236
|
+
self.set_expression(exp.Insert())
|
|
237
|
+
current_expr = self.get_expression()
|
|
238
|
+
|
|
239
|
+
if not isinstance(current_expr, exp.Insert):
|
|
240
|
+
msg = "Cannot set INSERT source on a non-INSERT expression."
|
|
241
|
+
raise SQLBuilderError(msg)
|
|
242
|
+
|
|
243
|
+
assert current_expr is not None
|
|
244
|
+
subquery_parameters = getattr(select_builder, "_parameters", None)
|
|
245
|
+
if isinstance(subquery_parameters, dict):
|
|
246
|
+
builder_with_params = cast("SQLBuilderProtocol", self)
|
|
247
|
+
for param_name, param_value in subquery_parameters.items():
|
|
248
|
+
builder_with_params.add_parameter(param_value, name=param_name)
|
|
249
|
+
|
|
250
|
+
select_expr = getattr(select_builder, "_expression", None)
|
|
251
|
+
if select_expr and isinstance(select_expr, exp.Select):
|
|
252
|
+
current_expr.set("expression", select_expr.copy())
|
|
253
|
+
else:
|
|
254
|
+
msg = "SelectBuilder must have a valid SELECT expression."
|
|
255
|
+
raise SQLBuilderError(msg)
|
|
256
|
+
return self
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# ---------------------------------------------------------------------------
|
|
260
|
+
# UPDATE helpers
|
|
261
|
+
# ---------------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@trait
|
|
265
|
+
class UpdateTableClauseMixin:
|
|
266
|
+
__slots__ = ()
|
|
267
|
+
|
|
268
|
+
def get_expression(self) -> exp.Expression | None: ...
|
|
269
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
270
|
+
|
|
271
|
+
def table(self, table_name: str, alias: str | None = None) -> Self:
|
|
272
|
+
current_expr = self.get_expression()
|
|
273
|
+
if current_expr is None or not isinstance(current_expr, exp.Update):
|
|
274
|
+
self.set_expression(exp.Update(this=None, expressions=[], joins=[]))
|
|
275
|
+
current_expr = self.get_expression()
|
|
276
|
+
|
|
277
|
+
assert current_expr is not None
|
|
278
|
+
|
|
279
|
+
table_expr: exp.Expression = exp.to_table(table_name, alias=alias)
|
|
280
|
+
current_expr.set("this", table_expr)
|
|
281
|
+
setattr(self, "_table", table_name)
|
|
282
|
+
return self
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@trait
|
|
286
|
+
class UpdateSetClauseMixin:
|
|
287
|
+
__slots__ = ()
|
|
288
|
+
|
|
289
|
+
def get_expression(self) -> exp.Expression | None: ...
|
|
290
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
291
|
+
|
|
292
|
+
def add_parameter(self, value: Any, name: str | None = None) -> tuple[Any, str]:
|
|
293
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
294
|
+
raise NotImplementedError(msg)
|
|
295
|
+
|
|
296
|
+
def _generate_unique_parameter_name(self, base_name: str) -> str:
|
|
297
|
+
msg = "Method must be provided by QueryBuilder subclass"
|
|
298
|
+
raise NotImplementedError(msg)
|
|
299
|
+
|
|
300
|
+
def _process_update_value(self, val: Any, col: Any) -> exp.Expression:
|
|
301
|
+
if isinstance(val, exp.Expression):
|
|
302
|
+
return val
|
|
303
|
+
if has_query_builder_parameters(val):
|
|
304
|
+
subquery = val.build()
|
|
305
|
+
sql_text = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
|
|
306
|
+
value_expr = exp.paren(exp.maybe_parse(sql_text, dialect=getattr(self, "dialect", None)))
|
|
307
|
+
for p_name, p_value in getattr(val, "parameters", {}).items():
|
|
308
|
+
self.add_parameter(p_value, name=p_name)
|
|
309
|
+
return value_expr
|
|
310
|
+
if hasattr(val, "expression") and hasattr(val, "sql"):
|
|
311
|
+
return extract_sql_object_expression(val, builder=self)
|
|
312
|
+
column_name = col if isinstance(col, str) else str(col)
|
|
313
|
+
if "." in column_name:
|
|
314
|
+
column_name = column_name.split(".")[-1]
|
|
315
|
+
param_name = self._generate_unique_parameter_name(column_name)
|
|
316
|
+
param_name = self.add_parameter(val, name=param_name)[1]
|
|
317
|
+
return exp.Placeholder(this=param_name)
|
|
318
|
+
|
|
319
|
+
def set(self, *args: Any, **kwargs: Any) -> Self:
|
|
320
|
+
current_expr = self.get_expression()
|
|
321
|
+
if current_expr is None:
|
|
322
|
+
self.set_expression(exp.Update())
|
|
323
|
+
current_expr = self.get_expression()
|
|
324
|
+
|
|
325
|
+
if not isinstance(current_expr, exp.Update):
|
|
326
|
+
msg = "Cannot add SET clause to non-UPDATE expression."
|
|
327
|
+
raise SQLBuilderError(msg)
|
|
328
|
+
|
|
329
|
+
assert current_expr is not None
|
|
330
|
+
|
|
331
|
+
assignments: list[exp.Expression] = []
|
|
332
|
+
if len(args) == ARG_PAIR_COUNT and not kwargs:
|
|
333
|
+
col, val = args
|
|
334
|
+
col_expr = col if isinstance(col, exp.Column) else exp.column(col)
|
|
335
|
+
assignments.append(exp.EQ(this=col_expr, expression=self._process_update_value(val, col)))
|
|
336
|
+
elif (len(args) == SINGLE_VALUE_COUNT and isinstance(args[0], Mapping)) or kwargs:
|
|
337
|
+
all_values = dict(args[0] if args else {}, **kwargs)
|
|
338
|
+
for col, val in all_values.items():
|
|
339
|
+
assignments.append(exp.EQ(this=exp.column(col), expression=self._process_update_value(val, col)))
|
|
340
|
+
else:
|
|
341
|
+
msg = "Invalid arguments for set(): use (column, value), mapping, or kwargs."
|
|
342
|
+
raise SQLBuilderError(msg)
|
|
343
|
+
|
|
344
|
+
existing = current_expr.args.get("expressions", [])
|
|
345
|
+
current_expr.set("expressions", existing + assignments)
|
|
346
|
+
return self
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
@trait
|
|
350
|
+
class UpdateFromClauseMixin:
|
|
351
|
+
__slots__ = ()
|
|
352
|
+
|
|
353
|
+
def get_expression(self) -> exp.Expression | None: ...
|
|
354
|
+
def set_expression(self, expression: exp.Expression) -> None: ...
|
|
355
|
+
|
|
356
|
+
def from_(self, table: str | exp.Expression | Any, alias: str | None = None) -> Self:
|
|
357
|
+
current_expr = self.get_expression()
|
|
358
|
+
if current_expr is None or not isinstance(current_expr, exp.Update):
|
|
359
|
+
msg = "Cannot add FROM clause to non-UPDATE expression. Set the main table first."
|
|
360
|
+
raise SQLBuilderError(msg)
|
|
361
|
+
|
|
362
|
+
assert current_expr is not None
|
|
363
|
+
table_expr: exp.Expression
|
|
364
|
+
if isinstance(table, str):
|
|
365
|
+
table_expr = exp.to_table(table, alias=alias)
|
|
366
|
+
elif has_query_builder_parameters(table):
|
|
367
|
+
subquery_params = getattr(table, "_parameters", None)
|
|
368
|
+
if isinstance(subquery_params, dict):
|
|
369
|
+
builder_with_params = cast("SQLBuilderProtocol", self)
|
|
370
|
+
for param_name, param_value in subquery_params.items():
|
|
371
|
+
builder_with_params.add_parameter(param_value, name=param_name)
|
|
372
|
+
raw_expression = getattr(table, "_expression", None)
|
|
373
|
+
subquery_source = raw_expression if isinstance(raw_expression, exp.Expression) else exp.select()
|
|
374
|
+
subquery_exp = exp.paren(subquery_source)
|
|
375
|
+
table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
|
|
376
|
+
elif isinstance(table, exp.Expression):
|
|
377
|
+
table_expr = exp.alias_(table, alias) if alias else table
|
|
378
|
+
else:
|
|
379
|
+
msg = f"Unsupported table type for FROM clause: {type(table)}"
|
|
380
|
+
raise SQLBuilderError(msg)
|
|
381
|
+
|
|
382
|
+
from_clause = current_expr.args.get("from")
|
|
383
|
+
if from_clause is None:
|
|
384
|
+
from_clause = exp.From(expressions=[])
|
|
385
|
+
current_expr.set("from", from_clause)
|
|
386
|
+
|
|
387
|
+
from_clause.append("expressions", table_expr)
|
|
388
|
+
return self
|