#![allow(clippy::unwrap_used)]
use std::collections::HashMap;
use async_trait::async_trait;
use indexmap::IndexMap;
use crate::{
backend::{
CursorValue, MutationRowGate, RelayPageResult, WriteRequest, Writer,
traits::{DatabaseAdapter, RelayDatabaseAdapter},
types::{DatabaseType, JsonbValue, PoolMetrics, sql_hints::OrderByClause},
where_clause::WhereClause,
},
error::Result,
schema::{AutoParams, CompiledSchema, CursorType, QueryDefinition},
};
pub struct CapturingMockAdapter {
pub mock_results: Vec<JsonbValue>,
pub mock_aggregate_rows: Vec<std::collections::HashMap<String, serde_json::Value>>,
pub captured_where: std::sync::Mutex<Option<WhereClause>>,
pub captured_limit: std::sync::Mutex<Option<u32>>,
pub captured_offset: std::sync::Mutex<Option<u32>>,
pub captured_order_by: std::sync::Mutex<Option<Vec<OrderByClause>>>,
pub captured_aggregate_sql: std::sync::Mutex<Option<String>>,
pub captured_aggregate_params: std::sync::Mutex<Option<Vec<serde_json::Value>>>,
pub captured_aggregate_session_vars: std::sync::Mutex<Option<Vec<(String, String)>>>,
pub mock_row_results: Vec<Vec<crate::backend::types::ColumnValue>>,
pub captured_row_read: std::sync::Mutex<Option<CapturedRowRead>>,
pub captured_composed: std::sync::Mutex<Option<crate::backend::ComposedLevel>>,
pub composes_reads: bool,
}
#[derive(Debug, Clone)]
pub struct CapturedRowRead {
pub view: String,
pub columns: Vec<String>,
pub where_sql: Option<String>,
pub order_by: Option<String>,
pub limit: Option<u32>,
pub offset: Option<u32>,
pub session_vars: Vec<(String, String)>,
}
impl CapturingMockAdapter {
pub fn new(mock_results: Vec<JsonbValue>) -> Self {
Self {
mock_results,
mock_aggregate_rows: Vec::new(),
captured_where: std::sync::Mutex::new(None),
captured_limit: std::sync::Mutex::new(None),
captured_offset: std::sync::Mutex::new(None),
captured_order_by: std::sync::Mutex::new(None),
captured_aggregate_sql: std::sync::Mutex::new(None),
captured_aggregate_params: std::sync::Mutex::new(None),
captured_aggregate_session_vars: std::sync::Mutex::new(None),
mock_row_results: Vec::new(),
captured_row_read: std::sync::Mutex::new(None),
captured_composed: std::sync::Mutex::new(None),
composes_reads: true,
}
}
#[must_use]
pub fn without_composed_reads(mut self) -> Self {
self.composes_reads = false;
self
}
#[cfg_attr(not(feature = "federation"), allow(dead_code))]
#[must_use]
pub fn with_aggregate_rows(
mut self,
rows: Vec<std::collections::HashMap<String, serde_json::Value>>,
) -> Self {
self.mock_aggregate_rows = rows;
self
}
pub fn captured_where(&self) -> Option<WhereClause> {
self.captured_where.lock().unwrap().clone()
}
pub fn captured_limit(&self) -> Option<u32> {
*self.captured_limit.lock().unwrap()
}
pub fn captured_offset(&self) -> Option<u32> {
*self.captured_offset.lock().unwrap()
}
pub fn captured_order_by(&self) -> Option<Vec<OrderByClause>> {
self.captured_order_by.lock().unwrap().clone()
}
pub fn captured_aggregate_sql(&self) -> Option<String> {
self.captured_aggregate_sql.lock().unwrap().clone()
}
#[allow(dead_code)] pub fn captured_aggregate_params(&self) -> Option<Vec<serde_json::Value>> {
self.captured_aggregate_params.lock().unwrap().clone()
}
pub fn captured_aggregate_session_vars(&self) -> Option<Vec<(String, String)>> {
self.captured_aggregate_session_vars.lock().unwrap().clone()
}
#[must_use]
pub fn with_row_results(mut self, rows: Vec<Vec<crate::backend::types::ColumnValue>>) -> Self {
self.mock_row_results = rows;
self
}
pub fn captured_row_read(&self) -> Option<CapturedRowRead> {
self.captured_row_read.lock().unwrap().clone()
}
pub fn captured_composed(&self) -> Option<crate::backend::ComposedLevel> {
self.captured_composed.lock().unwrap().clone()
}
}
#[async_trait]
impl DatabaseAdapter for CapturingMockAdapter {
async fn execute_with_projection(
&self,
view: &str,
_projection: Option<&crate::schema::SqlProjectionHint>,
where_clause: Option<&WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
order_by: Option<&[OrderByClause]>,
) -> Result<Vec<JsonbValue>> {
self.execute_where_query(view, where_clause, limit, offset, order_by).await
}
async fn execute_where_query(
&self,
_view: &str,
where_clause: Option<&WhereClause>,
limit: Option<u32>,
offset: Option<u32>,
order_by: Option<&[OrderByClause]>,
) -> Result<Vec<JsonbValue>> {
*self.captured_where.lock().unwrap() = where_clause.cloned();
*self.captured_limit.lock().unwrap() = limit;
*self.captured_offset.lock().unwrap() = offset;
*self.captured_order_by.lock().unwrap() = order_by.map(<[OrderByClause]>::to_vec);
Ok(self.mock_results.clone())
}
fn supports_composed_reads(&self) -> bool {
self.composes_reads
}
async fn execute_composed_with_session(
&self,
read: &crate::backend::ComposedLevel,
_session_vars: &[(&str, &str)],
_routing: crate::backend::types::ReadRouting,
) -> Result<std::sync::Arc<Vec<JsonbValue>>> {
*self.captured_composed.lock().unwrap() = Some(read.clone());
Ok(std::sync::Arc::new(self.mock_results.clone()))
}
async fn health_check(&self) -> Result<()> {
Ok(())
}
fn database_type(&self) -> DatabaseType {
DatabaseType::PostgreSQL
}
fn pool_metrics(&self) -> PoolMetrics {
PoolMetrics {
total_connections: 1,
active_connections: 0,
idle_connections: 1,
waiting_requests: 0,
}
}
async fn execute_raw_query(
&self,
_sql: &str,
) -> Result<Vec<std::collections::HashMap<String, serde_json::Value>>> {
Ok(vec![])
}
async fn execute_row_query(
&self,
view_name: &str,
columns: &[crate::backend::types::ColumnSpec],
where_sql: Option<&str>,
order_by: Option<&str>,
limit: Option<u32>,
offset: Option<u32>,
) -> Result<Vec<Vec<crate::backend::types::ColumnValue>>> {
*self.captured_row_read.lock().unwrap() = Some(CapturedRowRead {
view: view_name.to_string(),
columns: columns.iter().map(|c| c.name.clone()).collect(),
where_sql: where_sql.map(str::to_string),
order_by: order_by.map(str::to_string),
limit,
offset,
session_vars: Vec::new(),
});
Ok(self.mock_row_results.clone())
}
async fn execute_row_query_with_session(
&self,
view_name: &str,
columns: &[crate::backend::types::ColumnSpec],
where_sql: Option<&str>,
order_by: Option<&str>,
limit: Option<u32>,
offset: Option<u32>,
session_vars: &[(&str, &str)],
) -> Result<Vec<Vec<crate::backend::types::ColumnValue>>> {
*self.captured_row_read.lock().unwrap() = Some(CapturedRowRead {
view: view_name.to_string(),
columns: columns.iter().map(|c| c.name.clone()).collect(),
where_sql: where_sql.map(str::to_string),
order_by: order_by.map(str::to_string),
limit,
offset,
session_vars: session_vars
.iter()
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
.collect(),
});
Ok(self.mock_row_results.clone())
}
async fn execute_parameterized_aggregate(
&self,
sql: &str,
params: &[serde_json::Value],
) -> Result<Vec<std::collections::HashMap<String, serde_json::Value>>> {
*self.captured_aggregate_sql.lock().unwrap() = Some(sql.to_string());
*self.captured_aggregate_params.lock().unwrap() = Some(params.to_vec());
Ok(self.mock_aggregate_rows.clone())
}
async fn execute_parameterized_aggregate_with_session(
&self,
sql: &str,
params: &[serde_json::Value],
session_vars: &[(&str, &str)],
_routing: crate::backend::types::ReadRouting,
) -> Result<Vec<std::collections::HashMap<String, serde_json::Value>>> {
*self.captured_aggregate_session_vars.lock().unwrap() =
Some(session_vars.iter().map(|(k, v)| ((*k).to_string(), (*v).to_string())).collect());
self.execute_parameterized_aggregate(sql, params).await
}
}
#[async_trait]
impl Writer for CapturingMockAdapter {
async fn execute_write(
&self,
_request: &WriteRequest<'_>,
gate: MutationRowGate<'_>,
) -> Result<Vec<std::collections::HashMap<String, serde_json::Value>>> {
let rows = Vec::new();
gate(&rows)?;
Ok(rows)
}
}
pub struct MockAdapter {
pub mock_results: Vec<JsonbValue>,
pub view_responses: std::collections::HashMap<String, Vec<JsonbValue>>,
pub captured_raw_sql: std::sync::Mutex<Vec<String>>,
}
impl MockAdapter {
pub fn new(mock_results: Vec<JsonbValue>) -> Self {
Self {
mock_results,
view_responses: std::collections::HashMap::new(),
captured_raw_sql: std::sync::Mutex::new(Vec::new()),
}
}
pub fn raw_sql(&self) -> Vec<String> {
self.captured_raw_sql.lock().unwrap().clone()
}
pub fn with_view(mut self, view: &str, results: Vec<JsonbValue>) -> Self {
self.view_responses.insert(view.to_string(), results);
self
}
}
#[async_trait]
impl DatabaseAdapter for MockAdapter {
async fn execute_with_projection(
&self,
view: &str,
_projection: Option<&crate::schema::SqlProjectionHint>,
where_clause: Option<&WhereClause>,
limit: Option<u32>,
_offset: Option<u32>,
_order_by: Option<&[OrderByClause]>,
) -> Result<Vec<JsonbValue>> {
self.execute_where_query(view, where_clause, limit, None, None).await
}
async fn execute_where_query(
&self,
view: &str,
_where_clause: Option<&WhereClause>,
_limit: Option<u32>,
_offset: Option<u32>,
_order_by: Option<&[OrderByClause]>,
) -> Result<Vec<JsonbValue>> {
if let Some(results) = self.view_responses.get(view) {
return Ok(results.clone());
}
Ok(self.mock_results.clone())
}
async fn health_check(&self) -> Result<()> {
Ok(())
}
fn database_type(&self) -> DatabaseType {
DatabaseType::PostgreSQL
}
fn pool_metrics(&self) -> PoolMetrics {
PoolMetrics {
total_connections: 1,
active_connections: 0,
idle_connections: 1,
waiting_requests: 0,
}
}
async fn execute_raw_query(
&self,
sql: &str,
) -> Result<Vec<std::collections::HashMap<String, serde_json::Value>>> {
self.captured_raw_sql.lock().unwrap().push(sql.to_string());
Ok(vec![])
}
async fn execute_parameterized_aggregate(
&self,
_sql: &str,
_params: &[serde_json::Value],
) -> Result<Vec<std::collections::HashMap<String, serde_json::Value>>> {
Ok(vec![])
}
}
#[async_trait]
impl Writer for MockAdapter {
async fn execute_write(
&self,
_request: &WriteRequest<'_>,
gate: MutationRowGate<'_>,
) -> Result<Vec<std::collections::HashMap<String, serde_json::Value>>> {
let rows = Vec::new();
gate(&rows)?;
Ok(rows)
}
}
impl RelayDatabaseAdapter for MockAdapter {
async fn execute_relay_page<'a>(
&'a self,
_view: &'a str,
_cursor_column: &'a str,
_after: Option<CursorValue>,
_before: Option<CursorValue>,
_limit: u32,
_forward: bool,
_where_clause: Option<&'a WhereClause>,
_order_by: Option<&'a [OrderByClause]>,
_include_total_count: bool,
) -> Result<RelayPageResult> {
Ok(RelayPageResult::new(Vec::new(), None))
}
}
pub struct ReadOnlyMockAdapter;
#[async_trait]
impl DatabaseAdapter for ReadOnlyMockAdapter {
async fn execute_with_projection(
&self,
view: &str,
_projection: Option<&crate::schema::SqlProjectionHint>,
where_clause: Option<&WhereClause>,
limit: Option<u32>,
_offset: Option<u32>,
_order_by: Option<&[OrderByClause]>,
) -> Result<Vec<JsonbValue>> {
self.execute_where_query(view, where_clause, limit, None, None).await
}
async fn execute_where_query(
&self,
_view: &str,
_where_clause: Option<&WhereClause>,
_limit: Option<u32>,
_offset: Option<u32>,
_order_by: Option<&[OrderByClause]>,
) -> Result<Vec<JsonbValue>> {
Ok(vec![])
}
async fn health_check(&self) -> Result<()> {
Ok(())
}
fn database_type(&self) -> DatabaseType {
DatabaseType::PostgreSQL
}
fn pool_metrics(&self) -> PoolMetrics {
PoolMetrics {
total_connections: 1,
active_connections: 0,
idle_connections: 1,
waiting_requests: 0,
}
}
async fn execute_raw_query(
&self,
_sql: &str,
) -> Result<Vec<std::collections::HashMap<String, serde_json::Value>>> {
Ok(vec![])
}
async fn execute_parameterized_aggregate(
&self,
_sql: &str,
_params: &[serde_json::Value],
) -> Result<Vec<std::collections::HashMap<String, serde_json::Value>>> {
Ok(vec![])
}
}
pub fn test_schema() -> CompiledSchema {
let mut schema = CompiledSchema::new();
schema.queries.push(QueryDefinition {
function: None,
requires_actor: Vec::new(),
returns_count: false,
name: "users".to_string(),
return_type: "User".to_string(),
returns_list: true,
nullable: false,
arguments: Vec::new(),
sql_source: Some("v_user".to_string()),
description: None,
auto_params: AutoParams::all(),
deprecation: None,
jsonb_column: "data".to_string(),
relay: false,
relay_cursor_column: None,
relay_cursor_type: CursorType::default(),
inject_params: IndexMap::default(),
read_routing: crate::backend::types::ReadRouting::default(),
cache_ttl_seconds: None,
additional_views: vec![],
requires_role: None,
rest_path: None,
rest_method: None,
rest_stream: false,
native_columns: HashMap::new(),
pagination_order: Some(crate::schema::PaginationOrder::JsonIdentity),
});
schema
}
pub fn mock_user_results() -> Vec<JsonbValue> {
vec![
JsonbValue::new(serde_json::json!({"id": "1", "name": "Alice"})),
JsonbValue::new(serde_json::json!({"id": "2", "name": "Bob"})),
]
}