sqlspec 0.13.1__py3-none-any.whl → 0.16.2__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 +71 -8
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +1 -3
- sqlspec/_serialization.py +1 -2
- sqlspec/_sql.py +930 -136
- sqlspec/_typing.py +278 -142
- sqlspec/adapters/adbc/__init__.py +4 -3
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +116 -285
- sqlspec/adapters/adbc/driver.py +462 -340
- sqlspec/adapters/aiosqlite/__init__.py +18 -3
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +202 -150
- sqlspec/adapters/aiosqlite/driver.py +226 -247
- sqlspec/adapters/asyncmy/__init__.py +18 -3
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +80 -199
- sqlspec/adapters/asyncmy/driver.py +257 -215
- sqlspec/adapters/asyncpg/__init__.py +19 -4
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +81 -214
- sqlspec/adapters/asyncpg/driver.py +284 -359
- sqlspec/adapters/bigquery/__init__.py +17 -3
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +191 -299
- sqlspec/adapters/bigquery/driver.py +474 -634
- sqlspec/adapters/duckdb/__init__.py +14 -3
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +414 -397
- sqlspec/adapters/duckdb/driver.py +342 -393
- sqlspec/adapters/oracledb/__init__.py +19 -5
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +123 -458
- sqlspec/adapters/oracledb/driver.py +505 -531
- sqlspec/adapters/psqlpy/__init__.py +13 -3
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +93 -307
- sqlspec/adapters/psqlpy/driver.py +504 -213
- sqlspec/adapters/psycopg/__init__.py +19 -5
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +143 -472
- sqlspec/adapters/psycopg/driver.py +704 -825
- sqlspec/adapters/sqlite/__init__.py +14 -3
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +208 -142
- sqlspec/adapters/sqlite/driver.py +263 -278
- sqlspec/base.py +105 -9
- sqlspec/{statement/builder → builder}/__init__.py +12 -14
- sqlspec/{statement/builder/base.py → builder/_base.py} +184 -86
- sqlspec/{statement/builder/column.py → builder/_column.py} +97 -60
- sqlspec/{statement/builder/ddl.py → builder/_ddl.py} +61 -131
- sqlspec/{statement/builder → builder}/_ddl_utils.py +4 -10
- sqlspec/{statement/builder/delete.py → builder/_delete.py} +10 -30
- sqlspec/builder/_insert.py +421 -0
- sqlspec/builder/_merge.py +71 -0
- sqlspec/{statement/builder → builder}/_parsing_utils.py +49 -26
- sqlspec/builder/_select.py +170 -0
- sqlspec/{statement/builder/update.py → builder/_update.py} +16 -20
- sqlspec/builder/mixins/__init__.py +55 -0
- sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
- sqlspec/{statement/builder/mixins/_delete_from.py → builder/mixins/_delete_operations.py} +8 -1
- sqlspec/builder/mixins/_insert_operations.py +244 -0
- sqlspec/{statement/builder/mixins/_join.py → builder/mixins/_join_operations.py} +45 -13
- sqlspec/{statement/builder/mixins/_merge_clauses.py → builder/mixins/_merge_operations.py} +188 -30
- sqlspec/builder/mixins/_order_limit_operations.py +135 -0
- sqlspec/builder/mixins/_pivot_operations.py +153 -0
- sqlspec/builder/mixins/_select_operations.py +604 -0
- sqlspec/builder/mixins/_update_operations.py +202 -0
- sqlspec/builder/mixins/_where_clause.py +644 -0
- sqlspec/cli.py +247 -0
- sqlspec/config.py +183 -138
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.py +871 -0
- sqlspec/core/compiler.py +417 -0
- sqlspec/core/filters.py +830 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.py +1237 -0
- sqlspec/core/result.py +677 -0
- sqlspec/{statement → core}/splitter.py +321 -191
- sqlspec/core/statement.py +676 -0
- sqlspec/driver/__init__.py +7 -10
- sqlspec/driver/_async.py +422 -163
- sqlspec/driver/_common.py +545 -287
- sqlspec/driver/_sync.py +426 -160
- sqlspec/driver/mixins/__init__.py +2 -13
- sqlspec/driver/mixins/_result_tools.py +193 -0
- sqlspec/driver/mixins/_sql_translator.py +65 -14
- sqlspec/exceptions.py +5 -252
- sqlspec/extensions/aiosql/adapter.py +93 -96
- sqlspec/extensions/litestar/__init__.py +2 -1
- sqlspec/extensions/litestar/cli.py +48 -0
- sqlspec/extensions/litestar/config.py +0 -1
- sqlspec/extensions/litestar/handlers.py +15 -26
- sqlspec/extensions/litestar/plugin.py +21 -16
- sqlspec/extensions/litestar/providers.py +17 -52
- sqlspec/loader.py +423 -104
- sqlspec/migrations/__init__.py +35 -0
- sqlspec/migrations/base.py +414 -0
- sqlspec/migrations/commands.py +443 -0
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +213 -0
- sqlspec/migrations/tracker.py +140 -0
- sqlspec/migrations/utils.py +129 -0
- sqlspec/protocols.py +51 -186
- 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 -2
- 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 +482 -235
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/METADATA +7 -2
- sqlspec-0.16.2.dist-info/RECORD +134 -0
- sqlspec-0.16.2.dist-info/entry_points.txt +2 -0
- sqlspec/driver/connection.py +0 -207
- sqlspec/driver/mixins/_csv_writer.py +0 -91
- sqlspec/driver/mixins/_pipeline.py +0 -512
- sqlspec/driver/mixins/_result_utils.py +0 -140
- sqlspec/driver/mixins/_storage.py +0 -926
- sqlspec/driver/mixins/_type_coercion.py +0 -130
- sqlspec/driver/parameters.py +0 -138
- sqlspec/service/__init__.py +0 -4
- sqlspec/service/_util.py +0 -147
- sqlspec/service/base.py +0 -1131
- sqlspec/service/pagination.py +0 -26
- sqlspec/statement/__init__.py +0 -21
- sqlspec/statement/builder/insert.py +0 -288
- sqlspec/statement/builder/merge.py +0 -95
- sqlspec/statement/builder/mixins/__init__.py +0 -65
- sqlspec/statement/builder/mixins/_aggregate_functions.py +0 -250
- sqlspec/statement/builder/mixins/_case_builder.py +0 -91
- sqlspec/statement/builder/mixins/_common_table_expr.py +0 -90
- sqlspec/statement/builder/mixins/_from.py +0 -63
- sqlspec/statement/builder/mixins/_group_by.py +0 -118
- sqlspec/statement/builder/mixins/_having.py +0 -35
- sqlspec/statement/builder/mixins/_insert_from_select.py +0 -47
- sqlspec/statement/builder/mixins/_insert_into.py +0 -36
- sqlspec/statement/builder/mixins/_insert_values.py +0 -67
- sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
- sqlspec/statement/builder/mixins/_order_by.py +0 -46
- sqlspec/statement/builder/mixins/_pivot.py +0 -79
- sqlspec/statement/builder/mixins/_returning.py +0 -37
- sqlspec/statement/builder/mixins/_select_columns.py +0 -61
- sqlspec/statement/builder/mixins/_set_ops.py +0 -122
- sqlspec/statement/builder/mixins/_unpivot.py +0 -77
- sqlspec/statement/builder/mixins/_update_from.py +0 -55
- sqlspec/statement/builder/mixins/_update_set.py +0 -94
- sqlspec/statement/builder/mixins/_update_table.py +0 -29
- sqlspec/statement/builder/mixins/_where.py +0 -401
- sqlspec/statement/builder/mixins/_window_functions.py +0 -86
- sqlspec/statement/builder/select.py +0 -221
- sqlspec/statement/filters.py +0 -596
- sqlspec/statement/parameter_manager.py +0 -220
- sqlspec/statement/parameters.py +0 -867
- 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 -115
- 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 -718
- sqlspec/statement/pipelines/validators/_security.py +0 -967
- sqlspec/statement/result.py +0 -435
- sqlspec/statement/sql.py +0 -1704
- sqlspec/statement/sql_compiler.py +0 -140
- sqlspec/utils/cached_property.py +0 -25
- sqlspec-0.13.1.dist-info/RECORD +0 -150
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/WHEEL +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,221 +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
|
-
import re
|
|
8
|
-
from dataclasses import dataclass, field
|
|
9
|
-
from typing import Any, Optional, Union, cast
|
|
10
|
-
|
|
11
|
-
from sqlglot import exp
|
|
12
|
-
from typing_extensions import Self
|
|
13
|
-
|
|
14
|
-
from sqlspec.statement.builder.base import QueryBuilder, SafeQuery
|
|
15
|
-
from sqlspec.statement.builder.mixins import (
|
|
16
|
-
AggregateFunctionsMixin,
|
|
17
|
-
CaseBuilderMixin,
|
|
18
|
-
CommonTableExpressionMixin,
|
|
19
|
-
FromClauseMixin,
|
|
20
|
-
GroupByClauseMixin,
|
|
21
|
-
HavingClauseMixin,
|
|
22
|
-
JoinClauseMixin,
|
|
23
|
-
LimitOffsetClauseMixin,
|
|
24
|
-
OrderByClauseMixin,
|
|
25
|
-
PivotClauseMixin,
|
|
26
|
-
SelectColumnsMixin,
|
|
27
|
-
SetOperationMixin,
|
|
28
|
-
UnpivotClauseMixin,
|
|
29
|
-
WhereClauseMixin,
|
|
30
|
-
WindowFunctionsMixin,
|
|
31
|
-
)
|
|
32
|
-
from sqlspec.statement.result import SQLResult
|
|
33
|
-
from sqlspec.typing import RowT
|
|
34
|
-
|
|
35
|
-
__all__ = ("Select",)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@dataclass
|
|
39
|
-
class Select(
|
|
40
|
-
QueryBuilder[RowT],
|
|
41
|
-
WhereClauseMixin,
|
|
42
|
-
OrderByClauseMixin,
|
|
43
|
-
LimitOffsetClauseMixin,
|
|
44
|
-
SelectColumnsMixin,
|
|
45
|
-
JoinClauseMixin,
|
|
46
|
-
FromClauseMixin,
|
|
47
|
-
GroupByClauseMixin,
|
|
48
|
-
HavingClauseMixin,
|
|
49
|
-
SetOperationMixin,
|
|
50
|
-
CommonTableExpressionMixin,
|
|
51
|
-
AggregateFunctionsMixin,
|
|
52
|
-
WindowFunctionsMixin,
|
|
53
|
-
CaseBuilderMixin,
|
|
54
|
-
PivotClauseMixin,
|
|
55
|
-
UnpivotClauseMixin,
|
|
56
|
-
):
|
|
57
|
-
"""Type-safe builder for SELECT queries with schema/model integration.
|
|
58
|
-
|
|
59
|
-
This builder provides a fluent, safe interface for constructing SQL SELECT statements.
|
|
60
|
-
It supports type-safe result mapping via the `as_schema()` method, allowing users to
|
|
61
|
-
associate a schema/model (such as a Pydantic model, dataclass, or msgspec.Struct) with
|
|
62
|
-
the query for static type checking and IDE support.
|
|
63
|
-
|
|
64
|
-
Example:
|
|
65
|
-
>>> class User(BaseModel):
|
|
66
|
-
... id: int
|
|
67
|
-
... name: str
|
|
68
|
-
>>> builder = (
|
|
69
|
-
... SelectBuilder()
|
|
70
|
-
... .select("id", "name")
|
|
71
|
-
... .from_("users")
|
|
72
|
-
... .as_schema(User)
|
|
73
|
-
... )
|
|
74
|
-
>>> result: list[User] = driver.execute(builder)
|
|
75
|
-
|
|
76
|
-
Attributes:
|
|
77
|
-
_schema: The schema/model class for row typing, if set via as_schema().
|
|
78
|
-
"""
|
|
79
|
-
|
|
80
|
-
_with_parts: "dict[str, Union[exp.CTE, Select]]" = field(default_factory=dict, init=False)
|
|
81
|
-
_expression: Optional[exp.Expression] = field(default=None, init=False, repr=False, compare=False, hash=False)
|
|
82
|
-
_schema: Optional[type[RowT]] = None
|
|
83
|
-
_hints: "list[dict[str, object]]" = field(default_factory=list, init=False, repr=False)
|
|
84
|
-
|
|
85
|
-
def __init__(self, *columns: str, **kwargs: Any) -> None:
|
|
86
|
-
"""Initialize SELECT with optional columns.
|
|
87
|
-
|
|
88
|
-
Args:
|
|
89
|
-
*columns: Column names to select (e.g., "id", "name", "u.email")
|
|
90
|
-
**kwargs: Additional QueryBuilder arguments (dialect, schema, etc.)
|
|
91
|
-
|
|
92
|
-
Examples:
|
|
93
|
-
Select("id", "name") # Shorthand for Select().select("id", "name")
|
|
94
|
-
Select() # Same as SelectBuilder() - start empty
|
|
95
|
-
"""
|
|
96
|
-
super().__init__(**kwargs)
|
|
97
|
-
|
|
98
|
-
# Initialize fields from dataclass
|
|
99
|
-
self._with_parts = {}
|
|
100
|
-
self._expression = None
|
|
101
|
-
self._schema = None
|
|
102
|
-
self._hints = []
|
|
103
|
-
|
|
104
|
-
if self._expression is None:
|
|
105
|
-
self._create_base_expression()
|
|
106
|
-
|
|
107
|
-
# Add columns if provided - just a shorthand for .select()
|
|
108
|
-
if columns:
|
|
109
|
-
self.select(*columns)
|
|
110
|
-
|
|
111
|
-
@property
|
|
112
|
-
def _expected_result_type(self) -> "type[SQLResult[RowT]]":
|
|
113
|
-
"""Get the expected result type for SELECT operations.
|
|
114
|
-
|
|
115
|
-
Returns:
|
|
116
|
-
type: The SelectResult type.
|
|
117
|
-
"""
|
|
118
|
-
return SQLResult[RowT]
|
|
119
|
-
|
|
120
|
-
def _create_base_expression(self) -> "exp.Select":
|
|
121
|
-
if self._expression is None or not isinstance(self._expression, exp.Select):
|
|
122
|
-
self._expression = exp.Select()
|
|
123
|
-
# At this point, self._expression is exp.Select
|
|
124
|
-
return self._expression
|
|
125
|
-
|
|
126
|
-
def as_schema(self, schema: "type[RowT]") -> "Select[RowT]":
|
|
127
|
-
"""Return a new Select instance parameterized with the given schema/model type.
|
|
128
|
-
|
|
129
|
-
This enables type-safe result mapping: the returned builder will carry the schema type
|
|
130
|
-
for static analysis and IDE autocompletion. The schema should be a class such as a Pydantic
|
|
131
|
-
model, dataclass, or msgspec.Struct that describes the expected row shape.
|
|
132
|
-
|
|
133
|
-
Args:
|
|
134
|
-
schema: The schema/model class to use for row typing (e.g., a Pydantic model, dataclass, or msgspec.Struct).
|
|
135
|
-
|
|
136
|
-
Returns:
|
|
137
|
-
Select[RowT]: A new Select instance with RowT set to the provided schema/model type.
|
|
138
|
-
"""
|
|
139
|
-
new_builder = Select()
|
|
140
|
-
new_builder._expression = self._expression.copy() if self._expression is not None else None
|
|
141
|
-
new_builder._parameters = self._parameters.copy()
|
|
142
|
-
new_builder._parameter_counter = self._parameter_counter
|
|
143
|
-
new_builder.dialect = self.dialect
|
|
144
|
-
new_builder._schema = schema # type: ignore[assignment]
|
|
145
|
-
return cast("Select[RowT]", new_builder)
|
|
146
|
-
|
|
147
|
-
def with_hint(
|
|
148
|
-
self,
|
|
149
|
-
hint: "str",
|
|
150
|
-
*,
|
|
151
|
-
location: "str" = "statement",
|
|
152
|
-
table: "Optional[str]" = None,
|
|
153
|
-
dialect: "Optional[str]" = None,
|
|
154
|
-
) -> "Self":
|
|
155
|
-
"""Attach an optimizer or dialect-specific hint to the query.
|
|
156
|
-
|
|
157
|
-
Args:
|
|
158
|
-
hint: The raw hint string (e.g., 'INDEX(users idx_users_name)').
|
|
159
|
-
location: Where to apply the hint ('statement', 'table').
|
|
160
|
-
table: Table name if the hint is for a specific table.
|
|
161
|
-
dialect: Restrict the hint to a specific dialect (optional).
|
|
162
|
-
|
|
163
|
-
Returns:
|
|
164
|
-
The current builder instance for method chaining.
|
|
165
|
-
"""
|
|
166
|
-
self._hints.append({"hint": hint, "location": location, "table": table, "dialect": dialect})
|
|
167
|
-
return self
|
|
168
|
-
|
|
169
|
-
def build(self) -> "SafeQuery":
|
|
170
|
-
"""Builds the SQL query string and parameters with hint injection.
|
|
171
|
-
|
|
172
|
-
Returns:
|
|
173
|
-
SafeQuery: A dataclass containing the SQL string and parameters.
|
|
174
|
-
"""
|
|
175
|
-
safe_query = super().build()
|
|
176
|
-
|
|
177
|
-
if hasattr(self, "_hints") and self._hints:
|
|
178
|
-
modified_expr = self._expression.copy() if self._expression else None
|
|
179
|
-
|
|
180
|
-
if modified_expr and isinstance(modified_expr, exp.Select):
|
|
181
|
-
statement_hints = [h["hint"] for h in self._hints if h.get("location") == "statement"]
|
|
182
|
-
if statement_hints:
|
|
183
|
-
# Parse each hint and create proper hint expressions
|
|
184
|
-
hint_expressions = []
|
|
185
|
-
for hint in statement_hints:
|
|
186
|
-
try:
|
|
187
|
-
# Try to parse hint as an expression (e.g., "INDEX(users idx_name)")
|
|
188
|
-
hint_str = str(hint) # Ensure hint is a string
|
|
189
|
-
hint_expr: Optional[exp.Expression] = exp.maybe_parse(hint_str, dialect=self.dialect_name)
|
|
190
|
-
if hint_expr:
|
|
191
|
-
hint_expressions.append(hint_expr)
|
|
192
|
-
else:
|
|
193
|
-
hint_expressions.append(exp.Anonymous(this=hint_str))
|
|
194
|
-
except Exception: # noqa: PERF203
|
|
195
|
-
hint_expressions.append(exp.Anonymous(this=str(hint)))
|
|
196
|
-
|
|
197
|
-
if hint_expressions:
|
|
198
|
-
hint_node = exp.Hint(expressions=hint_expressions)
|
|
199
|
-
modified_expr.set("hint", hint_node)
|
|
200
|
-
|
|
201
|
-
# For table-level hints, we'll fall back to comment injection in SQL
|
|
202
|
-
# since SQLGlot doesn't have a standard way to attach hints to individual tables
|
|
203
|
-
modified_sql = modified_expr.sql(dialect=self.dialect_name, pretty=True)
|
|
204
|
-
|
|
205
|
-
table_hints = [h for h in self._hints if h.get("location") == "table" and h.get("table")]
|
|
206
|
-
if table_hints:
|
|
207
|
-
for th in table_hints:
|
|
208
|
-
table = str(th["table"])
|
|
209
|
-
hint = th["hint"]
|
|
210
|
-
# More precise regex that captures the table and optional alias
|
|
211
|
-
pattern = rf"\b{re.escape(table)}\b(\s+AS\s+\w+)?"
|
|
212
|
-
|
|
213
|
-
def replacement_func(match: re.Match[str]) -> str:
|
|
214
|
-
alias_part = match.group(1) or ""
|
|
215
|
-
return f"/*+ {hint} */ {table}{alias_part}" # noqa: B023
|
|
216
|
-
|
|
217
|
-
modified_sql = re.sub(pattern, replacement_func, modified_sql, flags=re.IGNORECASE, count=1)
|
|
218
|
-
|
|
219
|
-
return SafeQuery(sql=modified_sql, parameters=safe_query.parameters, dialect=safe_query.dialect)
|
|
220
|
-
|
|
221
|
-
return safe_query
|