#[cfg(feature = "postgres")]
use crate::row::Mirror;
use crate::row::{self, Key, KeyPart, Row, Table};
use crate::{Conn, Error};
use std::future::Future;
use std::pin::Pin;
use uuid::Uuid;
type Fut<'a, T> = Pin<Box<dyn Future<Output = Result<T, Error>> + Send + 'a>>;
pub type Place<R, P> = fn(&mut R, Uuid, &P);
pub struct Codec<P> {
pub(crate) table: Table,
#[cfg(feature = "sqlite")]
pub(crate) row: std::any::TypeId,
ops: Box<dyn Ops<P>>,
}
impl<P> std::fmt::Debug for Codec<P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Codec")
.field("table", &self.table.name)
.finish_non_exhaustive()
}
}
pub struct MapError(String);
impl MapError {
#[must_use]
pub fn new(reason: impl std::fmt::Display) -> Self {
MapError(reason.to_string())
}
}
impl<E: std::error::Error> From<E> for MapError {
fn from(e: E) -> Self {
MapError(e.to_string())
}
}
impl std::fmt::Display for MapError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl std::fmt::Debug for MapError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("MapError").field(&self.0).finish()
}
}
impl<P: Sync + 'static> Codec<P> {
pub fn new<R, M>(
to: fn(R) -> Result<M, MapError>,
from: fn(M) -> Result<R, MapError>,
place: Option<Place<R, P>>,
) -> Self
where
R: Row + Clone + Send + Sync,
M: prost::Message + Default + 'static,
{
Self::typed(
std::any::type_name::<M>(),
std::sync::Arc::new(move |row: R| {
to(row)
.map(|m| m.encode_to_vec())
.map_err(|e| Error::Misuse(format!("`{}` does not encode: {e}", R::TABLE)))
}),
Box::new(move |bytes: &[u8]| {
let message = M::decode(bytes).map_err(|e| malformed(R::TABLE, e))?;
from(message).map_err(|e| malformed(R::TABLE, e))
}),
place,
)
}
pub fn json<R>(place: Option<Place<R, P>>) -> Self
where
R: Row + Clone + Send + Sync + serde::Serialize + serde::de::DeserializeOwned,
{
let columns = Table::of::<R>().columns;
let columns_back = columns.clone();
Self::typed(
"json",
std::sync::Arc::new(move |row: R| json_encode(R::TABLE, &columns, &row)),
Box::new(move |bytes: &[u8]| json_decode(R::TABLE, &columns_back, bytes)),
place,
)
}
#[cfg(feature = "postgres")]
pub(crate) fn custom(table: Table, ops: Box<dyn Ops<P>>) -> Self {
Self { table, ops }
}
#[cfg(feature = "postgres")]
pub(crate) fn builder(&self) -> Option<std::sync::Arc<dyn Build>> {
self.ops.builder()
}
pub(crate) fn versions<'a>(
&'a self,
conn: &'a mut Conn,
of: Option<Uuid>,
) -> Fut<'a, Vec<String>> {
self.ops.versions(conn, of)
}
#[cfg(feature = "postgres")]
pub(crate) fn into_parts(self) -> (Table, Box<dyn Ops<P>>) {
(self.table, self.ops)
}
fn typed<R>(
encoding: &'static str,
encode: Encode<R>,
decode: Decode<R>,
place: Option<Place<R, P>>,
) -> Self
where
R: Row + Clone + Send + Sync,
{
#[cfg(feature = "sqlite")]
let place = {
let _ = place;
std::marker::PhantomData
};
#[cfg(feature = "sqlite")]
let _ = encoding;
let table = Table {
#[cfg(feature = "postgres")]
encoding,
..Table::of::<R>()
};
Self {
#[cfg(feature = "sqlite")]
row: std::any::TypeId::of::<R>(),
ops: Box::new(Typed {
owned: table.replica_owned(),
#[cfg(feature = "postgres")]
origin: table
.origin
.and_then(|origin| table.key.iter().position(|k| *k == origin)),
encode,
decode,
place,
}),
table,
}
}
pub(crate) fn key(&self, parts: &[String]) -> Result<Key, Error> {
if parts.len() != self.table.key.len() {
return Err(Error::Malformed {
table: self.table.name.to_owned(),
reason: format!(
"a key of {} parts, and the table is keyed by {:?}",
parts.len(),
self.table.key
),
});
}
self.table
.key
.iter()
.zip(parts)
.map(|(name, part)| {
let column = self
.table
.columns
.iter()
.find(|c| c.name == *name)
.expect("a key column is a column");
KeyPart::parse(column.key, part).ok_or_else(|| Error::Malformed {
table: self.table.name.to_owned(),
reason: format!("`{part}` is not a value of the key column `{name}`"),
})
})
.collect()
}
#[cfg(feature = "sqlite")]
pub(crate) fn decode_any(&self, bytes: &[u8]) -> Result<Box<dyn std::any::Any + Send>, Error> {
self.ops.decode_any(bytes)
}
pub(crate) fn load<'a>(&'a self, conn: &'a mut Conn, keys: &'a [Key]) -> Fut<'a, Encoded> {
self.ops.load(conn, keys)
}
#[cfg(feature = "sqlite")]
pub(crate) fn apply_down<'a>(
&'a self,
conn: &'a mut Conn,
rows: Vec<(Vec<u8>, Option<Key>)>,
) -> Fut<'a, Vec<Refused>> {
self.ops.apply_down(conn, rows)
}
#[cfg(feature = "postgres")]
pub(crate) fn apply_up<'a>(
&'a self,
conn: &'a mut Conn,
rows: Vec<Vec<u8>>,
sender: Uuid,
known: &'a P,
restated: bool,
) -> Fut<'a, Taken> {
self.ops.apply_up(conn, rows, sender, known, restated)
}
#[cfg(feature = "sqlite")]
pub(crate) fn retire<'a>(
&'a self,
conn: &'a mut Conn,
keys: &'a [Key],
held: &'a [Key],
server: &'a [(&'a str, Option<&'a str>)],
) -> Fut<'a, row::Retired> {
self.ops.retire(conn, keys, held, server)
}
#[cfg(feature = "sqlite")]
pub(crate) fn collect<'a>(&'a self, conn: &'a mut Conn, keys: &'a [Key]) -> Fut<'a, u64> {
self.ops.collect(conn, keys)
}
#[cfg(feature = "sqlite")]
pub(crate) fn present<'a>(&'a self, conn: &'a mut Conn, keys: &'a [Key]) -> Fut<'a, Vec<Key>> {
self.ops.present(conn, keys)
}
#[cfg(feature = "sqlite")]
pub(crate) fn pointing<'a>(
&'a self,
conn: &'a mut Conn,
name: usize,
) -> Fut<'a, Vec<row::Pointing>> {
self.ops.pointing(conn, name)
}
#[cfg(feature = "sqlite")]
pub(crate) fn broken<'a>(
&'a self,
conn: &'a mut Conn,
pointing: &'a [row::Pointing],
) -> Fut<'a, Vec<Key>> {
self.ops.broken(conn, pointing)
}
#[cfg(feature = "sqlite")]
pub(crate) fn orphaned<'a>(&'a self, conn: &'a mut Conn, keys: &'a [Key]) -> Fut<'a, Vec<Key>> {
self.ops.orphaned(conn, keys)
}
#[cfg(feature = "sqlite")]
pub(crate) fn unparented<'a>(
&'a self,
conn: &'a mut Conn,
keys: &'a [Key],
) -> Fut<'a, Vec<Key>> {
self.ops.unparented(conn, keys)
}
#[cfg(feature = "sqlite")]
pub(crate) fn keys<'a>(&'a self, conn: &'a mut Conn) -> Fut<'a, Vec<Key>> {
self.ops.keys(conn)
}
#[cfg(feature = "sqlite")]
pub(crate) fn live_keys<'a>(&'a self, conn: &'a mut Conn) -> Fut<'a, Vec<Key>> {
self.ops.live_keys(conn)
}
#[cfg(feature = "sqlite")]
pub(crate) fn retired_keys<'a>(&'a self, conn: &'a mut Conn) -> Fut<'a, Vec<Key>> {
self.ops.retired_keys(conn)
}
pub(crate) fn sent_down(&self) -> bool {
self.table.mirror.sent_down()
}
#[cfg(feature = "sqlite")]
pub(crate) fn goes_up(&self) -> bool {
self.table.mirror.goes_up()
}
fn origin_part(&self) -> Option<usize> {
let origin = self.table.origin?;
self.table.key.iter().position(|k| *k == origin)
}
pub(crate) fn takes_deletes(&self) -> bool {
self.origin_part().is_some()
}
#[cfg(feature = "postgres")]
pub(crate) async fn delete_up(
&self,
conn: &mut Conn,
keys: &[Key],
sender: Uuid,
) -> Result<Vec<Key>, Error> {
if !self.table.mirror.goes_up() {
return Err(Error::Misuse(format!(
"`{}` is the server's: a replica deletes none of it",
self.table.name
)));
}
let Some(part) = self.origin_part() else {
return Err(Error::Misuse(format!(
"`{}` is not keyed by its replica: a replica deletes nothing from it",
self.table.name
)));
};
let own: Vec<Key> = keys
.iter()
.filter(|key| key.get(part) == Some(&KeyPart::Uuid(sender)))
.cloned()
.collect();
self.ops.delete(conn, &own).await?;
Ok(own)
}
}
#[cfg(feature = "postgres")]
pub(crate) trait Build: Send + Sync {
fn wanted<'a>(
&'a self,
conn: &'a mut Conn,
key: &'a str,
whole: &'a serde_json::Value,
) -> Fut<'a, Vec<u8>>;
}
#[cfg(feature = "postgres")]
struct Builder<R> {
encode: Encode<R>,
}
#[cfg(feature = "postgres")]
impl<R: Row + Clone + Send + Sync> Build for Builder<R> {
fn wanted<'a>(
&'a self,
conn: &'a mut Conn,
key: &'a str,
whole: &'a serde_json::Value,
) -> Fut<'a, Vec<u8>> {
Box::pin(async move { (self.encode)(row::wanted::<R>(conn, key, whole).await?) })
}
}
type Placed<T> = Vec<(usize, T)>;
#[cfg(feature = "postgres")]
#[derive(Debug, Default)]
pub(crate) struct Taken {
pub(crate) held: Vec<Key>,
pub(crate) rejected: Vec<Refused>,
}
#[derive(Debug)]
pub(crate) struct Refused {
pub(crate) at: usize,
pub(crate) reason: String,
}
#[derive(Debug, Default)]
pub(crate) struct Encoded {
pub(crate) rows: Vec<(Key, Vec<u8>)>,
pub(crate) failed: Vec<(Key, Error)>,
}
pub(crate) trait Ops<P>: Send + Sync {
fn load<'a>(&'a self, conn: &'a mut Conn, keys: &'a [Key]) -> Fut<'a, Encoded>;
#[cfg(feature = "sqlite")]
fn decode_any(&self, bytes: &[u8]) -> Result<Box<dyn std::any::Any + Send>, Error>;
#[cfg(feature = "sqlite")]
fn apply_down<'a>(
&'a self,
conn: &'a mut Conn,
rows: Vec<(Vec<u8>, Option<Key>)>,
) -> Fut<'a, Vec<Refused>>;
#[cfg(feature = "postgres")]
fn apply_up<'a>(
&'a self,
conn: &'a mut Conn,
rows: Vec<Vec<u8>>,
sender: Uuid,
known: &'a P,
restated: bool,
) -> Fut<'a, Taken>;
#[cfg(feature = "postgres")]
fn builder(&self) -> Option<std::sync::Arc<dyn Build>>;
fn versions<'a>(&'a self, conn: &'a mut Conn, of: Option<Uuid>) -> Fut<'a, Vec<String>>;
#[cfg(feature = "sqlite")]
fn retire<'a>(
&'a self,
conn: &'a mut Conn,
keys: &'a [Key],
held: &'a [Key],
server: &'a [(&'a str, Option<&'a str>)],
) -> Fut<'a, row::Retired>;
#[cfg(feature = "sqlite")]
fn collect<'a>(&'a self, conn: &'a mut Conn, keys: &'a [Key]) -> Fut<'a, u64>;
#[cfg(feature = "postgres")]
fn delete<'a>(&'a self, conn: &'a mut Conn, keys: &'a [Key]) -> Fut<'a, u64>;
#[cfg(feature = "sqlite")]
fn unparented<'a>(&'a self, conn: &'a mut Conn, keys: &'a [Key]) -> Fut<'a, Vec<Key>>;
#[cfg(feature = "sqlite")]
fn present<'a>(&'a self, conn: &'a mut Conn, keys: &'a [Key]) -> Fut<'a, Vec<Key>>;
#[cfg(feature = "sqlite")]
fn pointing<'a>(&'a self, conn: &'a mut Conn, name: usize) -> Fut<'a, Vec<row::Pointing>>;
#[cfg(feature = "sqlite")]
fn broken<'a>(&'a self, conn: &'a mut Conn, pointing: &'a [row::Pointing])
-> Fut<'a, Vec<Key>>;
#[cfg(feature = "sqlite")]
fn orphaned<'a>(&'a self, conn: &'a mut Conn, keys: &'a [Key]) -> Fut<'a, Vec<Key>>;
#[cfg(feature = "sqlite")]
fn keys<'a>(&'a self, conn: &'a mut Conn) -> Fut<'a, Vec<Key>>;
#[cfg(feature = "sqlite")]
fn live_keys<'a>(&'a self, conn: &'a mut Conn) -> Fut<'a, Vec<Key>>;
#[cfg(feature = "sqlite")]
fn retired_keys<'a>(&'a self, conn: &'a mut Conn) -> Fut<'a, Vec<Key>>;
}
#[derive(Clone, Copy)]
enum How {
#[cfg(feature = "sqlite")]
AllButOwned,
#[cfg(feature = "postgres")]
OnlyOwned(Uuid),
#[cfg(feature = "postgres")]
Newest,
#[cfg(feature = "postgres")]
Latest,
#[cfg(feature = "postgres")]
First,
}
type Encode<R> = std::sync::Arc<dyn Fn(R) -> Result<Vec<u8>, Error> + Send + Sync>;
type Decode<R> = Box<dyn Fn(&[u8]) -> Result<R, Error> + Send + Sync>;
struct Typed<R, P> {
owned: Vec<&'static str>,
#[cfg(feature = "postgres")]
origin: Option<usize>,
encode: Encode<R>,
decode: Decode<R>,
#[cfg(feature = "postgres")]
place: Option<Place<R, P>>,
#[cfg(feature = "sqlite")]
place: std::marker::PhantomData<fn(&P)>,
}
fn malformed(table: &str, e: impl std::fmt::Display) -> Error {
Error::Malformed {
table: table.to_owned(),
reason: format!("a row that does not decode: {e}"),
}
}
pub(crate) fn json_encode<R: serde::Serialize>(
table: &str,
columns: &[row::Column],
row: &R,
) -> Result<Vec<u8>, Error> {
use serde_json::Value;
let misuse = |why: String| Error::Misuse(format!("`{table}` as JSON: {why}"));
let Value::Object(mut fields) = serde_json::to_value(row).map_err(|e| misuse(e.to_string()))?
else {
return Err(misuse("serde does not write the row as an object".into()));
};
let mut out = serde_json::Map::new();
for column in columns {
let value = fields.remove(column.field).ok_or_else(|| {
misuse(format!(
"serde does not write the field `{}` by its name",
column.field
))
})?;
let value = match (column.int64, value) {
(true, Value::Number(n)) => Value::String(n.to_string()),
(_, value) => value,
};
out.insert(column.name.to_owned(), value);
}
serde_json::to_vec(&Value::Object(out)).map_err(|e| misuse(e.to_string()))
}
pub(crate) fn json_decode<R: serde::de::DeserializeOwned>(
table: &str,
columns: &[row::Column],
bytes: &[u8],
) -> Result<R, Error> {
use serde_json::Value;
let Value::Object(mut fields) =
serde_json::from_slice(bytes).map_err(|e| malformed(table, e))?
else {
return Err(malformed(table, "not a JSON object"));
};
let mut out = serde_json::Map::new();
for column in columns {
let value = fields.remove(column.name).unwrap_or(Value::Null);
let value = match (column.int64, value) {
(true, Value::String(text)) => Value::Number(
text.parse::<i64>()
.map_err(|_| {
malformed(table, format!("`{}` is not a 64-bit integer", column.name))
})?
.into(),
),
(_, value) => value,
};
out.insert(column.field.to_owned(), value);
}
serde_json::from_value(Value::Object(out)).map_err(|e| malformed(table, e))
}
impl<R, P> Typed<R, P>
where
R: Row + Clone + Send + Sync,
{
fn decode(&self, rows: Vec<Vec<u8>>) -> (Placed<R>, Vec<Refused>) {
let mut decoded = Vec::new();
let mut failed = Vec::new();
for (at, bytes) in rows.into_iter().enumerate() {
let row = (self.decode)(bytes.as_slice());
match row {
Ok(row) => decoded.push((at, row)),
Err(e) => {
failed.push(Refused {
at,
reason: e.to_string(),
});
}
}
}
(decoded, failed)
}
async fn write(&self, conn: &mut Conn, rows: Vec<R>, how: How) -> Result<Vec<Key>, Error> {
match how {
#[cfg(feature = "sqlite")]
How::AllButOwned => {
let keys = row::overwrite_except(conn, rows, &self.owned).await?;
row::unretire_by_keys::<R>(conn, &keys).await?;
Ok(keys)
}
#[cfg(feature = "postgres")]
How::OnlyOwned(sender) => row::update_held(conn, rows, &self.owned, sender).await,
#[cfg(feature = "postgres")]
How::Newest => row::upsert(conn, rows).await,
#[cfg(feature = "postgres")]
How::Latest => row::overwrite(conn, rows).await,
#[cfg(feature = "postgres")]
How::First => row::insert_ignore(conn, rows).await,
}
}
async fn guarded(
&self,
conn: &mut Conn,
rows: Placed<R>,
how: How,
) -> Result<(Vec<Key>, Vec<Refused>), Error> {
if rows.is_empty() {
return Ok((Vec::new(), Vec::new()));
}
let all: Vec<R> = rows.iter().map(|(_, row)| row.clone()).collect();
let all = match self.attempt(conn, all, how).await? {
Ok(written) => return Ok((written, Vec::new())),
Err(e) => e,
};
tracing::warn!(
table = R::TABLE,
"rows refused together, written one by one: {all}"
);
let mut written = Vec::new();
let mut refused = Vec::new();
for (at, one) in rows {
match self.attempt(conn, vec![one], how).await? {
Ok(keys) => written.extend(keys),
Err(e) => refused.push(Refused {
at,
reason: e.to_string(),
}),
}
}
Ok((written, refused))
}
async fn attempt(
&self,
conn: &mut Conn,
rows: Vec<R>,
how: How,
) -> Result<Result<Vec<Key>, Error>, Error> {
row::savepoint(conn).await?;
let result = self.write(conn, rows, how).await;
row::released(conn, result).await
}
}
#[cfg(feature = "postgres")]
async fn unheld<R: Row>(
conn: &mut Conn,
unwritten: Vec<(usize, Key)>,
sender: Uuid,
) -> Result<Vec<Refused>, Error> {
if unwritten.is_empty() {
return Ok(Vec::new());
}
let (provisioning, sent): (bool, Vec<String>) = sqlx::query_as(
"SELECT EXISTS (SELECT 1 FROM sync.notes WHERE kind = 'resync' AND replica_id = $1 \
AND done_at IS NULL) \
OR EXISTS (SELECT 1 FROM sync.chains c CROSS JOIN sync.epoch e \
LEFT JOIN sync.replicas r USING (replica_id) \
WHERE c.replica_id = $1 AND c.snapshot_end IS NOT NULL \
AND (r.applied_epoch IS DISTINCT FROM e.epoch \
OR coalesce(r.applied_sequence, 0) < c.snapshot_end)), \
ARRAY(SELECT row_key FROM sync.held \
WHERE replica_id = $1 AND relation = $2 AND row_key = ANY($3) \
UNION SELECT row_key FROM sync.gone \
WHERE replica_id = $1 AND relation = $2 AND row_key = ANY($3))",
)
.bind(sender)
.bind(R::TABLE)
.bind(
unwritten
.iter()
.map(|(_, key)| row::key_text(key))
.collect::<Vec<_>>(),
)
.fetch_one(&mut *conn)
.await?;
if provisioning {
return Ok(unwritten
.into_iter()
.map(|(at, _)| Refused {
at,
reason: "an edit of a row the server does not record the replica holding, \
while it provisions it again"
.to_owned(),
})
.collect());
}
for (_, key) in unwritten {
let text = row::key_text(&key);
if sent.contains(&text) {
tracing::debug!(%sender, table = R::TABLE, key = text, "upload: an edit of a two-way row the replica no longer holds — left out");
} else {
tracing::error!(%sender, table = R::TABLE, key = text, "upload: an edit of a two-way row never sent it — malformed, left out");
}
}
Ok(Vec::new())
}
impl<R, P: Sync> Ops<P> for Typed<R, P>
where
R: Row + Clone + Send + Sync,
{
fn load<'a>(&'a self, conn: &'a mut Conn, keys: &'a [Key]) -> Fut<'a, Encoded> {
Box::pin(async move {
let mut encoded = Encoded::default();
for row in row::get_by_keys::<R>(conn, keys).await? {
let key = row.key();
match (self.encode)(row) {
Ok(bytes) => encoded.rows.push((key, bytes)),
Err(e) => encoded.failed.push((key, e)),
}
}
Ok(encoded)
})
}
#[cfg(feature = "sqlite")]
fn decode_any(&self, bytes: &[u8]) -> Result<Box<dyn std::any::Any + Send>, Error> {
Ok(Box::new((self.decode)(bytes)?))
}
#[cfg(feature = "sqlite")]
fn apply_down<'a>(
&'a self,
conn: &'a mut Conn,
rows: Vec<(Vec<u8>, Option<Key>)>,
) -> Fut<'a, Vec<Refused>> {
Box::pin(async move {
if !R::MIRROR.sent_down() {
return Err(Error::Misuse(format!(
"`{}` is not sent down: a replica takes none of it",
R::TABLE
)));
}
let (bytes, beside): (Vec<Vec<u8>>, Vec<Option<Key>>) = rows.into_iter().unzip();
let (decoded, mut refused) = self.decode(bytes);
let mut pending = Vec::new();
for (at, row) in decoded {
if beside[at].as_ref() == Some(&row.key()) {
pending.push((at, row));
} else {
refused.push(Refused {
at,
reason: "a row beside a key that is not its own".to_owned(),
});
}
}
let (_, again) = self.guarded(conn, pending, How::AllButOwned).await?;
refused.extend(again);
Ok(refused)
})
}
#[cfg(feature = "postgres")]
fn apply_up<'a>(
&'a self,
conn: &'a mut Conn,
rows: Vec<Vec<u8>>,
sender: Uuid,
known: &'a P,
restated: bool,
) -> Fut<'a, Taken> {
Box::pin(async move {
let how = match R::MIRROR {
Mirror::Both => How::OnlyOwned(sender),
Mirror::Setting if restated => How::Latest,
Mirror::Record | Mirror::Setting if R::VERSION.is_some() => How::Newest,
Mirror::Record => How::First,
_ => {
return Err(Error::Misuse(format!(
"`{}` is the server's: a replica writes none of it",
R::TABLE
)))
}
};
let (mut rows, mut rejected) = self.decode(rows);
if let Some(place) = self.place {
for (_, row) in &mut rows {
place(row, sender, known);
}
}
if let Some(part) = self.origin {
rows.retain(|(_, row)| {
let own = row.key().get(part) == Some(&KeyPart::Uuid(sender));
if !own {
tracing::error!(
%sender,
table = R::TABLE,
"upload: a row of another replica — malformed, left out"
);
}
own
});
}
let offered: Vec<(usize, Key)> =
rows.iter().map(|(at, row)| (*at, row.key())).collect();
let (written, refused) = self.guarded(conn, rows, how).await?;
rejected.extend(refused);
if let How::OnlyOwned(_) = how {
let unwritten: Vec<(usize, Key)> = offered
.into_iter()
.filter(|(at, key)| {
!written.contains(key) && !rejected.iter().any(|r| r.at == *at)
})
.collect();
rejected.extend(unheld::<R>(conn, unwritten, sender).await?);
}
Ok(Taken {
held: written,
rejected,
})
})
}
#[cfg(feature = "sqlite")]
fn retire<'a>(
&'a self,
conn: &'a mut Conn,
keys: &'a [Key],
held: &'a [Key],
server: &'a [(&'a str, Option<&'a str>)],
) -> Fut<'a, row::Retired> {
Box::pin(row::retire_by_keys::<R>(conn, keys, held, server))
}
#[cfg(feature = "sqlite")]
fn collect<'a>(&'a self, conn: &'a mut Conn, keys: &'a [Key]) -> Fut<'a, u64> {
Box::pin(row::collect_by_keys::<R>(conn, keys))
}
#[cfg(feature = "postgres")]
fn delete<'a>(&'a self, conn: &'a mut Conn, keys: &'a [Key]) -> Fut<'a, u64> {
Box::pin(row::delete_by_keys::<R>(conn, keys))
}
fn versions<'a>(&'a self, conn: &'a mut Conn, of: Option<Uuid>) -> Fut<'a, Vec<String>> {
Box::pin(row::versions::<R>(conn, of))
}
#[cfg(feature = "postgres")]
fn builder(&self) -> Option<std::sync::Arc<dyn Build>> {
(R::MIRROR == Mirror::Setting).then(|| {
std::sync::Arc::new(Builder {
encode: std::sync::Arc::clone(&self.encode),
}) as std::sync::Arc<dyn Build>
})
}
#[cfg(feature = "sqlite")]
fn keys<'a>(&'a self, conn: &'a mut Conn) -> Fut<'a, Vec<Key>> {
Box::pin(row::keys::<R>(conn))
}
#[cfg(feature = "sqlite")]
fn live_keys<'a>(&'a self, conn: &'a mut Conn) -> Fut<'a, Vec<Key>> {
Box::pin(row::live_keys::<R>(conn))
}
#[cfg(feature = "sqlite")]
fn retired_keys<'a>(&'a self, conn: &'a mut Conn) -> Fut<'a, Vec<Key>> {
Box::pin(row::retired_keys::<R>(conn))
}
#[cfg(feature = "sqlite")]
fn unparented<'a>(&'a self, conn: &'a mut Conn, keys: &'a [Key]) -> Fut<'a, Vec<Key>> {
Box::pin(row::unparented::<R>(conn, keys))
}
#[cfg(feature = "sqlite")]
fn present<'a>(&'a self, conn: &'a mut Conn, keys: &'a [Key]) -> Fut<'a, Vec<Key>> {
Box::pin(row::key_select::<R>(conn, keys))
}
#[cfg(feature = "sqlite")]
fn pointing<'a>(&'a self, conn: &'a mut Conn, name: usize) -> Fut<'a, Vec<row::Pointing>> {
Box::pin(row::pointing::<R>(conn, name))
}
#[cfg(feature = "sqlite")]
fn broken<'a>(
&'a self,
conn: &'a mut Conn,
pointing: &'a [row::Pointing],
) -> Fut<'a, Vec<Key>> {
Box::pin(row::broken::<R>(conn, pointing))
}
#[cfg(feature = "sqlite")]
fn orphaned<'a>(&'a self, conn: &'a mut Conn, keys: &'a [Key]) -> Fut<'a, Vec<Key>> {
Box::pin(row::orphaned::<R>(conn, keys))
}
}
#[cfg(test)]
mod tests {
use super::*;
use uuid::Uuid;
#[derive(
sqlx::FromRow, Clone, Debug, PartialEq, crate::Columns, serde::Serialize, serde::Deserialize,
)]
struct Label {
label: String,
}
#[derive(
sqlx::FromRow, Clone, Debug, PartialEq, crate::Row, serde::Serialize, serde::Deserialize,
)]
#[row(table = "flat", mirror = "catalog")]
struct Flat {
id: Uuid,
#[sqlx(flatten)]
#[serde(flatten)]
part: Label,
}
#[derive(
sqlx::FromRow, Clone, Debug, PartialEq, crate::Row, serde::Serialize, serde::Deserialize,
)]
#[row(table = "nested", mirror = "catalog")]
struct Nested {
id: Uuid,
#[sqlx(flatten)]
part: Label,
}
#[derive(
sqlx::FromRow, Clone, Debug, PartialEq, crate::Row, serde::Serialize, serde::Deserialize,
)]
#[row(table = "renamed", mirror = "catalog")]
struct Renamed {
id: Uuid,
#[serde(rename = "name")]
title: String,
}
fn encoded<R: Row + serde::Serialize>(row: &R) -> Result<Vec<u8>, Error> {
json_encode(R::TABLE, &Table::of::<R>().columns, row)
}
#[test]
fn a_part_flattened_for_serde_too_travels_as_json() {
let row = Flat {
id: Uuid::nil(),
part: Label { label: "x".into() },
};
let bytes = encoded(&row).unwrap();
let columns = Table::of::<Flat>().columns;
assert_eq!(json_decode::<Flat>("flat", &columns, &bytes).unwrap(), row);
}
#[test]
fn a_field_serde_writes_otherwise_than_its_name_does_not_encode() {
let renamed = Renamed {
id: Uuid::nil(),
title: "x".into(),
};
let nested = Nested {
id: Uuid::nil(),
part: Label { label: "x".into() },
};
for error in [
encoded(&renamed).unwrap_err(),
encoded(&nested).unwrap_err(),
] {
assert!(
matches!(&error, Error::Misuse(why) if why.contains("by its name")),
"{error}"
);
}
}
}