toolu-orm-connection 0.2.0

Connection pooling and driver adapters (libsql, rusqlite, Postgres) for toolu-orm
Documentation
//! `#[derive(FromRow)]` against real rows, on the two-driver shape.
//!
//! This lane unifies postgres + libsql onto `toolu-orm-core`, so the derive
//! emits `from_pg_row` *and* `from_libsql_row`, both real decoders. Needs the
//! live Postgres from `docker-compose.test.yaml`.
#![cfg(all(feature = "libsql", feature = "postgres"))]

use toolu_orm_connection::{Database, DbConnection, DbError, PgConfig, PgDatabase};
use toolu_orm_core::value::Value;
use toolu_orm_macros::FromRow;

type TestResult = Result<(), Box<dyn std::error::Error>>;

#[derive(FromRow, Debug, PartialEq)]
struct Person {
  id: String,
  age: Option<i64>,
}

const DDL: &str = "CREATE TABLE people (id TEXT PRIMARY KEY, age BIGINT)";
const INSERT: &str = "INSERT INTO people (id, age) VALUES ($1, $2)";

async fn pg_people(
  schema: &str,
) -> Result<(PgDatabase, impl DbConnection), Box<dyn std::error::Error>> {
  let db = PgDatabase::init(&PgConfig::for_test("toolu")).await?;
  let conn = db.connect().await?;
  conn
    .execute_batch(&format!(
      "DROP SCHEMA IF EXISTS {schema} CASCADE; CREATE SCHEMA {schema}; SET search_path TO {schema}; {DDL}"
    ))
    .await?;
  conn
    .execute_sql(INSERT, vec![Value::Text("p1".into()), Value::Null])
    .await?;
  conn
    .execute_sql(INSERT, vec![Value::Text("p2".into()), Value::Integer(30)])
    .await?;
  Ok((db, conn))
}

#[tokio::test]
async fn derive_decodes_postgres_rows_with_null_as_none() -> TestResult {
  let (_db, conn) = pg_people("conn_derive_null").await?;
  let people = conn
    .query_map::<Person>("SELECT id, age FROM people ORDER BY id", vec![])
    .await?;
  assert_eq!(
    people,
    vec![
      Person {
        id: "p1".into(),
        age: None
      },
      Person {
        id: "p2".into(),
        age: Some(30)
      },
    ]
  );
  Ok(())
}

#[tokio::test]
async fn derive_fewer_columns_than_required_is_row_mapping() -> TestResult {
  let (_db, conn) = pg_people("conn_derive_fewer").await?;
  let result = conn
    .query_map::<Person>("SELECT id FROM people", vec![])
    .await;
  let Err(DbError::RowMapping(msg)) = result else {
    return Err("expected DbError::RowMapping".into());
  };
  assert!(msg.contains("column 1 (age)"), "unexpected message: {msg}");
  Ok(())
}

/// The other half of the two-driver shape. This used to assert a stub error
/// (`"<Type> is only decoded from Postgres rows"`); the derive now generates a
/// real libsql decoder, so the *same* derived struct decodes both drivers and
/// the two assertions above and below are interchangeable.
#[tokio::test]
async fn derive_decodes_libsql_rows_with_null_as_none() -> TestResult {
  let db = Database::init_local(":memory:").await?;
  let conn = db.connect()?;
  conn
    .execute_batch("CREATE TABLE people (id TEXT PRIMARY KEY, age INTEGER)")
    .await?;
  conn
    .execute_sql(
      "INSERT INTO people (id, age) VALUES (?1, ?2)",
      vec![Value::Text("p1".into()), Value::Null],
    )
    .await?;
  conn
    .execute_sql(
      "INSERT INTO people (id, age) VALUES (?1, ?2)",
      vec![Value::Text("p2".into()), Value::Integer(30)],
    )
    .await?;

  let people = conn
    .query_map::<Person>("SELECT id, age FROM people ORDER BY id", vec![])
    .await?;

  assert_eq!(
    people,
    vec![
      Person {
        id: "p1".into(),
        age: None
      },
      Person {
        id: "p2".into(),
        age: Some(30)
      },
    ]
  );
  Ok(())
}