Skip to main content

winterbaume_redshiftdata/
backend.rs

1//! Pluggable query execution backend for the Redshift Data mock service.
2//!
3//! The [`RedshiftQueryBackend`] trait abstracts SQL execution so that
4//! alternative implementations (e.g. DuckDB-backed) can be swapped in without
5//! touching the protocol layer.
6//!
7//! The built-in [`InMemoryRedshiftQueryBackend`] is the default; it returns
8//! the same hardcoded three-row mock result (`Number`/`Street`/`City`) that
9//! the service used before the backend abstraction was introduced, preserving
10//! backward-compatible behavior for existing tests.
11//!
12//! # Object safety and async
13//!
14//! Uses the same `Pin<Box<dyn Future>>` pattern as `winterbaume-sqs`/`winterbaume-sns`
15//! so that `Arc<dyn RedshiftQueryBackend>` is object-safe without the
16//! `async-trait` crate.
17
18use std::future::Future;
19use std::pin::Pin;
20
21/// Result of executing a single SQL statement.
22#[derive(Debug, Clone, Default)]
23pub struct StatementResult {
24    /// Column metadata as `(name, type_str)` pairs.
25    /// `type_str` uses lowercase DuckDB type names: `"varchar"`, `"integer"`,
26    /// `"bigint"`, `"double"`, `"boolean"`, etc.
27    pub columns: Vec<(String, String)>,
28    /// Row data.  Each inner `Option<String>` is `None` for SQL NULL.
29    pub rows: Vec<Vec<Option<String>>>,
30    /// Non-`None` when execution failed.  Causes the statement status to be
31    /// set to `Failed`.
32    pub error: Option<String>,
33}
34
35/// Pluggable backend for Redshift Data query execution.
36pub trait RedshiftQueryBackend: Send + Sync {
37    /// Execute a single SQL statement and return the result asynchronously.
38    fn execute_statement(
39        &self,
40        sql: String,
41    ) -> Pin<Box<dyn Future<Output = StatementResult> + Send>>;
42
43    /// Execute a batch of SQL statements sequentially.
44    /// Batch executions typically do not return a result set.
45    fn batch_execute(
46        &self,
47        sqls: Vec<String>,
48    ) -> Pin<Box<dyn Future<Output = StatementResult> + Send>>;
49}
50
51/// Default in-memory backend: returns the hardcoded three-row mock result
52/// (`Number`/`Street`/`City`) for single statements, and an empty result for
53/// batch executions.  Statements are stored with status `Finished`.
54pub struct InMemoryRedshiftQueryBackend;
55
56impl RedshiftQueryBackend for InMemoryRedshiftQueryBackend {
57    fn execute_statement(
58        &self,
59        _sql: String,
60    ) -> Pin<Box<dyn Future<Output = StatementResult> + Send>> {
61        Box::pin(async move {
62            StatementResult {
63                columns: vec![
64                    ("Number".to_string(), "integer".to_string()),
65                    ("Street".to_string(), "varchar".to_string()),
66                    ("City".to_string(), "varchar".to_string()),
67                ],
68                rows: vec![
69                    vec![
70                        Some("10".to_string()),
71                        Some("Alpha st".to_string()),
72                        Some("Portland".to_string()),
73                    ],
74                    vec![
75                        Some("20".to_string()),
76                        Some("Beta st".to_string()),
77                        Some("Bellevue".to_string()),
78                    ],
79                    vec![
80                        Some("30".to_string()),
81                        Some("Gamma st".to_string()),
82                        Some("Seattle".to_string()),
83                    ],
84                ],
85                error: None,
86            }
87        })
88    }
89
90    fn batch_execute(
91        &self,
92        _sqls: Vec<String>,
93    ) -> Pin<Box<dyn Future<Output = StatementResult> + Send>> {
94        Box::pin(async move { StatementResult::default() })
95    }
96}