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.
- sqlspec/__init__.py +71 -8
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +1 -3
- sqlspec/_serialization.py +1 -2
- sqlspec/_sql.py +930 -136
- sqlspec/_typing.py +278 -142
- sqlspec/adapters/adbc/__init__.py +4 -3
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +116 -285
- sqlspec/adapters/adbc/driver.py +462 -340
- sqlspec/adapters/aiosqlite/__init__.py +18 -3
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +202 -150
- sqlspec/adapters/aiosqlite/driver.py +226 -247
- sqlspec/adapters/asyncmy/__init__.py +18 -3
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +80 -199
- sqlspec/adapters/asyncmy/driver.py +257 -215
- sqlspec/adapters/asyncpg/__init__.py +19 -4
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +81 -214
- sqlspec/adapters/asyncpg/driver.py +284 -359
- sqlspec/adapters/bigquery/__init__.py +17 -3
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +191 -299
- sqlspec/adapters/bigquery/driver.py +474 -634
- sqlspec/adapters/duckdb/__init__.py +14 -3
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +414 -397
- sqlspec/adapters/duckdb/driver.py +342 -393
- sqlspec/adapters/oracledb/__init__.py +19 -5
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +123 -458
- sqlspec/adapters/oracledb/driver.py +505 -531
- sqlspec/adapters/psqlpy/__init__.py +13 -3
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +93 -307
- sqlspec/adapters/psqlpy/driver.py +504 -213
- sqlspec/adapters/psycopg/__init__.py +19 -5
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +143 -472
- sqlspec/adapters/psycopg/driver.py +704 -825
- sqlspec/adapters/sqlite/__init__.py +14 -3
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +208 -142
- sqlspec/adapters/sqlite/driver.py +263 -278
- sqlspec/base.py +105 -9
- sqlspec/{statement/builder → builder}/__init__.py +12 -14
- sqlspec/{statement/builder/base.py → builder/_base.py} +184 -86
- sqlspec/{statement/builder/column.py → builder/_column.py} +97 -60
- sqlspec/{statement/builder/ddl.py → builder/_ddl.py} +61 -131
- sqlspec/{statement/builder → builder}/_ddl_utils.py +4 -10
- sqlspec/{statement/builder/delete.py → builder/_delete.py} +10 -30
- sqlspec/builder/_insert.py +421 -0
- sqlspec/builder/_merge.py +71 -0
- sqlspec/{statement/builder → builder}/_parsing_utils.py +49 -26
- sqlspec/builder/_select.py +170 -0
- sqlspec/{statement/builder/update.py → builder/_update.py} +16 -20
- sqlspec/builder/mixins/__init__.py +55 -0
- sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
- sqlspec/{statement/builder/mixins/_delete_from.py → builder/mixins/_delete_operations.py} +8 -1
- sqlspec/builder/mixins/_insert_operations.py +244 -0
- sqlspec/{statement/builder/mixins/_join.py → builder/mixins/_join_operations.py} +45 -13
- sqlspec/{statement/builder/mixins/_merge_clauses.py → builder/mixins/_merge_operations.py} +188 -30
- sqlspec/builder/mixins/_order_limit_operations.py +135 -0
- sqlspec/builder/mixins/_pivot_operations.py +153 -0
- sqlspec/builder/mixins/_select_operations.py +604 -0
- sqlspec/builder/mixins/_update_operations.py +202 -0
- sqlspec/builder/mixins/_where_clause.py +644 -0
- sqlspec/cli.py +247 -0
- sqlspec/config.py +183 -138
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.py +871 -0
- sqlspec/core/compiler.py +417 -0
- sqlspec/core/filters.py +830 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.py +1237 -0
- sqlspec/core/result.py +677 -0
- sqlspec/{statement → core}/splitter.py +321 -191
- sqlspec/core/statement.py +676 -0
- sqlspec/driver/__init__.py +7 -10
- sqlspec/driver/_async.py +422 -163
- sqlspec/driver/_common.py +545 -287
- sqlspec/driver/_sync.py +426 -160
- sqlspec/driver/mixins/__init__.py +2 -13
- sqlspec/driver/mixins/_result_tools.py +193 -0
- sqlspec/driver/mixins/_sql_translator.py +65 -14
- sqlspec/exceptions.py +5 -252
- sqlspec/extensions/aiosql/adapter.py +93 -96
- sqlspec/extensions/litestar/__init__.py +2 -1
- sqlspec/extensions/litestar/cli.py +48 -0
- sqlspec/extensions/litestar/config.py +0 -1
- sqlspec/extensions/litestar/handlers.py +15 -26
- sqlspec/extensions/litestar/plugin.py +21 -16
- sqlspec/extensions/litestar/providers.py +17 -52
- sqlspec/loader.py +423 -104
- sqlspec/migrations/__init__.py +35 -0
- sqlspec/migrations/base.py +414 -0
- sqlspec/migrations/commands.py +443 -0
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +213 -0
- sqlspec/migrations/tracker.py +140 -0
- sqlspec/migrations/utils.py +129 -0
- sqlspec/protocols.py +51 -186
- 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 -2
- 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 +482 -235
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/METADATA +7 -2
- sqlspec-0.16.2.dist-info/RECORD +134 -0
- sqlspec-0.16.2.dist-info/entry_points.txt +2 -0
- sqlspec/driver/connection.py +0 -207
- sqlspec/driver/mixins/_csv_writer.py +0 -91
- sqlspec/driver/mixins/_pipeline.py +0 -512
- sqlspec/driver/mixins/_result_utils.py +0 -140
- sqlspec/driver/mixins/_storage.py +0 -926
- sqlspec/driver/mixins/_type_coercion.py +0 -130
- sqlspec/driver/parameters.py +0 -138
- sqlspec/service/__init__.py +0 -4
- sqlspec/service/_util.py +0 -147
- sqlspec/service/base.py +0 -1131
- sqlspec/service/pagination.py +0 -26
- sqlspec/statement/__init__.py +0 -21
- sqlspec/statement/builder/insert.py +0 -288
- sqlspec/statement/builder/merge.py +0 -95
- sqlspec/statement/builder/mixins/__init__.py +0 -65
- sqlspec/statement/builder/mixins/_aggregate_functions.py +0 -250
- sqlspec/statement/builder/mixins/_case_builder.py +0 -91
- sqlspec/statement/builder/mixins/_common_table_expr.py +0 -90
- sqlspec/statement/builder/mixins/_from.py +0 -63
- sqlspec/statement/builder/mixins/_group_by.py +0 -118
- sqlspec/statement/builder/mixins/_having.py +0 -35
- sqlspec/statement/builder/mixins/_insert_from_select.py +0 -47
- sqlspec/statement/builder/mixins/_insert_into.py +0 -36
- sqlspec/statement/builder/mixins/_insert_values.py +0 -67
- sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
- sqlspec/statement/builder/mixins/_order_by.py +0 -46
- sqlspec/statement/builder/mixins/_pivot.py +0 -79
- sqlspec/statement/builder/mixins/_returning.py +0 -37
- sqlspec/statement/builder/mixins/_select_columns.py +0 -61
- sqlspec/statement/builder/mixins/_set_ops.py +0 -122
- sqlspec/statement/builder/mixins/_unpivot.py +0 -77
- sqlspec/statement/builder/mixins/_update_from.py +0 -55
- sqlspec/statement/builder/mixins/_update_set.py +0 -94
- sqlspec/statement/builder/mixins/_update_table.py +0 -29
- sqlspec/statement/builder/mixins/_where.py +0 -401
- sqlspec/statement/builder/mixins/_window_functions.py +0 -86
- sqlspec/statement/builder/select.py +0 -221
- sqlspec/statement/filters.py +0 -596
- sqlspec/statement/parameter_manager.py +0 -220
- sqlspec/statement/parameters.py +0 -867
- 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 -115
- 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 -718
- sqlspec/statement/pipelines/validators/_security.py +0 -967
- sqlspec/statement/result.py +0 -435
- sqlspec/statement/sql.py +0 -1704
- sqlspec/statement/sql_compiler.py +0 -140
- sqlspec/utils/cached_property.py +0 -25
- sqlspec-0.13.1.dist-info/RECORD +0 -150
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/WHEEL +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/NOTICE +0 -0
sqlspec/driver/_sync.py
CHANGED
|
@@ -1,237 +1,503 @@
|
|
|
1
|
-
"""Synchronous driver protocol implementation.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
from sqlspec.
|
|
11
|
-
from sqlspec.
|
|
12
|
-
from sqlspec.
|
|
1
|
+
"""Synchronous driver protocol implementation.
|
|
2
|
+
|
|
3
|
+
This module provides the sync driver infrastructure for database adapters,
|
|
4
|
+
including connection management, transaction support, and result processing.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import abstractmethod
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Final, NoReturn, Optional, Union, cast, overload
|
|
9
|
+
|
|
10
|
+
from sqlspec.core import SQL
|
|
11
|
+
from sqlspec.driver._common import CommonDriverAttributesMixin, ExecutionResult
|
|
12
|
+
from sqlspec.driver.mixins import SQLTranslatorMixin, ToSchemaMixin
|
|
13
|
+
from sqlspec.exceptions import NotFoundError
|
|
13
14
|
from sqlspec.utils.logging import get_logger
|
|
14
|
-
from sqlspec.utils.type_guards import
|
|
15
|
+
from sqlspec.utils.type_guards import is_dict_row, is_indexable_row
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
17
|
-
from
|
|
18
|
+
from collections.abc import Sequence
|
|
19
|
+
from contextlib import AbstractContextManager
|
|
20
|
+
|
|
21
|
+
from sqlspec.builder import QueryBuilder
|
|
22
|
+
from sqlspec.core import SQLResult, Statement, StatementConfig, StatementFilter
|
|
23
|
+
from sqlspec.typing import ModelDTOT, StatementParameters
|
|
24
|
+
|
|
25
|
+
_LOGGER_NAME: Final[str] = "sqlspec"
|
|
26
|
+
logger = get_logger(_LOGGER_NAME)
|
|
18
27
|
|
|
19
|
-
|
|
28
|
+
__all__ = ("SyncDriverAdapterBase",)
|
|
20
29
|
|
|
21
|
-
__all__ = ("SyncDriverAdapterProtocol",)
|
|
22
30
|
|
|
31
|
+
EMPTY_FILTERS: Final["list[StatementFilter]"] = []
|
|
23
32
|
|
|
24
|
-
EMPTY_FILTERS: "list[StatementFilter]" = []
|
|
25
33
|
|
|
34
|
+
class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToSchemaMixin):
|
|
35
|
+
"""Base class for synchronous database drivers.
|
|
36
|
+
|
|
37
|
+
Provides the foundation for sync database adapters, including connection management,
|
|
38
|
+
transaction support, and SQL execution methods. All database operations are performed
|
|
39
|
+
synchronously and support context manager patterns for proper resource cleanup.
|
|
40
|
+
"""
|
|
26
41
|
|
|
27
|
-
class SyncDriverAdapterProtocol(CommonDriverAttributesMixin[ConnectionT, RowT], ABC):
|
|
28
42
|
__slots__ = ()
|
|
29
43
|
|
|
30
|
-
def
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
44
|
+
def dispatch_statement_execution(self, statement: "SQL", connection: "Any") -> "SQLResult":
|
|
45
|
+
"""Central execution dispatcher using the Template Method Pattern.
|
|
46
|
+
|
|
47
|
+
Orchestrates the common execution flow, delegating database-specific steps
|
|
48
|
+
to abstract methods that concrete adapters must implement.
|
|
49
|
+
All database operations are wrapped in exception handling.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
statement: The SQL statement to execute
|
|
53
|
+
connection: The database connection to use
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
The result of the SQL execution
|
|
57
|
+
"""
|
|
58
|
+
with self.handle_database_exceptions(), self.with_cursor(connection) as cursor:
|
|
59
|
+
special_result = self._try_special_handling(cursor, statement)
|
|
60
|
+
if special_result is not None:
|
|
61
|
+
return special_result
|
|
62
|
+
|
|
63
|
+
if statement.is_script:
|
|
64
|
+
execution_result = self._execute_script(cursor, statement)
|
|
65
|
+
elif statement.is_many:
|
|
66
|
+
execution_result = self._execute_many(cursor, statement)
|
|
67
|
+
else:
|
|
68
|
+
execution_result = self._execute_statement(cursor, statement)
|
|
69
|
+
|
|
70
|
+
return self.build_statement_result(statement, execution_result)
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def with_cursor(self, connection: Any) -> Any:
|
|
74
|
+
"""Create and return a context manager for cursor acquisition and cleanup.
|
|
75
|
+
|
|
76
|
+
Returns a context manager that yields a cursor for database operations.
|
|
77
|
+
Concrete implementations handle database-specific cursor creation and cleanup.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def handle_database_exceptions(self) -> "AbstractContextManager[None]":
|
|
82
|
+
"""Handle database-specific exceptions and wrap them appropriately.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
ContextManager that can be used in with statements
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def begin(self) -> None:
|
|
90
|
+
"""Begin a database transaction on the current connection."""
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
def rollback(self) -> None:
|
|
94
|
+
"""Rollback the current transaction on the current connection."""
|
|
95
|
+
|
|
96
|
+
@abstractmethod
|
|
97
|
+
def commit(self) -> None:
|
|
98
|
+
"""Commit the current transaction on the current connection."""
|
|
99
|
+
|
|
100
|
+
@abstractmethod
|
|
101
|
+
def _try_special_handling(self, cursor: Any, statement: "SQL") -> "Optional[SQLResult]":
|
|
102
|
+
"""Hook for database-specific special operations (e.g., PostgreSQL COPY, bulk operations).
|
|
103
|
+
|
|
104
|
+
This method is called first in dispatch_statement_execution() to allow drivers to handle
|
|
105
|
+
special operations that don't follow the standard SQL execution pattern.
|
|
37
106
|
|
|
38
107
|
Args:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
108
|
+
cursor: Database cursor/connection object
|
|
109
|
+
statement: SQL statement to analyze
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
SQLResult if the special operation was handled and completed,
|
|
113
|
+
None if standard execution should proceed
|
|
42
114
|
"""
|
|
43
|
-
super().__init__(connection=connection, config=config, default_row_type=default_row_type)
|
|
44
115
|
|
|
45
|
-
def
|
|
116
|
+
def _execute_script(self, cursor: Any, statement: "SQL") -> ExecutionResult:
|
|
117
|
+
"""Execute a SQL script containing multiple statements.
|
|
118
|
+
|
|
119
|
+
Default implementation splits the script and executes statements individually.
|
|
120
|
+
Drivers can override for database-specific script execution methods.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
cursor: Database cursor/connection object
|
|
124
|
+
statement: SQL statement object with all necessary data and configuration
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
ExecutionResult with script execution data including statement counts
|
|
128
|
+
"""
|
|
129
|
+
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
130
|
+
statements = self.split_script_statements(sql, self.statement_config, strip_trailing_semicolon=True)
|
|
131
|
+
|
|
132
|
+
statement_count: int = len(statements)
|
|
133
|
+
successful_count: int = 0
|
|
134
|
+
|
|
135
|
+
for stmt in statements:
|
|
136
|
+
single_stmt = statement.copy(statement=stmt, parameters=prepared_parameters)
|
|
137
|
+
self._execute_statement(cursor, single_stmt)
|
|
138
|
+
successful_count += 1
|
|
139
|
+
|
|
140
|
+
return self.create_execution_result(
|
|
141
|
+
cursor, statement_count=statement_count, successful_statements=successful_count, is_script_result=True
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
@abstractmethod
|
|
145
|
+
def _execute_many(self, cursor: Any, statement: "SQL") -> ExecutionResult:
|
|
146
|
+
"""Execute SQL with multiple parameter sets (executemany).
|
|
147
|
+
|
|
148
|
+
Must be implemented by each driver for database-specific executemany logic.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
cursor: Database cursor/connection object
|
|
152
|
+
statement: SQL statement object with all necessary data and configuration
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
ExecutionResult with execution data for the many operation
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
@abstractmethod
|
|
159
|
+
def _execute_statement(self, cursor: Any, statement: "SQL") -> ExecutionResult:
|
|
160
|
+
"""Execute a single SQL statement.
|
|
161
|
+
|
|
162
|
+
Must be implemented by each driver for database-specific execution logic.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
cursor: Database cursor/connection object
|
|
166
|
+
statement: SQL statement object with all necessary data and configuration
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
ExecutionResult with execution data
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
def execute(
|
|
46
173
|
self,
|
|
47
|
-
statement: "Union[Statement, QueryBuilder
|
|
174
|
+
statement: "Union[SQL, Statement, QueryBuilder]",
|
|
175
|
+
/,
|
|
48
176
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
49
|
-
|
|
177
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
50
178
|
**kwargs: Any,
|
|
51
|
-
) -> "
|
|
52
|
-
|
|
53
|
-
|
|
179
|
+
) -> "SQLResult":
|
|
180
|
+
"""Execute a statement with parameter handling."""
|
|
181
|
+
sql_statement = self.prepare_statement(
|
|
182
|
+
statement, parameters, statement_config=statement_config or self.statement_config, kwargs=kwargs
|
|
183
|
+
)
|
|
184
|
+
return self.dispatch_statement_execution(statement=sql_statement, connection=self.connection)
|
|
185
|
+
|
|
186
|
+
def execute_many(
|
|
187
|
+
self,
|
|
188
|
+
statement: "Union[SQL, Statement, QueryBuilder]",
|
|
189
|
+
/,
|
|
190
|
+
parameters: "Sequence[StatementParameters]",
|
|
191
|
+
*filters: "Union[StatementParameters, StatementFilter]",
|
|
192
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
193
|
+
**kwargs: Any,
|
|
194
|
+
) -> "SQLResult":
|
|
195
|
+
"""Execute statement multiple times with different parameters.
|
|
196
|
+
|
|
197
|
+
Parameters passed will be used as the batch execution sequence.
|
|
198
|
+
"""
|
|
199
|
+
config = statement_config or self.statement_config
|
|
54
200
|
|
|
55
|
-
if isinstance(statement, QueryBuilder):
|
|
56
|
-
return statement.to_statement(config=_config)
|
|
57
|
-
# If statement is already a SQL object, handle additional parameters
|
|
58
201
|
if isinstance(statement, SQL):
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
# Use raw SQL if available to ensure proper parsing with dialect
|
|
64
|
-
sql_source = statement._raw_sql or statement._statement
|
|
65
|
-
# Preserve filters and state when creating new SQL object
|
|
66
|
-
existing_state = {
|
|
67
|
-
"is_many": statement._is_many,
|
|
68
|
-
"is_script": statement._is_script,
|
|
69
|
-
"original_parameters": statement._original_parameters,
|
|
70
|
-
"filters": statement._filters,
|
|
71
|
-
"positional_params": statement._positional_params,
|
|
72
|
-
"named_params": statement._named_params,
|
|
73
|
-
}
|
|
74
|
-
return SQL(sql_source, *parameters, config=new_config, _existing_state=existing_state, **kwargs)
|
|
75
|
-
# Even without additional parameters, ensure dialect is set
|
|
76
|
-
if self.dialect and (not statement._config.dialect or statement._config.dialect != self.dialect):
|
|
77
|
-
new_config = replace(statement._config, dialect=self.dialect)
|
|
78
|
-
# Use raw SQL if available to ensure proper parsing with dialect
|
|
79
|
-
sql_source = statement._raw_sql or statement._statement
|
|
80
|
-
# Preserve parameters and state when creating new SQL object
|
|
81
|
-
# Use the public parameters property which always has the right value
|
|
82
|
-
existing_state = {
|
|
83
|
-
"is_many": statement._is_many,
|
|
84
|
-
"is_script": statement._is_script,
|
|
85
|
-
"original_parameters": statement._original_parameters,
|
|
86
|
-
"filters": statement._filters,
|
|
87
|
-
"positional_params": statement._positional_params,
|
|
88
|
-
"named_params": statement._named_params,
|
|
89
|
-
}
|
|
90
|
-
if statement.parameters:
|
|
91
|
-
return SQL(
|
|
92
|
-
sql_source, parameters=statement.parameters, config=new_config, _existing_state=existing_state
|
|
93
|
-
)
|
|
94
|
-
return SQL(sql_source, config=new_config, _existing_state=existing_state)
|
|
95
|
-
return statement
|
|
96
|
-
new_config = _config
|
|
97
|
-
if self.dialect and not new_config.dialect:
|
|
98
|
-
new_config = replace(new_config, dialect=self.dialect)
|
|
99
|
-
return SQL(statement, *parameters, config=new_config, **kwargs)
|
|
202
|
+
sql_statement = SQL(statement._raw_sql, parameters, statement_config=config, is_many=True, **kwargs)
|
|
203
|
+
else:
|
|
204
|
+
base_statement = self.prepare_statement(statement, filters, statement_config=config, kwargs=kwargs)
|
|
205
|
+
sql_statement = SQL(base_statement._raw_sql, parameters, statement_config=config, is_many=True, **kwargs)
|
|
100
206
|
|
|
101
|
-
|
|
102
|
-
def _execute_statement(
|
|
103
|
-
self, statement: "SQL", connection: "Optional[ConnectionT]" = None, **kwargs: Any
|
|
104
|
-
) -> "SQLResult[RowT]":
|
|
105
|
-
"""Actual execution implementation by concrete drivers, using the raw connection.
|
|
207
|
+
return self.dispatch_statement_execution(statement=sql_statement, connection=self.connection)
|
|
106
208
|
|
|
107
|
-
|
|
209
|
+
def execute_script(
|
|
210
|
+
self,
|
|
211
|
+
statement: "Union[str, SQL]",
|
|
212
|
+
/,
|
|
213
|
+
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
214
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
215
|
+
**kwargs: Any,
|
|
216
|
+
) -> "SQLResult":
|
|
217
|
+
"""Execute a multi-statement script.
|
|
218
|
+
|
|
219
|
+
By default, validates each statement and logs warnings for dangerous
|
|
220
|
+
operations. Use suppress_warnings=True for migrations and admin scripts.
|
|
108
221
|
"""
|
|
109
|
-
|
|
222
|
+
config = statement_config or self.statement_config
|
|
223
|
+
sql_statement = self.prepare_statement(statement, parameters, statement_config=config, kwargs=kwargs)
|
|
224
|
+
|
|
225
|
+
return self.dispatch_statement_execution(statement=sql_statement.as_script(), connection=self.connection)
|
|
110
226
|
|
|
111
227
|
@overload
|
|
112
|
-
def
|
|
228
|
+
def select_one(
|
|
113
229
|
self,
|
|
114
|
-
statement: "
|
|
230
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
115
231
|
/,
|
|
116
232
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
117
233
|
schema_type: "type[ModelDTOT]",
|
|
118
|
-
|
|
119
|
-
_config: "Optional[SQLConfig]" = None,
|
|
234
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
120
235
|
**kwargs: Any,
|
|
121
|
-
) -> "
|
|
236
|
+
) -> "ModelDTOT": ...
|
|
122
237
|
|
|
123
238
|
@overload
|
|
124
|
-
def
|
|
239
|
+
def select_one(
|
|
125
240
|
self,
|
|
126
|
-
statement: "
|
|
241
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
127
242
|
/,
|
|
128
243
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
129
244
|
schema_type: None = None,
|
|
130
|
-
|
|
131
|
-
|
|
245
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
246
|
+
**kwargs: Any,
|
|
247
|
+
) -> "dict[str, Any]": ...
|
|
248
|
+
|
|
249
|
+
def select_one(
|
|
250
|
+
self,
|
|
251
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
252
|
+
/,
|
|
253
|
+
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
254
|
+
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
255
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
132
256
|
**kwargs: Any,
|
|
133
|
-
) -> "
|
|
257
|
+
) -> "Union[dict[str, Any], ModelDTOT]":
|
|
258
|
+
"""Execute a select statement and return exactly one row.
|
|
259
|
+
|
|
260
|
+
Raises an exception if no rows or more than one row is returned.
|
|
261
|
+
"""
|
|
262
|
+
result = self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
|
|
263
|
+
data = result.get_data()
|
|
264
|
+
data_len: int = len(data)
|
|
265
|
+
if data_len == 0:
|
|
266
|
+
self._raise_no_rows_found()
|
|
267
|
+
if data_len > 1:
|
|
268
|
+
self._raise_expected_one_row(data_len)
|
|
269
|
+
first_row = data[0]
|
|
270
|
+
return self.to_schema(first_row, schema_type=schema_type) if schema_type else first_row
|
|
134
271
|
|
|
135
272
|
@overload
|
|
136
|
-
def
|
|
273
|
+
def select_one_or_none(
|
|
137
274
|
self,
|
|
138
|
-
statement: "Union[
|
|
275
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
139
276
|
/,
|
|
140
277
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
141
|
-
|
|
142
|
-
|
|
278
|
+
schema_type: "type[ModelDTOT]",
|
|
279
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
143
280
|
**kwargs: Any,
|
|
144
|
-
) -> "
|
|
281
|
+
) -> "Optional[ModelDTOT]": ...
|
|
145
282
|
|
|
146
283
|
@overload
|
|
147
|
-
def
|
|
284
|
+
def select_one_or_none(
|
|
148
285
|
self,
|
|
149
|
-
statement: "Union[
|
|
286
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
287
|
+
/,
|
|
288
|
+
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
289
|
+
schema_type: None = None,
|
|
290
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
291
|
+
**kwargs: Any,
|
|
292
|
+
) -> "Optional[dict[str, Any]]": ...
|
|
293
|
+
|
|
294
|
+
def select_one_or_none(
|
|
295
|
+
self,
|
|
296
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
297
|
+
/,
|
|
298
|
+
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
299
|
+
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
300
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
301
|
+
**kwargs: Any,
|
|
302
|
+
) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
|
|
303
|
+
"""Execute a select statement and return at most one row.
|
|
304
|
+
|
|
305
|
+
Returns None if no rows are found.
|
|
306
|
+
Raises an exception if more than one row is returned.
|
|
307
|
+
"""
|
|
308
|
+
result = self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
|
|
309
|
+
data = result.get_data()
|
|
310
|
+
data_len: int = len(data)
|
|
311
|
+
if data_len == 0:
|
|
312
|
+
return None
|
|
313
|
+
if data_len > 1:
|
|
314
|
+
self._raise_expected_at_most_one_row(data_len)
|
|
315
|
+
first_row = data[0]
|
|
316
|
+
return cast(
|
|
317
|
+
"Optional[Union[dict[str, Any], ModelDTOT]]",
|
|
318
|
+
self.to_schema(first_row, schema_type=schema_type) if schema_type else first_row,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
@overload
|
|
322
|
+
def select(
|
|
323
|
+
self,
|
|
324
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
150
325
|
/,
|
|
151
326
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
152
327
|
schema_type: "type[ModelDTOT]",
|
|
153
|
-
|
|
154
|
-
_config: "Optional[SQLConfig]" = None,
|
|
328
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
155
329
|
**kwargs: Any,
|
|
156
|
-
) -> "
|
|
330
|
+
) -> "list[ModelDTOT]": ...
|
|
157
331
|
|
|
158
332
|
@overload
|
|
159
|
-
def
|
|
333
|
+
def select(
|
|
160
334
|
self,
|
|
161
|
-
statement: "Union[
|
|
335
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
162
336
|
/,
|
|
163
337
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
164
338
|
schema_type: None = None,
|
|
165
|
-
|
|
166
|
-
_config: "Optional[SQLConfig]" = None,
|
|
339
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
167
340
|
**kwargs: Any,
|
|
168
|
-
) -> "
|
|
341
|
+
) -> "list[dict[str, Any]]": ...
|
|
169
342
|
|
|
170
|
-
def
|
|
343
|
+
def select(
|
|
171
344
|
self,
|
|
172
|
-
statement: "Union[
|
|
345
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
173
346
|
/,
|
|
174
347
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
175
348
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if schema_type and result.data and can_convert_to_schema(self):
|
|
185
|
-
converted_data = list(self.to_schema(data=result.data, schema_type=schema_type))
|
|
186
|
-
return SQLResult[ModelDTOT](
|
|
187
|
-
statement=result.statement,
|
|
188
|
-
data=converted_data,
|
|
189
|
-
column_names=result.column_names,
|
|
190
|
-
rows_affected=result.rows_affected,
|
|
191
|
-
operation_type=result.operation_type,
|
|
192
|
-
last_inserted_id=result.last_inserted_id,
|
|
193
|
-
execution_time=result.execution_time,
|
|
194
|
-
metadata=result.metadata,
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
return result
|
|
349
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
350
|
+
**kwargs: Any,
|
|
351
|
+
) -> "Union[list[dict[str, Any]], list[ModelDTOT]]":
|
|
352
|
+
"""Execute a select statement and return all rows."""
|
|
353
|
+
result = self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
|
|
354
|
+
return cast(
|
|
355
|
+
"Union[list[dict[str, Any]], list[ModelDTOT]]", self.to_schema(result.get_data(), schema_type=schema_type)
|
|
356
|
+
)
|
|
198
357
|
|
|
199
|
-
def
|
|
358
|
+
def select_value(
|
|
200
359
|
self,
|
|
201
|
-
statement: "Union[
|
|
360
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
202
361
|
/,
|
|
203
362
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
204
|
-
|
|
205
|
-
_config: "Optional[SQLConfig]" = None,
|
|
363
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
206
364
|
**kwargs: Any,
|
|
207
|
-
) ->
|
|
208
|
-
|
|
365
|
+
) -> Any:
|
|
366
|
+
"""Execute a select statement and return a single scalar value.
|
|
209
367
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
368
|
+
Expects exactly one row with one column.
|
|
369
|
+
Raises an exception if no rows or more than one row/column is returned.
|
|
370
|
+
"""
|
|
371
|
+
result = self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
|
|
372
|
+
try:
|
|
373
|
+
row = result.one()
|
|
374
|
+
except ValueError as e:
|
|
375
|
+
self._raise_no_rows_found_from_exception(e)
|
|
376
|
+
if not row:
|
|
377
|
+
self._raise_no_rows_found()
|
|
378
|
+
if is_dict_row(row):
|
|
379
|
+
if not row:
|
|
380
|
+
self._raise_row_no_columns()
|
|
381
|
+
return next(iter(row.values()))
|
|
382
|
+
if is_indexable_row(row):
|
|
383
|
+
if not row:
|
|
384
|
+
self._raise_row_no_columns()
|
|
385
|
+
return row[0]
|
|
386
|
+
self._raise_unexpected_row_type(type(row))
|
|
387
|
+
return None
|
|
215
388
|
|
|
216
|
-
|
|
389
|
+
def select_value_or_none(
|
|
390
|
+
self,
|
|
391
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
392
|
+
/,
|
|
393
|
+
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
394
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
395
|
+
**kwargs: Any,
|
|
396
|
+
) -> Any:
|
|
397
|
+
"""Execute a select statement and return a single scalar value or None.
|
|
217
398
|
|
|
218
|
-
|
|
399
|
+
Returns None if no rows are found.
|
|
400
|
+
Expects at most one row with one column.
|
|
401
|
+
Raises an exception if more than one row is returned.
|
|
402
|
+
"""
|
|
403
|
+
result = self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
|
|
404
|
+
data = result.get_data()
|
|
405
|
+
data_len: int = len(data)
|
|
406
|
+
if data_len == 0:
|
|
407
|
+
return None
|
|
408
|
+
if data_len > 1:
|
|
409
|
+
msg = f"Expected at most one row, found {data_len}"
|
|
410
|
+
raise ValueError(msg)
|
|
411
|
+
row = data[0]
|
|
412
|
+
if isinstance(row, dict):
|
|
413
|
+
if not row:
|
|
414
|
+
return None
|
|
415
|
+
return next(iter(row.values()))
|
|
416
|
+
if isinstance(row, (tuple, list)):
|
|
417
|
+
return row[0]
|
|
418
|
+
msg = f"Cannot extract value from row type {type(row).__name__}"
|
|
419
|
+
raise TypeError(msg)
|
|
219
420
|
|
|
220
|
-
|
|
421
|
+
@overload
|
|
422
|
+
def select_with_total(
|
|
423
|
+
self,
|
|
424
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
425
|
+
/,
|
|
426
|
+
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
427
|
+
schema_type: "type[ModelDTOT]",
|
|
428
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
429
|
+
**kwargs: Any,
|
|
430
|
+
) -> "tuple[list[ModelDTOT], int]": ...
|
|
221
431
|
|
|
222
|
-
|
|
432
|
+
@overload
|
|
433
|
+
def select_with_total(
|
|
223
434
|
self,
|
|
224
|
-
statement: "Union[
|
|
435
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
225
436
|
/,
|
|
226
437
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
227
|
-
|
|
228
|
-
|
|
438
|
+
schema_type: None = None,
|
|
439
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
229
440
|
**kwargs: Any,
|
|
230
|
-
) -> "
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
441
|
+
) -> "tuple[list[dict[str, Any]], int]": ...
|
|
442
|
+
|
|
443
|
+
def select_with_total(
|
|
444
|
+
self,
|
|
445
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
446
|
+
/,
|
|
447
|
+
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
448
|
+
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
449
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
450
|
+
**kwargs: Any,
|
|
451
|
+
) -> "tuple[Union[list[dict[str, Any]], list[ModelDTOT]], int]":
|
|
452
|
+
"""Execute a select statement and return both the data and total count.
|
|
453
|
+
|
|
454
|
+
This method is designed for pagination scenarios where you need both
|
|
455
|
+
the current page of data and the total number of rows that match the query.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
statement: The SQL statement, QueryBuilder, or raw SQL string
|
|
459
|
+
*parameters: Parameters for the SQL statement
|
|
460
|
+
schema_type: Optional schema type for data transformation
|
|
461
|
+
statement_config: Optional SQL configuration
|
|
462
|
+
**kwargs: Additional keyword arguments
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
A tuple containing:
|
|
466
|
+
- List of data rows (transformed by schema_type if provided)
|
|
467
|
+
- Total count of rows matching the query (ignoring LIMIT/OFFSET)
|
|
468
|
+
"""
|
|
469
|
+
sql_statement = self.prepare_statement(
|
|
470
|
+
statement, parameters, statement_config=statement_config or self.statement_config, kwargs=kwargs
|
|
471
|
+
)
|
|
472
|
+
count_result = self.dispatch_statement_execution(self._create_count_query(sql_statement), self.connection)
|
|
473
|
+
select_result = self.execute(sql_statement)
|
|
474
|
+
|
|
475
|
+
return (self.to_schema(select_result.get_data(), schema_type=schema_type), count_result.scalar())
|
|
476
|
+
|
|
477
|
+
def _raise_no_rows_found(self) -> NoReturn:
|
|
478
|
+
msg = "No rows found"
|
|
479
|
+
raise NotFoundError(msg)
|
|
480
|
+
|
|
481
|
+
def _raise_no_rows_found_from_exception(self, e: ValueError) -> NoReturn:
|
|
482
|
+
msg = "No rows found"
|
|
483
|
+
raise NotFoundError(msg) from e
|
|
484
|
+
|
|
485
|
+
def _raise_expected_one_row(self, data_len: int) -> NoReturn:
|
|
486
|
+
msg = f"Expected exactly one row, found {data_len}"
|
|
487
|
+
raise ValueError(msg)
|
|
488
|
+
|
|
489
|
+
def _raise_expected_at_most_one_row(self, data_len: int) -> NoReturn:
|
|
490
|
+
msg = f"Expected at most one row, found {data_len}"
|
|
491
|
+
raise ValueError(msg)
|
|
492
|
+
|
|
493
|
+
def _raise_row_no_columns(self) -> NoReturn:
|
|
494
|
+
msg = "Row has no columns"
|
|
495
|
+
raise ValueError(msg)
|
|
496
|
+
|
|
497
|
+
def _raise_unexpected_row_type(self, row_type: type) -> NoReturn:
|
|
498
|
+
msg = f"Unexpected row type: {row_type}"
|
|
499
|
+
raise ValueError(msg)
|
|
234
500
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
501
|
+
def _raise_cannot_extract_value_from_row_type(self, type_name: str) -> NoReturn:
|
|
502
|
+
msg = f"Cannot extract value from row type {type_name}"
|
|
503
|
+
raise TypeError(msg)
|