use std::path::{Path, PathBuf};
use anyhow::Result;
use colored::Colorize;
use crate::cli_types::MetricArg;
use crate::cli_types::StorageModeArg;
use crate::import;
pub fn handle_export(
path: &Path,
collection: &str,
output: Option<PathBuf>,
include_vectors: bool,
) -> Result<()> {
let db = velesdb_core::Database::open(path)?;
let col = db.get_vector_collection(collection).ok_or_else(|| {
anyhow::anyhow!(
"Vector collection '{}' not found. Export requires a vector collection.",
collection
)
})?;
let cfg = col.config();
let output_path = output.unwrap_or_else(|| PathBuf::from(format!("{collection}.json")));
println!(
"Exporting {} records from {}...",
cfg.point_count,
collection.green()
);
let records = collect_export_records(&col, cfg.point_count, include_vectors);
std::fs::write(&output_path, serde_json::to_string_pretty(&records)?)?;
println!(
"{} Exported {} records to {}",
"\u{2713}".green(),
records.len(),
output_path.display().to_string().green()
);
Ok(())
}
fn collect_export_records(
col: &velesdb_core::VectorCollection,
point_count: usize,
include_vectors: bool,
) -> Vec<serde_json::Value> {
let mut records = Vec::new();
let batch_size = 1000;
for batch_start in (0..point_count).step_by(batch_size) {
let ids: Vec<u64> =
((batch_start as u64 + 1)..=((batch_start + batch_size) as u64)).collect();
let points = col.get(&ids);
for point in points.into_iter().flatten() {
let mut record = serde_json::Map::new();
record.insert("id".to_string(), serde_json::json!(point.id));
if include_vectors {
record.insert("vector".to_string(), serde_json::json!(point.vector));
}
if let Some(payload) = &point.payload {
record.insert("payload".to_string(), payload.clone());
}
records.push(serde_json::Value::Object(record));
}
}
records
}
#[allow(clippy::too_many_arguments)] pub fn handle_import(
file: &Path,
database: &Path,
collection: String,
dimension: Option<usize>,
metric: MetricArg,
storage_mode: StorageModeArg,
id_column: String,
vector_column: String,
batch_size: usize,
progress: bool,
) -> Result<()> {
let db = velesdb_core::Database::open(database)?;
let config = import::ImportConfig {
collection,
dimension,
metric: metric.into(),
storage_mode: storage_mode.into(),
batch_size,
id_column,
vector_column,
show_progress: progress,
};
let ext = file.extension().and_then(|e| e.to_str()).unwrap_or("");
let stats = match ext.to_lowercase().as_str() {
"jsonl" | "ndjson" => import::import_jsonl(&db, file, &config)?,
"csv" => import::import_csv(&db, file, &config)?,
_ => {
anyhow::bail!("Unsupported file format: {}. Use .csv or .jsonl", ext);
}
};
print_import_summary(&stats);
Ok(())
}
fn print_import_summary(stats: &import::ImportStats) {
println!("\n{}", "Import Summary".green().bold());
println!(" Total records: {}", stats.total);
println!(" Imported: {}", stats.imported.to_string().green());
if stats.errors > 0 {
println!(" Errors: {}", stats.errors.to_string().red());
}
println!(" Duration: {} ms", stats.duration_ms);
println!(
" Throughput: {:.0} records/sec",
stats.records_per_sec()
);
}
pub fn handle_get(path: &Path, collection: &str, id: u64, format: &str) -> Result<()> {
let db = velesdb_core::Database::open(path)?;
let col = db
.get_vector_collection(collection)
.ok_or_else(|| anyhow::anyhow!("Collection '{}' not found", collection))?;
let points = col.get(&[id]);
if format == "json" {
print_point_json(points);
} else {
print_point_table(points, id);
}
Ok(())
}
fn print_point_json(points: Vec<Option<velesdb_core::Point>>) {
if let Some(point) = points.into_iter().flatten().next() {
let output = serde_json::json!({
"id": point.id,
"vector": point.vector,
"payload": point.payload
});
if let Ok(json) = serde_json::to_string_pretty(&output) {
println!("{json}");
}
} else {
println!("null");
}
}
fn print_point_table(points: Vec<Option<velesdb_core::Point>>, id: u64) {
if let Some(point) = points.into_iter().flatten().next() {
println!("\n{}", "Point Found".bold().underline());
println!(" ID: {}", point.id.to_string().green());
println!(" Vector: [{} dimensions]", point.vector.len());
if let Some(payload) = &point.payload {
println!(" Payload: {payload}");
}
} else {
println!("{} Point with ID {} not found", "\u{274c}".red(), id);
}
}
pub fn handle_upsert(
path: &Path,
collection: &str,
id: u64,
vector: Option<String>,
payload: Option<String>,
) -> Result<()> {
let db = velesdb_core::Database::open(path)?;
let col = db
.get_vector_collection(collection)
.ok_or_else(|| anyhow::anyhow!("Vector collection '{}' not found", collection))?;
let vec_data = parse_vector_json(vector)?;
let payload_data = parse_payload_json(payload)?;
let point = velesdb_core::Point::new(id, vec_data, payload_data);
col.upsert(vec![point])
.map_err(|e| anyhow::anyhow!("Upsert failed: {e}"))?;
println!(
"{} Upserted point {} into '{}'",
"\u{2705}".green(),
id.to_string().green(),
collection.cyan()
);
Ok(())
}
fn parse_vector_json(raw: Option<String>) -> Result<Vec<f32>> {
match raw {
Some(v) => {
serde_json::from_str(&v).map_err(|e| anyhow::anyhow!("Invalid vector JSON: {e}"))
}
None => Ok(vec![]),
}
}
fn parse_payload_json(raw: Option<String>) -> Result<Option<serde_json::Value>> {
match raw {
Some(p) => {
let v = serde_json::from_str(&p)
.map_err(|e| anyhow::anyhow!("Invalid payload JSON: {e}"))?;
Ok(Some(v))
}
None => Ok(None),
}
}
pub fn handle_delete_points(path: &Path, collection: &str, ids: &[u64]) -> Result<()> {
let db = velesdb_core::Database::open(path)?;
let col = db
.get_vector_collection(collection)
.ok_or_else(|| anyhow::anyhow!("Vector collection '{}' not found", collection))?;
col.delete(ids)
.map_err(|e| anyhow::anyhow!("Delete failed: {e}"))?;
println!(
"{} Deleted {} point(s) from '{}'",
"\u{2705}".green(),
ids.len(),
collection.cyan()
);
Ok(())
}