query: replace CLI flags with pipe syntax
degree > 15 | sort degree | limit 10 | select degree,category * | sort weight asc | limit 20 category = core | count Output modifiers live in the grammar now, not in CLI flags. Also adds * wildcard for "all nodes" and string-aware sort fallback.
This commit is contained in:
parent
5c641d9f8a
commit
18face7063
2 changed files with 144 additions and 97 deletions
73
src/main.rs
73
src/main.rs
|
|
@ -195,10 +195,9 @@ Commands:
|
|||
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)
|
||||
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\"");
|
||||
query 'EXPR | stages' Query the memory graph
|
||||
Stages: sort F [asc], limit N, select F,F, count
|
||||
Ex: \"degree > 15 | sort degree | limit 10\"");
|
||||
}
|
||||
|
||||
fn cmd_search(args: &[String]) -> Result<(), String> {
|
||||
|
|
@ -1624,48 +1623,29 @@ fn cmd_interference(args: &[String]) -> Result<(), String> {
|
|||
|
||||
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());
|
||||
return Err("Usage: poc-memory query 'EXPR | stage | stage ...'\n\n\
|
||||
Expressions:\n \
|
||||
degree > 15 property filter\n \
|
||||
key ~ 'journal.*' AND degree > 10 boolean + regex\n \
|
||||
neighbors('identity.md') WHERE ... graph traversal\n \
|
||||
community_id = community('key') function as value\n \
|
||||
* all nodes\n\n\
|
||||
Pipe stages:\n \
|
||||
| sort FIELD [asc] sort (desc by default)\n \
|
||||
| limit N cap results\n \
|
||||
| select F,F,... output fields as TSV\n \
|
||||
| count just show 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 query_str = args.join(" ");
|
||||
let store = capnp_store::Store::load()?;
|
||||
let graph = store.build_graph();
|
||||
|
||||
let results = query::execute_query(&store, &graph, &query_str, &opts)?;
|
||||
let stages = query::output_stages(&query_str)?;
|
||||
let results = query::execute_query(&store, &graph, &query_str)?;
|
||||
|
||||
if opts.count_only {
|
||||
// Check for count stage
|
||||
if stages.iter().any(|s| matches!(s, query::Stage::Count)) {
|
||||
println!("{}", results.len());
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -1675,15 +1655,20 @@ fn cmd_query(args: &[String]) -> Result<(), String> {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
// If --fields specified, show as TSV with header
|
||||
if !opts.fields.is_empty() {
|
||||
// Check for select stage
|
||||
let fields: Option<&Vec<String>> = stages.iter().find_map(|s| match s {
|
||||
query::Stage::Select(f) => Some(f),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
if let Some(fields) = fields {
|
||||
let mut header = vec!["key".to_string()];
|
||||
header.extend(opts.fields.iter().cloned());
|
||||
header.extend(fields.iter().cloned());
|
||||
println!("{}", header.join("\t"));
|
||||
|
||||
for r in &results {
|
||||
let mut row = vec![r.key.clone()];
|
||||
for f in &opts.fields {
|
||||
for f in fields {
|
||||
row.push(query::format_field(f, &r.key, &store, &graph));
|
||||
}
|
||||
println!("{}", row.join("\t"));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue