sqlspec 0.14.0__py3-none-any.whl → 0.15.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 (158) hide show
  1. sqlspec/__init__.py +50 -25
  2. sqlspec/__main__.py +12 -0
  3. sqlspec/__metadata__.py +1 -3
  4. sqlspec/_serialization.py +1 -2
  5. sqlspec/_sql.py +256 -120
  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 +115 -248
  10. sqlspec/adapters/adbc/driver.py +462 -353
  11. sqlspec/adapters/aiosqlite/__init__.py +18 -3
  12. sqlspec/adapters/aiosqlite/_types.py +13 -0
  13. sqlspec/adapters/aiosqlite/config.py +199 -129
  14. sqlspec/adapters/aiosqlite/driver.py +230 -269
  15. sqlspec/adapters/asyncmy/__init__.py +18 -3
  16. sqlspec/adapters/asyncmy/_types.py +12 -0
  17. sqlspec/adapters/asyncmy/config.py +80 -168
  18. sqlspec/adapters/asyncmy/driver.py +260 -225
  19. sqlspec/adapters/asyncpg/__init__.py +19 -4
  20. sqlspec/adapters/asyncpg/_types.py +17 -0
  21. sqlspec/adapters/asyncpg/config.py +82 -181
  22. sqlspec/adapters/asyncpg/driver.py +285 -383
  23. sqlspec/adapters/bigquery/__init__.py +17 -3
  24. sqlspec/adapters/bigquery/_types.py +12 -0
  25. sqlspec/adapters/bigquery/config.py +191 -258
  26. sqlspec/adapters/bigquery/driver.py +474 -646
  27. sqlspec/adapters/duckdb/__init__.py +14 -3
  28. sqlspec/adapters/duckdb/_types.py +12 -0
  29. sqlspec/adapters/duckdb/config.py +415 -351
  30. sqlspec/adapters/duckdb/driver.py +343 -413
  31. sqlspec/adapters/oracledb/__init__.py +19 -5
  32. sqlspec/adapters/oracledb/_types.py +14 -0
  33. sqlspec/adapters/oracledb/config.py +123 -379
  34. sqlspec/adapters/oracledb/driver.py +507 -560
  35. sqlspec/adapters/psqlpy/__init__.py +13 -3
  36. sqlspec/adapters/psqlpy/_types.py +11 -0
  37. sqlspec/adapters/psqlpy/config.py +93 -254
  38. sqlspec/adapters/psqlpy/driver.py +505 -234
  39. sqlspec/adapters/psycopg/__init__.py +19 -5
  40. sqlspec/adapters/psycopg/_types.py +17 -0
  41. sqlspec/adapters/psycopg/config.py +143 -403
  42. sqlspec/adapters/psycopg/driver.py +706 -872
  43. sqlspec/adapters/sqlite/__init__.py +14 -3
  44. sqlspec/adapters/sqlite/_types.py +11 -0
  45. sqlspec/adapters/sqlite/config.py +202 -118
  46. sqlspec/adapters/sqlite/driver.py +264 -303
  47. sqlspec/base.py +105 -9
  48. sqlspec/{statement/builder → builder}/__init__.py +12 -14
  49. sqlspec/{statement/builder → builder}/_base.py +120 -55
  50. sqlspec/{statement/builder → builder}/_column.py +17 -6
  51. sqlspec/{statement/builder → builder}/_ddl.py +46 -79
  52. sqlspec/{statement/builder → builder}/_ddl_utils.py +5 -10
  53. sqlspec/{statement/builder → builder}/_delete.py +6 -25
  54. sqlspec/{statement/builder → builder}/_insert.py +6 -64
  55. sqlspec/builder/_merge.py +56 -0
  56. sqlspec/{statement/builder → builder}/_parsing_utils.py +3 -10
  57. sqlspec/{statement/builder → builder}/_select.py +11 -56
  58. sqlspec/{statement/builder → builder}/_update.py +12 -18
  59. sqlspec/{statement/builder → builder}/mixins/__init__.py +10 -14
  60. sqlspec/{statement/builder → builder}/mixins/_cte_and_set_ops.py +48 -59
  61. sqlspec/{statement/builder → builder}/mixins/_insert_operations.py +22 -16
  62. sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
  63. sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +3 -5
  64. sqlspec/{statement/builder → builder}/mixins/_order_limit_operations.py +3 -3
  65. sqlspec/{statement/builder → builder}/mixins/_pivot_operations.py +4 -8
  66. sqlspec/{statement/builder → builder}/mixins/_select_operations.py +21 -36
  67. sqlspec/{statement/builder → builder}/mixins/_update_operations.py +3 -14
  68. sqlspec/{statement/builder → builder}/mixins/_where_clause.py +52 -79
  69. sqlspec/cli.py +4 -5
  70. sqlspec/config.py +180 -133
  71. sqlspec/core/__init__.py +63 -0
  72. sqlspec/core/cache.py +873 -0
  73. sqlspec/core/compiler.py +396 -0
  74. sqlspec/core/filters.py +828 -0
  75. sqlspec/core/hashing.py +310 -0
  76. sqlspec/core/parameters.py +1209 -0
  77. sqlspec/core/result.py +664 -0
  78. sqlspec/{statement → core}/splitter.py +321 -191
  79. sqlspec/core/statement.py +651 -0
  80. sqlspec/driver/__init__.py +7 -10
  81. sqlspec/driver/_async.py +387 -176
  82. sqlspec/driver/_common.py +527 -289
  83. sqlspec/driver/_sync.py +390 -172
  84. sqlspec/driver/mixins/__init__.py +2 -19
  85. sqlspec/driver/mixins/_result_tools.py +168 -0
  86. sqlspec/driver/mixins/_sql_translator.py +6 -3
  87. sqlspec/exceptions.py +5 -252
  88. sqlspec/extensions/aiosql/adapter.py +93 -96
  89. sqlspec/extensions/litestar/config.py +0 -1
  90. sqlspec/extensions/litestar/handlers.py +15 -26
  91. sqlspec/extensions/litestar/plugin.py +16 -14
  92. sqlspec/extensions/litestar/providers.py +17 -52
  93. sqlspec/loader.py +424 -105
  94. sqlspec/migrations/__init__.py +12 -0
  95. sqlspec/migrations/base.py +92 -68
  96. sqlspec/migrations/commands.py +24 -106
  97. sqlspec/migrations/loaders.py +402 -0
  98. sqlspec/migrations/runner.py +49 -51
  99. sqlspec/migrations/tracker.py +31 -44
  100. sqlspec/migrations/utils.py +64 -24
  101. sqlspec/protocols.py +7 -183
  102. sqlspec/storage/__init__.py +1 -1
  103. sqlspec/storage/backends/base.py +37 -40
  104. sqlspec/storage/backends/fsspec.py +136 -112
  105. sqlspec/storage/backends/obstore.py +138 -160
  106. sqlspec/storage/capabilities.py +5 -4
  107. sqlspec/storage/registry.py +57 -106
  108. sqlspec/typing.py +136 -115
  109. sqlspec/utils/__init__.py +2 -3
  110. sqlspec/utils/correlation.py +0 -3
  111. sqlspec/utils/deprecation.py +6 -6
  112. sqlspec/utils/fixtures.py +6 -6
  113. sqlspec/utils/logging.py +0 -2
  114. sqlspec/utils/module_loader.py +7 -12
  115. sqlspec/utils/singleton.py +0 -1
  116. sqlspec/utils/sync_tools.py +16 -37
  117. sqlspec/utils/text.py +12 -51
  118. sqlspec/utils/type_guards.py +443 -232
  119. {sqlspec-0.14.0.dist-info → sqlspec-0.15.0.dist-info}/METADATA +7 -2
  120. sqlspec-0.15.0.dist-info/RECORD +134 -0
  121. sqlspec-0.15.0.dist-info/entry_points.txt +2 -0
  122. sqlspec/driver/connection.py +0 -207
  123. sqlspec/driver/mixins/_cache.py +0 -114
  124. sqlspec/driver/mixins/_csv_writer.py +0 -91
  125. sqlspec/driver/mixins/_pipeline.py +0 -508
  126. sqlspec/driver/mixins/_query_tools.py +0 -796
  127. sqlspec/driver/mixins/_result_utils.py +0 -138
  128. sqlspec/driver/mixins/_storage.py +0 -912
  129. sqlspec/driver/mixins/_type_coercion.py +0 -128
  130. sqlspec/driver/parameters.py +0 -138
  131. sqlspec/statement/__init__.py +0 -21
  132. sqlspec/statement/builder/_merge.py +0 -95
  133. sqlspec/statement/cache.py +0 -50
  134. sqlspec/statement/filters.py +0 -625
  135. sqlspec/statement/parameters.py +0 -996
  136. sqlspec/statement/pipelines/__init__.py +0 -210
  137. sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
  138. sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
  139. sqlspec/statement/pipelines/context.py +0 -115
  140. sqlspec/statement/pipelines/transformers/__init__.py +0 -7
  141. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
  142. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
  143. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
  144. sqlspec/statement/pipelines/validators/__init__.py +0 -23
  145. sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
  146. sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
  147. sqlspec/statement/pipelines/validators/_performance.py +0 -714
  148. sqlspec/statement/pipelines/validators/_security.py +0 -967
  149. sqlspec/statement/result.py +0 -435
  150. sqlspec/statement/sql.py +0 -1774
  151. sqlspec/utils/cached_property.py +0 -25
  152. sqlspec/utils/statement_hashing.py +0 -203
  153. sqlspec-0.14.0.dist-info/RECORD +0 -143
  154. sqlspec-0.14.0.dist-info/entry_points.txt +0 -2
  155. /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
  156. {sqlspec-0.14.0.dist-info → sqlspec-0.15.0.dist-info}/WHEEL +0 -0
  157. {sqlspec-0.14.0.dist-info → sqlspec-0.15.0.dist-info}/licenses/LICENSE +0 -0
  158. {sqlspec-0.14.0.dist-info → sqlspec-0.15.0.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,310 @@
1
+ """Statement hashing utilities for cache key generation.
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.
7
+ """
8
+
9
+ from typing import TYPE_CHECKING, Any, Optional
10
+
11
+ from sqlglot import exp
12
+
13
+ from sqlspec.utils.type_guards import is_typed_parameter
14
+
15
+ if TYPE_CHECKING:
16
+ from sqlspec.core.filters import StatementFilter
17
+ from sqlspec.core.statement import SQL
18
+
19
+ __all__ = (
20
+ "hash_expression",
21
+ "hash_expression_node",
22
+ "hash_optimized_expression",
23
+ "hash_parameters",
24
+ "hash_sql_statement",
25
+ )
26
+
27
+
28
+ def hash_expression(expr: Optional[exp.Expression], _seen: Optional[set[int]] = None) -> int:
29
+ """Generate deterministic hash from AST structure.
30
+
31
+ Args:
32
+ expr: SQLGlot Expression to hash
33
+ _seen: Set of seen object IDs to handle circular references
34
+
35
+ Returns:
36
+ Deterministic hash of the AST structure
37
+ """
38
+ if expr is None:
39
+ return hash(None)
40
+
41
+ if _seen is None:
42
+ _seen = set()
43
+
44
+ expr_id = id(expr)
45
+ if expr_id in _seen:
46
+ return hash(expr_id)
47
+
48
+ _seen.add(expr_id)
49
+
50
+ # Build hash from type and args
51
+ components: list[Any] = [type(expr).__name__]
52
+
53
+ for key, value in sorted(expr.args.items()):
54
+ components.extend((key, _hash_value(value, _seen)))
55
+
56
+ return hash(tuple(components))
57
+
58
+
59
+ def _hash_value(value: Any, _seen: set[int]) -> int:
60
+ """Hash different value types consistently.
61
+
62
+ Args:
63
+ value: Value to hash (can be Expression, list, dict, or primitive)
64
+ _seen: Set of seen object IDs to handle circular references
65
+
66
+ Returns:
67
+ Deterministic hash of the value
68
+ """
69
+ if isinstance(value, exp.Expression):
70
+ return hash_expression(value, _seen)
71
+ if isinstance(value, list):
72
+ return hash(tuple(_hash_value(v, _seen) for v in value))
73
+ if isinstance(value, dict):
74
+ items = sorted((k, _hash_value(v, _seen)) for k, v in value.items())
75
+ return hash(tuple(items))
76
+ if isinstance(value, tuple):
77
+ return hash(tuple(_hash_value(v, _seen) for v in value))
78
+ # Primitives: str, int, bool, None, etc.
79
+ return hash(value)
80
+
81
+
82
+ def hash_parameters(
83
+ positional_parameters: Optional[list[Any]] = None,
84
+ named_parameters: Optional[dict[str, Any]] = None,
85
+ original_parameters: Optional[Any] = None,
86
+ ) -> int:
87
+ """Generate hash for SQL parameters.
88
+
89
+ Args:
90
+ positional_parameters: List of positional parameters
91
+ named_parameters: Dictionary of named parameters
92
+ original_parameters: Original parameters (for execute_many)
93
+
94
+ Returns:
95
+ Combined hash of all parameters
96
+ """
97
+ param_hash = 0
98
+
99
+ # Hash positional parameters
100
+ if positional_parameters:
101
+ from sqlspec.core.parameters import TypedParameter
102
+
103
+ hashable_parameters: list[tuple[Any, Any]] = []
104
+ for param in positional_parameters:
105
+ if isinstance(param, TypedParameter):
106
+ if isinstance(param.value, (list, dict)):
107
+ hashable_parameters.append((repr(param.value), param.original_type))
108
+ else:
109
+ hashable_parameters.append((param.value, param.original_type))
110
+ elif isinstance(param, (list, dict)):
111
+ # Convert unhashable types to hashable representations
112
+ hashable_parameters.append((repr(param), "unhashable"))
113
+ else:
114
+ # Check if param itself contains unhashable types
115
+ try:
116
+ hash(param)
117
+ hashable_parameters.append((param, "primitive"))
118
+ except TypeError:
119
+ # If unhashable, convert to string representation
120
+ hashable_parameters.append((repr(param), "unhashable_repr"))
121
+
122
+ param_hash ^= hash(tuple(hashable_parameters))
123
+
124
+ if named_parameters:
125
+ # Handle unhashable types in named parameters
126
+ hashable_items = []
127
+ for key, value in sorted(named_parameters.items()):
128
+ if is_typed_parameter(value):
129
+ # For TypedParameter, hash its value with type info
130
+ if isinstance(value.value, (list, dict)):
131
+ hashable_items.append((key, (repr(value.value), value.original_type)))
132
+ else:
133
+ hashable_items.append((key, (value.value, value.original_type)))
134
+ elif isinstance(value, (list, dict)):
135
+ hashable_items.append((key, (repr(value), "unhashable")))
136
+ else:
137
+ hashable_items.append((key, (value, "primitive")))
138
+ param_hash ^= hash(tuple(hashable_items))
139
+
140
+ # Hash original parameters (important for execute_many)
141
+ if original_parameters is not None:
142
+ if isinstance(original_parameters, list):
143
+ # For execute_many, hash the count and first few items to avoid
144
+ param_hash ^= hash(("original_count", len(original_parameters)))
145
+ if original_parameters:
146
+ # Hash first 3 items as representatives
147
+ sample_size = min(3, len(original_parameters))
148
+ sample_hash = hash(repr(original_parameters[:sample_size]))
149
+ param_hash ^= hash(("original_sample", sample_hash))
150
+ else:
151
+ param_hash ^= hash(("original", repr(original_parameters)))
152
+
153
+ return param_hash
154
+
155
+
156
+ def _hash_filter_value(value: Any) -> int:
157
+ try:
158
+ return hash(value)
159
+ except TypeError:
160
+ return hash(repr(value))
161
+
162
+
163
+ def hash_filters(filters: Optional[list["StatementFilter"]] = None) -> int:
164
+ """Generate hash for statement filters.
165
+
166
+ Args:
167
+ filters: List of statement filters
168
+
169
+ Returns:
170
+ Hash of the filters
171
+ """
172
+ if not filters:
173
+ return 0
174
+
175
+ # Use class names and any hashable attributes
176
+ filter_components = []
177
+ for f in filters:
178
+ # Use class name as primary identifier
179
+ components: list[Any] = [f.__class__.__name__]
180
+
181
+ # Add any hashable attributes if available
182
+ # Use getattr with default instead of hasattr for mypyc compatibility
183
+ filter_dict = getattr(f, "__dict__", None)
184
+ if filter_dict is not None:
185
+ for key, value in sorted(filter_dict.items()):
186
+ components.append((key, _hash_filter_value(value)))
187
+
188
+ filter_components.append(tuple(components))
189
+
190
+ return hash(tuple(filter_components))
191
+
192
+
193
+ 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.
198
+
199
+ Args:
200
+ statement: SQL statement object
201
+
202
+ Returns:
203
+ Cache key string
204
+ """
205
+ from sqlspec.utils.type_guards import is_expression
206
+
207
+ # Hash the expression or raw SQL
208
+ if is_expression(statement._statement):
209
+ expr_hash = hash_expression(statement._statement)
210
+ else:
211
+ expr_hash = hash(statement._raw_sql)
212
+
213
+ # Hash all parameters
214
+ param_hash = hash_parameters(
215
+ positional_parameters=statement._positional_parameters,
216
+ named_parameters=statement._named_parameters,
217
+ original_parameters=statement._original_parameters,
218
+ )
219
+
220
+ # Hash filters
221
+ filter_hash = hash_filters(statement._filters)
222
+
223
+ # Combine with other state
224
+ state_components = [
225
+ expr_hash,
226
+ param_hash,
227
+ filter_hash,
228
+ hash(statement._dialect),
229
+ hash(statement._is_many),
230
+ hash(statement._is_script),
231
+ ]
232
+
233
+ return f"sql:{hash(tuple(state_components))}"
234
+
235
+
236
+ 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.
241
+
242
+ Args:
243
+ node: The expression node to hash
244
+ include_children: Whether to include child nodes in the hash
245
+ dialect: SQL dialect for context-aware hashing
246
+
247
+ Returns:
248
+ Cache key string for the expression node
249
+ """
250
+ if include_children:
251
+ # Full structural hash including all children
252
+ node_hash = hash_expression(node)
253
+ else:
254
+ # Shallow hash - just the node type and immediate args
255
+ components: list[Any] = [type(node).__name__]
256
+ for key, value in sorted(node.args.items()):
257
+ if not isinstance(value, (list, exp.Expression)):
258
+ # Only include primitive values, not child expressions
259
+ components.extend((key, hash(value)))
260
+ node_hash = hash(tuple(components))
261
+
262
+ dialect_part = f":{dialect}" if dialect else ""
263
+ return f"expr{dialect_part}:{node_hash}"
264
+
265
+
266
+ def hash_optimized_expression(
267
+ expr: exp.Expression,
268
+ dialect: str,
269
+ schema: Optional[dict[str, Any]] = None,
270
+ optimizer_settings: Optional[dict[str, Any]] = None,
271
+ ) -> str:
272
+ """Generate a cache key for optimized expressions.
273
+
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.
277
+
278
+ Args:
279
+ expr: The unoptimized expression
280
+ dialect: Target SQL dialect
281
+ schema: Schema information
282
+ optimizer_settings: Additional optimizer configuration
283
+
284
+ Returns:
285
+ Cache key string for the optimized expression
286
+ """
287
+ # Base expression hash
288
+ expr_hash = hash_expression(expr)
289
+
290
+ # Schema hash - simplified representation
291
+ schema_hash = 0
292
+ if schema:
293
+ # Hash table names and column counts to avoid deep schema hashing
294
+ schema_items = []
295
+ for table_name, table_schema in sorted(schema.items()):
296
+ if isinstance(table_schema, dict):
297
+ schema_items.append((table_name, len(table_schema)))
298
+ else:
299
+ schema_items.append((table_name, hash("unknown")))
300
+ schema_hash = hash(tuple(schema_items))
301
+
302
+ # Optimizer settings hash
303
+ settings_hash = 0
304
+ if optimizer_settings:
305
+ settings_items = sorted(optimizer_settings.items())
306
+ settings_hash = hash(tuple(settings_items))
307
+
308
+ # Combine all components
309
+ components = (expr_hash, dialect, schema_hash, settings_hash)
310
+ return f"opt:{hash(components)}"