Skip to main content

teaql_data_service/
lib.rs

1#![allow(async_fn_in_trait)]
2
3use std::time::SystemTime;
4use teaql_core::{
5    DeleteCommand, InsertCommand, Record, RecoverCommand, SelectQuery, TraceNode, UpdateCommand,
6};
7
8#[derive(Debug, Clone, Default)]
9pub struct DataServiceCapabilities {
10    pub query: bool,
11    pub mutation: bool,
12    pub transaction: bool,
13    pub schema: bool,
14    pub id_generation: bool,
15    pub batch_mutation: bool,
16    pub returning: bool,
17}
18
19#[derive(Debug, Clone)]
20pub struct QueryRequest {
21    pub query: SelectQuery,
22    pub trace_chain: Vec<TraceNode>,
23    pub comment: Option<String>,
24}
25
26#[derive(Debug, Clone)]
27pub struct QueryResult {
28    pub rows: Vec<Record>,
29    pub metadata: ExecutionMetadata,
30}
31
32#[derive(Debug, Clone)]
33pub enum MutationRequest {
34    Insert(InsertCommand),
35    Update(UpdateCommand),
36    Delete(DeleteCommand),
37    Recover(RecoverCommand),
38    Batch(Vec<MutationRequest>),
39}
40
41impl MutationRequest {
42    pub fn trace_chain(&self) -> &[teaql_core::TraceNode] {
43        match self {
44            MutationRequest::Insert(cmd) => &cmd.trace_chain,
45            MutationRequest::Update(cmd) => &cmd.trace_chain,
46            MutationRequest::Delete(cmd) => &cmd.trace_chain,
47            MutationRequest::Recover(cmd) => &cmd.trace_chain,
48            MutationRequest::Batch(_) => &[], // Batch traces are per-item
49        }
50    }
51
52    pub fn comment(&self) -> Option<&str> {
53        match self {
54            MutationRequest::Insert(cmd) => cmd.trace_chain.last().map(|n| n.comment.as_str()),
55            MutationRequest::Update(cmd) => cmd.trace_chain.last().map(|n| n.comment.as_str()),
56            MutationRequest::Delete(cmd) => cmd.trace_chain.last().map(|n| n.comment.as_str()),
57            MutationRequest::Recover(cmd) => cmd.trace_chain.last().map(|n| n.comment.as_str()),
58            MutationRequest::Batch(_) => None,
59        }
60    }
61}
62
63#[derive(Debug, Clone)]
64pub struct MutationResult {
65    pub affected_rows: u64,
66    pub generated_values: Record,
67    pub metadata: ExecutionMetadata,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum DataServiceOperation {
72    Query,
73    Insert,
74    Update,
75    Delete,
76    Recover,
77    Batch,
78    Schema,
79}
80
81#[derive(Debug, Clone)]
82pub struct ExecutionMetadata {
83    pub backend: String,
84    pub operation: DataServiceOperation,
85    pub started_at: SystemTime,
86    pub ended_at: SystemTime,
87    pub affected_rows: Option<u64>,
88    pub result_count: Option<usize>,
89    pub trace_chain: Vec<TraceNode>,
90    pub comment: Option<String>,
91    pub backend_request_id: Option<String>,
92    pub debug_query: Option<String>,
93}
94
95pub trait DataServiceExecutor {
96    type Error: std::error::Error + Send + Sync + 'static;
97
98    fn capabilities(&self) -> DataServiceCapabilities;
99}
100
101pub trait QueryExecutor: DataServiceExecutor {
102    fn query(
103        &self,
104        request: QueryRequest,
105    ) -> impl std::future::Future<Output = Result<QueryResult, Self::Error>> + Send;
106}
107
108/// Result of a single streaming chunk.
109#[derive(Debug, Clone)]
110pub struct StreamChunk {
111    pub rows: Vec<Record>,
112    pub chunk_index: usize,
113    pub is_last: bool,
114}
115
116pub type QueryStream<'a, E> =
117    std::pin::Pin<Box<dyn futures_core::Stream<Item = Result<StreamChunk, E>> + 'a>>;
118
119/// Streaming query executor. Returns rows in chunks rather than all at once.
120pub trait StreamQueryExecutor: DataServiceExecutor {
121    fn query_stream(
122        &self,
123        request: QueryRequest,
124        chunk_size: usize,
125    ) -> QueryStream<'_, Self::Error>;
126}
127
128pub trait MutationExecutor: DataServiceExecutor {
129    fn mutate(
130        &self,
131        request: MutationRequest,
132    ) -> impl std::future::Future<Output = Result<MutationResult, Self::Error>> + Send;
133}
134
135pub trait TransactionExecutor: DataServiceExecutor {
136    type Tx<'a>: QueryExecutor<Error = Self::Error>
137        + MutationExecutor<Error = Self::Error>
138        + Transaction<Error = Self::Error>
139    where
140        Self: 'a;
141
142    fn begin(&self) -> impl std::future::Future<Output = Result<Self::Tx<'_>, Self::Error>> + Send;
143}
144
145pub trait Transaction {
146    type Error: std::error::Error + Send + Sync + 'static;
147
148    fn commit(self) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send;
149    fn rollback(self) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send;
150}
151
152#[derive(Debug, Clone)]
153pub struct SchemaRequest {
154    pub entity_name: String,
155}
156
157#[derive(Debug, Clone)]
158pub struct SchemaResult {
159    pub changed: bool,
160}
161
162pub trait SchemaExecutor: DataServiceExecutor {
163    fn ensure_schema(
164        &self,
165        request: SchemaRequest,
166    ) -> impl std::future::Future<Output = Result<SchemaResult, Self::Error>> + Send;
167}
168
169pub trait IdGeneratorExecutor: DataServiceExecutor {
170    fn next_id(
171        &self,
172        entity: &str,
173    ) -> impl std::future::Future<Output = Result<u64, Self::Error>> + Send;
174}
175
176pub trait SchemaProvider: Send + Sync {
177    fn get_entity(&self, name: &str) -> Option<std::sync::Arc<teaql_core::EntityDescriptor>>;
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn test_mutation_request_trace_and_comment_accessors() {
186        let trace1 = TraceNode {
187            entity_type: "User".to_string(),
188            entity_id: Some(1),
189            comment: "Create User".to_string(),
190        };
191        let trace2 = TraceNode {
192            entity_type: "Profile".to_string(),
193            entity_id: None,
194            comment: "Create Profile".to_string(),
195        };
196        let trace_chain = vec![trace1.clone(), trace2.clone()];
197
198        // Test Insert
199        let insert_cmd = InsertCommand {
200            entity: "User".to_string(),
201            values: Record::new(),
202            trace_chain: trace_chain.clone(),
203        };
204        let req_insert = MutationRequest::Insert(insert_cmd);
205        assert_eq!(req_insert.trace_chain().len(), 2);
206        assert_eq!(req_insert.trace_chain()[1], trace2);
207        assert_eq!(req_insert.comment(), Some("Create Profile"));
208
209        // Test Update
210        let update_cmd = UpdateCommand {
211            entity: "User".to_string(),
212            id: teaql_core::Value::I64(1),
213            values: Record::new(),
214            expected_version: None,
215            old_values: None,
216            trace_chain: trace_chain.clone(),
217        };
218        let req_update = MutationRequest::Update(update_cmd);
219        assert_eq!(req_update.trace_chain().len(), 2);
220        assert_eq!(req_update.comment(), Some("Create Profile"));
221
222        // Test Delete
223        let delete_cmd = DeleteCommand {
224            entity: "User".to_string(),
225            id: teaql_core::Value::I64(1),
226            expected_version: None,
227            soft_delete: true,
228            trace_chain: trace_chain.clone(),
229        };
230        let req_delete = MutationRequest::Delete(delete_cmd);
231        assert_eq!(req_delete.trace_chain().len(), 2);
232        assert_eq!(req_delete.comment(), Some("Create Profile"));
233
234        // Test Recover
235        let recover_cmd = RecoverCommand {
236            entity: "User".to_string(),
237            id: teaql_core::Value::I64(1),
238            expected_version: 1,
239            trace_chain: trace_chain.clone(),
240        };
241        let req_recover = MutationRequest::Recover(recover_cmd);
242        assert_eq!(req_recover.trace_chain().len(), 2);
243        assert_eq!(req_recover.comment(), Some("Create Profile"));
244
245        // Test Batch
246        let req_batch = MutationRequest::Batch(vec![req_insert, req_update]);
247        assert_eq!(req_batch.trace_chain().len(), 0);
248        assert_eq!(req_batch.comment(), None);
249
250        // Test empty trace chain
251        let insert_empty = InsertCommand {
252            entity: "User".to_string(),
253            values: Record::new(),
254            trace_chain: vec![],
255        };
256        let req_empty = MutationRequest::Insert(insert_empty);
257        assert_eq!(req_empty.trace_chain().len(), 0);
258        assert_eq!(req_empty.comment(), None);
259    }
260}