Skip to main content

CachedDatabaseAdapter

Struct CachedDatabaseAdapter 

Source
pub struct CachedDatabaseAdapter<A: DatabaseAdapter> { /* private fields */ }
Expand description

Cached database adapter wrapper.

Wraps any DatabaseAdapter implementation with transparent query result caching. Cache keys include query, variables, WHERE clause, and schema version for security and correctness.

§Cache Behavior

  • Cache Hit: Returns cached result in ~0.1ms (50-200x faster than database)
  • Cache Miss: Executes query via underlying adapter, stores result in cache
  • Invalidation: Call invalidate_views() after mutations to clear affected caches

§Thread Safety

This adapter is Send + Sync and can be safely shared across async tasks. The underlying cache uses Arc<Mutex<>> for thread-safe access.

§Example

use fraiseql_core::cache::{CachedDatabaseAdapter, QueryResultCache, CacheConfig, InvalidationContext};
use fraiseql_core::db::{postgres::PostgresAdapter, DatabaseAdapter};

let db = PostgresAdapter::new("postgresql://localhost/db").await?;
let cache = QueryResultCache::new(CacheConfig::default());
let adapter = CachedDatabaseAdapter::new(db, cache, "1.0.0".to_string());

// First query - cache miss (slower)
let users1 = adapter.execute_where_query("v_user", Some(&where_clause), None, None).await?;

// Second query - cache hit (fast!)
let users2 = adapter.execute_where_query("v_user", Some(&where_clause), None, None).await?;

// After mutation, invalidate
let invalidation = InvalidationContext::for_mutation(
    "createUser",
    vec!["v_user".to_string()]
);
adapter.invalidate_views(&invalidation.modified_views)?;

Implementations§

Source§

impl<A: DatabaseAdapter> CachedDatabaseAdapter<A>

Source

pub fn new(adapter: A, cache: QueryResultCache, schema_version: String) -> Self

Create new cached database adapter.

§Arguments
  • adapter - Underlying database adapter to wrap
  • cache - Query result cache instance
  • schema_version - Current schema version (e.g., git hash, semver)
§Example
use fraiseql_core::cache::{CachedDatabaseAdapter, QueryResultCache, CacheConfig};
use fraiseql_core::db::postgres::PostgresAdapter;

let db = PostgresAdapter::new("postgresql://localhost/db").await?;
let cache = QueryResultCache::new(CacheConfig::default());
let adapter = CachedDatabaseAdapter::new(
    db,
    cache,
    env!("CARGO_PKG_VERSION").to_string()  // Use package version
);
Source

pub fn with_fact_table_config( adapter: A, cache: QueryResultCache, schema_version: String, fact_table_config: FactTableCacheConfig, ) -> Self

Create new cached database adapter with fact table caching configuration.

§Arguments
  • adapter - Underlying database adapter to wrap
  • cache - Query result cache instance
  • schema_version - Current schema version (e.g., git hash, semver)
  • fact_table_config - Configuration for fact table aggregation caching
§Example
use fraiseql_core::cache::{
    CachedDatabaseAdapter, QueryResultCache, CacheConfig,
    FactTableCacheConfig, FactTableVersionStrategy,
};
use fraiseql_core::db::postgres::PostgresAdapter;

let db = PostgresAdapter::new("postgresql://localhost/db").await?;
let cache = QueryResultCache::new(CacheConfig::default());

// Configure fact table caching strategies
let mut ft_config = FactTableCacheConfig::default();
ft_config.set_strategy("tf_sales", FactTableVersionStrategy::VersionTable);
ft_config.set_strategy("tf_events", FactTableVersionStrategy::time_based(300));

let adapter = CachedDatabaseAdapter::with_fact_table_config(
    db,
    cache,
    "1.0.0".to_string(),
    ft_config,
);
Source

pub fn invalidate_views(&self, views: &[String]) -> Result<u64>

Invalidate cache entries that read from specified views.

Call this after mutations to ensure cache consistency. All cache entries that accessed any of the modified views will be removed.

§Arguments
  • views - List of views/tables that were modified
§Returns

Number of cache entries invalidated

§Errors

Returns error if cache mutex is poisoned (very rare).

§Example
// After creating a user
let count = adapter.invalidate_views(&["v_user".to_string()])?;
println!("Invalidated {} cache entries", count);
Source

pub fn invalidate_cascade_entities( &self, cascade_response: &Value, parser: &CascadeResponseParser, ) -> Result<u64>

Invalidate cache entries based on GraphQL Cascade response entities.

This is the entity-aware invalidation method that provides more precise invalidation. Instead of invalidating all caches reading from a view, only caches that depend on the affected entities are invalidated.

§Arguments
  • cascade_response - GraphQL mutation response with cascade field
  • parser - CascadeResponseParser to extract entities
§Returns

Number of cache entries invalidated

§Example
let cascade_response = json!({
    "createPost": {
        "cascade": {
            "updated": [
                { "__typename": "User", "id": "uuid-1" }
            ]
        }
    }
});

let parser = CascadeResponseParser::new();
let count = adapter.invalidate_cascade_entities(&cascade_response, &parser)?;
println!("Invalidated {} cache entries", count);
§Note on Performance

This method replaces view-level invalidation with entity-level invalidation. Instead of clearing all caches that touch a view (e.g., v_user), only caches that touch the specific entities are cleared (e.g., User:uuid-1).

Expected improvement:

  • View-level: 60-70% hit rate (many false positives)
  • Entity-level: 90-95% hit rate (only true positives)
Source

pub const fn inner(&self) -> &A

Get reference to underlying adapter.

Useful for accessing adapter-specific methods not in the DatabaseAdapter trait.

§Example
// Access PostgreSQL-specific functionality
let pg_adapter = adapter.inner();
Source

pub fn cache(&self) -> &QueryResultCache

Get reference to cache.

Useful for metrics and monitoring.

§Example
let metrics = adapter.cache().metrics()?;
println!("Cache hit rate: {:.1}%", metrics.hit_rate() * 100.0);
Source

pub fn schema_version(&self) -> &str

Get schema version.

§Example
println!("Schema version: {}", adapter.schema_version());
Source

pub fn fact_table_config(&self) -> &FactTableCacheConfig

Get fact table cache configuration.

Source

pub fn version_provider(&self) -> &FactTableVersionProvider

Get the version provider for fact tables.

Source

pub async fn execute_aggregation_query( &self, sql: &str, ) -> Result<Vec<HashMap<String, Value>>>

Execute aggregation query with caching based on fact table versioning strategy.

This method provides transparent caching for aggregation queries on fact tables. The caching behavior depends on the configured strategy for the fact table.

§Arguments
  • sql - The aggregation SQL query
§Returns

Query results (from cache or database)

§Example
// This query will be cached according to tf_sales strategy
let results = adapter.execute_aggregation_query(
    "SELECT SUM(revenue) FROM tf_sales WHERE year = 2024"
).await?;

Trait Implementations§

Source§

impl<A: DatabaseAdapter> DatabaseAdapter for CachedDatabaseAdapter<A>

Source§

fn execute_with_projection<'life0, 'life1, 'life2, 'life3, 'async_trait>( &'life0 self, view: &'life1 str, projection: Option<&'life2 SqlProjectionHint>, where_clause: Option<&'life3 WhereClause>, limit: Option<u32>, ) -> Pin<Box<dyn Future<Output = Result<Vec<JsonbValue>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait,

Execute a WHERE query with SQL field projection optimization. Read more
Source§

fn execute_where_query<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, view: &'life1 str, where_clause: Option<&'life2 WhereClause>, limit: Option<u32>, offset: Option<u32>, ) -> Pin<Box<dyn Future<Output = Result<Vec<JsonbValue>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Execute a WHERE query against a view and return JSONB rows. Read more
Source§

fn database_type(&self) -> DatabaseType

Get database type (for logging/metrics). Read more
Source§

fn health_check<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Health check - verify database connectivity. Read more
Source§

fn pool_metrics(&self) -> PoolMetrics

Get connection pool metrics. Read more
Source§

fn execute_raw_query<'life0, 'life1, 'async_trait>( &'life0 self, sql: &'life1 str, ) -> Pin<Box<dyn Future<Output = Result<Vec<HashMap<String, Value>>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Execute raw SQL query and return rows as JSON objects. Read more
Source§

fn execute_function_call<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, function_name: &'life1 str, args: &'life2 [Value], ) -> Pin<Box<dyn Future<Output = Result<Vec<HashMap<String, Value>>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait,

Execute a PostgreSQL function call and return all columns as rows. Read more
Source§

fn capabilities(&self) -> DatabaseCapabilities

Get database capabilities. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more