Files
core/test/rust/runtime/database/scalar.rs
T
2026-11-03 03:32:07 +00:00

274 lines
10 KiB
Rust

use super::*;
use dever_runtime::{database::scalar, value::decimal::Decimal};
#[test]
fn decimal_storage_covers_every_precision_and_scale() {
for precision in 1..=34 {
let limit = 10_i128.pow(precision);
for scale in 0..=precision {
for coefficient in [-limit + 1, -1, 0, 1, limit - 1] {
let value = Decimal::parse(&format!("{coefficient}E-{scale}")).unwrap();
let key = scalar::decimal::encode(value, precision, scale).unwrap();
assert_eq!(
key,
format!(
"{:0width$}",
coefficient + limit,
width = precision as usize + 1
)
);
assert_eq!(
scalar::decimal::decode(&key, precision, scale).unwrap(),
value
);
}
for coefficient in [-limit, limit] {
let value = Decimal::parse(&format!("{coefficient}E-{scale}")).unwrap();
assert!(scalar::decimal::encode(value, precision, scale).is_err());
}
}
}
}
#[test]
fn decimal_storage_rejects_non_digit_keys() {
for position in 0..6 {
for byte in 0..=127u8 {
if byte.is_ascii_digit() {
continue;
}
let mut key = b"100100".to_vec();
key[position] = byte;
assert!(scalar::decimal::decode(std::str::from_utf8(&key).unwrap(), 5, 2).is_err());
}
}
}
#[test]
fn decimal_keys_are_exact_unique_and_sort_like_numbers() {
let values = [
"-999.99", "-100", "-1.01", "-0.01", "0", "0.01", "1.01", "100", "999.99",
];
let encoded =
values.map(|text| scalar::decimal::encode(Decimal::parse(text).unwrap(), 5, 2).unwrap());
assert!(encoded.windows(2).all(|pair| pair[0] < pair[1]));
for (text, key) in values.iter().zip(&encoded) {
assert_eq!(
scalar::decimal::decode(key, 5, 2).unwrap(),
Decimal::parse(text).unwrap()
);
}
for text in ["1", "1.0", "1.00", "1.000", "1E0"] {
assert_eq!(
scalar::decimal::encode(Decimal::parse(text).unwrap(), 5, 2).unwrap(),
"100100"
);
}
for text in ["0", "-0", "0E-6176"] {
assert_eq!(
scalar::decimal::encode(Decimal::parse(text).unwrap(), 5, 2).unwrap(),
"100000"
);
}
for text in ["1000", "-1000", "0.001", "-0.001", "1E6144", "1E-6176"] {
assert_eq!(
scalar::decimal::encode(Decimal::parse(text).unwrap(), 5, 2)
.unwrap_err()
.kind,
Kind::InvalidData,
"{text}"
);
}
for (precision, scale, text) in [
(34, 0, "9999999999999999999999999999999999"),
(34, 34, "0.9999999999999999999999999999999999"),
(34, 4, "-999999999999999999999999999999.9999"),
] {
let value = Decimal::parse(text).unwrap();
let key = scalar::decimal::encode(value, precision, scale).unwrap();
assert_eq!(
scalar::decimal::decode(&key, precision, scale).unwrap(),
value
);
}
for key in [
"000000", "200000", "10001", "0100000", "+00001", "10000x", "100\0xx",
] {
assert_eq!(
scalar::decimal::decode(key, 5, 2).unwrap_err().kind,
Kind::InvalidData
);
}
let directory = Directory::new();
runtime()
.run(async |scope| {
let database = Database::open(&directory.0, options(), scope)
.await
.unwrap();
let mut executor = database.executor().await.unwrap();
executor
.initialize(
scope,
"CREATE TABLE numbers (amount TEXT UNIQUE, optional TEXT)",
)
.await
.unwrap();
for key in encoded.iter().rev() {
executor
.execute(
scope,
"INSERT INTO numbers(amount) VALUES (?1)",
vec![Parameter::Text(key.clone())],
)
.await
.unwrap();
}
let rows = executor
.query(
scope,
"SELECT amount, optional FROM numbers ORDER BY amount",
vec![],
20,
|row| {
assert_eq!(
row.get_ref(1)?.data_type(),
dever_runtime::database::DataType::Null
);
scalar::decimal(row, 0, 5, 2)
},
)
.await
.unwrap();
assert_eq!(rows, values.map(|text| Decimal::parse(text).unwrap()));
assert_eq!(
executor
.execute(
scope,
"INSERT INTO numbers(amount) VALUES (?1)",
vec![Parameter::Text(encoded[4].clone())]
)
.await
.unwrap_err()
.kind,
Kind::Constraint
);
})
.unwrap();
}
#[cfg(all(feature = "calendar", feature = "uuid"))]
#[test]
fn typed_scalars_preserve_storage_bytes_and_reject_corrupt_values() {
use dever_runtime::{
time::calendar::{Date, Time},
value::uuid::Uuid,
wire::json::Json,
};
let directory = Directory::new();
runtime()
.run(async |scope| {
let database = Database::open(&directory.0, options(), scope)
.await
.unwrap();
let mut executor = database.executor().await.unwrap();
executor
.initialize(
scope,
"CREATE TABLE values_test (uid BLOB, day INTEGER, time INTEGER, document TEXT)",
)
.await
.unwrap();
let uid = Uuid::parse("ffffffff-ffff-ffff-ffff-ffffffffffff").unwrap();
let document = "{\"value\":123456789012345678901234567890.0010,\"nested\":[true,null]}";
for (day, time) in [
("0000-01-01", "00:00:00"),
("1969-12-31", "23:59:59.999"),
("9999-12-31", "12:30:42.001"),
] {
let day = Date::parse(day).unwrap();
let time = Time::parse(time).unwrap();
executor
.execute(
scope,
"INSERT INTO values_test VALUES (?1,?2,?3,?4)",
vec![
Parameter::Blob(uid.as_bytes().to_vec()),
Parameter::Integer(day.millis()),
Parameter::Integer(time.millis()),
Parameter::Text(document.into()),
],
)
.await
.unwrap();
let actual = executor
.query(
scope,
"SELECT * FROM values_test ORDER BY rowid DESC LIMIT 1",
vec![],
1,
|row| {
assert_eq!(
row.get_ref(0)?.data_type(),
dever_runtime::database::DataType::Blob
);
assert_eq!(
row.get_ref(1)?.data_type(),
dever_runtime::database::DataType::Integer
);
Ok((
scalar::uuid(row, 0)?,
scalar::date(row, 1)?,
scalar::time(row, 2)?,
scalar::json(row, 3)?,
))
},
)
.await
.unwrap();
assert_eq!(actual, [(uid, day, time, Json::parse(document).unwrap())]);
assert_eq!(actual[0].3.as_str(), document);
}
executor
.execute(scope, "DELETE FROM values_test", vec![])
.await
.unwrap();
for (sql, kind) in [
("INSERT INTO values_test(uid) VALUES (zeroblob(15))", "Uuid"),
("INSERT INTO values_test(day) VALUES (1)", "Date"),
(
"INSERT INTO values_test(day) VALUES (-62167219200001)",
"Date",
),
("INSERT INTO values_test(time) VALUES (86400000)", "Time"),
("INSERT INTO values_test(time) VALUES (-1)", "Time"),
(
"INSERT INTO values_test(document) VALUES ('{\"a\":1,\"a\":2}')",
"Json",
),
(
"INSERT INTO values_test(document) VALUES ('invalid')",
"Json",
),
] {
executor.execute(scope, sql, vec![]).await.unwrap();
let failure = executor
.query(
scope,
"SELECT * FROM values_test ORDER BY rowid DESC LIMIT 1",
vec![],
1,
move |row| match kind {
"Uuid" => scalar::uuid(row, 0).map(|_| ()),
"Date" => scalar::date(row, 1).map(|_| ()),
"Time" => scalar::time(row, 2).map(|_| ()),
_ => scalar::json(row, 3).map(|_| ()),
},
)
.await
.unwrap_err();
assert_eq!(failure.kind, Kind::InvalidData, "{sql}");
}
})
.unwrap();
}