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