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
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
# pyright: reportPrivateUsage=false
|
|
2
|
-
"""ORDER BY, LIMIT, OFFSET, and RETURNING clause mixins.
|
|
3
|
-
|
|
4
|
-
Provides mixins for query result ordering, limiting, and result
|
|
5
|
-
returning functionality.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from typing import TYPE_CHECKING, Optional, Union, cast
|
|
9
|
-
|
|
10
|
-
from mypy_extensions import trait
|
|
11
|
-
from sqlglot import exp
|
|
12
|
-
from typing_extensions import Self
|
|
13
|
-
|
|
14
|
-
from sqlspec.builder._parsing_utils import parse_order_expression
|
|
15
|
-
from sqlspec.exceptions import SQLBuilderError
|
|
16
|
-
|
|
17
|
-
if TYPE_CHECKING:
|
|
18
|
-
from sqlspec.builder._column import Column
|
|
19
|
-
from sqlspec.builder._expression_wrappers import ExpressionWrapper
|
|
20
|
-
from sqlspec.builder.mixins._select_operations import Case
|
|
21
|
-
from sqlspec.protocols import SQLBuilderProtocol
|
|
22
|
-
|
|
23
|
-
__all__ = ("LimitOffsetClauseMixin", "OrderByClauseMixin", "ReturningClauseMixin")
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@trait
|
|
27
|
-
class OrderByClauseMixin:
|
|
28
|
-
"""Mixin providing ORDER BY clause."""
|
|
29
|
-
|
|
30
|
-
__slots__ = ()
|
|
31
|
-
|
|
32
|
-
# Type annotation for PyRight - this will be provided by the base class
|
|
33
|
-
_expression: Optional[exp.Expression]
|
|
34
|
-
|
|
35
|
-
def order_by(self, *items: Union[str, exp.Ordered, "Column"], desc: bool = False) -> Self:
|
|
36
|
-
"""Add ORDER BY clause.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
*items: Columns to order by. Can be strings (column names) or sqlglot.exp.Ordered instances for specific directions (e.g., exp.column("name").desc()).
|
|
40
|
-
desc: Whether to order in descending order (applies to all items if they are strings).
|
|
41
|
-
|
|
42
|
-
Raises:
|
|
43
|
-
SQLBuilderError: If the current expression is not a SELECT statement or if the item type is unsupported.
|
|
44
|
-
|
|
45
|
-
Returns:
|
|
46
|
-
The current builder instance for method chaining.
|
|
47
|
-
"""
|
|
48
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
49
|
-
if not isinstance(builder._expression, exp.Select):
|
|
50
|
-
msg = "ORDER BY is only supported for SELECT statements."
|
|
51
|
-
raise SQLBuilderError(msg)
|
|
52
|
-
|
|
53
|
-
current_expr = builder._expression
|
|
54
|
-
for item in items:
|
|
55
|
-
if isinstance(item, str):
|
|
56
|
-
order_item = parse_order_expression(item)
|
|
57
|
-
if desc:
|
|
58
|
-
order_item = order_item.desc()
|
|
59
|
-
else:
|
|
60
|
-
# Extract expression from Column objects or use as-is for sqlglot expressions
|
|
61
|
-
from sqlspec._sql import SQLFactory
|
|
62
|
-
|
|
63
|
-
extracted_item = SQLFactory._extract_expression(item)
|
|
64
|
-
order_item = extracted_item
|
|
65
|
-
if desc and not isinstance(item, exp.Ordered):
|
|
66
|
-
order_item = order_item.desc()
|
|
67
|
-
current_expr = current_expr.order_by(order_item, copy=False)
|
|
68
|
-
builder._expression = current_expr
|
|
69
|
-
return cast("Self", builder)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
@trait
|
|
73
|
-
class LimitOffsetClauseMixin:
|
|
74
|
-
"""Mixin providing LIMIT and OFFSET clauses."""
|
|
75
|
-
|
|
76
|
-
__slots__ = ()
|
|
77
|
-
|
|
78
|
-
# Type annotation for PyRight - this will be provided by the base class
|
|
79
|
-
_expression: Optional[exp.Expression]
|
|
80
|
-
|
|
81
|
-
def limit(self, value: int) -> Self:
|
|
82
|
-
"""Add LIMIT clause.
|
|
83
|
-
|
|
84
|
-
Args:
|
|
85
|
-
value: The maximum number of rows to return.
|
|
86
|
-
|
|
87
|
-
Raises:
|
|
88
|
-
SQLBuilderError: If the current expression is not a SELECT statement.
|
|
89
|
-
|
|
90
|
-
Returns:
|
|
91
|
-
The current builder instance for method chaining.
|
|
92
|
-
"""
|
|
93
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
94
|
-
if not isinstance(builder._expression, exp.Select):
|
|
95
|
-
msg = "LIMIT is only supported for SELECT statements."
|
|
96
|
-
raise SQLBuilderError(msg)
|
|
97
|
-
builder._expression = builder._expression.limit(exp.convert(value), copy=False)
|
|
98
|
-
return cast("Self", builder)
|
|
99
|
-
|
|
100
|
-
def offset(self, value: int) -> Self:
|
|
101
|
-
"""Add OFFSET clause.
|
|
102
|
-
|
|
103
|
-
Args:
|
|
104
|
-
value: The number of rows to skip before starting to return rows.
|
|
105
|
-
|
|
106
|
-
Raises:
|
|
107
|
-
SQLBuilderError: If the current expression is not a SELECT statement.
|
|
108
|
-
|
|
109
|
-
Returns:
|
|
110
|
-
The current builder instance for method chaining.
|
|
111
|
-
"""
|
|
112
|
-
builder = cast("SQLBuilderProtocol", self)
|
|
113
|
-
if not isinstance(builder._expression, exp.Select):
|
|
114
|
-
msg = "OFFSET is only supported for SELECT statements."
|
|
115
|
-
raise SQLBuilderError(msg)
|
|
116
|
-
builder._expression = builder._expression.offset(exp.convert(value), copy=False)
|
|
117
|
-
return cast("Self", builder)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
@trait
|
|
121
|
-
class ReturningClauseMixin:
|
|
122
|
-
"""Mixin providing RETURNING clause."""
|
|
123
|
-
|
|
124
|
-
__slots__ = ()
|
|
125
|
-
# Type annotation for PyRight - this will be provided by the base class
|
|
126
|
-
_expression: Optional[exp.Expression]
|
|
127
|
-
|
|
128
|
-
def returning(self, *columns: Union[str, exp.Expression, "Column", "ExpressionWrapper", "Case"]) -> Self:
|
|
129
|
-
"""Add RETURNING clause to the statement.
|
|
130
|
-
|
|
131
|
-
Args:
|
|
132
|
-
*columns: Columns to return. Can be strings or sqlglot expressions.
|
|
133
|
-
|
|
134
|
-
Raises:
|
|
135
|
-
SQLBuilderError: If the current expression is not INSERT, UPDATE, or DELETE.
|
|
136
|
-
|
|
137
|
-
Returns:
|
|
138
|
-
The current builder instance for method chaining.
|
|
139
|
-
"""
|
|
140
|
-
if self._expression is None:
|
|
141
|
-
msg = "Cannot add RETURNING: expression is not initialized."
|
|
142
|
-
raise SQLBuilderError(msg)
|
|
143
|
-
valid_types = (exp.Insert, exp.Update, exp.Delete)
|
|
144
|
-
if not isinstance(self._expression, valid_types):
|
|
145
|
-
msg = "RETURNING is only supported for INSERT, UPDATE, and DELETE statements."
|
|
146
|
-
raise SQLBuilderError(msg)
|
|
147
|
-
# Extract expressions from various wrapper types
|
|
148
|
-
from sqlspec._sql import SQLFactory
|
|
149
|
-
|
|
150
|
-
returning_exprs = [SQLFactory._extract_expression(c) for c in columns]
|
|
151
|
-
self._expression.set("returning", exp.Returning(expressions=returning_exprs))
|
|
152
|
-
return self
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
# pyright: reportPrivateUsage=false
|
|
2
|
-
"""PIVOT and UNPIVOT operation mixins.
|
|
3
|
-
|
|
4
|
-
Provides mixins for PIVOT and UNPIVOT operations in SELECT statements.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from typing import TYPE_CHECKING, Optional, Union, cast
|
|
8
|
-
|
|
9
|
-
from mypy_extensions import trait
|
|
10
|
-
from sqlglot import exp
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from sqlglot.dialects.dialect import DialectType
|
|
14
|
-
|
|
15
|
-
from sqlspec.builder._select import Select
|
|
16
|
-
|
|
17
|
-
__all__ = ("PivotClauseMixin", "UnpivotClauseMixin")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@trait
|
|
21
|
-
class PivotClauseMixin:
|
|
22
|
-
"""Mixin class to add PIVOT functionality to a Select."""
|
|
23
|
-
|
|
24
|
-
__slots__ = ()
|
|
25
|
-
# Type annotation for PyRight - this will be provided by the base class
|
|
26
|
-
_expression: Optional[exp.Expression]
|
|
27
|
-
|
|
28
|
-
dialect: "DialectType" = None
|
|
29
|
-
|
|
30
|
-
def pivot(
|
|
31
|
-
self: "PivotClauseMixin",
|
|
32
|
-
aggregate_function: Union[str, exp.Expression],
|
|
33
|
-
aggregate_column: Union[str, exp.Expression],
|
|
34
|
-
pivot_column: Union[str, exp.Expression],
|
|
35
|
-
pivot_values: list[Union[str, int, float, exp.Expression]],
|
|
36
|
-
alias: Optional[str] = None,
|
|
37
|
-
) -> "Select":
|
|
38
|
-
"""Adds a PIVOT clause to the SELECT statement.
|
|
39
|
-
|
|
40
|
-
Example:
|
|
41
|
-
`query.pivot(aggregate_function="SUM", aggregate_column="Sales", pivot_column="Quarter", pivot_values=["Q1", "Q2", "Q3", "Q4"], alias="PivotTable")`
|
|
42
|
-
|
|
43
|
-
Args:
|
|
44
|
-
aggregate_function: The aggregate function to use (e.g., "SUM", "AVG").
|
|
45
|
-
aggregate_column: The column to be aggregated.
|
|
46
|
-
pivot_column: The column whose unique values will become new column headers.
|
|
47
|
-
pivot_values: A list of specific values from the pivot_column to be turned into columns.
|
|
48
|
-
alias: Optional alias for the pivoted table/subquery.
|
|
49
|
-
|
|
50
|
-
Returns:
|
|
51
|
-
The SelectBuilder instance for chaining.
|
|
52
|
-
"""
|
|
53
|
-
current_expr = self._expression
|
|
54
|
-
if not isinstance(current_expr, exp.Select):
|
|
55
|
-
msg = "Pivot can only be applied to a Select expression managed by SelectBuilder."
|
|
56
|
-
raise TypeError(msg)
|
|
57
|
-
|
|
58
|
-
agg_func_name = aggregate_function if isinstance(aggregate_function, str) else aggregate_function.name
|
|
59
|
-
agg_col_expr = exp.column(aggregate_column) if isinstance(aggregate_column, str) else aggregate_column
|
|
60
|
-
pivot_col_expr = exp.column(pivot_column) if isinstance(pivot_column, str) else pivot_column
|
|
61
|
-
|
|
62
|
-
pivot_agg_expr = exp.func(agg_func_name, agg_col_expr)
|
|
63
|
-
|
|
64
|
-
pivot_value_exprs: list[exp.Expression] = []
|
|
65
|
-
for val in pivot_values:
|
|
66
|
-
if isinstance(val, exp.Expression):
|
|
67
|
-
pivot_value_exprs.append(val)
|
|
68
|
-
elif isinstance(val, (str, int, float)):
|
|
69
|
-
pivot_value_exprs.append(exp.convert(val))
|
|
70
|
-
else:
|
|
71
|
-
pivot_value_exprs.append(exp.convert(str(val)))
|
|
72
|
-
|
|
73
|
-
in_expr = exp.In(this=pivot_col_expr, expressions=pivot_value_exprs)
|
|
74
|
-
|
|
75
|
-
pivot_node = exp.Pivot(expressions=[pivot_agg_expr], fields=[in_expr], unpivot=False)
|
|
76
|
-
|
|
77
|
-
if alias:
|
|
78
|
-
pivot_node.set("alias", exp.TableAlias(this=exp.to_identifier(alias)))
|
|
79
|
-
|
|
80
|
-
from_clause = current_expr.args.get("from")
|
|
81
|
-
if from_clause and isinstance(from_clause, exp.From):
|
|
82
|
-
table = from_clause.this
|
|
83
|
-
if isinstance(table, exp.Table):
|
|
84
|
-
existing_pivots = table.args.get("pivots", [])
|
|
85
|
-
existing_pivots.append(pivot_node)
|
|
86
|
-
table.set("pivots", existing_pivots)
|
|
87
|
-
|
|
88
|
-
return cast("Select", self)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
@trait
|
|
92
|
-
class UnpivotClauseMixin:
|
|
93
|
-
"""Mixin class to add UNPIVOT functionality to a Select."""
|
|
94
|
-
|
|
95
|
-
__slots__ = ()
|
|
96
|
-
# Type annotation for PyRight - this will be provided by the base class
|
|
97
|
-
_expression: Optional[exp.Expression]
|
|
98
|
-
|
|
99
|
-
dialect: "DialectType" = None
|
|
100
|
-
|
|
101
|
-
def unpivot(
|
|
102
|
-
self: "UnpivotClauseMixin",
|
|
103
|
-
value_column_name: str,
|
|
104
|
-
name_column_name: str,
|
|
105
|
-
columns_to_unpivot: list[Union[str, exp.Expression]],
|
|
106
|
-
alias: Optional[str] = None,
|
|
107
|
-
) -> "Select":
|
|
108
|
-
"""Adds an UNPIVOT clause to the SELECT statement.
|
|
109
|
-
|
|
110
|
-
Example:
|
|
111
|
-
`query.unpivot(value_column_name="Sales", name_column_name="Quarter", columns_to_unpivot=["Q1Sales", "Q2Sales"], alias="UnpivotTable")`
|
|
112
|
-
|
|
113
|
-
Args:
|
|
114
|
-
value_column_name: The name for the new column that will hold the values from the unpivoted columns.
|
|
115
|
-
name_column_name: The name for the new column that will hold the names of the original unpivoted columns.
|
|
116
|
-
columns_to_unpivot: A list of columns to be unpivoted into rows.
|
|
117
|
-
alias: Optional alias for the unpivoted table/subquery.
|
|
118
|
-
|
|
119
|
-
Raises:
|
|
120
|
-
TypeError: If the current expression is not a Select expression.
|
|
121
|
-
|
|
122
|
-
Returns:
|
|
123
|
-
The Select instance for chaining.
|
|
124
|
-
"""
|
|
125
|
-
current_expr = self._expression
|
|
126
|
-
if not isinstance(current_expr, exp.Select):
|
|
127
|
-
msg = "Unpivot can only be applied to a Select expression managed by Select."
|
|
128
|
-
raise TypeError(msg)
|
|
129
|
-
|
|
130
|
-
value_col_ident = exp.to_identifier(value_column_name)
|
|
131
|
-
name_col_ident = exp.to_identifier(name_column_name)
|
|
132
|
-
|
|
133
|
-
unpivot_cols_exprs: list[exp.Expression] = []
|
|
134
|
-
for col_name_or_expr in columns_to_unpivot:
|
|
135
|
-
if isinstance(col_name_or_expr, exp.Expression):
|
|
136
|
-
unpivot_cols_exprs.append(col_name_or_expr)
|
|
137
|
-
elif isinstance(col_name_or_expr, str):
|
|
138
|
-
unpivot_cols_exprs.append(exp.column(col_name_or_expr))
|
|
139
|
-
else:
|
|
140
|
-
unpivot_cols_exprs.append(exp.column(str(col_name_or_expr)))
|
|
141
|
-
|
|
142
|
-
in_expr = exp.In(this=name_col_ident, expressions=unpivot_cols_exprs)
|
|
143
|
-
|
|
144
|
-
unpivot_node = exp.Pivot(expressions=[value_col_ident], fields=[in_expr], unpivot=True)
|
|
145
|
-
|
|
146
|
-
if alias:
|
|
147
|
-
unpivot_node.set("alias", exp.TableAlias(this=exp.to_identifier(alias)))
|
|
148
|
-
|
|
149
|
-
from_clause = current_expr.args.get("from")
|
|
150
|
-
if from_clause and isinstance(from_clause, exp.From):
|
|
151
|
-
table = from_clause.this
|
|
152
|
-
if isinstance(table, exp.Table):
|
|
153
|
-
existing_pivots = table.args.get("pivots", [])
|
|
154
|
-
existing_pivots.append(unpivot_node)
|
|
155
|
-
table.set("pivots", existing_pivots)
|
|
156
|
-
|
|
157
|
-
return cast("Select", self)
|