pub trait DatabaseAdapter:
Send
+ Sync
+ 'static {
Show 32 methods
// Required methods
fn execute_where_query<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
view: &'life1 str,
where_clause: Option<&'life2 WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
order_by: Option<&'life3 [OrderByClause]>,
) -> Pin<Box<dyn Future<Output = Result<Vec<JsonbValue>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Self: 'async_trait;
fn execute_with_projection<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
view: &'life1 str,
projection: Option<&'life2 SqlProjectionHint>,
where_clause: Option<&'life3 WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
order_by: Option<&'life4 [OrderByClause]>,
) -> Pin<Box<dyn Future<Output = Result<Vec<JsonbValue>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Self: 'async_trait;
fn database_type(&self) -> DatabaseType;
fn health_check<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<(), FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
Self: 'async_trait;
fn pool_metrics(&self) -> PoolMetrics;
fn execute_raw_query<'life0, 'life1, 'async_trait>(
&'life0 self,
sql: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Vec<HashMap<String, Value>>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait;
fn execute_parameterized_aggregate<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
sql: &'life1 str,
params: &'life2 [Value],
) -> Pin<Box<dyn Future<Output = Result<Vec<HashMap<String, Value>>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait;
// Provided methods
fn execute_where_query_arc<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
view: &'life1 str,
where_clause: Option<&'life2 WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
order_by: Option<&'life3 [OrderByClause]>,
) -> Pin<Box<dyn Future<Output = Result<Arc<Vec<JsonbValue>>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Self: 'async_trait { ... }
fn execute_with_projection_arc<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
request: &'life1 ProjectionRequest<'life2>,
) -> Pin<Box<dyn Future<Output = Result<Arc<Vec<JsonbValue>>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait { ... }
fn serves_reads_from_standbys(&self) -> bool { ... }
fn execute_row_query<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
view_name: &'life1 str,
columns: &'life2 [ColumnSpec],
where_sql: Option<&'life3 str>,
order_by: Option<&'life4 str>,
limit: Option<u32>,
offset: Option<u32>,
) -> Pin<Box<dyn Future<Output = Result<Vec<Vec<ColumnValue>>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Self: 'async_trait { ... }
fn execute_row_query_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'life6, 'life7, 'async_trait>(
&'life0 self,
view_name: &'life1 str,
columns: &'life2 [ColumnSpec],
where_sql: Option<&'life3 str>,
order_by: Option<&'life4 str>,
limit: Option<u32>,
offset: Option<u32>,
_session_vars: &'life5 [(&'life6 str, &'life7 str)],
) -> Pin<Box<dyn Future<Output = Result<Vec<Vec<ColumnValue>>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
'life6: 'async_trait,
'life7: 'async_trait,
Self: 'async_trait { ... }
fn execute_parameterized_aggregate_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'async_trait>(
&'life0 self,
sql: &'life1 str,
params: &'life2 [Value],
_session_vars: &'life3 [(&'life4 str, &'life5 str)],
_routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<Vec<HashMap<String, Value>>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
Self: 'async_trait { ... }
fn supports_composed_reads(&self) -> bool { ... }
fn invalidate_views<'life0, 'life1, 'async_trait>(
&'life0 self,
_views: &'life1 [ViewName],
) -> Pin<Box<dyn Future<Output = Result<u64, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait { ... }
fn invalidate_by_entity<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_entity_type: &'life1 str,
_entity_id: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<u64, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait { ... }
fn result_cache_stats(&self) -> Option<ResultCacheStats> { ... }
fn clear_result_cache<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Option<usize>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
Self: 'async_trait { ... }
fn explain_query<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_sql: &'life1 str,
_params: &'life2 [Value],
) -> Pin<Box<dyn Future<Output = Result<Value, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait { ... }
fn explain_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<Value, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait { ... }
fn execute_where_query_arc_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'life6, 'async_trait>(
&'life0 self,
view: &'life1 str,
where_clause: Option<&'life2 WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
order_by: Option<&'life3 [OrderByClause]>,
_session_vars: &'life4 [(&'life5 str, &'life6 str)],
_routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<Arc<Vec<JsonbValue>>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
'life6: 'async_trait,
Self: 'async_trait { ... }
fn count_where_query<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'async_trait>(
&'life0 self,
view: &'life1 str,
where_clause: Option<&'life2 WhereClause>,
session_vars: &'life3 [(&'life4 str, &'life5 str)],
routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<u64, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
Self: 'async_trait { ... }
fn execute_with_projection_arc_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'async_trait>(
&'life0 self,
request: &'life1 ProjectionRequest<'life2>,
_session_vars: &'life3 [(&'life4 str, &'life5 str)],
_routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<Arc<Vec<JsonbValue>>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
Self: 'async_trait { ... }
fn stream_with_projection<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'async_trait>(
&'life0 self,
request: &'life1 ProjectionRequest<'life2>,
session_vars: &'life3 [(&'life4 str, &'life5 str)],
routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<Pin<Box<dyn Stream<Item = Result<JsonbValue, FraiseQLError>> + Send>>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
Self: 'async_trait { ... }
fn execute_composed_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
read: &'life1 ComposedLevel,
_session_vars: &'life2 [(&'life3 str, &'life4 str)],
_routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<Arc<Vec<JsonbValue>>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Self: 'async_trait { ... }
fn stream_row_query<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
view_name: &'life1 str,
columns: &'life2 [ColumnSpec],
where_sql: Option<&'life3 str>,
order_by: Option<&'life4 str>,
limit: Option<u32>,
offset: Option<u32>,
) -> Pin<Box<dyn Future<Output = Result<Pin<Box<dyn Stream<Item = Result<Vec<ColumnValue>, FraiseQLError>> + Send>>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Self: 'async_trait { ... }
fn stream_row_query_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'life6, 'life7, 'async_trait>(
&'life0 self,
view_name: &'life1 str,
columns: &'life2 [ColumnSpec],
where_sql: Option<&'life3 str>,
order_by: Option<&'life4 str>,
limit: Option<u32>,
offset: Option<u32>,
_session_vars: &'life5 [(&'life6 str, &'life7 str)],
) -> Pin<Box<dyn Future<Output = Result<Pin<Box<dyn Stream<Item = Result<Vec<ColumnValue>, FraiseQLError>> + Send>>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
'life6: 'async_trait,
'life7: 'async_trait,
Self: 'async_trait { ... }
fn query_stats<'life0, 'async_trait>(
&'life0 self,
_limit: u32,
) -> Pin<Box<dyn Future<Output = Result<Vec<QueryStatEntry>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
Self: 'async_trait { ... }
fn query_stats_by_id<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Option<QueryStatEntry>, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait { ... }
fn reset_query_stats<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<(), FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
Self: 'async_trait { ... }
fn on_schema_reload(&self) { ... }
fn execute_admin_sql<'life0, 'life1, 'async_trait>(
&'life0 self,
_request: &'life1 AdminSqlRequest,
) -> Pin<Box<dyn Future<Output = Result<AdminSqlOutcome, FraiseQLError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait { ... }
}Expand description
Database adapter for executing queries against views.
This trait abstracts over different database backends (PostgreSQL, MySQL, SQLite, SQL Server). All implementations must support:
- Executing parameterized WHERE queries against views
- Returning JSONB data from the
datacolumn - Connection pooling and health checks
- Row-level security (RLS) WHERE clauses
§Architecture
The adapter is the runtime interface to the database. It receives:
- View/table name (e.g., “v_user”, “tf_sales”)
- Parameterized WHERE clauses (AST form, not strings)
- Projection hints (for performance optimization)
- Pagination parameters (LIMIT/OFFSET)
And returns:
- JSONB rows from the
datacolumn (most operations) - Arbitrary rows as HashMap (for aggregation queries)
- Mutation results from stored procedures
§Implementing a New Adapter
To add support for a new database (e.g., Oracle, Snowflake):
-
Create a new module in
src/db/your_database/ -
Implement the trait:
ⓘpub struct YourDatabaseAdapter { /* fields */ } #[async_trait] impl DatabaseAdapter for YourDatabaseAdapter { async fn execute_where_query(&self, ...) -> Result<Vec<JsonbValue>> { // 1. Build parameterized SQL from WhereClause AST // 2. Execute with bound parameters (NO string concatenation) // 3. Return JSONB from data column } // Implement other required methods... } -
Add feature flag to
Cargo.toml(e.g.,feature = "your-database") -
Copy structure from PostgreSQL adapter — see
src/db/postgres/adapter.rs -
Add tests in
tests/integration/your_database_test.rs
§Security Requirements
All implementations MUST:
- Never concatenate user input into SQL strings
- Always use parameterized queries with bind parameters
- Validate parameter types before binding
- Preserve RLS WHERE clauses (never filter them out)
- Return errors, not silently fail (e.g., connection loss)
§Connection Management
- Use a connection pool (recommended: 20 connections default)
- Implement
health_check()for ping-based monitoring - Provide
pool_metrics()for observability - Handle stale connections gracefully
§Performance Characteristics
Expected throughput when properly implemented:
- Simple queries (single table, no WHERE): 250+ Kelem/s
- Complex queries (JOINs, multiple conditions): 50+ Kelem/s
- Mutations (stored procedures): 1-10 RPS (depends on procedure)
- Relay pagination (keyset cursors): 15-30ms latency
§Example: PostgreSQL Implementation
use sqlx::postgres::PgPool;
use async_trait::async_trait;
pub struct PostgresAdapter {
pool: PgPool,
}
#[async_trait]
impl DatabaseAdapter for PostgresAdapter {
async fn execute_where_query(
&self,
view: &str,
where_clause: Option<&WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
) -> Result<Vec<JsonbValue>> {
// 1. Build SQL: SELECT data FROM {view} WHERE {where_clause} LIMIT {limit}
let mut sql = format!(r#"SELECT data FROM "{}""#, view);
// 2. Add WHERE clause (converts AST to parameterized SQL)
let params = if let Some(where_clause) = where_clause {
sql.push_str(" WHERE ");
let (where_sql, params) = build_where_sql(where_clause)?;
sql.push_str(&where_sql);
params
} else {
vec![]
};
// 3. Add LIMIT and OFFSET
if let Some(limit) = limit {
sql.push_str(" LIMIT ");
sql.push_str(&limit.to_string());
}
if let Some(offset) = offset {
sql.push_str(" OFFSET ");
sql.push_str(&offset.to_string());
}
// 4. Execute with bound parameters (NO string interpolation)
let rows: Vec<(serde_json::Value,)> = sqlx::query_as(&sql)
.bind(¶ms[0])
.bind(¶ms[1])
// ... bind all parameters
.fetch_all(&self.pool)
.await?;
// 5. Extract JSONB and return
Ok(rows.into_iter().map(|(data,)| data).collect())
}
// Implement other required methods...
}§Example: Basic Usage
use fraiseql_db::{DatabaseAdapter, WhereClause, WhereOperator};
use serde_json::json;
// Build WHERE clause (AST, not string)
let where_clause = WhereClause::Field {
path: vec!["email".to_string()],
operator: WhereOperator::Icontains,
value: json!("example.com"),
};
// Execute query with parameters
let results = adapter
.execute_where_query("v_user", Some(&where_clause), Some(10), None, None)
.await?;
println!("Found {} users matching filter", results.len());§See Also
WhereClause— AST for parameterized WHERE clausesRelayDatabaseAdapter— Optional trait for keyset pagination- Performance Guide
Required Methods§
Sourcefn execute_where_query<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
view: &'life1 str,
where_clause: Option<&'life2 WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
order_by: Option<&'life3 [OrderByClause]>,
) -> Pin<Box<dyn Future<Output = Result<Vec<JsonbValue>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Self: 'async_trait,
fn execute_where_query<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
view: &'life1 str,
where_clause: Option<&'life2 WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
order_by: Option<&'life3 [OrderByClause]>,
) -> Pin<Box<dyn Future<Output = Result<Vec<JsonbValue>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Self: 'async_trait,
Execute a WHERE query against a view and return JSONB rows.
§Arguments
view- View name (e.g., “v_user”, “v_post”)where_clause- Optional WHERE clause ASTlimit- Optional row limit (for pagination)offset- Optional row offset (for pagination)security_context- Optional security context for RLS and caching decisions
§Returns
Vec of JSONB values from the data column.
§Errors
Returns FraiseQLError::Database on query execution failure.
Returns FraiseQLError::ConnectionPool if connection pool is exhausted.
§Example
// Simple query without WHERE clause
let all_users = adapter
.execute_where_query("v_user", None, Some(10), Some(0), None)
.await?;Sourcefn execute_with_projection<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
view: &'life1 str,
projection: Option<&'life2 SqlProjectionHint>,
where_clause: Option<&'life3 WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
order_by: Option<&'life4 [OrderByClause]>,
) -> Pin<Box<dyn Future<Output = Result<Vec<JsonbValue>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Self: 'async_trait,
fn execute_with_projection<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
view: &'life1 str,
projection: Option<&'life2 SqlProjectionHint>,
where_clause: Option<&'life3 WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
order_by: Option<&'life4 [OrderByClause]>,
) -> Pin<Box<dyn Future<Output = Result<Vec<JsonbValue>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Self: 'async_trait,
Execute a WHERE query with SQL field projection optimization.
Projects only the requested fields at the database level, reducing network payload and JSON deserialization overhead by 40-55% based on production measurements.
This is the primary query execution method for optimized GraphQL queries. It automatically selects only the fields requested in the GraphQL query, avoiding unnecessary network transfer and deserialization of unused fields.
§Automatic Projection
In most cases, you don’t call this directly. The Executor automatically:
- Determines which fields the GraphQL query requests
- Generates a
SqlProjectionHintusing database-specific SQL - Calls this method with the projection hint
§Arguments
view- View name (e.g., “v_user”, “v_post”)projection- Optional SQL projection hint with field listSome(hint): Use projection to select only requested fieldsNone: Falls back to standard query (full JSONB column)
where_clause- Optional WHERE clause AST for filteringlimit- Optional row limit (for pagination)
§Returns
Vec of JSONB values, either:
- Full objects (when projection is None)
- Projected objects with only requested fields (when projection is Some)
§Errors
Returns FraiseQLError::Database on query execution failure, including:
- Connection pool exhaustion
- SQL execution errors
- Type mismatches
§Performance Characteristics
When projection is provided (recommended):
- Latency: 40-55% reduction vs full object fetch
- Network: 40-55% smaller payload (proportional to unused fields)
- Throughput: Maintains 250+ Kelem/s (elements per second)
- Memory: Proportional to projected fields only
Improvement scales with:
- Percentage of unused fields (more unused = more improvement)
- Size of result set (larger sets benefit more)
- Network latency (network-bound queries benefit most)
When projection is None:
- Behavior identical to
execute_where_query() - Returns full JSONB column
- Used for compatibility/debugging
§Database Support
| Database | Status | Implementation |
|---|---|---|
| PostgreSQL | ✅ Optimized | jsonb_build_object() |
| MySQL | ⏳ Fallback | Server-side filtering (planned) |
| SQLite | ⏳ Fallback | Server-side filtering (planned) |
| SQL Server | ⏳ Fallback | Server-side filtering (planned) |
§Example: Direct Usage (Advanced)
// Requires: running PostgreSQL database and a DatabaseAdapter implementation.
use fraiseql_db::types::SqlProjectionHint;
use fraiseql_db::traits::DatabaseAdapter;
use fraiseql_db::DatabaseType;
let projection = SqlProjectionHint::new(
DatabaseType::PostgreSQL,
"jsonb_build_object(\
'id', data->>'id', \
'name', data->>'name', \
'email', data->>'email'\
)".to_string(),
75,
);
let results = adapter
.execute_with_projection("v_user", Some(&projection), None, Some(100), None, None)
.await?;
// results only contain id, name, email fields
// 75% smaller than fetching all fields§Example: Fallback (No Projection)
// Requires: running PostgreSQL database and a DatabaseAdapter implementation.
// For debugging or when projection not available
let results = adapter
.execute_with_projection("v_user", None, None, Some(100), None, None)
.await?;
// Equivalent to execute_where_query() - returns full objects§See Also
execute_where_query()- Standard query without projectionSqlProjectionHint- Structure defining field projection- Projection Optimization Guide
Sourcefn database_type(&self) -> DatabaseType
fn database_type(&self) -> DatabaseType
Get database type (for logging/metrics).
Used to identify which database backend is in use.
Sourcefn health_check<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<(), FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
Self: 'async_trait,
fn health_check<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<(), FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
Self: 'async_trait,
Health check - verify database connectivity.
Executes a simple query (e.g., SELECT 1) to verify the database is reachable.
§Errors
Returns FraiseQLError::Database if health check fails.
Sourcefn pool_metrics(&self) -> PoolMetrics
fn pool_metrics(&self) -> PoolMetrics
Get connection pool metrics.
Returns current statistics about the connection pool:
- Total connections
- Idle connections
- Active connections
- Waiting requests
Sourcefn execute_raw_query<'life0, 'life1, 'async_trait>(
&'life0 self,
sql: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Vec<HashMap<String, Value>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
fn execute_raw_query<'life0, 'life1, 'async_trait>(
&'life0 self,
sql: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Vec<HashMap<String, Value>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
Execute raw SQL query and return rows as JSON objects.
Used for aggregation queries where we need full row data, not just JSONB column.
§Security Warning
This method executes arbitrary SQL. NEVER pass untrusted input directly to this method. Always:
- Use parameterized queries with bound parameters
- Validate and sanitize SQL templates before execution
- Only execute SQL generated by the FraiseQL compiler
- Log SQL execution for audit trails
§Arguments
sql- Raw SQL query to execute (must be safe/trusted)
§Returns
Vec of rows, where each row is a HashMap of column name to JSON value.
§Errors
Returns FraiseQLError::Database on query execution failure.
§Example
// Safe: SQL generated by FraiseQL compiler
let sql = "SELECT category, SUM(revenue) as total FROM tf_sales GROUP BY category";
let rows = adapter.execute_raw_query(sql).await?;
for row in rows {
println!("Category: {}, Total: {}", row["category"], row["total"]);
}Sourcefn execute_parameterized_aggregate<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
sql: &'life1 str,
params: &'life2 [Value],
) -> Pin<Box<dyn Future<Output = Result<Vec<HashMap<String, Value>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn execute_parameterized_aggregate<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
sql: &'life1 str,
params: &'life2 [Value],
) -> Pin<Box<dyn Future<Output = Result<Vec<HashMap<String, Value>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
Execute a parameterized aggregate SQL query (GROUP BY / HAVING / window).
sql contains $N (PostgreSQL), ? (MySQL / SQLite), or @P1 (SQL Server)
placeholders for string and array values; numeric and NULL values may be inlined.
params are the corresponding values in placeholder order.
Unlike execute_raw_query, this method accepts bind parameters so that
user-supplied filter values never appear as string literals in the SQL text,
eliminating the injection risk that escape_sql_string mitigated previously.
§Arguments
sql- SQL with placeholders generated byAggregationSqlGenerator::generate_parameterizedparams- Bind parameters in placeholder order
§Returns
Vec of rows, where each row is a HashMap of column name to JSON value.
§Errors
Returns FraiseQLError::Database on execution failure.
Returns FraiseQLError::Database on adapters that do not support raw SQL
(e.g., FraiseWireAdapter).
Provided Methods§
Sourcefn execute_where_query_arc<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
view: &'life1 str,
where_clause: Option<&'life2 WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
order_by: Option<&'life3 [OrderByClause]>,
) -> Pin<Box<dyn Future<Output = Result<Arc<Vec<JsonbValue>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Self: 'async_trait,
fn execute_where_query_arc<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
view: &'life1 str,
where_clause: Option<&'life2 WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
order_by: Option<&'life3 [OrderByClause]>,
) -> Pin<Box<dyn Future<Output = Result<Arc<Vec<JsonbValue>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Self: 'async_trait,
Like execute_where_query but returns the result wrapped in an Arc.
The default implementation wraps the result of execute_where_query in a
fresh Arc. CachedDatabaseAdapter overrides this to return the cached Arc
directly — eliminating the full Vec<JsonbValue> clone that the non-Arc
path requires on every cache hit.
Callers on the hot query path should prefer this variant and borrow from the
Arc via &**arc rather than taking ownership.
§Errors
Same errors as execute_where_query.
Sourcefn execute_with_projection_arc<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
request: &'life1 ProjectionRequest<'life2>,
) -> Pin<Box<dyn Future<Output = Result<Arc<Vec<JsonbValue>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn execute_with_projection_arc<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
request: &'life1 ProjectionRequest<'life2>,
) -> Pin<Box<dyn Future<Output = Result<Arc<Vec<JsonbValue>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
Like execute_with_projection but returns the result wrapped in an Arc.
The default implementation wraps the result of execute_with_projection in a
fresh Arc. CachedDatabaseAdapter overrides this to return the cached Arc
directly — eliminating the full Vec<JsonbValue> clone that the non-Arc
path requires on every cache hit.
Parameters are passed in a ProjectionRequest struct (F043) so adapters
and callers cannot misorder them.
§Errors
Same errors as execute_with_projection.
Sourcefn serves_reads_from_standbys(&self) -> bool
fn serves_reads_from_standbys(&self) -> bool
Whether reads may be served by a hot standby — read replicas are configured (#1390).
A standby cannot read an UNLOGGED or temporary relation, so a server whose reads go to replicas checks at boot that none of its sources depends on one. A wrapper adapter must forward this to the adapter it wraps: inheriting the default would switch that check off.
Sourcefn execute_row_query<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
view_name: &'life1 str,
columns: &'life2 [ColumnSpec],
where_sql: Option<&'life3 str>,
order_by: Option<&'life4 str>,
limit: Option<u32>,
offset: Option<u32>,
) -> Pin<Box<dyn Future<Output = Result<Vec<Vec<ColumnValue>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Self: 'async_trait,
fn execute_row_query<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
view_name: &'life1 str,
columns: &'life2 [ColumnSpec],
where_sql: Option<&'life3 str>,
order_by: Option<&'life4 str>,
limit: Option<u32>,
offset: Option<u32>,
) -> Pin<Box<dyn Future<Output = Result<Vec<Vec<ColumnValue>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Self: 'async_trait,
Execute a row-shaped query against a view, returning typed column values.
Used by the gRPC transport for protobuf encoding of query results.
The default implementation delegates to execute_raw_query and converts
JSON results to ColumnValue vectors.
§Errors
Returns FraiseQLError::Database if the adapter returns an error.
Sourcefn execute_row_query_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'life6, 'life7, 'async_trait>(
&'life0 self,
view_name: &'life1 str,
columns: &'life2 [ColumnSpec],
where_sql: Option<&'life3 str>,
order_by: Option<&'life4 str>,
limit: Option<u32>,
offset: Option<u32>,
_session_vars: &'life5 [(&'life6 str, &'life7 str)],
) -> Pin<Box<dyn Future<Output = Result<Vec<Vec<ColumnValue>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
'life6: 'async_trait,
'life7: 'async_trait,
Self: 'async_trait,
fn execute_row_query_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'life6, 'life7, 'async_trait>(
&'life0 self,
view_name: &'life1 str,
columns: &'life2 [ColumnSpec],
where_sql: Option<&'life3 str>,
order_by: Option<&'life4 str>,
limit: Option<u32>,
offset: Option<u32>,
_session_vars: &'life5 [(&'life6 str, &'life7 str)],
) -> Pin<Box<dyn Future<Output = Result<Vec<Vec<ColumnValue>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
'life6: 'async_trait,
'life7: 'async_trait,
Self: 'async_trait,
Connection-affine variant of execute_row_query.
See
execute_where_query_arc_with_session
for the rationale: an RLS policy backed by current_setting() (#329) only sees
the variables if they are applied transaction-locally on the same connection
as the read.
§Why the row shape needs its own pair
The row-shaped read is the gRPC transport’s source. Until #1351 it resolved its
own predicate and never reached the engine, so it had no session variables to
pin and none of these methods existed. Routing it through the direct-read
chokepoint gives it session_variables like every other read — and a resolved
value the read cannot apply is the failure mode the chokepoint exists to
prevent, so it travels rather than being dropped.
§Errors
Same errors as execute_row_query; additionally
returns FraiseQLError::Database if set_config fails on any pair.
Sourcefn execute_parameterized_aggregate_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'async_trait>(
&'life0 self,
sql: &'life1 str,
params: &'life2 [Value],
_session_vars: &'life3 [(&'life4 str, &'life5 str)],
_routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<Vec<HashMap<String, Value>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
Self: 'async_trait,
fn execute_parameterized_aggregate_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'async_trait>(
&'life0 self,
sql: &'life1 str,
params: &'life2 [Value],
_session_vars: &'life3 [(&'life4 str, &'life5 str)],
_routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<Vec<HashMap<String, Value>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
Self: 'async_trait,
Connection-affine variant of
execute_parameterized_aggregate.
Applies session_vars transaction-locally on the same connection that
runs the aggregate, so aggregate views backed by current_setting() RLS
observe the configured values (fixes #329 for the aggregate path). The default
ignores session_vars: a backend without transaction-local settings has none to
apply.
§Errors
Same errors as execute_parameterized_aggregate;
additionally returns FraiseQLError::Database if set_config fails.
Sourcefn supports_composed_reads(&self) -> bool
fn supports_composed_reads(&self) -> bool
Whether this adapter implements
execute_composed_with_session — whether a
read can have related resources composed into it (the REST ?select= embed).
Asked twice, of one answer: when the REST transport is mounted, so a deployment
whose adapter cannot serve embeds is told at boot rather than by its first embed;
and by the engine before a composed read is sent, so the 501 a request gets is
that same fact rather than a second one.
Implementing execute_composed_with_session obliges you to override this too,
and a wrapping adapter must forward both. Defaults to false: a capability is not
something a backend should acquire by omission.
Sourcefn invalidate_views<'life0, 'life1, 'async_trait>(
&'life0 self,
_views: &'life1 [ViewName],
) -> Pin<Box<dyn Future<Output = Result<u64, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
fn invalidate_views<'life0, 'life1, 'async_trait>(
&'life0 self,
_views: &'life1 [ViewName],
) -> Pin<Box<dyn Future<Output = Result<u64, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
Invalidate cached query results for the specified views.
Called by the executor after a mutation succeeds, so that stale cache
entries reading from modified views are evicted. The default
implementation is a no-op; CachedDatabaseAdapter overrides this.
View names are passed as &[ViewName] so the wrapper’s Arc<str>
backing is preserved across the call. Callers that hold a String
can convert in place with ViewName::from(...).
§Returns
The number of cache entries evicted.
Sourcefn invalidate_by_entity<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_entity_type: &'life1 str,
_entity_id: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<u64, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn invalidate_by_entity<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_entity_type: &'life1 str,
_entity_id: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<u64, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
Evict cache entries that contain the given entity UUID.
Called by the executor after a successful UPDATE or DELETE mutation when
the mutation_response includes an entity_id. Only cache entries whose
entity-ID index contains the given UUID are removed; unrelated entries
remain warm.
The default implementation is a no-op. CachedDatabaseAdapter overrides
this to perform the selective eviction.
§Returns
The number of cache entries evicted.
Sourcefn result_cache_stats(&self) -> Option<ResultCacheStats>
fn result_cache_stats(&self) -> Option<ResultCacheStats>
A snapshot of this adapter’s query-result cache, or None if it has none.
The operator surface for the cache that actually serves GraphQL queries.
None means “this adapter does not cache” — distinct from Some(stats) with
zero entries, which means “the cache is there and currently empty”. The admin
API reported the second as the first for every non-Arrow deployment (#941).
The default implementation returns None; CachedDatabaseAdapter overrides it.
Sourcefn clear_result_cache<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Option<usize>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
Self: 'async_trait,
fn clear_result_cache<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<Option<usize>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
Self: 'async_trait,
Drop every entry in this adapter’s query-result cache.
Returns the number of entries dropped, or None if this adapter has no cache
— again so the caller can say “there is no such cache” rather than reporting a
successful clear of nothing.
The default implementation returns Ok(None); CachedDatabaseAdapter overrides it.
§Errors
Returns FraiseQLError if the underlying cache cannot be cleared.
Sourcefn explain_query<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_sql: &'life1 str,
_params: &'life2 [Value],
) -> Pin<Box<dyn Future<Output = Result<Value, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn explain_query<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
_sql: &'life1 str,
_params: &'life2 [Value],
) -> Pin<Box<dyn Future<Output = Result<Value, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
Run the database’s EXPLAIN on a SQL statement without executing it.
Returns a JSON representation of the query plan. The format is database-specific (e.g. PostgreSQL returns JSON, SQLite returns rows).
The default implementation returns Unsupported.
Sourcefn explain_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<Value, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn explain_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<Value, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
Run EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) against a view with the
same parameterized WHERE clause that execute_where_query would use.
Unlike explain_query, this method uses real bound parameters and
actually executes the query (ANALYZE mode), so the plan reflects
PostgreSQL’s runtime statistics for the given filter values.
Only PostgreSQL supports this; other adapters return
FraiseQLError::Unsupported by default.
§Arguments
view- View name (e.g., “v_user”)where_clause- Optional filter (same asexecute_where_query)limit- Optional row limitoffset- Optional row offset
§Errors
Returns FraiseQLError::Database on execution failure.
Returns FraiseQLError::Unsupported for non-PostgreSQL adapters.
Sourcefn execute_where_query_arc_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'life6, 'async_trait>(
&'life0 self,
view: &'life1 str,
where_clause: Option<&'life2 WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
order_by: Option<&'life3 [OrderByClause]>,
_session_vars: &'life4 [(&'life5 str, &'life6 str)],
_routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<Arc<Vec<JsonbValue>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
'life6: 'async_trait,
Self: 'async_trait,
fn execute_where_query_arc_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'life6, 'async_trait>(
&'life0 self,
view: &'life1 str,
where_clause: Option<&'life2 WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
order_by: Option<&'life3 [OrderByClause]>,
_session_vars: &'life4 [(&'life5 str, &'life6 str)],
_routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<Arc<Vec<JsonbValue>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
'life6: 'async_trait,
Self: 'async_trait,
Connection-affine variant of execute_where_query_arc.
Applies session_vars transaction-locally on the same connection that
runs the read, so PostgreSQL Row-Level-Security policies backed by
current_setting() see the configured values (fixes #329). The default ignores
session_vars: a backend without transaction-local settings has none to apply.
§Errors
Same errors as execute_where_query_arc; additionally
returns FraiseQLError::Database if set_config fails on any pair.
Sourcefn count_where_query<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'async_trait>(
&'life0 self,
view: &'life1 str,
where_clause: Option<&'life2 WhereClause>,
session_vars: &'life3 [(&'life4 str, &'life5 str)],
routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<u64, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
Self: 'async_trait,
fn count_where_query<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'async_trait>(
&'life0 self,
view: &'life1 str,
where_clause: Option<&'life2 WhereClause>,
session_vars: &'life3 [(&'life4 str, &'life5 str)],
routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<u64, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
Self: 'async_trait,
Count the rows of view matching where_clause, without materialising them.
Backs both the GraphQL <name>Count sibling (#938) and the REST
Prefer: count=exact header, so the two cannot answer the same question
differently.
session_vars are applied transaction-locally on the connection that
runs the count, exactly as for the read it describes (#329). This is
load-bearing rather than symmetric-for-neatness: under RLS, a count that
ran without the session variables would report the unfiltered total
beside a filtered page — a row-count oracle over rows the caller cannot
read.
§Default implementation
The default fetches the matching rows and counts them. That is correct
— same view, same predicate, same session — but it is O(rows) in memory,
so an adapter that can push the count into the database should override
it. PostgresAdapter does, with SELECT COUNT(*).
§Errors
Returns FraiseQLError::Database if the query fails.
Sourcefn execute_with_projection_arc_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'async_trait>(
&'life0 self,
request: &'life1 ProjectionRequest<'life2>,
_session_vars: &'life3 [(&'life4 str, &'life5 str)],
_routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<Arc<Vec<JsonbValue>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
Self: 'async_trait,
fn execute_with_projection_arc_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'async_trait>(
&'life0 self,
request: &'life1 ProjectionRequest<'life2>,
_session_vars: &'life3 [(&'life4 str, &'life5 str)],
_routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<Arc<Vec<JsonbValue>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
Self: 'async_trait,
Connection-affine variant of
execute_with_projection_arc.
See execute_where_query_arc_with_session for
the rationale.
§Errors
Same errors as execute_with_projection_arc;
additionally returns FraiseQLError::Database if set_config fails on any pair.
Sourcefn stream_with_projection<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'async_trait>(
&'life0 self,
request: &'life1 ProjectionRequest<'life2>,
session_vars: &'life3 [(&'life4 str, &'life5 str)],
routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<Pin<Box<dyn Stream<Item = Result<JsonbValue, FraiseQLError>> + Send>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
Self: 'async_trait,
fn stream_with_projection<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'async_trait>(
&'life0 self,
request: &'life1 ProjectionRequest<'life2>,
session_vars: &'life3 [(&'life4 str, &'life5 str)],
routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<Pin<Box<dyn Stream<Item = Result<JsonbValue, FraiseQLError>> + Send>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
Self: 'async_trait,
The same read as
execute_with_projection_arc_with_session,
delivered row by row instead of as a materialised Vec (#958).
Same view, same predicate, same session variables, same routing. The only
difference is when the rows arrive and how much memory the caller needs
to hold them: a streamed read is O(1) in the number of rows, so an export
of a table larger than memory is possible where the collecting method would
have to fail.
§Why this exists as its own method
A paginated re-execution loop (LIMIT n OFFSET k, k += n) delivers the
same rows in bounded memory and needs no new trait method, and that is what
the export surfaces did. It has two properties a stream does not:
- it is
O(offset)per batch, so exportingNrows costsO(N²)row scans; - each batch is its own snapshot, so a concurrent insert or delete between batches shifts rows across the page boundary — silently duplicating a row into two batches, or skipping one entirely.
One statement over one portal has neither problem.
§Default implementation
Collects the read and replays it. That is correct — same rows, same order —
but it holds the whole result set, so an adapter that can stream should
override it. PostgresAdapter does.
⚠ A wrapping adapter (caching, instrumentation) that inherits this default converts every streaming caller back into a buffering one without failing: the rows are right and the memory bound is gone. A wrapper must forward this method explicitly.
§Errors
Returns the same errors as
execute_with_projection_arc_with_session.
Note that an implementation that truly streams can only report the errors it
has seen so far when this returns: a failure raised after the first row —
a lost connection, a runtime cast error on a later row — surfaces as an Err
item inside the stream, not from this call.
Sourcefn execute_composed_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
read: &'life1 ComposedLevel,
_session_vars: &'life2 [(&'life3 str, &'life4 str)],
_routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<Arc<Vec<JsonbValue>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Self: 'async_trait,
fn execute_composed_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
read: &'life1 ComposedLevel,
_session_vars: &'life2 [(&'life3 str, &'life4 str)],
_routing: ReadRouting,
) -> Pin<Box<dyn Future<Output = Result<Arc<Vec<JsonbValue>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Self: 'async_trait,
Execute a read that composes its embedded levels into one statement.
The REST ?select= embed used to be a parent read plus one sub-read per parent
row per level; this is the same answer in one round trip, one snapshot and one
pooled connection. See ComposedLevel for the call shape and for the
{"d": …, "e": …} rows it returns.
Session variables and routing mean what they mean for
execute_with_projection_arc_with_session.
§Default implementation
Refuses. Composing needs correlated LATERAL subqueries and JSON aggregation,
which an adapter has to render for its own dialect; answering with anything
else — the root rows without their embeds, say — would be a partial response
indistinguishable from a parent with no related rows. A wrapping adapter must
forward this method explicitly.
§Errors
FraiseQLError::Unsupported from the default; otherwise the errors of
execute_with_projection_arc_with_session.
Sourcefn stream_row_query<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
view_name: &'life1 str,
columns: &'life2 [ColumnSpec],
where_sql: Option<&'life3 str>,
order_by: Option<&'life4 str>,
limit: Option<u32>,
offset: Option<u32>,
) -> Pin<Box<dyn Future<Output = Result<Pin<Box<dyn Stream<Item = Result<Vec<ColumnValue>, FraiseQLError>> + Send>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Self: 'async_trait,
fn stream_row_query<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
view_name: &'life1 str,
columns: &'life2 [ColumnSpec],
where_sql: Option<&'life3 str>,
order_by: Option<&'life4 str>,
limit: Option<u32>,
offset: Option<u32>,
) -> Pin<Box<dyn Future<Output = Result<Pin<Box<dyn Stream<Item = Result<Vec<ColumnValue>, FraiseQLError>> + Send>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
Self: 'async_trait,
The same read as execute_row_query, delivered
row by row (#958).
The column-shaped counterpart of
stream_with_projection; see that method for
why streaming and paginated re-execution are not interchangeable. This is the
gRPC transport’s source, which encodes one protobuf message per row and has
no use for a Vec of them.
§Default implementation
Collects and replays, with the same caveat about wrapping adapters.
§Errors
Returns the same errors as execute_row_query;
see stream_with_projection for which of
them can still arrive after this call has returned Ok.
Sourcefn stream_row_query_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'life6, 'life7, 'async_trait>(
&'life0 self,
view_name: &'life1 str,
columns: &'life2 [ColumnSpec],
where_sql: Option<&'life3 str>,
order_by: Option<&'life4 str>,
limit: Option<u32>,
offset: Option<u32>,
_session_vars: &'life5 [(&'life6 str, &'life7 str)],
) -> Pin<Box<dyn Future<Output = Result<Pin<Box<dyn Stream<Item = Result<Vec<ColumnValue>, FraiseQLError>> + Send>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
'life6: 'async_trait,
'life7: 'async_trait,
Self: 'async_trait,
fn stream_row_query_with_session<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'life6, 'life7, 'async_trait>(
&'life0 self,
view_name: &'life1 str,
columns: &'life2 [ColumnSpec],
where_sql: Option<&'life3 str>,
order_by: Option<&'life4 str>,
limit: Option<u32>,
offset: Option<u32>,
_session_vars: &'life5 [(&'life6 str, &'life7 str)],
) -> Pin<Box<dyn Future<Output = Result<Pin<Box<dyn Stream<Item = Result<Vec<ColumnValue>, FraiseQLError>> + Send>>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
'life5: 'async_trait,
'life6: 'async_trait,
'life7: 'async_trait,
Self: 'async_trait,
Connection-affine variant of stream_row_query.
The streaming twin of
execute_row_query_with_session; see
that method for why the row shape carries session variables at all. The
streaming arm is where dropping them costs the most, because it is the arm
that delivers an unbounded number of rows.
§Errors
Same errors as stream_row_query; additionally
returns FraiseQLError::Database if set_config fails on any pair.
Sourcefn query_stats<'life0, 'async_trait>(
&'life0 self,
_limit: u32,
) -> Pin<Box<dyn Future<Output = Result<Vec<QueryStatEntry>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
Self: 'async_trait,
fn query_stats<'life0, 'async_trait>(
&'life0 self,
_limit: u32,
) -> Pin<Box<dyn Future<Output = Result<Vec<QueryStatEntry>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
Self: 'async_trait,
Retrieve query performance statistics from the database.
Returns the top-N queries ordered by total execution time (descending). The exact data source depends on the backend:
- PostgreSQL:
pg_stat_statements(requires extension) - MySQL:
performance_schema.events_statements_summary_by_digest - SQL Server:
sys.dm_exec_query_stats - SQLite / Wire: empty (no stats available)
§Arguments
limit- Maximum number of entries to return.
§Errors
Returns FraiseQLError::Database if the stats query fails.
Sourcefn query_stats_by_id<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Option<QueryStatEntry>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
fn query_stats_by_id<'life0, 'life1, 'async_trait>(
&'life0 self,
id: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<Option<QueryStatEntry>, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
Retrieve statistics for a single query by its ID.
The default implementation fetches up to 1000 entries via
query_stats and filters client-side.
Backends with efficient single-query lookup (PostgreSQL, SQL Server)
should override with a WHERE clause.
§Errors
Returns FraiseQLError::Database if the underlying query fails.
Sourcefn reset_query_stats<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<(), FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
Self: 'async_trait,
fn reset_query_stats<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = Result<(), FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
Self: 'async_trait,
Reset query performance statistics.
Only PostgreSQL supports this (via pg_stat_statements_reset()).
All other adapters return Unsupported.
§Errors
Returns FraiseQLError::Unsupported for adapters that cannot reset stats.
Returns FraiseQLError::Database if the reset command fails.
Sourcefn on_schema_reload(&self)
fn on_schema_reload(&self)
Notify the adapter that the schema has changed.
Called during hot-reload after the new schema has been validated. Adapters that maintain schema-dependent state (e.g. cache keyed by schema version) should clear or rebuild that state here.
The default implementation is a no-op.
Sourcefn execute_admin_sql<'life0, 'life1, 'async_trait>(
&'life0 self,
_request: &'life1 AdminSqlRequest,
) -> Pin<Box<dyn Future<Output = Result<AdminSqlOutcome, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
fn execute_admin_sql<'life0, 'life1, 'async_trait>(
&'life0 self,
_request: &'life1 AdminSqlRequest,
) -> Pin<Box<dyn Future<Output = Result<AdminSqlOutcome, FraiseQLError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
Run one operator-supplied statement under the bounds in request (#962).
This is the only method on this trait whose SQL FraiseQL did not generate.
It exists for the Studio SQL console, and it is deliberately the narrowest
possible shape for that: a single statement, in a transaction whose mode,
timeout, row budget and session identity are all decided by the caller and
enforced by the database. See AdminSqlRequest for what each bound buys.
§Default implementation
Refuses. An adapter that has not implemented containment does not get
to run arbitrary SQL by inheriting a default that “just works” — the
convenient default here would be execute_raw_query, which has no
transaction, no timeout, no row cap and no way to roll back. Every
adapter that should serve this endpoint says so by overriding, and the
endpoint answers “unsupported” for the rest.
A wrapping adapter must forward explicitly for the same reason it forwards
stream_with_projection: inheriting the
default here turns a capable adapter into an incapable one, and the only
symptom is a refusal nobody asked for.
§Errors
Returns FraiseQLError::Unsupported by default. Implementations return
FraiseQLError::Database for anything PostgreSQL rejects — which
includes the write refused by a READ ONLY transaction (SQLSTATE
25006) and the statement cancelled by the timeout (57014).
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".