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