usecrate::postgres::common::*; usecrate::Decimal; use byteorder::{BigEndian, ReadBytesExt}; use bytes::{BufMut, BytesMut}; use postgres::types::{to_sql_checked, FromSql, IsNull, ToSql, Type}; use std::io::Cursor;
impl<'a> FromSql<'a> for Decimal { // Decimals are represented as follows: // Header: // u16 numGroups // i16 weightFirstGroup (10000^weight) // u16 sign (0x0000 = positive, 0x4000 = negative, 0xC000 = NaN) // i16 dscale. Number of digits (in base 10) to print after decimal separator // // Pseudo code : // const Decimals [ // 0.0000000000000000000000000001, // 0.000000000000000000000001, // 0.00000000000000000001, // 0.0000000000000001, // 0.000000000001, // 0.00000001, // 0.0001, // 1, // 10000, // 100000000, // 1000000000000, // 10000000000000000, // 100000000000000000000, // 1000000000000000000000000, // 10000000000000000000000000000 // ] // overflow = false // result = 0 // for i = 0, weight = weightFirstGroup + 7; i < numGroups; i++, weight-- // group = read.u16 // if weight < 0 or weight > MaxNum // overflow = true // else // result += Decimals[weight] * group // sign == 0x4000 ? -result : result
// So if we were to take the number: 3950.123456 // // Stored on Disk: // 00 03 00 00 00 00 00 06 0F 6E 04 D2 15 E0 // // Number of groups: 00 03 // Weight of first group: 00 00 // Sign: 00 00 // DScale: 00 06 // // 0F 6E = 3950 // result = result + 3950 * 1; // 04 D2 = 1234 // result = result + 1234 * 0.0001; // 15 E0 = 5600 // result = result + 5600 * 0.00000001; //
fn from_sql(_: &Type, raw: &[u8]) -> Result<Decimal, Box<dyn std::error::Error + 'static + Sync + Send>> { letmut raw = Cursor::new(raw); let num_groups = raw.read_u16::<BigEndian>()?; let weight = raw.read_i16::<BigEndian>()?; // 10000^weight // Sign: 0x0000 = positive, 0x4000 = negative, 0xC000 = NaN let sign = raw.read_u16::<BigEndian>()?; // Number of digits (in base 10) to print after decimal separator let scale = raw.read_u16::<BigEndian>()?;
// Read all of the groups letmut groups = Vec::new(); for _ in0..num_groups as usize {
groups.push(raw.read_u16::<BigEndian>()?);
}
// Number of groups
out.put_u16(num_digits.try_into().unwrap()); // Weight of first group
out.put_i16(weight); // Sign
out.put_u16(if neg { 0x4000 } else { 0x0000 }); // DScale
out.put_u16(scale); // Now process the number for digit in digits[0..num_digits].iter() {
out.put_i16(*digit);
}
#[cfg(test)] mod test { usesuper::*; use ::postgres::{Client, NoTls}; use core::str::FromStr;
/// Gets the URL for connecting to PostgreSQL for testing. Set the POSTGRES_URL /// environment variable to change from the default of "postgres://postgres@localhost". fn get_postgres_url() -> String { iflet Ok(url) = std::env::var("POSTGRES_URL") { return url;
} "postgres://postgres@localhost".to_string()
}
// Test NULL let result: Option<Decimal> = match client.query("SELECT NULL::numeric", &[]) {
Ok(x) => x.iter().next().unwrap().get(0),
Err(err) => panic!("{:#?}", err),
};
assert_eq!(None, result);
}
#[tokio::test] #[cfg(feature = "tokio-pg")] asyncfn async_test_null() { use futures::future::FutureExt; use tokio_postgres::connect;
let (client, connection) = connect(&get_postgres_url(), NoTls).await.unwrap(); let connection = connection.map(|e| e.unwrap());
tokio::spawn(connection);
let statement = client.prepare(&"SELECT NULL::numeric").await.unwrap(); let rows = client.query(&statement, &[]).await.unwrap(); let result: Option<Decimal> = rows.iter().next().unwrap().get(0);
assert_eq!(None, result);
}
#[test] fn read_very_small_numeric_type() { letmut client = match Client::connect(&get_postgres_url(), NoTls) {
Ok(x) => x,
Err(err) => panic!("{:#?}", err),
}; let result: Decimal = match client.query("SELECT 1e-130::NUMERIC(130, 0)", &[]) {
Ok(x) => x.iter().next().unwrap().get(0),
Err(err) => panic!("error - {:#?}", err),
}; // We compare this to zero since it is so small that it is effectively zero
assert_eq!(Decimal::ZERO, result);
}
#[test] fn write_numeric_type() { letmut client = match Client::connect(&get_postgres_url(), NoTls) {
Ok(x) => x,
Err(err) => panic!("{:#?}", err),
}; for &(precision, scale, sent, expected) in TEST_DECIMALS.iter() { let number = Decimal::from_str(sent).unwrap(); let result: Decimal = match client.query(&*format!("SELECT $1::NUMERIC({}, {})", precision, scale), &[&number]) {
Ok(x) => x.iter().next().unwrap().get(0),
Err(err) => panic!("{:#?}", err),
};
assert_eq!(expected, result.to_string(), "NUMERIC({}, {})", precision, scale);
}
}
#[tokio::test] #[cfg(feature = "tokio-pg")] asyncfn async_write_numeric_type() { use futures::future::FutureExt; use tokio_postgres::connect;
let (client, connection) = connect(&get_postgres_url(), NoTls).await.unwrap(); let connection = connection.map(|e| e.unwrap());
tokio::spawn(connection);
for &(precision, scale, sent, expected) in TEST_DECIMALS.iter() { let statement = client
.prepare(&*format!("SELECT $1::NUMERIC({}, {})", precision, scale))
.await
.unwrap(); let number = Decimal::from_str(sent).unwrap(); let rows = client.query(&statement, &[&number]).await.unwrap(); let result: Decimal = rows.iter().next().unwrap().get(0);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.