1use serde::{Deserialize, Serialize};
2
3#[derive(Deserialize)]
4#[allow(dead_code)]
5pub struct RpcRequest {
6 pub jsonrpc: String,
7 pub method: String,
8 pub params: RpcParams,
9 pub id: u64,
10}
11
12#[derive(Deserialize)]
13#[allow(dead_code)]
14pub struct RpcParams {
15 pub query: Option<String>,
16 pub token: Option<String>,
17}
18
19#[derive(Serialize)]
20#[allow(dead_code)]
21pub struct RpcResponse {
22 pub jsonrpc: String,
23 pub result: Option<serde_json::Value>,
24 pub error: Option<String>,
25 pub id: u64,
26}
27
28pub fn run_sparql_query(vault: &std::path::Path, query_str: &str) {
35 use qualia_core_db::q42_volume::{Q42QueryMode, RESIDENT_QUERY_MAX_BYTES};
36 use qualia_core_db::sparql_executor::QueryExecutor;
37 use qualia_core_db::sparql_planner::QueryPlanner;
38
39 let (query, ctx) = match qualia_core_db::sparql_parser::parse_sparql(query_str) {
40 Ok(pair) => pair,
41 Err(e) => {
42 eprintln!("SPARQL parse error: {e}");
43 return;
44 }
45 };
46
47 let plan = match QueryPlanner::plan(&query, &ctx) {
48 Ok(p) => p,
49 Err(e) => {
50 eprintln!("SPARQL plan error: {e}");
51 return;
52 }
53 };
54
55 let file_bytes = std::fs::metadata(vault).map(|m| m.len()).unwrap_or(0);
56 let mode = Q42QueryMode::for_file_bytes(file_bytes);
57
58 match try_range_query(vault, &plan, &ctx) {
59 Ok(()) => return,
60 Err(RangeDispatch::NoPlan(reason)) => {
61 if !mode.allows_read_all_quins() {
62 eprintln!(
63 "Range SPARQL required: vault is {file_bytes} bytes (limit {RESIDENT_QUERY_MAX_BYTES}); no range plan applies ({reason}). Resident fallback to read_q42_quins is forbidden."
64 );
65 return;
66 }
67 eprintln!("Range SPARQL unavailable; using resident compatibility executor: {reason}");
68 }
69 Err(RangeDispatch::Failed(error)) => {
70 if !mode.allows_read_all_quins() {
71 eprintln!(
72 "Range SPARQL failed; resident fallback forbidden ({file_bytes} bytes > {RESIDENT_QUERY_MAX_BYTES}): {error}"
73 );
74 return;
75 }
76 eprintln!("Range SPARQL unavailable; using resident compatibility executor: {error}");
77 }
78 }
79
80 if !mode.allows_read_all_quins() {
81 eprintln!(
82 "Range SPARQL required: vault is {file_bytes} bytes (limit {RESIDENT_QUERY_MAX_BYTES}); refusing read_q42_quins / read_all_quins."
83 );
84 return;
85 }
86
87 let quins = match qualia_core_db::q42_reader::read_q42_quins(vault) {
88 Ok(quins) => quins,
89 Err(e) => {
90 eprintln!("Warning: could not open vault '{}': {e}", vault.display());
91 Vec::new()
92 }
93 };
94 let lex = qualia_core_db::q42_lex::Q42Lexicon::load_for_q42(vault).ok();
95
96 if quins.is_empty() {
97 eprintln!(
98 "Warning: vault '{}' is empty or could not be opened.",
99 vault.display()
100 );
101 }
102
103 let executor = QueryExecutor::new(&quins);
104 match executor.execute(&plan, &ctx) {
105 Err(e) => eprintln!("SPARQL execution error: {e}"),
106 Ok(rows) => {
107 println!("{} result(s) from {}", rows.len(), vault.display());
108 for (i, row) in rows.iter().enumerate() {
109 print!("[{i:>4}]");
110 for (slot_idx, slot) in row.slots.iter().enumerate() {
111 if let Some(v) = slot {
112 match lex.as_ref().and_then(|l| l.lookup(*v)) {
113 Some(s) => print!(" ?v{slot_idx}={s}"),
114 None => print!(" ?v{slot_idx}=0x{v:016x}"),
115 }
116 }
117 }
118 println!();
119 }
120 }
121 }
122}
123
124enum RangeDispatch {
125 NoPlan(String),
126 Failed(String),
127}
128
129fn try_range_query(
130 vault: &std::path::Path,
131 plan: &qualia_core_db::sparql_planner::ExecutionPlan,
132 ctx: &qualia_core_db::sparql_ast::SparqlQueryContext,
133) -> Result<(), RangeDispatch> {
134 if let Ok(range_plan) = qualia_core_db::Q42RangeOptionalPlan::from_execution_plan(plan) {
135 return run_range_optional_query(vault, range_plan, ctx).map_err(RangeDispatch::Failed);
136 }
137 if let Ok(range_plan) = qualia_core_db::Q42RangeUnionPlan::from_execution_plan(plan) {
138 return run_range_union_query(vault, range_plan, ctx).map_err(RangeDispatch::Failed);
139 }
140 if let Ok(range_plan) = qualia_core_db::Q42RangeBindPlan::from_execution_plan(plan) {
141 return run_range_bind_query(vault, range_plan, ctx).map_err(RangeDispatch::Failed);
142 }
143 if let Ok(range_plan) = qualia_core_db::Q42RangeHashJoinPlan::from_execution_plan(plan) {
144 match run_range_hash_join_query(vault, range_plan, ctx) {
145 Ok(()) => return Ok(()),
146 Err(error) if error.contains("planner must keep NestedLoopJoin") => {
147 return run_range_join_select_query(vault, range_plan.as_join_select(), ctx)
148 .map_err(RangeDispatch::Failed);
149 }
150 Err(error) => return Err(RangeDispatch::Failed(error)),
151 }
152 }
153 if let Ok(range_plan) = qualia_core_db::Q42RangeJoinSelectPlan::from_execution_plan(plan) {
154 return run_range_join_select_query(vault, range_plan, ctx).map_err(RangeDispatch::Failed);
155 }
156 if let Ok(range_plan) =
157 qualia_core_db::sparql_executor::Q42RangeTripleSelectPlan::from_execution_plan(plan)
158 {
159 return run_range_triple_query(vault, range_plan, ctx).map_err(RangeDispatch::Failed);
160 }
161 if let Ok(range_plan) =
162 qualia_core_db::sparql_executor::Q42RangeNestedLoopJoinPlan::from_execution_plan(plan)
163 {
164 return run_range_join_query(vault, range_plan, ctx).map_err(RangeDispatch::Failed);
165 }
166 Err(RangeDispatch::NoPlan(
167 "query is not a range-executable TripleScan, join, OPTIONAL, UNION, or BIND".into(),
168 ))
169}
170
171fn run_range_triple_query(
172 vault: &std::path::Path,
173 plan: qualia_core_db::sparql_executor::Q42RangeTripleSelectPlan,
174 ctx: &qualia_core_db::sparql_ast::SparqlQueryContext,
175) -> Result<(), String> {
176 use qualia_core_db::q42_volume::{
177 LocalFileRangeSource, Q42RangeVolume, Q42RangeVolumeSet, Q42VolumeSegment,
178 };
179 use qualia_core_db::sparql_ast::BindingRow;
180 use qualia_core_db::sparql_executor::{
181 execute_range_triple_select_page_into, execute_range_volume_set_triple_select_page_into,
182 Q42RangeTripleSelectCursor, Q42RangeVolumeSetTripleSelectCursor,
183 };
184
185 const PAGE_ROWS: usize = 128;
186 let source = LocalFileRangeSource::open(vault).map_err(|error| error.to_string())?;
187 let root = Q42RangeVolume::open(source).map_err(|error| error.to_string())?;
188 let lexicon = qualia_core_db::q42_lex::Q42Lexicon::load_for_q42(vault).ok();
189 let mut compressed = [0u8; qualia_core_db::q42_volume::MAX_COMPRESSED_SUPERBLOCK_SIZE];
190 let mut decoded = [0u8; qualia_core_db::q42_volume::SUPERBLOCK_SIZE];
191 let mut quins = [qualia_core_db::NQuin::default(); PAGE_ROWS];
192 let mut rows = [BindingRow::default(); PAGE_ROWS];
193 let mut total = 0usize;
194
195 if root
196 .volume_manifest_length()
197 .map_err(|error| error.to_string())?
198 .is_some()
199 {
200 let parent = vault.parent().unwrap_or_else(|| std::path::Path::new("."));
201 let factory =
202 |entry: &Q42VolumeSegment| LocalFileRangeSource::open(&parent.join(&entry.locator));
203 let mut set =
204 Q42RangeVolumeSet::open_root(&root, &factory).map_err(|error| error.to_string())?;
205 set.attach_lexicon_segments(&|entry| {
206 LocalFileRangeSource::open(&parent.join(&entry.locator))
207 })
208 .map_err(|error| error.to_string())?;
209 let mut cursor = Q42RangeVolumeSetTripleSelectCursor::default();
210 loop {
211 let page = execute_range_volume_set_triple_select_page_into(
212 &set,
213 plan,
214 ctx,
215 cursor,
216 &mut compressed,
217 &mut decoded,
218 &mut quins,
219 &mut rows,
220 )?;
221 print_range_rows(&rows[..page.returned], lexicon.as_ref());
222 total += page.returned;
223 let Some(next) = page.next_cursor else {
224 break;
225 };
226 cursor = next;
227 }
228 } else {
229 let mut cursor = Q42RangeTripleSelectCursor::default();
230 loop {
231 let page = execute_range_triple_select_page_into(
232 &root,
233 plan,
234 ctx,
235 cursor,
236 &mut compressed,
237 &mut decoded,
238 &mut quins,
239 &mut rows,
240 )?;
241 print_range_rows(&rows[..page.returned], lexicon.as_ref());
242 total += page.returned;
243 let Some(next) = page.next_cursor else {
244 break;
245 };
246 cursor = next;
247 }
248 }
249 println!("{total} result(s) from {} (range-backed)", vault.display());
250 Ok(())
251}
252
253fn run_range_join_query(
258 vault: &std::path::Path,
259 plan: qualia_core_db::sparql_executor::Q42RangeNestedLoopJoinPlan,
260 ctx: &qualia_core_db::sparql_ast::SparqlQueryContext,
261) -> Result<(), String> {
262 use qualia_core_db::q42_volume::{
263 LocalFileRangeSource, Q42RangeVolume, Q42RangeVolumeSet, Q42VolumeSegment,
264 };
265 use qualia_core_db::sparql_ast::BindingRow;
266 use qualia_core_db::sparql_executor::{
267 execute_range_nested_loop_join_page_into,
268 execute_range_volume_set_nested_loop_join_page_into, Q42RangeNestedLoopJoinState,
269 Q42RangeVolumeSetNestedLoopJoinState,
270 };
271
272 const PAGE_ROWS: usize = 128;
273 let source = LocalFileRangeSource::open(vault).map_err(|error| error.to_string())?;
274 let root = Q42RangeVolume::open(source).map_err(|error| error.to_string())?;
275 let lexicon = qualia_core_db::q42_lex::Q42Lexicon::load_for_q42(vault).ok();
276 let mut compressed = [0u8; qualia_core_db::q42_volume::MAX_COMPRESSED_SUPERBLOCK_SIZE];
277 let mut decoded = [0u8; qualia_core_db::q42_volume::SUPERBLOCK_SIZE];
278 let mut quins = [qualia_core_db::NQuin::default(); PAGE_ROWS];
279 let mut left_rows = [BindingRow::default(); PAGE_ROWS];
280 let mut right_rows = [BindingRow::default(); PAGE_ROWS];
281 let mut rows = [BindingRow::default(); PAGE_ROWS];
282 let mut total = 0usize;
283
284 if root
285 .volume_manifest_length()
286 .map_err(|error| error.to_string())?
287 .is_some()
288 {
289 let parent = vault.parent().unwrap_or_else(|| std::path::Path::new("."));
290 let factory =
291 |entry: &Q42VolumeSegment| LocalFileRangeSource::open(&parent.join(&entry.locator));
292 let volumes =
293 Q42RangeVolumeSet::open_root(&root, &factory).map_err(|error| error.to_string())?;
294 let mut state = Q42RangeVolumeSetNestedLoopJoinState::default();
295 loop {
296 let page = execute_range_volume_set_nested_loop_join_page_into(
297 &volumes,
298 plan,
299 ctx,
300 &mut state,
301 &mut compressed,
302 &mut decoded,
303 &mut quins,
304 &mut left_rows,
305 &mut right_rows,
306 &mut rows,
307 )?;
308 print_range_rows(&rows[..page.returned], lexicon.as_ref());
309 total += page.returned;
310 if page.done {
311 break;
312 }
313 }
314 } else {
315 let mut state = Q42RangeNestedLoopJoinState::default();
316 loop {
317 let page = execute_range_nested_loop_join_page_into(
318 &root,
319 plan,
320 ctx,
321 &mut state,
322 &mut compressed,
323 &mut decoded,
324 &mut quins,
325 &mut left_rows,
326 &mut right_rows,
327 &mut rows,
328 )?;
329 print_range_rows(&rows[..page.returned], lexicon.as_ref());
330 total += page.returned;
331 if page.done {
332 break;
333 }
334 }
335 }
336 println!(
337 "{total} result(s) from {} (range-backed join)",
338 vault.display()
339 );
340 Ok(())
341}
342
343fn run_range_join_select_query(
344 vault: &std::path::Path,
345 plan: qualia_core_db::Q42RangeJoinSelectPlan,
346 ctx: &qualia_core_db::sparql_ast::SparqlQueryContext,
347) -> Result<(), String> {
348 use qualia_core_db::q42_volume::{
349 LocalFileRangeSource, Q42RangeVolume, Q42RangeVolumeSet, Q42VolumeSegment,
350 };
351 use qualia_core_db::sparql_ast::BindingRow;
352 use qualia_core_db::{
353 execute_range_join_select_page_into, execute_range_volume_set_join_select_page_into,
354 Q42RangeJoinSelectState, Q42RangeVolumeSetJoinSelectState,
355 };
356
357 const PAGE_ROWS: usize = 128;
358 let source = LocalFileRangeSource::open(vault).map_err(|error| error.to_string())?;
359 let root = Q42RangeVolume::open(source).map_err(|error| error.to_string())?;
360 let lexicon = qualia_core_db::q42_lex::Q42Lexicon::load_for_q42(vault).ok();
361 let mut compressed = [0u8; qualia_core_db::q42_volume::MAX_COMPRESSED_SUPERBLOCK_SIZE];
362 let mut decoded = [0u8; qualia_core_db::q42_volume::SUPERBLOCK_SIZE];
363 let mut quins = [qualia_core_db::NQuin::default(); PAGE_ROWS];
364 let mut left_rows = [BindingRow::default(); PAGE_ROWS];
365 let mut right_rows = [BindingRow::default(); PAGE_ROWS];
366 let mut join_out = [BindingRow::default(); PAGE_ROWS];
367 let mut rows = [BindingRow::default(); PAGE_ROWS];
368 let mut total = 0usize;
369
370 if root
371 .volume_manifest_length()
372 .map_err(|error| error.to_string())?
373 .is_some()
374 {
375 let parent = vault.parent().unwrap_or_else(|| std::path::Path::new("."));
376 let factory =
377 |entry: &Q42VolumeSegment| LocalFileRangeSource::open(&parent.join(&entry.locator));
378 let volumes =
379 Q42RangeVolumeSet::open_root(&root, &factory).map_err(|error| error.to_string())?;
380 let mut state = Q42RangeVolumeSetJoinSelectState::default();
381 loop {
382 let page = execute_range_volume_set_join_select_page_into(
383 &volumes,
384 plan,
385 ctx,
386 &mut state,
387 &mut compressed,
388 &mut decoded,
389 &mut quins,
390 &mut left_rows,
391 &mut right_rows,
392 &mut join_out,
393 &mut rows,
394 )?;
395 print_range_rows(&rows[..page.returned], lexicon.as_ref());
396 total += page.returned;
397 if page.done {
398 break;
399 }
400 }
401 } else {
402 let mut state = Q42RangeJoinSelectState::default();
403 loop {
404 let page = execute_range_join_select_page_into(
405 &root,
406 plan,
407 ctx,
408 &mut state,
409 &mut compressed,
410 &mut decoded,
411 &mut quins,
412 &mut left_rows,
413 &mut right_rows,
414 &mut join_out,
415 &mut rows,
416 )?;
417 print_range_rows(&rows[..page.returned], lexicon.as_ref());
418 total += page.returned;
419 if page.done {
420 break;
421 }
422 }
423 }
424 println!(
425 "{total} result(s) from {} (range-backed join-select)",
426 vault.display()
427 );
428 Ok(())
429}
430
431fn run_range_optional_query(
432 vault: &std::path::Path,
433 plan: qualia_core_db::Q42RangeOptionalPlan,
434 ctx: &qualia_core_db::sparql_ast::SparqlQueryContext,
435) -> Result<(), String> {
436 use qualia_core_db::q42_volume::{
437 LocalFileRangeSource, Q42RangeVolume, Q42RangeVolumeSet, Q42VolumeSegment,
438 };
439 use qualia_core_db::sparql_ast::BindingRow;
440 use qualia_core_db::{
441 execute_range_optional_page_into, execute_range_volume_set_optional_page_into,
442 Q42RangeOptionalState, Q42RangeVolumeSetOptionalState,
443 };
444
445 const PAGE_ROWS: usize = 128;
446 let source = LocalFileRangeSource::open(vault).map_err(|error| error.to_string())?;
447 let root = Q42RangeVolume::open(source).map_err(|error| error.to_string())?;
448 let lexicon = qualia_core_db::q42_lex::Q42Lexicon::load_for_q42(vault).ok();
449 let mut compressed = [0u8; qualia_core_db::q42_volume::MAX_COMPRESSED_SUPERBLOCK_SIZE];
450 let mut decoded = [0u8; qualia_core_db::q42_volume::SUPERBLOCK_SIZE];
451 let mut quins = [qualia_core_db::NQuin::default(); PAGE_ROWS];
452 let mut left_rows = [BindingRow::default(); PAGE_ROWS];
453 let mut right_rows = [BindingRow::default(); PAGE_ROWS];
454 let mut join_out = [BindingRow::default(); PAGE_ROWS];
455 let mut rows = [BindingRow::default(); PAGE_ROWS];
456 let mut total = 0usize;
457
458 if root
459 .volume_manifest_length()
460 .map_err(|error| error.to_string())?
461 .is_some()
462 {
463 let parent = vault.parent().unwrap_or_else(|| std::path::Path::new("."));
464 let factory =
465 |entry: &Q42VolumeSegment| LocalFileRangeSource::open(&parent.join(&entry.locator));
466 let volumes =
467 Q42RangeVolumeSet::open_root(&root, &factory).map_err(|error| error.to_string())?;
468 let mut state = Q42RangeVolumeSetOptionalState::default();
469 loop {
470 let page = execute_range_volume_set_optional_page_into(
471 &volumes,
472 plan,
473 ctx,
474 &mut state,
475 &mut compressed,
476 &mut decoded,
477 &mut quins,
478 &mut left_rows,
479 &mut right_rows,
480 &mut join_out,
481 &mut rows,
482 )?;
483 print_range_rows(&rows[..page.returned], lexicon.as_ref());
484 total += page.returned;
485 if page.done {
486 break;
487 }
488 }
489 } else {
490 let mut state = Q42RangeOptionalState::default();
491 loop {
492 let page = execute_range_optional_page_into(
493 &root,
494 plan,
495 ctx,
496 &mut state,
497 &mut compressed,
498 &mut decoded,
499 &mut quins,
500 &mut left_rows,
501 &mut right_rows,
502 &mut join_out,
503 &mut rows,
504 )?;
505 print_range_rows(&rows[..page.returned], lexicon.as_ref());
506 total += page.returned;
507 if page.done {
508 break;
509 }
510 }
511 }
512 println!(
513 "{total} result(s) from {} (range-backed optional)",
514 vault.display()
515 );
516 Ok(())
517}
518
519fn run_range_union_query(
520 vault: &std::path::Path,
521 plan: qualia_core_db::Q42RangeUnionPlan,
522 ctx: &qualia_core_db::sparql_ast::SparqlQueryContext,
523) -> Result<(), String> {
524 use qualia_core_db::q42_volume::{
525 LocalFileRangeSource, Q42RangeVolume, Q42RangeVolumeSet, Q42VolumeSegment,
526 };
527 use qualia_core_db::sparql_ast::BindingRow;
528 use qualia_core_db::{
529 execute_range_union_page_into, execute_range_volume_set_union_page_into, Q42RangeUnionState,
530 Q42RangeVolumeSetUnionState,
531 };
532
533 const PAGE_ROWS: usize = 128;
534 let source = LocalFileRangeSource::open(vault).map_err(|error| error.to_string())?;
535 let root = Q42RangeVolume::open(source).map_err(|error| error.to_string())?;
536 let lexicon = qualia_core_db::q42_lex::Q42Lexicon::load_for_q42(vault).ok();
537 let mut compressed = [0u8; qualia_core_db::q42_volume::MAX_COMPRESSED_SUPERBLOCK_SIZE];
538 let mut decoded = [0u8; qualia_core_db::q42_volume::SUPERBLOCK_SIZE];
539 let mut quins = [qualia_core_db::NQuin::default(); PAGE_ROWS];
540 let mut raw = [BindingRow::default(); PAGE_ROWS];
541 let mut rows = [BindingRow::default(); PAGE_ROWS];
542 let mut total = 0usize;
543
544 if root
545 .volume_manifest_length()
546 .map_err(|error| error.to_string())?
547 .is_some()
548 {
549 let parent = vault.parent().unwrap_or_else(|| std::path::Path::new("."));
550 let factory =
551 |entry: &Q42VolumeSegment| LocalFileRangeSource::open(&parent.join(&entry.locator));
552 let volumes =
553 Q42RangeVolumeSet::open_root(&root, &factory).map_err(|error| error.to_string())?;
554 let mut state = Q42RangeVolumeSetUnionState::default();
555 loop {
556 let page = execute_range_volume_set_union_page_into(
557 &volumes,
558 plan,
559 ctx,
560 &mut state,
561 &mut compressed,
562 &mut decoded,
563 &mut quins,
564 &mut raw,
565 &mut rows,
566 )?;
567 print_range_rows(&rows[..page.returned], lexicon.as_ref());
568 total += page.returned;
569 if page.done {
570 break;
571 }
572 }
573 } else {
574 let mut state = Q42RangeUnionState::default();
575 loop {
576 let page = execute_range_union_page_into(
577 &root,
578 plan,
579 ctx,
580 &mut state,
581 &mut compressed,
582 &mut decoded,
583 &mut quins,
584 &mut raw,
585 &mut rows,
586 )?;
587 print_range_rows(&rows[..page.returned], lexicon.as_ref());
588 total += page.returned;
589 if page.done {
590 break;
591 }
592 }
593 }
594 println!(
595 "{total} result(s) from {} (range-backed union)",
596 vault.display()
597 );
598 Ok(())
599}
600
601fn run_range_bind_query(
602 vault: &std::path::Path,
603 plan: qualia_core_db::Q42RangeBindPlan,
604 ctx: &qualia_core_db::sparql_ast::SparqlQueryContext,
605) -> Result<(), String> {
606 use qualia_core_db::q42_volume::{
607 LocalFileRangeSource, Q42RangeVolume, Q42RangeVolumeSet, Q42VolumeSegment,
608 };
609 use qualia_core_db::sparql_ast::BindingRow;
610 use qualia_core_db::{
611 execute_range_bind_page_into, execute_range_volume_set_bind_page_into, Q42RangeBindState,
612 Q42RangeVolumeSetBindState,
613 };
614
615 const PAGE_ROWS: usize = 128;
616 let source = LocalFileRangeSource::open(vault).map_err(|error| error.to_string())?;
617 let root = Q42RangeVolume::open(source).map_err(|error| error.to_string())?;
618 let lexicon = qualia_core_db::q42_lex::Q42Lexicon::load_for_q42(vault).ok();
619 let mut compressed = [0u8; qualia_core_db::q42_volume::MAX_COMPRESSED_SUPERBLOCK_SIZE];
620 let mut decoded = [0u8; qualia_core_db::q42_volume::SUPERBLOCK_SIZE];
621 let mut quins = [qualia_core_db::NQuin::default(); PAGE_ROWS];
622 let mut raw = [BindingRow::default(); PAGE_ROWS];
623 let mut rows = [BindingRow::default(); PAGE_ROWS];
624 let mut total = 0usize;
625
626 if root
627 .volume_manifest_length()
628 .map_err(|error| error.to_string())?
629 .is_some()
630 {
631 let parent = vault.parent().unwrap_or_else(|| std::path::Path::new("."));
632 let factory =
633 |entry: &Q42VolumeSegment| LocalFileRangeSource::open(&parent.join(&entry.locator));
634 let volumes =
635 Q42RangeVolumeSet::open_root(&root, &factory).map_err(|error| error.to_string())?;
636 let mut state = Q42RangeVolumeSetBindState::default();
637 loop {
638 let page = execute_range_volume_set_bind_page_into(
639 &volumes,
640 plan,
641 ctx,
642 &mut state,
643 &mut compressed,
644 &mut decoded,
645 &mut quins,
646 &mut raw,
647 &mut rows,
648 )?;
649 print_range_rows(&rows[..page.returned], lexicon.as_ref());
650 total += page.returned;
651 if page.done {
652 break;
653 }
654 }
655 } else {
656 let mut state = Q42RangeBindState::default();
657 loop {
658 let page = execute_range_bind_page_into(
659 &root,
660 plan,
661 ctx,
662 &mut state,
663 &mut compressed,
664 &mut decoded,
665 &mut quins,
666 &mut raw,
667 &mut rows,
668 )?;
669 print_range_rows(&rows[..page.returned], lexicon.as_ref());
670 total += page.returned;
671 if page.done {
672 break;
673 }
674 }
675 }
676 println!(
677 "{total} result(s) from {} (range-backed bind)",
678 vault.display()
679 );
680 Ok(())
681}
682
683fn run_range_hash_join_query(
684 vault: &std::path::Path,
685 plan: qualia_core_db::Q42RangeHashJoinPlan,
686 ctx: &qualia_core_db::sparql_ast::SparqlQueryContext,
687) -> Result<(), String> {
688 use qualia_core_db::q42_volume::{
689 LocalFileRangeSource, Q42RangeVolume, Q42RangeVolumeSet, Q42VolumeSegment,
690 };
691 use qualia_core_db::sparql_ast::BindingRow;
692 use qualia_core_db::{
693 execute_range_hash_join_page_into, execute_range_volume_set_hash_join_page_into,
694 Q42RangeHashJoinSlot, Q42RangeHashJoinState, Q42RangeVolumeSetHashJoinState,
695 };
696
697 const PAGE_ROWS: usize = 128;
698 const HASH_CAP: usize = 256;
699 let source = LocalFileRangeSource::open(vault).map_err(|error| error.to_string())?;
700 let root = Q42RangeVolume::open(source).map_err(|error| error.to_string())?;
701 let lexicon = qualia_core_db::q42_lex::Q42Lexicon::load_for_q42(vault).ok();
702 let mut compressed = [0u8; qualia_core_db::q42_volume::MAX_COMPRESSED_SUPERBLOCK_SIZE];
703 let mut decoded = [0u8; qualia_core_db::q42_volume::SUPERBLOCK_SIZE];
704 let mut quins = [qualia_core_db::NQuin::default(); PAGE_ROWS];
705 let mut probe_rows = [BindingRow::default(); PAGE_ROWS];
706 let mut join_out = [BindingRow::default(); PAGE_ROWS];
707 let mut rows = [BindingRow::default(); PAGE_ROWS];
708 let mut table = [Q42RangeHashJoinSlot::default(); HASH_CAP];
709 let mut total = 0usize;
710
711 if root
712 .volume_manifest_length()
713 .map_err(|error| error.to_string())?
714 .is_some()
715 {
716 let parent = vault.parent().unwrap_or_else(|| std::path::Path::new("."));
717 let factory =
718 |entry: &Q42VolumeSegment| LocalFileRangeSource::open(&parent.join(&entry.locator));
719 let volumes =
720 Q42RangeVolumeSet::open_root(&root, &factory).map_err(|error| error.to_string())?;
721 let mut state = Q42RangeVolumeSetHashJoinState::default();
722 loop {
723 let page = execute_range_volume_set_hash_join_page_into(
724 &volumes,
725 plan,
726 ctx,
727 &mut state,
728 &mut table,
729 &mut compressed,
730 &mut decoded,
731 &mut quins,
732 &mut probe_rows,
733 &mut join_out,
734 &mut rows,
735 )?;
736 print_range_rows(&rows[..page.returned], lexicon.as_ref());
737 total += page.returned;
738 if page.done {
739 break;
740 }
741 }
742 } else {
743 let mut state = Q42RangeHashJoinState::default();
744 loop {
745 let page = execute_range_hash_join_page_into(
746 &root,
747 plan,
748 ctx,
749 &mut state,
750 &mut table,
751 &mut compressed,
752 &mut decoded,
753 &mut quins,
754 &mut probe_rows,
755 &mut join_out,
756 &mut rows,
757 )?;
758 print_range_rows(&rows[..page.returned], lexicon.as_ref());
759 total += page.returned;
760 if page.done {
761 break;
762 }
763 }
764 }
765 println!(
766 "{total} result(s) from {} (range-backed hash-join)",
767 vault.display()
768 );
769 Ok(())
770}
771
772fn print_range_rows(
773 rows: &[qualia_core_db::sparql_ast::BindingRow],
774 lexicon: Option<&qualia_core_db::q42_lex::Q42Lexicon>,
775) {
776 for row in rows {
777 print!("[range]");
778 for (slot, value) in row.slots.iter().enumerate() {
779 if let Some(value) = value {
780 match lexicon.and_then(|lexicon| lexicon.lookup(*value)) {
781 Some(text) => print!(" ?v{slot}={text}"),
782 None => print!(" ?v{slot}=0x{value:016x}"),
783 }
784 }
785 }
786 println!();
787 }
788}