Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,273 @@
|
||||
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();
|
||||
}
|
||||
Reference in new issue
Block a user