sqlspec 0.14.0__py3-none-any.whl → 0.15.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 +12 -0
- sqlspec/__metadata__.py +1 -3
- sqlspec/_serialization.py +1 -2
- sqlspec/_sql.py +256 -120
- 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 -248
- sqlspec/adapters/adbc/driver.py +462 -353
- 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 +6 -64
- sqlspec/builder/_merge.py +56 -0
- sqlspec/{statement/builder → builder}/_parsing_utils.py +3 -10
- 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 +22 -16
- sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
- sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +3 -5
- 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 +21 -36
- sqlspec/{statement/builder → builder}/mixins/_update_operations.py +3 -14
- sqlspec/{statement/builder → builder}/mixins/_where_clause.py +52 -79
- 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 +828 -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 +651 -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 +168 -0
- sqlspec/driver/mixins/_sql_translator.py +6 -3
- sqlspec/exceptions.py +5 -252
- sqlspec/extensions/aiosql/adapter.py +93 -96
- sqlspec/extensions/litestar/config.py +0 -1
- sqlspec/extensions/litestar/handlers.py +15 -26
- sqlspec/extensions/litestar/plugin.py +16 -14
- 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 +16 -37
- sqlspec/utils/text.py +12 -51
- sqlspec/utils/type_guards.py +443 -232
- {sqlspec-0.14.0.dist-info → sqlspec-0.15.0.dist-info}/METADATA +7 -2
- sqlspec-0.15.0.dist-info/RECORD +134 -0
- sqlspec-0.15.0.dist-info/entry_points.txt +2 -0
- 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 -996
- 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 -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.0.dist-info/RECORD +0 -143
- sqlspec-0.14.0.dist-info/entry_points.txt +0 -2
- /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
- {sqlspec-0.14.0.dist-info → sqlspec-0.15.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.14.0.dist-info → sqlspec-0.15.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.14.0.dist-info → sqlspec-0.15.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/driver/_async.py
CHANGED
|
@@ -1,261 +1,472 @@
|
|
|
1
|
-
"""Asynchronous driver protocol implementation.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
from sqlspec.
|
|
11
|
-
from sqlspec.
|
|
12
|
-
from sqlspec.
|
|
1
|
+
"""Asynchronous driver protocol implementation.
|
|
2
|
+
|
|
3
|
+
This module provides the async 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, Optional, Union, cast, overload
|
|
9
|
+
|
|
10
|
+
from sqlspec.core import SQL, Statement
|
|
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 AbstractAsyncContextManager
|
|
20
|
+
|
|
21
|
+
from sqlspec.builder import QueryBuilder
|
|
22
|
+
from sqlspec.core import SQLResult, StatementConfig, StatementFilter
|
|
23
|
+
from sqlspec.typing import ModelDTOT, ModelT, RowT, StatementParameters
|
|
18
24
|
|
|
19
25
|
logger = get_logger("sqlspec")
|
|
20
26
|
|
|
21
|
-
__all__ = ("
|
|
27
|
+
__all__ = ("AsyncDriverAdapterBase",)
|
|
22
28
|
|
|
23
29
|
|
|
24
30
|
EMPTY_FILTERS: "list[StatementFilter]" = []
|
|
25
31
|
|
|
26
32
|
|
|
27
|
-
class
|
|
33
|
+
class AsyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToSchemaMixin):
|
|
34
|
+
"""Base class for asynchronous database drivers.
|
|
35
|
+
|
|
36
|
+
Provides the foundation for async database adapters, including connection management,
|
|
37
|
+
transaction support, and SQL execution methods. All database operations are performed
|
|
38
|
+
asynchronously and support context manager patterns for proper resource cleanup.
|
|
39
|
+
"""
|
|
40
|
+
|
|
28
41
|
__slots__ = ()
|
|
29
42
|
|
|
30
|
-
def
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
async def dispatch_statement_execution(self, statement: "SQL", connection: "Any") -> "SQLResult":
|
|
44
|
+
"""Central execution dispatcher using the Template Method Pattern.
|
|
45
|
+
|
|
46
|
+
Orchestrates the common execution flow, delegating database-specific steps
|
|
47
|
+
to abstract methods that concrete adapters must implement.
|
|
48
|
+
All database operations are wrapped in exception handling.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
statement: The SQL statement to execute
|
|
52
|
+
connection: The database connection to use
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
The result of the SQL execution
|
|
56
|
+
"""
|
|
57
|
+
async with self.handle_database_exceptions(), self.with_cursor(connection) as cursor:
|
|
58
|
+
special_result = await self._try_special_handling(cursor, statement)
|
|
59
|
+
if special_result is not None:
|
|
60
|
+
return special_result
|
|
61
|
+
|
|
62
|
+
if statement.is_script:
|
|
63
|
+
execution_result = await self._execute_script(cursor, statement)
|
|
64
|
+
elif statement.is_many:
|
|
65
|
+
execution_result = await self._execute_many(cursor, statement)
|
|
66
|
+
else:
|
|
67
|
+
execution_result = await self._execute_statement(cursor, statement)
|
|
68
|
+
|
|
69
|
+
return self.build_statement_result(statement, execution_result)
|
|
70
|
+
|
|
71
|
+
@abstractmethod
|
|
72
|
+
def with_cursor(self, connection: Any) -> Any:
|
|
73
|
+
"""Create and return an async context manager for cursor acquisition and cleanup.
|
|
74
|
+
|
|
75
|
+
Returns an async context manager that yields a cursor for database operations.
|
|
76
|
+
Concrete implementations handle database-specific cursor creation and cleanup.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def handle_database_exceptions(self) -> "AbstractAsyncContextManager[None]":
|
|
81
|
+
"""Handle database-specific exceptions and wrap them appropriately.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
AsyncContextManager that can be used in async with statements
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
@abstractmethod
|
|
88
|
+
async def begin(self) -> None:
|
|
89
|
+
"""Begin a database transaction on the current connection."""
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
async def rollback(self) -> None:
|
|
93
|
+
"""Rollback the current transaction on the current connection."""
|
|
94
|
+
|
|
95
|
+
@abstractmethod
|
|
96
|
+
async def commit(self) -> None:
|
|
97
|
+
"""Commit the current transaction on the current connection."""
|
|
98
|
+
|
|
99
|
+
@abstractmethod
|
|
100
|
+
async def _try_special_handling(self, cursor: Any, statement: "SQL") -> "Optional[SQLResult]":
|
|
101
|
+
"""Hook for database-specific special operations (e.g., PostgreSQL COPY, bulk operations).
|
|
102
|
+
|
|
103
|
+
This method is called first in dispatch_statement_execution() to allow drivers to handle
|
|
104
|
+
special operations that don't follow the standard SQL execution pattern.
|
|
37
105
|
|
|
38
106
|
Args:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
107
|
+
cursor: Database cursor/connection object
|
|
108
|
+
statement: SQL statement to analyze
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
SQLResult if the special operation was handled and completed,
|
|
112
|
+
None if standard execution should proceed
|
|
42
113
|
"""
|
|
43
|
-
super().__init__(connection=connection, config=config, default_row_type=default_row_type)
|
|
44
114
|
|
|
45
|
-
def
|
|
115
|
+
async def _execute_script(self, cursor: Any, statement: "SQL") -> ExecutionResult:
|
|
116
|
+
"""Execute a SQL script containing multiple statements.
|
|
117
|
+
|
|
118
|
+
Default implementation splits the script and executes statements individually.
|
|
119
|
+
Drivers can override for database-specific script execution methods.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
cursor: Database cursor/connection object
|
|
123
|
+
statement: SQL statement object with all necessary data and configuration
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
ExecutionResult with script execution data including statement counts
|
|
127
|
+
"""
|
|
128
|
+
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
129
|
+
statements = self.split_script_statements(sql, self.statement_config, strip_trailing_semicolon=True)
|
|
130
|
+
|
|
131
|
+
for stmt in statements:
|
|
132
|
+
single_stmt = statement.copy(statement=stmt, parameters=prepared_parameters)
|
|
133
|
+
await self._execute_statement(cursor, single_stmt)
|
|
134
|
+
|
|
135
|
+
return self.create_execution_result(
|
|
136
|
+
cursor, statement_count=len(statements), successful_statements=len(statements), is_script_result=True
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
@abstractmethod
|
|
140
|
+
async def _execute_many(self, cursor: Any, statement: "SQL") -> ExecutionResult:
|
|
141
|
+
"""Execute SQL with multiple parameter sets (executemany).
|
|
142
|
+
|
|
143
|
+
Must be implemented by each driver for database-specific executemany logic.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
cursor: Database cursor/connection object
|
|
147
|
+
statement: SQL statement object with all necessary data and configuration
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
ExecutionResult with execution data for the many operation
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
@abstractmethod
|
|
154
|
+
async def _execute_statement(self, cursor: Any, statement: "SQL") -> ExecutionResult:
|
|
155
|
+
"""Execute a single SQL statement.
|
|
156
|
+
|
|
157
|
+
Must be implemented by each driver for database-specific execution logic.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
cursor: Database cursor/connection object
|
|
161
|
+
statement: SQL statement object with all necessary data and configuration
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
ExecutionResult with execution data
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
async def execute(
|
|
46
168
|
self,
|
|
47
|
-
statement: "Union[Statement, QueryBuilder
|
|
169
|
+
statement: "Union[SQL, Statement, QueryBuilder]",
|
|
170
|
+
/,
|
|
48
171
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
49
|
-
|
|
172
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
50
173
|
**kwargs: Any,
|
|
51
|
-
) -> "
|
|
52
|
-
|
|
53
|
-
|
|
174
|
+
) -> "SQLResult":
|
|
175
|
+
"""Execute a statement with parameter handling."""
|
|
176
|
+
sql_statement = self.prepare_statement(
|
|
177
|
+
statement, parameters, statement_config=statement_config or self.statement_config, kwargs=kwargs
|
|
178
|
+
)
|
|
179
|
+
return await self.dispatch_statement_execution(statement=sql_statement, connection=self.connection)
|
|
180
|
+
|
|
181
|
+
async def execute_many(
|
|
182
|
+
self,
|
|
183
|
+
statement: "Union[SQL, Statement, QueryBuilder]",
|
|
184
|
+
/,
|
|
185
|
+
parameters: "Sequence[StatementParameters]",
|
|
186
|
+
*filters: "Union[StatementParameters, StatementFilter]",
|
|
187
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
188
|
+
**kwargs: Any,
|
|
189
|
+
) -> "SQLResult":
|
|
190
|
+
"""Execute statement multiple times with different parameters.
|
|
191
|
+
|
|
192
|
+
Parameters passed will be used as the batch execution sequence.
|
|
193
|
+
"""
|
|
194
|
+
config = statement_config or self.statement_config
|
|
54
195
|
|
|
55
|
-
if isinstance(statement, QueryBuilder):
|
|
56
|
-
return statement.to_statement(config=_config)
|
|
57
|
-
# If statement is already a SQL object, handle additional parameters
|
|
58
196
|
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)
|
|
197
|
+
sql_statement = SQL(statement._raw_sql, parameters, statement_config=config, is_many=True, **kwargs)
|
|
198
|
+
else:
|
|
199
|
+
base_statement = self.prepare_statement(statement, filters, statement_config=config, kwargs=kwargs)
|
|
200
|
+
sql_statement = SQL(base_statement._raw_sql, parameters, statement_config=config, is_many=True, **kwargs)
|
|
100
201
|
|
|
101
|
-
|
|
102
|
-
async 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.
|
|
202
|
+
return await self.dispatch_statement_execution(statement=sql_statement, connection=self.connection)
|
|
106
203
|
|
|
107
|
-
|
|
204
|
+
async def execute_script(
|
|
205
|
+
self,
|
|
206
|
+
statement: "Union[str, SQL]",
|
|
207
|
+
/,
|
|
208
|
+
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
209
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
210
|
+
**kwargs: Any,
|
|
211
|
+
) -> "SQLResult":
|
|
212
|
+
"""Execute a multi-statement script.
|
|
213
|
+
|
|
214
|
+
By default, validates each statement and logs warnings for dangerous
|
|
215
|
+
operations. Use suppress_warnings=True for migrations and admin scripts.
|
|
108
216
|
"""
|
|
109
|
-
|
|
217
|
+
script_config = statement_config or self.statement_config
|
|
218
|
+
sql_statement = self.prepare_statement(statement, parameters, statement_config=script_config, kwargs=kwargs)
|
|
219
|
+
|
|
220
|
+
return await self.dispatch_statement_execution(statement=sql_statement.as_script(), connection=self.connection)
|
|
110
221
|
|
|
111
222
|
@overload
|
|
112
|
-
async def
|
|
223
|
+
async def select_one(
|
|
113
224
|
self,
|
|
114
|
-
statement: "
|
|
225
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
115
226
|
/,
|
|
116
227
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
117
228
|
schema_type: "type[ModelDTOT]",
|
|
118
|
-
|
|
119
|
-
_config: "Optional[SQLConfig]" = None,
|
|
229
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
120
230
|
**kwargs: Any,
|
|
121
|
-
) -> "
|
|
231
|
+
) -> "ModelDTOT": ...
|
|
122
232
|
|
|
123
233
|
@overload
|
|
124
|
-
async def
|
|
234
|
+
async def select_one(
|
|
125
235
|
self,
|
|
126
|
-
statement: "
|
|
236
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
127
237
|
/,
|
|
128
238
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
129
239
|
schema_type: None = None,
|
|
130
|
-
|
|
131
|
-
_config: "Optional[SQLConfig]" = None,
|
|
240
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
132
241
|
**kwargs: Any,
|
|
133
|
-
) -> "
|
|
242
|
+
) -> "Union[ModelT, RowT, dict[str, Any]]": ... # pyright: ignore[reportInvalidTypeVarUse]
|
|
134
243
|
|
|
135
|
-
|
|
136
|
-
async def execute(
|
|
244
|
+
async def select_one(
|
|
137
245
|
self,
|
|
138
|
-
statement: "Union[
|
|
246
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
139
247
|
/,
|
|
140
248
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
141
|
-
|
|
142
|
-
|
|
249
|
+
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
250
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
143
251
|
**kwargs: Any,
|
|
144
|
-
) -> "
|
|
252
|
+
) -> "Union[ModelT, RowT,ModelDTOT]": # pyright: ignore[reportInvalidTypeVarUse]
|
|
253
|
+
"""Execute a select statement and return exactly one row.
|
|
254
|
+
|
|
255
|
+
Raises an exception if no rows or more than one row is returned.
|
|
256
|
+
"""
|
|
257
|
+
result = await self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
|
|
258
|
+
data = result.get_data()
|
|
259
|
+
if not data:
|
|
260
|
+
msg = "No rows found"
|
|
261
|
+
raise NotFoundError(msg)
|
|
262
|
+
if len(data) > 1:
|
|
263
|
+
msg = f"Expected exactly one row, found {len(data)}"
|
|
264
|
+
raise ValueError(msg)
|
|
265
|
+
return cast(
|
|
266
|
+
"Union[ModelT, RowT, ModelDTOT]",
|
|
267
|
+
self.to_schema(data[0], schema_type=schema_type) if schema_type else data[0],
|
|
268
|
+
)
|
|
145
269
|
|
|
146
270
|
@overload
|
|
147
|
-
async def
|
|
271
|
+
async def select_one_or_none(
|
|
148
272
|
self,
|
|
149
|
-
statement: "Union[
|
|
273
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
150
274
|
/,
|
|
151
275
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
152
276
|
schema_type: "type[ModelDTOT]",
|
|
153
|
-
|
|
154
|
-
_config: "Optional[SQLConfig]" = None,
|
|
277
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
155
278
|
**kwargs: Any,
|
|
156
|
-
) -> "
|
|
279
|
+
) -> "Optional[ModelDTOT]": ...
|
|
157
280
|
|
|
158
281
|
@overload
|
|
159
|
-
async def
|
|
282
|
+
async def select_one_or_none(
|
|
160
283
|
self,
|
|
161
|
-
statement: "Union[
|
|
284
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
162
285
|
/,
|
|
163
286
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
164
287
|
schema_type: None = None,
|
|
165
|
-
|
|
166
|
-
_config: "Optional[SQLConfig]" = None,
|
|
288
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
167
289
|
**kwargs: Any,
|
|
168
|
-
) -> "
|
|
290
|
+
) -> "Optional[ModelT]": ... # pyright: ignore[reportInvalidTypeVarUse]
|
|
169
291
|
|
|
170
|
-
async def
|
|
292
|
+
async def select_one_or_none(
|
|
171
293
|
self,
|
|
172
|
-
statement: "Union[
|
|
294
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
173
295
|
/,
|
|
174
296
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
175
297
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
176
|
-
|
|
177
|
-
_config: "Optional[SQLConfig]" = None,
|
|
298
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
178
299
|
**kwargs: Any,
|
|
179
|
-
) -> "Union[
|
|
180
|
-
|
|
181
|
-
result = await self._execute_statement(
|
|
182
|
-
statement=sql_statement, connection=self._connection(_connection), **kwargs
|
|
183
|
-
)
|
|
300
|
+
) -> "Optional[Union[ModelT, ModelDTOT]]": # pyright: ignore[reportInvalidTypeVarUse]
|
|
301
|
+
"""Execute a select statement and return at most one row.
|
|
184
302
|
|
|
185
|
-
|
|
186
|
-
if
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
metadata=result.metadata,
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
return result
|
|
303
|
+
Returns None if no rows are found.
|
|
304
|
+
Raises an exception if more than one row is returned.
|
|
305
|
+
"""
|
|
306
|
+
result = await self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
|
|
307
|
+
data = result.get_data()
|
|
308
|
+
if not data:
|
|
309
|
+
return None
|
|
310
|
+
if len(data) > 1:
|
|
311
|
+
msg = f"Expected at most one row, found {len(data)}"
|
|
312
|
+
raise ValueError(msg)
|
|
313
|
+
return cast("Optional[Union[ModelT, ModelDTOT]]", self.to_schema(data[0], schema_type=schema_type))
|
|
200
314
|
|
|
201
|
-
|
|
315
|
+
@overload
|
|
316
|
+
async def select(
|
|
202
317
|
self,
|
|
203
|
-
statement: "Union[
|
|
318
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
204
319
|
/,
|
|
205
320
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
206
|
-
|
|
207
|
-
|
|
321
|
+
schema_type: "type[ModelDTOT]",
|
|
322
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
208
323
|
**kwargs: Any,
|
|
209
|
-
) -> "
|
|
210
|
-
"""Execute statement multiple times with different parameters.
|
|
211
|
-
|
|
212
|
-
Now passes first parameter set through pipeline to enable
|
|
213
|
-
literal extraction and consistent parameter processing.
|
|
214
|
-
"""
|
|
215
|
-
filters, param_sequence = process_execute_many_parameters(parameters)
|
|
216
|
-
|
|
217
|
-
# Process first parameter set through pipeline for literal extraction
|
|
218
|
-
first_params = param_sequence[0] if param_sequence else None
|
|
324
|
+
) -> "list[ModelDTOT]": ...
|
|
219
325
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
326
|
+
@overload
|
|
327
|
+
async def select(
|
|
328
|
+
self,
|
|
329
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
330
|
+
/,
|
|
331
|
+
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
332
|
+
schema_type: None = None,
|
|
333
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
334
|
+
**kwargs: Any,
|
|
335
|
+
) -> "list[ModelT]": ... # pyright: ignore[reportInvalidTypeVarUse]
|
|
336
|
+
async def select(
|
|
337
|
+
self,
|
|
338
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
339
|
+
/,
|
|
340
|
+
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
341
|
+
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
342
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
343
|
+
**kwargs: Any,
|
|
344
|
+
) -> "Union[list[ModelT], list[ModelDTOT]]": # pyright: ignore[reportInvalidTypeVarUse]
|
|
345
|
+
"""Execute a select statement and return all rows."""
|
|
346
|
+
result = await self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
|
|
347
|
+
return cast(
|
|
348
|
+
"Union[list[ModelT], list[ModelDTOT]]",
|
|
349
|
+
self.to_schema(cast("list[ModelT]", result.get_data()), schema_type=schema_type),
|
|
223
350
|
)
|
|
224
351
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
352
|
+
async def select_value(
|
|
353
|
+
self,
|
|
354
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
355
|
+
/,
|
|
356
|
+
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
357
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
358
|
+
**kwargs: Any,
|
|
359
|
+
) -> Any:
|
|
360
|
+
"""Execute a select statement and return a single scalar value.
|
|
231
361
|
|
|
232
|
-
|
|
362
|
+
Expects exactly one row with one column.
|
|
363
|
+
Raises an exception if no rows or more than one row/column is returned.
|
|
364
|
+
"""
|
|
365
|
+
result = await self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
|
|
366
|
+
try:
|
|
367
|
+
row = result.one()
|
|
368
|
+
except ValueError as e:
|
|
369
|
+
msg = "No rows found"
|
|
370
|
+
raise NotFoundError(msg) from e
|
|
371
|
+
if not row:
|
|
372
|
+
msg = "No rows found"
|
|
373
|
+
raise NotFoundError(msg)
|
|
374
|
+
if is_dict_row(row):
|
|
375
|
+
if not row:
|
|
376
|
+
msg = "Row has no columns"
|
|
377
|
+
raise ValueError(msg)
|
|
378
|
+
return next(iter(row.values()))
|
|
379
|
+
if is_indexable_row(row):
|
|
380
|
+
if not row:
|
|
381
|
+
msg = "Row has no columns"
|
|
382
|
+
raise ValueError(msg)
|
|
383
|
+
return row[0]
|
|
384
|
+
msg = f"Unexpected row type: {type(row)}"
|
|
385
|
+
raise ValueError(msg)
|
|
386
|
+
|
|
387
|
+
async def select_value_or_none(
|
|
233
388
|
self,
|
|
234
|
-
statement: "Union[
|
|
389
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
235
390
|
/,
|
|
236
391
|
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
237
|
-
|
|
238
|
-
_config: "Optional[SQLConfig]" = None,
|
|
239
|
-
_suppress_warnings: bool = False, # New parameter for migrations
|
|
392
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
240
393
|
**kwargs: Any,
|
|
241
|
-
) ->
|
|
242
|
-
"""Execute a
|
|
394
|
+
) -> Any:
|
|
395
|
+
"""Execute a select statement and return a single scalar value or None.
|
|
243
396
|
|
|
244
|
-
|
|
245
|
-
|
|
397
|
+
Returns None if no rows are found.
|
|
398
|
+
Expects at most one row with one column.
|
|
399
|
+
Raises an exception if more than one row is returned.
|
|
246
400
|
"""
|
|
247
|
-
|
|
401
|
+
result = await self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
|
|
402
|
+
data = result.get_data()
|
|
403
|
+
if not data:
|
|
404
|
+
return None
|
|
405
|
+
if len(data) > 1:
|
|
406
|
+
msg = f"Expected at most one row, found {len(data)}"
|
|
407
|
+
raise ValueError(msg)
|
|
408
|
+
row = data[0]
|
|
409
|
+
if is_dict_row(row):
|
|
410
|
+
if not row:
|
|
411
|
+
return None
|
|
412
|
+
return next(iter(row.values()))
|
|
413
|
+
if is_indexable_row(row):
|
|
414
|
+
return row[0]
|
|
415
|
+
msg = f"Cannot extract value from row type {type(row).__name__}"
|
|
416
|
+
raise TypeError(msg)
|
|
248
417
|
|
|
249
|
-
|
|
250
|
-
|
|
418
|
+
@overload
|
|
419
|
+
async def select_with_total(
|
|
420
|
+
self,
|
|
421
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
422
|
+
/,
|
|
423
|
+
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
424
|
+
schema_type: "type[ModelDTOT]",
|
|
425
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
426
|
+
**kwargs: Any,
|
|
427
|
+
) -> "tuple[list[ModelDTOT], int]": ...
|
|
251
428
|
|
|
252
|
-
|
|
253
|
-
|
|
429
|
+
@overload
|
|
430
|
+
async def select_with_total(
|
|
431
|
+
self,
|
|
432
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
433
|
+
/,
|
|
434
|
+
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
435
|
+
schema_type: None = None,
|
|
436
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
437
|
+
**kwargs: Any,
|
|
438
|
+
) -> "tuple[list[dict[str, Any]], int]": ...
|
|
254
439
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
440
|
+
async def select_with_total(
|
|
441
|
+
self,
|
|
442
|
+
statement: "Union[Statement, QueryBuilder]",
|
|
443
|
+
/,
|
|
444
|
+
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
445
|
+
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
446
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
447
|
+
**kwargs: Any,
|
|
448
|
+
) -> "tuple[Union[list[dict[str, Any]], list[ModelDTOT]], int]":
|
|
449
|
+
"""Execute a select statement and return both the data and total count.
|
|
258
450
|
|
|
259
|
-
|
|
260
|
-
|
|
451
|
+
This method is designed for pagination scenarios where you need both
|
|
452
|
+
the current page of data and the total number of rows that match the query.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
statement: The SQL statement, QueryBuilder, or raw SQL string
|
|
456
|
+
*parameters: Parameters for the SQL statement
|
|
457
|
+
schema_type: Optional schema type for data transformation
|
|
458
|
+
statement_config: Optional SQL configuration
|
|
459
|
+
**kwargs: Additional keyword arguments
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
A tuple containing:
|
|
463
|
+
- List of data rows (transformed by schema_type if provided)
|
|
464
|
+
- Total count of rows matching the query (ignoring LIMIT/OFFSET)
|
|
465
|
+
"""
|
|
466
|
+
sql_statement = self.prepare_statement(
|
|
467
|
+
statement, parameters, statement_config=statement_config or self.statement_config, kwargs=kwargs
|
|
261
468
|
)
|
|
469
|
+
count_result = await self.dispatch_statement_execution(self._create_count_query(sql_statement), self.connection)
|
|
470
|
+
select_result = await self.execute(sql_statement)
|
|
471
|
+
|
|
472
|
+
return (self.to_schema(select_result.get_data(), schema_type=schema_type), count_result.scalar())
|