vantage-sql 0.10.3

Vantage extension for SQL databases (Postgres, MySQL, SQLite)
Documentation
//! Decimal type matrix test via Table<PostgresDB, Entity>.
//!
//! rust_decimal::Decimal × 6 column types.
//! Tables are pre-created by v5.sql (same shape: id, name, value).
//! Postgres may reject text binds for typed columns (NUMERIC, etc).

use rust_decimal::Decimal;
use std::str::FromStr;

#[allow(unused_imports)]
use vantage_sql::postgres::PostgresType;
use vantage_sql::postgres::{AnyPostgresType, PostgresDB};
use vantage_table::table::Table;
use vantage_types::entity;

use vantage_dataset::{ReadableDataSet, WritableDataSet};

const PG_URL: &str = "postgres://vantage:vantage@localhost:5433/vantage_v5";

async fn db() -> PostgresDB {
    PostgresDB::connect(PG_URL).await.unwrap()
}

#[entity(PostgresType)]
#[derive(Debug, Clone, PartialEq, Default)]
struct ValDecimal {
    name: String,
    value: Decimal,
}

macro_rules! table_for {
    ($table:expr, $db:expr) => {
        Table::<PostgresDB, ValDecimal>::new($table, $db)
            .with_id_column("id")
            .with_column_of::<String>("name")
            .with_column_of::<Decimal>("value")
    };
}

async fn try_round_trip(
    table: &Table<PostgresDB, ValDecimal>,
    id: &str,
    entity: &ValDecimal,
) -> Result<ValDecimal, String> {
    table.replace(id, entity).await.map_err(|e| e.to_string())?;
    table
        .get(id)
        .await
        .map_err(|e| e.to_string())?
        .ok_or_else(|| "row missing after replace".to_string())
}

fn dec(s: &str) -> Decimal {
    Decimal::from_str(s).unwrap()
}

// ═════════════════════════════════════════════════════════════════════════
// 1. VARCHAR — exact string round-trip
// ══════════════════════════════════════════════════════════════════════���══

#[tokio::test]
async fn test_varchar_high_precision() {
    let t = table_for!("decimal_varchar", db().await);
    let orig = ValDecimal {
        name: "hp".into(),
        value: dec("99999999999999999.123456789012345"),
    };
    let fetched = try_round_trip(&t, "vc_hp", &orig).await.unwrap();
    assert_eq!(fetched, orig);
}

// ═════════════════════════════════════════════════════════════════════════
// 2. NUMERIC(p,s) — the column's scale decides what survives
// ════════════════════════════════════════════════════════���════════════════

#[tokio::test]
async fn test_numeric_scale() {
    let db = db().await;
    let narrow = table_for!("decimal_numeric", db.clone());
    let wide = table_for!("decimal_numeric_wide", db);
    let rounded = ValDecimal {
        name: "trunc".into(),
        value: dec("123.123456789"),
    };
    let exact = ValDecimal {
        name: "hp".into(),
        value: dec("99999999999999999.123456789012345"),
    };
    // NUMERIC(20,6) rounds to 6 places; NUMERIC(38,15) keeps all 33 digits
    let fetched = try_round_trip(&narrow, "dn_trunc", &rounded).await.unwrap();
    assert_eq!(fetched.value, dec("123.123457"));
    assert_eq!(
        try_round_trip(&wide, "dnw_hp", &exact).await.unwrap(),
        exact
    );
}

// ═══════════════════════════════════════════════════════��═════════════════
// 3. DOUBLE PRECISION — f64 precision (~15 significant digits)
// ═══════════════════════════════════════════════════════════��═════════════

#[tokio::test]
async fn test_double_simple() {
    let t = table_for!("decimal_double", db().await);
    let orig = ValDecimal {
        name: "d".into(),
        value: dec("123.456"),
    };
    let fetched = try_round_trip(&t, "dbl_s", &orig).await.unwrap();
    let diff = (fetched.value - orig.value).abs();
    assert!(diff < dec("0.001"), "diff too large: {}", diff);
}

// ═════════════════════════════════════════════════════════════════════════
// 4. REAL — f32 precision (~7 significant digits)
// ═════════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn test_real_simple() {
    let t = table_for!("decimal_real", db().await);
    let orig = ValDecimal {
        name: "d".into(),
        value: dec("123.456"),
    };
    let fetched = try_round_trip(&t, "real_s", &orig).await.unwrap();
    let diff = (fetched.value - orig.value).abs();
    assert!(diff < dec("0.01"), "diff too large: {}", diff);
}

// ═════════════════════════════════════════════════════════════════════════
// 5. BIGINT — integer only, fractional part lost
// ═════════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn test_bigint_rounds_fraction() {
    let t = table_for!("decimal_bigint", db().await);
    let orig = ValDecimal {
        name: "frac".into(),
        value: dec("123.999"),
    };
    let fetched = try_round_trip(&t, "bi_frac", &orig).await.unwrap();
    // BIGINT rounds the fractional part
    assert_eq!(fetched.value, dec("124"));
}