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,55 +0,0 @@
1
- from typing import Any, Optional, Union
2
-
3
- from sqlglot import exp
4
- from typing_extensions import Self
5
-
6
- from sqlspec.exceptions import SQLBuilderError
7
- from sqlspec.utils.type_guards import has_query_builder_parameters
8
-
9
- __all__ = ("UpdateFromClauseMixin",)
10
-
11
-
12
- class UpdateFromClauseMixin:
13
- """Mixin providing FROM clause for UPDATE builders (e.g., PostgreSQL style)."""
14
-
15
- def from_(self, table: Union[str, exp.Expression, Any], alias: Optional[str] = None) -> Self:
16
- """Add a FROM clause to the UPDATE statement.
17
-
18
- Args:
19
- table: The table name, expression, or subquery to add to the FROM clause.
20
- alias: Optional alias for the table in the FROM clause.
21
-
22
- Returns:
23
- The current builder instance for method chaining.
24
-
25
- Raises:
26
- SQLBuilderError: If the current expression is not an UPDATE statement.
27
- """
28
- if self._expression is None or not isinstance(self._expression, exp.Update): # type: ignore[attr-defined]
29
- msg = "Cannot add FROM clause to non-UPDATE expression. Set the main table first."
30
- raise SQLBuilderError(msg)
31
- table_expr: exp.Expression
32
- if isinstance(table, str):
33
- table_expr = exp.to_table(table, alias=alias)
34
- elif has_query_builder_parameters(table):
35
- subquery_builder_params = getattr(table, "_parameters", None)
36
- if subquery_builder_params:
37
- for p_name, p_value in subquery_builder_params.items():
38
- self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
39
- subquery_exp = exp.paren(getattr(table, "_expression", exp.select()))
40
- table_expr = exp.alias_(subquery_exp, alias) if alias else subquery_exp
41
- elif isinstance(table, exp.Expression):
42
- table_expr = exp.alias_(table, alias) if alias else table
43
- else:
44
- msg = f"Unsupported table type for FROM clause: {type(table)}"
45
- raise SQLBuilderError(msg)
46
- if self._expression.args.get("from") is None: # type: ignore[attr-defined]
47
- self._expression.set("from", exp.From(expressions=[])) # type: ignore[attr-defined]
48
- from_clause = self._expression.args["from"] # type: ignore[attr-defined]
49
- if hasattr(from_clause, "append"):
50
- from_clause.append("expressions", table_expr)
51
- else:
52
- if not from_clause.expressions:
53
- from_clause.expressions = []
54
- from_clause.expressions.append(table_expr)
55
- return self
@@ -1,94 +0,0 @@
1
- from collections.abc import Mapping
2
- from typing import Any, Optional
3
-
4
- from sqlglot import exp
5
- from typing_extensions import Self
6
-
7
- from sqlspec.exceptions import SQLBuilderError
8
- from sqlspec.utils.type_guards import has_query_builder_parameters
9
-
10
- __all__ = ("UpdateSetClauseMixin",)
11
-
12
- MIN_SET_ARGS = 2
13
-
14
-
15
- class UpdateSetClauseMixin:
16
- """Mixin providing SET clause for UPDATE builders."""
17
-
18
- _expression: Optional[exp.Expression] = None
19
-
20
- def set(self, *args: Any, **kwargs: Any) -> Self:
21
- """Set columns and values for the UPDATE statement.
22
-
23
- Supports:
24
- - set(column, value)
25
- - set(mapping)
26
- - set(**kwargs)
27
- - set(mapping, **kwargs)
28
-
29
- Args:
30
- *args: Either (column, value) or a mapping.
31
- **kwargs: Column-value pairs to set.
32
-
33
- Raises:
34
- SQLBuilderError: If the current expression is not an UPDATE statement or usage is invalid.
35
-
36
- Returns:
37
- The current builder instance for method chaining.
38
- """
39
-
40
- if self._expression is None:
41
- self._expression = exp.Update()
42
- if not isinstance(self._expression, exp.Update):
43
- msg = "Cannot add SET clause to non-UPDATE expression."
44
- raise SQLBuilderError(msg)
45
- assignments = []
46
- # (column, value) signature
47
- if len(args) == MIN_SET_ARGS and not kwargs:
48
- col, val = args
49
- col_expr = col if isinstance(col, exp.Column) else exp.column(col)
50
- # If value is an expression, use it directly
51
- if isinstance(val, exp.Expression):
52
- value_expr = val
53
- elif has_query_builder_parameters(val):
54
- # It's a builder (like SelectBuilder), convert to subquery
55
- subquery = val.build()
56
- # Parse the SQL and use as expression
57
- sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
58
- value_expr = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect", None)))
59
- # Merge parameters from subquery
60
- if has_query_builder_parameters(val):
61
- for p_name, p_value in val.parameters.items():
62
- self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
63
- else:
64
- param_name = self.add_parameter(val)[1] # type: ignore[attr-defined]
65
- value_expr = exp.Placeholder(this=param_name)
66
- assignments.append(exp.EQ(this=col_expr, expression=value_expr))
67
- # mapping and/or kwargs
68
- elif (len(args) == 1 and isinstance(args[0], Mapping)) or kwargs:
69
- all_values = dict(args[0] if args else {}, **kwargs)
70
- for col, val in all_values.items():
71
- # If value is an expression, use it directly
72
- if isinstance(val, exp.Expression):
73
- value_expr = val
74
- elif has_query_builder_parameters(val):
75
- # It's a builder (like SelectBuilder), convert to subquery
76
- subquery = val.build()
77
- # Parse the SQL and use as expression
78
- sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
79
- value_expr = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect", None)))
80
- # Merge parameters from subquery
81
- if has_query_builder_parameters(val):
82
- for p_name, p_value in val.parameters.items():
83
- self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
84
- else:
85
- param_name = self.add_parameter(val)[1] # type: ignore[attr-defined]
86
- value_expr = exp.Placeholder(this=param_name)
87
- assignments.append(exp.EQ(this=exp.column(col), expression=value_expr))
88
- else:
89
- msg = "Invalid arguments for set(): use (column, value), mapping, or kwargs."
90
- raise SQLBuilderError(msg)
91
- # Append to existing expressions instead of replacing
92
- existing = self._expression.args.get("expressions", [])
93
- self._expression.set("expressions", existing + assignments)
94
- return self
@@ -1,29 +0,0 @@
1
- from typing import Optional
2
-
3
- from sqlglot import exp
4
- from typing_extensions import Self
5
-
6
- __all__ = ("UpdateTableClauseMixin",)
7
-
8
-
9
- class UpdateTableClauseMixin:
10
- """Mixin providing TABLE clause for UPDATE builders."""
11
-
12
- _expression: Optional[exp.Expression] = None
13
-
14
- def table(self, table_name: str, alias: Optional[str] = None) -> Self:
15
- """Set the table to update.
16
-
17
- Args:
18
- table_name: The name of the table.
19
- alias: Optional alias for the table.
20
-
21
- Returns:
22
- The current builder instance for method chaining.
23
- """
24
- if self._expression is None or not isinstance(self._expression, exp.Update):
25
- self._expression = exp.Update(this=None, expressions=[], joins=[])
26
- table_expr: exp.Expression = exp.to_table(table_name, alias=alias)
27
- self._expression.set("this", table_expr)
28
- setattr(self, "_table", table_name)
29
- return self
@@ -1,401 +0,0 @@
1
- # ruff: noqa: PLR2004
2
- from typing import TYPE_CHECKING, Any, Optional, Union, cast
3
-
4
- from sqlglot import exp, parse_one
5
- from typing_extensions import Self
6
-
7
- from sqlspec.exceptions import SQLBuilderError
8
- from sqlspec.statement.builder._parsing_utils import parse_column_expression, parse_condition_expression
9
- from sqlspec.utils.type_guards import has_query_builder_parameters, is_iterable_parameters
10
-
11
- if TYPE_CHECKING:
12
- from sqlspec.protocols import SQLBuilderProtocol
13
- from sqlspec.statement.builder.column import ColumnExpression
14
-
15
- __all__ = ("WhereClauseMixin",)
16
-
17
-
18
- class WhereClauseMixin:
19
- """Mixin providing WHERE clause methods for SELECT, UPDATE, and DELETE builders."""
20
-
21
- def where(
22
- self,
23
- condition: Union[str, exp.Expression, exp.Condition, tuple[str, Any], tuple[str, str, Any], "ColumnExpression"],
24
- ) -> Self:
25
- """Add a WHERE clause to the statement.
26
-
27
- Args:
28
- condition: The condition for the WHERE clause. Can be:
29
- - A string condition
30
- - A sqlglot Expression or Condition
31
- - A 2-tuple (column, value) for equality comparison
32
- - A 3-tuple (column, operator, value) for custom comparison
33
-
34
- Raises:
35
- SQLBuilderError: If the current expression is not a supported statement type.
36
-
37
- Returns:
38
- The current builder instance for method chaining.
39
- """
40
- # Special case: if this is an Update and _expression is not exp.Update, raise the expected error for test coverage
41
-
42
- if self.__class__.__name__ == "Update" and not (
43
- hasattr(self, "_expression") and isinstance(getattr(self, "_expression", None), exp.Update)
44
- ):
45
- msg = "Cannot add WHERE clause to non-UPDATE expression"
46
- raise SQLBuilderError(msg)
47
- builder = cast("SQLBuilderProtocol", self)
48
- if builder._expression is None:
49
- msg = "Cannot add WHERE clause: expression is not initialized."
50
- raise SQLBuilderError(msg)
51
- valid_types = (exp.Select, exp.Update, exp.Delete)
52
- if not isinstance(builder._expression, valid_types):
53
- msg = f"Cannot add WHERE clause to unsupported expression type: {type(builder._expression).__name__}."
54
- raise SQLBuilderError(msg)
55
-
56
- if isinstance(builder._expression, exp.Delete) and not builder._expression.args.get("this"):
57
- msg = "WHERE clause requires a table to be set. Use from() to set the table first."
58
- raise SQLBuilderError(msg)
59
-
60
- # Normalize the condition using enhanced parsing
61
- condition_expr: exp.Expression
62
- if isinstance(condition, tuple):
63
- if len(condition) == 2:
64
- # 2-tuple: (column, value) -> column = value
65
- param_name = builder.add_parameter(condition[1])[1]
66
- condition_expr = exp.EQ(
67
- this=parse_column_expression(condition[0]), expression=exp.Placeholder(this=param_name)
68
- )
69
- elif len(condition) == 3:
70
- # 3-tuple: (column, operator, value) -> column operator value
71
- column, operator, value = condition
72
- param_name = builder.add_parameter(value)[1]
73
- col_expr = parse_column_expression(column)
74
- placeholder_expr = exp.Placeholder(this=param_name)
75
-
76
- # Map operator strings to sqlglot expression types
77
- operator_map = {
78
- "=": exp.EQ,
79
- "==": exp.EQ,
80
- "!=": exp.NEQ,
81
- "<>": exp.NEQ,
82
- "<": exp.LT,
83
- "<=": exp.LTE,
84
- ">": exp.GT,
85
- ">=": exp.GTE,
86
- "like": exp.Like,
87
- "in": exp.In,
88
- "any": exp.Any,
89
- }
90
- operator = operator.lower()
91
- if operator == "not like":
92
- condition_expr = exp.Not(this=exp.Like(this=col_expr, expression=placeholder_expr))
93
- elif operator == "not in":
94
- condition_expr = exp.Not(this=exp.In(this=col_expr, expression=placeholder_expr))
95
- elif operator == "not any":
96
- condition_expr = exp.Not(this=exp.Any(this=col_expr, expression=placeholder_expr))
97
- else:
98
- expr_class = operator_map.get(operator)
99
- if expr_class is None:
100
- msg = f"Unsupported operator in WHERE condition: {operator}"
101
- raise SQLBuilderError(msg)
102
-
103
- condition_expr = expr_class(this=col_expr, expression=placeholder_expr)
104
- else:
105
- msg = f"WHERE tuple must have 2 or 3 elements, got {len(condition)}"
106
- raise SQLBuilderError(msg)
107
- # Handle ColumnExpression objects
108
- elif hasattr(condition, "sqlglot_expression"):
109
- # This is a ColumnExpression from our new Column syntax
110
- raw_expr = getattr(condition, "sqlglot_expression", None)
111
- if raw_expr is not None:
112
- condition_expr = builder._parameterize_expression(raw_expr)
113
- else:
114
- # Fallback if attribute exists but is None
115
- condition_expr = parse_condition_expression(str(condition))
116
- else:
117
- # Existing logic for strings and raw SQLGlot expressions
118
- # Convert to string if it's not a recognized type
119
- if not isinstance(condition, (str, exp.Expression, tuple)):
120
- condition = str(condition)
121
- condition_expr = parse_condition_expression(condition)
122
-
123
- # Use dialect if available for Delete
124
- if isinstance(builder._expression, exp.Delete):
125
- builder._expression = builder._expression.where(
126
- condition_expr, dialect=getattr(builder, "dialect_name", None)
127
- )
128
- else:
129
- builder._expression = builder._expression.where(condition_expr, copy=False)
130
- return cast("Self", builder)
131
-
132
- # The following methods are moved from the old WhereClauseMixin in _base.py
133
- def where_eq(self, column: "Union[str, exp.Column]", value: Any) -> "Self":
134
- _, param_name = self.add_parameter(value) # type: ignore[attr-defined]
135
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
136
- condition: exp.Expression = col_expr.eq(exp.var(param_name))
137
- return self.where(condition)
138
-
139
- def where_neq(self, column: "Union[str, exp.Column]", value: Any) -> "Self":
140
- _, param_name = self.add_parameter(value) # type: ignore[attr-defined]
141
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
142
- condition: exp.Expression = col_expr.neq(exp.var(param_name))
143
- return self.where(condition)
144
-
145
- def where_lt(self, column: "Union[str, exp.Column]", value: Any) -> "Self":
146
- _, param_name = self.add_parameter(value) # type: ignore[attr-defined]
147
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
148
- condition: exp.Expression = exp.LT(this=col_expr, expression=exp.var(param_name))
149
- return self.where(condition)
150
-
151
- def where_lte(self, column: "Union[str, exp.Column]", value: Any) -> "Self":
152
- _, param_name = self.add_parameter(value) # type: ignore[attr-defined]
153
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
154
- condition: exp.Expression = exp.LTE(this=col_expr, expression=exp.var(param_name))
155
- return self.where(condition)
156
-
157
- def where_gt(self, column: "Union[str, exp.Column]", value: Any) -> "Self":
158
- _, param_name = self.add_parameter(value) # type: ignore[attr-defined]
159
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
160
- condition: exp.Expression = exp.GT(this=col_expr, expression=exp.var(param_name))
161
- return self.where(condition)
162
-
163
- def where_gte(self, column: "Union[str, exp.Column]", value: Any) -> "Self":
164
- _, param_name = self.add_parameter(value) # type: ignore[attr-defined]
165
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
166
- condition: exp.Expression = exp.GTE(this=col_expr, expression=exp.var(param_name))
167
- return self.where(condition)
168
-
169
- def where_between(self, column: "Union[str, exp.Column]", low: Any, high: Any) -> "Self":
170
- _, low_param = self.add_parameter(low) # type: ignore[attr-defined]
171
- _, high_param = self.add_parameter(high) # type: ignore[attr-defined]
172
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
173
- condition: exp.Expression = col_expr.between(exp.var(low_param), exp.var(high_param))
174
- return self.where(condition)
175
-
176
- def where_like(self, column: "Union[str, exp.Column]", pattern: str, escape: Optional[str] = None) -> "Self":
177
- _, param_name = self.add_parameter(pattern) # type: ignore[attr-defined]
178
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
179
- if escape is not None:
180
- cond = exp.Like(this=col_expr, expression=exp.var(param_name), escape=exp.Literal.string(str(escape)))
181
- else:
182
- cond = col_expr.like(exp.var(param_name))
183
- condition: exp.Expression = cond
184
- return self.where(condition)
185
-
186
- def where_not_like(self, column: "Union[str, exp.Column]", pattern: str) -> "Self":
187
- _, param_name = self.add_parameter(pattern) # type: ignore[attr-defined]
188
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
189
- condition: exp.Expression = col_expr.like(exp.var(param_name)).not_()
190
- return self.where(condition)
191
-
192
- def where_ilike(self, column: "Union[str, exp.Column]", pattern: str) -> "Self":
193
- _, param_name = self.add_parameter(pattern) # type: ignore[attr-defined]
194
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
195
- condition: exp.Expression = col_expr.ilike(exp.var(param_name))
196
- return self.where(condition)
197
-
198
- def where_is_null(self, column: "Union[str, exp.Column]") -> "Self":
199
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
200
- condition: exp.Expression = col_expr.is_(exp.null())
201
- return self.where(condition)
202
-
203
- def where_is_not_null(self, column: "Union[str, exp.Column]") -> "Self":
204
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
205
- condition: exp.Expression = col_expr.is_(exp.null()).not_()
206
- return self.where(condition)
207
-
208
- def where_exists(self, subquery: "Union[str, Any]") -> "Self":
209
- sub_expr: exp.Expression
210
- if has_query_builder_parameters(subquery):
211
- subquery_builder_params: dict[str, Any] = subquery.parameters
212
- if subquery_builder_params:
213
- for p_name, p_value in subquery_builder_params.items():
214
- self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
215
- sub_sql_obj = subquery.build() # pyright: ignore
216
- sql_str = (
217
- sub_sql_obj.sql if hasattr(sub_sql_obj, "sql") and not callable(sub_sql_obj.sql) else str(sub_sql_obj)
218
- )
219
- sub_expr = exp.maybe_parse(sql_str, dialect=getattr(self, "dialect_name", None))
220
- else:
221
- sub_expr = exp.maybe_parse(str(subquery), dialect=getattr(self, "dialect_name", None))
222
-
223
- if sub_expr is None:
224
- msg = "Could not parse subquery for EXISTS"
225
- raise SQLBuilderError(msg)
226
-
227
- exists_expr = exp.Exists(this=sub_expr)
228
- return self.where(exists_expr)
229
-
230
- def where_not_exists(self, subquery: "Union[str, Any]") -> "Self":
231
- sub_expr: exp.Expression
232
- if has_query_builder_parameters(subquery):
233
- subquery_builder_params: dict[str, Any] = subquery.parameters
234
- if subquery_builder_params:
235
- for p_name, p_value in subquery_builder_params.items():
236
- self.add_parameter(p_value, name=p_name) # type: ignore[attr-defined]
237
- sub_sql_obj = subquery.build() # pyright: ignore
238
- sql_str = (
239
- sub_sql_obj.sql if hasattr(sub_sql_obj, "sql") and not callable(sub_sql_obj.sql) else str(sub_sql_obj)
240
- )
241
- sub_expr = exp.maybe_parse(sql_str, dialect=getattr(self, "dialect_name", None))
242
- else:
243
- sub_expr = exp.maybe_parse(str(subquery), dialect=getattr(self, "dialect_name", None))
244
-
245
- if sub_expr is None:
246
- msg = "Could not parse subquery for NOT EXISTS"
247
- raise SQLBuilderError(msg)
248
-
249
- not_exists_expr = exp.Not(this=exp.Exists(this=sub_expr))
250
- return self.where(not_exists_expr)
251
-
252
- def where_not_null(self, column: "Union[str, exp.Column]") -> "Self":
253
- """Alias for where_is_not_null for compatibility with test expectations."""
254
- return self.where_is_not_null(column)
255
-
256
- def where_in(self, column: "Union[str, exp.Column]", values: Any) -> "Self":
257
- """Add a WHERE ... IN (...) clause. Supports subqueries and iterables."""
258
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
259
- # Subquery support
260
- if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
261
- subquery_exp: exp.Expression
262
- if has_query_builder_parameters(values):
263
- subquery = values.build() # pyright: ignore
264
- sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
265
- subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect_name", None)))
266
- else:
267
- subquery_exp = values # type: ignore[assignment]
268
- condition = col_expr.isin(subquery_exp)
269
- return self.where(condition)
270
- # Iterable of values
271
- if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
272
- msg = "Unsupported type for 'values' in WHERE IN"
273
- raise SQLBuilderError(msg)
274
- params = []
275
- for v in values:
276
- _, param_name = self.add_parameter(v) # type: ignore[attr-defined]
277
- params.append(exp.var(param_name))
278
- condition = col_expr.isin(*params)
279
- return self.where(condition)
280
-
281
- def where_not_in(self, column: "Union[str, exp.Column]", values: Any) -> "Self":
282
- """Add a WHERE ... NOT IN (...) clause. Supports subqueries and iterables."""
283
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
284
- if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
285
- subquery_exp: exp.Expression
286
- if has_query_builder_parameters(values):
287
- subquery = values.build() # pyright: ignore
288
- sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
289
- subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect_name", None)))
290
- else:
291
- subquery_exp = values # type: ignore[assignment]
292
- condition = exp.Not(this=col_expr.isin(subquery_exp))
293
- return self.where(condition)
294
- if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
295
- msg = "Values for where_not_in must be a non-string iterable or subquery."
296
- raise SQLBuilderError(msg)
297
- params = []
298
- for v in values:
299
- _, param_name = self.add_parameter(v) # type: ignore[attr-defined]
300
- params.append(exp.var(param_name))
301
- condition = exp.Not(this=col_expr.isin(*params))
302
- return self.where(condition)
303
-
304
- def where_null(self, column: "Union[str, exp.Column]") -> "Self":
305
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
306
- condition: exp.Expression = col_expr.is_(exp.null())
307
- return self.where(condition)
308
-
309
- def where_any(self, column: "Union[str, exp.Column]", values: Any) -> "Self":
310
- """Add a WHERE ... = ANY (...) clause. Supports subqueries and iterables.
311
-
312
- Args:
313
- column: The column to compare.
314
- values: A subquery or iterable of values.
315
-
316
- Returns:
317
- The current builder instance for method chaining.
318
- """
319
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
320
- if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
321
- subquery_exp: exp.Expression
322
- if has_query_builder_parameters(values):
323
- subquery = values.build() # pyright: ignore
324
- sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
325
- subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect_name", None)))
326
- else:
327
- subquery_exp = values # type: ignore[assignment]
328
- condition = exp.EQ(this=col_expr, expression=exp.Any(this=subquery_exp))
329
- return self.where(condition)
330
- if isinstance(values, str):
331
- # Try to parse as subquery expression with enhanced parsing
332
- try:
333
- # Parse as a subquery expression
334
- parsed_expr = parse_one(values)
335
- if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
336
- subquery_exp = exp.paren(parsed_expr)
337
- condition = exp.EQ(this=col_expr, expression=exp.Any(this=subquery_exp))
338
- return self.where(condition)
339
- except Exception: # noqa: S110
340
- # Subquery parsing failed for WHERE ANY
341
- pass
342
- # If parsing fails, fall through to error
343
- msg = "Unsupported type for 'values' in WHERE ANY"
344
- raise SQLBuilderError(msg)
345
- if not is_iterable_parameters(values) or isinstance(values, bytes):
346
- msg = "Unsupported type for 'values' in WHERE ANY"
347
- raise SQLBuilderError(msg)
348
- params = []
349
- for v in values:
350
- _, param_name = self.add_parameter(v) # type: ignore[attr-defined]
351
- params.append(exp.var(param_name))
352
- tuple_expr = exp.Tuple(expressions=params)
353
- condition = exp.EQ(this=col_expr, expression=exp.Any(this=tuple_expr))
354
- return self.where(condition)
355
-
356
- def where_not_any(self, column: "Union[str, exp.Column]", values: Any) -> "Self":
357
- """Add a WHERE ... <> ANY (...) (or NOT = ANY) clause. Supports subqueries and iterables.
358
-
359
- Args:
360
- column: The column to compare.
361
- values: A subquery or iterable of values.
362
-
363
- Returns:
364
- The current builder instance for method chaining.
365
- """
366
- col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
367
- if has_query_builder_parameters(values) or isinstance(values, exp.Expression):
368
- subquery_exp: exp.Expression
369
- if has_query_builder_parameters(values):
370
- subquery = values.build() # pyright: ignore
371
- sql_str = subquery.sql if hasattr(subquery, "sql") and not callable(subquery.sql) else str(subquery)
372
- subquery_exp = exp.paren(exp.maybe_parse(sql_str, dialect=getattr(self, "dialect_name", None)))
373
- else:
374
- subquery_exp = values # type: ignore[assignment]
375
- condition = exp.NEQ(this=col_expr, expression=exp.Any(this=subquery_exp))
376
- return self.where(condition)
377
- if isinstance(values, str):
378
- # Try to parse as subquery expression with enhanced parsing
379
- try:
380
- # Parse as a subquery expression
381
- parsed_expr = parse_one(values)
382
- if isinstance(parsed_expr, (exp.Select, exp.Union, exp.Subquery)):
383
- subquery_exp = exp.paren(parsed_expr)
384
- condition = exp.NEQ(this=col_expr, expression=exp.Any(this=subquery_exp))
385
- return self.where(condition)
386
- except Exception: # noqa: S110
387
- # Subquery parsing failed for WHERE NOT ANY
388
- pass
389
- # If parsing fails, fall through to error
390
- msg = "Unsupported type for 'values' in WHERE NOT ANY"
391
- raise SQLBuilderError(msg)
392
- if not is_iterable_parameters(values) or isinstance(values, bytes):
393
- msg = "Unsupported type for 'values' in WHERE NOT ANY"
394
- raise SQLBuilderError(msg)
395
- params = []
396
- for v in values:
397
- _, param_name = self.add_parameter(v) # type: ignore[attr-defined]
398
- params.append(exp.var(param_name))
399
- tuple_expr = exp.Tuple(expressions=params)
400
- condition = exp.NEQ(this=col_expr, expression=exp.Any(this=tuple_expr))
401
- return self.where(condition)
@@ -1,86 +0,0 @@
1
- from typing import Any, Optional, Union
2
-
3
- from sqlglot import exp
4
- from typing_extensions import Self
5
-
6
- from sqlspec.exceptions import SQLBuilderError
7
-
8
- __all__ = ("WindowFunctionsMixin",)
9
-
10
-
11
- class WindowFunctionsMixin:
12
- """Mixin providing window function methods for SQL builders."""
13
-
14
- _expression: Optional[exp.Expression] = None
15
-
16
- def window(
17
- self,
18
- function_expr: Union[str, exp.Expression],
19
- partition_by: Optional[Union[str, list[str], exp.Expression, list[exp.Expression]]] = None,
20
- order_by: Optional[Union[str, list[str], exp.Expression, list[exp.Expression]]] = None,
21
- frame: Optional[str] = None,
22
- alias: Optional[str] = None,
23
- ) -> Self:
24
- """Add a window function to the SELECT clause.
25
-
26
- Args:
27
- function_expr: The window function expression (e.g., "COUNT(*)", "ROW_NUMBER()").
28
- partition_by: Column(s) to partition by.
29
- order_by: Column(s) to order by within the window.
30
- frame: Window frame specification (e.g., "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW").
31
- alias: Optional alias for the window function.
32
-
33
- Raises:
34
- SQLBuilderError: If the current expression is not a SELECT statement or function parsing fails.
35
-
36
- Returns:
37
- The current builder instance for method chaining.
38
- """
39
- if self._expression is None:
40
- self._expression = exp.Select()
41
- if not isinstance(self._expression, exp.Select):
42
- msg = "Cannot add window function to a non-SELECT expression."
43
- raise SQLBuilderError(msg)
44
-
45
- func_expr_parsed: exp.Expression
46
- if isinstance(function_expr, str):
47
- parsed: Optional[exp.Expression] = exp.maybe_parse(function_expr, dialect=getattr(self, "dialect", None))
48
- if not parsed:
49
- msg = f"Could not parse function expression: {function_expr}"
50
- raise SQLBuilderError(msg)
51
- func_expr_parsed = parsed
52
- else:
53
- func_expr_parsed = function_expr
54
-
55
- over_args: dict[str, Any] = {} # Stringified dict
56
- if partition_by:
57
- if isinstance(partition_by, str):
58
- over_args["partition_by"] = [exp.column(partition_by)]
59
- elif isinstance(partition_by, list): # Check for list
60
- over_args["partition_by"] = [exp.column(col) if isinstance(col, str) else col for col in partition_by]
61
- elif isinstance(partition_by, exp.Expression): # Check for exp.Expression
62
- over_args["partition_by"] = [partition_by]
63
-
64
- if order_by:
65
- if isinstance(order_by, str):
66
- over_args["order"] = exp.column(order_by).asc()
67
- elif isinstance(order_by, list):
68
- # Properly handle multiple ORDER BY columns using Order expression
69
- order_expressions: list[Union[exp.Expression, exp.Column]] = []
70
- for col in order_by:
71
- if isinstance(col, str):
72
- order_expressions.append(exp.column(col).asc())
73
- else:
74
- order_expressions.append(col)
75
- over_args["order"] = exp.Order(expressions=order_expressions)
76
- elif isinstance(order_by, exp.Expression):
77
- over_args["order"] = order_by
78
-
79
- if frame:
80
- frame_expr: Optional[exp.Expression] = exp.maybe_parse(frame, dialect=getattr(self, "dialect", None))
81
- if frame_expr:
82
- over_args["frame"] = frame_expr
83
-
84
- window_expr = exp.Window(this=func_expr_parsed, **over_args)
85
- self._expression.select(exp.alias_(window_expr, alias) if alias else window_expr, copy=False)
86
- return self