-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathquery_planner_bidirectional.ex
More file actions
331 lines (283 loc) · 11 KB
/
Copy pathquery_planner_bidirectional.ex
File metadata and controls
331 lines (283 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# SPDX-License-Identifier: MPL-2.0
defmodule VeriSim.QueryPlanner.Bidirectional do
@moduledoc """
Bidirectional query optimization using both forward and backward propagation.
Forward Propagation (Top-Down):
- Push predicates down from SELECT to WHERE
- Example: If LIMIT 10, tell stores "only need 10 results"
Backward Propagation (Bottom-Up):
- Pull constraints up from stores to query plan
- Example: If store has index on 'year', reorganize to use it
Combined:
- Forward pass: Optimize predicates
- Backward pass: Rewrite based on store capabilities
- Forward pass: Execute optimized plan
"""
alias VeriSim.QueryPlannerConfig
@doc """
Optimize query using bidirectional propagation.
Steps:
1. Forward pass: Push down predicates (LIMIT, WHERE conditions)
2. Backward pass: Pull up store capabilities (indexes, partitions)
3. Generate final plan incorporating both
"""
def optimize_bidirectional(query_ast) do
# Phase 1: Forward propagation
forward_plan = forward_propagate(query_ast)
# Phase 2: Backward propagation (gather store capabilities)
store_hints = backward_propagate(forward_plan)
# Phase 3: Merge and finalize
finalize_plan(forward_plan, store_hints)
end
# === Forward Propagation (Top-Down) ===
defp forward_propagate(query_ast) do
query_ast
|> push_limit_down()
|> push_predicates_down()
|> push_projections_down()
|> eliminate_redundant_operations()
end
defp push_limit_down(query_ast) do
# If query has LIMIT, tell each store to limit results early
case query_ast.limit do
nil -> query_ast
limit_value ->
# Push LIMIT to each modality operation
updated_operations = Enum.map(query_ast.operations, fn op ->
%{op | early_limit: limit_value * 2} # 2x buffer for joins
end)
%{query_ast | operations: updated_operations}
end
end
defp push_predicates_down(query_ast) do
# Push WHERE conditions to the stores that can evaluate them
case query_ast.where do
nil -> query_ast
conditions ->
# Decompose conditions by modality
condition_map = decompose_conditions_by_modality(conditions)
# Assign to operations
updated_operations = Enum.map(query_ast.operations, fn op ->
modality_conditions = Map.get(condition_map, op.modality, [])
%{op | pushed_predicates: modality_conditions}
end)
%{query_ast | operations: updated_operations}
end
end
defp push_projections_down(query_ast) do
# If SELECT only requests specific fields, tell stores to return subset
# Example: SELECT GRAPH(nodes, edges) → only fetch nodes and edges
case query_ast.projections do
nil -> query_ast
projections ->
updated_operations = Enum.map(query_ast.operations, fn op ->
fields = Map.get(projections, op.modality, :all)
%{op | projection: fields}
end)
%{query_ast | operations: updated_operations}
end
end
defp eliminate_redundant_operations(query_ast) do
# Remove operations that can't possibly return results
# Example: If SEMANTIC condition fails, no need to query other modalities
query_ast
end
# === Backward Propagation (Bottom-Up) ===
defp backward_propagate(forward_plan) do
# Query each store for capabilities and hints
forward_plan.operations
|> Enum.map(&gather_store_hints/1)
|> aggregate_hints()
end
defp gather_store_hints(operation) do
store_id = operation.store_id
modality = operation.modality
# Ask store: "What indexes/optimizations do you have?"
hints = case modality do
"GRAPH" -> gather_graph_hints(store_id, operation)
"VECTOR" -> gather_vector_hints(store_id, operation)
"DOCUMENT" -> gather_document_hints(store_id, operation)
"SEMANTIC" -> gather_semantic_hints(store_id, operation)
"TENSOR" -> gather_tensor_hints(store_id, operation)
"TEMPORAL" -> gather_temporal_hints(store_id, operation)
end
%{operation: operation, hints: hints}
end
defp gather_graph_hints(store_id, operation) do
# Ask Oxigraph: "What indexes exist for this query?"
# Example response: [:edge_type_index, :node_label_index]
case OxigraphClient.get_available_indexes(store_id, operation.condition) do
{:ok, indexes} ->
%{
available_indexes: indexes,
supports_parallel: true,
estimated_cardinality: OxigraphClient.estimate_result_count(store_id, operation.condition),
suggestion: suggest_graph_optimization(indexes, operation)
}
{:error, _} ->
%{available_indexes: [], supports_parallel: false}
end
end
defp gather_vector_hints(store_id, operation) do
# Ask Milvus: "What's the best way to run this similarity search?"
case MilvusClient.get_index_info(store_id) do
{:ok, index_info} ->
%{
index_type: index_info.type, # HNSW, IVF, etc.
dimension: index_info.dimension,
metric: index_info.metric,
supports_filtering: index_info.supports_filtering,
suggestion: suggest_vector_optimization(index_info, operation)
}
{:error, _} ->
%{index_type: :unknown}
end
end
defp gather_document_hints(store_id, operation) do
# Ask Tantivy: "Do you have inverted index for this query?"
case TantivyClient.get_schema_info(store_id) do
{:ok, schema} ->
%{
indexed_fields: schema.indexed_fields,
supports_phrase_query: schema.supports_phrase_query,
has_stored_fields: schema.has_stored_fields,
suggestion: suggest_document_optimization(schema, operation)
}
{:error, _} ->
%{indexed_fields: []}
end
end
defp gather_semantic_hints(_store_id, operation) do
# Semantic operations are ZKP-based, limited optimization
%{
contract_name: operation.condition.contract_name,
verification_cost: ProvenLibrary.estimate_verification_cost(operation.condition),
suggestion: :execute_late # ZKP verification is expensive, do last
}
end
defp gather_tensor_hints(_store_id, _operation) do
# Tensor operations depend on shape/dtype
%{supports_gpu: BurnClient.has_gpu?()}
end
defp gather_temporal_hints(store_id, operation) do
# Ask verisim-temporal: "Is this version cached?"
case VeriSimTemporal.check_cache(store_id, operation.condition) do
{:ok, :cached} -> %{suggestion: :execute_early, cached: true}
{:ok, :not_cached} -> %{suggestion: :normal, cached: false}
{:error, _} -> %{cached: false}
end
end
# === Suggestion Heuristics ===
defp suggest_graph_optimization(indexes, operation) do
cond do
:edge_type_index in indexes and operation.condition.edge_type != nil ->
{:use_index, :edge_type_index}
:node_label_index in indexes ->
{:use_index, :node_label_index}
true ->
:full_scan
end
end
defp suggest_vector_optimization(index_info, operation) do
cond do
index_info.type == :hnsw and operation.condition.threshold > 0.9 ->
{:use_hnsw, :high_precision}
index_info.supports_filtering and operation.condition.filter != nil ->
{:use_filtering, :pre_filter}
true ->
:standard_ann
end
end
defp suggest_document_optimization(schema, operation) do
query_fields = extract_fulltext_fields(operation.condition)
if Enum.all?(query_fields, &(&1 in schema.indexed_fields)) do
{:use_inverted_index, query_fields}
else
:full_scan
end
end
# === Finalization (Merge Forward + Backward) ===
defp finalize_plan(forward_plan, store_hints) do
# Reorder operations based on backward hints
operations_with_hints = Enum.zip(forward_plan.operations, store_hints)
# Sort by:
# 1. Cached operations first
# 2. Indexed operations second
# 3. Full scans last
sorted_operations = Enum.sort_by(operations_with_hints, fn {_op, hints} ->
priority = case hints.hints.suggestion do
:execute_early -> 1
{:use_index, _} -> 2
{:use_hnsw, _} -> 2
:execute_late -> 99
_ -> 50
end
priority
end)
final_operations = Enum.map(sorted_operations, fn {op, hints} ->
%{op | optimization_hint: hints.hints.suggestion}
end)
%{forward_plan | operations: final_operations, optimization: :bidirectional}
end
# === Helper Functions ===
defp decompose_conditions_by_modality(conditions) when is_list(conditions) do
Enum.reduce(conditions, %{}, fn condition, acc ->
modality = classify_condition_modality(condition)
Map.update(acc, modality, [condition], &[condition | &1])
end)
end
defp decompose_conditions_by_modality(conditions) when is_map(conditions) do
# Single condition wrapped in a map
modality = classify_condition_modality(conditions)
%{modality => [conditions]}
end
defp decompose_conditions_by_modality(_), do: %{}
defp classify_condition_modality(condition) when is_map(condition) do
cond do
Map.has_key?(condition, :embedding) or Map.has_key?(condition, :similar_to) ->
"VECTOR"
Map.has_key?(condition, :edge_type) or Map.has_key?(condition, :traversal) ->
"GRAPH"
Map.has_key?(condition, :fulltext) or Map.has_key?(condition, :contains) ->
"DOCUMENT"
Map.has_key?(condition, :proof) or Map.has_key?(condition, :contract) ->
"SEMANTIC"
Map.has_key?(condition, :shape) or Map.has_key?(condition, :tensor_op) ->
"TENSOR"
Map.has_key?(condition, :version) or Map.has_key?(condition, :as_of) ->
"TEMPORAL"
true ->
"GRAPH" # Default modality
end
end
defp classify_condition_modality(condition) when is_binary(condition) do
upper = String.upcase(condition)
cond do
String.contains?(upper, "SIMILAR") or String.contains?(upper, "EMBEDDING") -> "VECTOR"
String.contains?(upper, "CITES") or String.contains?(upper, "EDGE") or String.contains?(upper, ")-[") -> "GRAPH"
String.contains?(upper, "FULLTEXT") or String.contains?(upper, "CONTAINS") -> "DOCUMENT"
String.contains?(upper, "PROOF") or String.contains?(upper, "VERIFY") -> "SEMANTIC"
String.contains?(upper, "TENSOR") or String.contains?(upper, "SHAPE") -> "TENSOR"
String.contains?(upper, "VERSION") or String.contains?(upper, "AS OF") -> "TEMPORAL"
true -> "GRAPH"
end
end
defp classify_condition_modality(_), do: "GRAPH"
defp aggregate_hints(hints_list) do
hints_list
end
defp extract_fulltext_fields(condition) when is_map(condition) do
case Map.get(condition, :fulltext) do
nil ->
case Map.get(condition, :fields) do
nil -> ["title", "body"] # Default searchable fields
fields when is_list(fields) -> fields
field when is_binary(field) -> [field]
_ -> ["title", "body"]
end
%{fields: fields} when is_list(fields) -> fields
_ -> ["title", "body"]
end
end
defp extract_fulltext_fields(_condition), do: ["title", "body"]
end