query: peg-based query language for ad-hoc graph exploration
poc-memory query "degree > 15"
poc-memory query "key ~ 'journal.*' AND degree > 10"
poc-memory query "neighbors('identity.md') WHERE strength > 0.5"
poc-memory query "community_id = community('identity.md')" --fields degree,category
Grammar-driven: the peg definition IS the language spec. Supports
boolean logic (AND/OR/NOT), numeric and string comparison, regex
match (~), graph traversal (neighbors() with WHERE), and function
calls (community(), degree()). Output flags: --fields, --sort,
--limit, --count.
New dependency: peg 0.8 (~68KB, 2 tiny deps).
This commit is contained in:
parent
71e6f15d82
commit
a36449032c
5 changed files with 544 additions and 1 deletions
83
src/main.rs
83
src/main.rs
|
|
@ -20,6 +20,7 @@ mod search;
|
|||
mod similarity;
|
||||
mod migrate;
|
||||
mod neuro;
|
||||
mod query;
|
||||
mod spectral;
|
||||
|
||||
pub mod memory_capnp {
|
||||
|
|
@ -118,6 +119,7 @@ fn main() {
|
|||
"export" => cmd_export(&args[2..]),
|
||||
"journal-write" => cmd_journal_write(&args[2..]),
|
||||
"journal-tail" => cmd_journal_tail(&args[2..]),
|
||||
"query" => cmd_query(&args[2..]),
|
||||
_ => {
|
||||
eprintln!("Unknown command: {}", args[1]);
|
||||
usage();
|
||||
|
|
@ -192,7 +194,11 @@ Commands:
|
|||
import FILE [FILE...] Import markdown file(s) into the store
|
||||
export [FILE|--all] Export store nodes to markdown file(s)
|
||||
journal-write TEXT Write a journal entry to the store
|
||||
journal-tail [N] [--full] Show last N journal entries (default 20, --full for content)");
|
||||
journal-tail [N] [--full] Show last N journal entries (default 20, --full for content)
|
||||
query EXPR [--fields F] [--sort F] [--limit N] [--count]
|
||||
Query the memory graph with expressions
|
||||
Examples: \"degree > 15\", \"key ~ 'journal.*'\",
|
||||
\"neighbors('identity.md') WHERE strength > 0.5\"");
|
||||
}
|
||||
|
||||
fn cmd_search(args: &[String]) -> Result<(), String> {
|
||||
|
|
@ -1615,3 +1621,78 @@ fn cmd_interference(args: &[String]) -> Result<(), String> {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cmd_query(args: &[String]) -> Result<(), String> {
|
||||
if args.is_empty() {
|
||||
return Err("Usage: poc-memory query EXPR [--fields F,F,...] [--sort F] [--limit N] [--count]".into());
|
||||
}
|
||||
|
||||
// Parse flags — query string is the first non-flag arg
|
||||
let mut opts = query::QueryOpts::default();
|
||||
let mut query_str = None;
|
||||
let mut i = 0;
|
||||
while i < args.len() {
|
||||
match args[i].as_str() {
|
||||
"--fields" if i + 1 < args.len() => {
|
||||
opts.fields = args[i + 1].split(',').map(|s| s.trim().to_string()).collect();
|
||||
i += 2;
|
||||
}
|
||||
"--sort" if i + 1 < args.len() => {
|
||||
opts.sort_field = Some(args[i + 1].clone());
|
||||
i += 2;
|
||||
}
|
||||
"--limit" if i + 1 < args.len() => {
|
||||
opts.limit = Some(args[i + 1].parse().map_err(|_| "invalid --limit")?);
|
||||
i += 2;
|
||||
}
|
||||
"--count" => {
|
||||
opts.count_only = true;
|
||||
i += 1;
|
||||
}
|
||||
_ if query_str.is_none() => {
|
||||
query_str = Some(args[i].clone());
|
||||
i += 1;
|
||||
}
|
||||
_ => {
|
||||
return Err(format!("unexpected argument: {}", args[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let query_str = query_str.ok_or("missing query expression")?;
|
||||
let store = capnp_store::Store::load()?;
|
||||
let graph = store.build_graph();
|
||||
|
||||
let results = query::execute_query(&store, &graph, &query_str, &opts)?;
|
||||
|
||||
if opts.count_only {
|
||||
println!("{}", results.len());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if results.is_empty() {
|
||||
eprintln!("No results");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// If --fields specified, show as TSV with header
|
||||
if !opts.fields.is_empty() {
|
||||
let mut header = vec!["key".to_string()];
|
||||
header.extend(opts.fields.iter().cloned());
|
||||
println!("{}", header.join("\t"));
|
||||
|
||||
for r in &results {
|
||||
let mut row = vec![r.key.clone()];
|
||||
for f in &opts.fields {
|
||||
row.push(query::format_field(f, &r.key, &store, &graph));
|
||||
}
|
||||
println!("{}", row.join("\t"));
|
||||
}
|
||||
} else {
|
||||
for r in &results {
|
||||
println!("{}", r.key);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue