sqlspec 0.17.1__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.

Files changed (75) hide show
  1. sqlspec/__init__.py +1 -1
  2. sqlspec/_sql.py +54 -159
  3. sqlspec/adapters/adbc/config.py +24 -30
  4. sqlspec/adapters/adbc/driver.py +42 -61
  5. sqlspec/adapters/aiosqlite/config.py +5 -10
  6. sqlspec/adapters/aiosqlite/driver.py +9 -25
  7. sqlspec/adapters/aiosqlite/pool.py +43 -35
  8. sqlspec/adapters/asyncmy/config.py +10 -7
  9. sqlspec/adapters/asyncmy/driver.py +18 -39
  10. sqlspec/adapters/asyncpg/config.py +4 -0
  11. sqlspec/adapters/asyncpg/driver.py +32 -79
  12. sqlspec/adapters/bigquery/config.py +12 -65
  13. sqlspec/adapters/bigquery/driver.py +39 -133
  14. sqlspec/adapters/duckdb/config.py +11 -15
  15. sqlspec/adapters/duckdb/driver.py +61 -85
  16. sqlspec/adapters/duckdb/pool.py +2 -5
  17. sqlspec/adapters/oracledb/_types.py +8 -1
  18. sqlspec/adapters/oracledb/config.py +55 -38
  19. sqlspec/adapters/oracledb/driver.py +35 -92
  20. sqlspec/adapters/oracledb/migrations.py +257 -0
  21. sqlspec/adapters/psqlpy/config.py +13 -9
  22. sqlspec/adapters/psqlpy/driver.py +28 -103
  23. sqlspec/adapters/psycopg/config.py +9 -5
  24. sqlspec/adapters/psycopg/driver.py +107 -175
  25. sqlspec/adapters/sqlite/config.py +7 -5
  26. sqlspec/adapters/sqlite/driver.py +37 -73
  27. sqlspec/adapters/sqlite/pool.py +3 -12
  28. sqlspec/base.py +1 -8
  29. sqlspec/builder/__init__.py +1 -1
  30. sqlspec/builder/_base.py +34 -20
  31. sqlspec/builder/_ddl.py +407 -183
  32. sqlspec/builder/_insert.py +1 -1
  33. sqlspec/builder/mixins/_insert_operations.py +26 -6
  34. sqlspec/builder/mixins/_merge_operations.py +1 -1
  35. sqlspec/builder/mixins/_select_operations.py +1 -5
  36. sqlspec/config.py +32 -13
  37. sqlspec/core/__init__.py +89 -14
  38. sqlspec/core/cache.py +57 -104
  39. sqlspec/core/compiler.py +57 -112
  40. sqlspec/core/filters.py +1 -21
  41. sqlspec/core/hashing.py +13 -47
  42. sqlspec/core/parameters.py +272 -261
  43. sqlspec/core/result.py +12 -27
  44. sqlspec/core/splitter.py +17 -21
  45. sqlspec/core/statement.py +150 -159
  46. sqlspec/driver/_async.py +2 -15
  47. sqlspec/driver/_common.py +16 -95
  48. sqlspec/driver/_sync.py +2 -15
  49. sqlspec/driver/mixins/_result_tools.py +8 -29
  50. sqlspec/driver/mixins/_sql_translator.py +6 -8
  51. sqlspec/exceptions.py +1 -2
  52. sqlspec/loader.py +43 -115
  53. sqlspec/migrations/__init__.py +1 -1
  54. sqlspec/migrations/base.py +34 -45
  55. sqlspec/migrations/commands.py +34 -15
  56. sqlspec/migrations/loaders.py +1 -1
  57. sqlspec/migrations/runner.py +104 -19
  58. sqlspec/migrations/tracker.py +49 -2
  59. sqlspec/protocols.py +3 -6
  60. sqlspec/storage/__init__.py +4 -4
  61. sqlspec/storage/backends/fsspec.py +5 -6
  62. sqlspec/storage/backends/obstore.py +7 -8
  63. sqlspec/storage/registry.py +3 -3
  64. sqlspec/utils/__init__.py +2 -2
  65. sqlspec/utils/logging.py +6 -10
  66. sqlspec/utils/sync_tools.py +27 -4
  67. sqlspec/utils/text.py +6 -1
  68. {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/METADATA +1 -1
  69. sqlspec-0.18.0.dist-info/RECORD +138 -0
  70. sqlspec/builder/_ddl_utils.py +0 -103
  71. sqlspec-0.17.1.dist-info/RECORD +0 -138
  72. {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/WHEEL +0 -0
  73. {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/entry_points.txt +0 -0
  74. {sqlspec-0.17.1.dist-info → sqlspec-0.18.0.dist-info}/licenses/LICENSE +0 -0
  75. {sqlspec-0.17.1.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
- # Filter out pool-specific parameters that SQLite doesn't use
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
- # Pass all pool_config as kwargs to be ignored by the pool
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
- """Enhanced SQLite driver with CORE_ROUND_3 architecture integration.
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 with enhanced error handling."""
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) # Mark as intentionally unused
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
- """Custom sync context manager for handling SQLite database exceptions."""
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
- """Enhanced SQLite driver with CORE_ROUND_3 architecture integration.
139
-
140
- This driver leverages the complete core module system for maximum performance:
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
- enhanced_config = sqlite_statement_config.replace(
134
+ statement_config = sqlite_statement_config.replace(
174
135
  enable_caching=cache_config.compiled_cache_enabled,
175
- enable_parsing=True, # Default to enabled
176
- enable_validation=True, # Default to enabled
177
- dialect="sqlite", # Use adapter-specific dialect
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 with enhanced resource management."""
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 using enhanced statement splitting and parameter handling.
164
+ """Execute SQL script with statement splitting and parameter handling.
208
165
 
209
- Uses core module optimization for statement parsing and parameter processing.
210
- Parameters are embedded as static values for script execution compatibility.
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 using optimized batch processing.
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
- Leverages core parameter processing for enhanced type handling and validation.
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 with enhanced data handling and performance optimization.
210
+ """Execute single SQL statement.
248
211
 
249
- Uses core processing for optimal parameter handling and result processing.
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 with enhanced error handling."""
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 with enhanced error handling."""
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 with enhanced error handling."""
253
+ """Commit the current transaction."""
290
254
  try:
291
255
  self.connection.commit()
292
256
  except sqlite3.Error as e:
@@ -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
- # Set busy timeout for better concurrent access
66
+
72
67
  connection.execute("PRAGMA busy_timeout = 5000")
73
68
  connection.execute("PRAGMA optimize")
74
- # These work for all database types
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() # Let exceptions propagate for proper logging
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
 
@@ -1,6 +1,6 @@
1
1
  """SQL query builders for safe SQL construction.
2
2
 
3
- This package provides fluent interfaces for building SQL queries with automatic
3
+ This package provides fluent interfaces for building SQL queries with
4
4
  parameter binding and validation.
5
5
  """
6
6
 
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 safely,
4
- with automatic parameter binding and validation.
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
- query construction, and query optimization using SQLGlot.
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): # Reasonable upper bound to prevent infinite loops
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 with caching.
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 with caching.
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._to_statement_without_cache(config)
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._to_statement_without_cache(config)
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 _to_statement_without_cache(self, config: "Optional[StatementConfig]" = None) -> "SQL":
406
- """Internal method to create SQL statement without caching.
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
- from sqlspec.core.statement import StatementConfig
431
-
432
- parameter_config = ParameterStyleConfig(
433
- default_parameter_style=ParameterStyle.QMARK, supported_parameter_styles={ParameterStyle.QMARK}
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
- config = StatementConfig(parameter_config=parameter_config, dialect=safe_query.dialect)
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(safe_query.sql, statement_config=config, **kwargs)
452
+ return SQL(sql_string, statement_config=config, **kwargs)
439
453
  if parameters:
440
- return SQL(safe_query.sql, *parameters, statement_config=config)
441
- return SQL(safe_query.sql, statement_config=config)
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.