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
@@ -173,7 +173,7 @@ class Insert(QueryBuilder, ReturningClauseMixin, InsertValuesMixin, InsertFromSe
173
173
  else:
174
174
  param_name = self._generate_unique_parameter_name(f"value_{i + 1}")
175
175
  _, param_name = self.add_parameter(value, name=param_name)
176
- value_placeholders.append(exp.var(param_name))
176
+ value_placeholders.append(exp.Placeholder(this=param_name))
177
177
 
178
178
  tuple_expr = exp.Tuple(expressions=value_placeholders)
179
179
  if self._values_added_count == 0:
@@ -75,14 +75,34 @@ class InsertValuesMixin:
75
75
  if not isinstance(self._expression, exp.Insert):
76
76
  msg = "Cannot set columns on a non-INSERT expression."
77
77
  raise SQLBuilderError(msg)
78
- column_exprs = [exp.column(col) if isinstance(col, str) else col for col in columns]
79
- self._expression.set("columns", column_exprs)
78
+
79
+ # Get the current table from the expression
80
+ current_this = self._expression.args.get("this")
81
+ if current_this is None:
82
+ msg = "Table must be set using .into() before setting columns."
83
+ raise SQLBuilderError(msg)
84
+
85
+ if columns:
86
+ # Create identifiers for columns
87
+ column_identifiers = [exp.to_identifier(col) if isinstance(col, str) else col for col in columns]
88
+
89
+ # Get table name from current this
90
+ table_name = current_this.this
91
+
92
+ # Create Schema object with table and columns
93
+ schema = exp.Schema(this=table_name, expressions=column_identifiers)
94
+ self._expression.set("this", schema)
95
+ # No columns specified - ensure we have just a Table object
96
+ elif isinstance(current_this, exp.Schema):
97
+ table_name = current_this.this
98
+ self._expression.set("this", exp.Table(this=table_name))
99
+
80
100
  try:
81
101
  cols = self._columns
82
102
  if not columns:
83
103
  cols.clear()
84
104
  else:
85
- cols[:] = [col.name if isinstance(col, exp.Column) else str(col) for col in columns]
105
+ cols[:] = [col if isinstance(col, str) else str(col) for col in columns]
86
106
  except AttributeError:
87
107
  pass
88
108
  return self
@@ -128,7 +148,7 @@ class InsertValuesMixin:
128
148
  column_name = column_name.split(".")[-1]
129
149
  param_name = self._generate_unique_parameter_name(column_name)
130
150
  _, param_name = self.add_parameter(val, name=param_name)
131
- row_exprs.append(exp.var(param_name))
151
+ row_exprs.append(exp.Placeholder(this=param_name))
132
152
  elif len(values) == 1 and hasattr(values[0], "items"):
133
153
  mapping = values[0]
134
154
  try:
@@ -147,7 +167,7 @@ class InsertValuesMixin:
147
167
  column_name = column_name.split(".")[-1]
148
168
  param_name = self._generate_unique_parameter_name(column_name)
149
169
  _, param_name = self.add_parameter(val, name=param_name)
150
- row_exprs.append(exp.var(param_name))
170
+ row_exprs.append(exp.Placeholder(this=param_name))
151
171
  else:
152
172
  try:
153
173
  _columns = self._columns
@@ -173,7 +193,7 @@ class InsertValuesMixin:
173
193
  except AttributeError:
174
194
  param_name = self._generate_unique_parameter_name(f"value_{i + 1}")
175
195
  _, param_name = self.add_parameter(v, name=param_name)
176
- row_exprs.append(exp.var(param_name))
196
+ row_exprs.append(exp.Placeholder(this=param_name))
177
197
 
178
198
  values_expr = exp.Values(expressions=[row_exprs])
179
199
  self._expression.set("expression", values_expr)
@@ -365,7 +365,7 @@ class MergeNotMatchedClauseMixin:
365
365
  column_name = column_name.split(".")[-1]
366
366
  param_name = self._generate_unique_parameter_name(column_name)
367
367
  param_name = self.add_parameter(val, name=param_name)[1]
368
- parameterized_values.append(exp.var(param_name))
368
+ parameterized_values.append(exp.Placeholder())
369
369
 
370
370
  insert_args["this"] = exp.Tuple(expressions=[exp.column(c) for c in columns])
371
371
  insert_args["expression"] = exp.Tuple(expressions=parameterized_values)
@@ -1,6 +1,5 @@
1
1
  """SELECT clause mixins consolidated into a single module."""
2
2
 
3
- from dataclasses import dataclass
4
3
  from typing import TYPE_CHECKING, Any, Optional, Union, cast
5
4
 
6
5
  from mypy_extensions import trait
@@ -538,13 +537,10 @@ class SelectClauseMixin:
538
537
  return CaseBuilder(builder, alias)
539
538
 
540
539
 
541
- @dataclass
542
540
  class CaseBuilder:
543
541
  """Builder for CASE expressions."""
544
542
 
545
- _parent: "SelectBuilderProtocol"
546
- _alias: Optional[str]
547
- _case_expr: exp.Case
543
+ __slots__ = ("_alias", "_case_expr", "_parent")
548
544
 
549
545
  def __init__(self, parent: "SelectBuilderProtocol", alias: "Optional[str]" = None) -> None:
550
546
  """Initialize CaseBuilder.
sqlspec/config.py CHANGED
@@ -5,6 +5,7 @@ from typing_extensions import NotRequired, TypedDict
5
5
 
6
6
  from sqlspec.core.parameters import ParameterStyle, ParameterStyleConfig
7
7
  from sqlspec.core.statement import StatementConfig
8
+ from sqlspec.migrations.tracker import AsyncMigrationTracker, SyncMigrationTracker
8
9
  from sqlspec.utils.logging import get_logger
9
10
 
10
11
  if TYPE_CHECKING:
@@ -21,6 +22,7 @@ __all__ = (
21
22
  "DatabaseConfigProtocol",
22
23
  "DriverT",
23
24
  "LifecycleConfig",
25
+ "MigrationConfig",
24
26
  "NoPoolAsyncConfig",
25
27
  "NoPoolSyncConfig",
26
28
  "SyncConfigT",
@@ -43,7 +45,7 @@ logger = get_logger("config")
43
45
 
44
46
 
45
47
  class LifecycleConfig(TypedDict, total=False):
46
- """Universal lifecycle hooks for all adapters.
48
+ """Lifecycle hooks for all adapters.
47
49
 
48
50
  Each hook accepts a list of callables to support multiple handlers.
49
51
  """
@@ -59,6 +61,22 @@ class LifecycleConfig(TypedDict, total=False):
59
61
  on_error: NotRequired[list[Callable[[Exception, str, dict], None]]]
60
62
 
61
63
 
64
+ class MigrationConfig(TypedDict, total=False):
65
+ """Configuration options for database migrations.
66
+
67
+ All fields are optional with sensible defaults.
68
+ """
69
+
70
+ script_location: NotRequired[str]
71
+ """Path to the migrations directory. Defaults to 'migrations'."""
72
+
73
+ version_table_name: NotRequired[str]
74
+ """Name of the table used to track applied migrations. Defaults to 'sqlspec_migrations'."""
75
+
76
+ project_root: NotRequired[str]
77
+ """Path to the project root directory. Used for relative path resolution."""
78
+
79
+
62
80
  class DatabaseConfigProtocol(ABC, Generic[ConnectionT, PoolT, DriverT]):
63
81
  """Protocol defining the interface for database configurations."""
64
82
 
@@ -73,7 +91,7 @@ class DatabaseConfigProtocol(ABC, Generic[ConnectionT, PoolT, DriverT]):
73
91
  supports_native_parquet_export: "ClassVar[bool]" = False
74
92
  statement_config: "StatementConfig"
75
93
  pool_instance: "Optional[PoolT]"
76
- migration_config: "dict[str, Any]"
94
+ migration_config: "Union[dict[str, Any], MigrationConfig]"
77
95
 
78
96
  def __hash__(self) -> int:
79
97
  return id(self)
@@ -142,18 +160,19 @@ class NoPoolSyncConfig(DatabaseConfigProtocol[ConnectionT, None, DriverT]):
142
160
  __slots__ = ("connection_config",)
143
161
  is_async: "ClassVar[bool]" = False
144
162
  supports_connection_pooling: "ClassVar[bool]" = False
163
+ migration_tracker_type: "ClassVar[type[Any]]" = SyncMigrationTracker
145
164
 
146
165
  def __init__(
147
166
  self,
148
167
  *,
149
168
  connection_config: Optional[dict[str, Any]] = None,
150
- migration_config: "Optional[dict[str, Any]]" = None,
169
+ migration_config: "Optional[Union[dict[str, Any], MigrationConfig]]" = None,
151
170
  statement_config: "Optional[StatementConfig]" = None,
152
171
  driver_features: "Optional[dict[str, Any]]" = None,
153
172
  ) -> None:
154
173
  self.pool_instance = None
155
174
  self.connection_config = connection_config or {}
156
- self.migration_config: dict[str, Any] = migration_config if migration_config is not None else {}
175
+ self.migration_config: Union[dict[str, Any], MigrationConfig] = migration_config or {}
157
176
 
158
177
  if statement_config is None:
159
178
  default_parameter_config = ParameterStyleConfig(
@@ -192,21 +211,21 @@ class NoPoolAsyncConfig(DatabaseConfigProtocol[ConnectionT, None, DriverT]):
192
211
  """Base class for an async database configurations that do not implement a pool."""
193
212
 
194
213
  __slots__ = ("connection_config",)
195
-
196
214
  is_async: "ClassVar[bool]" = True
197
215
  supports_connection_pooling: "ClassVar[bool]" = False
216
+ migration_tracker_type: "ClassVar[type[Any]]" = AsyncMigrationTracker
198
217
 
199
218
  def __init__(
200
219
  self,
201
220
  *,
202
221
  connection_config: "Optional[dict[str, Any]]" = None,
203
- migration_config: "Optional[dict[str, Any]]" = None,
222
+ migration_config: "Optional[Union[dict[str, Any], MigrationConfig]]" = None,
204
223
  statement_config: "Optional[StatementConfig]" = None,
205
224
  driver_features: "Optional[dict[str, Any]]" = None,
206
225
  ) -> None:
207
226
  self.pool_instance = None
208
227
  self.connection_config = connection_config or {}
209
- self.migration_config: dict[str, Any] = migration_config if migration_config is not None else {}
228
+ self.migration_config: Union[dict[str, Any], MigrationConfig] = migration_config or {}
210
229
 
211
230
  if statement_config is None:
212
231
  default_parameter_config = ParameterStyleConfig(
@@ -245,22 +264,22 @@ class SyncDatabaseConfig(DatabaseConfigProtocol[ConnectionT, PoolT, DriverT]):
245
264
  """Generic Sync Database Configuration."""
246
265
 
247
266
  __slots__ = ("pool_config",)
248
-
249
267
  is_async: "ClassVar[bool]" = False
250
268
  supports_connection_pooling: "ClassVar[bool]" = True
269
+ migration_tracker_type: "ClassVar[type[Any]]" = SyncMigrationTracker
251
270
 
252
271
  def __init__(
253
272
  self,
254
273
  *,
255
274
  pool_config: "Optional[dict[str, Any]]" = None,
256
275
  pool_instance: "Optional[PoolT]" = None,
257
- migration_config: "Optional[dict[str, Any]]" = None,
276
+ migration_config: "Optional[Union[dict[str, Any], MigrationConfig]]" = None,
258
277
  statement_config: "Optional[StatementConfig]" = None,
259
278
  driver_features: "Optional[dict[str, Any]]" = None,
260
279
  ) -> None:
261
280
  self.pool_instance = pool_instance
262
281
  self.pool_config = pool_config or {}
263
- self.migration_config: dict[str, Any] = migration_config if migration_config is not None else {}
282
+ self.migration_config: Union[dict[str, Any], MigrationConfig] = migration_config or {}
264
283
 
265
284
  if statement_config is None:
266
285
  default_parameter_config = ParameterStyleConfig(
@@ -321,22 +340,22 @@ class AsyncDatabaseConfig(DatabaseConfigProtocol[ConnectionT, PoolT, DriverT]):
321
340
  """Generic Async Database Configuration."""
322
341
 
323
342
  __slots__ = ("pool_config",)
324
-
325
343
  is_async: "ClassVar[bool]" = True
326
344
  supports_connection_pooling: "ClassVar[bool]" = True
345
+ migration_tracker_type: "ClassVar[type[Any]]" = AsyncMigrationTracker
327
346
 
328
347
  def __init__(
329
348
  self,
330
349
  *,
331
350
  pool_config: "Optional[dict[str, Any]]" = None,
332
351
  pool_instance: "Optional[PoolT]" = None,
333
- migration_config: "Optional[dict[str, Any]]" = None,
352
+ migration_config: "Optional[Union[dict[str, Any], MigrationConfig]]" = None,
334
353
  statement_config: "Optional[StatementConfig]" = None,
335
354
  driver_features: "Optional[dict[str, Any]]" = None,
336
355
  ) -> None:
337
356
  self.pool_instance = pool_instance
338
357
  self.pool_config = pool_config or {}
339
- self.migration_config: dict[str, Any] = migration_config if migration_config is not None else {}
358
+ self.migration_config: Union[dict[str, Any], MigrationConfig] = migration_config or {}
340
359
 
341
360
  if statement_config is None:
342
361
  self.statement_config = StatementConfig(
sqlspec/core/__init__.py CHANGED
@@ -1,17 +1,92 @@
1
- """SQLSpec Core Module - SQL Processing System.
2
-
3
- This module provides the core SQL processing components including statement handling,
4
- parameter processing, compilation, and result management.
5
-
6
- Components:
7
- - statement.py: SQL class with StatementConfig
8
- - parameters.py: Parameter processing pipeline
9
- - compiler.py: SQL compilation with caching
10
- - result.py: Result classes for query execution
11
- - filters.py: Statement filter system
12
- - cache.py: Unified caching system
13
- - splitter.py: SQL statement splitter
14
- - hashing.py: Cache key generation
1
+ """SQLSpec Core Module - High-Performance SQL Processing System.
2
+
3
+ This module provides the core SQL processing infrastructure for SQLSpec, implementing
4
+ a complete pipeline for SQL statement compilation, parameter processing, caching,
5
+ and result management. All components are optimized for MyPyC compilation and
6
+ designed for maximum performance with minimal overhead.
7
+
8
+ Architecture Overview:
9
+ The core module implements a single-pass processing pipeline where SQL statements
10
+ are parsed once, transformed once, and validated once. The SQL object serves as
11
+ the single source of truth throughout the system.
12
+
13
+ Key Components:
14
+ statement.py: SQL statement representation and configuration management
15
+ - SQL class for statement encapsulation with lazy compilation
16
+ - StatementConfig for processing pipeline configuration
17
+ - ProcessedState for cached compilation results
18
+ - Support for execute_many and script execution modes
19
+
20
+ parameters.py: Type-safe parameter processing and style conversion
21
+ - Automatic parameter style detection and conversion
22
+ - Support for QMARK (?), NAMED (:name), NUMERIC ($1), FORMAT (%s) styles
23
+ - Parameter validation and type coercion
24
+ - Batch parameter handling for execute_many operations
25
+
26
+ compiler.py: SQL compilation with validation and optimization
27
+ - SQLProcessor for statement compilation and validation
28
+ - Operation type detection (SELECT, INSERT, UPDATE, DELETE, etc.)
29
+ - AST-based SQL analysis using SQLGlot
30
+ - Support for multiple SQL dialects
31
+ - Compiled result caching for performance
32
+
33
+ result.py: Comprehensive result handling for all SQL operations
34
+ - SQLResult for standard query results with metadata
35
+ - ArrowResult for Apache Arrow format integration
36
+ - Support for DML operations with RETURNING clauses
37
+ - Script execution result aggregation
38
+ - Iterator protocol support for result rows
39
+
40
+ filters.py: Composable SQL statement filters
41
+ - BeforeAfterFilter for date range filtering
42
+ - InCollectionFilter for IN clause generation
43
+ - LimitOffsetFilter for pagination
44
+ - OrderByFilter for dynamic sorting
45
+ - SearchFilter for text search operations
46
+ - Parameter conflict resolution
47
+
48
+ cache.py: Unified caching system with LRU eviction
49
+ - UnifiedCache with configurable TTL and size limits
50
+ - StatementCache for compiled SQL statements
51
+ - ExpressionCache for parsed SQLGlot expressions
52
+ - ParameterCache for processed parameters
53
+ - Thread-safe operations with fine-grained locking
54
+ - Cache statistics and monitoring
55
+
56
+ splitter.py: Dialect-aware SQL script splitting
57
+ - Support for Oracle PL/SQL, T-SQL, PostgreSQL, MySQL
58
+ - Proper handling of block structures (BEGIN/END)
59
+ - Dollar-quoted string support for PostgreSQL
60
+ - Batch separator recognition (GO for T-SQL)
61
+ - Comment and string literal preservation
62
+
63
+ hashing.py: Efficient cache key generation
64
+ - SQL statement hashing with parameter consideration
65
+ - Expression tree hashing for AST caching
66
+ - Parameter set hashing for batch operations
67
+ - Optimized hash computation with caching
68
+
69
+ Performance Optimizations:
70
+ - MyPyC compilation support with proper annotations
71
+ - __slots__ usage for memory efficiency
72
+ - Final annotations for constant folding
73
+ - Lazy evaluation and compilation
74
+ - Comprehensive result caching
75
+ - Minimal object allocation in hot paths
76
+
77
+ Thread Safety:
78
+ All caching components are thread-safe with RLock protection.
79
+ The processing pipeline is stateless and safe for concurrent use.
80
+
81
+ Example Usage:
82
+ >>> from sqlspec.core import SQL, StatementConfig
83
+ >>> config = StatementConfig(dialect="postgresql")
84
+ >>> stmt = SQL(
85
+ ... "SELECT * FROM users WHERE id = ?",
86
+ ... 1,
87
+ ... statement_config=config,
88
+ ... )
89
+ >>> compiled_sql, params = stmt.compile()
15
90
  """
16
91
 
17
92
  from sqlspec.core import filters