Skip to content

Commit

Permalink
Cleanup of database module (#532)
Browse files Browse the repository at this point in the history
* Refactor database logic for possible diesel upgrade
* Flatten mysql until necessary
* Remove unnecessary feature flags
* Fixes build script to avoid additional allocations
  • Loading branch information
paupino committed Aug 4, 2022
1 parent afd41f7 commit 6f1c6ee
Show file tree
Hide file tree
Showing 8 changed files with 982 additions and 973 deletions.
14 changes: 10 additions & 4 deletions Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -184,14 +184,20 @@ dependencies = [
command = "cargo"
args = ["test", "--workspace", "--no-default-features", "--features=rust-fuzz", "rust_fuzz", "--", "--skip", "generated"]

[tasks.test-db-diesel-all]
dependencies = [
"test-db-diesel-mysql",
"test-db-diesel-postgres",
]

[tasks.test-db-mysql-all]
dependencies = [
"test-db-diesel-mysql"
]

[tasks.test-db-diesel-mysql]
command = "cargo"
args = ["test", "--workspace", "--tests", "--features=db-diesel-mysql", "db", "--", "--skip", "generated"]
args = ["test", "--workspace", "--tests", "--features=db-diesel-mysql", "mysql", "--", "--skip", "generated"]

[tasks.test-db-postgres-all]
dependencies = [
Expand All @@ -202,15 +208,15 @@ dependencies = [

[tasks.test-db-postgres]
command = "cargo"
args = ["test", "--workspace", "--tests", "--features=db-postgres", "db", "--", "--skip", "generated"]
args = ["test", "--workspace", "--tests", "--features=db-postgres", "postgres", "--", "--skip", "generated"]

[tasks.test-db-tokio-postgres]
command = "cargo"
args = ["test", "--workspace", "--tests", "--features=db-tokio-postgres", "db", "--", "--skip", "generated"]
args = ["test", "--workspace", "--tests", "--features=db-tokio-postgres", "postgres", "--", "--skip", "generated"]

[tasks.test-db-diesel-postgres]
command = "cargo"
args = ["test", "--workspace", "--tests", "--features=db-diesel-postgres", "db", "--", "--skip", "generated"]
args = ["test", "--workspace", "--tests", "--features=db-diesel-postgres", "postgres", "--", "--skip", "generated"]

[tasks.test-rocket-traits]
command = "cargo"
Expand Down
32 changes: 19 additions & 13 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use std::fmt::Write;
use std::{fs, path::PathBuf};

fn main() {
println!("cargo:rerun-if-changed=README.md");
let readme = fs::read_to_string("README.md").unwrap();
let output = PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("README-lib.md");
fs::write(output, prepare(&readme)).unwrap();
let contents = prepare(&readme).unwrap();
fs::write(output, contents).unwrap();
}

fn prepare(readme: &str) -> String {
fn prepare(readme: &str) -> Result<String, Box<dyn std::error::Error>> {
// This is a naive implementation to get things off the ground.
// We just do a few things for this at the moment:
// 1. Strip header stuff
Expand All @@ -27,26 +29,30 @@ fn prepare(readme: &str) -> String {

// Add the line as is, unless it contains "(BUILD.md)"
if line.contains("(BUILD.md)") {
cleaned.push_str(&line.replace(
"(BUILD.md)",
"(https://github.com/paupino/rust-decimal/blob/master/BUILD.md)",
));
write!(
cleaned,
"{}",
&line.replace(
"(BUILD.md)",
"(https://github.com/paupino/rust-decimal/blob/master/BUILD.md)",
)
)?;
} else if feature_section && line.starts_with("```rust") {
// This is a bit naive, but it's to make the Serde examples cleaner. Should probably
// be a bit more "defensive" here.
cleaned.push_str("```rust\n");
cleaned.push_str("# use rust_decimal::Decimal;\n");
cleaned.push_str("# use serde::{Serialize, Deserialize};\n");
cleaned.push_str(&format!("# #[cfg(features = \"{}\")]", feature));
writeln!(cleaned, "```rust")?;
writeln!(cleaned, "# use rust_decimal::Decimal;")?;
writeln!(cleaned, "# use serde::{{Serialize, Deserialize}};")?;
write!(cleaned, "# #[cfg(features = \"{}\")]", feature)?;
} else {
if !feature_section && line.starts_with("## Features") {
feature_section = true;
} else if feature_section && line.starts_with("### ") {
feature = line.replace("### ", "").replace('`', "");
}
cleaned.push_str(line);
write!(cleaned, "{}", line)?;
}
cleaned.push('\n');
writeln!(cleaned)?;
}
cleaned
Ok(cleaned)
}
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ mod arithmetic_impls;
mod fuzz;
#[cfg(feature = "maths")]
mod maths;
#[cfg(any(feature = "db-diesel-mysql"))]
#[cfg(feature = "db-diesel-mysql")]
mod mysql;
#[cfg(any(
feature = "db-tokio-postgres",
feature = "db-postgres",
feature = "db-diesel-postgres",
feature = "db-diesel-postgres"
))]
mod postgres;
#[cfg(feature = "rand")]
Expand Down
248 changes: 121 additions & 127 deletions src/mysql.rs
Original file line number Diff line number Diff line change
@@ -1,150 +1,144 @@
use crate::prelude::*;
use crate::Decimal;
use diesel::{
deserialize::{self, FromSql},
mysql::Mysql,
serialize::{self, IsNull, Output, ToSql},
sql_types::Numeric,
};
use std::io::Write;
use std::str::FromStr;

#[cfg(feature = "db-diesel-mysql")]
mod diesel_mysql {
use super::*;
use ::diesel::{
deserialize::{self, FromSql},
mysql::Mysql,
serialize::{self, IsNull, Output, ToSql},
sql_types::Numeric,
};
use std::io::Write;
use std::str::FromStr;

impl ToSql<Numeric, Mysql> for Decimal {
fn to_sql<W: Write>(&self, out: &mut Output<W, Mysql>) -> serialize::Result {
// From what I can ascertain, MySQL simply writes to a string format for the Decimal type.
write!(out, "{}", *self).map(|_| IsNull::No).map_err(|e| e.into())
}
impl ToSql<Numeric, Mysql> for Decimal {
fn to_sql<W: Write>(&self, out: &mut Output<W, Mysql>) -> serialize::Result {
// From what I can ascertain, MySQL simply writes to a string format for the Decimal type.
write!(out, "{}", *self).map(|_| IsNull::No).map_err(|e| e.into())
}
}

impl FromSql<Numeric, Mysql> for Decimal {
fn from_sql(numeric: Option<&[u8]>) -> deserialize::Result<Self> {
// From what I can ascertain, MySQL simply reads from a string format for the Decimal type.
// Explicitly, it looks like it is length followed by the string. Regardless, we can leverage
// internal types.
let bytes = numeric.ok_or("Invalid decimal")?;
let s = std::str::from_utf8(bytes)?;
Decimal::from_str(s).map_err(|e| e.into())
}
impl FromSql<Numeric, Mysql> for Decimal {
fn from_sql(numeric: Option<&[u8]>) -> deserialize::Result<Self> {
// From what I can ascertain, MySQL simply reads from a string format for the Decimal type.
// Explicitly, it looks like it is length followed by the string. Regardless, we can leverage
// internal types.
let bytes = numeric.ok_or("Invalid decimal")?;
let s = std::str::from_utf8(bytes)?;
Decimal::from_str(s).map_err(|e| e.into())
}
}

#[cfg(test)]
mod tests {
use super::*;
use diesel::deserialize::QueryableByName;
use diesel::prelude::*;
use diesel::row::NamedRow;
use diesel::sql_query;
use diesel::sql_types::Text;
#[cfg(test)]
mod tests {
use super::*;
use diesel::deserialize::QueryableByName;
use diesel::prelude::*;
use diesel::row::NamedRow;
use diesel::sql_query;
use diesel::sql_types::Text;

struct Test {
value: Decimal,
}
struct Test {
value: Decimal,
}

struct NullableTest {
value: Option<Decimal>,
}
struct NullableTest {
value: Option<Decimal>,
}

impl QueryableByName<Mysql> for Test {
fn build<R: NamedRow<Mysql>>(row: &R) -> deserialize::Result<Self> {
let value = row.get("value")?;
Ok(Test { value })
}
impl QueryableByName<Mysql> for Test {
fn build<R: NamedRow<Mysql>>(row: &R) -> deserialize::Result<Self> {
let value = row.get("value")?;
Ok(Test { value })
}
}

impl QueryableByName<Mysql> for NullableTest {
fn build<R: NamedRow<Mysql>>(row: &R) -> deserialize::Result<Self> {
let value = row.get("value")?;
Ok(NullableTest { value })
}
impl QueryableByName<Mysql> for NullableTest {
fn build<R: NamedRow<Mysql>>(row: &R) -> deserialize::Result<Self> {
let value = row.get("value")?;
Ok(NullableTest { value })
}
}

pub static TEST_DECIMALS: &[(u32, u32, &str, &str)] = &[
// precision, scale, sent, expected
(1, 0, "1", "1"),
(6, 2, "1", "1.00"),
(6, 2, "9999.99", "9999.99"),
(35, 6, "3950.123456", "3950.123456"),
(10, 2, "3950.123456", "3950.12"),
(35, 6, "3950", "3950.000000"),
(4, 0, "3950", "3950"),
(35, 6, "0.1", "0.100000"),
(35, 6, "0.01", "0.010000"),
(35, 6, "0.001", "0.001000"),
(35, 6, "0.0001", "0.000100"),
(35, 6, "0.00001", "0.000010"),
(35, 6, "0.000001", "0.000001"),
(35, 6, "1", "1.000000"),
(35, 6, "-100", "-100.000000"),
(35, 6, "-123.456", "-123.456000"),
(35, 6, "119996.25", "119996.250000"),
(35, 6, "1000000", "1000000.000000"),
(35, 6, "9999999.99999", "9999999.999990"),
(35, 6, "12340.56789", "12340.567890"),
];
pub static TEST_DECIMALS: &[(u32, u32, &str, &str)] = &[
// precision, scale, sent, expected
(1, 0, "1", "1"),
(6, 2, "1", "1.00"),
(6, 2, "9999.99", "9999.99"),
(35, 6, "3950.123456", "3950.123456"),
(10, 2, "3950.123456", "3950.12"),
(35, 6, "3950", "3950.000000"),
(4, 0, "3950", "3950"),
(35, 6, "0.1", "0.100000"),
(35, 6, "0.01", "0.010000"),
(35, 6, "0.001", "0.001000"),
(35, 6, "0.0001", "0.000100"),
(35, 6, "0.00001", "0.000010"),
(35, 6, "0.000001", "0.000001"),
(35, 6, "1", "1.000000"),
(35, 6, "-100", "-100.000000"),
(35, 6, "-123.456", "-123.456000"),
(35, 6, "119996.25", "119996.250000"),
(35, 6, "1000000", "1000000.000000"),
(35, 6, "9999999.99999", "9999999.999990"),
(35, 6, "12340.56789", "12340.567890"),
];

/// Gets the URL for connecting to MySQL for testing. Set the MYSQL_URL
/// environment variable to change from the default of "mysql://root@localhost/mysql".
fn get_mysql_url() -> String {
if let Ok(url) = std::env::var("MYSQL_URL") {
return url;
}
"mysql://root@127.0.0.1/mysql".to_string()
/// Gets the URL for connecting to MySQL for testing. Set the MYSQL_URL
/// environment variable to change from the default of "mysql://root@localhost/mysql".
fn get_mysql_url() -> String {
if let Ok(url) = std::env::var("MYSQL_URL") {
return url;
}
"mysql://root@127.0.0.1/mysql".to_string()
}

#[test]
fn test_null() {
let connection = diesel::MysqlConnection::establish(&get_mysql_url()).expect("Establish connection");
#[test]
fn test_null() {
let connection = diesel::MysqlConnection::establish(&get_mysql_url()).expect("Establish connection");

// Test NULL
let items: Vec<NullableTest> = sql_query("SELECT CAST(NULL AS DECIMAL) AS value")
.load(&connection)
.expect("Unable to query value");
let result = items.first().unwrap().value;
assert_eq!(None, result);
// Test NULL
let items: Vec<NullableTest> = sql_query("SELECT CAST(NULL AS DECIMAL) AS value")
.load(&connection)
.expect("Unable to query value");
let result = items.first().unwrap().value;
assert_eq!(None, result);
}

#[test]
fn read_numeric_type() {
let connection = diesel::MysqlConnection::establish(&get_mysql_url()).expect("Establish connection");
for &(precision, scale, sent, expected) in TEST_DECIMALS.iter() {
let items: Vec<Test> = sql_query(format!(
"SELECT CAST('{}' AS DECIMAL({}, {})) AS value",
sent, precision, scale
))
.load(&connection)
.expect("Unable to query value");
assert_eq!(
expected,
items.first().unwrap().value.to_string(),
"DECIMAL({}, {}) sent: {}",
precision,
scale,
sent
);
}
}

#[test]
fn read_numeric_type() {
let connection = diesel::MysqlConnection::establish(&get_mysql_url()).expect("Establish connection");
for &(precision, scale, sent, expected) in TEST_DECIMALS.iter() {
let items: Vec<Test> = sql_query(format!(
"SELECT CAST('{}' AS DECIMAL({}, {})) AS value",
sent, precision, scale
))
#[test]
fn write_numeric_type() {
let connection = diesel::MysqlConnection::establish(&get_mysql_url()).expect("Establish connection");
for &(precision, scale, sent, expected) in TEST_DECIMALS.iter() {
let items: Vec<Test> = sql_query(format!("SELECT CAST(? AS DECIMAL({}, {})) AS value", precision, scale))
.bind::<Text, _>(sent)
.load(&connection)
.expect("Unable to query value");
assert_eq!(
expected,
items.first().unwrap().value.to_string(),
"DECIMAL({}, {}) sent: {}",
precision,
scale,
sent
);
}
}

#[test]
fn write_numeric_type() {
let connection = diesel::MysqlConnection::establish(&get_mysql_url()).expect("Establish connection");
for &(precision, scale, sent, expected) in TEST_DECIMALS.iter() {
let items: Vec<Test> =
sql_query(format!("SELECT CAST($1 AS DECIMAL({}, {})) AS value", precision, scale))
.bind::<Text, _>(sent)
.load(&connection)
.expect("Unable to query value");
assert_eq!(
expected,
items.first().unwrap().value.to_string(),
"DECIMAL({}, {}) sent: {}",
precision,
scale,
sent
);
}
assert_eq!(
expected,
items.first().unwrap().value.to_string(),
"DECIMAL({}, {}) sent: {}",
precision,
scale,
sent
);
}
}
}

0 comments on commit 6f1c6ee

Please sign in to comment.