sqlspec 0.17.0__py3-none-any.whl → 0.18.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 (80) hide show
  1. sqlspec/__init__.py +1 -1
  2. sqlspec/_sql.py +188 -234
  3. sqlspec/adapters/adbc/config.py +24 -30
  4. sqlspec/adapters/adbc/driver.py +42 -61
  5. sqlspec/adapters/aiosqlite/config.py +5 -10
  6. sqlspec/adapters/aiosqlite/driver.py +9 -25
  7. sqlspec/adapters/aiosqlite/pool.py +43 -35
  8. sqlspec/adapters/asyncmy/config.py +10 -7
  9. sqlspec/adapters/asyncmy/driver.py +18 -39
  10. sqlspec/adapters/asyncpg/config.py +4 -0
  11. sqlspec/adapters/asyncpg/driver.py +32 -79
  12. sqlspec/adapters/bigquery/config.py +12 -65
  13. sqlspec/adapters/bigquery/driver.py +39 -133
  14. sqlspec/adapters/duckdb/config.py +11 -15
  15. sqlspec/adapters/duckdb/driver.py +61 -85
  16. sqlspec/adapters/duckdb/pool.py +2 -5
  17. sqlspec/adapters/oracledb/_types.py +8 -1
  18. sqlspec/adapters/oracledb/config.py +55 -38
  19. sqlspec/adapters/oracledb/driver.py +35 -92
  20. sqlspec/adapters/oracledb/migrations.py +257 -0
  21. sqlspec/adapters/psqlpy/config.py +13 -9
  22. sqlspec/adapters/psqlpy/driver.py +28 -103
  23. sqlspec/adapters/psycopg/config.py +9 -5
  24. sqlspec/adapters/psycopg/driver.py +107 -175
  25. sqlspec/adapters/sqlite/config.py +7 -5
  26. sqlspec/adapters/sqlite/driver.py +37 -73
  27. sqlspec/adapters/sqlite/pool.py +3 -12
  28. sqlspec/base.py +1 -8
  29. sqlspec/builder/__init__.py +1 -1
  30. sqlspec/builder/_base.py +34 -20
  31. sqlspec/builder/_column.py +5 -1
  32. sqlspec/builder/_ddl.py +407 -183
  33. sqlspec/builder/_expression_wrappers.py +46 -0
  34. sqlspec/builder/_insert.py +2 -4
  35. sqlspec/builder/_update.py +5 -5
  36. sqlspec/builder/mixins/_insert_operations.py +26 -6
  37. sqlspec/builder/mixins/_merge_operations.py +1 -1
  38. sqlspec/builder/mixins/_order_limit_operations.py +16 -4
  39. sqlspec/builder/mixins/_select_operations.py +3 -7
  40. sqlspec/builder/mixins/_update_operations.py +4 -4
  41. sqlspec/config.py +32 -13
  42. sqlspec/core/__init__.py +89 -14
  43. sqlspec/core/cache.py +57 -104
  44. sqlspec/core/compiler.py +57 -112
  45. sqlspec/core/filters.py +1 -21
  46. sqlspec/core/hashing.py +13 -47
  47. sqlspec/core/parameters.py +272 -261
  48. sqlspec/core/result.py +12 -27
  49. sqlspec/core/splitter.py +17 -21
  50. sqlspec/core/statement.py +150 -159
  51. sqlspec/driver/_async.py +2 -15
  52. sqlspec/driver/_common.py +16 -95
  53. sqlspec/driver/_sync.py +2 -15
  54. sqlspec/driver/mixins/_result_tools.py +8 -29
  55. sqlspec/driver/mixins/_sql_translator.py +6 -8
  56. sqlspec/exceptions.py +1 -2
  57. sqlspec/loader.py +43 -115
  58. sqlspec/migrations/__init__.py +1 -1
  59. sqlspec/migrations/base.py +34 -45
  60. sqlspec/migrations/commands.py +34 -15
  61. sqlspec/migrations/loaders.py +1 -1
  62. sqlspec/migrations/runner.py +104 -19
  63. sqlspec/migrations/tracker.py +49 -2
  64. sqlspec/protocols.py +13 -6
  65. sqlspec/storage/__init__.py +4 -4
  66. sqlspec/storage/backends/fsspec.py +5 -6
  67. sqlspec/storage/backends/obstore.py +7 -8
  68. sqlspec/storage/registry.py +3 -3
  69. sqlspec/utils/__init__.py +2 -2
  70. sqlspec/utils/logging.py +6 -10
  71. sqlspec/utils/sync_tools.py +27 -4
  72. sqlspec/utils/text.py +6 -1
  73. {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/METADATA +1 -1
  74. sqlspec-0.18.0.dist-info/RECORD +138 -0
  75. sqlspec/builder/_ddl_utils.py +0 -103
  76. sqlspec-0.17.0.dist-info/RECORD +0 -137
  77. {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/WHEEL +0 -0
  78. {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/entry_points.txt +0 -0
  79. {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/licenses/LICENSE +0 -0
  80. {sqlspec-0.17.0.dist-info → sqlspec-0.18.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/core/hashing.py CHANGED
@@ -1,9 +1,7 @@
1
1
  """Statement hashing utilities for cache key generation.
2
2
 
3
- This module provides centralized hashing logic for SQL statements,
4
- including expressions, parameters, filters, and complete SQL objects.
5
-
6
- Also supports fine-grained AST sub-expression caching.
3
+ Provides hashing functions for SQL statements, expressions, parameters,
4
+ filters, and AST sub-expressions.
7
5
  """
8
6
 
9
7
  from typing import TYPE_CHECKING, Any, Optional
@@ -26,14 +24,14 @@ __all__ = (
26
24
 
27
25
 
28
26
  def hash_expression(expr: Optional[exp.Expression], _seen: Optional[set[int]] = None) -> int:
29
- """Generate deterministic hash from AST structure.
27
+ """Generate hash from AST structure.
30
28
 
31
29
  Args:
32
30
  expr: SQLGlot Expression to hash
33
31
  _seen: Set of seen object IDs to handle circular references
34
32
 
35
33
  Returns:
36
- Deterministic hash of the AST structure
34
+ Hash of the AST structure
37
35
  """
38
36
  if expr is None:
39
37
  return hash(None)
@@ -47,7 +45,6 @@ def hash_expression(expr: Optional[exp.Expression], _seen: Optional[set[int]] =
47
45
 
48
46
  _seen.add(expr_id)
49
47
 
50
- # Build hash from type and args
51
48
  components: list[Any] = [type(expr).__name__]
52
49
 
53
50
  for key, value in sorted(expr.args.items()):
@@ -57,14 +54,14 @@ def hash_expression(expr: Optional[exp.Expression], _seen: Optional[set[int]] =
57
54
 
58
55
 
59
56
  def _hash_value(value: Any, _seen: set[int]) -> int:
60
- """Hash different value types consistently.
57
+ """Hash different value types.
61
58
 
62
59
  Args:
63
60
  value: Value to hash (can be Expression, list, dict, or primitive)
64
61
  _seen: Set of seen object IDs to handle circular references
65
62
 
66
63
  Returns:
67
- Deterministic hash of the value
64
+ Hash of the value
68
65
  """
69
66
  if isinstance(value, exp.Expression):
70
67
  return hash_expression(value, _seen)
@@ -75,7 +72,7 @@ def _hash_value(value: Any, _seen: set[int]) -> int:
75
72
  return hash(tuple(items))
76
73
  if isinstance(value, tuple):
77
74
  return hash(tuple(_hash_value(v, _seen) for v in value))
78
- # Primitives: str, int, bool, None, etc.
75
+
79
76
  return hash(value)
80
77
 
81
78
 
@@ -96,7 +93,6 @@ def hash_parameters(
96
93
  """
97
94
  param_hash = 0
98
95
 
99
- # Hash positional parameters
100
96
  if positional_parameters:
101
97
  from sqlspec.core.parameters import TypedParameter
102
98
 
@@ -108,25 +104,20 @@ def hash_parameters(
108
104
  else:
109
105
  hashable_parameters.append((param.value, param.original_type))
110
106
  elif isinstance(param, (list, dict)):
111
- # Convert unhashable types to hashable representations
112
107
  hashable_parameters.append((repr(param), "unhashable"))
113
108
  else:
114
- # Check if param itself contains unhashable types
115
109
  try:
116
110
  hash(param)
117
111
  hashable_parameters.append((param, "primitive"))
118
112
  except TypeError:
119
- # If unhashable, convert to string representation
120
113
  hashable_parameters.append((repr(param), "unhashable_repr"))
121
114
 
122
115
  param_hash ^= hash(tuple(hashable_parameters))
123
116
 
124
117
  if named_parameters:
125
- # Handle unhashable types in named parameters
126
118
  hashable_items = []
127
119
  for key, value in sorted(named_parameters.items()):
128
120
  if is_typed_parameter(value):
129
- # For TypedParameter, hash its value with type info
130
121
  if isinstance(value.value, (list, dict)):
131
122
  hashable_items.append((key, (repr(value.value), value.original_type)))
132
123
  else:
@@ -137,13 +128,10 @@ def hash_parameters(
137
128
  hashable_items.append((key, (value, "primitive")))
138
129
  param_hash ^= hash(tuple(hashable_items))
139
130
 
140
- # Hash original parameters (important for execute_many)
141
131
  if original_parameters is not None:
142
132
  if isinstance(original_parameters, list):
143
- # For execute_many, hash the count and first few items to avoid
144
133
  param_hash ^= hash(("original_count", len(original_parameters)))
145
134
  if original_parameters:
146
- # Hash first 3 items as representatives
147
135
  sample_size = min(3, len(original_parameters))
148
136
  sample_hash = hash(repr(original_parameters[:sample_size]))
149
137
  param_hash ^= hash(("original_sample", sample_hash))
@@ -172,14 +160,10 @@ def hash_filters(filters: Optional[list["StatementFilter"]] = None) -> int:
172
160
  if not filters:
173
161
  return 0
174
162
 
175
- # Use class names and any hashable attributes
176
163
  filter_components = []
177
164
  for f in filters:
178
- # Use class name as primary identifier
179
165
  components: list[Any] = [f.__class__.__name__]
180
166
 
181
- # Add any hashable attributes if available
182
- # Use getattr with default instead of hasattr for mypyc compatibility
183
167
  filter_dict = getattr(f, "__dict__", None)
184
168
  if filter_dict is not None:
185
169
  for key, value in sorted(filter_dict.items()):
@@ -191,10 +175,7 @@ def hash_filters(filters: Optional[list["StatementFilter"]] = None) -> int:
191
175
 
192
176
 
193
177
  def hash_sql_statement(statement: "SQL") -> str:
194
- """Generate a complete cache key for a SQL statement.
195
-
196
- This centralizes all the complex hashing logic that was previously
197
- scattered across different parts of the codebase.
178
+ """Generate cache key for a SQL statement.
198
179
 
199
180
  Args:
200
181
  statement: SQL statement object
@@ -204,23 +185,19 @@ def hash_sql_statement(statement: "SQL") -> str:
204
185
  """
205
186
  from sqlspec.utils.type_guards import is_expression
206
187
 
207
- # Hash the expression or raw SQL
208
188
  if is_expression(statement._statement):
209
189
  expr_hash = hash_expression(statement._statement)
210
190
  else:
211
191
  expr_hash = hash(statement._raw_sql)
212
192
 
213
- # Hash all parameters
214
193
  param_hash = hash_parameters(
215
194
  positional_parameters=statement._positional_parameters,
216
195
  named_parameters=statement._named_parameters,
217
196
  original_parameters=statement._original_parameters,
218
197
  )
219
198
 
220
- # Hash filters
221
199
  filter_hash = hash_filters(statement._filters)
222
200
 
223
- # Combine with other state
224
201
  state_components = [
225
202
  expr_hash,
226
203
  param_hash,
@@ -234,10 +211,7 @@ def hash_sql_statement(statement: "SQL") -> str:
234
211
 
235
212
 
236
213
  def hash_expression_node(node: exp.Expression, include_children: bool = True, dialect: Optional[str] = None) -> str:
237
- """Generate a cache key for an individual expression node.
238
-
239
- This is used for sub-expression caching where we want to cache
240
- frequently used SQL fragments like complex JOIN clauses or WHERE conditions.
214
+ """Generate cache key for an expression node.
241
215
 
242
216
  Args:
243
217
  node: The expression node to hash
@@ -248,14 +222,11 @@ def hash_expression_node(node: exp.Expression, include_children: bool = True, di
248
222
  Cache key string for the expression node
249
223
  """
250
224
  if include_children:
251
- # Full structural hash including all children
252
225
  node_hash = hash_expression(node)
253
226
  else:
254
- # Shallow hash - just the node type and immediate args
255
227
  components: list[Any] = [type(node).__name__]
256
228
  for key, value in sorted(node.args.items()):
257
229
  if not isinstance(value, (list, exp.Expression)):
258
- # Only include primitive values, not child expressions
259
230
  components.extend((key, hash(value)))
260
231
  node_hash = hash(tuple(components))
261
232
 
@@ -269,11 +240,10 @@ def hash_optimized_expression(
269
240
  schema: Optional[dict[str, Any]] = None,
270
241
  optimizer_settings: Optional[dict[str, Any]] = None,
271
242
  ) -> str:
272
- """Generate a cache key for optimized expressions.
243
+ """Generate cache key for optimized expressions.
273
244
 
274
- This creates a unique key that captures the expression structure,
275
- dialect, schema context, and optimizer settings to ensure we only
276
- reuse optimized expressions when all context matches.
245
+ Creates a key that includes expression structure, dialect, schema
246
+ context, and optimizer settings.
277
247
 
278
248
  Args:
279
249
  expr: The unoptimized expression
@@ -284,13 +254,11 @@ def hash_optimized_expression(
284
254
  Returns:
285
255
  Cache key string for the optimized expression
286
256
  """
287
- # Base expression hash
257
+
288
258
  expr_hash = hash_expression(expr)
289
259
 
290
- # Schema hash - simplified representation
291
260
  schema_hash = 0
292
261
  if schema:
293
- # Hash table names and column counts to avoid deep schema hashing
294
262
  schema_items = []
295
263
  for table_name, table_schema in sorted(schema.items()):
296
264
  if isinstance(table_schema, dict):
@@ -299,12 +267,10 @@ def hash_optimized_expression(
299
267
  schema_items.append((table_name, hash("unknown")))
300
268
  schema_hash = hash(tuple(schema_items))
301
269
 
302
- # Optimizer settings hash
303
270
  settings_hash = 0
304
271
  if optimizer_settings:
305
272
  settings_items = sorted(optimizer_settings.items())
306
273
  settings_hash = hash(tuple(settings_items))
307
274
 
308
- # Combine all components
309
275
  components = (expr_hash, dialect, schema_hash, settings_hash)
310
276
  return f"opt:{hash(components)}"