sqlspec 0.25.0__py3-none-any.whl → 0.27.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 +7 -15
- sqlspec/_serialization.py +256 -24
- sqlspec/_typing.py +71 -52
- sqlspec/adapters/adbc/_types.py +1 -1
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +870 -0
- sqlspec/adapters/adbc/config.py +69 -12
- sqlspec/adapters/adbc/data_dictionary.py +340 -0
- sqlspec/adapters/adbc/driver.py +266 -58
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +504 -0
- sqlspec/adapters/adbc/type_converter.py +153 -0
- sqlspec/adapters/aiosqlite/_types.py +1 -1
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +527 -0
- sqlspec/adapters/aiosqlite/config.py +88 -15
- sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
- sqlspec/adapters/aiosqlite/driver.py +143 -40
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
- sqlspec/adapters/aiosqlite/pool.py +7 -7
- sqlspec/adapters/asyncmy/__init__.py +7 -1
- sqlspec/adapters/asyncmy/_types.py +2 -2
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +493 -0
- sqlspec/adapters/asyncmy/config.py +68 -23
- sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
- sqlspec/adapters/asyncmy/driver.py +313 -58
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +2 -1
- sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
- sqlspec/adapters/asyncpg/_types.py +11 -7
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +450 -0
- sqlspec/adapters/asyncpg/config.py +59 -35
- sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
- sqlspec/adapters/asyncpg/driver.py +170 -25
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +253 -0
- sqlspec/adapters/bigquery/_types.py +1 -1
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +576 -0
- sqlspec/adapters/bigquery/config.py +27 -10
- sqlspec/adapters/bigquery/data_dictionary.py +149 -0
- sqlspec/adapters/bigquery/driver.py +368 -142
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +327 -0
- sqlspec/adapters/bigquery/type_converter.py +125 -0
- sqlspec/adapters/duckdb/_types.py +1 -1
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +553 -0
- sqlspec/adapters/duckdb/config.py +80 -20
- sqlspec/adapters/duckdb/data_dictionary.py +163 -0
- sqlspec/adapters/duckdb/driver.py +167 -45
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +4 -4
- sqlspec/adapters/duckdb/type_converter.py +133 -0
- sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
- sqlspec/adapters/oracledb/_types.py +20 -2
- sqlspec/adapters/oracledb/adk/__init__.py +5 -0
- sqlspec/adapters/oracledb/adk/store.py +1745 -0
- sqlspec/adapters/oracledb/config.py +122 -32
- sqlspec/adapters/oracledb/data_dictionary.py +509 -0
- sqlspec/adapters/oracledb/driver.py +353 -91
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +767 -0
- sqlspec/adapters/oracledb/migrations.py +348 -73
- sqlspec/adapters/oracledb/type_converter.py +207 -0
- sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
- sqlspec/adapters/psqlpy/_types.py +2 -1
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +482 -0
- sqlspec/adapters/psqlpy/config.py +46 -17
- sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
- sqlspec/adapters/psqlpy/driver.py +123 -209
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +272 -0
- sqlspec/adapters/psqlpy/type_converter.py +102 -0
- sqlspec/adapters/psycopg/_type_handlers.py +80 -0
- sqlspec/adapters/psycopg/_types.py +2 -1
- sqlspec/adapters/psycopg/adk/__init__.py +5 -0
- sqlspec/adapters/psycopg/adk/store.py +944 -0
- sqlspec/adapters/psycopg/config.py +69 -35
- sqlspec/adapters/psycopg/data_dictionary.py +331 -0
- sqlspec/adapters/psycopg/driver.py +238 -81
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/sqlite/__init__.py +2 -1
- sqlspec/adapters/sqlite/_type_handlers.py +86 -0
- sqlspec/adapters/sqlite/_types.py +1 -1
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +572 -0
- sqlspec/adapters/sqlite/config.py +87 -15
- sqlspec/adapters/sqlite/data_dictionary.py +149 -0
- sqlspec/adapters/sqlite/driver.py +137 -54
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +18 -9
- sqlspec/base.py +45 -26
- sqlspec/builder/__init__.py +73 -4
- sqlspec/builder/_base.py +162 -89
- sqlspec/builder/_column.py +62 -29
- sqlspec/builder/_ddl.py +180 -121
- sqlspec/builder/_delete.py +5 -4
- sqlspec/builder/_dml.py +388 -0
- sqlspec/{_sql.py → builder/_factory.py} +53 -94
- sqlspec/builder/_insert.py +32 -131
- sqlspec/builder/_join.py +375 -0
- sqlspec/builder/_merge.py +446 -11
- sqlspec/builder/_parsing_utils.py +111 -17
- sqlspec/builder/_select.py +1457 -24
- sqlspec/builder/_update.py +11 -42
- sqlspec/cli.py +307 -194
- sqlspec/config.py +252 -67
- sqlspec/core/__init__.py +5 -4
- sqlspec/core/cache.py +17 -17
- sqlspec/core/compiler.py +62 -9
- sqlspec/core/filters.py +37 -37
- sqlspec/core/hashing.py +9 -9
- sqlspec/core/parameters.py +83 -48
- sqlspec/core/result.py +102 -46
- sqlspec/core/splitter.py +16 -17
- sqlspec/core/statement.py +36 -30
- sqlspec/core/type_conversion.py +235 -0
- sqlspec/driver/__init__.py +7 -6
- sqlspec/driver/_async.py +188 -151
- sqlspec/driver/_common.py +285 -80
- sqlspec/driver/_sync.py +188 -152
- sqlspec/driver/mixins/_result_tools.py +20 -236
- sqlspec/driver/mixins/_sql_translator.py +4 -4
- sqlspec/exceptions.py +75 -7
- sqlspec/extensions/adk/__init__.py +53 -0
- sqlspec/extensions/adk/_types.py +51 -0
- sqlspec/extensions/adk/converters.py +172 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +181 -0
- sqlspec/extensions/adk/store.py +536 -0
- sqlspec/extensions/aiosql/adapter.py +73 -53
- sqlspec/extensions/litestar/__init__.py +21 -4
- sqlspec/extensions/litestar/cli.py +54 -10
- sqlspec/extensions/litestar/config.py +59 -266
- sqlspec/extensions/litestar/handlers.py +46 -17
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +324 -223
- sqlspec/extensions/litestar/providers.py +25 -25
- sqlspec/extensions/litestar/store.py +265 -0
- sqlspec/loader.py +30 -49
- sqlspec/migrations/__init__.py +4 -3
- sqlspec/migrations/base.py +302 -39
- sqlspec/migrations/commands.py +611 -144
- sqlspec/migrations/context.py +142 -0
- sqlspec/migrations/fix.py +199 -0
- sqlspec/migrations/loaders.py +68 -23
- sqlspec/migrations/runner.py +543 -107
- sqlspec/migrations/tracker.py +237 -21
- sqlspec/migrations/utils.py +51 -3
- sqlspec/migrations/validation.py +177 -0
- sqlspec/protocols.py +66 -36
- sqlspec/storage/_utils.py +98 -0
- sqlspec/storage/backends/fsspec.py +134 -106
- sqlspec/storage/backends/local.py +78 -51
- sqlspec/storage/backends/obstore.py +278 -162
- sqlspec/storage/registry.py +75 -39
- sqlspec/typing.py +16 -84
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/correlation.py +4 -5
- sqlspec/utils/data_transformation.py +3 -2
- sqlspec/utils/deprecation.py +9 -8
- sqlspec/utils/fixtures.py +4 -4
- sqlspec/utils/logging.py +46 -6
- sqlspec/utils/module_loader.py +2 -2
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +50 -2
- sqlspec/utils/sync_tools.py +21 -17
- sqlspec/utils/text.py +1 -2
- sqlspec/utils/type_guards.py +111 -20
- sqlspec/utils/version.py +433 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
- sqlspec-0.27.0.dist-info/RECORD +207 -0
- sqlspec/builder/mixins/__init__.py +0 -55
- sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
- sqlspec/builder/mixins/_delete_operations.py +0 -50
- sqlspec/builder/mixins/_insert_operations.py +0 -282
- sqlspec/builder/mixins/_join_operations.py +0 -389
- sqlspec/builder/mixins/_merge_operations.py +0 -592
- sqlspec/builder/mixins/_order_limit_operations.py +0 -152
- sqlspec/builder/mixins/_pivot_operations.py +0 -157
- sqlspec/builder/mixins/_select_operations.py +0 -936
- sqlspec/builder/mixins/_update_operations.py +0 -218
- sqlspec/builder/mixins/_where_clause.py +0 -1304
- sqlspec-0.25.0.dist-info/RECORD +0 -139
- sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
sqlspec/builder/__init__.py
CHANGED
|
@@ -24,21 +24,64 @@ from sqlspec.builder._ddl import (
|
|
|
24
24
|
Truncate,
|
|
25
25
|
)
|
|
26
26
|
from sqlspec.builder._delete import Delete
|
|
27
|
+
from sqlspec.builder._dml import (
|
|
28
|
+
DeleteFromClauseMixin,
|
|
29
|
+
InsertFromSelectMixin,
|
|
30
|
+
InsertIntoClauseMixin,
|
|
31
|
+
InsertValuesMixin,
|
|
32
|
+
UpdateFromClauseMixin,
|
|
33
|
+
UpdateSetClauseMixin,
|
|
34
|
+
UpdateTableClauseMixin,
|
|
35
|
+
)
|
|
36
|
+
from sqlspec.builder._expression_wrappers import (
|
|
37
|
+
AggregateExpression,
|
|
38
|
+
ConversionExpression,
|
|
39
|
+
FunctionExpression,
|
|
40
|
+
MathExpression,
|
|
41
|
+
StringExpression,
|
|
42
|
+
)
|
|
43
|
+
from sqlspec.builder._factory import SQLFactory, sql
|
|
27
44
|
from sqlspec.builder._insert import Insert
|
|
45
|
+
from sqlspec.builder._join import JoinBuilder
|
|
28
46
|
from sqlspec.builder._merge import Merge
|
|
29
|
-
from sqlspec.builder.
|
|
47
|
+
from sqlspec.builder._parsing_utils import (
|
|
48
|
+
extract_expression,
|
|
49
|
+
parse_column_expression,
|
|
50
|
+
parse_condition_expression,
|
|
51
|
+
parse_order_expression,
|
|
52
|
+
parse_table_expression,
|
|
53
|
+
to_expression,
|
|
54
|
+
)
|
|
55
|
+
from sqlspec.builder._select import (
|
|
56
|
+
Case,
|
|
57
|
+
CaseBuilder,
|
|
58
|
+
CommonTableExpressionMixin,
|
|
59
|
+
HavingClauseMixin,
|
|
60
|
+
LimitOffsetClauseMixin,
|
|
61
|
+
OrderByClauseMixin,
|
|
62
|
+
PivotClauseMixin,
|
|
63
|
+
ReturningClauseMixin,
|
|
64
|
+
Select,
|
|
65
|
+
SelectClauseMixin,
|
|
66
|
+
SetOperationMixin,
|
|
67
|
+
SubqueryBuilder,
|
|
68
|
+
UnpivotClauseMixin,
|
|
69
|
+
WhereClauseMixin,
|
|
70
|
+
WindowFunctionBuilder,
|
|
71
|
+
)
|
|
30
72
|
from sqlspec.builder._update import Update
|
|
31
|
-
from sqlspec.builder.mixins import WhereClauseMixin
|
|
32
|
-
from sqlspec.builder.mixins._join_operations import JoinBuilder
|
|
33
|
-
from sqlspec.builder.mixins._select_operations import Case, SubqueryBuilder, WindowFunctionBuilder
|
|
34
73
|
from sqlspec.exceptions import SQLBuilderError
|
|
35
74
|
|
|
36
75
|
__all__ = (
|
|
76
|
+
"AggregateExpression",
|
|
37
77
|
"AlterTable",
|
|
38
78
|
"Case",
|
|
79
|
+
"CaseBuilder",
|
|
39
80
|
"Column",
|
|
40
81
|
"ColumnExpression",
|
|
41
82
|
"CommentOn",
|
|
83
|
+
"CommonTableExpressionMixin",
|
|
84
|
+
"ConversionExpression",
|
|
42
85
|
"CreateIndex",
|
|
43
86
|
"CreateMaterializedView",
|
|
44
87
|
"CreateSchema",
|
|
@@ -47,22 +90,48 @@ __all__ = (
|
|
|
47
90
|
"CreateView",
|
|
48
91
|
"DDLBuilder",
|
|
49
92
|
"Delete",
|
|
93
|
+
"DeleteFromClauseMixin",
|
|
50
94
|
"DropIndex",
|
|
51
95
|
"DropSchema",
|
|
52
96
|
"DropTable",
|
|
53
97
|
"DropView",
|
|
54
98
|
"FunctionColumn",
|
|
99
|
+
"FunctionExpression",
|
|
100
|
+
"HavingClauseMixin",
|
|
55
101
|
"Insert",
|
|
102
|
+
"InsertFromSelectMixin",
|
|
103
|
+
"InsertIntoClauseMixin",
|
|
104
|
+
"InsertValuesMixin",
|
|
56
105
|
"JoinBuilder",
|
|
106
|
+
"LimitOffsetClauseMixin",
|
|
107
|
+
"MathExpression",
|
|
57
108
|
"Merge",
|
|
109
|
+
"OrderByClauseMixin",
|
|
110
|
+
"PivotClauseMixin",
|
|
58
111
|
"QueryBuilder",
|
|
59
112
|
"RenameTable",
|
|
113
|
+
"ReturningClauseMixin",
|
|
60
114
|
"SQLBuilderError",
|
|
115
|
+
"SQLFactory",
|
|
61
116
|
"SafeQuery",
|
|
62
117
|
"Select",
|
|
118
|
+
"SelectClauseMixin",
|
|
119
|
+
"SetOperationMixin",
|
|
120
|
+
"StringExpression",
|
|
63
121
|
"SubqueryBuilder",
|
|
64
122
|
"Truncate",
|
|
123
|
+
"UnpivotClauseMixin",
|
|
65
124
|
"Update",
|
|
125
|
+
"UpdateFromClauseMixin",
|
|
126
|
+
"UpdateSetClauseMixin",
|
|
127
|
+
"UpdateTableClauseMixin",
|
|
66
128
|
"WhereClauseMixin",
|
|
67
129
|
"WindowFunctionBuilder",
|
|
130
|
+
"extract_expression",
|
|
131
|
+
"parse_column_expression",
|
|
132
|
+
"parse_condition_expression",
|
|
133
|
+
"parse_order_expression",
|
|
134
|
+
"parse_table_expression",
|
|
135
|
+
"sql",
|
|
136
|
+
"to_expression",
|
|
68
137
|
)
|
sqlspec/builder/_base.py
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
Provides abstract base classes and core functionality for SQL query builders.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
import hashlib
|
|
7
|
+
import uuid
|
|
6
8
|
from abc import ABC, abstractmethod
|
|
7
|
-
from typing import TYPE_CHECKING, Any, NoReturn,
|
|
9
|
+
from typing import TYPE_CHECKING, Any, NoReturn, cast
|
|
8
10
|
|
|
9
11
|
import sqlglot
|
|
10
12
|
from sqlglot import Dialect, exp
|
|
@@ -19,13 +21,15 @@ from sqlspec.core.parameters import ParameterStyle, ParameterStyleConfig
|
|
|
19
21
|
from sqlspec.core.statement import SQL, StatementConfig
|
|
20
22
|
from sqlspec.exceptions import SQLBuilderError
|
|
21
23
|
from sqlspec.utils.logging import get_logger
|
|
22
|
-
from sqlspec.utils.type_guards import has_expression_and_parameters, has_sql_method, has_with_method
|
|
24
|
+
from sqlspec.utils.type_guards import has_expression_and_parameters, has_sql_method, has_with_method, is_expression
|
|
23
25
|
|
|
24
26
|
if TYPE_CHECKING:
|
|
25
27
|
from sqlspec.core.result import SQLResult
|
|
26
28
|
|
|
27
29
|
__all__ = ("QueryBuilder", "SafeQuery")
|
|
28
30
|
|
|
31
|
+
MAX_PARAMETER_COLLISION_ATTEMPTS = 1000
|
|
32
|
+
|
|
29
33
|
logger = get_logger(__name__)
|
|
30
34
|
|
|
31
35
|
|
|
@@ -34,9 +38,7 @@ class SafeQuery:
|
|
|
34
38
|
|
|
35
39
|
__slots__ = ("dialect", "parameters", "sql")
|
|
36
40
|
|
|
37
|
-
def __init__(
|
|
38
|
-
self, sql: str, parameters: Optional[dict[str, Any]] = None, dialect: Optional[DialectType] = None
|
|
39
|
-
) -> None:
|
|
41
|
+
def __init__(self, sql: str, parameters: dict[str, Any] | None = None, dialect: DialectType | None = None) -> None:
|
|
40
42
|
self.sql = sql
|
|
41
43
|
self.parameters = parameters if parameters is not None else {}
|
|
42
44
|
self.dialect = dialect
|
|
@@ -64,8 +66,8 @@ class QueryBuilder(ABC):
|
|
|
64
66
|
|
|
65
67
|
def __init__(
|
|
66
68
|
self,
|
|
67
|
-
dialect:
|
|
68
|
-
schema:
|
|
69
|
+
dialect: DialectType | None = None,
|
|
70
|
+
schema: dict[str, dict[str, str]] | None = None,
|
|
69
71
|
enable_optimization: bool = True,
|
|
70
72
|
optimize_joins: bool = True,
|
|
71
73
|
optimize_predicates: bool = True,
|
|
@@ -78,7 +80,7 @@ class QueryBuilder(ABC):
|
|
|
78
80
|
self.optimize_predicates = optimize_predicates
|
|
79
81
|
self.simplify_expressions = simplify_expressions
|
|
80
82
|
|
|
81
|
-
self._expression:
|
|
83
|
+
self._expression: exp.Expression | None = None
|
|
82
84
|
self._parameters: dict[str, Any] = {}
|
|
83
85
|
self._parameter_counter: int = 0
|
|
84
86
|
self._with_ctes: dict[str, exp.CTE] = {}
|
|
@@ -91,7 +93,7 @@ class QueryBuilder(ABC):
|
|
|
91
93
|
"QueryBuilder._create_base_expression must return a valid sqlglot expression."
|
|
92
94
|
)
|
|
93
95
|
|
|
94
|
-
def get_expression(self) ->
|
|
96
|
+
def get_expression(self) -> exp.Expression | None:
|
|
95
97
|
"""Get expression reference (no copy).
|
|
96
98
|
|
|
97
99
|
Returns:
|
|
@@ -104,13 +106,9 @@ class QueryBuilder(ABC):
|
|
|
104
106
|
|
|
105
107
|
Args:
|
|
106
108
|
expression: SQLGlot expression to set
|
|
107
|
-
|
|
108
|
-
Raises:
|
|
109
|
-
TypeError: If expression is not a SQLGlot Expression
|
|
110
109
|
"""
|
|
111
|
-
if not
|
|
112
|
-
|
|
113
|
-
raise TypeError(msg)
|
|
110
|
+
if not is_expression(expression):
|
|
111
|
+
self._raise_invalid_expression_type(expression)
|
|
114
112
|
self._expression = expression
|
|
115
113
|
|
|
116
114
|
def has_expression(self) -> bool:
|
|
@@ -139,7 +137,7 @@ class QueryBuilder(ABC):
|
|
|
139
137
|
"""
|
|
140
138
|
|
|
141
139
|
@staticmethod
|
|
142
|
-
def _raise_sql_builder_error(message: str, cause:
|
|
140
|
+
def _raise_sql_builder_error(message: str, cause: BaseException | None = None) -> NoReturn:
|
|
143
141
|
"""Helper to raise SQLBuilderError, potentially with a cause.
|
|
144
142
|
|
|
145
143
|
Args:
|
|
@@ -151,7 +149,121 @@ class QueryBuilder(ABC):
|
|
|
151
149
|
"""
|
|
152
150
|
raise SQLBuilderError(message) from cause
|
|
153
151
|
|
|
154
|
-
|
|
152
|
+
@staticmethod
|
|
153
|
+
def _raise_invalid_expression_type(expression: Any) -> NoReturn:
|
|
154
|
+
"""Raise error for invalid expression type.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
expression: The invalid expression object
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
TypeError: Always raised for type mismatch
|
|
161
|
+
"""
|
|
162
|
+
msg = f"Expected Expression, got {type(expression)}"
|
|
163
|
+
raise TypeError(msg)
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def _raise_cte_query_error(alias: str, message: str) -> NoReturn:
|
|
167
|
+
"""Raise error for CTE query issues.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
alias: CTE alias name
|
|
171
|
+
message: Specific error message
|
|
172
|
+
|
|
173
|
+
Raises:
|
|
174
|
+
SQLBuilderError: Always raised for CTE errors
|
|
175
|
+
"""
|
|
176
|
+
msg = f"CTE '{alias}': {message}"
|
|
177
|
+
raise SQLBuilderError(msg)
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def _raise_cte_parse_error(cause: BaseException) -> NoReturn:
|
|
181
|
+
"""Raise error for CTE parsing failures.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
cause: The original parsing exception
|
|
185
|
+
|
|
186
|
+
Raises:
|
|
187
|
+
SQLBuilderError: Always raised with chained cause
|
|
188
|
+
"""
|
|
189
|
+
msg = f"Failed to parse CTE query: {cause!s}"
|
|
190
|
+
raise SQLBuilderError(msg) from cause
|
|
191
|
+
|
|
192
|
+
def _build_final_expression(self, *, copy: bool = False) -> exp.Expression:
|
|
193
|
+
"""Construct the current expression with attached CTEs.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
copy: Whether to copy the underlying expression tree before
|
|
197
|
+
applying transformations.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Expression representing the current builder state with CTEs applied.
|
|
201
|
+
"""
|
|
202
|
+
if self._expression is None:
|
|
203
|
+
self._raise_sql_builder_error("QueryBuilder expression not initialized.")
|
|
204
|
+
|
|
205
|
+
base_expression = self._expression.copy() if copy else self._expression
|
|
206
|
+
|
|
207
|
+
if not self._with_ctes:
|
|
208
|
+
return base_expression
|
|
209
|
+
|
|
210
|
+
final_expression: exp.Expression = base_expression
|
|
211
|
+
if has_with_method(final_expression):
|
|
212
|
+
for alias, cte_node in self._with_ctes.items():
|
|
213
|
+
final_expression = cast("Any", final_expression).with_(cte_node.args["this"], as_=alias, copy=False)
|
|
214
|
+
return cast("exp.Expression", final_expression)
|
|
215
|
+
|
|
216
|
+
if isinstance(final_expression, (exp.Select, exp.Insert, exp.Update, exp.Delete, exp.Union)):
|
|
217
|
+
return exp.With(expressions=list(self._with_ctes.values()), this=final_expression)
|
|
218
|
+
|
|
219
|
+
return final_expression
|
|
220
|
+
|
|
221
|
+
def _spawn_like_self(self: Self) -> Self:
|
|
222
|
+
"""Create a new builder instance with matching configuration."""
|
|
223
|
+
return type(self)(
|
|
224
|
+
dialect=self.dialect,
|
|
225
|
+
schema=self.schema,
|
|
226
|
+
enable_optimization=self.enable_optimization,
|
|
227
|
+
optimize_joins=self.optimize_joins,
|
|
228
|
+
optimize_predicates=self.optimize_predicates,
|
|
229
|
+
simplify_expressions=self.simplify_expressions,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
def _resolve_cte_query(self, alias: str, query: "QueryBuilder | exp.Select | str") -> exp.Select:
|
|
233
|
+
"""Resolve a CTE query into a Select expression with merged parameters."""
|
|
234
|
+
if isinstance(query, QueryBuilder):
|
|
235
|
+
query_expr = query.get_expression()
|
|
236
|
+
if query_expr is None:
|
|
237
|
+
self._raise_cte_query_error(alias, "query builder has no expression")
|
|
238
|
+
if not isinstance(query_expr, exp.Select):
|
|
239
|
+
self._raise_cte_query_error(alias, f"expression must be a Select, got {type(query_expr).__name__}")
|
|
240
|
+
cte_select_expression = query_expr.copy()
|
|
241
|
+
param_mapping = self._merge_cte_parameters(alias, query.parameters)
|
|
242
|
+
updated_expression = self._update_placeholders_in_expression(cte_select_expression, param_mapping)
|
|
243
|
+
if not isinstance(updated_expression, exp.Select): # pragma: no cover - defensive
|
|
244
|
+
msg = "CTE placeholder update produced non-select expression"
|
|
245
|
+
raise SQLBuilderError(msg)
|
|
246
|
+
return updated_expression
|
|
247
|
+
|
|
248
|
+
if isinstance(query, str):
|
|
249
|
+
try:
|
|
250
|
+
parsed_expression = sqlglot.parse_one(query, read=self.dialect_name)
|
|
251
|
+
except SQLGlotParseError as e: # pragma: no cover - defensive
|
|
252
|
+
self._raise_cte_parse_error(e)
|
|
253
|
+
if not isinstance(parsed_expression, exp.Select):
|
|
254
|
+
self._raise_cte_query_error(
|
|
255
|
+
alias, f"query string must parse to SELECT, got {type(parsed_expression).__name__}"
|
|
256
|
+
)
|
|
257
|
+
return parsed_expression
|
|
258
|
+
|
|
259
|
+
if isinstance(query, exp.Select):
|
|
260
|
+
return query
|
|
261
|
+
|
|
262
|
+
self._raise_cte_query_error(alias, f"invalid query type: {type(query).__name__}")
|
|
263
|
+
msg = "Unreachable"
|
|
264
|
+
raise AssertionError(msg)
|
|
265
|
+
|
|
266
|
+
def _add_parameter(self, value: Any, context: str | None = None) -> str:
|
|
155
267
|
"""Adds a parameter to the query and returns its placeholder name.
|
|
156
268
|
|
|
157
269
|
Args:
|
|
@@ -192,7 +304,7 @@ class QueryBuilder(ABC):
|
|
|
192
304
|
|
|
193
305
|
return expression.transform(replacer, copy=False)
|
|
194
306
|
|
|
195
|
-
def add_parameter(self: Self, value: Any, name:
|
|
307
|
+
def add_parameter(self: Self, value: Any, name: str | None = None) -> tuple[Self, str]:
|
|
196
308
|
"""Explicitly adds a parameter to the query.
|
|
197
309
|
|
|
198
310
|
This is useful for parameters that are not directly tied to a
|
|
@@ -229,13 +341,11 @@ class QueryBuilder(ABC):
|
|
|
229
341
|
if base_name not in self._parameters:
|
|
230
342
|
return base_name
|
|
231
343
|
|
|
232
|
-
for i in range(1,
|
|
344
|
+
for i in range(1, MAX_PARAMETER_COLLISION_ATTEMPTS):
|
|
233
345
|
name = f"{base_name}_{i}"
|
|
234
346
|
if name not in self._parameters:
|
|
235
347
|
return name
|
|
236
348
|
|
|
237
|
-
import uuid
|
|
238
|
-
|
|
239
349
|
return f"{base_name}_{uuid.uuid4().hex[:8]}"
|
|
240
350
|
|
|
241
351
|
def _merge_cte_parameters(self, cte_name: str, parameters: dict[str, Any]) -> dict[str, str]:
|
|
@@ -275,7 +385,7 @@ class QueryBuilder(ABC):
|
|
|
275
385
|
|
|
276
386
|
return expression.transform(placeholder_replacer, copy=False)
|
|
277
387
|
|
|
278
|
-
def _generate_builder_cache_key(self, config: "
|
|
388
|
+
def _generate_builder_cache_key(self, config: "StatementConfig | None" = None) -> str:
|
|
279
389
|
"""Generate cache key based on builder state and configuration.
|
|
280
390
|
|
|
281
391
|
Args:
|
|
@@ -284,8 +394,6 @@ class QueryBuilder(ABC):
|
|
|
284
394
|
Returns:
|
|
285
395
|
A unique cache key representing the builder state and configuration
|
|
286
396
|
"""
|
|
287
|
-
import hashlib
|
|
288
|
-
|
|
289
397
|
dialect_name: str = self.dialect_name or "default"
|
|
290
398
|
|
|
291
399
|
if self._expression is None:
|
|
@@ -320,7 +428,7 @@ class QueryBuilder(ABC):
|
|
|
320
428
|
state_string = "|".join(state_parts)
|
|
321
429
|
return f"builder:{hashlib.sha256(state_string.encode()).hexdigest()[:16]}"
|
|
322
430
|
|
|
323
|
-
def with_cte(self: Self, alias: str, query: "
|
|
431
|
+
def with_cte(self: Self, alias: str, query: "QueryBuilder | exp.Select | str") -> Self:
|
|
324
432
|
"""Adds a Common Table Expression (CTE) to the query.
|
|
325
433
|
|
|
326
434
|
Args:
|
|
@@ -334,41 +442,7 @@ class QueryBuilder(ABC):
|
|
|
334
442
|
if alias in self._with_ctes:
|
|
335
443
|
self._raise_sql_builder_error(f"CTE with alias '{alias}' already exists.")
|
|
336
444
|
|
|
337
|
-
cte_select_expression
|
|
338
|
-
|
|
339
|
-
if isinstance(query, QueryBuilder):
|
|
340
|
-
query_expr = query.get_expression()
|
|
341
|
-
if query_expr is None:
|
|
342
|
-
self._raise_sql_builder_error("CTE query builder has no expression.")
|
|
343
|
-
if not isinstance(query_expr, exp.Select):
|
|
344
|
-
msg = f"CTE query builder expression must be a Select, got {type(query_expr).__name__}."
|
|
345
|
-
self._raise_sql_builder_error(msg)
|
|
346
|
-
cte_select_expression = query_expr
|
|
347
|
-
param_mapping = self._merge_cte_parameters(alias, query.parameters)
|
|
348
|
-
updated_expression = self._update_placeholders_in_expression(cte_select_expression, param_mapping)
|
|
349
|
-
if not isinstance(updated_expression, exp.Select):
|
|
350
|
-
msg = f"Updated CTE expression must be a Select, got {type(updated_expression).__name__}."
|
|
351
|
-
self._raise_sql_builder_error(msg)
|
|
352
|
-
cte_select_expression = updated_expression
|
|
353
|
-
|
|
354
|
-
elif isinstance(query, str):
|
|
355
|
-
try:
|
|
356
|
-
parsed_expression = sqlglot.parse_one(query, read=self.dialect_name)
|
|
357
|
-
if not isinstance(parsed_expression, exp.Select):
|
|
358
|
-
msg = f"CTE query string must parse to a SELECT statement, got {type(parsed_expression).__name__}."
|
|
359
|
-
self._raise_sql_builder_error(msg)
|
|
360
|
-
cte_select_expression = parsed_expression
|
|
361
|
-
except SQLGlotParseError as e:
|
|
362
|
-
self._raise_sql_builder_error(f"Failed to parse CTE query string: {e!s}", e)
|
|
363
|
-
except Exception as e:
|
|
364
|
-
msg = f"An unexpected error occurred while parsing CTE query string: {e!s}"
|
|
365
|
-
self._raise_sql_builder_error(msg, e)
|
|
366
|
-
elif isinstance(query, exp.Select):
|
|
367
|
-
cte_select_expression = query
|
|
368
|
-
else:
|
|
369
|
-
msg = f"Invalid query type for CTE: {type(query).__name__}"
|
|
370
|
-
self._raise_sql_builder_error(msg)
|
|
371
|
-
|
|
445
|
+
cte_select_expression = self._resolve_cte_query(alias, query)
|
|
372
446
|
self._with_ctes[alias] = exp.CTE(this=cte_select_expression, alias=exp.to_table(alias))
|
|
373
447
|
return self
|
|
374
448
|
|
|
@@ -378,18 +452,7 @@ class QueryBuilder(ABC):
|
|
|
378
452
|
Returns:
|
|
379
453
|
SafeQuery: A dataclass containing the SQL string and parameters.
|
|
380
454
|
"""
|
|
381
|
-
|
|
382
|
-
self._raise_sql_builder_error("QueryBuilder expression not initialized.")
|
|
383
|
-
|
|
384
|
-
if self._with_ctes:
|
|
385
|
-
final_expression = self._expression
|
|
386
|
-
if has_with_method(final_expression):
|
|
387
|
-
for alias, cte_node in self._with_ctes.items():
|
|
388
|
-
final_expression = cast("Any", final_expression).with_(cte_node.args["this"], as_=alias, copy=False)
|
|
389
|
-
elif isinstance(final_expression, (exp.Select, exp.Insert, exp.Update, exp.Delete, exp.Union)):
|
|
390
|
-
final_expression = exp.With(expressions=list(self._with_ctes.values()), this=final_expression)
|
|
391
|
-
else:
|
|
392
|
-
final_expression = self._expression
|
|
455
|
+
final_expression = self._build_final_expression()
|
|
393
456
|
|
|
394
457
|
if self.enable_optimization and isinstance(final_expression, exp.Expression):
|
|
395
458
|
final_expression = self._optimize_expression(final_expression)
|
|
@@ -438,15 +501,14 @@ class QueryBuilder(ABC):
|
|
|
438
501
|
optimized = optimize(
|
|
439
502
|
expression, schema=self.schema, dialect=self.dialect_name, optimizer_settings=optimizer_settings
|
|
440
503
|
)
|
|
441
|
-
|
|
442
504
|
cache.put("optimized", cache_key, optimized)
|
|
443
|
-
|
|
444
505
|
except Exception:
|
|
506
|
+
logger.debug("Expression optimization failed, using original expression")
|
|
445
507
|
return expression
|
|
446
508
|
else:
|
|
447
509
|
return optimized
|
|
448
510
|
|
|
449
|
-
def to_statement(self, config: "
|
|
511
|
+
def to_statement(self, config: "StatementConfig | None" = None) -> "SQL":
|
|
450
512
|
"""Converts the built query into a SQL statement object.
|
|
451
513
|
|
|
452
514
|
Args:
|
|
@@ -471,7 +533,7 @@ class QueryBuilder(ABC):
|
|
|
471
533
|
|
|
472
534
|
return sql_statement
|
|
473
535
|
|
|
474
|
-
def _to_statement(self, config: "
|
|
536
|
+
def _to_statement(self, config: "StatementConfig | None" = None) -> "SQL":
|
|
475
537
|
"""Internal method to create SQL statement.
|
|
476
538
|
|
|
477
539
|
Args:
|
|
@@ -482,18 +544,7 @@ class QueryBuilder(ABC):
|
|
|
482
544
|
"""
|
|
483
545
|
safe_query = self.build()
|
|
484
546
|
|
|
485
|
-
|
|
486
|
-
kwargs = safe_query.parameters
|
|
487
|
-
parameters: Optional[tuple[Any, ...]] = None
|
|
488
|
-
else:
|
|
489
|
-
kwargs = None
|
|
490
|
-
parameters = (
|
|
491
|
-
safe_query.parameters
|
|
492
|
-
if isinstance(safe_query.parameters, tuple)
|
|
493
|
-
else tuple(safe_query.parameters)
|
|
494
|
-
if safe_query.parameters
|
|
495
|
-
else None
|
|
496
|
-
)
|
|
547
|
+
kwargs, parameters = self._extract_statement_parameters(safe_query.parameters)
|
|
497
548
|
|
|
498
549
|
if config is None:
|
|
499
550
|
config = StatementConfig(
|
|
@@ -521,6 +572,28 @@ class QueryBuilder(ABC):
|
|
|
521
572
|
return SQL(sql_string, *parameters, statement_config=config)
|
|
522
573
|
return SQL(sql_string, statement_config=config)
|
|
523
574
|
|
|
575
|
+
def _extract_statement_parameters(
|
|
576
|
+
self, raw_parameters: Any
|
|
577
|
+
) -> "tuple[dict[str, Any] | None, tuple[Any, ...] | None]":
|
|
578
|
+
"""Extract parameters for SQL statement creation.
|
|
579
|
+
|
|
580
|
+
Args:
|
|
581
|
+
raw_parameters: Raw parameter data from SafeQuery
|
|
582
|
+
|
|
583
|
+
Returns:
|
|
584
|
+
Tuple of (kwargs, parameters) for SQL statement construction
|
|
585
|
+
"""
|
|
586
|
+
if isinstance(raw_parameters, dict):
|
|
587
|
+
return raw_parameters, None
|
|
588
|
+
|
|
589
|
+
if isinstance(raw_parameters, tuple):
|
|
590
|
+
return None, raw_parameters
|
|
591
|
+
|
|
592
|
+
if raw_parameters:
|
|
593
|
+
return None, tuple(raw_parameters)
|
|
594
|
+
|
|
595
|
+
return None, None
|
|
596
|
+
|
|
524
597
|
def __str__(self) -> str:
|
|
525
598
|
"""Return the SQL string representation of the query.
|
|
526
599
|
|
|
@@ -530,7 +603,7 @@ class QueryBuilder(ABC):
|
|
|
530
603
|
return self.build().sql
|
|
531
604
|
|
|
532
605
|
@property
|
|
533
|
-
def dialect_name(self) -> "
|
|
606
|
+
def dialect_name(self) -> "str | None":
|
|
534
607
|
"""Returns the name of the dialect, if set."""
|
|
535
608
|
if isinstance(self.dialect, str):
|
|
536
609
|
return self.dialect
|