use powdb_query::ast::Literal;
use powdb_query::executor::Engine;
use powdb_query::result::{QueryError, QueryResult};
use powdb_storage::types::Value;
const UUID1: &str = "550e8400-e29b-41d4-a716-446655440000";
const UUID1_BYTES: [u8; 16] = [
0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44, 0x00, 0x00,
];
const UUID2: &str = "00000000-0000-0000-0000-000000000002";
fn rows(result: QueryResult) -> Vec<Vec<Value>> {
match result {
QueryResult::Rows { rows, .. } => rows,
other => panic!("expected rows, got {other:?}"),
}
}
fn count(engine: &mut Engine, table: &str) -> i64 {
match engine.execute_powql(&format!("count({table})")).unwrap() {
QueryResult::Scalar(Value::Int(n)) => n,
QueryResult::Rows { rows, .. } => match rows[0][0] {
Value::Int(n) => n,
ref v => panic!("expected int count, got {v:?}"),
},
other => panic!("expected count, got {other:?}"),
}
}
#[test]
fn string_coercion_round_trip() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_powql("type T { id: uuid, blob: bytes }")
.unwrap();
engine
.execute_powql(&format!(
r#"insert T {{ id := "{UUID1}", blob := "\\xdeadbeef" }}"#
))
.unwrap();
let r = rows(engine.execute_powql("T { .id, .blob }").unwrap());
assert_eq!(r.len(), 1);
assert_eq!(r[0][0], Value::Uuid(UUID1_BYTES));
assert_eq!(r[0][1], Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef]));
}
#[test]
fn invalid_uuid_errors_all_or_nothing() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine.execute_powql("type T { id: uuid }").unwrap();
let err = engine
.execute_powql(&format!(
r#"insert T {{ id := "{UUID1}" }}, {{ id := "not-a-uuid" }}"#
))
.unwrap_err();
assert!(
matches!(&err, QueryError::Execution(m) if m.contains("uuid")),
"expected a uuid coercion error, got {err:?}"
);
assert_eq!(count(&mut engine, "T"), 0, "no row may be written");
}
#[test]
fn bad_hex_errors() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine.execute_powql("type T { blob: bytes }").unwrap();
assert!(engine
.execute_powql(r#"insert T { blob := "\\xabc" }"#)
.is_err());
assert!(engine
.execute_powql(r#"insert T { blob := "deadbeef" }"#)
.is_err());
assert_eq!(count(&mut engine, "T"), 0);
}
#[test]
fn sugar_and_cast_forms_in_insert_position() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_powql("type T { id: uuid, blob: bytes }")
.unwrap();
engine
.execute_powql(&format!(
r#"insert T {{ id := uuid("{UUID1}"), blob := bytes("\\x0102") }}"#
))
.unwrap();
engine
.execute_powql(&format!(r#"insert T {{ id := cast("{UUID2}", "uuid") }}"#))
.unwrap();
let r = rows(engine.execute_powql("T order .id { .id, .blob }").unwrap());
assert_eq!(r.len(), 2);
assert_eq!(
r[0][0],
Value::Uuid([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2])
);
assert_eq!(r[1][0], Value::Uuid(UUID1_BYTES));
assert_eq!(r[1][1], Value::Bytes(vec![0x01, 0x02]));
}
#[test]
fn datetime_cast_in_insert_position() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine.execute_powql("type T { at: datetime }").unwrap();
engine
.execute_powql(r#"insert T { at := cast(1718000000, "datetime") }"#)
.unwrap();
let r = rows(engine.execute_powql("T { .at }").unwrap());
assert_eq!(r[0][0], Value::DateTime(1718000000));
}
#[test]
fn filter_uuid_cache_hit_correctness() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine.execute_powql("type T { id: uuid }").unwrap();
engine
.execute_powql(&format!(
r#"insert T {{ id := "{UUID1}" }}, {{ id := "{UUID2}" }}"#
))
.unwrap();
let r1 = rows(
engine
.execute_powql(&format!(r#"T filter .id = uuid("{UUID1}") {{ .id }}"#))
.unwrap(),
);
assert_eq!(r1.len(), 1);
assert_eq!(r1[0][0], Value::Uuid(UUID1_BYTES));
let r2 = rows(
engine
.execute_powql(&format!(r#"T filter .id = uuid("{UUID2}") {{ .id }}"#))
.unwrap(),
);
assert_eq!(r2.len(), 1);
assert_eq!(
r2[0][0],
Value::Uuid([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2])
);
}
#[test]
fn prepared_insert_stores_real_uuid() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine.execute_powql("type T { id: uuid }").unwrap();
let prep = engine
.prepare(r#"insert T { id := "00000000-0000-0000-0000-000000000000" }"#)
.unwrap();
engine
.execute_prepared(&prep, &[Literal::String(UUID1.into())])
.unwrap();
let r = rows(engine.execute_powql("T { .id }").unwrap());
assert_eq!(r.len(), 1);
assert_eq!(
r[0][0],
Value::Uuid(UUID1_BYTES),
"prepared insert must store a typed uuid, not a raw string"
);
}
#[test]
fn sql_frontend_uuid_bytea_round_trip() {
let dir = tempfile::tempdir().unwrap();
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_sql("CREATE TABLE t (id uuid, blob bytea)")
.unwrap();
engine
.execute_sql(&format!(
r#"INSERT INTO t (id, blob) VALUES ('{UUID1}', '\\xdeadbeef')"#
))
.unwrap();
engine
.execute_sql(&format!(
r#"INSERT INTO t (id, blob) VALUES ('{UUID2}', '\\x00')"#
))
.unwrap();
for _ in 0..2 {
let r = rows(engine.execute_sql("SELECT id, blob FROM t").unwrap());
assert_eq!(r.len(), 2);
}
let r = rows(
engine
.execute_powql(&format!(r#"t filter .id = uuid("{UUID1}") {{ .blob }}"#))
.unwrap(),
);
assert_eq!(r[0][0], Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef]));
}
#[test]
fn wal_replay_durability_of_uuid_bytes() {
let dir = tempfile::tempdir().unwrap();
{
let mut engine = Engine::new(dir.path()).unwrap();
engine
.execute_powql("type T { id: uuid, blob: bytes }")
.unwrap();
engine
.execute_powql(&format!(
r#"insert T {{ id := "{UUID1}", blob := "\\xcafe" }}"#
))
.unwrap();
}
let mut engine = Engine::new(dir.path()).unwrap();
let r = rows(engine.execute_powql("T { .id, .blob }").unwrap());
assert_eq!(r.len(), 1, "uuid/bytes row must survive WAL replay");
assert_eq!(r[0][0], Value::Uuid(UUID1_BYTES));
assert_eq!(r[0][1], Value::Bytes(vec![0xca, 0xfe]));
}