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(); }