use crate::wire::MapError;
use std::cell::Cell;
use uuid::Uuid;
#[derive(sqlx::FromRow, Clone, Debug, serde::Serialize, serde::Deserialize, crate::Row)]
#[row(
table = "items",
schema = "fixture",
mirror = "catalog",
sync = "broadcast",
active = "items",
retired = "retired_at"
)]
pub struct Item {
pub id: Uuid,
pub partition_id: Uuid,
pub name: String,
}
#[derive(Clone, PartialEq, prost::Message)]
pub struct ItemRow {
#[prost(string, tag = "1")]
pub id: String,
#[prost(string, tag = "2")]
pub partition_id: String,
#[prost(string, tag = "3")]
pub name: String,
}
thread_local! {
pub static BUGGY: Cell<bool> = const { Cell::new(true) };
}
pub fn to_wire(item: Item) -> Result<ItemRow, MapError> {
if item.name == "poison" && BUGGY.get() {
return Err(MapError::new("a value this build cannot encode"));
}
Ok(ItemRow {
id: item.id.to_string(),
partition_id: item.partition_id.to_string(),
name: item.name,
})
}
pub fn from_wire(row: ItemRow) -> Result<Item, MapError> {
Ok(Item {
id: row.id.parse()?,
partition_id: row.partition_id.parse()?,
name: row.name,
})
}
pub struct Walk(pub u64);
impl Walk {
pub fn next(&mut self) -> u64 {
if self.0 == 0 {
self.0 = 0x9E37_79B9_7F4A_7C15;
}
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
pub fn below(&mut self, n: usize) -> usize {
(self.next() % n as u64) as usize
}
pub fn pick<T: Copy>(&mut self, from: &[T]) -> Option<T> {
(!from.is_empty()).then(|| from[self.below(from.len())])
}
pub fn uuid(&mut self) -> Uuid {
let (high, low) = (self.next(), self.next());
uuid::Builder::from_random_bytes(((u128::from(high) << 64) | u128::from(low)).to_be_bytes())
.into_uuid()
}
}
#[cfg(feature = "postgres")]
pub mod records {
use super::*;
#[derive(sqlx::FromRow, Clone, Debug, crate::Row)]
#[row(table = "readings", schema = "fixture", mirror = "record")]
pub struct Reading {
pub id: Uuid,
pub replica_id: Uuid,
pub value: String,
}
#[derive(Clone, PartialEq, prost::Message)]
pub struct ReadingRow {
#[prost(string, tag = "1")]
pub id: String,
#[prost(string, tag = "2")]
pub replica_id: String,
#[prost(string, tag = "3")]
pub value: String,
}
pub fn reading_to_wire(reading: Reading) -> Result<ReadingRow, MapError> {
Ok(ReadingRow {
id: reading.id.to_string(),
replica_id: reading.replica_id.to_string(),
value: reading.value,
})
}
pub fn reading_from_wire(row: ReadingRow) -> Result<Reading, MapError> {
if row.value == "poison" && BUGGY.get() {
return Err(MapError::new("a value this build cannot decode"));
}
Ok(Reading {
id: row.id.parse()?,
replica_id: row.replica_id.parse()?,
value: row.value,
})
}
}
#[cfg(feature = "postgres")]
pub async fn back_up(conn: &mut crate::Conn, schemas: &[&str]) {
sqlx::raw_sql("DROP SCHEMA IF EXISTS backup CASCADE; CREATE SCHEMA backup;")
.execute(&mut *conn)
.await
.expect("a schema for the copy");
for (schema, table) in tables(conn, schemas).await {
sqlx::raw_sql(&format!(
"CREATE TABLE backup.\"{schema}.{table}\" AS TABLE \"{schema}\".\"{table}\""
))
.execute(&mut *conn)
.await
.expect("a table copied");
}
sqlx::raw_sql(
"CREATE TABLE backup.sequences (schemaname TEXT, sequencename TEXT, last_value BIGINT, called BOOLEAN)",
)
.execute(&mut *conn)
.await
.expect("a table for the sequences");
sqlx::query(
"INSERT INTO backup.sequences \
SELECT schemaname, sequencename, last_value, last_value IS NOT NULL \
FROM pg_sequences WHERE schemaname = ANY($1)",
)
.bind(schemas)
.execute(&mut *conn)
.await
.expect("the sequences copied");
}
#[cfg(feature = "postgres")]
pub async fn restore(conn: &mut crate::Conn, schemas: &[&str]) {
sqlx::raw_sql("SET session_replication_role = replica")
.execute(&mut *conn)
.await
.expect("no trigger fires");
for (schema, table) in tables(conn, schemas).await {
sqlx::raw_sql(&format!(
"DELETE FROM \"{schema}\".\"{table}\"; \
INSERT INTO \"{schema}\".\"{table}\" SELECT * FROM backup.\"{schema}.{table}\""
))
.execute(&mut *conn)
.await
.expect("a table restored");
}
let sequences: Vec<(String, String, Option<i64>, bool)> =
sqlx::query_as("SELECT * FROM backup.sequences")
.fetch_all(&mut *conn)
.await
.expect("the sequences");
for (schema, sequence, value, called) in sequences {
sqlx::query("SELECT setval(format('%I.%I', $1::text, $2::text), $3, $4)")
.bind(schema)
.bind(sequence)
.bind(value.unwrap_or(1))
.bind(called)
.execute(&mut *conn)
.await
.expect("a sequence restored");
}
sqlx::raw_sql("SET session_replication_role = origin")
.execute(&mut *conn)
.await
.expect("triggers back");
}
#[cfg(feature = "postgres")]
async fn tables(conn: &mut crate::Conn, schemas: &[&str]) -> Vec<(String, String)> {
sqlx::query_as(
"SELECT table_schema, table_name FROM information_schema.tables \
WHERE table_type = 'BASE TABLE' AND table_schema = ANY($1) ORDER BY 1, 2",
)
.bind(schemas)
.fetch_all(&mut *conn)
.await
.expect("the tables")
}
#[cfg(feature = "postgres")]
pub async fn file<P: Sync + 'static>(
pool: &crate::DbPool,
codecs: &[crate::wire::Codec<P>],
up: &crate::envelope::Up,
sender: Uuid,
known: &P,
) {
let mut tx = pool.begin().await.expect("a transaction");
crate::filing::file(&mut tx, codecs, up, sender, known, i64::MAX)
.await
.expect("filed");
tx.commit().await.expect("committed");
}
#[cfg(feature = "postgres")]
pub async fn confirmation(
pool: &crate::DbPool,
replica: Uuid,
sequence: u64,
) -> crate::envelope::Up {
let (epoch, secret, nonce): (Uuid, Vec<u8>, Option<i64>) = sqlx::query_as(
"SELECT e.epoch, e.secret, \
(SELECT CASE WHEN c.last_sequence = $2 THEN c.last_nonce \
WHEN r.applied_sequence = $2 THEN r.applied_nonce END \
FROM sync.replicas r FULL JOIN sync.chains c USING (replica_id) \
WHERE replica_id = $1) \
FROM sync.epoch e",
)
.bind(replica)
.bind(i64::try_from(sequence).expect("a sequence"))
.fetch_one(pool)
.await
.expect("the epoch");
let nonce = nonce.map_or(1, |n| u64::try_from(n).expect("a nonce"));
crate::envelope::Up {
protocol: crate::envelope::PROTOCOL,
epoch: epoch.to_string(),
sequence,
nonce,
mac: crate::envelope::seal(&secret, replica, epoch, sequence, nonce),
..crate::envelope::Up::default()
}
}