sqlspec 0.24.1__py3-none-any.whl → 0.25.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.

Files changed (42) hide show
  1. sqlspec/_sql.py +11 -15
  2. sqlspec/_typing.py +2 -0
  3. sqlspec/adapters/adbc/driver.py +2 -2
  4. sqlspec/adapters/oracledb/driver.py +5 -0
  5. sqlspec/adapters/psycopg/config.py +2 -4
  6. sqlspec/base.py +3 -4
  7. sqlspec/builder/_base.py +55 -13
  8. sqlspec/builder/_column.py +9 -0
  9. sqlspec/builder/_ddl.py +7 -7
  10. sqlspec/builder/_insert.py +10 -6
  11. sqlspec/builder/_parsing_utils.py +23 -4
  12. sqlspec/builder/_update.py +1 -1
  13. sqlspec/builder/mixins/_cte_and_set_ops.py +31 -22
  14. sqlspec/builder/mixins/_delete_operations.py +12 -7
  15. sqlspec/builder/mixins/_insert_operations.py +50 -36
  16. sqlspec/builder/mixins/_join_operations.py +1 -0
  17. sqlspec/builder/mixins/_merge_operations.py +54 -28
  18. sqlspec/builder/mixins/_order_limit_operations.py +1 -0
  19. sqlspec/builder/mixins/_pivot_operations.py +1 -0
  20. sqlspec/builder/mixins/_select_operations.py +42 -14
  21. sqlspec/builder/mixins/_update_operations.py +30 -18
  22. sqlspec/builder/mixins/_where_clause.py +48 -60
  23. sqlspec/core/__init__.py +3 -2
  24. sqlspec/core/cache.py +297 -351
  25. sqlspec/core/compiler.py +5 -3
  26. sqlspec/core/filters.py +246 -213
  27. sqlspec/core/hashing.py +9 -11
  28. sqlspec/core/parameters.py +20 -7
  29. sqlspec/core/statement.py +67 -12
  30. sqlspec/driver/_async.py +2 -2
  31. sqlspec/driver/_common.py +31 -14
  32. sqlspec/driver/_sync.py +2 -2
  33. sqlspec/driver/mixins/_result_tools.py +60 -7
  34. sqlspec/loader.py +8 -9
  35. sqlspec/storage/backends/fsspec.py +1 -0
  36. sqlspec/typing.py +2 -0
  37. {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/METADATA +1 -1
  38. {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/RECORD +42 -42
  39. {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/WHEEL +0 -0
  40. {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/entry_points.txt +0 -0
  41. {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/licenses/LICENSE +0 -0
  42. {sqlspec-0.24.1.dist-info → sqlspec-0.25.0.dist-info}/licenses/NOTICE +0 -0
@@ -1,4 +1,5 @@
1
1
  # ruff: noqa: PLR2004
2
+ # pyright: reportPrivateUsage=false, reportPrivateImportUsage=false
2
3
  """WHERE and HAVING clause mixins.
3
4
 
4
5
  Provides mixins for WHERE and HAVING clause functionality with
@@ -14,7 +15,7 @@ from mypy_extensions import trait
14
15
  from sqlglot import exp
15
16
  from typing_extensions import Self
16
17
 
17
- from sqlspec.builder._parsing_utils import parse_column_expression, parse_condition_expression
18
+ from sqlspec.builder._parsing_utils import extract_column_name, parse_column_expression, parse_condition_expression
18
19
  from sqlspec.exceptions import SQLBuilderError
19
20
  from sqlspec.utils.type_guards import (
20
21
  has_expression_and_parameters,
@@ -24,31 +25,6 @@ from sqlspec.utils.type_guards import (
24
25
  is_iterable_parameters,
25
26
  )
26
27
 
27
-
28
- def _extract_column_name(column: Union[str, exp.Column]) -> str:
29
- """Extract column name from column expression for parameter naming.
30
-
31
- Args:
32
- column: Column expression (string or SQLGlot Column)
33
-
34
- Returns:
35
- Column name as string for use as parameter name
36
- """
37
- if isinstance(column, str):
38
- # Handle simple column names and table.column references
39
- if "." in column:
40
- return column.split(".")[-1] # Return just the column part
41
- return column
42
- if isinstance(column, exp.Column):
43
- # Extract the column name from SQLGlot Column expression
44
- try:
45
- return str(column.this.this)
46
- except AttributeError:
47
- return str(column.this) if column.this else "column"
48
- # Fallback for any unexpected types (defensive programming)
49
- return "column"
50
-
51
-
52
28
  if TYPE_CHECKING:
53
29
  from sqlspec.builder._column import ColumnExpression
54
30
  from sqlspec.protocols import SQLBuilderProtocol
@@ -62,8 +38,9 @@ class WhereClauseMixin:
62
38
 
63
39
  __slots__ = ()
64
40
 
65
- # Type annotation for PyRight - this will be provided by the base class
66
- _expression: Optional[exp.Expression]
41
+ # Type annotations for PyRight - these will be provided by the base class
42
+ def get_expression(self) -> Optional[exp.Expression]: ...
43
+ def set_expression(self, expression: exp.Expression) -> None: ...
67
44
 
68
45
  def _create_parameterized_condition(
69
46
  self,
@@ -82,7 +59,7 @@ class WhereClauseMixin:
82
59
  The created condition expression
83
60
  """
84
61
  builder = cast("SQLBuilderProtocol", self)
85
- column_name = _extract_column_name(column)
62
+ column_name = extract_column_name(column)
86
63
  param_name = builder._generate_unique_parameter_name(column_name)
87
64
  _, param_name = builder.add_parameter(value, name=param_name)
88
65
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
@@ -118,18 +95,20 @@ class WhereClauseMixin:
118
95
  Self with OR condition applied
119
96
  """
120
97
  # Create a temporary clone to capture the condition
121
- original_expr = self._expression
98
+ original_expr = self.get_expression()
122
99
 
123
100
  # Apply the where method to get the condition
124
101
  where_method(*args, **kwargs)
125
102
 
126
103
  # Get the last condition added by extracting it from the modified expression
127
- if isinstance(self._expression, (exp.Select, exp.Update, exp.Delete)) and original_expr != self._expression:
128
- last_where = self._expression.find(exp.Where)
104
+ current_expr = self.get_expression()
105
+ if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)) and original_expr != current_expr:
106
+ last_where = current_expr.find(exp.Where)
129
107
  if last_where and last_where.this:
130
108
  condition = last_where.this
131
109
  # Restore original expression
132
- self._expression = original_expr
110
+ if original_expr is not None:
111
+ self.set_expression(original_expr)
133
112
  # Apply as OR
134
113
  return self.or_where(condition)
135
114
 
@@ -236,7 +215,7 @@ class WhereClauseMixin:
236
215
  column_name_raw, operator, value = condition
237
216
  operator = str(operator).upper()
238
217
  column_exp = parse_column_expression(column_name_raw)
239
- column_name = _extract_column_name(column_name_raw)
218
+ column_name = extract_column_name(column_name_raw)
240
219
 
241
220
  # Simple operators that use direct parameterization
242
221
  simple_operators = {
@@ -299,16 +278,17 @@ class WhereClauseMixin:
299
278
  Returns:
300
279
  The current builder instance for method chaining.
301
280
  """
302
- if self.__class__.__name__ == "Update" and not isinstance(self._expression, exp.Update):
281
+ current_expr = self.get_expression()
282
+ if self.__class__.__name__ == "Update" and not isinstance(current_expr, exp.Update):
303
283
  msg = "Cannot add WHERE clause to non-UPDATE expression"
304
284
  raise SQLBuilderError(msg)
305
285
 
306
286
  builder = cast("SQLBuilderProtocol", self)
307
- if builder._expression is None:
287
+ if current_expr is None:
308
288
  msg = "Cannot add WHERE clause: expression is not initialized."
309
289
  raise SQLBuilderError(msg)
310
290
 
311
- if isinstance(builder._expression, exp.Delete) and not builder._expression.args.get("this"):
291
+ if isinstance(current_expr, exp.Delete) and not current_expr.args.get("this"):
312
292
  msg = "WHERE clause requires a table to be set. Use from() to set the table first."
313
293
  raise SQLBuilderError(msg)
314
294
 
@@ -357,10 +337,11 @@ class WhereClauseMixin:
357
337
  else:
358
338
  where_expr = self._process_tuple_condition((condition, values[0]))
359
339
  # Process this condition and skip the rest
360
- if isinstance(builder._expression, (exp.Select, exp.Update, exp.Delete)):
361
- builder._expression = builder._expression.where(where_expr, copy=False)
340
+ if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)):
341
+ updated_expr = current_expr.where(where_expr, copy=False)
342
+ self.set_expression(updated_expr)
362
343
  else:
363
- msg = f"WHERE clause not supported for {type(builder._expression).__name__}"
344
+ msg = f"WHERE clause not supported for {type(current_expr).__name__}"
364
345
  raise SQLBuilderError(msg)
365
346
  return self
366
347
  else:
@@ -400,10 +381,11 @@ class WhereClauseMixin:
400
381
  msg = f"Unsupported condition type: {type(condition).__name__}"
401
382
  raise SQLBuilderError(msg)
402
383
 
403
- if isinstance(builder._expression, (exp.Select, exp.Update, exp.Delete)):
404
- builder._expression = builder._expression.where(where_expr, copy=False)
384
+ if isinstance(current_expr, (exp.Select, exp.Update, exp.Delete)):
385
+ updated_expr = current_expr.where(where_expr, copy=False)
386
+ self.set_expression(updated_expr)
405
387
  else:
406
- msg = f"WHERE clause not supported for {type(builder._expression).__name__}"
388
+ msg = f"WHERE clause not supported for {type(current_expr).__name__}"
407
389
  raise SQLBuilderError(msg)
408
390
  return self
409
391
 
@@ -448,7 +430,7 @@ class WhereClauseMixin:
448
430
  def where_between(self, column: Union[str, exp.Column], low: Any, high: Any) -> Self:
449
431
  """Add WHERE column BETWEEN low AND high clause."""
450
432
  builder = cast("SQLBuilderProtocol", self)
451
- column_name = _extract_column_name(column)
433
+ column_name = extract_column_name(column)
452
434
  low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
453
435
  high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
454
436
  _, low_param = builder.add_parameter(low, name=low_param)
@@ -460,7 +442,7 @@ class WhereClauseMixin:
460
442
  def where_like(self, column: Union[str, exp.Column], pattern: str, escape: Optional[str] = None) -> Self:
461
443
  """Add WHERE column LIKE pattern clause."""
462
444
  builder = cast("SQLBuilderProtocol", self)
463
- column_name = _extract_column_name(column)
445
+ column_name = extract_column_name(column)
464
446
  param_name = builder._generate_unique_parameter_name(column_name)
465
447
  _, param_name = builder.add_parameter(pattern, name=param_name)
466
448
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
@@ -519,7 +501,7 @@ class WhereClauseMixin:
519
501
  if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
520
502
  msg = "Unsupported type for 'values' in WHERE IN"
521
503
  raise SQLBuilderError(msg)
522
- column_name = _extract_column_name(column)
504
+ column_name = extract_column_name(column)
523
505
  parameters = []
524
506
  for i, v in enumerate(values):
525
507
  if len(values) == 1:
@@ -548,7 +530,7 @@ class WhereClauseMixin:
548
530
  if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
549
531
  msg = "Values for where_not_in must be a non-string iterable or subquery."
550
532
  raise SQLBuilderError(msg)
551
- column_name = _extract_column_name(column)
533
+ column_name = extract_column_name(column)
552
534
  parameters = []
553
535
  for i, v in enumerate(values):
554
536
  if len(values) == 1:
@@ -638,7 +620,7 @@ class WhereClauseMixin:
638
620
  if not is_iterable_parameters(values) or isinstance(values, bytes):
639
621
  msg = "Unsupported type for 'values' in WHERE ANY"
640
622
  raise SQLBuilderError(msg)
641
- column_name = _extract_column_name(column)
623
+ column_name = extract_column_name(column)
642
624
  parameters = []
643
625
  for i, v in enumerate(values):
644
626
  if len(values) == 1:
@@ -678,7 +660,7 @@ class WhereClauseMixin:
678
660
  if not is_iterable_parameters(values) or isinstance(values, bytes):
679
661
  msg = "Unsupported type for 'values' in WHERE NOT ANY"
680
662
  raise SQLBuilderError(msg)
681
- column_name = _extract_column_name(column)
663
+ column_name = extract_column_name(column)
682
664
  parameters = []
683
665
  for i, v in enumerate(values):
684
666
  if len(values) == 1:
@@ -975,7 +957,7 @@ class WhereClauseMixin:
975
957
  if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
976
958
  msg = "Unsupported type for 'values' in OR WHERE IN"
977
959
  raise SQLBuilderError(msg)
978
- column_name = _extract_column_name(column)
960
+ column_name = extract_column_name(column)
979
961
  parameters = []
980
962
  for i, v in enumerate(values):
981
963
  if len(values) == 1:
@@ -990,7 +972,7 @@ class WhereClauseMixin:
990
972
  def or_where_like(self, column: Union[str, exp.Column], pattern: str, escape: Optional[str] = None) -> Self:
991
973
  """Add OR column LIKE pattern clause."""
992
974
  builder = cast("SQLBuilderProtocol", self)
993
- column_name = _extract_column_name(column)
975
+ column_name = extract_column_name(column)
994
976
  param_name = builder._generate_unique_parameter_name(column_name)
995
977
  _, param_name = builder.add_parameter(pattern, name=param_name)
996
978
  col_expr = parse_column_expression(column) if not isinstance(column, exp.Column) else column
@@ -1044,7 +1026,7 @@ class WhereClauseMixin:
1044
1026
  def or_where_between(self, column: Union[str, exp.Column], low: Any, high: Any) -> Self:
1045
1027
  """Add OR column BETWEEN low AND high clause."""
1046
1028
  builder = cast("SQLBuilderProtocol", self)
1047
- column_name = _extract_column_name(column)
1029
+ column_name = extract_column_name(column)
1048
1030
  low_param = builder._generate_unique_parameter_name(f"{column_name}_low")
1049
1031
  high_param = builder._generate_unique_parameter_name(f"{column_name}_high")
1050
1032
  _, low_param = builder.add_parameter(low, name=low_param)
@@ -1092,7 +1074,7 @@ class WhereClauseMixin:
1092
1074
  if not is_iterable_parameters(values) or isinstance(values, (str, bytes)):
1093
1075
  msg = "Values for or_where_not_in must be a non-string iterable or subquery."
1094
1076
  raise SQLBuilderError(msg)
1095
- column_name = _extract_column_name(column)
1077
+ column_name = extract_column_name(column)
1096
1078
  parameters = []
1097
1079
  for i, v in enumerate(values):
1098
1080
  if len(values) == 1:
@@ -1216,7 +1198,7 @@ class WhereClauseMixin:
1216
1198
  if not is_iterable_parameters(values) or isinstance(values, bytes):
1217
1199
  msg = "Unsupported type for 'values' in OR WHERE ANY"
1218
1200
  raise SQLBuilderError(msg)
1219
- column_name = _extract_column_name(column)
1201
+ column_name = extract_column_name(column)
1220
1202
  parameters = []
1221
1203
  for i, v in enumerate(values):
1222
1204
  if len(values) == 1:
@@ -1272,7 +1254,7 @@ class WhereClauseMixin:
1272
1254
  if not is_iterable_parameters(values) or isinstance(values, bytes):
1273
1255
  msg = "Unsupported type for 'values' in OR WHERE NOT ANY"
1274
1256
  raise SQLBuilderError(msg)
1275
- column_name = _extract_column_name(column)
1257
+ column_name = extract_column_name(column)
1276
1258
  parameters = []
1277
1259
  for i, v in enumerate(values):
1278
1260
  if len(values) == 1:
@@ -1292,7 +1274,9 @@ class HavingClauseMixin:
1292
1274
 
1293
1275
  __slots__ = ()
1294
1276
 
1295
- _expression: Optional[exp.Expression]
1277
+ # Type annotations for PyRight - these will be provided by the base class
1278
+ def get_expression(self) -> Optional[exp.Expression]: ...
1279
+ def set_expression(self, expression: exp.Expression) -> None: ...
1296
1280
 
1297
1281
  def having(self, condition: Union[str, exp.Expression]) -> Self:
1298
1282
  """Add HAVING clause.
@@ -1306,11 +1290,15 @@ class HavingClauseMixin:
1306
1290
  Returns:
1307
1291
  The current builder instance for method chaining.
1308
1292
  """
1309
- if self._expression is None:
1310
- self._expression = exp.Select()
1311
- if not isinstance(self._expression, exp.Select):
1293
+ current_expr = self.get_expression()
1294
+ if current_expr is None:
1295
+ self.set_expression(exp.Select())
1296
+ current_expr = self.get_expression()
1297
+
1298
+ if not isinstance(current_expr, exp.Select):
1312
1299
  msg = "Cannot add HAVING to a non-SELECT expression."
1313
1300
  raise SQLBuilderError(msg)
1314
1301
  having_expr = exp.condition(condition) if isinstance(condition, str) else condition
1315
- self._expression = self._expression.having(having_expr, copy=False)
1302
+ updated_expr = current_expr.having(having_expr, copy=False)
1303
+ self.set_expression(updated_expr)
1316
1304
  return self
sqlspec/core/__init__.py CHANGED
@@ -90,7 +90,7 @@ Example Usage:
90
90
  """
91
91
 
92
92
  from sqlspec.core import filters
93
- from sqlspec.core.cache import CacheConfig, CacheStats, UnifiedCache, get_statement_cache
93
+ from sqlspec.core.cache import CacheConfig, CacheStats, MultiLevelCache, UnifiedCache, get_cache
94
94
  from sqlspec.core.compiler import OperationType, SQLProcessor
95
95
  from sqlspec.core.filters import StatementFilter
96
96
  from sqlspec.core.hashing import (
@@ -115,6 +115,7 @@ __all__ = (
115
115
  "ArrowResult",
116
116
  "CacheConfig",
117
117
  "CacheStats",
118
+ "MultiLevelCache",
118
119
  "OperationType",
119
120
  "ParameterConverter",
120
121
  "ParameterProcessor",
@@ -129,7 +130,7 @@ __all__ = (
129
130
  "TypedParameter",
130
131
  "UnifiedCache",
131
132
  "filters",
132
- "get_statement_cache",
133
+ "get_cache",
133
134
  "hash_expression",
134
135
  "hash_expression_node",
135
136
  "hash_optimized_expression",