#![doc = include_str!("./README.md")]
use anyhow::Result;
use ordered_float::OrderedFloat;
use std::rc::Rc;
use wingfoil::adapters::postgres::*;
use wingfoil::*;
type Price = OrderedFloat<f64>;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct Trade {
sym: String,
price: Price,
qty: i64,
}
impl PostgresDeserialize for Trade {
fn from_row(row: &Row) -> Result<(NanoTime, Self)> {
Ok((
row.get_nanotime(0)?, Trade {
sym: row.try_get(1)?,
price: OrderedFloat(row.try_get(2)?),
qty: row.try_get(3)?,
},
))
}
}
impl PostgresSerialize for Trade {
fn to_params(&self) -> Vec<Box<dyn ToSql + Sync + Send>> {
vec![
Box::new(self.sym.clone()),
Box::new(self.price.into_inner()),
Box::new(self.qty),
]
}
}
fn generate(num_rows: u32) -> Rc<dyn Stream<Burst<Trade>>> {
let syms = ["AAPL", "GOOG", "MSFT"];
ticker(std::time::Duration::from_secs(1))
.count()
.map(move |i| {
burst![Trade {
sym: syms[i as usize % syms.len()].to_string(),
price: OrderedFloat(100.0 + i as f64),
qty: (i * 10 + 1) as i64,
}]
})
.limit(num_rows)
}
fn validate<T: Element + Eq>(a: Rc<dyn Stream<T>>, b: Rc<dyn Stream<T>>) -> Vec<Rc<dyn Node>> {
fn assert_equal<T: Element + Eq>(a: Rc<dyn Stream<T>>, b: Rc<dyn Stream<T>>) -> Rc<dyn Node> {
bimap(Dep::Active(a), Dep::Active(b), |a, b| {
assert!(a == b, "generated and read data did not tie out");
})
}
vec![
assert_equal(a.clone(), b.clone()),
assert_equal(a.count(), b.count()),
]
}
fn reset_table(conn: &PostgresConnection) -> Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let (client, connection) =
tokio_postgres::connect(&conn.conn_str, tokio_postgres::NoTls).await?;
tokio::spawn(async move {
let _ = connection.await;
});
client
.batch_execute(
"DROP TABLE IF EXISTS example_trades; \
CREATE TABLE example_trades \
(time timestamp, sym text, price float8, qty int8)",
)
.await?;
Ok::<(), anyhow::Error>(())
})
}
fn main() -> Result<()> {
env_logger::init();
let conn = PostgresConnection::new(
"host=localhost port=5432 user=postgres password=postgres dbname=postgres",
);
let table = "example_trades";
let num_rows = 10;
let run_mode = RunMode::HistoricalFrom(NanoTime::from_kdb_timestamp(0));
let run_for = RunFor::Duration(std::time::Duration::from_secs(11));
reset_table(&conn)?;
generate(num_rows)
.postgres_write(conn.clone(), table)
.run(run_mode, run_for)?;
let baseline = generate(num_rows);
let read = postgres_read::<Trade>(
conn,
std::time::Duration::from_secs(86400),
move |(t0, t1), _date, _iter| {
format!(
"SELECT time, sym, price, qty FROM {table} \
WHERE time >= '{}' AND time < '{}' ORDER BY time",
postgres_timestamp(t0),
postgres_timestamp(t1),
)
},
);
let check = validate(baseline, read);
Graph::new(check, run_mode, run_for).run()?;
println!("✓ {num_rows} written, read and validated");
Ok(())
}