use std::marker::PhantomData;
use sea_query::{Alias, Expr, OnConflict, PostgresQueryBuilder, Query, SqliteQueryBuilder};
use sea_query_binder::SqlxBinder;
use serde::{Deserialize, Serialize};
use super::{Model, PrimaryKey};
#[derive(Debug, Clone)]
pub struct M2M<T: Model, P: PrimaryKey = i64> {
resolved: Option<Vec<T>>,
parent_id: Option<P>,
junction_table: Option<&'static str>,
pending: Vec<sea_query::Value>,
_phantom: PhantomData<T>,
}
impl<T: Model, P: PrimaryKey> Default for M2M<T, P> {
fn default() -> Self {
Self::empty()
}
}
impl<T: Model, P: PrimaryKey> M2M<T, P> {
pub fn empty() -> Self {
Self {
resolved: None,
parent_id: None,
junction_table: None,
pending: Vec::new(),
_phantom: PhantomData,
}
}
pub fn resolved(&self) -> Option<&[T]> {
self.resolved.as_deref()
}
pub fn set_resolved(&mut self, rows: Vec<T>) {
self.resolved = Some(rows);
}
pub fn parent_id(&self) -> Option<&P> {
self.parent_id.as_ref()
}
pub fn set_parent_id(&mut self, id: P) {
self.parent_id = Some(id);
}
pub fn set_junction_table(&mut self, name: &'static str) {
self.junction_table = Some(name);
}
pub fn junction_table(&self) -> Option<&'static str> {
self.junction_table
}
pub fn set_pending_ids(&mut self, ids: Vec<sea_query::Value>) {
self.pending = ids;
}
pub fn take_pending_ids(&mut self) -> Vec<sea_query::Value> {
std::mem::take(&mut self.pending)
}
pub async fn fetch(&self) -> Result<Vec<T>, sqlx::Error>
where
T: for<'r> sqlx::FromRow<'r, sqlx::sqlite::SqliteRow>
+ for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>,
{
let Some((parent_id, junction)) = self.junction_handle() else {
return Ok(Vec::new());
};
let child_pk = child_pk_col::<T>();
let mut q = Query::select();
q.columns(
T::FIELDS
.iter()
.map(|f| (Alias::new("c"), Alias::new(f.name))),
)
.from_as(
crate::db::router::schema_qualified_table(T::TABLE),
Alias::new("c"),
)
.join_as(
sea_query::JoinType::InnerJoin,
crate::db::router::schema_qualified_table(junction),
Alias::new("j"),
Expr::col((Alias::new("j"), Alias::new("child_id")))
.equals((Alias::new("c"), Alias::new(child_pk))),
)
.and_where(Expr::col((Alias::new("j"), Alias::new("parent_id"))).eq(parent_id));
let pool = crate::db::pool_dispatched();
match pool {
crate::db::DbPool::Sqlite(p) => {
let (sql, values) = q.build_sqlx(SqliteQueryBuilder);
sqlx::query_as_with::<sqlx::Sqlite, T, _>(&sql, values)
.fetch_all(p)
.await
}
crate::db::DbPool::Postgres(p) => {
let (sql, values) = q.build_sqlx(PostgresQueryBuilder);
sqlx::query_as_with::<sqlx::Postgres, T, _>(&sql, values)
.fetch_all(p)
.await
}
}
}
pub async fn add(&self, child: &T) -> Result<(), sqlx::Error> {
let Some((parent_id, junction)) = self.junction_handle() else {
return Ok(());
};
let child_pk: T::PrimaryKey = child.primary_key();
let child_pk_json = pk_seaval_to_json(child_pk.clone().into());
let mut q = Query::insert();
q.into_table(crate::db::router::schema_qualified_table(junction))
.columns([Alias::new("parent_id"), Alias::new("child_id")])
.values_panic([
Expr::value(parent_id.clone()).into(),
Expr::value(child_pk).into(),
])
.on_conflict(
OnConflict::columns([Alias::new("parent_id"), Alias::new("child_id")])
.do_nothing()
.to_owned(),
);
execute_sql(&q).await?;
let parent_id_json = pk_seaval_to_json(parent_id.into());
crate::signals::emit_m2m_changed(
junction,
"add",
parent_id_json,
vec![child_pk_json],
Vec::new(),
)
.await;
Ok(())
}
pub async fn remove(&self, child: &T) -> Result<(), sqlx::Error> {
let Some((parent_id, junction)) = self.junction_handle() else {
return Ok(());
};
let child_pk: T::PrimaryKey = child.primary_key();
let child_pk_json = pk_seaval_to_json(child_pk.clone().into());
let mut q = Query::delete();
q.from_table(crate::db::router::schema_qualified_table(junction))
.and_where(Expr::col(Alias::new("parent_id")).eq(parent_id.clone()))
.and_where(Expr::col(Alias::new("child_id")).eq(child_pk));
execute_delete(&q).await?;
let parent_id_json = pk_seaval_to_json(parent_id.into());
crate::signals::emit_m2m_changed(
junction,
"remove",
parent_id_json,
Vec::new(),
vec![child_pk_json],
)
.await;
Ok(())
}
pub async fn clear(&self) -> Result<u64, sqlx::Error> {
let Some((parent_id, junction)) = self.junction_handle() else {
return Ok(0);
};
let mut q = Query::delete();
q.from_table(crate::db::router::schema_qualified_table(junction))
.and_where(Expr::col(Alias::new("parent_id")).eq(parent_id.clone()))
.returning_col(Alias::new("child_id"));
let removed_ids = execute_delete_returning_ids::<T>(&q).await?;
let count = removed_ids.len() as u64;
if !removed_ids.is_empty() {
let parent_id_json = pk_seaval_to_json(parent_id.into());
crate::signals::emit_m2m_changed(
junction,
"clear",
parent_id_json,
Vec::new(),
removed_ids,
)
.await;
}
Ok(count)
}
pub async fn set(&self, children: &[&T]) -> Result<(), sqlx::Error> {
let Some((parent_id, junction)) = self.junction_handle() else {
return Ok(());
};
let added_json: Vec<serde_json::Value> = children
.iter()
.map(|c| pk_seaval_to_json(c.primary_key().into()))
.collect();
let pool = crate::db::pool_dispatched();
let mut delete = Query::delete();
delete
.from_table(crate::db::router::schema_qualified_table(junction))
.and_where(Expr::col(Alias::new("parent_id")).eq(parent_id.clone()))
.returning_col(Alias::new("child_id"));
let child_pk_ty = child_pk_ty::<T>();
let removed_json: Vec<serde_json::Value> = match pool {
crate::db::DbPool::Sqlite(p) => {
let mut tx = p.begin().await?;
let (sql, values) = delete.build_sqlx(SqliteQueryBuilder);
let rows = sqlx::query_with(&sql, values).fetch_all(&mut *tx).await?;
let removed: Vec<serde_json::Value> = rows
.iter()
.map(|r| child_id_to_json_sqlite(r, child_pk_ty))
.collect::<Result<_, _>>()?;
for child in children {
let child_pk: T::PrimaryKey = child.primary_key();
let mut insert = Query::insert();
insert
.into_table(crate::db::router::schema_qualified_table(junction))
.columns([Alias::new("parent_id"), Alias::new("child_id")])
.values_panic([
Expr::value(parent_id.clone()).into(),
Expr::value(child_pk).into(),
])
.on_conflict(
OnConflict::columns([Alias::new("parent_id"), Alias::new("child_id")])
.do_nothing()
.to_owned(),
);
let (sql, values) = insert.build_sqlx(SqliteQueryBuilder);
sqlx::query_with(&sql, values).execute(&mut *tx).await?;
}
tx.commit().await?;
removed
}
crate::db::DbPool::Postgres(p) => {
let mut tx = p.begin().await?;
let (sql, values) = delete.build_sqlx(PostgresQueryBuilder);
let rows = sqlx::query_with(&sql, values).fetch_all(&mut *tx).await?;
let removed: Vec<serde_json::Value> = rows
.iter()
.map(|r| child_id_to_json_pg(r, child_pk_ty))
.collect::<Result<_, _>>()?;
for child in children {
let child_pk: T::PrimaryKey = child.primary_key();
let mut insert = Query::insert();
insert
.into_table(crate::db::router::schema_qualified_table(junction))
.columns([Alias::new("parent_id"), Alias::new("child_id")])
.values_panic([
Expr::value(parent_id.clone()).into(),
Expr::value(child_pk).into(),
])
.on_conflict(
OnConflict::columns([Alias::new("parent_id"), Alias::new("child_id")])
.do_nothing()
.to_owned(),
);
let (sql, values) = insert.build_sqlx(PostgresQueryBuilder);
sqlx::query_with(&sql, values).execute(&mut *tx).await?;
}
tx.commit().await?;
removed
}
};
let parent_id_json = pk_seaval_to_json(parent_id.into());
crate::signals::emit_m2m_changed(junction, "set", parent_id_json, added_json, removed_json)
.await;
Ok(())
}
fn junction_handle(&self) -> Option<(P, &'static str)> {
Some((self.parent_id.clone()?, self.junction_table?))
}
pub async fn any_holds(
junction_table: &str,
parent_ids: &[P],
child_pk: T::PrimaryKey,
) -> Result<bool, sqlx::Error> {
if parent_ids.is_empty() {
return Ok(false);
}
let mut q = Query::select();
q.expr(Expr::value(1))
.from(crate::db::router::schema_qualified_table(junction_table))
.and_where(
Expr::col(Alias::new("parent_id"))
.is_in(parent_ids.iter().cloned().map(|v| v.into())),
)
.and_where(Expr::col(Alias::new("child_id")).eq(Expr::value(child_pk)))
.limit(1);
let pool = crate::db::pool_dispatched();
let exists = match pool {
crate::db::DbPool::Sqlite(p) => {
let (sql, values) = q.build_sqlx(SqliteQueryBuilder);
sqlx::query_with(&sql, values)
.fetch_optional(p)
.await?
.is_some()
}
crate::db::DbPool::Postgres(p) => {
let (sql, values) = q.build_sqlx(PostgresQueryBuilder);
sqlx::query_with(&sql, values)
.fetch_optional(p)
.await?
.is_some()
}
};
Ok(exists)
}
pub async fn holders_of_any(
junction_table: &str,
parent_ids: &[P],
) -> Result<Vec<T::PrimaryKey>, sqlx::Error>
where
T::PrimaryKey: for<'r> sqlx::Decode<'r, sqlx::Sqlite>
+ for<'r> sqlx::Decode<'r, sqlx::Postgres>
+ sqlx::Type<sqlx::Sqlite>
+ sqlx::Type<sqlx::Postgres>
+ Send
+ Unpin,
{
if parent_ids.is_empty() {
return Ok(Vec::new());
}
let mut q = Query::select();
q.distinct()
.column(Alias::new("child_id"))
.from(crate::db::router::schema_qualified_table(junction_table))
.and_where(
Expr::col(Alias::new("parent_id"))
.is_in(parent_ids.iter().cloned().map(|v| v.into())),
);
let pool = crate::db::pool_dispatched();
let rows: Vec<(T::PrimaryKey,)> = match pool {
crate::db::DbPool::Sqlite(p) => {
let (sql, values) = q.build_sqlx(SqliteQueryBuilder);
sqlx::query_as_with::<sqlx::Sqlite, (T::PrimaryKey,), _>(&sql, values)
.fetch_all(p)
.await?
}
crate::db::DbPool::Postgres(p) => {
let (sql, values) = q.build_sqlx(PostgresQueryBuilder);
sqlx::query_as_with::<sqlx::Postgres, (T::PrimaryKey,), _>(&sql, values)
.fetch_all(p)
.await?
}
};
Ok(rows.into_iter().map(|(pk,)| pk).collect())
}
}
fn junction_pool_for_write(parent_model: Option<&str>) -> crate::db::DbPool {
if let Some(name) = parent_model {
if let Some(meta) = crate::migrate::model_meta_ref(name) {
let ctx = crate::db::route_context::current();
let alias = crate::db::router::router().db_for_write(meta, &ctx);
return crate::db::pool_for_dispatched(alias.as_str()).clone();
}
}
crate::db::pool_dispatched().clone()
}
fn junction_pool_for_read(parent_model: Option<&str>) -> crate::db::DbPool {
if let Some(name) = parent_model {
if let Some(meta) = crate::migrate::model_meta_ref(name) {
let ctx = crate::db::route_context::current();
let alias = crate::db::router::router().db_for_read(meta, &ctx);
return crate::db::pool_for_dispatched(alias.as_str()).clone();
}
}
crate::db::pool_dispatched().clone()
}
pub async fn set_junction_dynamic(
junction_table: &str,
parent_id: sea_query::Value,
child_ids: Vec<sea_query::Value>,
parent_model: Option<&str>,
) -> Result<(), sqlx::Error> {
let insert: Option<sea_query::InsertStatement> = if child_ids.is_empty() {
None
} else {
let mut insert = Query::insert();
insert
.into_table(crate::db::router::schema_qualified_table(junction_table))
.columns([Alias::new("parent_id"), Alias::new("child_id")])
.on_conflict(
OnConflict::columns([Alias::new("parent_id"), Alias::new("child_id")])
.do_nothing()
.to_owned(),
);
for child_id in child_ids {
insert.values_panic([
Expr::value(parent_id.clone()).into(),
Expr::value(child_id).into(),
]);
}
Some(insert)
};
let mut delete = Query::delete();
delete
.from_table(crate::db::router::schema_qualified_table(junction_table))
.and_where(Expr::col(Alias::new("parent_id")).eq(parent_id));
let pool = junction_pool_for_write(parent_model);
match pool {
crate::db::DbPool::Sqlite(p) => {
let mut tx = p.begin().await?;
let (sql, values) = delete.build_sqlx(SqliteQueryBuilder);
sqlx::query_with(&sql, values).execute(&mut *tx).await?;
if let Some(insert) = insert {
let (sql, values) = insert.build_sqlx(SqliteQueryBuilder);
sqlx::query_with(&sql, values).execute(&mut *tx).await?;
}
tx.commit().await?;
}
crate::db::DbPool::Postgres(p) => {
let mut tx = p.begin().await?;
let (sql, values) = delete.build_sqlx(PostgresQueryBuilder);
sqlx::query_with(&sql, values).execute(&mut *tx).await?;
if let Some(insert) = insert {
let (sql, values) = insert.build_sqlx(PostgresQueryBuilder);
sqlx::query_with(&sql, values).execute(&mut *tx).await?;
}
tx.commit().await?;
}
}
Ok(())
}
pub async fn set_junction_dynamic_in_tx(
junction_table: &str,
parent_id: sea_query::Value,
child_ids: Vec<sea_query::Value>,
tx: &mut crate::db::Transaction,
) -> Result<(), sqlx::Error> {
let insert: Option<sea_query::InsertStatement> = if child_ids.is_empty() {
None
} else {
let mut insert = Query::insert();
insert
.into_table(crate::db::router::schema_qualified_table(junction_table))
.columns([Alias::new("parent_id"), Alias::new("child_id")])
.on_conflict(
OnConflict::columns([Alias::new("parent_id"), Alias::new("child_id")])
.do_nothing()
.to_owned(),
);
for child_id in child_ids {
insert.values_panic([
Expr::value(parent_id.clone()).into(),
Expr::value(child_id).into(),
]);
}
Some(insert)
};
let mut delete = Query::delete();
delete
.from_table(crate::db::router::schema_qualified_table(junction_table))
.and_where(Expr::col(Alias::new("parent_id")).eq(parent_id));
match tx.backend_name() {
"sqlite" => {
let inner = tx
.as_sqlite_mut()
.expect("backend_name == sqlite implies a sqlite tx");
let (sql, values) = delete.build_sqlx(SqliteQueryBuilder);
sqlx::query_with(&sql, values).execute(&mut **inner).await?;
if let Some(insert) = insert {
let (sql, values) = insert.build_sqlx(SqliteQueryBuilder);
sqlx::query_with(&sql, values).execute(&mut **inner).await?;
}
}
_ => {
let inner = tx
.as_pg_mut()
.expect("backend_name == postgres implies a pg tx");
let (sql, values) = delete.build_sqlx(PostgresQueryBuilder);
sqlx::query_with(&sql, values).execute(&mut **inner).await?;
if let Some(insert) = insert {
let (sql, values) = insert.build_sqlx(PostgresQueryBuilder);
sqlx::query_with(&sql, values).execute(&mut **inner).await?;
}
}
}
Ok(())
}
pub async fn load_junction_selection(
junction_table: &str,
parent_id: sea_query::Value,
child_pk_ty: super::SqlType,
parent_model: Option<&str>,
) -> Result<Vec<String>, sqlx::Error> {
let mut q = Query::select();
q.distinct()
.column(Alias::new("child_id"))
.from(crate::db::router::schema_qualified_table(junction_table))
.and_where(Expr::col(Alias::new("parent_id")).eq(parent_id));
let pool = junction_pool_for_read(parent_model);
let mut out: Vec<String> = Vec::new();
match &pool {
crate::db::DbPool::Sqlite(p) => {
let (sql, values) = q.build_sqlx(SqliteQueryBuilder);
let rows = sqlx::query_with(&sql, values).fetch_all(p).await?;
for row in rows {
let s = stringify_sqlite_child_id(&row, child_pk_ty)?;
out.push(s);
}
}
crate::db::DbPool::Postgres(p) => {
let (sql, values) = q.build_sqlx(PostgresQueryBuilder);
let rows = sqlx::query_with(&sql, values).fetch_all(p).await?;
for row in rows {
let s = stringify_postgres_child_id(&row, child_pk_ty)?;
out.push(s);
}
}
}
Ok(out)
}
fn stringify_sqlite_child_id(
row: &sqlx::sqlite::SqliteRow,
ty: super::SqlType,
) -> Result<String, sqlx::Error> {
use sqlx::Row;
Ok(match ty {
super::SqlType::SmallInt | super::SqlType::Integer => {
row.try_get::<i32, _>("child_id")?.to_string()
}
super::SqlType::BigInt | super::SqlType::ForeignKey => {
row.try_get::<i64, _>("child_id")?.to_string()
}
super::SqlType::Uuid => row.try_get::<uuid::Uuid, _>("child_id")?.to_string(),
_ => row.try_get::<String, _>("child_id")?,
})
}
fn stringify_postgres_child_id(
row: &sqlx::postgres::PgRow,
ty: super::SqlType,
) -> Result<String, sqlx::Error> {
use sqlx::Row;
Ok(match ty {
super::SqlType::SmallInt => row.try_get::<i16, _>("child_id")?.to_string(),
super::SqlType::Integer => row.try_get::<i32, _>("child_id")?.to_string(),
super::SqlType::BigInt | super::SqlType::ForeignKey => {
row.try_get::<i64, _>("child_id")?.to_string()
}
super::SqlType::Uuid => row.try_get::<uuid::Uuid, _>("child_id")?.to_string(),
_ => row.try_get::<String, _>("child_id")?,
})
}
fn child_pk_col<T: Model>() -> &'static str {
T::FIELDS
.iter()
.find(|f| f.primary_key)
.map(|f| f.name)
.unwrap_or("id")
}
async fn execute_sql(q: &sea_query::InsertStatement) -> Result<(), sqlx::Error> {
let pool = crate::db::pool_dispatched();
match pool {
crate::db::DbPool::Sqlite(p) => {
let (sql, values) = q.build_sqlx(SqliteQueryBuilder);
sqlx::query_with(&sql, values).execute(p).await?;
}
crate::db::DbPool::Postgres(p) => {
let (sql, values) = q.build_sqlx(PostgresQueryBuilder);
sqlx::query_with(&sql, values).execute(p).await?;
}
}
Ok(())
}
fn pk_seaval_to_json(v: sea_query::Value) -> serde_json::Value {
use sea_query::Value as SV;
use serde_json::json;
match v {
SV::TinyInt(Some(n)) => json!(n),
SV::SmallInt(Some(n)) => json!(n),
SV::Int(Some(n)) => json!(n),
SV::BigInt(Some(n)) => json!(n),
SV::TinyUnsigned(Some(n)) => json!(n),
SV::SmallUnsigned(Some(n)) => json!(n),
SV::Unsigned(Some(n)) => json!(n),
SV::BigUnsigned(Some(n)) => json!(n),
SV::Float(Some(f)) => json!(f),
SV::Double(Some(f)) => json!(f),
SV::String(Some(s)) => json!(*s),
other => json!(format!("{:?}", other)),
}
}
fn child_pk_ty<T: Model>() -> super::SqlType {
T::FIELDS
.iter()
.find(|f| f.primary_key)
.map(|f| f.ty)
.unwrap_or(super::SqlType::BigInt)
}
fn child_id_to_json_sqlite(
row: &sqlx::sqlite::SqliteRow,
ty: super::SqlType,
) -> Result<serde_json::Value, sqlx::Error> {
use serde_json::json;
use sqlx::Row;
Ok(match ty {
super::SqlType::SmallInt | super::SqlType::Integer => {
json!(row.try_get::<i32, _>("child_id")?)
}
super::SqlType::BigInt | super::SqlType::ForeignKey => {
json!(row.try_get::<i64, _>("child_id")?)
}
super::SqlType::Uuid => json!(row.try_get::<uuid::Uuid, _>("child_id")?.to_string()),
_ => json!(row.try_get::<String, _>("child_id")?),
})
}
fn child_id_to_json_pg(
row: &sqlx::postgres::PgRow,
ty: super::SqlType,
) -> Result<serde_json::Value, sqlx::Error> {
use serde_json::json;
use sqlx::Row;
Ok(match ty {
super::SqlType::SmallInt => json!(row.try_get::<i16, _>("child_id")?),
super::SqlType::Integer => json!(row.try_get::<i32, _>("child_id")?),
super::SqlType::BigInt | super::SqlType::ForeignKey => {
json!(row.try_get::<i64, _>("child_id")?)
}
super::SqlType::Uuid => json!(row.try_get::<uuid::Uuid, _>("child_id")?.to_string()),
_ => json!(row.try_get::<String, _>("child_id")?),
})
}
async fn execute_delete_returning_ids<T: Model>(
q: &sea_query::DeleteStatement,
) -> Result<Vec<serde_json::Value>, sqlx::Error> {
let ty = child_pk_ty::<T>();
let pool = crate::db::pool_dispatched();
Ok(match pool {
crate::db::DbPool::Sqlite(p) => {
let (sql, values) = q.build_sqlx(SqliteQueryBuilder);
let rows = sqlx::query_with(&sql, values).fetch_all(p).await?;
rows.iter()
.map(|r| child_id_to_json_sqlite(r, ty))
.collect::<Result<_, _>>()?
}
crate::db::DbPool::Postgres(p) => {
let (sql, values) = q.build_sqlx(PostgresQueryBuilder);
let rows = sqlx::query_with(&sql, values).fetch_all(p).await?;
rows.iter()
.map(|r| child_id_to_json_pg(r, ty))
.collect::<Result<_, _>>()?
}
})
}
async fn execute_delete(q: &sea_query::DeleteStatement) -> Result<u64, sqlx::Error> {
let pool = crate::db::pool_dispatched();
let n = match pool {
crate::db::DbPool::Sqlite(p) => {
let (sql, values) = q.build_sqlx(SqliteQueryBuilder);
sqlx::query_with(&sql, values)
.execute(p)
.await?
.rows_affected()
}
crate::db::DbPool::Postgres(p) => {
let (sql, values) = q.build_sqlx(PostgresQueryBuilder);
sqlx::query_with(&sql, values)
.execute(p)
.await?
.rows_affected()
}
};
Ok(n)
}
impl<T: Model + Serialize, P: PrimaryKey> Serialize for M2M<T, P> {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
if let Some(resolved) = &self.resolved {
resolved.serialize(s)
} else {
let empty: Vec<T> = Vec::new();
empty.serialize(s)
}
}
}
impl<'de, T: Model, P: PrimaryKey> Deserialize<'de> for M2M<T, P> {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let _ = serde_json::Value::deserialize(d)?;
Ok(Self::empty())
}
}
impl<T: Model, P: PrimaryKey> sqlx::Type<sqlx::Sqlite> for M2M<T, P> {
fn type_info() -> sqlx::sqlite::SqliteTypeInfo {
<i64 as sqlx::Type<sqlx::Sqlite>>::type_info()
}
fn compatible(ty: &sqlx::sqlite::SqliteTypeInfo) -> bool {
<i64 as sqlx::Type<sqlx::Sqlite>>::compatible(ty)
}
}
impl<T: Model, P: PrimaryKey> sqlx::Type<sqlx::Postgres> for M2M<T, P> {
fn type_info() -> sqlx::postgres::PgTypeInfo {
<i64 as sqlx::Type<sqlx::Postgres>>::type_info()
}
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
<i64 as sqlx::Type<sqlx::Postgres>>::compatible(ty)
}
}
impl<'r, T: Model, P: PrimaryKey> sqlx::Decode<'r, sqlx::Sqlite> for M2M<T, P> {
fn decode(
value: sqlx::sqlite::SqliteValueRef<'r>,
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let _ = <i64 as sqlx::Decode<sqlx::Sqlite>>::decode(value)?;
Ok(Self::empty())
}
}
impl<'r, T: Model, P: PrimaryKey> sqlx::Decode<'r, sqlx::Postgres> for M2M<T, P> {
fn decode(
value: sqlx::postgres::PgValueRef<'r>,
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let _ = <i64 as sqlx::Decode<sqlx::Postgres>>::decode(value)?;
Ok(Self::empty())
}
}
impl<'q, T: Model, P: PrimaryKey> sqlx::Encode<'q, sqlx::Sqlite> for M2M<T, P> {
fn encode_by_ref(
&self,
buf: &mut <sqlx::Sqlite as sqlx::Database>::ArgumentBuffer<'q>,
) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
<i64 as sqlx::Encode<'q, sqlx::Sqlite>>::encode_by_ref(&0i64, buf)
}
}
impl<'q, T: Model, P: PrimaryKey> sqlx::Encode<'q, sqlx::Postgres> for M2M<T, P> {
fn encode_by_ref(
&self,
buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
<i64 as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&0i64, buf)
}
}