use diesel::dsl::count;
use diesel::{
insert_into, Connection, ExpressionMethods, OptionalExtension, PgConnection, QueryDsl,
RunQueryDsl, SelectableHelper,
};
use snafu::ResultExt;
use radicle::cob::{ObjectId, TypeName};
use radicle::git::Oid;
use radicle::prelude::RepoId;
use crate::models::activity_feed::ActivityFeedOperation;
use crate::models::entry::{OperationEntry, TimelineEntry};
use crate::schema::activity_feed_timeline;
use crate::schema::{activity_feed_operations, seeded_radicle_repository};
use crate::storage::{FeedStorage, StorageStats};
pub struct PostgresStorage {
connection: PgConnection,
}
impl PostgresStorage {
pub fn new(db_url: String) -> Result<Self, snafu::Whatever> {
let conn =
PgConnection::establish(&db_url).whatever_context("Unable to establish connection")?;
Ok(Self { connection: conn })
}
}
impl FeedStorage for PostgresStorage {
type Error = snafu::Whatever;
fn get_last_processed_operation(
&mut self,
rid: &RepoId,
cob_id: &ObjectId,
typename: &TypeName,
) -> Result<Option<String>, Self::Error> {
let rows = activity_feed_timeline::table
.filter(activity_feed_timeline::repo.eq(rid.to_string()))
.filter(activity_feed_timeline::cob_id.eq(cob_id.to_string()))
.filter(activity_feed_timeline::typename.eq(typename.to_string()))
.select(activity_feed_timeline::last_operation_id)
.load::<Option<String>>(&mut self.connection)
.whatever_context("Unable to get_last_processed_operation")?;
if rows.is_empty() {
Ok(None)
} else {
Ok(rows[0].clone())
}
}
fn get_operation_by_id(
&mut self,
id: &radicle::git::Oid,
) -> Result<ActivityFeedOperation, Self::Error> {
activity_feed_operations::table
.filter(activity_feed_operations::operation_id.eq(id.to_string()))
.select(ActivityFeedOperation::as_select())
.first::<ActivityFeedOperation>(&mut self.connection)
.whatever_context("Unable to get_operation_by_id")
}
fn operation_exists(&mut self, operation_id: &Oid) -> Result<bool, Self::Error> {
let rows = activity_feed_operations::table
.filter(activity_feed_operations::operation_id.eq(operation_id.to_string()))
.select(activity_feed_operations::operation_id)
.first::<String>(&mut self.connection)
.optional()
.whatever_context("Unable to load last feed operation")?;
Ok(rows.is_some())
}
fn insert_timeline_entry(&mut self, entry: &TimelineEntry) -> Result<(), Self::Error> {
insert_into(activity_feed_timeline::table)
.values(entry)
.on_conflict((
activity_feed_timeline::repo,
activity_feed_timeline::node,
activity_feed_timeline::cob_id,
))
.do_update()
.set(activity_feed_timeline::last_operation_id.eq(entry.last_operation_id.clone()))
.execute(&mut self.connection)
.whatever_context("Unable to insert")?;
Ok(())
}
fn resolve_rid(&mut self, rid: &RepoId) -> Result<Option<String>, Self::Error> {
seeded_radicle_repository::table
.filter(seeded_radicle_repository::repository_id.eq(rid.to_string()))
.select(seeded_radicle_repository::alias)
.first::<Option<String>>(&mut self.connection)
.whatever_context("Unable to resolve rid")
}
fn insert_batch(&mut self, entries: &[OperationEntry]) -> Result<(), Self::Error> {
if entries.is_empty() {
return Ok(());
}
insert_into(activity_feed_operations::table)
.values(entries)
.execute(&mut self.connection)
.whatever_context("context")?;
Ok(())
}
fn get_stats(&mut self) -> Result<StorageStats, Self::Error> {
let mut stats = StorageStats::default();
let statement_total_operations = activity_feed_operations::table
.count()
.first::<i64>(&mut self.connection)
.whatever_context("context")?;
stats.total_operations = statement_total_operations as u64;
let statement_operations_by_type = activity_feed_operations::table
.count()
.group_by(activity_feed_operations::typename)
.select((
activity_feed_operations::typename,
count(activity_feed_operations::id),
))
.load::<(String, i64)>(&mut self.connection)
.whatever_context("context")?;
for (typename, count) in statement_operations_by_type {
stats.operations_by_type.insert(typename, count as u64);
}
let statement_tracked_objects = activity_feed_operations::table
.count()
.get_results::<i64>(&mut self.connection)
.whatever_context("context")?;
for count in statement_tracked_objects {
stats.tracked_objects = count as u64;
}
Ok(stats)
}
}