use crate::parse::deps::*;
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
pub const BLOCK_RANGE_COL: &str = "__block_number_range";
pub const BLOCK_NUMBER_COL: &str = "__block_number";
impl crate::codegen::CodegenWrapper<super::TransformerStruct> {
pub(crate) fn impl_persistent(&self) -> syn::Result<TokenStream> {
let from_row = self.from_row();
let db_table = self.impl_db_table()?;
Ok(quote! {
#from_row
#db_table
})
}
pub(crate) fn impl_db_table(&self) -> syn::Result<TokenStream> {
let db_ty_ident = self.db_name();
let max_table_rows = self.struct_opts.db_opts.partition_max_rows;
let max_table_bytes = self.struct_opts.db_opts.partition_max_bytes;
let sql_table_ident = self.sql_table_name();
let sql_id_name = &*self.id_field().sql_config.name;
let cidomap = &self.struct_opts.cidomap;
let mut fields = vec![];
let kind = if self.is_entity() {
let check_not_empty = format!("NOT isempty({BLOCK_RANGE_COL})");
fields.push(quote!(::cido::__internal::ColDescription {
name: #BLOCK_RANGE_COL,
type_fn: <<<#cidomap as ::cido::__internal::Cidomap>::Network as ::cido::__internal::Network>::BlockNumberRange as ::cido::__internal::sqlx::Type<::cido::__internal::sqlx::Postgres>>::type_info,
constraints: &[
::cido::__internal::ColConstraint::NotNull,
::cido::__internal::ColConstraint::Check { check: #check_not_empty },
],
is_static: false,
indexed: false,
}));
quote!(::cido::__internal::RecordType::Entity)
} else {
fields.push(quote!(::cido::__internal::ColDescription {
name: #BLOCK_NUMBER_COL,
type_fn: <<<#cidomap as ::cido::__internal::Cidomap>::Network as ::cido::__internal::Network>::BlockNumber as ::cido::__internal::sqlx::Type<::cido::__internal::sqlx::Postgres>>::type_info,
constraints: &[::cido::__internal::ColConstraint::NotNull],
is_static: false,
indexed: false,
}));
quote!(::cido::__internal::RecordType::Event)
};
let id_field_idx = self.id_offset + 1;
for f in self.fields.iter() {
let name = &*f.sql_config.name;
let ty = f.db_type();
let mut constraints = quote!();
if !f.sql_config.nullable {
quote!(::cido::__internal::ColConstraint::NotNull,).to_tokens(&mut constraints)
}
let type_fn = quote!(<#ty as ::cido::__internal::sqlx::Type<::cido::__internal::sqlx::Postgres>>::type_info);
let is_static = f.is_static;
let indexed = f.indexed;
fields.push(quote!(
::cido::__internal::ColDescription {
name: #name,
type_fn: #type_fn,
constraints: &[#constraints],
is_static: #is_static,
indexed: #indexed,
}
));
}
let table_constraints = {
let list = match self.struct_type {
super::StructType::Entity => {
let method = format!(
"using gist({sql_id_name} with =, {} with &&)",
BLOCK_RANGE_COL
);
let gist_name = format!("exclude_{sql_id_name}_{BLOCK_RANGE_COL}");
let check_block_number_range_open = format!("upper({BLOCK_RANGE_COL}) is null");
let check_block_number_range_open_name = format!("{BLOCK_RANGE_COL}_is_open");
let check_block_number_range_closed = format!("upper({BLOCK_RANGE_COL}) is not null");
let check_block_number_range_closed_name = format!("{BLOCK_RANGE_COL}_is_closed");
let check_lower_is_not_null = format!("lower({BLOCK_RANGE_COL}) is not null");
let check_lower_is_not_null_name = format!("lower_{BLOCK_RANGE_COL}_is_not_null");
quote! {
::cido::__internal::TableConstraint::new(
::cido::__internal::TableConstraintType::Exclude{method: #method},
::core::option::Option::Some(#gist_name),
)
.apply_to_non_current(),
::cido::__internal::TableConstraint::new(
::cido::__internal::TableConstraintType::PrimaryKey{columns: &[#sql_id_name]},
::core::option::Option::Some("pk"),
)
.apply_to_current(),
::cido::__internal::TableConstraint::new(
::cido::__internal::TableConstraintType::Check{check: #check_block_number_range_open},
::core::option::Option::Some(#check_block_number_range_open_name),
)
.apply_to_current(),
::cido::__internal::TableConstraint::new(
::cido::__internal::TableConstraintType::Check{check: #check_block_number_range_closed},
::core::option::Option::Some(#check_block_number_range_closed_name),
)
.apply_to_non_current(),
::cido::__internal::TableConstraint::new(
::cido::__internal::TableConstraintType::Check{check: #check_lower_is_not_null},
::core::option::Option::Some(#check_lower_is_not_null_name),
)
}
}
super::StructType::Event => quote! {
::cido::__internal::TableConstraint::new(
::cido::__internal::TableConstraintType::PrimaryKey{columns: &[#sql_id_name]}, ::core::option::Option::None,
),
},
};
quote!(&[#list])
};
let mut tuple_bindings = vec![];
for (i, f) in self.fields.iter().enumerate() {
let f_name = &f.db_config.name;
let clone = (i < self.fields.len() - 1).then(|| quote!(.clone()));
let conversion = f.db_config.field_to_db.conversion(f_name);
let transform = if f.self_type() == f.db_type() || f.entity_or_event.is_some() {
quote! {|s|&s.#f_name}
} else {
quote! {
|s| {
let #f_name = &s.#f_name;
#conversion
}
}
};
tuple_bindings.push(quote! {
query.push_bind(::cido::__internal::PgBindIter::from(iter #clone.map(#transform)));
});
}
Ok(quote! {
impl #db_ty_ident {
fn __static_table_resolver() -> &'static ::std::sync::OnceLock::<::cido::__internal::TableResolver<#db_ty_ident>> {
static __TABLE_RESOLVER: ::std::sync::OnceLock::<::cido::__internal::TableResolver<#db_ty_ident>> = ::std::sync::OnceLock::new();
&__TABLE_RESOLVER
}
fn __table_descriptor_inner() -> &'static ::cido::__internal::TableDescriptor::<#db_ty_ident> {
static FIELDS: &[::cido::__internal::ColDescription] = &[#(#fields),*];
static TABLE_DESCRIPTOR: ::cido::__internal::TableDescriptor::<#db_ty_ident> = ::cido::__internal::TableDescriptor::<#db_ty_ident> {
base_table_name: #sql_table_ident,
fields: FIELDS,
id_field: &FIELDS[#id_field_idx],
event_order_field: &FIELDS[0],
constraints: #table_constraints,
record_kind: #kind,
max_table_rows: #max_table_rows,
max_table_bytes: #max_table_bytes,
table_resolver: #db_ty_ident::__static_table_resolver,
};
&TABLE_DESCRIPTOR
}
fn __bind_array_inner<'q>(
iter: impl Iterator<Item = &'q Self> + Clone + Send + 'q,
query: &mut ::cido::__internal::sqlx::query_builder::Separated<'_, 'q, ::cido::__internal::sqlx::Postgres, &'static str>,
) {
#(#tuple_bindings)*
}
}
})
}
#[allow(clippy::wrong_self_convention)]
fn from_row(&self) -> TokenStream {
let db_ty_ident = self.struct_name();
let resolve_fields = self.fields.iter().map(|f| {
let field_name = &f.name;
let sql_name = &*f.sql_config.name;
let db_type = f.db_type();
let db_conversion = f.db_config.db_to_field.conversion(field_name);
quote! {
if let Ok(#field_name) = row.try_get::<#db_type, _>(#sql_name) {
__this__.#field_name = #db_conversion;
}
}
});
quote! {
impl<'r, R> ::cido::__internal::sqlx::FromRow<'r, R> for #db_ty_ident
where R: ::cido::__internal::sqlx::Row<Database = ::cido::__internal::sqlx::Postgres>,
for<'a> &'a str: ::cido::__internal::sqlx::ColumnIndex<R>,
{
#[inline]
fn from_row(row: &'r R) -> ::core::result::Result<Self, ::cido::__internal::sqlx::Error> {
use ::cido::__internal::sqlx::Row;
let mut __this__ = <Self as ::core::default::Default>::default();
let mut f = || -> ::core::result::Result::<(), ::cido::prelude::CidomapError> {
#(#resolve_fields)*
::core::result::Result::Ok(())
};
match f() {
::core::result::Result::Ok(()) => ::core::result::Result::Ok(__this__),
::core::result::Result::Err(e) => ::core::result::Result::Err(::cido::__internal::sqlx::Error::Decode(::std::boxed::Box::new(e) as _)),
}
}
}
}
}
}