use crate::{
cidomap::{Cidomap, Identifiable, Transformer},
filter::*,
network::Network,
persistence::{ColDescription, DbTable, MetaInfo},
sql::query_builder::*,
};
use async_graphql::indexmap::IndexMap;
use futures::{future::BoxFuture, FutureExt};
use sqlx::Postgres;
use std::{borrow::Borrow, sync::Arc};
mod derived_map;
mod order_by;
mod order_direction;
mod type_map;
pub use derived_map::*;
pub use order_by::*;
pub use order_direction::*;
pub use type_map::*;
pub trait GraphqlMetaExtension<N: Network + ?Sized>:
async_graphql::ObjectType + Send + Sync + Sized + 'static
{
fn from_block(sync_block: N::BlockNumber) -> Self;
}
pub trait ToIdentifiable {
type Identifiable;
type DbIdentifiable;
}
impl<T: Transformer> ToIdentifiable for T {
type Identifiable = <T as Identifiable>::Id;
type DbIdentifiable = <T::Persistent as Identifiable>::Id;
}
impl<T: ToTransformer> ToIdentifiable for Vec<T> {
type Identifiable = Vec<<T::Transformer as Identifiable>::Id>;
type DbIdentifiable = Vec<<<T::Transformer as Transformer>::Persistent as Identifiable>::Id>;
}
impl<T: ToTransformer> ToIdentifiable for Option<T> {
type Identifiable = Option<<T::Transformer as Identifiable>::Id>;
type DbIdentifiable = Option<<<T::Transformer as Transformer>::Persistent as Identifiable>::Id>;
}
pub trait ToTransformer {
type Transformer: Transformer;
}
impl<T: Transformer> ToTransformer for T {
type Transformer = T;
}
impl<T: ToTransformer> ToTransformer for Vec<T> {
type Transformer = T::Transformer;
}
impl<T: ToTransformer> ToTransformer for Option<T> {
type Transformer = T::Transformer;
}
pub trait FromId<T>: Sized {
fn from(value: T) -> Self;
}
impl<T: GraphqlOutputType> FromId<Vec<T::Id>> for Vec<T> {
fn from(values: Vec<T::Id>) -> Self {
values
.into_iter()
.map(|val| {
let mut t = T::default();
t.set_id(val);
t
})
.collect::<Vec<_>>()
}
}
impl<T: GraphqlOutputType> FromId<T::Id> for T {
fn from(value: T::Id) -> Self {
let mut t = T::default();
t.set_id(value);
t
}
}
impl<T: GraphqlOutputType> FromId<T::Id> for Option<T> {
fn from(value: T::Id) -> Self {
let mut t = T::default();
t.set_id(value);
Some(t)
}
}
pub trait ToGraphql {
type Graphql;
}
impl<T: Transformer> ToGraphql for T {
type Graphql = T::GraphqlOutput;
}
impl<T: Transformer> ToGraphql for Option<T> {
type Graphql = Option<T::GraphqlOutput>;
}
impl<T: Transformer> ToGraphql for Vec<T> {
type Graphql = Vec<T::GraphqlOutput>;
}
#[derive(Debug, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct GraphqlPagination<T: Transformer> {
pub(crate) first: u64,
pub(crate) skip: u64,
_type: std::marker::PhantomData<fn() -> T>,
}
impl<T: Transformer> Default for GraphqlPagination<T> {
fn default() -> Self {
Self::new(None, None).unwrap()
}
}
impl<T: Transformer> Clone for GraphqlPagination<T> {
fn clone(&self) -> Self {
Self {
first: self.first,
skip: self.skip,
_type: self._type,
}
}
}
impl<T: Transformer> GraphqlPagination<T> {
#[inline]
pub fn new(first: Option<u64>, skip: Option<u64>) -> async_graphql::Result<Self> {
let max_first = <T::GraphqlOutput as GraphqlOutputType>::MAX_RETURNED_ITEMS;
let max_skip = <T::GraphqlOutput as GraphqlOutputType>::MAX_SKIP_ITEMS;
let first = if let Some(first) = first {
if first > max_first {
return Err(async_graphql::Error::new(format!(
"first is beyond limit of {max_first}"
)));
}
first
} else {
max_first
};
let skip = if let Some(skip) = skip {
if skip > max_skip {
return Err(async_graphql::Error::new(format!(
"skip is beyond limit of {max_skip}"
)));
}
skip
} else {
0
};
Ok(Self {
first,
skip,
_type: std::marker::PhantomData,
})
}
}
pub struct GraphqlQueryBuilder<T>
where
T: Transformer,
<<T as Transformer>::GraphqlInput as GraphqlInputType>::Where: RootWhere<
IdFilter = <<<T as Transformer>::Persistent as Identifiable>::Id as ToDbFilter>::Filter,
>,
<<T as Transformer>::Persistent as Identifiable>::Id: ToDbFilter,
<<<T as Transformer>::Persistent as Identifiable>::Id as ToDbFilter>::Filter: Filter,
{
pub comparison: Option<Arc<<T::GraphqlInput as GraphqlInputType>::Where>>,
pub id_comparison2: Option<Arc<<<T as ToIdentifiable>::DbIdentifiable as ToDbFilter>::Filter>>,
pub order_by: Option<Arc<GraphqlOrderBy<<T::GraphqlInput as GraphqlInputType>::OrderByVariant>>>,
pub order_direction: Arc<GraphqlOrderDirection>,
pub pagination: GraphqlPagination<T>,
}
impl<T> Clone for GraphqlQueryBuilder<T>
where
T: Transformer,
<<T as Transformer>::GraphqlInput as GraphqlInputType>::Where: RootWhere<
IdFilter = <<<T as Transformer>::Persistent as Identifiable>::Id as ToDbFilter>::Filter,
>,
<<T as Transformer>::Persistent as Identifiable>::Id: ToDbFilter,
<<<T as Transformer>::Persistent as Identifiable>::Id as ToDbFilter>::Filter: Filter,
{
fn clone(&self) -> Self {
Self {
comparison: self.comparison.clone(),
id_comparison2: self.id_comparison2.clone(),
order_by: self.order_by.clone(),
order_direction: self.order_direction.clone(),
pagination: self.pagination.clone(),
}
}
}
impl<T> Default for GraphqlQueryBuilder<T>
where
T: Transformer,
<<T as Transformer>::GraphqlInput as GraphqlInputType>::Where: RootWhere<
IdFilter = <<<T as Transformer>::Persistent as Identifiable>::Id as ToDbFilter>::Filter,
>,
<<T as Transformer>::Persistent as Identifiable>::Id: ToDbFilter,
<<<T as Transformer>::Persistent as Identifiable>::Id as ToDbFilter>::Filter: Filter,
{
fn default() -> Self {
Self {
comparison: None,
id_comparison2: None,
order_by: None,
order_direction: Arc::new(GraphqlOrderDirection::Single(Default::default())),
pagination: GraphqlPagination::<T>::default(),
}
}
}
impl<T> core::fmt::Debug for GraphqlQueryBuilder<T>
where
T: Transformer,
<<T as Transformer>::GraphqlInput as GraphqlInputType>::Where: RootWhere<
IdFilter = <<<T as Transformer>::Persistent as Identifiable>::Id as ToDbFilter>::Filter,
>,
<<T as Transformer>::Persistent as Identifiable>::Id: ToDbFilter,
<<<T as Transformer>::Persistent as Identifiable>::Id as ToDbFilter>::Filter: Filter,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GraphqlQueryBuilder").finish()
}
}
impl<T> GraphqlQueryBuilder<T>
where
T: Transformer,
<<T as Transformer>::GraphqlInput as GraphqlInputType>::Where: RootWhere<
BlockNumber = <<<T as Transformer>::Cidomap as Cidomap>::Network as Network>::BlockNumber,
IdFilter = <<<T as Transformer>::Persistent as Identifiable>::Id as ToDbFilter>::Filter,
>,
<<T as Transformer>::Persistent as Identifiable>::Id: ToDbFilter,
<<<T as Transformer>::Persistent as Identifiable>::Id as ToDbFilter>::Filter: Filter,
{
#[inline]
pub fn new(
comparison: Option<Arc<<T::GraphqlInput as GraphqlInputType>::Where>>,
id_comparison2: Option<Arc<<<T as ToIdentifiable>::DbIdentifiable as ToDbFilter>::Filter>>,
order_by: Option<Arc<GraphqlOrderBy<<T::GraphqlInput as GraphqlInputType>::OrderByVariant>>>,
order_direction: Arc<GraphqlOrderDirection>,
pagination: GraphqlPagination<T>,
) -> Self {
Self {
comparison,
id_comparison2,
order_by,
order_direction,
pagination,
}
}
}
#[cido_macros::impls(keep)]
impl<T> GraphqlQueryBuilder<T>
where
T: Transformer,
<<T as Transformer>::GraphqlInput as GraphqlInputType>::Where: RootWhere<
BlockNumber = <<<T as Transformer>::Cidomap as Cidomap>::Network as Network>::BlockNumber,
IdFilter = <<<T as Transformer>::Persistent as Identifiable>::Id as ToDbFilter>::Filter,
>,
<<T as Transformer>::Persistent as Identifiable>::Id: ToDbFilter,
<<<T as Transformer>::Persistent as Identifiable>::Id as ToDbFilter>::Filter: Filter,
{
pub async fn fulltext_search<'b, 'ctx>(
&self,
ctx: &'b async_graphql::Context<'ctx>,
include_fields: &'static [&'static str],
text: String,
block_number: <<T::GraphqlInput as GraphqlInputType>::Where as RootWhere>::BlockNumber,
) -> Result<Vec<T::GraphqlOutput>, async_graphql::Error> {
self
.fulltext_search_internal(ctx, include_fields, text, block_number)
.await
}
pub async fn fetch_optional_by_blocknumber<'a, 'ctx>(
self,
ctx: &async_graphql::Context<'ctx>,
id: &'a <T::Persistent as Identifiable>::IdBorrow,
block_number: <<T::Cidomap as Cidomap>::Network as Network>::BlockNumber,
) -> Result<Option<T::GraphqlOutput>, async_graphql::Error> {
self
.fetch_optional_by_blocknumber_internal(ctx, id, block_number)
.await
}
pub async fn fetch_optional_by_blockrange<'a, 'ctx>(
self,
ctx: &async_graphql::Context<'ctx>,
id: &'a <T::Persistent as Identifiable>::IdBorrow,
block_number: <<T::Cidomap as Cidomap>::Network as Network>::BlockNumber,
_end_block_number: Option<<<T::Cidomap as Cidomap>::Network as Network>::BlockNumber>,
) -> Result<Option<T::GraphqlOutput>, async_graphql::Error> {
self
.fetch_optional_by_blockrange_internal(ctx, id, block_number, _end_block_number)
.await
}
pub async fn fetch_all<'s: 'b, 'b, 'ctx>(
self,
ctx: &async_graphql::Context<'ctx>,
block_number: <<T::GraphqlInput as GraphqlInputType>::Where as RootWhere>::BlockNumber,
) -> Result<Vec<T::GraphqlOutput>, async_graphql::Error> {
self.fetch_all_internal(ctx, block_number).await
}
pub async fn fetch_history<'b, 'ctx>(
&self,
ctx: &'b async_graphql::Context<'ctx>,
id: Option<&'b <T::Persistent as Identifiable>::IdBorrow>,
start_block: <<T::GraphqlInput as GraphqlInputType>::Where as RootWhere>::BlockNumber,
end_block: <<T::GraphqlInput as GraphqlInputType>::Where as RootWhere>::BlockNumber,
get_persistent: bool,
) -> Result<(Vec<T::GraphqlOutput>, Option<Vec<T::Persistent>>), async_graphql::Error> {
self
.fetch_history_internal(ctx, id, start_block, end_block, get_persistent)
.await
}
#[inline]
pub(crate) async fn fetch_helper(
rows: Vec<sqlx::postgres::PgRow>,
ctx: &async_graphql::Context<'_>,
block_number: <<T::Cidomap as Cidomap>::Network as Network>::BlockNumber,
all_fields: bool,
same_id: bool,
) -> Result<Vec<T::GraphqlOutput>, async_graphql::Error> {
Self::fetch_helper_internal(rows, ctx, block_number, all_fields, same_id).await
}
pub(crate) fn apply_order_by<'a, Clause>(
order_by: Option<
&Arc<GraphqlOrderBy<<<T as Transformer>::GraphqlInput as GraphqlInputType>::OrderByVariant>>,
>,
order_direction: &GraphqlOrderDirection,
limit: GraphqlPagination<T>,
builder: QueryBuilder<'a, Clause>,
) -> Result<sqlx::QueryBuilder<'a, Postgres>, async_graphql::Error>
where
T: Transformer,
Clause: QueryBuilderTransition<OrderByClause>,
{
Self::apply_order_by_internal(order_by, order_direction, limit, builder)
}
}
pub trait GraphqlInputType: Send + Sync + 'static {
type Filter: Filter + Send + Sync + 'static;
type Where: RootWhere<BlockNumber = <<<Self::Transformer as Transformer>::Cidomap as Cidomap>::Network as Network>::BlockNumber> + ComplexFilter + TryMapFilter<Self::Filter> + Send + Sync + 'static + std::default::Default;
type OrderByVariant: GraphqlOrderByVariant;
type Transformer: Transformer;
}
pub trait GraphqlOutputType: Default + Sized + Send + Sync + 'static {
type Transformer: Transformer;
type Id: Clone + Eq + core::hash::Hash + Send + Sync + 'static;
const MAX_RETURNED_ITEMS: u64 = 100_000;
const MAX_SKIP_ITEMS: u64 = 10_000;
fn graphql_type_name() -> std::borrow::Cow<'static, str>;
fn selected_fields(ctx: &async_graphql::Context<'_>) -> Vec<&'static ColDescription>;
fn only_select_id(ctx: &async_graphql::Context<'_>) -> bool;
fn __get_query_context(&self) -> GraphqlQueryBuilder<Self::Transformer>
where
<<Self::Transformer as Transformer>::GraphqlInput as GraphqlInputType>::Where: RootWhere<IdFilter = <<<Self::Transformer as Transformer>::Persistent as Identifiable>::Id as ToDbFilter>::Filter>,
<<Self::Transformer as Transformer>::Persistent as Identifiable>::Id: ToDbFilter,
<<<Self::Transformer as Transformer>::Persistent as Identifiable>::Id as ToDbFilter>::Filter: Filter
;
fn get_id(&self) -> &Self::Id;
fn set_id(&mut self, id: Self::Id);
fn from_id(id: Self::Id) -> Self {
let mut graphql = Self::default();
graphql.set_id(id);
graphql
}
fn __set_block_number(
&mut self,
range: <<<Self::Transformer as Transformer>::Cidomap as Cidomap>::Network as Network>::BlockNumberRange,
);
fn __get_start_block(
&self,
) -> <<<Self::Transformer as Transformer>::Cidomap as Cidomap>::Network as Network>::BlockNumber;
fn __get_query_block(
&self,
) -> <<<Self::Transformer as Transformer>::Cidomap as Cidomap>::Network as Network>::BlockNumber;
fn __set_query_block(
&mut self,
block: <<<Self::Transformer as Transformer>::Cidomap as Cidomap>::Network as Network>::BlockNumber,
);
}
pub trait GraphqlOrderByVariant:
core::hash::Hash
+ Ord
+ Copy
+ Default
+ crate::cidomap::FromStrErrDisplay
+ Sized
+ Send
+ Sync
+ 'static
+ async_graphql::InputType
{
fn id() -> Self;
fn order_by_type_name() -> &'static str;
fn enum_values() -> IndexMap<&'static str, async_graphql::registry::MetaEnumValue>;
fn db_field_name(&self) -> &'static str;
fn is_block_number(&self) -> bool;
}
#[derive(async_graphql::MergedObject)]
#[graphql(name = "_Meta_")]
pub struct GraphqlCidomapMetaInfo<N: Network>(MetaInfo<N>, N::GraphqlMetaExtension);
impl<N: Network> GraphqlCidomapMetaInfo<N> {
pub fn new(meta: MetaInfo<N>) -> Self {
let extension =
<N::GraphqlMetaExtension as GraphqlMetaExtension<N>>::from_block(meta.get_sync_block());
Self(meta, extension)
}
}
pub trait LoaderConfig {
const CACHE_SIZE: usize;
const MAX_BATCH_SIZE: usize;
const DELAY: std::time::Duration;
}