use grafeo_common::types::Value;
use grafeo_engine::GrafeoDB;
use std::io::Write;
fn temp_file(name: &str, content: &str) -> String {
let dir = std::env::temp_dir().join("grafeo_test_load");
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join(name);
let mut f = std::fs::File::create(&path).unwrap();
f.write_all(content.as_bytes()).unwrap();
path.to_string_lossy().replace('\\', "/")
}
#[test]
fn test_csv_with_utf8_bom() {
let csv = "\u{feff}name,score\nAlix,95\nGus,88";
let path = temp_file("bom_test.csv", csv);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT CSV WITH HEADERS AS row RETURN row.name AS name ORDER BY row.name"
))
.unwrap();
assert_eq!(r.rows.len(), 2);
assert_eq!(r.rows[0][0], Value::String("Alix".into()));
assert_eq!(r.rows[1][0], Value::String("Gus".into()));
}
#[test]
fn test_csv_without_headers_returns_list() {
let csv = "Alix,30\nGus,25\n";
let path = temp_file("no_headers.csv", csv);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT CSV AS row RETURN row[0] AS name"
))
.unwrap();
assert_eq!(r.rows.len(), 2);
assert_eq!(r.rows[0][0], Value::String("Alix".into()));
}
#[test]
fn test_csv_windows_line_endings() {
let csv = "name,city\r\nAlix,Amsterdam\r\nGus,Berlin\r\n";
let path = temp_file("crlf.csv", csv);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT CSV WITH HEADERS AS row RETURN row.name AS n ORDER BY row.name"
))
.unwrap();
assert_eq!(r.rows.len(), 2);
assert_eq!(r.rows[0][0], Value::String("Alix".into()));
}
#[test]
fn test_csv_quoted_commas() {
let csv = "name,bio\n\"Alix\",\"likes cats, dogs\"\n\"Gus\",\"quiet\"\n";
let path = temp_file("quoted.csv", csv);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT CSV WITH HEADERS AS row RETURN row.bio AS bio ORDER BY row.name"
))
.unwrap();
assert_eq!(r.rows[0][0], Value::String("likes cats, dogs".into()));
}
#[test]
fn test_csv_headers_only() {
let csv = "name,age\n";
let path = temp_file("empty_data.csv", csv);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT CSV WITH HEADERS AS row RETURN row.name AS name"
))
.unwrap();
assert_eq!(r.rows.len(), 0);
}
#[test]
fn test_csv_pipe_delimiter() {
let csv = "name|score\nAlix|100\nGus|85\n";
let path = temp_file("pipe.csv", csv);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT CSV WITH HEADERS AS row FIELDTERMINATOR '|' RETURN row.score AS s ORDER BY row.name"
))
.unwrap();
assert_eq!(r.rows.len(), 2);
assert_eq!(r.rows[0][0], Value::String("100".into()));
}
#[test]
fn test_csv_semicolon_delimiter() {
let csv = "name;city\nAlix;Amsterdam\nGus;Berlin\n";
let path = temp_file("semicolon.csv", csv);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT CSV WITH HEADERS AS row FIELDTERMINATOR ';' RETURN row.city AS c ORDER BY row.name"
))
.unwrap();
assert_eq!(r.rows[0][0], Value::String("Amsterdam".into()));
assert_eq!(r.rows[1][0], Value::String("Berlin".into()));
}
#[test]
#[cfg(feature = "jsonl-import")]
fn test_jsonl_blank_lines() {
let jsonl = "{\"name\":\"Alix\"}\n\n{\"name\":\"Gus\"}\n\n";
let path = temp_file("blanks.jsonl", jsonl);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT JSONL AS row RETURN row.name AS name ORDER BY row.name"
))
.unwrap();
assert_eq!(r.rows.len(), 2);
}
#[test]
#[cfg(feature = "jsonl-import")]
fn test_jsonl_nested_objects() {
let jsonl = "{\"name\":\"Alix\",\"address\":{\"city\":\"Amsterdam\",\"zip\":\"1011\"}}\n";
let path = temp_file("nested.jsonl", jsonl);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT JSONL AS row RETURN row.name AS name"
))
.unwrap();
assert_eq!(r.rows.len(), 1);
assert_eq!(r.rows[0][0], Value::String("Alix".into()));
}
#[test]
#[cfg(feature = "jsonl-import")]
fn test_jsonl_arrays() {
let jsonl = "{\"name\":\"Alix\",\"tags\":[\"rust\",\"graph\"]}\n";
let path = temp_file("arrays.jsonl", jsonl);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT JSONL AS row RETURN row.name AS name"
))
.unwrap();
assert_eq!(r.rows.len(), 1);
}
#[test]
#[cfg(feature = "jsonl-import")]
fn test_jsonl_null_and_bool() {
let jsonl = "{\"name\":\"Alix\",\"active\":true,\"note\":null}\n{\"name\":\"Gus\",\"active\":false,\"note\":\"hello\"}\n";
let path = temp_file("null_bool.jsonl", jsonl);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT JSONL AS row RETURN row.active AS a, row.note AS n ORDER BY row.name"
))
.unwrap();
assert_eq!(r.rows.len(), 2);
assert_eq!(r.rows[0][0], Value::Bool(true));
assert_eq!(r.rows[0][1], Value::Null);
assert_eq!(r.rows[1][0], Value::Bool(false));
}
#[test]
fn test_load_data_file_not_found() {
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session.execute(
"LOAD DATA FROM '/does/not/exist/data.csv' FORMAT CSV WITH HEADERS AS row RETURN row",
);
assert!(r.is_err());
}
#[test]
fn test_csv_insert_nodes() {
let csv = "name,age\nVincent,40\nJules,35\n";
let path = temp_file("insert_nodes.csv", csv);
let db = GrafeoDB::new_in_memory();
let session = db.session();
session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT CSV WITH HEADERS AS row INSERT (:Person {{name: row.name}})"
))
.unwrap();
let r = session
.execute("MATCH (p:Person) RETURN p.name AS name ORDER BY name")
.unwrap();
assert_eq!(r.rows.len(), 2);
assert_eq!(r.rows[0][0], Value::String("Jules".into()));
assert_eq!(r.rows[1][0], Value::String("Vincent".into()));
}
#[test]
#[cfg(feature = "jsonl-import")]
fn test_ndjson_alias() {
let jsonl = "{\"name\":\"Mia\"}\n{\"name\":\"Butch\"}\n";
let path = temp_file("ndjson.jsonl", jsonl);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT NDJSON AS row RETURN row.name AS name ORDER BY row.name"
))
.unwrap();
assert_eq!(r.rows.len(), 2);
}
#[test]
fn test_csv_fewer_columns_than_header() {
let csv = "name,score,grade\nAlix,95\nGus,88,A";
let path = temp_file("fewer_cols.csv", csv);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT CSV WITH HEADERS AS row RETURN row"
))
.unwrap();
assert_eq!(r.rows.len(), 2, "both rows should be returned");
}
#[test]
fn test_csv_more_columns_than_header() {
let csv = "name,score\nAlix,95,extra_value\nGus,88";
let path = temp_file("more_cols.csv", csv);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT CSV WITH HEADERS AS row RETURN row"
))
.unwrap();
assert_eq!(r.rows.len(), 2, "both rows should be returned");
}
#[test]
fn test_csv_empty_file() {
let csv = "";
let path = temp_file("empty.csv", csv);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT CSV WITH HEADERS AS row RETURN row"
))
.unwrap();
assert_eq!(r.rows.len(), 0, "empty CSV should return no rows");
}
#[test]
#[cfg(feature = "jsonl-import")]
fn test_jsonl_invalid_json_line() {
let jsonl = "{\"name\":\"Alix\"}\n{invalid json}\n{\"name\":\"Gus\"}";
let path = temp_file("invalid.jsonl", jsonl);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT JSONL AS row RETURN row.name AS name"
));
assert!(r.is_err(), "invalid JSON line should produce an error");
let err = r.unwrap_err().to_string();
assert!(
err.contains("JSON") || err.contains("parse"),
"error should mention JSON parsing, got: {err}"
);
}
#[test]
fn test_csv_only_newlines() {
let csv = "\n\n\n";
let path = temp_file("only_newlines.csv", csv);
let db = GrafeoDB::new_in_memory();
let session = db.session();
let r = session
.execute(&format!(
"LOAD DATA FROM '{path}' FORMAT CSV WITH HEADERS AS row RETURN row"
))
.unwrap();
assert_eq!(r.rows.len(), 0, "newlines-only CSV should return no rows");
}