sqlspec 0.17.0__py3-none-any.whl → 0.18.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 +1 -1
- sqlspec/_sql.py +188 -234
- sqlspec/adapters/adbc/config.py +24 -30
- sqlspec/adapters/adbc/driver.py +42 -61
- sqlspec/adapters/aiosqlite/config.py +5 -10
- sqlspec/adapters/aiosqlite/driver.py +9 -25
- sqlspec/adapters/aiosqlite/pool.py +43 -35
- sqlspec/adapters/asyncmy/config.py +10 -7
- sqlspec/adapters/asyncmy/driver.py +18 -39
- sqlspec/adapters/asyncpg/config.py +4 -0
- sqlspec/adapters/asyncpg/driver.py +32 -79
- sqlspec/adapters/bigquery/config.py +12 -65
- sqlspec/adapters/bigquery/driver.py +39 -133
- sqlspec/adapters/duckdb/config.py +11 -15
- sqlspec/adapters/duckdb/driver.py +61 -85
- sqlspec/adapters/duckdb/pool.py +2 -5
- sqlspec/adapters/oracledb/_types.py +8 -1
- sqlspec/adapters/oracledb/config.py +55 -38
- sqlspec/adapters/oracledb/driver.py +35 -92
- sqlspec/adapters/oracledb/migrations.py +257 -0
- sqlspec/adapters/psqlpy/config.py +13 -9
- sqlspec/adapters/psqlpy/driver.py +28 -103
- sqlspec/adapters/psycopg/config.py +9 -5
- sqlspec/adapters/psycopg/driver.py +107 -175
- sqlspec/adapters/sqlite/config.py +7 -5
- sqlspec/adapters/sqlite/driver.py +37 -73
- sqlspec/adapters/sqlite/pool.py +3 -12
- sqlspec/base.py +1 -8
- sqlspec/builder/__init__.py +1 -1
- sqlspec/builder/_base.py +34 -20
- sqlspec/builder/_column.py +5 -1
- sqlspec/builder/_ddl.py +407 -183
- sqlspec/builder/_expression_wrappers.py +46 -0
- sqlspec/builder/_insert.py +2 -4
- sqlspec/builder/_update.py +5 -5
- sqlspec/builder/mixins/_insert_operations.py +26 -6
- sqlspec/builder/mixins/_merge_operations.py +1 -1
- sqlspec/builder/mixins/_order_limit_operations.py +16 -4
- sqlspec/builder/mixins/_select_operations.py +3 -7
- sqlspec/builder/mixins/_update_operations.py +4 -4
- sqlspec/config.py +32 -13
- sqlspec/core/__init__.py +89 -14
- sqlspec/core/cache.py +57 -104
- sqlspec/core/compiler.py +57 -112
- sqlspec/core/filters.py +1 -21
- sqlspec/core/hashing.py +13 -47
- sqlspec/core/parameters.py +272 -261
- sqlspec/core/result.py +12 -27
- sqlspec/core/splitter.py +17 -21
- sqlspec/core/statement.py +150 -159
- sqlspec/driver/_async.py +2 -15
- sqlspec/driver/_common.py +16 -95
- sqlspec/driver/_sync.py +2 -15
- sqlspec/driver/mixins/_result_tools.py +8 -29
- sqlspec/driver/mixins/_sql_translator.py +6 -8
- sqlspec/exceptions.py +1 -2
- sqlspec/loader.py +43 -115
- sqlspec/migrations/__init__.py +1 -1
- sqlspec/migrations/base.py +34 -45
- sqlspec/migrations/commands.py +34 -15
- sqlspec/migrations/loaders.py +1 -1
- sqlspec/migrations/runner.py +104 -19
- sqlspec/migrations/tracker.py +49 -2
- sqlspec/protocols.py +13 -6
- sqlspec/storage/__init__.py +4 -4
- sqlspec/storage/backends/fsspec.py +5 -6
- sqlspec/storage/backends/obstore.py +7 -8
- sqlspec/storage/registry.py +3 -3
- sqlspec/utils/__init__.py +2 -2
- sqlspec/utils/logging.py +6 -10
- sqlspec/utils/sync_tools.py +27 -4
- sqlspec/utils/text.py +6 -1
- {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/METADATA +1 -1
- sqlspec-0.18.0.dist-info/RECORD +138 -0
- sqlspec/builder/_ddl_utils.py +0 -103
- sqlspec-0.17.0.dist-info/RECORD +0 -137
- {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -44,16 +44,18 @@ class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, Sq
|
|
|
44
44
|
*,
|
|
45
45
|
pool_config: "Optional[Union[SqliteConnectionParams, dict[str, Any]]]" = None,
|
|
46
46
|
pool_instance: "Optional[SqliteConnectionPool]" = None,
|
|
47
|
-
statement_config: "Optional[StatementConfig]" = None,
|
|
48
47
|
migration_config: "Optional[dict[str, Any]]" = None,
|
|
48
|
+
statement_config: "Optional[StatementConfig]" = None,
|
|
49
|
+
driver_features: "Optional[dict[str, Any]]" = None,
|
|
49
50
|
) -> None:
|
|
50
51
|
"""Initialize SQLite configuration.
|
|
51
52
|
|
|
52
53
|
Args:
|
|
53
54
|
pool_config: Configuration parameters including connection settings
|
|
54
55
|
pool_instance: Pre-created pool instance
|
|
55
|
-
statement_config: Default SQL statement configuration
|
|
56
56
|
migration_config: Migration configuration
|
|
57
|
+
statement_config: Default SQL statement configuration
|
|
58
|
+
driver_features: Optional driver feature configuration
|
|
57
59
|
"""
|
|
58
60
|
if pool_config is None:
|
|
59
61
|
pool_config = {}
|
|
@@ -66,19 +68,19 @@ class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, Sq
|
|
|
66
68
|
pool_config=cast("dict[str, Any]", pool_config),
|
|
67
69
|
migration_config=migration_config,
|
|
68
70
|
statement_config=statement_config or sqlite_statement_config,
|
|
69
|
-
driver_features={},
|
|
71
|
+
driver_features=driver_features or {},
|
|
70
72
|
)
|
|
71
73
|
|
|
72
74
|
def _get_connection_config_dict(self) -> "dict[str, Any]":
|
|
73
75
|
"""Get connection configuration as plain dict for pool creation."""
|
|
74
|
-
|
|
76
|
+
|
|
75
77
|
excluded_keys = {"pool_min_size", "pool_max_size", "pool_timeout", "pool_recycle_seconds", "extra"}
|
|
76
78
|
return {k: v for k, v in self.pool_config.items() if v is not None and k not in excluded_keys}
|
|
77
79
|
|
|
78
80
|
def _create_pool(self) -> SqliteConnectionPool:
|
|
79
81
|
"""Create connection pool from configuration."""
|
|
80
82
|
config_dict = self._get_connection_config_dict()
|
|
81
|
-
|
|
83
|
+
|
|
82
84
|
return SqliteConnectionPool(connection_parameters=config_dict, **self.pool_config)
|
|
83
85
|
|
|
84
86
|
def _close_pool(self) -> None:
|
|
@@ -1,18 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
This driver implements the complete CORE_ROUND_3 architecture for:
|
|
4
|
-
- 5-10x faster SQL compilation through single-pass processing
|
|
5
|
-
- 40-60% memory reduction through __slots__ optimization
|
|
6
|
-
- Enhanced caching for repeated statement execution
|
|
7
|
-
- Complete backward compatibility with existing functionality
|
|
8
|
-
|
|
9
|
-
Architecture Features:
|
|
10
|
-
- Direct integration with sqlspec.core modules
|
|
11
|
-
- Enhanced parameter processing with type coercion
|
|
12
|
-
- Thread-safe unified caching system
|
|
13
|
-
- MyPyC-optimized performance patterns
|
|
14
|
-
- Zero-copy data access where possible
|
|
15
|
-
"""
|
|
1
|
+
"""SQLite driver implementation."""
|
|
16
2
|
|
|
17
3
|
import contextlib
|
|
18
4
|
import datetime
|
|
@@ -38,7 +24,6 @@ if TYPE_CHECKING:
|
|
|
38
24
|
__all__ = ("SqliteCursor", "SqliteDriver", "SqliteExceptionHandler", "sqlite_statement_config")
|
|
39
25
|
|
|
40
26
|
|
|
41
|
-
# Enhanced SQLite statement configuration using core modules with performance optimizations
|
|
42
27
|
sqlite_statement_config = StatementConfig(
|
|
43
28
|
dialect="sqlite",
|
|
44
29
|
parameter_config=ParameterStyleConfig(
|
|
@@ -51,18 +36,12 @@ sqlite_statement_config = StatementConfig(
|
|
|
51
36
|
datetime.datetime: lambda v: v.isoformat(),
|
|
52
37
|
datetime.date: lambda v: v.isoformat(),
|
|
53
38
|
Decimal: str,
|
|
54
|
-
# Note: Don't auto-convert dicts to JSON for SQLite
|
|
55
|
-
# This interferes with named parameter processing in execute_many
|
|
56
|
-
# dict: to_json, # Removed to prevent parameter interference
|
|
57
39
|
list: to_json,
|
|
58
|
-
# Note: Don't convert tuples to JSON for SQLite as they're used as parameter sets
|
|
59
|
-
# tuple: lambda v: to_json(list(v)), # Removed - tuples are parameter sets
|
|
60
40
|
},
|
|
61
41
|
has_native_list_expansion=False,
|
|
62
42
|
needs_static_script_compilation=False,
|
|
63
43
|
preserve_parameter_format=True,
|
|
64
44
|
),
|
|
65
|
-
# Core processing features enabled for performance
|
|
66
45
|
enable_parsing=True,
|
|
67
46
|
enable_validation=True,
|
|
68
47
|
enable_caching=True,
|
|
@@ -71,7 +50,7 @@ sqlite_statement_config = StatementConfig(
|
|
|
71
50
|
|
|
72
51
|
|
|
73
52
|
class SqliteCursor:
|
|
74
|
-
"""Context manager for SQLite cursor management
|
|
53
|
+
"""Context manager for SQLite cursor management."""
|
|
75
54
|
|
|
76
55
|
__slots__ = ("connection", "cursor")
|
|
77
56
|
|
|
@@ -84,14 +63,14 @@ class SqliteCursor:
|
|
|
84
63
|
return self.cursor
|
|
85
64
|
|
|
86
65
|
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
87
|
-
_ = (exc_type, exc_val, exc_tb)
|
|
66
|
+
_ = (exc_type, exc_val, exc_tb)
|
|
88
67
|
if self.cursor is not None:
|
|
89
68
|
with contextlib.suppress(Exception):
|
|
90
69
|
self.cursor.close()
|
|
91
70
|
|
|
92
71
|
|
|
93
72
|
class SqliteExceptionHandler:
|
|
94
|
-
"""
|
|
73
|
+
"""Context manager for handling SQLite database exceptions."""
|
|
95
74
|
|
|
96
75
|
__slots__ = ()
|
|
97
76
|
|
|
@@ -135,27 +114,10 @@ class SqliteExceptionHandler:
|
|
|
135
114
|
|
|
136
115
|
|
|
137
116
|
class SqliteDriver(SyncDriverAdapterBase):
|
|
138
|
-
"""
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
Performance Improvements:
|
|
143
|
-
- 5-10x faster SQL compilation through single-pass processing
|
|
144
|
-
- 40-60% memory reduction through __slots__ optimization
|
|
145
|
-
- Enhanced caching for repeated statement execution
|
|
146
|
-
- Zero-copy parameter processing where possible
|
|
147
|
-
|
|
148
|
-
Core Integration Features:
|
|
149
|
-
- sqlspec.core.statement for enhanced SQL processing
|
|
150
|
-
- sqlspec.core.parameters for optimized parameter handling
|
|
151
|
-
- sqlspec.core.cache for unified statement caching
|
|
152
|
-
- sqlspec.core.config for centralized configuration management
|
|
153
|
-
|
|
154
|
-
Compatibility:
|
|
155
|
-
- 100% backward compatibility with existing SQLite driver interface
|
|
156
|
-
- All existing tests pass without modification
|
|
157
|
-
- Complete StatementConfig API compatibility
|
|
158
|
-
- Preserved cursor management and exception handling patterns
|
|
117
|
+
"""SQLite driver implementation.
|
|
118
|
+
|
|
119
|
+
Provides SQL statement execution, transaction management, and result handling
|
|
120
|
+
for SQLite databases using the standard sqlite3 module.
|
|
159
121
|
"""
|
|
160
122
|
|
|
161
123
|
__slots__ = ()
|
|
@@ -167,21 +129,19 @@ class SqliteDriver(SyncDriverAdapterBase):
|
|
|
167
129
|
statement_config: "Optional[StatementConfig]" = None,
|
|
168
130
|
driver_features: "Optional[dict[str, Any]]" = None,
|
|
169
131
|
) -> None:
|
|
170
|
-
# Enhanced configuration with global settings integration
|
|
171
132
|
if statement_config is None:
|
|
172
133
|
cache_config = get_cache_config()
|
|
173
|
-
|
|
134
|
+
statement_config = sqlite_statement_config.replace(
|
|
174
135
|
enable_caching=cache_config.compiled_cache_enabled,
|
|
175
|
-
enable_parsing=True,
|
|
176
|
-
enable_validation=True,
|
|
177
|
-
dialect="sqlite",
|
|
136
|
+
enable_parsing=True,
|
|
137
|
+
enable_validation=True,
|
|
138
|
+
dialect="sqlite",
|
|
178
139
|
)
|
|
179
|
-
statement_config = enhanced_config
|
|
180
140
|
|
|
181
141
|
super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
|
|
182
142
|
|
|
183
143
|
def with_cursor(self, connection: "SqliteConnection") -> "SqliteCursor":
|
|
184
|
-
"""Create context manager for SQLite cursor
|
|
144
|
+
"""Create context manager for SQLite cursor."""
|
|
185
145
|
return SqliteCursor(connection)
|
|
186
146
|
|
|
187
147
|
def handle_database_exceptions(self) -> "AbstractContextManager[None]":
|
|
@@ -191,9 +151,6 @@ class SqliteDriver(SyncDriverAdapterBase):
|
|
|
191
151
|
def _try_special_handling(self, cursor: "sqlite3.Cursor", statement: "SQL") -> "Optional[SQLResult]":
|
|
192
152
|
"""Hook for SQLite-specific special operations.
|
|
193
153
|
|
|
194
|
-
SQLite doesn't have complex special operations like PostgreSQL COPY,
|
|
195
|
-
so this always returns None to proceed with standard execution.
|
|
196
|
-
|
|
197
154
|
Args:
|
|
198
155
|
cursor: SQLite cursor object
|
|
199
156
|
statement: SQL statement to analyze
|
|
@@ -204,10 +161,14 @@ class SqliteDriver(SyncDriverAdapterBase):
|
|
|
204
161
|
return None
|
|
205
162
|
|
|
206
163
|
def _execute_script(self, cursor: "sqlite3.Cursor", statement: "SQL") -> "ExecutionResult":
|
|
207
|
-
"""Execute SQL script
|
|
164
|
+
"""Execute SQL script with statement splitting and parameter handling.
|
|
208
165
|
|
|
209
|
-
|
|
210
|
-
|
|
166
|
+
Args:
|
|
167
|
+
cursor: SQLite cursor object
|
|
168
|
+
statement: SQL statement containing multiple statements
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
ExecutionResult with script execution details
|
|
211
172
|
"""
|
|
212
173
|
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
213
174
|
statements = self.split_script_statements(sql, statement.statement_config, strip_trailing_semicolon=True)
|
|
@@ -224,34 +185,40 @@ class SqliteDriver(SyncDriverAdapterBase):
|
|
|
224
185
|
)
|
|
225
186
|
|
|
226
187
|
def _execute_many(self, cursor: "sqlite3.Cursor", statement: "SQL") -> "ExecutionResult":
|
|
227
|
-
"""Execute SQL with multiple parameter sets
|
|
188
|
+
"""Execute SQL with multiple parameter sets.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
cursor: SQLite cursor object
|
|
192
|
+
statement: SQL statement with multiple parameter sets
|
|
228
193
|
|
|
229
|
-
|
|
194
|
+
Returns:
|
|
195
|
+
ExecutionResult with batch execution details
|
|
230
196
|
"""
|
|
231
197
|
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
232
198
|
|
|
233
|
-
# Enhanced parameter validation for executemany
|
|
234
199
|
if not prepared_parameters:
|
|
235
200
|
msg = "execute_many requires parameters"
|
|
236
201
|
raise ValueError(msg)
|
|
237
202
|
|
|
238
|
-
# Ensure prepared_parameters is a list of parameter sets for SQLite executemany
|
|
239
203
|
cursor.executemany(sql, prepared_parameters)
|
|
240
204
|
|
|
241
|
-
# Calculate affected rows more accurately
|
|
242
205
|
affected_rows = cursor.rowcount if cursor.rowcount and cursor.rowcount > 0 else 0
|
|
243
206
|
|
|
244
207
|
return self.create_execution_result(cursor, rowcount_override=affected_rows, is_many_result=True)
|
|
245
208
|
|
|
246
209
|
def _execute_statement(self, cursor: "sqlite3.Cursor", statement: "SQL") -> "ExecutionResult":
|
|
247
|
-
"""Execute single SQL statement
|
|
210
|
+
"""Execute single SQL statement.
|
|
248
211
|
|
|
249
|
-
|
|
212
|
+
Args:
|
|
213
|
+
cursor: SQLite cursor object
|
|
214
|
+
statement: SQL statement to execute
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
ExecutionResult with statement execution details
|
|
250
218
|
"""
|
|
251
219
|
sql, prepared_parameters = self._get_compiled_sql(statement, self.statement_config)
|
|
252
220
|
cursor.execute(sql, prepared_parameters or ())
|
|
253
221
|
|
|
254
|
-
# Enhanced SELECT result processing
|
|
255
222
|
if statement.returns_rows():
|
|
256
223
|
fetched_data = cursor.fetchall()
|
|
257
224
|
column_names = [col[0] for col in cursor.description or []]
|
|
@@ -262,15 +229,12 @@ class SqliteDriver(SyncDriverAdapterBase):
|
|
|
262
229
|
cursor, selected_data=data, column_names=column_names, data_row_count=len(data), is_select_result=True
|
|
263
230
|
)
|
|
264
231
|
|
|
265
|
-
# Enhanced non-SELECT result processing
|
|
266
232
|
affected_rows = cursor.rowcount if cursor.rowcount and cursor.rowcount > 0 else 0
|
|
267
233
|
return self.create_execution_result(cursor, rowcount_override=affected_rows)
|
|
268
234
|
|
|
269
|
-
# Transaction management with enhanced error handling
|
|
270
235
|
def begin(self) -> None:
|
|
271
|
-
"""Begin a database transaction
|
|
236
|
+
"""Begin a database transaction."""
|
|
272
237
|
try:
|
|
273
|
-
# Only begin if not already in a transaction
|
|
274
238
|
if not self.connection.in_transaction:
|
|
275
239
|
self.connection.execute("BEGIN")
|
|
276
240
|
except sqlite3.Error as e:
|
|
@@ -278,7 +242,7 @@ class SqliteDriver(SyncDriverAdapterBase):
|
|
|
278
242
|
raise SQLSpecError(msg) from e
|
|
279
243
|
|
|
280
244
|
def rollback(self) -> None:
|
|
281
|
-
"""Rollback the current transaction
|
|
245
|
+
"""Rollback the current transaction."""
|
|
282
246
|
try:
|
|
283
247
|
self.connection.rollback()
|
|
284
248
|
except sqlite3.Error as e:
|
|
@@ -286,7 +250,7 @@ class SqliteDriver(SyncDriverAdapterBase):
|
|
|
286
250
|
raise SQLSpecError(msg) from e
|
|
287
251
|
|
|
288
252
|
def commit(self) -> None:
|
|
289
|
-
"""Commit the current transaction
|
|
253
|
+
"""Commit the current transaction."""
|
|
290
254
|
try:
|
|
291
255
|
self.connection.commit()
|
|
292
256
|
except sqlite3.Error as e:
|
sqlspec/adapters/sqlite/pool.py
CHANGED
|
@@ -40,10 +40,7 @@ class SqliteConnectionPool:
|
|
|
40
40
|
__slots__ = ("_connection_parameters", "_enable_optimizations", "_thread_local")
|
|
41
41
|
|
|
42
42
|
def __init__(
|
|
43
|
-
self,
|
|
44
|
-
connection_parameters: "dict[str, Any]",
|
|
45
|
-
enable_optimizations: bool = True,
|
|
46
|
-
**kwargs: Any, # Accept and ignore pool parameters for compatibility
|
|
43
|
+
self, connection_parameters: "dict[str, Any]", enable_optimizations: bool = True, **kwargs: Any
|
|
47
44
|
) -> None:
|
|
48
45
|
"""Initialize the thread-local connection manager.
|
|
49
46
|
|
|
@@ -60,18 +57,16 @@ class SqliteConnectionPool:
|
|
|
60
57
|
"""Create a new SQLite connection with optimizations."""
|
|
61
58
|
connection = sqlite3.connect(**self._connection_parameters)
|
|
62
59
|
|
|
63
|
-
# Only apply optimizations if requested and not in-memory
|
|
64
60
|
if self._enable_optimizations:
|
|
65
61
|
database = self._connection_parameters.get("database", ":memory:")
|
|
66
62
|
is_memory = database == ":memory:" or database.startswith("file::memory:")
|
|
67
63
|
|
|
68
64
|
if not is_memory:
|
|
69
|
-
# WAL mode doesn't work with in-memory databases
|
|
70
65
|
connection.execute("PRAGMA journal_mode = WAL")
|
|
71
|
-
|
|
66
|
+
|
|
72
67
|
connection.execute("PRAGMA busy_timeout = 5000")
|
|
73
68
|
connection.execute("PRAGMA optimize")
|
|
74
|
-
|
|
69
|
+
|
|
75
70
|
connection.execute("PRAGMA foreign_keys = ON")
|
|
76
71
|
connection.execute("PRAGMA synchronous = NORMAL")
|
|
77
72
|
|
|
@@ -82,7 +77,6 @@ class SqliteConnectionPool:
|
|
|
82
77
|
try:
|
|
83
78
|
return cast("SqliteConnection", self._thread_local.connection)
|
|
84
79
|
except AttributeError:
|
|
85
|
-
# Connection doesn't exist for this thread yet
|
|
86
80
|
connection = self._create_connection()
|
|
87
81
|
self._thread_local.connection = connection
|
|
88
82
|
return connection
|
|
@@ -94,7 +88,6 @@ class SqliteConnectionPool:
|
|
|
94
88
|
connection.close()
|
|
95
89
|
del self._thread_local.connection
|
|
96
90
|
except AttributeError:
|
|
97
|
-
# No connection for this thread
|
|
98
91
|
pass
|
|
99
92
|
|
|
100
93
|
@contextmanager
|
|
@@ -124,9 +117,7 @@ class SqliteConnectionPool:
|
|
|
124
117
|
Args:
|
|
125
118
|
connection: The connection to release (ignored)
|
|
126
119
|
"""
|
|
127
|
-
# No-op: thread-local connections are managed per-thread
|
|
128
120
|
|
|
129
|
-
# Compatibility methods that return dummy values
|
|
130
121
|
def size(self) -> int:
|
|
131
122
|
"""Get pool size (always 1 for thread-local)."""
|
|
132
123
|
try:
|
sqlspec/base.py
CHANGED
|
@@ -45,7 +45,6 @@ class SQLSpec:
|
|
|
45
45
|
|
|
46
46
|
def __init__(self, *, loader: "Optional[SQLFileLoader]" = None) -> None:
|
|
47
47
|
self._configs: dict[Any, DatabaseConfigProtocol[Any, Any, Any]] = {}
|
|
48
|
-
# Register sync cleanup only for sync resources
|
|
49
48
|
atexit.register(self._cleanup_sync_pools)
|
|
50
49
|
self._instance_cache_config: Optional[CacheConfig] = None
|
|
51
50
|
self._sql_loader: Optional[SQLFileLoader] = loader
|
|
@@ -90,7 +89,6 @@ class SQLSpec:
|
|
|
90
89
|
except Exception as e:
|
|
91
90
|
logger.warning("Failed to prepare cleanup for config %s: %s", config_type.__name__, e)
|
|
92
91
|
|
|
93
|
-
# Close async pools concurrently
|
|
94
92
|
if cleanup_tasks:
|
|
95
93
|
try:
|
|
96
94
|
await asyncio.gather(*cleanup_tasks, return_exceptions=True)
|
|
@@ -98,9 +96,8 @@ class SQLSpec:
|
|
|
98
96
|
except Exception as e:
|
|
99
97
|
logger.warning("Failed to complete async pool cleanup: %s", e)
|
|
100
98
|
|
|
101
|
-
# Close sync pools
|
|
102
99
|
for _config_type, config in sync_configs:
|
|
103
|
-
config.close_pool()
|
|
100
|
+
config.close_pool()
|
|
104
101
|
|
|
105
102
|
if sync_configs:
|
|
106
103
|
logger.debug("Sync pool cleanup completed. Cleaned %d pools.", len(sync_configs))
|
|
@@ -596,12 +593,9 @@ class SQLSpec:
|
|
|
596
593
|
)
|
|
597
594
|
)
|
|
598
595
|
|
|
599
|
-
# SQL File Loading Integration
|
|
600
|
-
|
|
601
596
|
def _ensure_sql_loader(self) -> "SQLFileLoader":
|
|
602
597
|
"""Ensure SQL loader is initialized lazily."""
|
|
603
598
|
if self._sql_loader is None:
|
|
604
|
-
# Import here to avoid circular imports
|
|
605
599
|
from sqlspec.loader import SQLFileLoader
|
|
606
600
|
|
|
607
601
|
self._sql_loader = SQLFileLoader()
|
|
@@ -677,7 +671,6 @@ class SQLSpec:
|
|
|
677
671
|
Note: This clears the cache and requires calling load_sql_files again.
|
|
678
672
|
"""
|
|
679
673
|
if self._sql_loader is not None:
|
|
680
|
-
# Clear cache to force reload
|
|
681
674
|
self._sql_loader.clear_cache()
|
|
682
675
|
logger.debug("Cleared SQL cache for reload")
|
|
683
676
|
|
sqlspec/builder/__init__.py
CHANGED
sqlspec/builder/_base.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Safe SQL query builder with validation and parameter binding.
|
|
2
2
|
|
|
3
|
-
This module provides a fluent interface for building SQL queries
|
|
4
|
-
|
|
3
|
+
This module provides a fluent interface for building SQL queries with
|
|
4
|
+
parameter binding and validation.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
@@ -47,7 +47,7 @@ class QueryBuilder(ABC):
|
|
|
47
47
|
"""Abstract base class for SQL query builders with SQLGlot optimization.
|
|
48
48
|
|
|
49
49
|
Provides common functionality for dialect handling, parameter management,
|
|
50
|
-
|
|
50
|
+
and query construction using SQLGlot.
|
|
51
51
|
"""
|
|
52
52
|
|
|
53
53
|
__slots__ = (
|
|
@@ -79,7 +79,6 @@ class QueryBuilder(ABC):
|
|
|
79
79
|
self.optimize_predicates = optimize_predicates
|
|
80
80
|
self.simplify_expressions = simplify_expressions
|
|
81
81
|
|
|
82
|
-
# Initialize mutable attributes
|
|
83
82
|
self._expression: Optional[exp.Expression] = None
|
|
84
83
|
self._parameters: dict[str, Any] = {}
|
|
85
84
|
self._parameter_counter: int = 0
|
|
@@ -201,12 +200,11 @@ class QueryBuilder(ABC):
|
|
|
201
200
|
if base_name not in self._parameters:
|
|
202
201
|
return base_name
|
|
203
202
|
|
|
204
|
-
for i in range(1, 1000):
|
|
203
|
+
for i in range(1, 1000):
|
|
205
204
|
name = f"{base_name}_{i}"
|
|
206
205
|
if name not in self._parameters:
|
|
207
206
|
return name
|
|
208
207
|
|
|
209
|
-
# Fallback for edge case
|
|
210
208
|
import uuid
|
|
211
209
|
|
|
212
210
|
return f"{base_name}_{uuid.uuid4().hex[:8]}"
|
|
@@ -223,6 +221,10 @@ class QueryBuilder(ABC):
|
|
|
223
221
|
import hashlib
|
|
224
222
|
|
|
225
223
|
dialect_name: str = self.dialect_name or "default"
|
|
224
|
+
|
|
225
|
+
if self._expression is None:
|
|
226
|
+
self._expression = self._create_base_expression()
|
|
227
|
+
|
|
226
228
|
expr_sql: str = self._expression.sql() if self._expression else "None"
|
|
227
229
|
|
|
228
230
|
state_parts = [
|
|
@@ -336,7 +338,7 @@ class QueryBuilder(ABC):
|
|
|
336
338
|
return SafeQuery(sql=sql_string, parameters=self._parameters.copy(), dialect=self.dialect)
|
|
337
339
|
|
|
338
340
|
def _optimize_expression(self, expression: exp.Expression) -> exp.Expression:
|
|
339
|
-
"""Apply SQLGlot optimizations to the expression
|
|
341
|
+
"""Apply SQLGlot optimizations to the expression.
|
|
340
342
|
|
|
341
343
|
Args:
|
|
342
344
|
expression: The expression to optimize
|
|
@@ -377,7 +379,7 @@ class QueryBuilder(ABC):
|
|
|
377
379
|
return optimized
|
|
378
380
|
|
|
379
381
|
def to_statement(self, config: "Optional[StatementConfig]" = None) -> "SQL":
|
|
380
|
-
"""Converts the built query into a SQL statement object
|
|
382
|
+
"""Converts the built query into a SQL statement object.
|
|
381
383
|
|
|
382
384
|
Args:
|
|
383
385
|
config: Optional SQL configuration.
|
|
@@ -387,7 +389,7 @@ class QueryBuilder(ABC):
|
|
|
387
389
|
"""
|
|
388
390
|
cache_config = get_cache_config()
|
|
389
391
|
if not cache_config.compiled_cache_enabled:
|
|
390
|
-
return self.
|
|
392
|
+
return self._to_statement(config)
|
|
391
393
|
|
|
392
394
|
cache_key_str = self._generate_builder_cache_key(config)
|
|
393
395
|
cache_key = CacheKey((cache_key_str,))
|
|
@@ -397,13 +399,13 @@ class QueryBuilder(ABC):
|
|
|
397
399
|
if cached_sql is not None:
|
|
398
400
|
return cast("SQL", cached_sql)
|
|
399
401
|
|
|
400
|
-
sql_statement = self.
|
|
402
|
+
sql_statement = self._to_statement(config)
|
|
401
403
|
unified_cache.put(cache_key, sql_statement)
|
|
402
404
|
|
|
403
405
|
return sql_statement
|
|
404
406
|
|
|
405
|
-
def
|
|
406
|
-
"""Internal method to create SQL statement
|
|
407
|
+
def _to_statement(self, config: "Optional[StatementConfig]" = None) -> "SQL":
|
|
408
|
+
"""Internal method to create SQL statement.
|
|
407
409
|
|
|
408
410
|
Args:
|
|
409
411
|
config: Optional SQL configuration.
|
|
@@ -427,18 +429,30 @@ class QueryBuilder(ABC):
|
|
|
427
429
|
)
|
|
428
430
|
|
|
429
431
|
if config is None:
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
432
|
+
config = StatementConfig(
|
|
433
|
+
parameter_config=ParameterStyleConfig(
|
|
434
|
+
default_parameter_style=ParameterStyle.QMARK, supported_parameter_styles={ParameterStyle.QMARK}
|
|
435
|
+
),
|
|
436
|
+
dialect=safe_query.dialect,
|
|
434
437
|
)
|
|
435
|
-
|
|
438
|
+
|
|
439
|
+
sql_string = safe_query.sql
|
|
440
|
+
if (
|
|
441
|
+
config.dialect is not None
|
|
442
|
+
and config.dialect != safe_query.dialect
|
|
443
|
+
and self._expression is not None
|
|
444
|
+
and hasattr(self._expression, "sql")
|
|
445
|
+
):
|
|
446
|
+
try:
|
|
447
|
+
sql_string = self._expression.sql(dialect=config.dialect, pretty=True)
|
|
448
|
+
except Exception:
|
|
449
|
+
sql_string = safe_query.sql
|
|
436
450
|
|
|
437
451
|
if kwargs:
|
|
438
|
-
return SQL(
|
|
452
|
+
return SQL(sql_string, statement_config=config, **kwargs)
|
|
439
453
|
if parameters:
|
|
440
|
-
return SQL(
|
|
441
|
-
return SQL(
|
|
454
|
+
return SQL(sql_string, *parameters, statement_config=config)
|
|
455
|
+
return SQL(sql_string, statement_config=config)
|
|
442
456
|
|
|
443
457
|
def __str__(self) -> str:
|
|
444
458
|
"""Return the SQL string representation of the query.
|
sqlspec/builder/_column.py
CHANGED
|
@@ -5,7 +5,7 @@ for building SQL conditions with type safety and parameter binding.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from collections.abc import Iterable
|
|
8
|
-
from typing import Any, Optional
|
|
8
|
+
from typing import Any, Optional, cast
|
|
9
9
|
|
|
10
10
|
from sqlglot import exp
|
|
11
11
|
|
|
@@ -241,6 +241,10 @@ class Column:
|
|
|
241
241
|
"""Create a DESC ordering expression."""
|
|
242
242
|
return exp.Ordered(this=self._expression, desc=True)
|
|
243
243
|
|
|
244
|
+
def as_(self, alias: str) -> exp.Alias:
|
|
245
|
+
"""Create an aliased expression."""
|
|
246
|
+
return cast("exp.Alias", exp.alias_(self._expression, alias))
|
|
247
|
+
|
|
244
248
|
def __repr__(self) -> str:
|
|
245
249
|
if self.table:
|
|
246
250
|
return f"Column<{self.table}.{self.name}>"
|