sqlspec 0.14.1__py3-none-any.whl → 0.16.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 +50 -25
- sqlspec/__main__.py +1 -1
- sqlspec/__metadata__.py +1 -3
- sqlspec/_serialization.py +1 -2
- sqlspec/_sql.py +480 -121
- sqlspec/_typing.py +278 -142
- sqlspec/adapters/adbc/__init__.py +4 -3
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +115 -260
- sqlspec/adapters/adbc/driver.py +462 -367
- sqlspec/adapters/aiosqlite/__init__.py +18 -3
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +199 -129
- sqlspec/adapters/aiosqlite/driver.py +230 -269
- sqlspec/adapters/asyncmy/__init__.py +18 -3
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +80 -168
- sqlspec/adapters/asyncmy/driver.py +260 -225
- sqlspec/adapters/asyncpg/__init__.py +19 -4
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +82 -181
- sqlspec/adapters/asyncpg/driver.py +285 -383
- sqlspec/adapters/bigquery/__init__.py +17 -3
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +191 -258
- sqlspec/adapters/bigquery/driver.py +474 -646
- sqlspec/adapters/duckdb/__init__.py +14 -3
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +415 -351
- sqlspec/adapters/duckdb/driver.py +343 -413
- sqlspec/adapters/oracledb/__init__.py +19 -5
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +123 -379
- sqlspec/adapters/oracledb/driver.py +507 -560
- sqlspec/adapters/psqlpy/__init__.py +13 -3
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +93 -254
- sqlspec/adapters/psqlpy/driver.py +505 -234
- sqlspec/adapters/psycopg/__init__.py +19 -5
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +143 -403
- sqlspec/adapters/psycopg/driver.py +706 -872
- sqlspec/adapters/sqlite/__init__.py +14 -3
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +202 -118
- sqlspec/adapters/sqlite/driver.py +264 -303
- sqlspec/base.py +105 -9
- sqlspec/{statement/builder → builder}/__init__.py +12 -14
- sqlspec/{statement/builder → builder}/_base.py +120 -55
- sqlspec/{statement/builder → builder}/_column.py +17 -6
- sqlspec/{statement/builder → builder}/_ddl.py +46 -79
- sqlspec/{statement/builder → builder}/_ddl_utils.py +5 -10
- sqlspec/{statement/builder → builder}/_delete.py +6 -25
- sqlspec/{statement/builder → builder}/_insert.py +18 -65
- sqlspec/builder/_merge.py +56 -0
- sqlspec/{statement/builder → builder}/_parsing_utils.py +8 -11
- sqlspec/{statement/builder → builder}/_select.py +11 -56
- sqlspec/{statement/builder → builder}/_update.py +12 -18
- sqlspec/{statement/builder → builder}/mixins/__init__.py +10 -14
- sqlspec/{statement/builder → builder}/mixins/_cte_and_set_ops.py +48 -59
- sqlspec/{statement/builder → builder}/mixins/_insert_operations.py +34 -18
- sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
- sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +19 -9
- sqlspec/{statement/builder → builder}/mixins/_order_limit_operations.py +3 -3
- sqlspec/{statement/builder → builder}/mixins/_pivot_operations.py +4 -8
- sqlspec/{statement/builder → builder}/mixins/_select_operations.py +25 -38
- sqlspec/{statement/builder → builder}/mixins/_update_operations.py +15 -16
- sqlspec/{statement/builder → builder}/mixins/_where_clause.py +210 -137
- sqlspec/cli.py +4 -5
- sqlspec/config.py +180 -133
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.py +873 -0
- sqlspec/core/compiler.py +396 -0
- sqlspec/core/filters.py +830 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.py +1209 -0
- sqlspec/core/result.py +664 -0
- sqlspec/{statement → core}/splitter.py +321 -191
- sqlspec/core/statement.py +666 -0
- sqlspec/driver/__init__.py +7 -10
- sqlspec/driver/_async.py +387 -176
- sqlspec/driver/_common.py +527 -289
- sqlspec/driver/_sync.py +390 -172
- sqlspec/driver/mixins/__init__.py +2 -19
- sqlspec/driver/mixins/_result_tools.py +164 -0
- sqlspec/driver/mixins/_sql_translator.py +6 -3
- sqlspec/exceptions.py +5 -252
- sqlspec/extensions/aiosql/adapter.py +93 -96
- sqlspec/extensions/litestar/cli.py +1 -1
- sqlspec/extensions/litestar/config.py +0 -1
- sqlspec/extensions/litestar/handlers.py +15 -26
- sqlspec/extensions/litestar/plugin.py +18 -16
- sqlspec/extensions/litestar/providers.py +17 -52
- sqlspec/loader.py +424 -105
- sqlspec/migrations/__init__.py +12 -0
- sqlspec/migrations/base.py +92 -68
- sqlspec/migrations/commands.py +24 -106
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +49 -51
- sqlspec/migrations/tracker.py +31 -44
- sqlspec/migrations/utils.py +64 -24
- sqlspec/protocols.py +7 -183
- sqlspec/storage/__init__.py +1 -1
- sqlspec/storage/backends/base.py +37 -40
- sqlspec/storage/backends/fsspec.py +136 -112
- sqlspec/storage/backends/obstore.py +138 -160
- sqlspec/storage/capabilities.py +5 -4
- sqlspec/storage/registry.py +57 -106
- sqlspec/typing.py +136 -115
- sqlspec/utils/__init__.py +2 -3
- sqlspec/utils/correlation.py +0 -3
- sqlspec/utils/deprecation.py +6 -6
- sqlspec/utils/fixtures.py +6 -6
- sqlspec/utils/logging.py +0 -2
- sqlspec/utils/module_loader.py +7 -12
- sqlspec/utils/singleton.py +0 -1
- sqlspec/utils/sync_tools.py +17 -38
- sqlspec/utils/text.py +12 -51
- sqlspec/utils/type_guards.py +443 -232
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/METADATA +7 -2
- sqlspec-0.16.0.dist-info/RECORD +134 -0
- sqlspec/adapters/adbc/transformers.py +0 -108
- sqlspec/driver/connection.py +0 -207
- sqlspec/driver/mixins/_cache.py +0 -114
- sqlspec/driver/mixins/_csv_writer.py +0 -91
- sqlspec/driver/mixins/_pipeline.py +0 -508
- sqlspec/driver/mixins/_query_tools.py +0 -796
- sqlspec/driver/mixins/_result_utils.py +0 -138
- sqlspec/driver/mixins/_storage.py +0 -912
- sqlspec/driver/mixins/_type_coercion.py +0 -128
- sqlspec/driver/parameters.py +0 -138
- sqlspec/statement/__init__.py +0 -21
- sqlspec/statement/builder/_merge.py +0 -95
- sqlspec/statement/cache.py +0 -50
- sqlspec/statement/filters.py +0 -625
- sqlspec/statement/parameters.py +0 -956
- sqlspec/statement/pipelines/__init__.py +0 -210
- sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
- sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
- sqlspec/statement/pipelines/context.py +0 -109
- sqlspec/statement/pipelines/transformers/__init__.py +0 -7
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
- sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
- sqlspec/statement/pipelines/validators/__init__.py +0 -23
- sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
- sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
- sqlspec/statement/pipelines/validators/_performance.py +0 -714
- sqlspec/statement/pipelines/validators/_security.py +0 -967
- sqlspec/statement/result.py +0 -435
- sqlspec/statement/sql.py +0 -1774
- sqlspec/utils/cached_property.py +0 -25
- sqlspec/utils/statement_hashing.py +0 -203
- sqlspec-0.14.1.dist-info/RECORD +0 -145
- /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
"""Type coercion mixin for database drivers.
|
|
2
|
-
|
|
3
|
-
This module provides a mixin that all database drivers use to handle
|
|
4
|
-
TypedParameter objects and perform appropriate type conversions.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from decimal import Decimal
|
|
8
|
-
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
9
|
-
|
|
10
|
-
from sqlspec.utils.type_guards import has_parameter_value
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from sqlspec.typing import SQLParameterType
|
|
14
|
-
|
|
15
|
-
__all__ = ("TypeCoercionMixin",)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class TypeCoercionMixin:
|
|
19
|
-
"""Mixin providing type coercion for database drivers.
|
|
20
|
-
|
|
21
|
-
This mixin is used by all database drivers to handle TypedParameter objects
|
|
22
|
-
and convert values to database-specific types.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
def _process_parameters(self, parameters: "SQLParameterType") -> "SQLParameterType":
|
|
26
|
-
"""Process parameters, extracting values from TypedParameter objects.
|
|
27
|
-
|
|
28
|
-
This method is called by drivers before executing SQL to handle
|
|
29
|
-
TypedParameter objects and perform necessary type conversions.
|
|
30
|
-
|
|
31
|
-
Args:
|
|
32
|
-
parameters: Raw parameters that may contain TypedParameter objects
|
|
33
|
-
|
|
34
|
-
Returns:
|
|
35
|
-
Processed parameters with TypedParameter values extracted and converted
|
|
36
|
-
"""
|
|
37
|
-
if parameters is None:
|
|
38
|
-
return None
|
|
39
|
-
|
|
40
|
-
if isinstance(parameters, dict):
|
|
41
|
-
return self._process_dict_parameters(parameters)
|
|
42
|
-
if isinstance(parameters, (list, tuple)):
|
|
43
|
-
return self._process_sequence_parameters(parameters)
|
|
44
|
-
# Single scalar parameter
|
|
45
|
-
return self._coerce_parameter_type(parameters)
|
|
46
|
-
|
|
47
|
-
def _process_dict_parameters(self, params: dict[str, Any]) -> dict[str, Any]:
|
|
48
|
-
"""Process dictionary parameters."""
|
|
49
|
-
result = {}
|
|
50
|
-
for key, value in params.items():
|
|
51
|
-
result[key] = self._coerce_parameter_type(value)
|
|
52
|
-
return result
|
|
53
|
-
|
|
54
|
-
def _process_sequence_parameters(self, params: Union[list, tuple]) -> Union[list, tuple]:
|
|
55
|
-
"""Process list/tuple parameters."""
|
|
56
|
-
result = [self._coerce_parameter_type(p) for p in params]
|
|
57
|
-
return tuple(result) if isinstance(params, tuple) else result
|
|
58
|
-
|
|
59
|
-
def _coerce_parameter_type(self, param: Any) -> Any:
|
|
60
|
-
"""Coerce a single parameter to the appropriate database type.
|
|
61
|
-
|
|
62
|
-
This method checks if the parameter is a TypedParameter and extracts
|
|
63
|
-
its value, then applies driver-specific type conversions.
|
|
64
|
-
|
|
65
|
-
Args:
|
|
66
|
-
param: Parameter value or TypedParameter object
|
|
67
|
-
|
|
68
|
-
Returns:
|
|
69
|
-
Coerced parameter value suitable for the database
|
|
70
|
-
"""
|
|
71
|
-
if has_parameter_value(param):
|
|
72
|
-
value = param.value
|
|
73
|
-
type_hint = param.type_hint
|
|
74
|
-
|
|
75
|
-
return self._apply_type_coercion(value, type_hint)
|
|
76
|
-
# Regular parameter - apply default coercion
|
|
77
|
-
return self._apply_type_coercion(param, None)
|
|
78
|
-
|
|
79
|
-
def _apply_type_coercion(self, value: Any, type_hint: Optional[str]) -> Any:
|
|
80
|
-
"""Apply driver-specific type coercion.
|
|
81
|
-
|
|
82
|
-
This method should be overridden by each driver to implement
|
|
83
|
-
database-specific type conversions.
|
|
84
|
-
|
|
85
|
-
Args:
|
|
86
|
-
value: The value to coerce
|
|
87
|
-
type_hint: Optional type hint from TypedParameter
|
|
88
|
-
|
|
89
|
-
Returns:
|
|
90
|
-
Coerced value
|
|
91
|
-
"""
|
|
92
|
-
# Default implementation - override in specific drivers
|
|
93
|
-
# This base implementation handles common cases
|
|
94
|
-
|
|
95
|
-
if value is None:
|
|
96
|
-
return None
|
|
97
|
-
|
|
98
|
-
# Use type hint if available
|
|
99
|
-
if type_hint:
|
|
100
|
-
if type_hint == "boolean":
|
|
101
|
-
return self._coerce_boolean(value)
|
|
102
|
-
if type_hint == "decimal":
|
|
103
|
-
return self._coerce_decimal(value)
|
|
104
|
-
if type_hint == "json":
|
|
105
|
-
return self._coerce_json(value)
|
|
106
|
-
if type_hint.startswith("array"):
|
|
107
|
-
return self._coerce_array(value)
|
|
108
|
-
|
|
109
|
-
# Default: return value as-is
|
|
110
|
-
return value
|
|
111
|
-
|
|
112
|
-
def _coerce_boolean(self, value: Any) -> Any:
|
|
113
|
-
"""Coerce boolean values. Override in drivers without native boolean support."""
|
|
114
|
-
return value
|
|
115
|
-
|
|
116
|
-
def _coerce_decimal(self, value: Any) -> Any:
|
|
117
|
-
"""Coerce decimal values. Override for specific decimal handling."""
|
|
118
|
-
if isinstance(value, str):
|
|
119
|
-
return Decimal(value)
|
|
120
|
-
return value
|
|
121
|
-
|
|
122
|
-
def _coerce_json(self, value: Any) -> Any:
|
|
123
|
-
"""Coerce JSON values. Override for databases needing JSON strings."""
|
|
124
|
-
return value
|
|
125
|
-
|
|
126
|
-
def _coerce_array(self, value: Any) -> Any:
|
|
127
|
-
"""Coerce array values. Override for databases without native array support."""
|
|
128
|
-
return value
|
sqlspec/driver/parameters.py
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
"""Consolidated parameter processing utilities for database drivers.
|
|
2
|
-
|
|
3
|
-
This module provides centralized parameter handling logic to avoid duplication
|
|
4
|
-
across sync and async driver implementations.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
8
|
-
|
|
9
|
-
from sqlspec.statement.filters import StatementFilter
|
|
10
|
-
from sqlspec.utils.type_guards import is_sync_transaction_capable
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from sqlspec.typing import StatementParameters
|
|
14
|
-
|
|
15
|
-
__all__ = (
|
|
16
|
-
"convert_parameter_sequence",
|
|
17
|
-
"convert_parameters_to_positional",
|
|
18
|
-
"process_execute_many_parameters",
|
|
19
|
-
"separate_filters_and_parameters",
|
|
20
|
-
"should_use_transaction",
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def separate_filters_and_parameters(
|
|
25
|
-
parameters: "tuple[Union[StatementParameters, StatementFilter], ...]",
|
|
26
|
-
) -> "tuple[list[StatementFilter], list[Any]]":
|
|
27
|
-
"""Separate filters from parameters in a mixed parameter tuple.
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
parameters: Mixed tuple of parameters and filters
|
|
31
|
-
|
|
32
|
-
Returns:
|
|
33
|
-
Tuple of (filters, parameters) lists
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
filters: list[StatementFilter] = []
|
|
37
|
-
param_values: list[Any] = []
|
|
38
|
-
|
|
39
|
-
for param in parameters:
|
|
40
|
-
if isinstance(param, StatementFilter):
|
|
41
|
-
filters.append(param)
|
|
42
|
-
else:
|
|
43
|
-
param_values.append(param)
|
|
44
|
-
|
|
45
|
-
return filters, param_values
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def process_execute_many_parameters(
|
|
49
|
-
parameters: "tuple[Union[StatementParameters, StatementFilter], ...]",
|
|
50
|
-
) -> "tuple[list[StatementFilter], Optional[list[Any]]]":
|
|
51
|
-
"""Process parameters for execute_many operations.
|
|
52
|
-
|
|
53
|
-
Args:
|
|
54
|
-
parameters: Mixed tuple of parameters and filters
|
|
55
|
-
|
|
56
|
-
Returns:
|
|
57
|
-
Tuple of (filters, parameter_sequence)
|
|
58
|
-
"""
|
|
59
|
-
filters, param_values = separate_filters_and_parameters(parameters)
|
|
60
|
-
|
|
61
|
-
# Use first parameter as the sequence for execute_many
|
|
62
|
-
param_sequence = param_values[0] if param_values else None
|
|
63
|
-
|
|
64
|
-
# Normalize the parameter sequence
|
|
65
|
-
param_sequence = convert_parameter_sequence(param_sequence)
|
|
66
|
-
|
|
67
|
-
return filters, param_sequence
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def convert_parameter_sequence(params: Any) -> Optional[list[Any]]:
|
|
71
|
-
"""Normalize a parameter sequence to a list format.
|
|
72
|
-
|
|
73
|
-
Args:
|
|
74
|
-
params: Parameter sequence in various formats
|
|
75
|
-
|
|
76
|
-
Returns:
|
|
77
|
-
converted list of parameters or None
|
|
78
|
-
"""
|
|
79
|
-
if params is None:
|
|
80
|
-
return None
|
|
81
|
-
|
|
82
|
-
if isinstance(params, list):
|
|
83
|
-
return params
|
|
84
|
-
|
|
85
|
-
if isinstance(params, tuple):
|
|
86
|
-
return list(params)
|
|
87
|
-
|
|
88
|
-
# Check if it's iterable (but not string or dict)
|
|
89
|
-
# Use duck typing to check for iterable protocol
|
|
90
|
-
try:
|
|
91
|
-
iter(params)
|
|
92
|
-
if not isinstance(params, (str, dict)):
|
|
93
|
-
return list(params)
|
|
94
|
-
except TypeError:
|
|
95
|
-
pass
|
|
96
|
-
|
|
97
|
-
# Single parameter, wrap in list
|
|
98
|
-
return [params]
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def convert_parameters_to_positional(params: "dict[str, Any]", parameter_info: "list[Any]") -> list[Any]:
|
|
102
|
-
"""Convert named parameters to positional based on SQL order.
|
|
103
|
-
|
|
104
|
-
Args:
|
|
105
|
-
params: Dictionary of named parameters
|
|
106
|
-
parameter_info: List of parameter info from SQL parsing
|
|
107
|
-
|
|
108
|
-
Returns:
|
|
109
|
-
List of positional parameters
|
|
110
|
-
"""
|
|
111
|
-
if not params:
|
|
112
|
-
return []
|
|
113
|
-
|
|
114
|
-
# Handle param_0, param_1, etc. pattern
|
|
115
|
-
if all(key.startswith("param_") for key in params):
|
|
116
|
-
return [params[f"param_{i}"] for i in range(len(params))]
|
|
117
|
-
|
|
118
|
-
# Convert based on parameter info order
|
|
119
|
-
# Check for name attribute using getattr with default
|
|
120
|
-
result = []
|
|
121
|
-
for info in parameter_info:
|
|
122
|
-
param_name = getattr(info, "name", None)
|
|
123
|
-
if param_name is not None:
|
|
124
|
-
result.append(params.get(param_name, None))
|
|
125
|
-
return result
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def should_use_transaction(connection: Any, auto_commit: bool = True) -> bool:
|
|
129
|
-
"""Determine if a transaction should be used.
|
|
130
|
-
|
|
131
|
-
Args:
|
|
132
|
-
connection: Database connection object
|
|
133
|
-
auto_commit: Whether auto-commit is enabled
|
|
134
|
-
|
|
135
|
-
Returns:
|
|
136
|
-
True if transaction capabilities are available and should be used
|
|
137
|
-
"""
|
|
138
|
-
return False if auto_commit else is_sync_transaction_capable(connection)
|
sqlspec/statement/__init__.py
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"""SQL utilities, validation, and parameter handling."""
|
|
2
|
-
|
|
3
|
-
from sqlspec.statement import builder, filters, parameters, result, sql
|
|
4
|
-
from sqlspec.statement.filters import StatementFilter
|
|
5
|
-
from sqlspec.statement.result import ArrowResult, SQLResult, StatementResult
|
|
6
|
-
from sqlspec.statement.sql import SQL, SQLConfig, Statement
|
|
7
|
-
|
|
8
|
-
__all__ = (
|
|
9
|
-
"SQL",
|
|
10
|
-
"ArrowResult",
|
|
11
|
-
"SQLConfig",
|
|
12
|
-
"SQLResult",
|
|
13
|
-
"Statement",
|
|
14
|
-
"StatementFilter",
|
|
15
|
-
"StatementResult",
|
|
16
|
-
"builder",
|
|
17
|
-
"filters",
|
|
18
|
-
"parameters",
|
|
19
|
-
"result",
|
|
20
|
-
"sql",
|
|
21
|
-
)
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"""Safe SQL query builder with validation and parameter binding.
|
|
2
|
-
|
|
3
|
-
This module provides a fluent interface for building SQL queries safely,
|
|
4
|
-
with automatic parameter binding and validation.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from dataclasses import dataclass
|
|
8
|
-
|
|
9
|
-
from sqlglot import exp
|
|
10
|
-
|
|
11
|
-
from sqlspec.statement.builder._base import QueryBuilder
|
|
12
|
-
from sqlspec.statement.builder.mixins import (
|
|
13
|
-
MergeIntoClauseMixin,
|
|
14
|
-
MergeMatchedClauseMixin,
|
|
15
|
-
MergeNotMatchedBySourceClauseMixin,
|
|
16
|
-
MergeNotMatchedClauseMixin,
|
|
17
|
-
MergeOnClauseMixin,
|
|
18
|
-
MergeUsingClauseMixin,
|
|
19
|
-
)
|
|
20
|
-
from sqlspec.statement.result import SQLResult
|
|
21
|
-
from sqlspec.typing import RowT
|
|
22
|
-
|
|
23
|
-
__all__ = ("Merge",)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@dataclass(unsafe_hash=True)
|
|
27
|
-
class Merge(
|
|
28
|
-
QueryBuilder[RowT],
|
|
29
|
-
MergeUsingClauseMixin,
|
|
30
|
-
MergeOnClauseMixin,
|
|
31
|
-
MergeMatchedClauseMixin,
|
|
32
|
-
MergeNotMatchedClauseMixin,
|
|
33
|
-
MergeIntoClauseMixin,
|
|
34
|
-
MergeNotMatchedBySourceClauseMixin,
|
|
35
|
-
):
|
|
36
|
-
"""Builder for MERGE statements.
|
|
37
|
-
|
|
38
|
-
This builder provides a fluent interface for constructing SQL MERGE statements
|
|
39
|
-
(also known as UPSERT in some databases) with automatic parameter binding and validation.
|
|
40
|
-
|
|
41
|
-
Example:
|
|
42
|
-
```python
|
|
43
|
-
# Basic MERGE statement
|
|
44
|
-
merge_query = (
|
|
45
|
-
Merge()
|
|
46
|
-
.into("target_table")
|
|
47
|
-
.using("source_table", "src")
|
|
48
|
-
.on("target_table.id = src.id")
|
|
49
|
-
.when_matched_then_update(
|
|
50
|
-
{"name": "src.name", "updated_at": "NOW()"}
|
|
51
|
-
)
|
|
52
|
-
.when_not_matched_then_insert(
|
|
53
|
-
columns=["id", "name", "created_at"],
|
|
54
|
-
values=["src.id", "src.name", "NOW()"],
|
|
55
|
-
)
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
# MERGE with subquery source
|
|
59
|
-
source_query = (
|
|
60
|
-
SelectBuilder()
|
|
61
|
-
.select("id", "name", "email")
|
|
62
|
-
.from_("temp_users")
|
|
63
|
-
.where("status = 'pending'")
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
merge_query = (
|
|
67
|
-
Merge()
|
|
68
|
-
.into("users")
|
|
69
|
-
.using(source_query, "src")
|
|
70
|
-
.on("users.email = src.email")
|
|
71
|
-
.when_matched_then_update({"name": "src.name"})
|
|
72
|
-
.when_not_matched_then_insert(
|
|
73
|
-
columns=["id", "name", "email"],
|
|
74
|
-
values=["src.id", "src.name", "src.email"],
|
|
75
|
-
)
|
|
76
|
-
)
|
|
77
|
-
```
|
|
78
|
-
"""
|
|
79
|
-
|
|
80
|
-
@property
|
|
81
|
-
def _expected_result_type(self) -> "type[SQLResult[RowT]]":
|
|
82
|
-
"""Return the expected result type for this builder.
|
|
83
|
-
|
|
84
|
-
Returns:
|
|
85
|
-
The SQLResult type for MERGE statements.
|
|
86
|
-
"""
|
|
87
|
-
return SQLResult[RowT]
|
|
88
|
-
|
|
89
|
-
def _create_base_expression(self) -> "exp.Merge":
|
|
90
|
-
"""Create a base MERGE expression.
|
|
91
|
-
|
|
92
|
-
Returns:
|
|
93
|
-
A new sqlglot Merge expression with empty clauses.
|
|
94
|
-
"""
|
|
95
|
-
return exp.Merge(this=None, using=None, on=None, whens=exp.Whens(expressions=[]))
|
sqlspec/statement/cache.py
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
"""Cache implementation for SQL statement processing."""
|
|
2
|
-
|
|
3
|
-
import threading
|
|
4
|
-
from collections import OrderedDict
|
|
5
|
-
from typing import Any, Optional
|
|
6
|
-
|
|
7
|
-
__all__ = ("SQLCache",)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
DEFAULT_CACHE_MAX_SIZE = 1000
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class SQLCache:
|
|
14
|
-
"""A thread-safe LRU cache for processed SQL states."""
|
|
15
|
-
|
|
16
|
-
def __init__(self, max_size: int = DEFAULT_CACHE_MAX_SIZE) -> None:
|
|
17
|
-
self.cache: OrderedDict[str, Any] = OrderedDict()
|
|
18
|
-
self.max_size = max_size
|
|
19
|
-
self.lock = threading.Lock()
|
|
20
|
-
|
|
21
|
-
@property
|
|
22
|
-
def size(self) -> int:
|
|
23
|
-
"""Get current cache size."""
|
|
24
|
-
return len(self.cache)
|
|
25
|
-
|
|
26
|
-
def get(self, key: str) -> Optional[Any]:
|
|
27
|
-
"""Get an item from the cache, marking it as recently used."""
|
|
28
|
-
with self.lock:
|
|
29
|
-
if key in self.cache:
|
|
30
|
-
self.cache.move_to_end(key)
|
|
31
|
-
return self.cache[key]
|
|
32
|
-
return None
|
|
33
|
-
|
|
34
|
-
def set(self, key: str, value: Any) -> None:
|
|
35
|
-
"""Set an item in the cache with LRU eviction."""
|
|
36
|
-
with self.lock:
|
|
37
|
-
if key in self.cache:
|
|
38
|
-
self.cache.move_to_end(key)
|
|
39
|
-
# Add new entry
|
|
40
|
-
elif len(self.cache) >= self.max_size:
|
|
41
|
-
self.cache.popitem(last=False)
|
|
42
|
-
self.cache[key] = value
|
|
43
|
-
|
|
44
|
-
def clear(self) -> None:
|
|
45
|
-
"""Clear the cache."""
|
|
46
|
-
with self.lock:
|
|
47
|
-
self.cache.clear()
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
sql_cache = SQLCache()
|