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}