sqlspec 0.13.1__py3-none-any.whl → 0.14.1__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 +39 -1
- sqlspec/__main__.py +12 -0
- sqlspec/adapters/adbc/config.py +16 -40
- sqlspec/adapters/adbc/driver.py +43 -16
- sqlspec/adapters/adbc/transformers.py +108 -0
- sqlspec/adapters/aiosqlite/config.py +2 -20
- sqlspec/adapters/aiosqlite/driver.py +36 -18
- sqlspec/adapters/asyncmy/config.py +2 -33
- sqlspec/adapters/asyncmy/driver.py +23 -16
- sqlspec/adapters/asyncpg/config.py +5 -39
- sqlspec/adapters/asyncpg/driver.py +41 -18
- sqlspec/adapters/bigquery/config.py +2 -43
- sqlspec/adapters/bigquery/driver.py +26 -14
- sqlspec/adapters/duckdb/config.py +2 -49
- sqlspec/adapters/duckdb/driver.py +35 -16
- sqlspec/adapters/oracledb/config.py +4 -83
- sqlspec/adapters/oracledb/driver.py +54 -27
- sqlspec/adapters/psqlpy/config.py +2 -55
- sqlspec/adapters/psqlpy/driver.py +28 -8
- sqlspec/adapters/psycopg/config.py +4 -73
- sqlspec/adapters/psycopg/driver.py +69 -24
- sqlspec/adapters/sqlite/config.py +3 -21
- sqlspec/adapters/sqlite/driver.py +50 -26
- sqlspec/cli.py +248 -0
- sqlspec/config.py +18 -20
- sqlspec/driver/_async.py +28 -10
- sqlspec/driver/_common.py +5 -4
- sqlspec/driver/_sync.py +28 -10
- sqlspec/driver/mixins/__init__.py +6 -0
- sqlspec/driver/mixins/_cache.py +114 -0
- sqlspec/driver/mixins/_pipeline.py +0 -4
- sqlspec/{service/base.py → driver/mixins/_query_tools.py} +86 -421
- sqlspec/driver/mixins/_result_utils.py +0 -2
- sqlspec/driver/mixins/_sql_translator.py +0 -2
- sqlspec/driver/mixins/_storage.py +4 -18
- sqlspec/driver/mixins/_type_coercion.py +0 -2
- sqlspec/driver/parameters.py +4 -4
- sqlspec/extensions/aiosql/adapter.py +4 -4
- sqlspec/extensions/litestar/__init__.py +2 -1
- sqlspec/extensions/litestar/cli.py +48 -0
- sqlspec/extensions/litestar/plugin.py +3 -0
- sqlspec/loader.py +1 -1
- sqlspec/migrations/__init__.py +23 -0
- sqlspec/migrations/base.py +390 -0
- sqlspec/migrations/commands.py +525 -0
- sqlspec/migrations/runner.py +215 -0
- sqlspec/migrations/tracker.py +153 -0
- sqlspec/migrations/utils.py +89 -0
- sqlspec/protocols.py +37 -3
- sqlspec/statement/builder/__init__.py +8 -8
- sqlspec/statement/builder/{column.py → _column.py} +82 -52
- sqlspec/statement/builder/{ddl.py → _ddl.py} +5 -5
- sqlspec/statement/builder/_ddl_utils.py +1 -1
- sqlspec/statement/builder/{delete.py → _delete.py} +1 -1
- sqlspec/statement/builder/{insert.py → _insert.py} +1 -1
- sqlspec/statement/builder/{merge.py → _merge.py} +1 -1
- sqlspec/statement/builder/_parsing_utils.py +5 -3
- sqlspec/statement/builder/{select.py → _select.py} +59 -61
- sqlspec/statement/builder/{update.py → _update.py} +2 -2
- sqlspec/statement/builder/mixins/__init__.py +24 -30
- sqlspec/statement/builder/mixins/{_set_ops.py → _cte_and_set_ops.py} +86 -2
- sqlspec/statement/builder/mixins/{_delete_from.py → _delete_operations.py} +2 -0
- sqlspec/statement/builder/mixins/{_insert_values.py → _insert_operations.py} +70 -1
- sqlspec/statement/builder/mixins/{_merge_clauses.py → _merge_operations.py} +2 -0
- sqlspec/statement/builder/mixins/_order_limit_operations.py +123 -0
- sqlspec/statement/builder/mixins/{_pivot.py → _pivot_operations.py} +71 -2
- sqlspec/statement/builder/mixins/_select_operations.py +612 -0
- sqlspec/statement/builder/mixins/{_update_set.py → _update_operations.py} +73 -2
- sqlspec/statement/builder/mixins/_where_clause.py +536 -0
- sqlspec/statement/cache.py +50 -0
- sqlspec/statement/filters.py +37 -8
- sqlspec/statement/parameters.py +143 -54
- sqlspec/statement/pipelines/__init__.py +1 -1
- sqlspec/statement/pipelines/context.py +4 -10
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +3 -3
- sqlspec/statement/pipelines/validators/_parameter_style.py +22 -22
- sqlspec/statement/pipelines/validators/_performance.py +1 -5
- sqlspec/statement/sql.py +246 -176
- sqlspec/utils/__init__.py +2 -1
- sqlspec/utils/statement_hashing.py +203 -0
- sqlspec/utils/type_guards.py +32 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.14.1.dist-info}/METADATA +1 -1
- sqlspec-0.14.1.dist-info/RECORD +145 -0
- sqlspec-0.14.1.dist-info/entry_points.txt +2 -0
- sqlspec/service/__init__.py +0 -4
- sqlspec/service/_util.py +0 -147
- sqlspec/service/pagination.py +0 -26
- 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/_limit_offset.py +0 -53
- sqlspec/statement/builder/mixins/_order_by.py +0 -46
- sqlspec/statement/builder/mixins/_returning.py +0 -37
- sqlspec/statement/builder/mixins/_select_columns.py +0 -61
- sqlspec/statement/builder/mixins/_unpivot.py +0 -77
- sqlspec/statement/builder/mixins/_update_from.py +0 -55
- 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/parameter_manager.py +0 -220
- sqlspec/statement/sql_compiler.py +0 -140
- sqlspec-0.13.1.dist-info/RECORD +0 -150
- /sqlspec/statement/builder/{base.py → _base.py} +0 -0
- /sqlspec/statement/builder/mixins/{_join.py → _join_operations.py} +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.14.1.dist-info}/WHEEL +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.14.1.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.14.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -22,7 +22,6 @@ if TYPE_CHECKING:
|
|
|
22
22
|
from collections.abc import AsyncGenerator, Callable, Generator
|
|
23
23
|
|
|
24
24
|
from psycopg import Connection
|
|
25
|
-
from sqlglot.dialects.dialect import DialectType
|
|
26
25
|
|
|
27
26
|
logger = logging.getLogger("sqlspec.adapters.psycopg")
|
|
28
27
|
|
|
@@ -67,39 +66,6 @@ __all__ = ("CONNECTION_FIELDS", "POOL_FIELDS", "PsycopgAsyncConfig", "PsycopgSyn
|
|
|
67
66
|
class PsycopgSyncConfig(SyncDatabaseConfig[PsycopgSyncConnection, ConnectionPool, PsycopgSyncDriver]):
|
|
68
67
|
"""Configuration for Psycopg synchronous database connections with direct field-based configuration."""
|
|
69
68
|
|
|
70
|
-
__slots__ = (
|
|
71
|
-
"_dialect",
|
|
72
|
-
"application_name",
|
|
73
|
-
"autocommit",
|
|
74
|
-
"configure",
|
|
75
|
-
"connect_timeout",
|
|
76
|
-
"conninfo",
|
|
77
|
-
"dbname",
|
|
78
|
-
"default_row_type",
|
|
79
|
-
"extras",
|
|
80
|
-
"host",
|
|
81
|
-
"kwargs",
|
|
82
|
-
"max_idle",
|
|
83
|
-
"max_lifetime",
|
|
84
|
-
"max_size",
|
|
85
|
-
"max_waiting",
|
|
86
|
-
"min_size",
|
|
87
|
-
"name",
|
|
88
|
-
"num_workers",
|
|
89
|
-
"options",
|
|
90
|
-
"password",
|
|
91
|
-
"pool_instance",
|
|
92
|
-
"port",
|
|
93
|
-
"reconnect_timeout",
|
|
94
|
-
"sslcert",
|
|
95
|
-
"sslkey",
|
|
96
|
-
"sslmode",
|
|
97
|
-
"sslrootcert",
|
|
98
|
-
"statement_config",
|
|
99
|
-
"timeout",
|
|
100
|
-
"user",
|
|
101
|
-
)
|
|
102
|
-
|
|
103
69
|
is_async: ClassVar[bool] = False
|
|
104
70
|
supports_connection_pooling: ClassVar[bool] = True
|
|
105
71
|
|
|
@@ -110,7 +76,7 @@ class PsycopgSyncConfig(SyncDatabaseConfig[PsycopgSyncConnection, ConnectionPool
|
|
|
110
76
|
supported_parameter_styles: ClassVar[tuple[str, ...]] = ("pyformat_positional", "pyformat_named")
|
|
111
77
|
"""Psycopg supports %s (positional) and %(name)s (named) parameter styles."""
|
|
112
78
|
|
|
113
|
-
|
|
79
|
+
default_parameter_style: ClassVar[str] = "pyformat_positional"
|
|
114
80
|
"""Psycopg's native parameter style is %s (pyformat positional)."""
|
|
115
81
|
|
|
116
82
|
def __init__(
|
|
@@ -216,7 +182,6 @@ class PsycopgSyncConfig(SyncDatabaseConfig[PsycopgSyncConnection, ConnectionPool
|
|
|
216
182
|
# Store other config
|
|
217
183
|
self.statement_config = statement_config or SQLConfig()
|
|
218
184
|
self.default_row_type = default_row_type
|
|
219
|
-
self._dialect: DialectType = None
|
|
220
185
|
|
|
221
186
|
super().__init__()
|
|
222
187
|
|
|
@@ -385,7 +350,7 @@ class PsycopgSyncConfig(SyncDatabaseConfig[PsycopgSyncConnection, ConnectionPool
|
|
|
385
350
|
statement_config = replace(
|
|
386
351
|
statement_config,
|
|
387
352
|
allowed_parameter_styles=self.supported_parameter_styles,
|
|
388
|
-
|
|
353
|
+
default_parameter_style=self.default_parameter_style,
|
|
389
354
|
)
|
|
390
355
|
driver = self.driver_type(connection=conn, config=statement_config)
|
|
391
356
|
yield driver
|
|
@@ -417,39 +382,6 @@ class PsycopgSyncConfig(SyncDatabaseConfig[PsycopgSyncConnection, ConnectionPool
|
|
|
417
382
|
class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnectionPool, PsycopgAsyncDriver]):
|
|
418
383
|
"""Configuration for Psycopg asynchronous database connections with direct field-based configuration."""
|
|
419
384
|
|
|
420
|
-
__slots__ = (
|
|
421
|
-
"_dialect",
|
|
422
|
-
"application_name",
|
|
423
|
-
"autocommit",
|
|
424
|
-
"configure",
|
|
425
|
-
"connect_timeout",
|
|
426
|
-
"conninfo",
|
|
427
|
-
"dbname",
|
|
428
|
-
"default_row_type",
|
|
429
|
-
"extras",
|
|
430
|
-
"host",
|
|
431
|
-
"kwargs",
|
|
432
|
-
"max_idle",
|
|
433
|
-
"max_lifetime",
|
|
434
|
-
"max_size",
|
|
435
|
-
"max_waiting",
|
|
436
|
-
"min_size",
|
|
437
|
-
"name",
|
|
438
|
-
"num_workers",
|
|
439
|
-
"options",
|
|
440
|
-
"password",
|
|
441
|
-
"pool_instance",
|
|
442
|
-
"port",
|
|
443
|
-
"reconnect_timeout",
|
|
444
|
-
"sslcert",
|
|
445
|
-
"sslkey",
|
|
446
|
-
"sslmode",
|
|
447
|
-
"sslrootcert",
|
|
448
|
-
"statement_config",
|
|
449
|
-
"timeout",
|
|
450
|
-
"user",
|
|
451
|
-
)
|
|
452
|
-
|
|
453
385
|
is_async: ClassVar[bool] = True
|
|
454
386
|
supports_connection_pooling: ClassVar[bool] = True
|
|
455
387
|
|
|
@@ -461,7 +393,7 @@ class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnec
|
|
|
461
393
|
supported_parameter_styles: ClassVar[tuple[str, ...]] = ("pyformat_positional", "pyformat_named")
|
|
462
394
|
"""Psycopg supports %s (pyformat_positional) and %(name)s (pyformat_named) parameter styles."""
|
|
463
395
|
|
|
464
|
-
|
|
396
|
+
default_parameter_style: ClassVar[str] = "pyformat_positional"
|
|
465
397
|
"""Psycopg's preferred parameter style is %s (pyformat_positional)."""
|
|
466
398
|
|
|
467
399
|
def __init__(
|
|
@@ -567,7 +499,6 @@ class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnec
|
|
|
567
499
|
# Store other config
|
|
568
500
|
self.statement_config = statement_config or SQLConfig()
|
|
569
501
|
self.default_row_type = default_row_type
|
|
570
|
-
self._dialect: DialectType = None
|
|
571
502
|
|
|
572
503
|
super().__init__()
|
|
573
504
|
|
|
@@ -726,7 +657,7 @@ class PsycopgAsyncConfig(AsyncDatabaseConfig[PsycopgAsyncConnection, AsyncConnec
|
|
|
726
657
|
statement_config = replace(
|
|
727
658
|
statement_config,
|
|
728
659
|
allowed_parameter_styles=self.supported_parameter_styles,
|
|
729
|
-
|
|
660
|
+
default_parameter_style=self.default_parameter_style,
|
|
730
661
|
)
|
|
731
662
|
driver = self.driver_type(connection=conn, config=statement_config)
|
|
732
663
|
yield driver
|
|
@@ -13,15 +13,17 @@ from sqlglot.dialects.dialect import DialectType
|
|
|
13
13
|
from sqlspec.driver import AsyncDriverAdapterProtocol, SyncDriverAdapterProtocol
|
|
14
14
|
from sqlspec.driver.connection import managed_transaction_async, managed_transaction_sync
|
|
15
15
|
from sqlspec.driver.mixins import (
|
|
16
|
+
AsyncAdapterCacheMixin,
|
|
16
17
|
AsyncPipelinedExecutionMixin,
|
|
17
18
|
AsyncStorageMixin,
|
|
18
19
|
SQLTranslatorMixin,
|
|
20
|
+
SyncAdapterCacheMixin,
|
|
19
21
|
SyncPipelinedExecutionMixin,
|
|
20
22
|
SyncStorageMixin,
|
|
21
23
|
ToSchemaMixin,
|
|
22
24
|
TypeCoercionMixin,
|
|
23
25
|
)
|
|
24
|
-
from sqlspec.driver.parameters import
|
|
26
|
+
from sqlspec.driver.parameters import convert_parameter_sequence
|
|
25
27
|
from sqlspec.exceptions import PipelineExecutionError
|
|
26
28
|
from sqlspec.statement.parameters import ParameterStyle, ParameterValidator
|
|
27
29
|
from sqlspec.statement.result import ArrowResult, SQLResult
|
|
@@ -43,6 +45,7 @@ PsycopgAsyncConnection = AsyncConnection[PsycopgDictRow]
|
|
|
43
45
|
|
|
44
46
|
class PsycopgSyncDriver(
|
|
45
47
|
SyncDriverAdapterProtocol[PsycopgSyncConnection, RowT],
|
|
48
|
+
SyncAdapterCacheMixin,
|
|
46
49
|
SQLTranslatorMixin,
|
|
47
50
|
TypeCoercionMixin,
|
|
48
51
|
SyncStorageMixin,
|
|
@@ -57,7 +60,6 @@ class PsycopgSyncDriver(
|
|
|
57
60
|
ParameterStyle.NAMED_PYFORMAT,
|
|
58
61
|
)
|
|
59
62
|
default_parameter_style: ParameterStyle = ParameterStyle.POSITIONAL_PYFORMAT
|
|
60
|
-
__slots__ = ()
|
|
61
63
|
|
|
62
64
|
def __init__(
|
|
63
65
|
self,
|
|
@@ -77,7 +79,7 @@ class PsycopgSyncDriver(
|
|
|
77
79
|
self, statement: SQL, connection: Optional[PsycopgSyncConnection] = None, **kwargs: Any
|
|
78
80
|
) -> SQLResult[RowT]:
|
|
79
81
|
if statement.is_script:
|
|
80
|
-
sql, _ =
|
|
82
|
+
sql, _ = self._get_compiled_sql(statement, ParameterStyle.STATIC)
|
|
81
83
|
return self._execute_script(sql, connection=connection, **kwargs)
|
|
82
84
|
|
|
83
85
|
detected_styles = set()
|
|
@@ -105,7 +107,7 @@ class PsycopgSyncDriver(
|
|
|
105
107
|
sql = statement.to_sql(placeholder_style=target_style)
|
|
106
108
|
params = kwargs_params
|
|
107
109
|
else:
|
|
108
|
-
sql, params =
|
|
110
|
+
sql, params = self._get_compiled_sql(statement, target_style)
|
|
109
111
|
if params is not None:
|
|
110
112
|
processed_params = [self._process_parameters(param_set) for param_set in params]
|
|
111
113
|
params = processed_params
|
|
@@ -120,7 +122,7 @@ class PsycopgSyncDriver(
|
|
|
120
122
|
sql = statement.to_sql(placeholder_style=target_style)
|
|
121
123
|
params = kwargs_params
|
|
122
124
|
else:
|
|
123
|
-
sql, params =
|
|
125
|
+
sql, params = self._get_compiled_sql(statement, target_style)
|
|
124
126
|
params = self._process_parameters(params)
|
|
125
127
|
|
|
126
128
|
# Fix over-nested parameters for Psycopg
|
|
@@ -231,8 +233,8 @@ class PsycopgSyncDriver(
|
|
|
231
233
|
|
|
232
234
|
with managed_transaction_sync(conn, auto_commit=True) as txn_conn:
|
|
233
235
|
# Normalize parameter list using consolidated utility
|
|
234
|
-
|
|
235
|
-
final_param_list =
|
|
236
|
+
converted_param_list = convert_parameter_sequence(param_list)
|
|
237
|
+
final_param_list = converted_param_list or []
|
|
236
238
|
|
|
237
239
|
with self._get_cursor(txn_conn) as cursor:
|
|
238
240
|
cursor.executemany(sql, final_param_list)
|
|
@@ -256,15 +258,37 @@ class PsycopgSyncDriver(
|
|
|
256
258
|
conn = connection if connection is not None else self._connection(None)
|
|
257
259
|
|
|
258
260
|
with managed_transaction_sync(conn, auto_commit=True) as txn_conn, self._get_cursor(txn_conn) as cursor:
|
|
259
|
-
|
|
261
|
+
# Split script into individual statements for validation
|
|
262
|
+
statements = self._split_script_statements(script)
|
|
263
|
+
suppress_warnings = kwargs.get("_suppress_warnings", False)
|
|
264
|
+
|
|
265
|
+
executed_count = 0
|
|
266
|
+
total_rows = 0
|
|
267
|
+
last_status = None
|
|
268
|
+
|
|
269
|
+
# Execute each statement individually for better control and validation
|
|
270
|
+
for statement in statements:
|
|
271
|
+
if statement.strip():
|
|
272
|
+
# Validate each statement unless warnings suppressed
|
|
273
|
+
if not suppress_warnings:
|
|
274
|
+
# Run validation through pipeline
|
|
275
|
+
temp_sql = SQL(statement, config=self.config)
|
|
276
|
+
temp_sql._ensure_processed()
|
|
277
|
+
# Validation errors are logged as warnings by default
|
|
278
|
+
|
|
279
|
+
cursor.execute(statement)
|
|
280
|
+
executed_count += 1
|
|
281
|
+
total_rows += cursor.rowcount or 0
|
|
282
|
+
last_status = cursor.statusmessage
|
|
283
|
+
|
|
260
284
|
return SQLResult(
|
|
261
285
|
statement=SQL(script, _dialect=self.dialect).as_script(),
|
|
262
286
|
data=[],
|
|
263
|
-
rows_affected=
|
|
287
|
+
rows_affected=total_rows,
|
|
264
288
|
operation_type="SCRIPT",
|
|
265
|
-
metadata={"status_message":
|
|
266
|
-
total_statements=
|
|
267
|
-
successful_statements=
|
|
289
|
+
metadata={"status_message": last_status or "SCRIPT EXECUTED"},
|
|
290
|
+
total_statements=executed_count,
|
|
291
|
+
successful_statements=executed_count,
|
|
268
292
|
)
|
|
269
293
|
|
|
270
294
|
def _ingest_arrow_table(self, table: "Any", table_name: str, mode: str = "append", **options: Any) -> int:
|
|
@@ -471,6 +495,7 @@ class PsycopgSyncDriver(
|
|
|
471
495
|
|
|
472
496
|
class PsycopgAsyncDriver(
|
|
473
497
|
AsyncDriverAdapterProtocol[PsycopgAsyncConnection, RowT],
|
|
498
|
+
AsyncAdapterCacheMixin,
|
|
474
499
|
SQLTranslatorMixin,
|
|
475
500
|
TypeCoercionMixin,
|
|
476
501
|
AsyncStorageMixin,
|
|
@@ -485,7 +510,6 @@ class PsycopgAsyncDriver(
|
|
|
485
510
|
ParameterStyle.NAMED_PYFORMAT,
|
|
486
511
|
)
|
|
487
512
|
default_parameter_style: ParameterStyle = ParameterStyle.POSITIONAL_PYFORMAT
|
|
488
|
-
__slots__ = ()
|
|
489
513
|
|
|
490
514
|
def __init__(
|
|
491
515
|
self,
|
|
@@ -505,7 +529,7 @@ class PsycopgAsyncDriver(
|
|
|
505
529
|
self, statement: SQL, connection: Optional[PsycopgAsyncConnection] = None, **kwargs: Any
|
|
506
530
|
) -> SQLResult[RowT]:
|
|
507
531
|
if statement.is_script:
|
|
508
|
-
sql, _ =
|
|
532
|
+
sql, _ = self._get_compiled_sql(statement, ParameterStyle.STATIC)
|
|
509
533
|
return await self._execute_script(sql, connection=connection, **kwargs)
|
|
510
534
|
|
|
511
535
|
detected_styles = set()
|
|
@@ -535,8 +559,7 @@ class PsycopgAsyncDriver(
|
|
|
535
559
|
sql = statement.to_sql(placeholder_style=target_style)
|
|
536
560
|
params = kwargs_params
|
|
537
561
|
else:
|
|
538
|
-
sql,
|
|
539
|
-
params = statement.parameters
|
|
562
|
+
sql, params = self._get_compiled_sql(statement, target_style)
|
|
540
563
|
if params is not None:
|
|
541
564
|
processed_params = [self._process_parameters(param_set) for param_set in params]
|
|
542
565
|
params = processed_params
|
|
@@ -560,7 +583,7 @@ class PsycopgAsyncDriver(
|
|
|
560
583
|
sql = statement.to_sql(placeholder_style=target_style)
|
|
561
584
|
params = kwargs_params
|
|
562
585
|
else:
|
|
563
|
-
sql, params =
|
|
586
|
+
sql, params = self._get_compiled_sql(statement, target_style)
|
|
564
587
|
params = self._process_parameters(params)
|
|
565
588
|
|
|
566
589
|
# Fix over-nested parameters for Psycopg
|
|
@@ -683,8 +706,8 @@ class PsycopgAsyncDriver(
|
|
|
683
706
|
|
|
684
707
|
async with managed_transaction_async(conn, auto_commit=True) as txn_conn:
|
|
685
708
|
# Normalize parameter list using consolidated utility
|
|
686
|
-
|
|
687
|
-
final_param_list =
|
|
709
|
+
converted_param_list = convert_parameter_sequence(param_list)
|
|
710
|
+
final_param_list = converted_param_list or []
|
|
688
711
|
|
|
689
712
|
async with txn_conn.cursor() as cursor:
|
|
690
713
|
await cursor.executemany(cast("Query", sql), final_param_list)
|
|
@@ -703,15 +726,37 @@ class PsycopgAsyncDriver(
|
|
|
703
726
|
conn = connection if connection is not None else self._connection(None)
|
|
704
727
|
|
|
705
728
|
async with managed_transaction_async(conn, auto_commit=True) as txn_conn, txn_conn.cursor() as cursor:
|
|
706
|
-
|
|
729
|
+
# Split script into individual statements for validation
|
|
730
|
+
statements = self._split_script_statements(script)
|
|
731
|
+
suppress_warnings = kwargs.get("_suppress_warnings", False)
|
|
732
|
+
|
|
733
|
+
executed_count = 0
|
|
734
|
+
total_rows = 0
|
|
735
|
+
last_status = None
|
|
736
|
+
|
|
737
|
+
# Execute each statement individually for better control and validation
|
|
738
|
+
for statement in statements:
|
|
739
|
+
if statement.strip():
|
|
740
|
+
# Validate each statement unless warnings suppressed
|
|
741
|
+
if not suppress_warnings:
|
|
742
|
+
# Run validation through pipeline
|
|
743
|
+
temp_sql = SQL(statement, config=self.config)
|
|
744
|
+
temp_sql._ensure_processed()
|
|
745
|
+
# Validation errors are logged as warnings by default
|
|
746
|
+
|
|
747
|
+
await cursor.execute(cast("Query", statement))
|
|
748
|
+
executed_count += 1
|
|
749
|
+
total_rows += cursor.rowcount or 0
|
|
750
|
+
last_status = cursor.statusmessage
|
|
751
|
+
|
|
707
752
|
return SQLResult(
|
|
708
753
|
statement=SQL(script, _dialect=self.dialect).as_script(),
|
|
709
754
|
data=[],
|
|
710
|
-
rows_affected=
|
|
755
|
+
rows_affected=total_rows,
|
|
711
756
|
operation_type="SCRIPT",
|
|
712
|
-
metadata={"status_message":
|
|
713
|
-
total_statements=
|
|
714
|
-
successful_statements=
|
|
757
|
+
metadata={"status_message": last_status or "SCRIPT EXECUTED"},
|
|
758
|
+
total_statements=executed_count,
|
|
759
|
+
successful_statements=executed_count,
|
|
715
760
|
)
|
|
716
761
|
|
|
717
762
|
async def _fetch_arrow_table(self, sql: SQL, connection: "Optional[Any]" = None, **kwargs: Any) -> "ArrowResult":
|
|
@@ -13,7 +13,6 @@ from sqlspec.typing import DictRow
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
from collections.abc import Generator
|
|
15
15
|
|
|
16
|
-
from sqlglot.dialects.dialect import DialectType
|
|
17
16
|
|
|
18
17
|
logger = logging.getLogger(__name__)
|
|
19
18
|
|
|
@@ -36,26 +35,10 @@ __all__ = ("CONNECTION_FIELDS", "SqliteConfig", "sqlite3")
|
|
|
36
35
|
class SqliteConfig(NoPoolSyncConfig[SqliteConnection, SqliteDriver]):
|
|
37
36
|
"""Configuration for SQLite database connections with direct field-based configuration."""
|
|
38
37
|
|
|
39
|
-
__slots__ = (
|
|
40
|
-
"_dialect",
|
|
41
|
-
"cached_statements",
|
|
42
|
-
"check_same_thread",
|
|
43
|
-
"database",
|
|
44
|
-
"default_row_type",
|
|
45
|
-
"detect_types",
|
|
46
|
-
"extras",
|
|
47
|
-
"factory",
|
|
48
|
-
"isolation_level",
|
|
49
|
-
"pool_instance",
|
|
50
|
-
"statement_config",
|
|
51
|
-
"timeout",
|
|
52
|
-
"uri",
|
|
53
|
-
)
|
|
54
|
-
|
|
55
38
|
driver_type: type[SqliteDriver] = SqliteDriver
|
|
56
39
|
connection_type: type[SqliteConnection] = SqliteConnection
|
|
57
40
|
supported_parameter_styles: ClassVar[tuple[str, ...]] = ("qmark", "named_colon")
|
|
58
|
-
|
|
41
|
+
default_parameter_style: ClassVar[str] = "qmark"
|
|
59
42
|
|
|
60
43
|
def __init__(
|
|
61
44
|
self,
|
|
@@ -65,7 +48,7 @@ class SqliteConfig(NoPoolSyncConfig[SqliteConnection, SqliteDriver]):
|
|
|
65
48
|
# SQLite connection parameters
|
|
66
49
|
timeout: Optional[float] = None,
|
|
67
50
|
detect_types: Optional[int] = None,
|
|
68
|
-
isolation_level:
|
|
51
|
+
isolation_level: Union[None, str] = None,
|
|
69
52
|
check_same_thread: Optional[bool] = None,
|
|
70
53
|
factory: Optional[type[SqliteConnection]] = None,
|
|
71
54
|
cached_statements: Optional[int] = None,
|
|
@@ -106,7 +89,6 @@ class SqliteConfig(NoPoolSyncConfig[SqliteConnection, SqliteDriver]):
|
|
|
106
89
|
# Store other config
|
|
107
90
|
self.statement_config = statement_config or SQLConfig()
|
|
108
91
|
self.default_row_type = default_row_type
|
|
109
|
-
self._dialect: DialectType = None
|
|
110
92
|
super().__init__()
|
|
111
93
|
|
|
112
94
|
@property
|
|
@@ -169,6 +151,6 @@ class SqliteConfig(NoPoolSyncConfig[SqliteConnection, SqliteDriver]):
|
|
|
169
151
|
statement_config = replace(
|
|
170
152
|
statement_config,
|
|
171
153
|
allowed_parameter_styles=self.supported_parameter_styles,
|
|
172
|
-
|
|
154
|
+
default_parameter_style=self.default_parameter_style,
|
|
173
155
|
)
|
|
174
156
|
yield self.driver_type(connection=connection, config=statement_config)
|
|
@@ -12,12 +12,14 @@ from sqlspec.driver import SyncDriverAdapterProtocol
|
|
|
12
12
|
from sqlspec.driver.connection import managed_transaction_sync
|
|
13
13
|
from sqlspec.driver.mixins import (
|
|
14
14
|
SQLTranslatorMixin,
|
|
15
|
+
SyncAdapterCacheMixin,
|
|
15
16
|
SyncPipelinedExecutionMixin,
|
|
17
|
+
SyncQueryMixin,
|
|
16
18
|
SyncStorageMixin,
|
|
17
19
|
ToSchemaMixin,
|
|
18
20
|
TypeCoercionMixin,
|
|
19
21
|
)
|
|
20
|
-
from sqlspec.driver.parameters import
|
|
22
|
+
from sqlspec.driver.parameters import convert_parameter_sequence
|
|
21
23
|
from sqlspec.statement.parameters import ParameterStyle, ParameterValidator
|
|
22
24
|
from sqlspec.statement.result import SQLResult
|
|
23
25
|
from sqlspec.statement.sql import SQL, SQLConfig
|
|
@@ -37,10 +39,12 @@ SqliteConnection: TypeAlias = sqlite3.Connection
|
|
|
37
39
|
|
|
38
40
|
class SqliteDriver(
|
|
39
41
|
SyncDriverAdapterProtocol[SqliteConnection, RowT],
|
|
42
|
+
SyncAdapterCacheMixin,
|
|
40
43
|
SQLTranslatorMixin,
|
|
41
44
|
TypeCoercionMixin,
|
|
42
45
|
SyncStorageMixin,
|
|
43
46
|
SyncPipelinedExecutionMixin,
|
|
47
|
+
SyncQueryMixin,
|
|
44
48
|
ToSchemaMixin,
|
|
45
49
|
):
|
|
46
50
|
"""SQLite Sync Driver Adapter with Arrow/Parquet export support.
|
|
@@ -49,8 +53,6 @@ class SqliteDriver(
|
|
|
49
53
|
instrumentation standards following the psycopg pattern.
|
|
50
54
|
"""
|
|
51
55
|
|
|
52
|
-
__slots__ = ()
|
|
53
|
-
|
|
54
56
|
dialect: "DialectType" = "sqlite"
|
|
55
57
|
supported_parameter_styles: "tuple[ParameterStyle, ...]" = (ParameterStyle.QMARK, ParameterStyle.NAMED_COLON)
|
|
56
58
|
default_parameter_style: ParameterStyle = ParameterStyle.QMARK
|
|
@@ -106,7 +108,7 @@ class SqliteDriver(
|
|
|
106
108
|
self, statement: SQL, connection: Optional[SqliteConnection] = None, **kwargs: Any
|
|
107
109
|
) -> SQLResult[RowT]:
|
|
108
110
|
if statement.is_script:
|
|
109
|
-
sql, _ =
|
|
111
|
+
sql, _ = self._get_compiled_sql(statement, ParameterStyle.STATIC)
|
|
110
112
|
return self._execute_script(sql, connection=connection, statement=statement, **kwargs)
|
|
111
113
|
|
|
112
114
|
detected_styles = set()
|
|
@@ -126,17 +128,17 @@ class SqliteDriver(
|
|
|
126
128
|
target_style = self.default_parameter_style
|
|
127
129
|
elif detected_styles:
|
|
128
130
|
# Single style detected - use it if supported
|
|
129
|
-
|
|
130
|
-
if
|
|
131
|
-
target_style =
|
|
131
|
+
detected_style = next(iter(detected_styles))
|
|
132
|
+
if detected_style.value in self.supported_parameter_styles:
|
|
133
|
+
target_style = detected_style
|
|
132
134
|
else:
|
|
133
135
|
target_style = self.default_parameter_style
|
|
134
136
|
|
|
135
137
|
if statement.is_many:
|
|
136
|
-
sql, params =
|
|
138
|
+
sql, params = self._get_compiled_sql(statement, target_style)
|
|
137
139
|
return self._execute_many(sql, params, connection=connection, statement=statement, **kwargs)
|
|
138
140
|
|
|
139
|
-
sql, params =
|
|
141
|
+
sql, params = self._get_compiled_sql(statement, target_style)
|
|
140
142
|
|
|
141
143
|
params = self._process_parameters(params)
|
|
142
144
|
|
|
@@ -153,18 +155,18 @@ class SqliteDriver(
|
|
|
153
155
|
# Use provided connection or driver's default connection
|
|
154
156
|
conn = connection if connection is not None else self._connection(None)
|
|
155
157
|
with managed_transaction_sync(conn, auto_commit=True) as txn_conn, self._get_cursor(txn_conn) as cursor:
|
|
156
|
-
#
|
|
157
|
-
|
|
158
|
+
# Convert parameters using consolidated utility
|
|
159
|
+
converted_params_list = convert_parameter_sequence(parameters)
|
|
158
160
|
params_for_execute: Any
|
|
159
|
-
if
|
|
161
|
+
if converted_params_list and len(converted_params_list) == 1:
|
|
160
162
|
# Single parameter should be tuple for SQLite
|
|
161
|
-
if not isinstance(
|
|
162
|
-
params_for_execute = (
|
|
163
|
+
if not isinstance(converted_params_list[0], (tuple, list, dict)):
|
|
164
|
+
params_for_execute = (converted_params_list[0],)
|
|
163
165
|
else:
|
|
164
|
-
params_for_execute =
|
|
166
|
+
params_for_execute = converted_params_list[0]
|
|
165
167
|
else:
|
|
166
168
|
# Multiple parameters
|
|
167
|
-
params_for_execute = tuple(
|
|
169
|
+
params_for_execute = tuple(converted_params_list) if converted_params_list else ()
|
|
168
170
|
|
|
169
171
|
cursor.execute(sql, params_for_execute)
|
|
170
172
|
if self.returns_rows(statement.expression):
|
|
@@ -199,10 +201,10 @@ class SqliteDriver(
|
|
|
199
201
|
conn = connection if connection is not None else self._connection(None)
|
|
200
202
|
with managed_transaction_sync(conn, auto_commit=True) as txn_conn:
|
|
201
203
|
# Normalize parameter list using consolidated utility
|
|
202
|
-
|
|
204
|
+
converted_param_list = convert_parameter_sequence(param_list)
|
|
203
205
|
formatted_params: list[tuple[Any, ...]] = []
|
|
204
|
-
if
|
|
205
|
-
for param_set in
|
|
206
|
+
if converted_param_list:
|
|
207
|
+
for param_set in converted_param_list:
|
|
206
208
|
if isinstance(param_set, (list, tuple)):
|
|
207
209
|
formatted_params.append(tuple(param_set))
|
|
208
210
|
elif param_set is None:
|
|
@@ -227,12 +229,34 @@ class SqliteDriver(
|
|
|
227
229
|
def _execute_script(
|
|
228
230
|
self, script: str, connection: Optional[SqliteConnection] = None, statement: Optional[SQL] = None, **kwargs: Any
|
|
229
231
|
) -> SQLResult[RowT]:
|
|
230
|
-
"""Execute
|
|
231
|
-
|
|
232
|
+
"""Execute script using splitter for per-statement validation."""
|
|
233
|
+
from sqlspec.statement.splitter import split_sql_script
|
|
234
|
+
|
|
232
235
|
conn = connection if connection is not None else self._connection(None)
|
|
236
|
+
statements = split_sql_script(script, dialect="sqlite")
|
|
237
|
+
|
|
238
|
+
total_rows = 0
|
|
239
|
+
successful = 0
|
|
240
|
+
suppress_warnings = kwargs.get("_suppress_warnings", False)
|
|
241
|
+
|
|
233
242
|
with self._get_cursor(conn) as cursor:
|
|
234
|
-
|
|
235
|
-
|
|
243
|
+
for stmt in statements:
|
|
244
|
+
try:
|
|
245
|
+
# Validate each statement unless warnings suppressed
|
|
246
|
+
if not suppress_warnings and statement:
|
|
247
|
+
# Run validation through pipeline
|
|
248
|
+
temp_sql = SQL(stmt, config=statement._config)
|
|
249
|
+
temp_sql._ensure_processed()
|
|
250
|
+
# Validation errors are logged as warnings by default
|
|
251
|
+
|
|
252
|
+
cursor.execute(stmt)
|
|
253
|
+
successful += 1
|
|
254
|
+
total_rows += cursor.rowcount or 0
|
|
255
|
+
except Exception as e: # noqa: PERF203
|
|
256
|
+
if not kwargs.get("continue_on_error", False):
|
|
257
|
+
raise
|
|
258
|
+
logger.warning("Script statement failed: %s", e)
|
|
259
|
+
|
|
236
260
|
conn.commit()
|
|
237
261
|
|
|
238
262
|
if statement is None:
|
|
@@ -241,10 +265,10 @@ class SqliteDriver(
|
|
|
241
265
|
return SQLResult(
|
|
242
266
|
statement=statement,
|
|
243
267
|
data=[],
|
|
244
|
-
rows_affected
|
|
268
|
+
rows_affected=total_rows,
|
|
245
269
|
operation_type="SCRIPT",
|
|
246
|
-
total_statements
|
|
247
|
-
successful_statements
|
|
270
|
+
total_statements=len(statements),
|
|
271
|
+
successful_statements=successful,
|
|
248
272
|
metadata={"status_message": "SCRIPT EXECUTED"},
|
|
249
273
|
)
|
|
250
274
|
|