use std::time::Instant;
use hyperdb_api::{Connection, CreateMode, HyperProcess, Parameters};
mod common;
fn params() -> Parameters {
let dir = std::env::current_dir().unwrap().join("test_results");
std::fs::create_dir_all(&dir).unwrap();
let mut p = Parameters::new();
p.set(
"log_dir",
dir.canonicalize().unwrap().to_string_lossy().to_string(),
);
p
}
fn temp_db(tag: &str) -> (tempfile::TempDir, std::path::PathBuf) {
let dir = tempfile::tempdir().unwrap();
let db = dir.path().join(format!("{tag}.hyper"));
(dir, db)
}
#[test]
#[ignore = "Phase 0 measurement spike; run manually with --ignored --nocapture"]
fn spike_startup_timing() {
let t0 = Instant::now();
let hyper = HyperProcess::new(None, Some(¶ms())).expect("start hyperd");
let cold = t0.elapsed();
let (_g, db) = temp_db("spike_timing");
let t1 = Instant::now();
let conn = Connection::new(&hyper, &db, CreateMode::CreateAndReplace).expect("connect");
let connect = t1.elapsed();
let t2 = Instant::now();
let _ = conn.execute_query("SELECT 1").expect("select 1");
let first_query = t2.elapsed();
let t3 = Instant::now();
let hyper2 = HyperProcess::new(None, Some(¶ms())).expect("start hyperd #2");
let warm_second_process = t3.elapsed();
drop(hyper2);
println!("\n=== SPIKE 1: startup timing ===");
println!("cold HyperProcess::new() : {cold:?}");
println!("Connection::new() (create db) : {connect:?}");
println!("first 'SELECT 1' round-trip : {first_query:?}");
println!("2nd HyperProcess::new() : {warm_second_process:?}");
println!("(architecture amortizes ONE start across all macros in a crate)\n");
}
#[test]
#[ignore = "Phase 0 measurement spike; run manually with --ignored --nocapture"]
fn spike_concurrency_stress() {
const N: usize = 16;
println!("\n=== SPIKE 2 (S3): {N} concurrent HyperProcess::new() ===");
let t0 = Instant::now();
let handles: Vec<_> = (0..N)
.map(|i| {
std::thread::spawn(move || {
let start = Instant::now();
let hyper = HyperProcess::new(None, Some(¶ms()))
.unwrap_or_else(|e| panic!("instance {i} failed to start: {e}"));
let (_g, db) = temp_db(&format!("spike_conc_{i}"));
let conn = Connection::new(&hyper, &db, CreateMode::CreateAndReplace)
.unwrap_or_else(|e| panic!("instance {i} failed to connect: {e}"));
let v: i32 = conn
.execute_scalar_query::<i32>("SELECT 1")
.expect("scalar")
.expect("non-null");
assert_eq!(v, 1, "instance {i} returned wrong value");
(i, start.elapsed())
})
})
.collect();
let mut elapsed = Vec::new();
let mut failures = 0;
for h in handles {
match h.join() {
Ok((i, dur)) => elapsed.push((i, dur)),
Err(_) => failures += 1,
}
}
let wall = t0.elapsed();
elapsed.sort_by_key(|(_, d)| *d);
let slowest = elapsed.last().map(|(_, d)| *d).unwrap_or_default();
let fastest = elapsed.first().map(|(_, d)| *d).unwrap_or_default();
println!(
"started+queried {} / {N} instances concurrently",
elapsed.len()
);
println!("failures: {failures}");
println!("per-instance fastest: {fastest:?}, slowest: {slowest:?}");
println!("total wall-clock for all {N}: {wall:?}");
println!("(if no collisions/port conflicts, all {N} succeed)\n");
assert_eq!(
failures, 0,
"some concurrent instances failed — collision risk!"
);
}
#[test]
#[ignore = "Phase 0 measurement spike; run manually with --ignored --nocapture"]
fn spike_missing_table_error_shape() {
let hyper = HyperProcess::new(None, Some(¶ms())).expect("start hyperd");
let (_g, db) = temp_db("spike_err");
let conn = Connection::new(&hyper, &db, CreateMode::CreateAndReplace).expect("connect");
println!("\n=== SPIKE 3: missing-table error shape ===");
let dry_run = |sql: &str| -> hyperdb_api::Result<()> {
let mut rs = conn.execute_query(sql)?;
while rs.next_chunk()?.is_some() {}
Ok(())
};
let err = dry_run("SELECT * FROM ghosts").expect_err("should fail: table doesn't exist");
let msg = format!("{err}");
let dbg = format!("{err:?}");
println!("Display : {msg}");
println!("Debug : {dbg}");
println!("contains 'ghosts': {}", msg.contains("ghosts"));
conn.execute_command("CREATE TABLE t (id BIGINT, name TEXT)")
.expect("create t");
let col_err =
dry_run("SELECT id, ema1l FROM t").expect_err("should fail: column doesn't exist");
let col_msg = format!("{col_err}");
println!("\nmissing-column Display: {col_msg}");
println!("contains 'ema1l': {}", col_msg.contains("ema1l"));
println!("(if the bad identifier appears verbatim, Hyper-first extraction is viable)\n");
}
#[test]
#[ignore = "Phase 0 measurement spike; run manually with --ignored --nocapture"]
fn spike_limit_zero_dry_run() {
let hyper = HyperProcess::new(None, Some(¶ms())).expect("start hyperd");
let (_g, db) = temp_db("spike_dry");
let conn = Connection::new(&hyper, &db, CreateMode::CreateAndReplace).expect("connect");
conn.execute_command(
"CREATE TABLE users (id BIGINT, name TEXT, email TEXT, score DOUBLE PRECISION)",
)
.expect("create users");
println!("\n=== SPIKE 4: LIMIT 0 dry-run schema ===");
for (label, sql) in [
("plain LIMIT 0", "SELECT id, name, email FROM users LIMIT 0"),
(
"CTE wrapper",
"WITH __hdb_q AS (SELECT id, name, email FROM users) SELECT * FROM __hdb_q LIMIT 0",
),
("SELECT *", "SELECT * FROM users LIMIT 0"),
("expression no FROM", "SELECT 1 AS a, 'x' AS b LIMIT 0"),
] {
match conn.execute_query(sql) {
Ok(mut rs) => {
match rs.next_chunk() {
Ok(_) => {}
Err(e) => {
println!("[{label}] drain ERROR: {e}");
continue;
}
}
match rs.schema() {
Some(s) => {
let cols: Vec<String> = s
.columns()
.iter()
.map(|c| format!("{}:{:?}", c.name(), c.sql_type()))
.collect();
println!("[{label}] {} cols -> {}", s.column_count(), cols.join(", "));
}
None => println!("[{label}] schema() returned None AFTER drain (!)"),
}
}
Err(e) => println!("[{label}] submit ERROR: {e}"),
}
}
println!("(if column names + SqlTypes come back on zero rows, the dry-run mechanism works)\n");
}