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