1#![allow(clippy::manual_async_fn)]
2#![allow(async_fn_in_trait)]
3
4use std::collections::HashMap;
5use std::sync::{Arc, RwLock};
6use std::time::SystemTime;
7use teaql_core::{
8 CompactRow, EntityDescriptor, EntitySnapshot, Expr, GeneratedValues, SelectQuery, Value,
9};
10use teaql_data_service::{
11 DataServiceCapabilities, DataServiceExecutor, DataServiceOperation, ExecutionMetadata,
12 MutationExecutor, MutationRequest, MutationResult, QueryExecutor, QueryRequest, QueryResult,
13};
14
15use crate::{CompiledQuery, SqlCompileError, SqlDialect};
16
17pub trait SqlTransport: Send + Sync {
18 type Error: std::error::Error + Send + Sync + 'static;
19
20 fn fetch_all_compact_sql(
21 &self,
22 query: &CompiledQuery,
23 ) -> impl std::future::Future<Output = Result<Vec<CompactRow>, Self::Error>> + Send;
24 fn fetch_repeated_compact_sql(
25 &self,
26 template: &CompiledQuery,
27 param_index: usize,
28 values: &[Value],
29 ) -> impl std::future::Future<Output = Result<Vec<CompactRow>, Self::Error>> + Send {
30 async move {
31 let mut rows = Vec::new();
32 for value in values {
33 let mut query = template.clone();
34 query.params[param_index] = value.clone();
35 rows.extend(self.fetch_all_compact_sql(&query).await?);
36 }
37 Ok(rows)
38 }
39 }
40 fn execute_sql(
41 &self,
42 query: &CompiledQuery,
43 ) -> impl std::future::Future<Output = Result<u64, Self::Error>> + Send;
44}
45
46pub trait StreamingSqlTransport: SqlTransport {
47 fn stream_sql(
48 &self,
49 query: CompiledQuery,
50 chunk_size: usize,
51 ) -> teaql_data_service::QueryStream<'_, Self::Error>;
52}
53
54pub trait SqlTransactionTransport: SqlTransport {
55 type Tx<'a>: SqlTransport<Error = Self::Error>
56 + SqlTransaction<Error = Self::Error>
57 + Send
58 + Sync
59 + 'a
60 where
61 Self: 'a;
62
63 fn begin_sql(
64 &self,
65 ) -> impl std::future::Future<Output = Result<Self::Tx<'_>, Self::Error>> + Send;
66}
67
68pub trait SqlTransaction {
69 type Error: std::error::Error + Send + Sync + 'static;
70 fn commit_sql(self) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send;
71 fn rollback_sql(self) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send;
72}
73
74#[derive(Debug)]
75pub enum SqlExecutorError<E: std::error::Error + Send + Sync + 'static> {
76 Compile(SqlCompileError),
77 Transport(E),
78 PersistedRecord(String),
79}
80
81impl<E: std::error::Error + Send + Sync + 'static> std::fmt::Display for SqlExecutorError<E> {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 match self {
84 SqlExecutorError::Compile(e) => write!(f, "SQL compile error: {}", e),
85 SqlExecutorError::Transport(e) => write!(f, "Transport error: {}", e),
86 SqlExecutorError::PersistedRecord(e) => write!(f, "Persisted record error: {}", e),
87 }
88 }
89}
90
91impl<E: std::error::Error + Send + Sync + 'static> std::error::Error for SqlExecutorError<E> {}
92
93#[derive(Clone)]
94pub struct SqlDataServiceExecutor<D, T, S> {
95 pub dialect: D,
96 pub transport: T,
97 pub schema_provider: S,
98 descriptor_cache: Arc<RwLock<HashMap<String, Arc<teaql_core::EntityDescriptor>>>>,
99 select_plan_cache: Arc<RwLock<Vec<(SelectQuery, String)>>>,
100}
101
102impl<D, T, S> SqlDataServiceExecutor<D, T, S> {
103 pub fn new(dialect: D, transport: T, schema_provider: S) -> Self {
104 Self {
105 dialect,
106 transport,
107 schema_provider,
108 descriptor_cache: Arc::new(RwLock::new(HashMap::new())),
109 select_plan_cache: Arc::new(RwLock::new(Vec::new())),
110 }
111 }
112}
113
114impl<D, T, S> SqlDataServiceExecutor<D, T, S>
115where
116 D: SqlDialect,
117 S: teaql_data_service::SchemaProvider,
118{
119 fn compile_select_cached(
120 &self,
121 entity: &EntityDescriptor,
122 query: &SelectQuery,
123 ) -> Result<CompiledQuery, SqlCompileError> {
124 compile_select_with_cache(&self.dialect, &self.select_plan_cache, entity, query)
125 }
126}
127
128fn compile_select_with_cache<D: SqlDialect>(
129 dialect: &D,
130 plan_cache: &RwLock<Vec<(SelectQuery, String)>>,
131 entity: &EntityDescriptor,
132 query: &SelectQuery,
133) -> Result<CompiledQuery, SqlCompileError> {
134 if let Ok(cache) = plan_cache.read()
135 && let Some((_, sql)) = cache
136 .iter()
137 .find(|(candidate, _)| select_plan_matches(candidate, query))
138 {
139 return Ok(CompiledQuery {
140 sql: sql.clone(),
141 params: collect_select_params(entity, query, dialect.large_in_uses_array_param()),
142 comment: query.comment.clone(),
143 });
144 }
145
146 let key = select_plan_key(query);
147 let compiled = dialect.compile_select(entity, query)?;
148 if let Ok(mut cache) = plan_cache.write() {
149 if cache.len() >= 256 {
150 cache.remove(0);
151 }
152 if !cache
153 .iter()
154 .any(|(candidate, _)| select_plan_matches(candidate, query))
155 {
156 cache.push((key, compiled.sql.clone()));
157 }
158 }
159 Ok(compiled)
160}
161
162fn select_plan_matches(key: &SelectQuery, query: &SelectQuery) -> bool {
163 key.hard_limit == query.hard_limit
164 && key.entity == query.entity
165 && key.projection == query.projection
166 && key.expr_projection.len() == query.expr_projection.len()
167 && key
168 .expr_projection
169 .iter()
170 .zip(&query.expr_projection)
171 .all(|(left, right)| {
172 left.alias == right.alias && expr_plan_matches(&left.expr, &right.expr)
173 })
174 && key.search_with_text.is_some() == query.search_with_text.is_some()
175 && optional_expr_plan_matches(key.filter.as_ref(), query.filter.as_ref())
176 && optional_expr_plan_matches(key.having.as_ref(), query.having.as_ref())
177 && key.order_by.len() == query.order_by.len()
178 && key
179 .order_by
180 .iter()
181 .zip(&query.order_by)
182 .all(|(left, right)| {
183 left.field == right.field
184 && left.direction == right.direction
185 && optional_expr_plan_matches(left.expr.as_ref(), right.expr.as_ref())
186 })
187 && key.slice == query.slice
188 && key.partition_by == query.partition_by
189 && key.aggregates == query.aggregates
190 && key.group_by == query.group_by
191 && key.relations == query.relations
192 && key.aggregation_cache == query.aggregation_cache
193 && key.raw_sql == query.raw_sql
194 && key.raw_sql_search_criteria == query.raw_sql_search_criteria
195 && key.dynamic_properties == query.dynamic_properties
196 && key.raw_projections == query.raw_projections
197 && key.object_group_bys == query.object_group_bys
198 && key.child_enhancements == query.child_enhancements
199 && key.stream_config == query.stream_config
200 && key.continuous_page_fetch == query.continuous_page_fetch
201}
202
203fn optional_expr_plan_matches(key: Option<&Expr>, query: Option<&Expr>) -> bool {
204 match (key, query) {
205 (Some(key), Some(query)) => expr_plan_matches(key, query),
206 (None, None) => true,
207 _ => false,
208 }
209}
210
211fn expr_plan_matches(key: &Expr, query: &Expr) -> bool {
212 match (key, query) {
213 (Expr::Column(left), Expr::Column(right)) => left == right,
214 (Expr::Value(Value::List(left)), Expr::Value(Value::List(right))) => {
215 left.len() == right.len()
216 }
217 (Expr::Value(Value::List(_)), Expr::Value(_))
218 | (Expr::Value(_), Expr::Value(Value::List(_))) => false,
219 (Expr::Value(_), Expr::Value(_)) => true,
220 (
221 Expr::Function {
222 function: left_function,
223 args: left_args,
224 },
225 Expr::Function {
226 function: right_function,
227 args: right_args,
228 },
229 ) => left_function == right_function && expr_slice_plan_matches(left_args, right_args),
230 (
231 Expr::Binary {
232 left: left_left,
233 op: left_op,
234 right: left_right,
235 },
236 Expr::Binary {
237 left: right_left,
238 op: right_op,
239 right: right_right,
240 },
241 ) => {
242 left_op == right_op
243 && expr_plan_matches(left_left, right_left)
244 && expr_plan_matches(left_right, right_right)
245 }
246 (
247 Expr::SubQuery {
248 left: left_expr,
249 op: left_op,
250 entity: left_entity,
251 query: left_query,
252 },
253 Expr::SubQuery {
254 left: right_expr,
255 op: right_op,
256 entity: right_entity,
257 query: right_query,
258 },
259 ) => {
260 left_op == right_op
261 && left_entity == right_entity
262 && expr_plan_matches(left_expr, right_expr)
263 && select_plan_matches(left_query, right_query)
264 }
265 (
266 Expr::Between {
267 expr: left_expr,
268 lower: left_lower,
269 upper: left_upper,
270 },
271 Expr::Between {
272 expr: right_expr,
273 lower: right_lower,
274 upper: right_upper,
275 },
276 ) => {
277 expr_plan_matches(left_expr, right_expr)
278 && expr_plan_matches(left_lower, right_lower)
279 && expr_plan_matches(left_upper, right_upper)
280 }
281 (Expr::IsNull(left), Expr::IsNull(right))
282 | (Expr::IsNotNull(left), Expr::IsNotNull(right))
283 | (Expr::Not(left), Expr::Not(right)) => expr_plan_matches(left, right),
284 (Expr::And(left), Expr::And(right)) | (Expr::Or(left), Expr::Or(right)) => {
285 expr_slice_plan_matches(left, right)
286 }
287 _ => false,
288 }
289}
290
291fn expr_slice_plan_matches(left: &[Expr], right: &[Expr]) -> bool {
292 left.len() == right.len()
293 && left
294 .iter()
295 .zip(right)
296 .all(|(left, right)| expr_plan_matches(left, right))
297}
298
299fn select_plan_key(query: &SelectQuery) -> SelectQuery {
300 let mut key = query.clone();
301 key.comment = None;
302 key.trace_chain.clear();
303 if key.search_with_text.is_some() {
304 key.search_with_text = Some(String::new());
305 }
306 for projection in &mut key.expr_projection {
307 normalize_expr_values(&mut projection.expr);
308 }
309 if let Some(expr) = &mut key.filter {
310 normalize_expr_values(expr);
311 }
312 if let Some(expr) = &mut key.having {
313 normalize_expr_values(expr);
314 }
315 for order in &mut key.order_by {
316 if let Some(expr) = &mut order.expr {
317 normalize_expr_values(expr);
318 }
319 }
320 key
321}
322
323fn normalize_expr_values(expr: &mut Expr) {
324 match expr {
325 Expr::Value(Value::List(values)) => {
326 values.fill(Value::Null);
327 }
328 Expr::Value(value) => *value = Value::Null,
329 Expr::Function { args, .. } | Expr::And(args) | Expr::Or(args) => {
330 for arg in args {
331 normalize_expr_values(arg);
332 }
333 }
334 Expr::Binary { left, right, .. } => {
335 normalize_expr_values(left);
336 normalize_expr_values(right);
337 }
338 Expr::SubQuery { left, query, .. } => {
339 normalize_expr_values(left);
340 **query = select_plan_key(query);
341 }
342 Expr::Between { expr, lower, upper } => {
343 normalize_expr_values(expr);
344 normalize_expr_values(lower);
345 normalize_expr_values(upper);
346 }
347 Expr::IsNull(expr) | Expr::IsNotNull(expr) | Expr::Not(expr) => {
348 normalize_expr_values(expr);
349 }
350 Expr::Column(_) => {}
351 }
352}
353
354fn collect_select_params(
355 entity: &EntityDescriptor,
356 query: &SelectQuery,
357 large_in_uses_array_param: bool,
358) -> Vec<Value> {
359 let mut params = Vec::new();
360 if query.raw_sql.is_some() {
361 return params;
362 }
363 for projection in &query.expr_projection {
364 collect_expr_params(&projection.expr, &mut params, large_in_uses_array_param);
365 }
366 let partitioned = query.partition_by.is_some() && query.slice.is_some();
367 if partitioned {
368 for order in &query.order_by {
369 if let Some(expr) = &order.expr {
370 collect_expr_params(expr, &mut params, large_in_uses_array_param);
371 }
372 }
373 }
374 if let Some(filter) = &query.filter {
375 collect_expr_params(filter, &mut params, large_in_uses_array_param);
376 }
377 if let Some(search_text) = &query.search_with_text {
378 let value = Value::from(format!("%{search_text}%"));
379 params.extend(
380 entity
381 .properties
382 .iter()
383 .filter(|property| {
384 matches!(
385 property.data_type,
386 teaql_core::DataType::Text | teaql_core::DataType::LargeText
387 )
388 })
389 .map(|_| value.clone()),
390 );
391 }
392 if partitioned {
393 return params;
394 }
395 if let Some(having) = &query.having {
396 collect_expr_params(having, &mut params, large_in_uses_array_param);
397 }
398 for order in &query.order_by {
399 if let Some(expr) = &order.expr {
400 collect_expr_params(expr, &mut params, large_in_uses_array_param);
401 }
402 }
403 params
404}
405
406fn collect_expr_params(expr: &Expr, params: &mut Vec<Value>, large_in_uses_array_param: bool) {
407 match expr {
408 Expr::Column(_) => {}
409 Expr::Value(value) => params.push(value.clone()),
410 Expr::Function { args, .. } | Expr::And(args) | Expr::Or(args) => {
411 for arg in args {
412 collect_expr_params(arg, params, large_in_uses_array_param);
413 }
414 }
415 Expr::Binary { left, op, right } => {
416 collect_expr_params(left, params, large_in_uses_array_param);
417 if let Expr::Value(Value::List(values)) = right.as_ref()
418 && matches!(
419 op,
420 teaql_core::BinaryOp::In
421 | teaql_core::BinaryOp::NotIn
422 | teaql_core::BinaryOp::InLarge
423 | teaql_core::BinaryOp::NotInLarge
424 )
425 {
426 if large_in_uses_array_param
427 && matches!(
428 op,
429 teaql_core::BinaryOp::InLarge | teaql_core::BinaryOp::NotInLarge
430 )
431 {
432 params.push(Value::List(values.clone()));
433 } else {
434 params.extend(values.iter().cloned());
435 }
436 } else {
437 collect_expr_params(right, params, large_in_uses_array_param);
438 }
439 }
440 Expr::SubQuery {
441 left,
442 entity,
443 query,
444 ..
445 } => {
446 collect_expr_params(left, params, large_in_uses_array_param);
447 params.extend(collect_select_params(
448 entity,
449 query,
450 large_in_uses_array_param,
451 ));
452 }
453 Expr::Between { expr, lower, upper } => {
454 collect_expr_params(expr, params, large_in_uses_array_param);
455 collect_expr_params(lower, params, large_in_uses_array_param);
456 collect_expr_params(upper, params, large_in_uses_array_param);
457 }
458 Expr::IsNull(expr) | Expr::IsNotNull(expr) | Expr::Not(expr) => {
459 collect_expr_params(expr, params, large_in_uses_array_param);
460 }
461 }
462}
463
464fn partition_probe_values(query: &SelectQuery) -> Option<Vec<Value>> {
465 let field = query.partition_by.as_deref()?;
466 fn find(expr: &Expr, field: &str) -> Option<Vec<Value>> {
467 match expr {
468 Expr::Binary { left, op, right }
469 if matches!(op, teaql_core::BinaryOp::In | teaql_core::BinaryOp::InLarge)
470 && matches!(left.as_ref(), Expr::Column(column) if column == field) =>
471 {
472 match right.as_ref() {
473 Expr::Value(Value::List(values)) => Some(values.clone()),
474 _ => None,
475 }
476 }
477 Expr::And(parts) => parts.iter().find_map(|part| find(part, field)),
478 _ => None,
479 }
480 }
481 find(query.filter.as_ref()?, field)
482}
483
484fn scalar_partition_probe_query(query: &SelectQuery, value: Value) -> Option<SelectQuery> {
485 let field = query.partition_by.as_deref()?;
486 fn replace(expr: &mut Expr, field: &str, value: &Value) -> bool {
487 match expr {
488 Expr::Binary { left, op, right }
489 if matches!(op, teaql_core::BinaryOp::In | teaql_core::BinaryOp::InLarge)
490 && matches!(left.as_ref(), Expr::Column(column) if column == field) =>
491 {
492 *op = teaql_core::BinaryOp::Eq;
493 **right = Expr::Value(value.clone());
494 true
495 }
496 Expr::And(parts) => parts.iter_mut().any(|part| replace(part, field, value)),
497 _ => false,
498 }
499 }
500
501 let mut scalar = query.clone();
502 if !replace(scalar.filter.as_mut()?, field, &value) {
503 return None;
504 }
505 scalar.partition_by = None;
506 Some(scalar)
507}
508
509impl<D, T, S> SqlDataServiceExecutor<D, T, S>
510where
511 S: teaql_data_service::SchemaProvider,
512{
513 fn entity_descriptor(&self, name: &str) -> Option<Arc<teaql_core::EntityDescriptor>> {
514 if let Ok(cache) = self.descriptor_cache.read()
515 && let Some(descriptor) = cache.get(name)
516 {
517 return Some(descriptor.clone());
518 }
519 let descriptor = self.schema_provider.get_entity(name)?;
520 if let Ok(mut cache) = self.descriptor_cache.write() {
521 return Some(
522 cache
523 .entry(name.to_owned())
524 .or_insert_with(|| descriptor.clone())
525 .clone(),
526 );
527 }
528 Some(descriptor)
529 }
530}
531
532impl<
533 D: SqlDialect + Send + Sync,
534 T: SqlTransport + Send + Sync,
535 S: teaql_data_service::SchemaProvider + Send + Sync,
536> DataServiceExecutor for SqlDataServiceExecutor<D, T, S>
537{
538 type Error = SqlExecutorError<T::Error>;
539
540 fn capabilities(&self) -> DataServiceCapabilities {
541 DataServiceCapabilities {
542 query: true,
543 mutation: true,
544 transaction: false, schema: false,
546 id_generation: false,
547 batch_mutation: true,
548 returning: false,
549 small_parent_relation_probes: self.dialect.prefers_small_parent_relation_probes(),
550 }
551 }
552}
553
554#[cfg(test)]
555mod tests {
556 use super::*;
557 use std::sync::atomic::{AtomicUsize, Ordering};
558 use teaql_core::{DataType, EntityDescriptor, PropertyDescriptor};
559
560 #[derive(Clone, Copy)]
561 struct TestDialect;
562
563 impl SqlDialect for TestDialect {
564 fn kind(&self) -> crate::DatabaseKind {
565 crate::DatabaseKind::PostgreSql
566 }
567
568 fn quote_ident(&self, ident: &str) -> String {
569 format!("\"{ident}\"")
570 }
571
572 fn placeholder(&self, index: usize) -> String {
573 format!("${index}")
574 }
575 }
576
577 #[derive(Clone, Copy)]
578 struct ArrayTestDialect;
579
580 impl SqlDialect for ArrayTestDialect {
581 fn kind(&self) -> crate::DatabaseKind {
582 crate::DatabaseKind::PostgreSql
583 }
584
585 fn quote_ident(&self, ident: &str) -> String {
586 format!("\"{ident}\"")
587 }
588
589 fn placeholder(&self, index: usize) -> String {
590 format!("${index}")
591 }
592
593 fn large_in_uses_array_param(&self) -> bool {
594 true
595 }
596
597 fn compile_in(
598 &self,
599 entity: &EntityDescriptor,
600 left: &Expr,
601 op: teaql_core::BinaryOp,
602 right: &Expr,
603 params: &mut Vec<Value>,
604 ) -> Result<String, SqlCompileError> {
605 if matches!(
606 op,
607 teaql_core::BinaryOp::InLarge | teaql_core::BinaryOp::NotInLarge
608 ) && let Expr::Value(Value::List(values)) = right
609 {
610 let lhs = self.compile_expr(entity, left, params)?;
611 params.push(Value::List(values.clone()));
612 let operator = if op == teaql_core::BinaryOp::InLarge {
613 "= ANY"
614 } else {
615 "<> ALL"
616 };
617 return Ok(format!("({lhs} {operator}(${}))", params.len()));
618 }
619 Err(SqlCompileError::InvalidFunctionArguments(
620 "array test dialect only supports large IN".to_owned(),
621 ))
622 }
623 }
624
625 #[derive(Clone, Copy)]
626 struct EmptyTransport;
627
628 impl SqlTransport for EmptyTransport {
629 type Error = std::io::Error;
630
631 async fn fetch_all_compact_sql(
632 &self,
633 _query: &CompiledQuery,
634 ) -> Result<Vec<CompactRow>, Self::Error> {
635 Ok(Vec::new())
636 }
637
638 async fn execute_sql(&self, _query: &CompiledQuery) -> Result<u64, Self::Error> {
639 Ok(0)
640 }
641 }
642
643 #[derive(Clone)]
644 struct RepeatedProbeTransport {
645 calls: Arc<AtomicUsize>,
646 single_calls: Arc<AtomicUsize>,
647 }
648
649 impl SqlTransport for RepeatedProbeTransport {
650 type Error = std::io::Error;
651
652 async fn fetch_all_compact_sql(
653 &self,
654 _query: &CompiledQuery,
655 ) -> Result<Vec<CompactRow>, Self::Error> {
656 self.single_calls.fetch_add(1, Ordering::Relaxed);
657 Ok(Vec::new())
658 }
659
660 async fn fetch_repeated_compact_sql(
661 &self,
662 template: &CompiledQuery,
663 param_index: usize,
664 values: &[Value],
665 ) -> Result<Vec<CompactRow>, Self::Error> {
666 self.calls.fetch_add(1, Ordering::Relaxed);
667 assert_eq!(values, [Value::U64(7), Value::U64(9)]);
668 assert_eq!(template.params[param_index], Value::U64(7));
669 Ok(Vec::new())
670 }
671
672 async fn execute_sql(&self, _query: &CompiledQuery) -> Result<u64, Self::Error> {
673 Ok(0)
674 }
675 }
676
677 #[derive(Clone, Copy)]
678 struct ProbeDialect;
679
680 impl SqlDialect for ProbeDialect {
681 fn kind(&self) -> crate::DatabaseKind {
682 crate::DatabaseKind::Sqlite
683 }
684
685 fn quote_ident(&self, ident: &str) -> String {
686 format!("\"{ident}\"")
687 }
688
689 fn placeholder(&self, _index: usize) -> String {
690 "?".to_owned()
691 }
692
693 fn prefers_small_parent_relation_probes(&self) -> bool {
694 true
695 }
696 }
697
698 #[derive(Clone)]
699 struct CountingSchemaProvider {
700 lookups: Arc<AtomicUsize>,
701 }
702
703 impl teaql_data_service::SchemaProvider for CountingSchemaProvider {
704 fn get_entity(&self, name: &str) -> Option<Arc<EntityDescriptor>> {
705 self.lookups.fetch_add(1, Ordering::Relaxed);
706 (name == "Order").then(|| Arc::new(test_entity()))
707 }
708 }
709
710 fn test_entity() -> EntityDescriptor {
711 EntityDescriptor::new("Order")
712 .property(PropertyDescriptor::new("id", DataType::U64).id().not_null())
713 .property(PropertyDescriptor::new("name", DataType::Text))
714 }
715
716 fn query_request(capture_debug_query: bool) -> QueryRequest {
717 QueryRequest {
718 query: SelectQuery::new("Order"),
719 trace_chain: Vec::new(),
720 comment: None,
721 capture_debug_query,
722 capture_execution_metadata: true,
723 }
724 }
725
726 #[tokio::test]
727 async fn caches_entity_descriptors_across_executor_clones() {
728 let lookups = Arc::new(AtomicUsize::new(0));
729 let executor = SqlDataServiceExecutor::new(
730 TestDialect,
731 EmptyTransport,
732 CountingSchemaProvider {
733 lookups: lookups.clone(),
734 },
735 );
736
737 let result = executor.query(query_request(false)).await.unwrap();
738 executor.clone().query(query_request(true)).await.unwrap();
739
740 assert_eq!(lookups.load(Ordering::Relaxed), 1);
741 assert!(result.metadata.debug_query.is_none());
742 }
743
744 #[tokio::test]
745 async fn skips_execution_metadata_when_caller_will_discard_it() {
746 let executor = SqlDataServiceExecutor::new(
747 TestDialect,
748 EmptyTransport,
749 CountingSchemaProvider {
750 lookups: Arc::new(AtomicUsize::new(0)),
751 },
752 );
753 let mut request = query_request(false);
754 request.capture_execution_metadata = false;
755
756 let result = executor.query(request).await.unwrap();
757
758 assert!(result.metadata.backend.is_empty());
759 assert_eq!(result.metadata.started_at, SystemTime::UNIX_EPOCH);
760 assert!(result.metadata.parameterized_query.is_none());
761 assert!(result.metadata.params.is_empty());
762 assert!(result.metadata.trace_chain.is_empty());
763 }
764
765 #[tokio::test]
766 async fn topn_004_011_sqlite_reuses_one_repeated_probe_boundary() {
767 let calls = Arc::new(AtomicUsize::new(0));
768 let single_calls = Arc::new(AtomicUsize::new(0));
769 let executor = SqlDataServiceExecutor::new(
770 ProbeDialect,
771 RepeatedProbeTransport {
772 calls: calls.clone(),
773 single_calls: single_calls.clone(),
774 },
775 CountingSchemaProvider {
776 lookups: Arc::new(AtomicUsize::new(0)),
777 },
778 );
779 let mut request = query_request(false);
780 request.capture_execution_metadata = false;
781 request.query = request
782 .query
783 .filter(Expr::in_list("id", [Value::U64(7), Value::U64(9)]))
784 .order_desc("id")
785 .limit(1)
786 .partition_by("id");
787
788 let result = executor.query(request).await.unwrap();
789
790 assert!(result.rows.is_empty());
791 assert_eq!(calls.load(Ordering::Relaxed), 1);
792 assert_eq!(single_calls.load(Ordering::Relaxed), 0);
793 assert_eq!(result.metadata.started_at, SystemTime::UNIX_EPOCH);
794 }
795
796 #[tokio::test]
797 async fn keeps_partition_query_observable_when_metadata_is_enabled() {
798 let calls = Arc::new(AtomicUsize::new(0));
799 let single_calls = Arc::new(AtomicUsize::new(0));
800 let executor = SqlDataServiceExecutor::new(
801 ProbeDialect,
802 RepeatedProbeTransport {
803 calls: calls.clone(),
804 single_calls: single_calls.clone(),
805 },
806 CountingSchemaProvider {
807 lookups: Arc::new(AtomicUsize::new(0)),
808 },
809 );
810 let mut request = query_request(false);
811 request.query = request
812 .query
813 .filter(Expr::in_list("id", [Value::U64(7), Value::U64(9)]))
814 .order_desc("id")
815 .limit(1)
816 .partition_by("id");
817
818 executor.query(request).await.unwrap();
819
820 assert_eq!(calls.load(Ordering::Relaxed), 0);
821 assert_eq!(single_calls.load(Ordering::Relaxed), 1);
822 }
823
824 #[tokio::test]
825 async fn topn_001_explicit_zero_threshold_forces_window_for_probe_provider() {
826 let calls = Arc::new(AtomicUsize::new(0));
827 let single_calls = Arc::new(AtomicUsize::new(0));
828 let executor = SqlDataServiceExecutor::new(
829 ProbeDialect,
830 RepeatedProbeTransport {
831 calls: calls.clone(),
832 single_calls: single_calls.clone(),
833 },
834 CountingSchemaProvider {
835 lookups: Arc::new(AtomicUsize::new(0)),
836 },
837 );
838 let mut request = query_request(false);
839 request.capture_execution_metadata = false;
840 request.query = request
841 .query
842 .filter(Expr::in_list("id", [Value::U64(7), Value::U64(9)]))
843 .order_desc("id")
844 .limit(1)
845 .partition_by("id")
846 .top_n_probe_parent_threshold(0);
847
848 executor.query(request).await.unwrap();
849
850 assert_eq!(calls.load(Ordering::Relaxed), 0);
851 assert_eq!(single_calls.load(Ordering::Relaxed), 1);
852 }
853
854 #[test]
855 fn topn_007_probe_rewrites_only_partition_membership_filter() {
856 let policy = Expr::eq("tenant_id", 7_u64);
857 let visibility = Expr::gt("version", 0_i64);
858 let business = Expr::eq("status", "ACTIVE");
859 let query = SelectQuery::new("Order")
860 .filter(Expr::and([
861 Expr::in_list("owner_id", [Value::U64(11), Value::U64(12)]),
862 policy.clone(),
863 visibility.clone(),
864 business.clone(),
865 ]))
866 .order_desc("id")
867 .limit(3)
868 .partition_by("owner_id");
869
870 let probe = scalar_partition_probe_query(&query, Value::U64(11)).unwrap();
871 let expected = Expr::and([Expr::eq("owner_id", 11_u64), policy, visibility, business]);
872
873 assert_eq!(probe.filter, Some(expected));
874 assert!(probe.partition_by.is_none());
875 assert_eq!(probe.order_by, query.order_by);
876 assert_eq!(probe.slice, query.slice);
877 }
878
879 #[tokio::test]
880 async fn cached_select_plan_rebinds_values_and_separates_in_list_lengths() {
881 let lookups = Arc::new(AtomicUsize::new(0));
882 let executor = SqlDataServiceExecutor::new(
883 TestDialect,
884 EmptyTransport,
885 CountingSchemaProvider { lookups },
886 );
887 let request = |filter| QueryRequest {
888 query: SelectQuery::new("Order").filter(filter),
889 trace_chain: Vec::new(),
890 comment: None,
891 capture_debug_query: false,
892 capture_execution_metadata: true,
893 };
894
895 let first = executor
896 .query(request(Expr::eq("id", 7_u64)))
897 .await
898 .unwrap();
899 let second = executor
900 .query(request(Expr::eq("id", 9_u64)))
901 .await
902 .unwrap();
903 assert_eq!(
904 first.metadata.parameterized_query,
905 second.metadata.parameterized_query
906 );
907 assert_eq!(first.metadata.params, vec![Value::U64(7)]);
908 assert_eq!(second.metadata.params, vec![Value::U64(9)]);
909
910 let short = executor
911 .query(request(Expr::in_list("id", [Value::U64(1), Value::U64(2)])))
912 .await
913 .unwrap();
914 let long = executor
915 .query(request(Expr::in_list(
916 "id",
917 [Value::U64(1), Value::U64(2), Value::U64(3)],
918 )))
919 .await
920 .unwrap();
921 assert_ne!(
922 short.metadata.parameterized_query,
923 long.metadata.parameterized_query
924 );
925 assert_eq!(short.metadata.params.len(), 2);
926 assert_eq!(long.metadata.params.len(), 3);
927 }
928
929 #[tokio::test]
930 async fn cached_select_plan_rebinds_large_in_as_one_array_parameter() {
931 let executor = SqlDataServiceExecutor::new(
932 ArrayTestDialect,
933 EmptyTransport,
934 CountingSchemaProvider {
935 lookups: Arc::new(AtomicUsize::new(0)),
936 },
937 );
938 let request = |values: Vec<Value>| QueryRequest {
939 query: SelectQuery::new("Order").filter(Expr::in_list("id", values)),
940 trace_chain: Vec::new(),
941 comment: None,
942 capture_debug_query: false,
943 capture_execution_metadata: true,
944 };
945 let first_values = (1_u64..=21).map(Value::from).collect::<Vec<_>>();
946 let second_values = (101_u64..=121).map(Value::from).collect::<Vec<_>>();
947
948 let first = executor.query(request(first_values.clone())).await.unwrap();
949 let second = executor
950 .query(request(second_values.clone()))
951 .await
952 .unwrap();
953
954 assert_eq!(
955 first.metadata.parameterized_query,
956 second.metadata.parameterized_query
957 );
958 assert_eq!(first.metadata.params, vec![Value::List(first_values)]);
959 assert_eq!(second.metadata.params, vec![Value::List(second_values)]);
960 }
961
962 #[tokio::test]
963 async fn cached_select_plan_preserves_parameter_order_for_supported_query_shapes() {
964 let executor = SqlDataServiceExecutor::new(
965 TestDialect,
966 EmptyTransport,
967 CountingSchemaProvider {
968 lookups: Arc::new(AtomicUsize::new(0)),
969 },
970 );
971
972 async fn assert_rebound(
973 executor: &SqlDataServiceExecutor<TestDialect, EmptyTransport, CountingSchemaProvider>,
974 warm: SelectQuery,
975 current: SelectQuery,
976 ) {
977 let request = |query| QueryRequest {
978 query,
979 trace_chain: Vec::new(),
980 comment: None,
981 capture_debug_query: false,
982 capture_execution_metadata: true,
983 };
984 executor.query(request(warm)).await.unwrap();
985 let actual = executor.query(request(current.clone())).await.unwrap();
986 let expected = TestDialect
987 .compile_select(&test_entity(), ¤t)
988 .unwrap();
989 assert_eq!(actual.metadata.parameterized_query, Some(expected.sql));
990 assert_eq!(actual.metadata.params, expected.params);
991 }
992
993 assert_rebound(
994 &executor,
995 SelectQuery::new("Order").search_with_text("first"),
996 SelectQuery::new("Order").search_with_text("second"),
997 )
998 .await;
999 assert_rebound(
1000 &executor,
1001 SelectQuery::new("Order")
1002 .project_expr("marker", Expr::value(1_i64))
1003 .filter(Expr::eq("id", 2_u64))
1004 .having(Expr::gt("id", 3_u64))
1005 .order_by(teaql_core::OrderBy::asc_expr(Expr::value(4_i64))),
1006 SelectQuery::new("Order")
1007 .project_expr("marker", Expr::value(11_i64))
1008 .filter(Expr::eq("id", 12_u64))
1009 .having(Expr::gt("id", 13_u64))
1010 .order_by(teaql_core::OrderBy::asc_expr(Expr::value(14_i64))),
1011 )
1012 .await;
1013 assert_rebound(
1014 &executor,
1015 SelectQuery::new("Order")
1016 .filter(Expr::eq("id", 1_u64))
1017 .order_by(teaql_core::OrderBy::asc_expr(Expr::value(2_i64)))
1018 .page(0, 10)
1019 .partition_by("name"),
1020 SelectQuery::new("Order")
1021 .filter(Expr::eq("id", 3_u64))
1022 .order_by(teaql_core::OrderBy::asc_expr(Expr::value(4_i64)))
1023 .page(0, 10)
1024 .partition_by("name"),
1025 )
1026 .await;
1027 assert_rebound(
1028 &executor,
1029 SelectQuery::new("Order").filter(Expr::in_subquery(
1030 "id",
1031 test_entity(),
1032 SelectQuery::new("Order").filter(Expr::gt("id", 20_u64)),
1033 "id",
1034 )),
1035 SelectQuery::new("Order").filter(Expr::in_subquery(
1036 "id",
1037 test_entity(),
1038 SelectQuery::new("Order").filter(Expr::gt("id", 30_u64)),
1039 "id",
1040 )),
1041 )
1042 .await;
1043 }
1044}
1045
1046impl<
1047 D: SqlDialect + Send + Sync,
1048 T: SqlTransport + Send + Sync,
1049 S: teaql_data_service::SchemaProvider + Send + Sync,
1050> QueryExecutor for SqlDataServiceExecutor<D, T, S>
1051{
1052 fn query(
1053 &self,
1054 request: QueryRequest,
1055 ) -> impl std::future::Future<Output = Result<QueryResult, Self::Error>> + Send {
1056 async move {
1057 let entity_desc = self
1058 .entity_descriptor(&request.query.entity)
1059 .ok_or_else(|| {
1060 SqlExecutorError::Compile(SqlCompileError::UnknownEntity(
1061 request.query.entity.clone(),
1062 ))
1063 })?;
1064
1065 if !request.capture_execution_metadata
1066 && self.dialect.prefers_small_parent_relation_probes()
1067 && request.query.top_n_probe_parent_threshold.is_none()
1068 && let Some(values) = partition_probe_values(&request.query)
1069 && values.len() >= 2
1070 && let (Some(first_query), Some(second_query)) = (
1071 scalar_partition_probe_query(&request.query, values[0].clone()),
1072 scalar_partition_probe_query(&request.query, values[1].clone()),
1073 )
1074 {
1075 let first = self
1076 .compile_select_cached(&entity_desc, &first_query)
1077 .map_err(SqlExecutorError::Compile)?;
1078 let second_params = collect_select_params(
1079 &entity_desc,
1080 &second_query,
1081 self.dialect.large_in_uses_array_param(),
1082 );
1083 if let Some(param_index) = first
1084 .params
1085 .iter()
1086 .zip(&second_params)
1087 .position(|(left, right)| left != right)
1088 {
1089 let rows = self
1090 .transport
1091 .fetch_repeated_compact_sql(&first, param_index, &values)
1092 .await
1093 .map_err(SqlExecutorError::Transport)?;
1094 return Ok(QueryResult {
1095 metadata: ExecutionMetadata::unrecorded_query(rows.len()),
1096 rows,
1097 });
1098 }
1099 }
1100
1101 let compiled = self
1102 .compile_select_cached(&entity_desc, &request.query)
1103 .map_err(SqlExecutorError::Compile)?;
1104 let start = request.capture_execution_metadata.then(SystemTime::now);
1105 let rows = self
1106 .transport
1107 .fetch_all_compact_sql(&compiled)
1108 .await
1109 .map_err(SqlExecutorError::Transport)?;
1110 let end = request.capture_execution_metadata.then(SystemTime::now);
1111 let debug_query = request
1112 .capture_debug_query
1113 .then(|| compiled.debug_sql(self.dialect.kind()));
1114 let metadata = if request.capture_execution_metadata {
1115 let CompiledQuery { sql, params, .. } = compiled;
1116 ExecutionMetadata {
1117 backend: "sql".to_string(),
1118 operation: DataServiceOperation::Query,
1119 started_at: start.expect("captured query start"),
1120 ended_at: end.expect("captured query end"),
1121 affected_rows: None,
1122 result_count: Some(rows.len()),
1123 trace_chain: request.trace_chain,
1124 comment: request.comment,
1125 backend_request_id: None,
1126 parameterized_query: Some(sql),
1127 params,
1128 debug_query,
1129 }
1130 } else {
1131 ExecutionMetadata::unrecorded_query(rows.len())
1132 };
1133
1134 Ok(QueryResult { rows, metadata })
1135 }
1136 }
1137}
1138
1139impl<
1140 D: SqlDialect + Send + Sync,
1141 T: SqlTransport + Send + Sync,
1142 S: teaql_data_service::SchemaProvider + Send + Sync,
1143> MutationExecutor for SqlDataServiceExecutor<D, T, S>
1144{
1145 fn mutate(
1146 &self,
1147 request: MutationRequest,
1148 ) -> impl std::future::Future<Output = Result<MutationResult, Self::Error>> + Send {
1149 async move {
1150 let entity_name = match &request {
1151 MutationRequest::Insert(cmd) => &cmd.entity,
1152 MutationRequest::Update(cmd) => &cmd.entity,
1153 MutationRequest::Delete(cmd) => &cmd.entity,
1154 MutationRequest::Recover(cmd) => &cmd.entity,
1155 MutationRequest::Batch(mutations) => {
1156 let mut total_affected = 0;
1157 let mut parameterized_queries = Vec::new();
1158 let mut params = Vec::new();
1159 let mut debug_queries = Vec::new();
1160 let start = SystemTime::now();
1161 for req in mutations {
1162 let res = Box::pin(self.mutate(req.clone())).await?;
1163 total_affected += res.affected_rows;
1164 if let Some(query) = res.metadata.parameterized_query {
1165 parameterized_queries.push(query);
1166 }
1167 params.extend(res.metadata.params);
1168 if let Some(query) = res.metadata.debug_query {
1169 debug_queries.push(query);
1170 }
1171 }
1172 let end = SystemTime::now();
1173 return Ok(MutationResult {
1174 affected_rows: total_affected,
1175 generated_values: GeneratedValues::default(),
1176 persisted_snapshot: None,
1177 metadata: ExecutionMetadata {
1178 backend: "sql".to_string(),
1179 operation: DataServiceOperation::Batch,
1180 started_at: start,
1181 ended_at: end,
1182 affected_rows: Some(total_affected),
1183 result_count: None,
1184 trace_chain: Vec::new(),
1185 comment: None,
1186 backend_request_id: None,
1187 parameterized_query: (!parameterized_queries.is_empty())
1188 .then(|| parameterized_queries.join("; ")),
1189 params,
1190 debug_query: (!debug_queries.is_empty())
1191 .then(|| debug_queries.join("; ")),
1192 },
1193 });
1194 }
1195 };
1196
1197 let entity_desc = self.entity_descriptor(entity_name).ok_or_else(|| {
1198 SqlExecutorError::Compile(SqlCompileError::UnknownEntity(entity_name.clone()))
1199 })?;
1200
1201 let compiled = match &request {
1202 MutationRequest::Insert(cmd) => self
1203 .dialect
1204 .compile_insert(&entity_desc, cmd)
1205 .map_err(SqlExecutorError::Compile)?,
1206 MutationRequest::Update(cmd) => self
1207 .dialect
1208 .compile_update(&entity_desc, cmd)
1209 .map_err(SqlExecutorError::Compile)?,
1210 MutationRequest::Delete(cmd) => self
1211 .dialect
1212 .compile_delete(&entity_desc, cmd)
1213 .map_err(SqlExecutorError::Compile)?,
1214 MutationRequest::Recover(cmd) => self
1215 .dialect
1216 .compile_recover(&entity_desc, cmd)
1217 .map_err(SqlExecutorError::Compile)?,
1218 MutationRequest::Batch(_) => unreachable!(),
1219 };
1220
1221 let start = SystemTime::now();
1222 let affected_rows = self
1223 .transport
1224 .execute_sql(&compiled)
1225 .await
1226 .map_err(SqlExecutorError::Transport)?;
1227 let end = SystemTime::now();
1228
1229 let operation = match &request {
1230 MutationRequest::Insert(_) => DataServiceOperation::Insert,
1231 MutationRequest::Update(_) => DataServiceOperation::Update,
1232 MutationRequest::Delete(_) => DataServiceOperation::Delete,
1233 MutationRequest::Recover(_) => DataServiceOperation::Recover,
1234 MutationRequest::Batch(_) => DataServiceOperation::Batch,
1235 };
1236
1237 let metadata = ExecutionMetadata {
1238 backend: "sql".to_string(),
1239 operation,
1240 started_at: start,
1241 ended_at: end,
1242 affected_rows: Some(affected_rows),
1243 result_count: None,
1244 trace_chain: request.trace_chain().to_vec(),
1245 comment: request.comment().map(|s| s.to_owned()),
1246 backend_request_id: None,
1247 parameterized_query: Some(compiled.sql.clone()),
1248 params: compiled.params.clone(),
1249 debug_query: Some(compiled.debug_sql(self.dialect.kind())),
1250 };
1251
1252 Ok(MutationResult {
1253 affected_rows,
1254 generated_values: GeneratedValues::default(),
1255 persisted_snapshot: None,
1256 metadata,
1257 })
1258 }
1259 }
1260}
1261
1262#[derive(Clone)]
1263pub struct SqlDataServiceTransaction<'a, D, Tx: SqlTransport + SqlTransaction, S> {
1264 pub dialect: &'a D,
1265 pub transport: Tx,
1266 pub schema_provider: &'a S,
1267 descriptor_cache: Arc<RwLock<HashMap<String, Arc<teaql_core::EntityDescriptor>>>>,
1268 select_plan_cache: Arc<RwLock<Vec<(SelectQuery, String)>>>,
1269}
1270
1271impl<'a, D, Tx: SqlTransport + SqlTransaction, S> SqlDataServiceTransaction<'a, D, Tx, S>
1272where
1273 S: teaql_data_service::SchemaProvider,
1274{
1275 fn entity_descriptor(&self, name: &str) -> Option<Arc<teaql_core::EntityDescriptor>> {
1276 if let Ok(cache) = self.descriptor_cache.read()
1277 && let Some(descriptor) = cache.get(name)
1278 {
1279 return Some(descriptor.clone());
1280 }
1281 let descriptor = self.schema_provider.get_entity(name)?;
1282 if let Ok(mut cache) = self.descriptor_cache.write() {
1283 return Some(
1284 cache
1285 .entry(name.to_owned())
1286 .or_insert_with(|| descriptor.clone())
1287 .clone(),
1288 );
1289 }
1290 Some(descriptor)
1291 }
1292
1293 fn compile_select_cached(
1294 &self,
1295 entity: &EntityDescriptor,
1296 query: &SelectQuery,
1297 ) -> Result<CompiledQuery, SqlCompileError>
1298 where
1299 D: SqlDialect,
1300 {
1301 compile_select_with_cache(self.dialect, &self.select_plan_cache, entity, query)
1302 }
1303}
1304
1305impl<
1306 'a,
1307 D: SqlDialect + Send + Sync,
1308 Tx: SqlTransport + SqlTransaction<Error = <Tx as SqlTransport>::Error> + Send + Sync,
1309 S: teaql_data_service::SchemaProvider + Send + Sync,
1310> DataServiceExecutor for SqlDataServiceTransaction<'a, D, Tx, S>
1311{
1312 type Error = SqlExecutorError<<Tx as SqlTransport>::Error>;
1313
1314 fn capabilities(&self) -> DataServiceCapabilities {
1315 DataServiceCapabilities {
1316 query: true,
1317 mutation: true,
1318 transaction: false,
1319 schema: false,
1320 id_generation: false,
1321 batch_mutation: true,
1322 returning: false,
1323 small_parent_relation_probes: self.dialect.prefers_small_parent_relation_probes(),
1324 }
1325 }
1326}
1327
1328impl<
1329 'a,
1330 D: SqlDialect + Send + Sync,
1331 Tx: SqlTransport + SqlTransaction<Error = <Tx as SqlTransport>::Error> + Send + Sync,
1332 S: teaql_data_service::SchemaProvider + Send + Sync,
1333> QueryExecutor for SqlDataServiceTransaction<'a, D, Tx, S>
1334{
1335 fn query(
1336 &self,
1337 request: QueryRequest,
1338 ) -> impl std::future::Future<Output = Result<QueryResult, Self::Error>> + Send {
1339 async move {
1340 let entity_desc = self
1341 .entity_descriptor(&request.query.entity)
1342 .ok_or_else(|| {
1343 SqlExecutorError::Compile(SqlCompileError::UnknownEntity(
1344 request.query.entity.clone(),
1345 ))
1346 })?;
1347
1348 let compiled = self
1349 .compile_select_cached(&entity_desc, &request.query)
1350 .map_err(SqlExecutorError::Compile)?;
1351 let start = SystemTime::now();
1352 let rows = self
1353 .transport
1354 .fetch_all_compact_sql(&compiled)
1355 .await
1356 .map_err(SqlExecutorError::Transport)?;
1357 let end = SystemTime::now();
1358
1359 let metadata = ExecutionMetadata {
1360 backend: "sql".to_string(),
1361 operation: DataServiceOperation::Query,
1362 started_at: start,
1363 ended_at: end,
1364 affected_rows: None,
1365 result_count: Some(rows.len()),
1366 trace_chain: request.trace_chain,
1367 comment: request.comment,
1368 backend_request_id: None,
1369 parameterized_query: Some(compiled.sql.clone()),
1370 params: compiled.params.clone(),
1371 debug_query: request
1372 .capture_debug_query
1373 .then(|| compiled.debug_sql(self.dialect.kind())),
1374 };
1375
1376 Ok(QueryResult { rows, metadata })
1377 }
1378 }
1379}
1380
1381impl<
1382 'a,
1383 D: SqlDialect + Send + Sync,
1384 Tx: SqlTransport + SqlTransaction<Error = <Tx as SqlTransport>::Error> + Send + Sync,
1385 S: teaql_data_service::SchemaProvider + Send + Sync,
1386> MutationExecutor for SqlDataServiceTransaction<'a, D, Tx, S>
1387{
1388 fn mutate(
1389 &self,
1390 request: MutationRequest,
1391 ) -> impl std::future::Future<Output = Result<MutationResult, Self::Error>> + Send {
1392 async move {
1393 let entity_name = match &request {
1394 MutationRequest::Insert(cmd) => &cmd.entity,
1395 MutationRequest::Update(cmd) => &cmd.entity,
1396 MutationRequest::Delete(cmd) => &cmd.entity,
1397 MutationRequest::Recover(cmd) => &cmd.entity,
1398 MutationRequest::Batch(mutations) => {
1399 let mut total_affected = 0;
1400 let mut parameterized_queries = Vec::new();
1401 let mut params = Vec::new();
1402 let mut debug_queries = Vec::new();
1403 let start = SystemTime::now();
1404 for req in mutations {
1405 let res = Box::pin(self.mutate(req.clone())).await?;
1406 total_affected += res.affected_rows;
1407 if let Some(query) = res.metadata.parameterized_query {
1408 parameterized_queries.push(query);
1409 }
1410 params.extend(res.metadata.params);
1411 if let Some(query) = res.metadata.debug_query {
1412 debug_queries.push(query);
1413 }
1414 }
1415 let end = SystemTime::now();
1416 return Ok(MutationResult {
1417 affected_rows: total_affected,
1418 generated_values: GeneratedValues::default(),
1419 persisted_snapshot: None,
1420 metadata: ExecutionMetadata {
1421 backend: "sql".to_string(),
1422 operation: DataServiceOperation::Batch,
1423 started_at: start,
1424 ended_at: end,
1425 affected_rows: Some(total_affected),
1426 result_count: None,
1427 trace_chain: Vec::new(),
1428 comment: None,
1429 backend_request_id: None,
1430 parameterized_query: (!parameterized_queries.is_empty())
1431 .then(|| parameterized_queries.join("; ")),
1432 params,
1433 debug_query: (!debug_queries.is_empty())
1434 .then(|| debug_queries.join("; ")),
1435 },
1436 });
1437 }
1438 };
1439
1440 let entity_desc = self.entity_descriptor(entity_name).ok_or_else(|| {
1441 SqlExecutorError::Compile(SqlCompileError::UnknownEntity(entity_name.clone()))
1442 })?;
1443
1444 let compiled = match &request {
1445 MutationRequest::Insert(cmd) => self
1446 .dialect
1447 .compile_insert(&entity_desc, cmd)
1448 .map_err(SqlExecutorError::Compile)?,
1449 MutationRequest::Update(cmd) => self
1450 .dialect
1451 .compile_update(&entity_desc, cmd)
1452 .map_err(SqlExecutorError::Compile)?,
1453 MutationRequest::Delete(cmd) => self
1454 .dialect
1455 .compile_delete(&entity_desc, cmd)
1456 .map_err(SqlExecutorError::Compile)?,
1457 MutationRequest::Recover(cmd) => self
1458 .dialect
1459 .compile_recover(&entity_desc, cmd)
1460 .map_err(SqlExecutorError::Compile)?,
1461 MutationRequest::Batch(_) => unreachable!("batch handled above"),
1462 };
1463
1464 let start = SystemTime::now();
1465 let affected_rows = self
1466 .transport
1467 .execute_sql(&compiled)
1468 .await
1469 .map_err(SqlExecutorError::Transport)?;
1470 let end = SystemTime::now();
1471
1472 let operation = match &request {
1473 MutationRequest::Insert(_) => DataServiceOperation::Insert,
1474 MutationRequest::Update(_) => DataServiceOperation::Update,
1475 MutationRequest::Delete(_) => DataServiceOperation::Delete,
1476 MutationRequest::Recover(_) => DataServiceOperation::Recover,
1477 MutationRequest::Batch(_) => DataServiceOperation::Batch,
1478 };
1479
1480 let persisted_id = match &request {
1481 MutationRequest::Insert(cmd) => cmd.values.get("id").cloned(),
1482 MutationRequest::Update(cmd) => Some(cmd.id.clone()),
1483 MutationRequest::Delete(cmd) if cmd.soft_delete => Some(cmd.id.clone()),
1484 MutationRequest::Recover(cmd) => Some(cmd.id.clone()),
1485 MutationRequest::Delete(_) | MutationRequest::Batch(_) => None,
1486 };
1487 let persisted_snapshot = if affected_rows == 1 {
1488 if let Some(id) = persisted_id {
1489 let query = SelectQuery::new(entity_name.clone()).filter(Expr::eq("id", id));
1490 let compiled_readback = self
1491 .compile_select_cached(&entity_desc, &query)
1492 .map_err(SqlExecutorError::Compile)?;
1493 let mut rows = self
1494 .transport
1495 .fetch_all_compact_sql(&compiled_readback)
1496 .await
1497 .map_err(SqlExecutorError::Transport)?;
1498 if rows.len() != 1 {
1499 return Err(SqlExecutorError::PersistedRecord(format!(
1500 "persisted {entity_name} record could not be read back"
1501 )));
1502 }
1503 rows.pop().map(|row| EntitySnapshot::from(row.into_map()))
1504 } else {
1505 None
1506 }
1507 } else {
1508 None
1509 };
1510
1511 let metadata = ExecutionMetadata {
1512 backend: "sql".to_string(),
1513 operation,
1514 started_at: start,
1515 ended_at: end,
1516 affected_rows: Some(affected_rows),
1517 result_count: None,
1518 trace_chain: request.trace_chain().to_vec(),
1519 comment: request.comment().map(|s| s.to_owned()),
1520 backend_request_id: None,
1521 parameterized_query: Some(compiled.sql.clone()),
1522 params: compiled.params.clone(),
1523 debug_query: Some(compiled.debug_sql(self.dialect.kind())),
1524 };
1525
1526 Ok(MutationResult {
1527 affected_rows,
1528 generated_values: GeneratedValues::default(),
1529 persisted_snapshot,
1530 metadata,
1531 })
1532 }
1533 }
1534}
1535
1536impl<
1537 'a,
1538 D: SqlDialect + Send + Sync,
1539 Tx: SqlTransport + SqlTransaction<Error = <Tx as SqlTransport>::Error> + Send + Sync,
1540 S: teaql_data_service::SchemaProvider + Send + Sync,
1541> teaql_data_service::Transaction for SqlDataServiceTransaction<'a, D, Tx, S>
1542{
1543 type Error = SqlExecutorError<<Tx as SqlTransport>::Error>;
1544
1545 fn commit(self) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send {
1546 async move {
1547 self.transport
1548 .commit_sql()
1549 .await
1550 .map_err(SqlExecutorError::Transport)
1551 }
1552 }
1553
1554 fn rollback(self) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send {
1555 async move {
1556 self.transport
1557 .rollback_sql()
1558 .await
1559 .map_err(SqlExecutorError::Transport)
1560 }
1561 }
1562}
1563
1564impl<
1565 D: SqlDialect + Send + Sync,
1566 T: SqlTransactionTransport + Send + Sync,
1567 S: teaql_data_service::SchemaProvider + Send + Sync,
1568> teaql_data_service::TransactionExecutor for SqlDataServiceExecutor<D, T, S>
1569{
1570 type Tx<'a>
1571 = SqlDataServiceTransaction<'a, D, T::Tx<'a>, S>
1572 where
1573 Self: 'a;
1574
1575 fn begin(&self) -> impl std::future::Future<Output = Result<Self::Tx<'_>, Self::Error>> + Send {
1576 async move {
1577 let tx = self
1578 .transport
1579 .begin_sql()
1580 .await
1581 .map_err(SqlExecutorError::Transport)?;
1582 Ok(SqlDataServiceTransaction {
1583 dialect: &self.dialect,
1584 transport: tx,
1585 schema_provider: &self.schema_provider,
1586 descriptor_cache: self.descriptor_cache.clone(),
1587 select_plan_cache: self.select_plan_cache.clone(),
1588 })
1589 }
1590 }
1591}
1592
1593impl<
1594 D: SqlDialect + Send + Sync,
1595 T: StreamingSqlTransport + Send + Sync,
1596 S: teaql_data_service::SchemaProvider + Send + Sync,
1597> teaql_data_service::StreamQueryExecutor for SqlDataServiceExecutor<D, T, S>
1598{
1599 fn query_stream(
1600 &self,
1601 request: teaql_data_service::QueryRequest,
1602 chunk_size: usize,
1603 ) -> teaql_data_service::QueryStream<'_, Self::Error> {
1604 use futures_util::StreamExt;
1605 let entity = match self.entity_descriptor(&request.query.entity) {
1606 Some(entity) => entity,
1607 None => {
1608 return Box::pin(futures_util::stream::once(async {
1609 Err(SqlExecutorError::Compile(SqlCompileError::UnknownEntity(
1610 request.query.entity,
1611 )))
1612 }));
1613 }
1614 };
1615 match self.compile_select_cached(&entity, &request.query) {
1616 Ok(compiled) => Box::pin(
1617 self.transport
1618 .stream_sql(compiled, chunk_size)
1619 .map(|r| r.map_err(SqlExecutorError::Transport)),
1620 ),
1621 Err(error) => Box::pin(futures_util::stream::once(async {
1622 Err(SqlExecutorError::Compile(error))
1623 })),
1624 }
1625 }
1626}