iridium-db 0.2.0

A high-performance vector-graph hybrid storage and indexing engine
use std::path::PathBuf;

#[path = "cli/parse.rs"]
mod parse;
#[cfg(test)]
#[path = "cli/tests.rs"]
mod tests;

#[derive(Debug, PartialEq)]
pub(crate) enum Command {
    Help,
    PutEdge {
        node_id: u64,
        version: u64,
        payload: Vec<u8>,
    },
    PutVector {
        node_id: u64,
        version: u64,
        payload: Vec<u8>,
    },
    PutFull {
        node_id: u64,
        version: u64,
        adjacency: Vec<u64>,
    },
    Get {
        node_id: u64,
    },
    Traverse {
        node_id: u64,
    },
    Recover,
    Stats,
    Health,
    Metrics,
    MetricsProm,
    Status,
    TriggerCompaction {
        level: Option<u32>,
    },
    Recluster {
        force: bool,
    },
    IngestEdge {
        node_id: u64,
        version: u64,
        payload: Vec<u8>,
    },
    IngestFile {
        path: PathBuf,
        batch_size: usize,
    },
    IngestVector {
        node_id: u64,
        version: u64,
        payload: Vec<u8>,
    },
    IngestNode {
        node_id: u64,
        version: u64,
        adjacency: Vec<u64>,
    },
    IngestBatchEdge {
        start_node_id: u64,
        count: u64,
        version: u64,
        payload_prefix: String,
    },
    IngestBatchEdgeLoop {
        start_node_id: u64,
        count: u64,
        rounds: u64,
        version: u64,
        payload_prefix: String,
    },
    Parse {
        query: String,
    },
    Explain {
        query: String,
    },
    Query {
        query: String,
    },
    ContractReport {
        scenario: String,
    },
    EvidenceReport {
        surface: String,
    },
    ServiceReport {
        listen: Option<String>,
    },
    ServiceValidate {
        listen: Option<String>,
        telemetry_endpoint: Option<String>,
        tls: String,
        admin_token: Option<String>,
        max_requests: usize,
    },
    ServiceServe {
        listen: Option<String>,
        telemetry_endpoint: Option<String>,
        tls: String,
        admin_token: Option<String>,
        max_requests: usize,
    },
    Bench {
        iterations: u64,
        seed: u64,
        json_out: Option<PathBuf>,
    },
    CollectStats,
}

#[derive(Debug, PartialEq)]
pub(crate) struct ParsedArgs {
    pub command: Command,
    pub data_dir: Option<PathBuf>,
    pub wal_sync_policy: Option<String>,
    pub wal_sync_interval_ms: Option<u64>,
}

pub(crate) fn help_text() -> &'static str {
    concat!(
        "Iridium CLI\n\n",
        "Usage:\n",
        "  ir <command> [options]\n",
        "  ir --data <path> <command> [options]\n",
        "  ir --help\n\n",
        "Global Options:\n",
        "  --data <path>                               Data root directory (contains wal/sst/manifest)\n\n",
        "  --wal-sync-policy <always|interval|manual> WAL durability policy\n",
        "  --wal-sync-interval-ms <u64>               WAL interval sync millis (interval policy)\n\n",
        "Core Commands:\n",
        "  help | -h | --help                         Show this help text\n",
        "  parse <query>                              Parse query and print AST JSON\n",
        "  explain <query>                            Explain query plan JSON\n",
        "  query <query>                              Execute query and print row stream JSON\n",
        "  contract-report <acceptance|retrieval-quality>\n",
        "                                             Print shared Alloy assurance/telemetry contract JSON\n",
        "  evidence-report <restart-requery>\n",
        "                                             Print typed Iridium scenario evidence JSON\n",
        "  service-report [--listen <host:port>]\n",
        "                                             Print first service-path contract JSON\n",
        "  service-validate [--listen <host:port>] [--telemetry-endpoint <target>] [--tls <disabled|operator-optional>] [--admin-token <token>] [--max-requests <n>]\n",
        "                                             Validate the first service candidate config surface\n",
        "  service-serve [--listen <host:port>] [--telemetry-endpoint <target>] [--tls <disabled|operator-optional>] [--admin-token <token>] [--max-requests <n>]\n",
        "                                             Run the first minimal service candidate surface\n",
        "  bench <iterations> [--seed <u64>] [--json <path>]\n",
        "                                             Run benchmark suite\n\n",
        "Storage Commands:\n",
        "  put-edge <node_id> <version> <payload>\n",
        "  put-vector <node_id> <version> <csv_f32>\n",
        "  put-full <node_id> <version> <adjacency_csv>\n",
        "  get <node_id>\n",
        "  traverse <node_id>\n",
        "  recover\n",
        "  stats\n",
        "  status\n",
        "  trigger-compaction [--level <u32>]\n",
        "  recluster [--force]\n\n",
        "Ingest Commands:\n",
        "  ingest-edge <node_id> <version> <payload>\n",
        "  ingest-vector <node_id> <version> <csv_f32>\n",
        "  ingest-node <node_id> <version> <adjacency_csv>\n",
        "  ingest-batch-edge <start_node_id> <count> <version> <payload_prefix>\n",
        "  ingest-batch-edge-loop <start_node_id> <count> <rounds> <version> <payload_prefix>\n",
        "  ingest --file <path> [--batch-size <n>]\n\n",
        "Ops Commands:\n",
        "  health\n",
        "  metrics\n",
        "  metrics-prom\n\n",
        "Planner Commands:\n",
        "  collect-stats           Collect planner statistics from storage and arm the cost-based optimizer\n\n",
        "Notes:\n",
        "  - Default data root is ./data for storage/query/ingest commands.\n",
        "  - Query runtime tuning env vars: IR_QUERY_MORSEL_SIZE,\n",
        "    IR_QUERY_PARALLEL_WORKERS, IR_QUERY_SCAN_LIMIT_MULTIPLIER,\n",
        "    IR_QUERY_SCAN_MIN.\n",
    )
}

pub(crate) fn parse_args(args: &[String]) -> Result<ParsedArgs, String> {
    if args.len() < 2 {
        return Ok(ParsedArgs {
            command: Command::Help,
            data_dir: None,
            wal_sync_policy: None,
            wal_sync_interval_ms: None,
        });
    }

    let mut idx = 1usize;
    let mut data_dir: Option<PathBuf> = None;
    let mut wal_sync_policy: Option<String> = None;
    let mut wal_sync_interval_ms: Option<u64> = None;
    while idx < args.len() {
        match args[idx].as_str() {
            "--data" => {
                if idx + 1 >= args.len() {
                    return Err("missing value for --data".to_string());
                }
                data_dir = Some(PathBuf::from(&args[idx + 1]));
                idx += 2;
            }
            "--wal-sync-policy" => {
                if idx + 1 >= args.len() {
                    return Err("missing value for --wal-sync-policy".to_string());
                }
                wal_sync_policy = Some(args[idx + 1].to_ascii_lowercase());
                idx += 2;
            }
            "--wal-sync-interval-ms" => {
                if idx + 1 >= args.len() {
                    return Err("missing value for --wal-sync-interval-ms".to_string());
                }
                wal_sync_interval_ms = Some(
                    args[idx + 1]
                        .parse::<u64>()
                        .map_err(|_| "invalid --wal-sync-interval-ms".to_string())?,
                );
                idx += 2;
            }
            "help" | "-h" | "--help" => {
                return Ok(ParsedArgs {
                    command: Command::Help,
                    data_dir,
                    wal_sync_policy,
                    wal_sync_interval_ms,
                });
            }
            _ => break,
        }
    }
    if idx >= args.len() {
        return Ok(ParsedArgs {
            command: Command::Help,
            data_dir,
            wal_sync_policy,
            wal_sync_interval_ms,
        });
    }

    let mut command_args = Vec::with_capacity(args.len() - idx + 1);
    command_args.push(args[0].clone());
    command_args.extend_from_slice(&args[idx..]);
    let command = parse::parse_command_args(&command_args)?;
    Ok(ParsedArgs {
        command,
        data_dir,
        wal_sync_policy,
        wal_sync_interval_ms,
    })
}