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.

Files changed (185) hide show
  1. sqlspec/__init__.py +71 -8
  2. sqlspec/__main__.py +12 -0
  3. sqlspec/__metadata__.py +1 -3
  4. sqlspec/_serialization.py +1 -2
  5. sqlspec/_sql.py +930 -136
  6. sqlspec/_typing.py +278 -142
  7. sqlspec/adapters/adbc/__init__.py +4 -3
  8. sqlspec/adapters/adbc/_types.py +12 -0
  9. sqlspec/adapters/adbc/config.py +116 -285
  10. sqlspec/adapters/adbc/driver.py +462 -340
  11. sqlspec/adapters/aiosqlite/__init__.py +18 -3
  12. sqlspec/adapters/aiosqlite/_types.py +13 -0
  13. sqlspec/adapters/aiosqlite/config.py +202 -150
  14. sqlspec/adapters/aiosqlite/driver.py +226 -247
  15. sqlspec/adapters/asyncmy/__init__.py +18 -3
  16. sqlspec/adapters/asyncmy/_types.py +12 -0
  17. sqlspec/adapters/asyncmy/config.py +80 -199
  18. sqlspec/adapters/asyncmy/driver.py +257 -215
  19. sqlspec/adapters/asyncpg/__init__.py +19 -4
  20. sqlspec/adapters/asyncpg/_types.py +17 -0
  21. sqlspec/adapters/asyncpg/config.py +81 -214
  22. sqlspec/adapters/asyncpg/driver.py +284 -359
  23. sqlspec/adapters/bigquery/__init__.py +17 -3
  24. sqlspec/adapters/bigquery/_types.py +12 -0
  25. sqlspec/adapters/bigquery/config.py +191 -299
  26. sqlspec/adapters/bigquery/driver.py +474 -634
  27. sqlspec/adapters/duckdb/__init__.py +14 -3
  28. sqlspec/adapters/duckdb/_types.py +12 -0
  29. sqlspec/adapters/duckdb/config.py +414 -397
  30. sqlspec/adapters/duckdb/driver.py +342 -393
  31. sqlspec/adapters/oracledb/__init__.py +19 -5
  32. sqlspec/adapters/oracledb/_types.py +14 -0
  33. sqlspec/adapters/oracledb/config.py +123 -458
  34. sqlspec/adapters/oracledb/driver.py +505 -531
  35. sqlspec/adapters/psqlpy/__init__.py +13 -3
  36. sqlspec/adapters/psqlpy/_types.py +11 -0
  37. sqlspec/adapters/psqlpy/config.py +93 -307
  38. sqlspec/adapters/psqlpy/driver.py +504 -213
  39. sqlspec/adapters/psycopg/__init__.py +19 -5
  40. sqlspec/adapters/psycopg/_types.py +17 -0
  41. sqlspec/adapters/psycopg/config.py +143 -472
  42. sqlspec/adapters/psycopg/driver.py +704 -825
  43. sqlspec/adapters/sqlite/__init__.py +14 -3
  44. sqlspec/adapters/sqlite/_types.py +11 -0
  45. sqlspec/adapters/sqlite/config.py +208 -142
  46. sqlspec/adapters/sqlite/driver.py +263 -278
  47. sqlspec/base.py +105 -9
  48. sqlspec/{statement/builder → builder}/__init__.py +12 -14
  49. sqlspec/{statement/builder/base.py → builder/_base.py} +184 -86
  50. sqlspec/{statement/builder/column.py → builder/_column.py} +97 -60
  51. sqlspec/{statement/builder/ddl.py → builder/_ddl.py} +61 -131
  52. sqlspec/{statement/builder → builder}/_ddl_utils.py +4 -10
  53. sqlspec/{statement/builder/delete.py → builder/_delete.py} +10 -30
  54. sqlspec/builder/_insert.py +421 -0
  55. sqlspec/builder/_merge.py +71 -0
  56. sqlspec/{statement/builder → builder}/_parsing_utils.py +49 -26
  57. sqlspec/builder/_select.py +170 -0
  58. sqlspec/{statement/builder/update.py → builder/_update.py} +16 -20
  59. sqlspec/builder/mixins/__init__.py +55 -0
  60. sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
  61. sqlspec/{statement/builder/mixins/_delete_from.py → builder/mixins/_delete_operations.py} +8 -1
  62. sqlspec/builder/mixins/_insert_operations.py +244 -0
  63. sqlspec/{statement/builder/mixins/_join.py → builder/mixins/_join_operations.py} +45 -13
  64. sqlspec/{statement/builder/mixins/_merge_clauses.py → builder/mixins/_merge_operations.py} +188 -30
  65. sqlspec/builder/mixins/_order_limit_operations.py +135 -0
  66. sqlspec/builder/mixins/_pivot_operations.py +153 -0
  67. sqlspec/builder/mixins/_select_operations.py +604 -0
  68. sqlspec/builder/mixins/_update_operations.py +202 -0
  69. sqlspec/builder/mixins/_where_clause.py +644 -0
  70. sqlspec/cli.py +247 -0
  71. sqlspec/config.py +183 -138
  72. sqlspec/core/__init__.py +63 -0
  73. sqlspec/core/cache.py +871 -0
  74. sqlspec/core/compiler.py +417 -0
  75. sqlspec/core/filters.py +830 -0
  76. sqlspec/core/hashing.py +310 -0
  77. sqlspec/core/parameters.py +1237 -0
  78. sqlspec/core/result.py +677 -0
  79. sqlspec/{statement → core}/splitter.py +321 -191
  80. sqlspec/core/statement.py +676 -0
  81. sqlspec/driver/__init__.py +7 -10
  82. sqlspec/driver/_async.py +422 -163
  83. sqlspec/driver/_common.py +545 -287
  84. sqlspec/driver/_sync.py +426 -160
  85. sqlspec/driver/mixins/__init__.py +2 -13
  86. sqlspec/driver/mixins/_result_tools.py +193 -0
  87. sqlspec/driver/mixins/_sql_translator.py +65 -14
  88. sqlspec/exceptions.py +5 -252
  89. sqlspec/extensions/aiosql/adapter.py +93 -96
  90. sqlspec/extensions/litestar/__init__.py +2 -1
  91. sqlspec/extensions/litestar/cli.py +48 -0
  92. sqlspec/extensions/litestar/config.py +0 -1
  93. sqlspec/extensions/litestar/handlers.py +15 -26
  94. sqlspec/extensions/litestar/plugin.py +21 -16
  95. sqlspec/extensions/litestar/providers.py +17 -52
  96. sqlspec/loader.py +423 -104
  97. sqlspec/migrations/__init__.py +35 -0
  98. sqlspec/migrations/base.py +414 -0
  99. sqlspec/migrations/commands.py +443 -0
  100. sqlspec/migrations/loaders.py +402 -0
  101. sqlspec/migrations/runner.py +213 -0
  102. sqlspec/migrations/tracker.py +140 -0
  103. sqlspec/migrations/utils.py +129 -0
  104. sqlspec/protocols.py +51 -186
  105. sqlspec/storage/__init__.py +1 -1
  106. sqlspec/storage/backends/base.py +37 -40
  107. sqlspec/storage/backends/fsspec.py +136 -112
  108. sqlspec/storage/backends/obstore.py +138 -160
  109. sqlspec/storage/capabilities.py +5 -4
  110. sqlspec/storage/registry.py +57 -106
  111. sqlspec/typing.py +136 -115
  112. sqlspec/utils/__init__.py +2 -2
  113. sqlspec/utils/correlation.py +0 -3
  114. sqlspec/utils/deprecation.py +6 -6
  115. sqlspec/utils/fixtures.py +6 -6
  116. sqlspec/utils/logging.py +0 -2
  117. sqlspec/utils/module_loader.py +7 -12
  118. sqlspec/utils/singleton.py +0 -1
  119. sqlspec/utils/sync_tools.py +17 -38
  120. sqlspec/utils/text.py +12 -51
  121. sqlspec/utils/type_guards.py +482 -235
  122. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/METADATA +7 -2
  123. sqlspec-0.16.2.dist-info/RECORD +134 -0
  124. sqlspec-0.16.2.dist-info/entry_points.txt +2 -0
  125. sqlspec/driver/connection.py +0 -207
  126. sqlspec/driver/mixins/_csv_writer.py +0 -91
  127. sqlspec/driver/mixins/_pipeline.py +0 -512
  128. sqlspec/driver/mixins/_result_utils.py +0 -140
  129. sqlspec/driver/mixins/_storage.py +0 -926
  130. sqlspec/driver/mixins/_type_coercion.py +0 -130
  131. sqlspec/driver/parameters.py +0 -138
  132. sqlspec/service/__init__.py +0 -4
  133. sqlspec/service/_util.py +0 -147
  134. sqlspec/service/base.py +0 -1131
  135. sqlspec/service/pagination.py +0 -26
  136. sqlspec/statement/__init__.py +0 -21
  137. sqlspec/statement/builder/insert.py +0 -288
  138. sqlspec/statement/builder/merge.py +0 -95
  139. sqlspec/statement/builder/mixins/__init__.py +0 -65
  140. sqlspec/statement/builder/mixins/_aggregate_functions.py +0 -250
  141. sqlspec/statement/builder/mixins/_case_builder.py +0 -91
  142. sqlspec/statement/builder/mixins/_common_table_expr.py +0 -90
  143. sqlspec/statement/builder/mixins/_from.py +0 -63
  144. sqlspec/statement/builder/mixins/_group_by.py +0 -118
  145. sqlspec/statement/builder/mixins/_having.py +0 -35
  146. sqlspec/statement/builder/mixins/_insert_from_select.py +0 -47
  147. sqlspec/statement/builder/mixins/_insert_into.py +0 -36
  148. sqlspec/statement/builder/mixins/_insert_values.py +0 -67
  149. sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
  150. sqlspec/statement/builder/mixins/_order_by.py +0 -46
  151. sqlspec/statement/builder/mixins/_pivot.py +0 -79
  152. sqlspec/statement/builder/mixins/_returning.py +0 -37
  153. sqlspec/statement/builder/mixins/_select_columns.py +0 -61
  154. sqlspec/statement/builder/mixins/_set_ops.py +0 -122
  155. sqlspec/statement/builder/mixins/_unpivot.py +0 -77
  156. sqlspec/statement/builder/mixins/_update_from.py +0 -55
  157. sqlspec/statement/builder/mixins/_update_set.py +0 -94
  158. sqlspec/statement/builder/mixins/_update_table.py +0 -29
  159. sqlspec/statement/builder/mixins/_where.py +0 -401
  160. sqlspec/statement/builder/mixins/_window_functions.py +0 -86
  161. sqlspec/statement/builder/select.py +0 -221
  162. sqlspec/statement/filters.py +0 -596
  163. sqlspec/statement/parameter_manager.py +0 -220
  164. sqlspec/statement/parameters.py +0 -867
  165. sqlspec/statement/pipelines/__init__.py +0 -210
  166. sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
  167. sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
  168. sqlspec/statement/pipelines/context.py +0 -115
  169. sqlspec/statement/pipelines/transformers/__init__.py +0 -7
  170. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
  171. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
  172. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
  173. sqlspec/statement/pipelines/validators/__init__.py +0 -23
  174. sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
  175. sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
  176. sqlspec/statement/pipelines/validators/_performance.py +0 -718
  177. sqlspec/statement/pipelines/validators/_security.py +0 -967
  178. sqlspec/statement/result.py +0 -435
  179. sqlspec/statement/sql.py +0 -1704
  180. sqlspec/statement/sql_compiler.py +0 -140
  181. sqlspec/utils/cached_property.py +0 -25
  182. sqlspec-0.13.1.dist-info/RECORD +0 -150
  183. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/WHEEL +0 -0
  184. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/LICENSE +0 -0
  185. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/NOTICE +0 -0
@@ -1,47 +0,0 @@
1
- from typing import Any, Optional
2
-
3
- from sqlglot import exp
4
- from typing_extensions import Self
5
-
6
- from sqlspec.exceptions import SQLBuilderError
7
-
8
- __all__ = ("InsertFromSelectMixin",)
9
-
10
-
11
- class InsertFromSelectMixin:
12
- """Mixin providing INSERT ... SELECT support for INSERT builders."""
13
-
14
- _expression: Optional[exp.Expression] = None
15
-
16
- def from_select(self, select_builder: Any) -> Self:
17
- """Sets the INSERT source to a SELECT statement.
18
-
19
- Args:
20
- select_builder: A SelectBuilder instance representing the SELECT query.
21
-
22
- Returns:
23
- The current builder instance for method chaining.
24
-
25
- Raises:
26
- SQLBuilderError: If the table is not set or the select_builder is invalid.
27
- """
28
- if not getattr(self, "_table", None):
29
- msg = "The target table must be set using .into() before adding values."
30
- raise SQLBuilderError(msg)
31
- if self._expression is None:
32
- self._expression = exp.Insert()
33
- if not isinstance(self._expression, exp.Insert):
34
- msg = "Cannot set INSERT source on a non-INSERT expression."
35
- raise SQLBuilderError(msg)
36
- # Merge parameters from the SELECT builder
37
- subquery_params = getattr(select_builder, "_parameters", None)
38
- if subquery_params:
39
- for p_name, p_value in subquery_params.items():
40
- self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
41
- select_expr = getattr(select_builder, "_expression", None)
42
- if select_expr and isinstance(select_expr, exp.Select):
43
- self._expression.set("expression", select_expr.copy())
44
- else:
45
- msg = "SelectBuilder must have a valid SELECT expression."
46
- raise SQLBuilderError(msg)
47
- return self
@@ -1,36 +0,0 @@
1
- from typing import Optional
2
-
3
- from sqlglot import exp
4
- from typing_extensions import Self
5
-
6
- from sqlspec.exceptions import SQLBuilderError
7
-
8
- __all__ = ("InsertIntoClauseMixin",)
9
-
10
-
11
- class InsertIntoClauseMixin:
12
- """Mixin providing INTO clause for INSERT builders."""
13
-
14
- _expression: Optional[exp.Expression] = None
15
-
16
- def into(self, table: str) -> Self:
17
- """Set the target table for the INSERT statement.
18
-
19
- Args:
20
- table: The name of the table to insert data into.
21
-
22
- Raises:
23
- SQLBuilderError: If the current expression is not an INSERT statement.
24
-
25
- Returns:
26
- The current builder instance for method chaining.
27
- """
28
- if self._expression is None:
29
- self._expression = exp.Insert()
30
- if not isinstance(self._expression, exp.Insert):
31
- msg = "Cannot set target table on a non-INSERT expression."
32
- raise SQLBuilderError(msg)
33
-
34
- setattr(self, "_table", table)
35
- self._expression.set("this", exp.to_table(table))
36
- return self
@@ -1,67 +0,0 @@
1
- from collections.abc import Sequence
2
- from typing import Any, Optional, Union
3
-
4
- from sqlglot import exp
5
- from typing_extensions import Self
6
-
7
- from sqlspec.exceptions import SQLBuilderError
8
-
9
- __all__ = ("InsertValuesMixin",)
10
-
11
-
12
- class InsertValuesMixin:
13
- """Mixin providing VALUES and columns methods for INSERT builders."""
14
-
15
- _expression: Optional[exp.Expression] = None
16
-
17
- def columns(self, *columns: Union[str, exp.Expression]) -> Self:
18
- """Set the columns for the INSERT statement and synchronize the _columns attribute on the builder."""
19
- if self._expression is None:
20
- self._expression = exp.Insert()
21
- if not isinstance(self._expression, exp.Insert):
22
- msg = "Cannot set columns on a non-INSERT expression."
23
- raise SQLBuilderError(msg)
24
- column_exprs = [exp.column(col) if isinstance(col, str) else col for col in columns]
25
- self._expression.set("columns", column_exprs)
26
- # Synchronize the _columns attribute on the builder (if present)
27
- if hasattr(self, "_columns"):
28
- # If no columns, clear the list
29
- if not columns:
30
- self._columns.clear() # pyright: ignore
31
- else:
32
- self._columns[:] = [col.name if isinstance(col, exp.Column) else str(col) for col in columns] # pyright: ignore
33
- return self
34
-
35
- def values(self, *values: Any) -> Self:
36
- """Add a row of values to the INSERT statement, validating against _columns if set."""
37
- if self._expression is None:
38
- self._expression = exp.Insert()
39
- if not isinstance(self._expression, exp.Insert):
40
- msg = "Cannot add values to a non-INSERT expression."
41
- raise SQLBuilderError(msg)
42
- if (
43
- hasattr(self, "_columns") and getattr(self, "_columns", []) and len(values) != len(self._columns) # pyright: ignore
44
- ):
45
- msg = f"Number of values ({len(values)}) does not match the number of specified columns ({len(self._columns)})." # pyright: ignore
46
- raise SQLBuilderError(msg)
47
- row_exprs = []
48
- for v in values:
49
- if isinstance(v, exp.Expression):
50
- row_exprs.append(v)
51
- else:
52
- _, param_name = self.add_parameter(v) # type: ignore[attr-defined]
53
- row_exprs.append(exp.var(param_name))
54
- values_expr = exp.Values(expressions=[row_exprs])
55
- self._expression.set("expression", values_expr)
56
- return self
57
-
58
- def add_values(self, values: Sequence[Any]) -> Self:
59
- """Add a row of values to the INSERT statement (alternative signature).
60
-
61
- Args:
62
- values: Sequence of values for the row.
63
-
64
- Returns:
65
- The current builder instance for method chaining.
66
- """
67
- return self.values(*values)
@@ -1,53 +0,0 @@
1
- from typing import TYPE_CHECKING, cast
2
-
3
- from sqlglot import exp
4
- from typing_extensions import Self
5
-
6
- if TYPE_CHECKING:
7
- from sqlspec.protocols import SQLBuilderProtocol
8
-
9
- from sqlspec.exceptions import SQLBuilderError
10
-
11
- __all__ = ("LimitOffsetClauseMixin",)
12
-
13
-
14
- class LimitOffsetClauseMixin:
15
- """Mixin providing LIMIT and OFFSET clauses for SELECT builders."""
16
-
17
- def limit(self, value: int) -> Self:
18
- """Add LIMIT clause.
19
-
20
- Args:
21
- value: The maximum number of rows to return.
22
-
23
- Raises:
24
- SQLBuilderError: If the current expression is not a SELECT statement.
25
-
26
- Returns:
27
- The current builder instance for method chaining.
28
- """
29
- builder = cast("SQLBuilderProtocol", self)
30
- if not isinstance(builder._expression, exp.Select):
31
- msg = "LIMIT is only supported for SELECT statements."
32
- raise SQLBuilderError(msg)
33
- builder._expression = builder._expression.limit(exp.Literal.number(value), copy=False)
34
- return cast("Self", builder)
35
-
36
- def offset(self, value: int) -> Self:
37
- """Add OFFSET clause.
38
-
39
- Args:
40
- value: The number of rows to skip before starting to return rows.
41
-
42
- Raises:
43
- SQLBuilderError: If the current expression is not a SELECT statement.
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 = "OFFSET is only supported for SELECT statements."
51
- raise SQLBuilderError(msg)
52
- builder._expression = builder._expression.offset(exp.Literal.number(value), copy=False)
53
- return cast("Self", builder)
@@ -1,46 +0,0 @@
1
- from typing import TYPE_CHECKING, Union, cast
2
-
3
- from sqlglot import exp
4
- from typing_extensions import Self
5
-
6
- from sqlspec.exceptions import SQLBuilderError
7
- from sqlspec.statement.builder._parsing_utils import parse_order_expression
8
-
9
- if TYPE_CHECKING:
10
- from sqlspec.protocols import SQLBuilderProtocol
11
-
12
- __all__ = ("OrderByClauseMixin",)
13
-
14
-
15
- class OrderByClauseMixin:
16
- """Mixin providing ORDER BY clause for SELECT builders."""
17
-
18
- def order_by(self, *items: Union[str, exp.Ordered], desc: bool = False) -> Self:
19
- """Add ORDER BY clause.
20
-
21
- Args:
22
- *items: Columns to order by. Can be strings (column names) or sqlglot.exp.Ordered instances for specific directions (e.g., exp.column("name").desc()).
23
- desc: Whether to order in descending order (applies to all items if they are strings).
24
-
25
- Raises:
26
- SQLBuilderError: If the current expression is not a SELECT statement or if the item type is unsupported.
27
-
28
- Returns:
29
- The current builder instance for method chaining.
30
- """
31
- builder = cast("SQLBuilderProtocol", self)
32
- if not isinstance(builder._expression, exp.Select):
33
- msg = "ORDER BY is only supported for SELECT statements."
34
- raise SQLBuilderError(msg)
35
-
36
- current_expr = builder._expression
37
- for item in items:
38
- if isinstance(item, str):
39
- order_item = parse_order_expression(item)
40
- if desc:
41
- order_item = order_item.desc()
42
- else:
43
- order_item = item
44
- current_expr = current_expr.order_by(order_item, copy=False)
45
- builder._expression = current_expr
46
- return cast("Self", builder)
@@ -1,79 +0,0 @@
1
- from typing import TYPE_CHECKING, Optional, Union, cast
2
-
3
- from sqlglot import exp
4
-
5
- if TYPE_CHECKING:
6
- from sqlglot.dialects.dialect import DialectType
7
-
8
- from sqlspec.statement.builder.select import Select
9
-
10
- __all__ = ("PivotClauseMixin",)
11
-
12
-
13
- class PivotClauseMixin:
14
- """Mixin class to add PIVOT functionality to a Select."""
15
-
16
- _expression: "Optional[exp.Expression]" = None
17
- dialect: "DialectType" = None
18
-
19
- def pivot(
20
- self: "PivotClauseMixin",
21
- aggregate_function: Union[str, exp.Expression],
22
- aggregate_column: Union[str, exp.Expression],
23
- pivot_column: Union[str, exp.Expression],
24
- pivot_values: list[Union[str, int, float, exp.Expression]],
25
- alias: Optional[str] = None,
26
- ) -> "Select":
27
- """Adds a PIVOT clause to the SELECT statement.
28
-
29
- Example:
30
- `query.pivot(aggregate_function="SUM", aggregate_column="Sales", pivot_column="Quarter", pivot_values=["Q1", "Q2", "Q3", "Q4"], alias="PivotTable")`
31
-
32
- Args:
33
- aggregate_function: The aggregate function to use (e.g., "SUM", "AVG").
34
- aggregate_column: The column to be aggregated.
35
- pivot_column: The column whose unique values will become new column headers.
36
- pivot_values: A list of specific values from the pivot_column to be turned into columns.
37
- alias: Optional alias for the pivoted table/subquery.
38
-
39
- Returns:
40
- The SelectBuilder instance for chaining.
41
- """
42
- current_expr = self._expression
43
- if not isinstance(current_expr, exp.Select):
44
- msg = "Pivot can only be applied to a Select expression managed by SelectBuilder."
45
- raise TypeError(msg)
46
-
47
- agg_func_name = aggregate_function if isinstance(aggregate_function, str) else aggregate_function.name
48
- agg_col_expr = exp.column(aggregate_column) if isinstance(aggregate_column, str) else aggregate_column
49
- pivot_col_expr = exp.column(pivot_column) if isinstance(pivot_column, str) else pivot_column
50
-
51
- pivot_agg_expr = exp.func(agg_func_name, agg_col_expr)
52
-
53
- pivot_value_exprs: list[exp.Expression] = []
54
- for val in pivot_values:
55
- if isinstance(val, exp.Expression):
56
- pivot_value_exprs.append(val)
57
- elif isinstance(val, str):
58
- pivot_value_exprs.append(exp.Literal.string(val))
59
- elif isinstance(val, (int, float)):
60
- pivot_value_exprs.append(exp.Literal.number(val))
61
- else:
62
- pivot_value_exprs.append(exp.Literal.string(str(val)))
63
-
64
- in_expr = exp.In(this=pivot_col_expr, expressions=pivot_value_exprs)
65
-
66
- pivot_node = exp.Pivot(expressions=[pivot_agg_expr], fields=[in_expr], unpivot=False)
67
-
68
- if alias:
69
- pivot_node.set("alias", exp.TableAlias(this=exp.to_identifier(alias)))
70
-
71
- from_clause = current_expr.args.get("from")
72
- if from_clause and isinstance(from_clause, exp.From):
73
- table = from_clause.this
74
- if isinstance(table, exp.Table):
75
- existing_pivots = table.args.get("pivots", [])
76
- existing_pivots.append(pivot_node)
77
- table.set("pivots", existing_pivots)
78
-
79
- return cast("Select", self)
@@ -1,37 +0,0 @@
1
- from typing import Optional, Union
2
-
3
- from sqlglot import exp
4
- from typing_extensions import Self
5
-
6
- from sqlspec.exceptions import SQLBuilderError
7
-
8
- __all__ = ("ReturningClauseMixin",)
9
-
10
-
11
- class ReturningClauseMixin:
12
- """Mixin providing RETURNING clause for INSERT, UPDATE, and DELETE builders."""
13
-
14
- _expression: Optional[exp.Expression] = None
15
-
16
- def returning(self, *columns: Union[str, exp.Expression]) -> Self:
17
- """Add RETURNING clause to the statement.
18
-
19
- Args:
20
- *columns: Columns to return. Can be strings or sqlglot expressions.
21
-
22
- Raises:
23
- SQLBuilderError: If the current expression is not INSERT, UPDATE, or DELETE.
24
-
25
- Returns:
26
- The current builder instance for method chaining.
27
- """
28
- if self._expression is None:
29
- msg = "Cannot add RETURNING: expression is not initialized."
30
- raise SQLBuilderError(msg)
31
- valid_types = (exp.Insert, exp.Update, exp.Delete)
32
- if not isinstance(self._expression, valid_types):
33
- msg = "RETURNING is only supported for INSERT, UPDATE, and DELETE statements."
34
- raise SQLBuilderError(msg)
35
- returning_exprs = [exp.column(c) if isinstance(c, str) else c for c in columns]
36
- self._expression.set("returning", exp.Returning(expressions=returning_exprs))
37
- return self
@@ -1,61 +0,0 @@
1
- from typing import TYPE_CHECKING, Union, cast
2
-
3
- from sqlglot import exp
4
- from typing_extensions import Self
5
-
6
- from sqlspec.exceptions import SQLBuilderError
7
- from sqlspec.statement.builder._parsing_utils import parse_column_expression
8
-
9
- if TYPE_CHECKING:
10
- from sqlspec.protocols import SQLBuilderProtocol
11
- from sqlspec.statement.builder.column import Column, FunctionColumn
12
-
13
- __all__ = ("SelectColumnsMixin",)
14
-
15
-
16
- class SelectColumnsMixin:
17
- """Mixin providing SELECT column and DISTINCT clauses for SELECT builders."""
18
-
19
- def select(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn"]) -> Self:
20
- """Add columns to SELECT clause.
21
-
22
- Raises:
23
- SQLBuilderError: If the current expression is not a SELECT statement.
24
-
25
- Returns:
26
- The current builder instance for method chaining.
27
- """
28
- builder = cast("SQLBuilderProtocol", self)
29
- if builder._expression is None:
30
- builder._expression = exp.Select()
31
- if not isinstance(builder._expression, exp.Select):
32
- msg = "Cannot add select columns to a non-SELECT expression."
33
- raise SQLBuilderError(msg)
34
- for column in columns:
35
- builder._expression = builder._expression.select(parse_column_expression(column), copy=False)
36
- return cast("Self", builder)
37
-
38
- def distinct(self, *columns: Union[str, exp.Expression, "Column", "FunctionColumn"]) -> Self:
39
- """Add DISTINCT clause to SELECT.
40
-
41
- Args:
42
- *columns: Optional columns to make distinct. If none provided, applies DISTINCT to all selected columns.
43
-
44
- Raises:
45
- SQLBuilderError: If the current expression is not a SELECT statement.
46
-
47
- Returns:
48
- The current builder instance for method chaining.
49
- """
50
- builder = cast("SQLBuilderProtocol", self)
51
- if builder._expression is None:
52
- builder._expression = exp.Select()
53
- if not isinstance(builder._expression, exp.Select):
54
- msg = "Cannot add DISTINCT to a non-SELECT expression."
55
- raise SQLBuilderError(msg)
56
- if not columns:
57
- builder._expression.set("distinct", exp.Distinct())
58
- else:
59
- distinct_columns = [parse_column_expression(column) for column in columns]
60
- builder._expression.set("distinct", exp.Distinct(expressions=distinct_columns))
61
- return cast("Self", builder)
@@ -1,122 +0,0 @@
1
- from typing import Any, Optional
2
-
3
- from sqlglot import exp
4
- from typing_extensions import Self
5
-
6
- from sqlspec.exceptions import SQLBuilderError
7
-
8
- __all__ = ("SetOperationMixin",)
9
-
10
-
11
- class SetOperationMixin:
12
- """Mixin providing set operations (UNION, INTERSECT, EXCEPT) for SELECT builders."""
13
-
14
- _expression: Any = None
15
- _parameters: dict[str, Any] = {}
16
- dialect: Any = None
17
-
18
- def union(self, other: Any, all_: bool = False) -> Self:
19
- """Combine this query with another using UNION.
20
-
21
- Args:
22
- other: Another SelectBuilder or compatible builder to union with.
23
- all_: If True, use UNION ALL instead of UNION.
24
-
25
- Raises:
26
- SQLBuilderError: If the current expression is not a SELECT statement.
27
-
28
- Returns:
29
- The new builder instance for the union query.
30
- """
31
- left_query = self.build() # type: ignore[attr-defined]
32
- right_query = other.build()
33
- left_expr: Optional[exp.Expression] = exp.maybe_parse(left_query.sql, dialect=getattr(self, "dialect", None))
34
- right_expr: Optional[exp.Expression] = exp.maybe_parse(right_query.sql, dialect=getattr(self, "dialect", None))
35
- if not left_expr or not right_expr:
36
- msg = "Could not parse queries for UNION operation"
37
- raise SQLBuilderError(msg)
38
- union_expr = exp.union(left_expr, right_expr, distinct=not all_)
39
- new_builder = type(self)()
40
- new_builder.dialect = getattr(self, "dialect", None)
41
- new_builder._expression = union_expr
42
- merged_params = dict(left_query.parameters)
43
- for param_name, param_value in right_query.parameters.items():
44
- if param_name in merged_params:
45
- counter = 1
46
- new_param_name = f"{param_name}_right_{counter}"
47
- while new_param_name in merged_params:
48
- counter += 1
49
- new_param_name = f"{param_name}_right_{counter}"
50
-
51
- # Use AST transformation instead of string manipulation
52
- def rename_parameter(node: exp.Expression) -> exp.Expression:
53
- if isinstance(node, exp.Placeholder) and node.name == param_name: # noqa: B023
54
- return exp.Placeholder(this=new_param_name) # noqa: B023
55
- return node
56
-
57
- right_expr = right_expr.transform(rename_parameter)
58
- union_expr = exp.union(left_expr, right_expr, distinct=not all_)
59
- new_builder._expression = union_expr
60
- merged_params[new_param_name] = param_value
61
- else:
62
- merged_params[param_name] = param_value
63
- new_builder._parameters = merged_params
64
- return new_builder
65
-
66
- def intersect(self, other: Any) -> Self:
67
- """Add INTERSECT clause.
68
-
69
- Args:
70
- other: Another SelectBuilder or compatible builder to intersect with.
71
-
72
- Raises:
73
- SQLBuilderError: If the current expression is not a SELECT statement.
74
-
75
- Returns:
76
- The new builder instance for the intersect query.
77
- """
78
- left_query = self.build() # type: ignore[attr-defined]
79
- right_query = other.build()
80
- left_expr: Optional[exp.Expression] = exp.maybe_parse(left_query.sql, dialect=getattr(self, "dialect", None))
81
- right_expr: Optional[exp.Expression] = exp.maybe_parse(right_query.sql, dialect=getattr(self, "dialect", None))
82
- if not left_expr or not right_expr:
83
- msg = "Could not parse queries for INTERSECT operation"
84
- raise SQLBuilderError(msg)
85
- intersect_expr = exp.intersect(left_expr, right_expr, distinct=True)
86
- new_builder = type(self)()
87
- new_builder.dialect = getattr(self, "dialect", None)
88
- new_builder._expression = intersect_expr
89
- # Merge parameters
90
- merged_params = dict(left_query.parameters)
91
- merged_params.update(right_query.parameters)
92
- new_builder._parameters = merged_params
93
- return new_builder
94
-
95
- def except_(self, other: Any) -> Self:
96
- """Combine this query with another using EXCEPT.
97
-
98
- Args:
99
- other: Another SelectBuilder or compatible builder to except with.
100
-
101
- Raises:
102
- SQLBuilderError: If the current expression is not a SELECT statement.
103
-
104
- Returns:
105
- The new builder instance for the except query.
106
- """
107
- left_query = self.build() # type: ignore[attr-defined]
108
- right_query = other.build()
109
- left_expr: Optional[exp.Expression] = exp.maybe_parse(left_query.sql, dialect=getattr(self, "dialect", None))
110
- right_expr: Optional[exp.Expression] = exp.maybe_parse(right_query.sql, dialect=getattr(self, "dialect", None))
111
- if not left_expr or not right_expr:
112
- msg = "Could not parse queries for EXCEPT operation"
113
- raise SQLBuilderError(msg)
114
- except_expr = exp.except_(left_expr, right_expr)
115
- new_builder = type(self)()
116
- new_builder.dialect = getattr(self, "dialect", None)
117
- new_builder._expression = except_expr
118
- # Merge parameters
119
- merged_params = dict(left_query.parameters)
120
- merged_params.update(right_query.parameters)
121
- new_builder._parameters = merged_params
122
- return new_builder
@@ -1,77 +0,0 @@
1
- from typing import TYPE_CHECKING, Optional, Union, cast
2
-
3
- from sqlglot import exp
4
-
5
- if TYPE_CHECKING:
6
- from sqlglot.dialects.dialect import DialectType
7
-
8
- from sqlspec.statement.builder.select import Select
9
-
10
- __all__ = ("UnpivotClauseMixin",)
11
-
12
-
13
- class UnpivotClauseMixin:
14
- """Mixin class to add UNPIVOT functionality to a Select."""
15
-
16
- _expression: "Optional[exp.Expression]" = None
17
- dialect: "DialectType" = None
18
-
19
- def unpivot(
20
- self: "UnpivotClauseMixin",
21
- value_column_name: str,
22
- name_column_name: str,
23
- columns_to_unpivot: list[Union[str, exp.Expression]],
24
- alias: Optional[str] = None,
25
- ) -> "Select":
26
- """Adds an UNPIVOT clause to the SELECT statement.
27
-
28
- Example:
29
- `query.unpivot(value_column_name="Sales", name_column_name="Quarter", columns_to_unpivot=["Q1Sales", "Q2Sales"], alias="UnpivotTable")`
30
-
31
- Args:
32
- value_column_name: The name for the new column that will hold the values from the unpivoted columns.
33
- name_column_name: The name for the new column that will hold the names of the original unpivoted columns.
34
- columns_to_unpivot: A list of columns to be unpivoted into rows.
35
- alias: Optional alias for the unpivoted table/subquery.
36
-
37
- Raises:
38
- TypeError: If the current expression is not a Select expression.
39
-
40
- Returns:
41
- The Select instance for chaining.
42
- """
43
- current_expr = self._expression
44
- if not isinstance(current_expr, exp.Select):
45
- # SelectBuilder's __init__ ensures _expression is exp.Select.
46
- msg = "Unpivot can only be applied to a Select expression managed by Select."
47
- raise TypeError(msg)
48
-
49
- value_col_ident = exp.to_identifier(value_column_name)
50
- name_col_ident = exp.to_identifier(name_column_name)
51
-
52
- unpivot_cols_exprs: list[exp.Expression] = []
53
- for col_name_or_expr in columns_to_unpivot:
54
- if isinstance(col_name_or_expr, exp.Expression):
55
- unpivot_cols_exprs.append(col_name_or_expr)
56
- elif isinstance(col_name_or_expr, str):
57
- unpivot_cols_exprs.append(exp.column(col_name_or_expr))
58
- else:
59
- # Fallback for other types, should ideally be an error or more specific handling
60
- unpivot_cols_exprs.append(exp.column(str(col_name_or_expr)))
61
-
62
- in_expr = exp.In(this=name_col_ident, expressions=unpivot_cols_exprs)
63
-
64
- unpivot_node = exp.Pivot(expressions=[value_col_ident], fields=[in_expr], unpivot=True)
65
-
66
- if alias:
67
- unpivot_node.set("alias", exp.TableAlias(this=exp.to_identifier(alias)))
68
-
69
- from_clause = current_expr.args.get("from")
70
- if from_clause and isinstance(from_clause, exp.From):
71
- table = from_clause.this
72
- if isinstance(table, exp.Table):
73
- existing_pivots = table.args.get("pivots", [])
74
- existing_pivots.append(unpivot_node)
75
- table.set("pivots", existing_pivots)
76
-
77
- return cast("Select", self)