use rete_core::{
auto_block, BlockCacheReader, CountingReader, RangeReader, Rete, SearchView, SummaryView,
};
use crate::commands::card;
use crate::commands::range_source::RangedSourceReader;
use crate::commands::render::print_query_output_warned;
pub(crate) fn card_url(
url: &str,
json: bool,
format: Option<&str>,
sha256: Option<&str>,
) -> anyhow::Result<()> {
let format = card::CardFormat::resolve(json, format)?;
let reader = CountingReader::new(RangedSourceReader::open(url)?);
let total = reader.len();
let read = card::load_card_and_build_ranged(&reader)?;
match &read.card {
None => println!("(no dataset card — {})", read.text_index.describe()),
Some(dataset_card) => {
let checksum = card::hex16(&read.header.content_hash);
card::print_card(
dataset_card,
read.build.as_ref(),
&checksum,
url,
format,
sha256,
)?;
}
}
eprintln!(
"fetched {} of {} bytes in {} range request(s) — index NOT fetched",
reader.bytes_read(),
total,
reader.requests()
);
Ok(())
}
pub(crate) fn search_url(
url: &str,
prefix: &str,
words: &[String],
contains_prefix: Option<&str>,
limit: usize,
json: bool,
) -> anyhow::Result<()> {
let reader = std::sync::Arc::new(CountingReader::new(RangedSourceReader::open(url)?));
let total = reader.len();
let block: u64 = match std::env::var("RETE_BLOCK_KB")
.ok()
.and_then(|v| v.parse::<u64>().ok())
{
Some(kb) => kb * 1024,
None => auto_block(total),
};
let view = if block == 0 {
SearchView::open_ranged(reader.clone())?
} else {
SearchView::open_ranged(std::sync::Arc::new(BlockCacheReader::new(
reader.clone(),
block,
)))?
};
let full_text = !words.is_empty() || contains_prefix.is_some();
if full_text && !view.has_text_index() {
if json {
println!(
"{{\"schemaVersion\":{},\"matches\":[]}}",
crate::JSON_SCHEMA_VERSION
);
} else {
eprintln!("(this file has no text index — rebuild with `rete build --text-index`)");
}
return Ok(());
}
if !full_text && !view.has_labels() {
if json {
println!(
"{{\"schemaVersion\":{},\"matches\":[]}}",
crate::JSON_SCHEMA_VERSION
);
} else {
eprintln!("(this file has no label index — rebuild with `rete build --label-index`)");
}
return Ok(());
}
let hits: Vec<(Option<String>, String)> = if full_text {
let word_refs: Vec<&str> = words.iter().map(String::as_str).collect();
view.text_search(&word_refs, contains_prefix, limit)
.into_iter()
.map(|iri| (None, iri))
.collect()
} else {
view.prefix_search(prefix, limit)
.into_iter()
.map(|(label, iri)| (Some(label), iri))
.collect()
};
if json {
let items: Vec<String> = hits
.iter()
.map(|(label, iri)| match label {
Some(l) => format!(
"{{\"label\":{},\"subject\":{}}}",
crate::commands::inspect::json_str(l),
crate::commands::inspect::json_str(iri)
),
None => format!(
"{{\"subject\":{}}}",
crate::commands::inspect::json_str(iri)
),
})
.collect();
println!(
"{{\"schemaVersion\":{},\"matches\":[{}]}}",
crate::JSON_SCHEMA_VERSION,
items.join(",")
);
} else if hits.is_empty() {
if full_text {
let mut terms: Vec<String> = words.to_vec();
if let Some(p) = contains_prefix {
terms.push(format!("{p}*"));
}
println!("(no entities contain {})", terms.join(" + "));
} else {
println!("(no labels match \"{prefix}\")");
}
} else {
for (label, iri) in &hits {
match label {
Some(l) => println!("{l}\t{iri}"),
None => println!("{iri}"),
}
}
}
eprintln!(
"fetched {} of {} bytes in {} range request(s) — index NOT fetched",
reader.bytes_read(),
total,
reader.requests()
);
Ok(())
}
pub(crate) fn summary_url(url: &str) -> anyhow::Result<()> {
let reader = CountingReader::new(RangedSourceReader::open(url)?);
let total = reader.len();
match SummaryView::open_ranged(&reader)? {
Some(view) => {
println!(
"pyramid round {} — {} communities, {} superedge(s):",
view.round,
view.community_count(),
view.summary.len()
);
for e in &view.summary {
let pred = view
.predicate_term(e.predicate)
.unwrap_or_else(|| format!("#{}", e.predicate));
let arrow = if e.s_comm == e.o_comm {
"(internal)"
} else {
"->"
};
println!(
" C{} {arrow} C{} via {pred} x{}",
e.s_comm, e.o_comm, e.count
);
}
crate::commands::inspect::render_schema_overview(&view);
eprintln!(
"fetched {} of {} bytes in {} range request(s) — index NOT fetched",
reader.bytes_read(),
total,
reader.requests()
);
}
None => eprintln!("file has no pyramid"),
}
Ok(())
}
pub(crate) fn query_url(
url: &str,
s: Option<String>,
p: Option<String>,
o: Option<String>,
) -> anyhow::Result<()> {
let reader = CountingReader::new(RangedSourceReader::open(url)?);
let total = reader.len();
let results = Rete::query_ranged(&reader, s.as_deref(), p.as_deref(), o.as_deref())?;
for (s, p, o) in &results {
println!("{s} {p} {o} .");
}
eprintln!(
"{} result(s) · fetched {} bytes in {} range request(s) (file is {} bytes)",
results.len(),
reader.bytes_read(),
reader.requests(),
total
);
Ok(())
}
pub(crate) type CountedSource = std::sync::Arc<CountingReader<RangedSourceReader>>;
pub(crate) fn open_url_lazy(
url: &str,
budget: Option<u64>,
) -> anyhow::Result<(Rete, CountedSource)> {
let reader = std::sync::Arc::new(CountingReader::new(RangedSourceReader::open(url)?));
let total = reader.len();
let block: u64 = match std::env::var("RETE_BLOCK_KB")
.ok()
.and_then(|v| v.parse::<u64>().ok())
{
Some(kb) => kb * 1024,
None => auto_block(total),
};
let rete = if block == 0 {
Rete::open_ranged_lazy(reader.clone())?
} else {
let mut bc = BlockCacheReader::new(reader.clone(), block);
let block_cap = rete_core::split_memory_budget(budget.unwrap_or(u64::MAX)).block_cache;
if block_cap != u64::MAX {
bc = bc.with_cache_cap(block_cap);
}
Rete::open_ranged_lazy(std::sync::Arc::new(bc))?
};
rete.set_memory_budget(budget);
Ok((rete, reader))
}
pub(crate) fn sparql_url(
url: &str,
query: &str,
json: bool,
entail: bool,
memory_budget_mb: Option<u64>,
) -> anyhow::Result<()> {
let budget = super::range_source::query_budget(memory_budget_mb);
let (mut rete, reader) = open_url_lazy(url, budget)?;
let total = reader.len();
rete.set_service_client(Box::new(super::service_http::HttpServiceClient));
let opts = rete_core::QueryOpts {
reason: entail,
..Default::default()
};
let (result, warnings) = rete_core::eval_query_with_warnings(&rete, query, opts)
.map_err(|e| anyhow::anyhow!("{e}"))?;
if rete.index_incomplete() {
anyhow::bail!(
"a range request failed while streaming index tiles from {url}; \
results would be incomplete — retry"
);
}
print_query_output_warned(&result, &warnings, json);
eprintln!(
"(fetched {} bytes in {} range request(s); file is {} bytes)",
reader.bytes_read(),
reader.requests(),
total
);
Ok(())
}
pub(crate) fn why_url(
url: &str,
subject: Option<String>,
predicate: Option<String>,
object: Option<String>,
json: bool,
) -> anyhow::Result<()> {
let reader = std::sync::Arc::new(CountingReader::new(RangedSourceReader::open(url)?));
let total = reader.len();
let block: u64 = match std::env::var("RETE_BLOCK_KB")
.ok()
.and_then(|v| v.parse::<u64>().ok())
{
Some(kb) => kb * 1024,
None => auto_block(total),
};
let rete = if block == 0 {
Rete::open_ranged_lazy(reader.clone())?
} else {
Rete::open_ranged_lazy(std::sync::Arc::new(BlockCacheReader::new(
reader.clone(),
block,
)))?
};
let results =
rete.query_with_provenance(subject.as_deref(), predicate.as_deref(), object.as_deref());
if rete.index_incomplete() {
anyhow::bail!(
"a range request failed while explaining the pattern over {url}; the \
provenance would be incomplete — retry"
);
}
crate::commands::query::print_provenance(
subject.as_deref(),
predicate.as_deref(),
object.as_deref(),
&results,
json,
)?;
eprintln!(
"(fetched {} bytes in {} range request(s); file is {} bytes)",
reader.bytes_read(),
reader.requests(),
total
);
Ok(())
}