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
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Expression wrapper classes for proper type annotations."""
|
|
2
|
+
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
from sqlglot import exp
|
|
6
|
+
|
|
7
|
+
__all__ = ("AggregateExpression", "ConversionExpression", "FunctionExpression", "MathExpression", "StringExpression")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ExpressionWrapper:
|
|
11
|
+
"""Base wrapper for SQLGlot expressions."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, expression: exp.Expression) -> None:
|
|
14
|
+
self._expression = expression
|
|
15
|
+
|
|
16
|
+
def as_(self, alias: str) -> exp.Alias:
|
|
17
|
+
"""Create an aliased expression."""
|
|
18
|
+
return cast("exp.Alias", exp.alias_(self._expression, alias))
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def expression(self) -> exp.Expression:
|
|
22
|
+
"""Get the underlying SQLGlot expression."""
|
|
23
|
+
return self._expression
|
|
24
|
+
|
|
25
|
+
def __str__(self) -> str:
|
|
26
|
+
return str(self._expression)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AggregateExpression(ExpressionWrapper):
|
|
30
|
+
"""Aggregate functions like COUNT, SUM, AVG."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class FunctionExpression(ExpressionWrapper):
|
|
34
|
+
"""General SQL functions."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MathExpression(ExpressionWrapper):
|
|
38
|
+
"""Mathematical functions like ROUND."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class StringExpression(ExpressionWrapper):
|
|
42
|
+
"""String functions like UPPER, LOWER, LENGTH."""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ConversionExpression(ExpressionWrapper):
|
|
46
|
+
"""Conversion functions like CAST, COALESCE."""
|
sqlspec/builder/_insert.py
CHANGED
|
@@ -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.
|
|
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:
|
|
@@ -412,9 +412,7 @@ class ConflictBuilder:
|
|
|
412
412
|
# Create ON CONFLICT with proper structure
|
|
413
413
|
conflict_keys = [exp.to_identifier(col) for col in self._columns] if self._columns else None
|
|
414
414
|
on_conflict = exp.OnConflict(
|
|
415
|
-
conflict_keys=conflict_keys,
|
|
416
|
-
action=exp.var("DO UPDATE"),
|
|
417
|
-
expressions=set_expressions if set_expressions else None,
|
|
415
|
+
conflict_keys=conflict_keys, action=exp.var("DO UPDATE"), expressions=set_expressions or None
|
|
418
416
|
)
|
|
419
417
|
|
|
420
418
|
insert_expr.set("conflict", on_conflict)
|
sqlspec/builder/_update.py
CHANGED
|
@@ -44,26 +44,26 @@ class Update(
|
|
|
44
44
|
update_query = (
|
|
45
45
|
Update()
|
|
46
46
|
.table("users")
|
|
47
|
-
.
|
|
48
|
-
.
|
|
47
|
+
.set_(name="John Doe")
|
|
48
|
+
.set_(email="john@example.com")
|
|
49
49
|
.where("id = 1")
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
update_query = (
|
|
53
|
-
Update("users").
|
|
53
|
+
Update("users").set_(name="John Doe").where("id = 1")
|
|
54
54
|
)
|
|
55
55
|
|
|
56
56
|
update_query = (
|
|
57
57
|
Update()
|
|
58
58
|
.table("users")
|
|
59
|
-
.
|
|
59
|
+
.set_(status="active")
|
|
60
60
|
.where_eq("id", 123)
|
|
61
61
|
)
|
|
62
62
|
|
|
63
63
|
update_query = (
|
|
64
64
|
Update()
|
|
65
65
|
.table("users", "u")
|
|
66
|
-
.
|
|
66
|
+
.set_(name="Updated Name")
|
|
67
67
|
.from_("profiles", "p")
|
|
68
68
|
.where("u.id = p.user_id AND p.is_verified = true")
|
|
69
69
|
)
|
|
@@ -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
|
-
|
|
79
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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)
|
|
@@ -10,6 +10,9 @@ from sqlspec.builder._parsing_utils import parse_order_expression
|
|
|
10
10
|
from sqlspec.exceptions import SQLBuilderError
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
|
+
from sqlspec.builder._column import Column
|
|
14
|
+
from sqlspec.builder._expression_wrappers import ExpressionWrapper
|
|
15
|
+
from sqlspec.builder.mixins._select_operations import Case
|
|
13
16
|
from sqlspec.protocols import SQLBuilderProtocol
|
|
14
17
|
|
|
15
18
|
__all__ = ("LimitOffsetClauseMixin", "OrderByClauseMixin", "ReturningClauseMixin")
|
|
@@ -24,7 +27,7 @@ class OrderByClauseMixin:
|
|
|
24
27
|
# Type annotation for PyRight - this will be provided by the base class
|
|
25
28
|
_expression: Optional[exp.Expression]
|
|
26
29
|
|
|
27
|
-
def order_by(self, *items: Union[str, exp.Ordered], desc: bool = False) -> Self:
|
|
30
|
+
def order_by(self, *items: Union[str, exp.Ordered, "Column"], desc: bool = False) -> Self:
|
|
28
31
|
"""Add ORDER BY clause.
|
|
29
32
|
|
|
30
33
|
Args:
|
|
@@ -49,7 +52,13 @@ class OrderByClauseMixin:
|
|
|
49
52
|
if desc:
|
|
50
53
|
order_item = order_item.desc()
|
|
51
54
|
else:
|
|
52
|
-
|
|
55
|
+
# Extract expression from Column objects or use as-is for sqlglot expressions
|
|
56
|
+
from sqlspec._sql import SQLFactory
|
|
57
|
+
|
|
58
|
+
extracted_item = SQLFactory._extract_expression(item)
|
|
59
|
+
order_item = extracted_item
|
|
60
|
+
if desc and not isinstance(item, exp.Ordered):
|
|
61
|
+
order_item = order_item.desc()
|
|
53
62
|
current_expr = current_expr.order_by(order_item, copy=False)
|
|
54
63
|
builder._expression = current_expr
|
|
55
64
|
return cast("Self", builder)
|
|
@@ -111,7 +120,7 @@ class ReturningClauseMixin:
|
|
|
111
120
|
# Type annotation for PyRight - this will be provided by the base class
|
|
112
121
|
_expression: Optional[exp.Expression]
|
|
113
122
|
|
|
114
|
-
def returning(self, *columns: Union[str, exp.Expression]) -> Self:
|
|
123
|
+
def returning(self, *columns: Union[str, exp.Expression, "Column", "ExpressionWrapper", "Case"]) -> Self:
|
|
115
124
|
"""Add RETURNING clause to the statement.
|
|
116
125
|
|
|
117
126
|
Args:
|
|
@@ -130,6 +139,9 @@ class ReturningClauseMixin:
|
|
|
130
139
|
if not isinstance(self._expression, valid_types):
|
|
131
140
|
msg = "RETURNING is only supported for INSERT, UPDATE, and DELETE statements."
|
|
132
141
|
raise SQLBuilderError(msg)
|
|
133
|
-
|
|
142
|
+
# Extract expressions from various wrapper types
|
|
143
|
+
from sqlspec._sql import SQLFactory
|
|
144
|
+
|
|
145
|
+
returning_exprs = [SQLFactory._extract_expression(c) for c in columns]
|
|
134
146
|
self._expression.set("returning", exp.Returning(expressions=returning_exprs))
|
|
135
147
|
return self
|
|
@@ -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
|
-
|
|
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.
|
|
@@ -858,7 +854,7 @@ class Case:
|
|
|
858
854
|
from sqlspec._sql import SQLFactory
|
|
859
855
|
|
|
860
856
|
cond_expr = exp.maybe_parse(condition) or exp.column(condition) if isinstance(condition, str) else condition
|
|
861
|
-
val_expr = SQLFactory.
|
|
857
|
+
val_expr = SQLFactory._to_expression(value)
|
|
862
858
|
|
|
863
859
|
# SQLGlot uses exp.If for CASE WHEN clauses, not exp.When
|
|
864
860
|
when_clause = exp.If(this=cond_expr, true=val_expr)
|
|
@@ -876,7 +872,7 @@ class Case:
|
|
|
876
872
|
"""
|
|
877
873
|
from sqlspec._sql import SQLFactory
|
|
878
874
|
|
|
879
|
-
self._default = SQLFactory.
|
|
875
|
+
self._default = SQLFactory._to_expression(value)
|
|
880
876
|
return self
|
|
881
877
|
|
|
882
878
|
def end(self) -> Self:
|
|
@@ -111,10 +111,10 @@ class UpdateSetClauseMixin:
|
|
|
111
111
|
"""Set columns and values for the UPDATE statement.
|
|
112
112
|
|
|
113
113
|
Supports:
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
114
|
+
- set_(column, value)
|
|
115
|
+
- set_(mapping)
|
|
116
|
+
- set_(**kwargs)
|
|
117
|
+
- set_(mapping, **kwargs)
|
|
118
118
|
|
|
119
119
|
Args:
|
|
120
120
|
*args: Either (column, value) or a mapping.
|
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
|
-
"""
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|