1use std::any::{Any, TypeId};
2use std::collections::{BTreeMap, HashMap};
3use std::future::Future;
4
5use std::pin::Pin;
6use std::sync::Mutex;
7use std::time::{Duration, SystemTime};
8
9use teaql_core::{EntityDescriptor, Record, UpdateCommand, Value};
10use teaql_sql::{CompiledQuery, DatabaseKind};
11
12use crate::{
13 CheckResults, CheckerRegistry, ContextError, EntityDataServiceBehavior,
14 EntityDataServiceBehaviorRegistry, EntityRegistry, GraphNode, InternalIdGenerator, Language,
15 MetadataStore, ObjectLocation, RawAuditEvent, RawAuditEventSink, RequestPolicy, RuntimeError,
16 local_id_generator, translate_check_result,
17};
18use crate::{DataServiceError, EntityRoot};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum SqlLogOperation {
22 Select,
23 Insert,
24 Update,
25 Delete,
26 Recover,
27}
28
29impl SqlLogOperation {
30 pub fn is_select(self) -> bool {
31 matches!(self, Self::Select)
32 }
33
34 pub fn is_mutation(self) -> bool {
35 !self.is_select()
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub struct SqlLogOptions {
41 pub select: bool,
42 pub mutation: bool,
43}
44
45impl SqlLogOptions {
46 pub fn disabled() -> Self {
47 Self {
48 select: false,
49 mutation: false,
50 }
51 }
52
53 pub fn select_only() -> Self {
54 Self {
55 select: true,
56 mutation: false,
57 }
58 }
59
60 pub fn mutation_only() -> Self {
61 Self {
62 select: false,
63 mutation: true,
64 }
65 }
66
67 pub fn all() -> Self {
68 Self {
69 select: true,
70 mutation: true,
71 }
72 }
73
74 pub fn enabled_for(self, operation: SqlLogOperation) -> bool {
75 if operation.is_select() {
76 self.select
77 } else {
78 self.mutation
79 }
80 }
81}
82
83#[derive(Debug, Clone, PartialEq)]
84pub struct SqlLogEntry {
85 pub operation: SqlLogOperation,
86 pub sql: String,
87 pub params: Vec<Value>,
88 pub debug_sql: String,
89 pub pretty_sql: String,
90 pub started_at: SystemTime,
91 pub ended_at: SystemTime,
92 pub elapsed: Duration,
93 pub result_count: Option<usize>,
94 pub result_type: Option<String>,
95 pub affected_rows: Option<u64>,
96 pub result_summary: String,
97}
98
99#[derive(Debug, Clone, PartialEq)]
100pub struct UnifiedLogEntry {
101 pub timestamp: SystemTime,
102 pub user_identifier: Option<String>,
103 pub trace_chain: Vec<teaql_core::TraceNode>,
104 pub payload: LogPayload,
105}
106
107#[derive(Debug, Clone, PartialEq)]
108pub enum LogPayload {
109 Sql(SqlLogEntry),
110 Info(InfoLogEntry),
111}
112
113#[derive(Debug, Clone, PartialEq)]
114pub struct InfoLogEntry {
115 pub message: String,
116}
117
118#[derive(Clone, Default)]
119pub struct UnifiedLogBuffer {
120 pub entries: std::sync::Arc<Mutex<Vec<UnifiedLogEntry>>>,
121}
122
123pub trait SchemaProvider: Send + Sync {
124 fn ensure_schema<'a>(
125 &'a self,
126 ctx: &'a UserContext,
127 ) -> Pin<Box<dyn Future<Output = Result<(), RuntimeError>> + Send + 'a>>;
128}
129
130pub struct UserContext {
131 pub(crate) metadata: Option<Box<dyn MetadataStore>>,
132 pub(crate) entity_registry: Option<Box<dyn EntityRegistry>>,
133 pub(crate) entity_data_service_behavior_registry:
134 Option<Box<dyn EntityDataServiceBehaviorRegistry>>,
135 pub(crate) request_policy: Option<Box<dyn RequestPolicy>>,
136 pub(crate) checker_registry: Option<Box<dyn CheckerRegistry>>,
137 pub(crate) event_sink: Option<Box<dyn RawAuditEventSink>>,
138 pub(crate) custom_event_sink: Option<Box<dyn crate::SafeAuditEventSink>>,
139 pub(crate) internal_id_generator: Option<Box<dyn InternalIdGenerator>>,
140 schema_provider: Option<Box<dyn SchemaProvider>>,
141 language: Language,
142 typed_resources: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
143 named_resources: BTreeMap<String, Box<dyn Any + Send + Sync>>,
144 locals: BTreeMap<String, Value>,
145 pub(crate) initial_graphs: Vec<GraphNode>,
146 entity_root: EntityRoot,
147 sql_log_options: SqlLogOptions,
148 sql_log_entries: Mutex<Vec<SqlLogEntry>>,
149 user_identifier: Option<String>,
150 timezone: Option<String>,
151 trace_id: String,
152}
153
154impl Default for UserContext {
155 fn default() -> Self {
156 let pid = std::process::id();
157 let thread_id_str = format!("{:?}", std::thread::current().id());
158 let numeric_thread_id = thread_id_str
159 .strip_prefix("ThreadId(")
160 .and_then(|s| s.strip_suffix(")"))
161 .unwrap_or(&thread_id_str);
162 let os_user = std::env::var("USER")
163 .or_else(|_| std::env::var("USERNAME"))
164 .unwrap_or_else(|_| "main".to_owned());
165 let user_id = format!("{os_user}@pid-{pid}.tid-{numeric_thread_id}");
166 Self {
167 metadata: None,
168 entity_registry: None,
169 entity_data_service_behavior_registry: None,
170 request_policy: None,
171 checker_registry: None,
172 event_sink: None,
173 custom_event_sink: None,
174 internal_id_generator: None,
175 schema_provider: None,
176 language: Language::default(),
177 typed_resources: HashMap::new(),
178 named_resources: BTreeMap::new(),
179 locals: BTreeMap::new(),
180 initial_graphs: Vec::new(),
181 entity_root: EntityRoot::default(),
182 sql_log_options: SqlLogOptions::all(),
183 sql_log_entries: Mutex::new(Vec::new()),
184 user_identifier: Some(user_id),
185 timezone: Some("UTC".to_owned()),
186 trace_id: format!(
187 "req-{pid}-{numeric_thread_id}-{:x}",
188 std::time::SystemTime::now()
189 .duration_since(std::time::UNIX_EPOCH)
190 .unwrap_or_default()
191 .as_micros()
192 ),
193 }
194 }
195}
196
197#[async_trait::async_trait]
198pub trait DataStore: Send + Sync + 'static {
199 async fn get(&self, key: &str) -> Option<Value>;
200 async fn put(&self, key: &str, value: Value, timeout_seconds: Option<u64>);
201 async fn remove(&self, key: &str);
202}
203
204#[derive(Default)]
205pub struct InMemoryDataStore {
206 cache: std::sync::RwLock<HashMap<String, (Value, Option<std::time::Instant>)>>,
207}
208
209#[async_trait::async_trait]
210impl DataStore for InMemoryDataStore {
211 async fn get(&self, key: &str) -> Option<Value> {
212 let lock = self.cache.read().unwrap();
213 if let Some((val, expires_at)) = lock.get(key) {
214 if let Some(exp) = expires_at {
215 if std::time::Instant::now() > *exp {
216 return None;
217 }
218 }
219 return Some(val.clone());
220 }
221 None
222 }
223
224 async fn put(&self, key: &str, value: Value, timeout_seconds: Option<u64>) {
225 let mut lock = self.cache.write().unwrap();
226 let expires_at = timeout_seconds
227 .map(|secs| std::time::Instant::now() + std::time::Duration::from_secs(secs));
228 lock.insert(key.to_string(), (value, expires_at));
229 }
230
231 async fn remove(&self, key: &str) {
232 let mut lock = self.cache.write().unwrap();
233 lock.remove(key);
234 }
235}
236
237impl UserContext {
238 pub fn new() -> Self {
239 Self::default()
240 }
241
242 pub fn user_identifier(&self) -> Option<&str> {
243 self.user_identifier.as_deref()
244 }
245
246 pub fn set_user_identifier(&mut self, user_identifier: impl Into<String>) {
247 self.user_identifier = Some(user_identifier.into());
248 }
249
250 pub fn with_user_identifier(mut self, user_identifier: impl Into<String>) -> Self {
251 self.user_identifier = Some(user_identifier.into());
252 self
253 }
254
255 pub fn set_user_identifier_option(&mut self, user_identifier: Option<String>) {
256 self.user_identifier = user_identifier;
257 }
258
259 pub fn with_user_identifier_option(mut self, user_identifier: Option<String>) -> Self {
260 self.user_identifier = user_identifier;
261 self
262 }
263
264 pub fn timezone(&self) -> Option<&str> {
265 self.timezone.as_deref()
266 }
267
268 pub fn set_timezone(&mut self, timezone: impl Into<String>) {
269 self.timezone = Some(timezone.into());
270 }
271
272 pub fn with_timezone(mut self, timezone: impl Into<String>) -> Self {
273 self.timezone = Some(timezone.into());
274 self
275 }
276
277 pub fn trace_id(&self) -> &str {
278 &self.trace_id
279 }
280
281 pub fn set_trace_id(&mut self, trace_id: impl Into<String>) {
282 self.trace_id = trace_id.into();
283 }
284
285 pub fn with_trace_id(mut self, trace_id: impl Into<String>) -> Self {
286 self.trace_id = trace_id.into();
287 self
288 }
289
290 pub fn with_module(mut self, module: crate::RuntimeModule) -> Self {
291 module.apply_to(&mut self);
292 self
293 }
294
295 pub fn entity_root(&self) -> EntityRoot {
296 self.entity_root.clone()
297 }
298
299 pub fn initial_graphs(&self) -> &[GraphNode] {
300 &self.initial_graphs
301 }
302
303 pub fn set_initial_graphs(&mut self, graphs: Vec<GraphNode>) {
304 self.initial_graphs = graphs;
305 }
306
307 pub fn with_metadata(mut self, metadata: impl MetadataStore + 'static) -> Self {
308 self.metadata = Some(Box::new(metadata));
309 self
310 }
311
312 pub fn set_metadata(&mut self, metadata: impl MetadataStore + 'static) {
313 self.metadata = Some(Box::new(metadata));
314 }
315
316 pub fn with_entity_registry(mut self, registry: impl EntityRegistry + 'static) -> Self {
317 self.entity_registry = Some(Box::new(registry));
318 self
319 }
320
321 pub fn set_entity_registry(&mut self, registry: impl EntityRegistry + 'static) {
322 self.entity_registry = Some(Box::new(registry));
323 }
324
325 pub fn with_entity_data_service_behavior_registry(
326 mut self,
327 registry: impl EntityDataServiceBehaviorRegistry + 'static,
328 ) -> Self {
329 self.entity_data_service_behavior_registry = Some(Box::new(registry));
330 self
331 }
332
333 pub fn set_entity_data_service_behavior_registry(
334 &mut self,
335 registry: impl EntityDataServiceBehaviorRegistry + 'static,
336 ) {
337 self.entity_data_service_behavior_registry = Some(Box::new(registry));
338 }
339
340 pub fn with_request_policy(mut self, policy: impl RequestPolicy + 'static) -> Self {
341 self.request_policy = Some(Box::new(policy));
342 self
343 }
344
345 pub fn set_request_policy(&mut self, policy: impl RequestPolicy + 'static) {
346 self.request_policy = Some(Box::new(policy));
347 }
348
349 pub fn clear_request_policy(&mut self) {
350 self.request_policy = None;
351 }
352
353 pub fn with_checker_registry(mut self, registry: impl CheckerRegistry + 'static) -> Self {
354 self.checker_registry = Some(Box::new(registry));
355 self
356 }
357
358 pub fn set_checker_registry(&mut self, registry: impl CheckerRegistry + 'static) {
359 self.checker_registry = Some(Box::new(registry));
360 }
361
362 pub(crate) fn with_event_sink(mut self, sink: impl RawAuditEventSink + 'static) -> Self {
363 self.event_sink = Some(Box::new(sink));
364 self
365 }
366
367 pub(crate) fn set_event_sink(&mut self, sink: impl RawAuditEventSink + 'static) {
368 self.event_sink = Some(Box::new(sink));
369 }
370
371 pub fn with_custom_event_sink(
372 mut self,
373 sink: impl crate::SafeAuditEventSink + 'static,
374 ) -> Self {
375 self.custom_event_sink = Some(Box::new(sink));
376 self
377 }
378
379 pub fn set_custom_event_sink(&mut self, sink: impl crate::SafeAuditEventSink + 'static) {
380 self.custom_event_sink = Some(Box::new(sink));
381 }
382
383 pub fn with_internal_id_generator(
384 mut self,
385 generator: impl InternalIdGenerator + 'static,
386 ) -> Self {
387 self.internal_id_generator = Some(Box::new(generator));
388 self
389 }
390
391 pub fn set_internal_id_generator(&mut self, generator: impl InternalIdGenerator + 'static) {
392 self.internal_id_generator = Some(Box::new(generator));
393 }
394
395 pub fn with_schema_provider(mut self, provider: impl SchemaProvider + 'static) -> Self {
396 self.schema_provider = Some(Box::new(provider));
397 self
398 }
399
400 pub fn set_schema_provider(&mut self, provider: impl SchemaProvider + 'static) {
401 self.schema_provider = Some(Box::new(provider));
402 }
403
404 pub async fn ensure_schema(&self) -> Result<(), RuntimeError> {
405 let provider = self
406 .schema_provider
407 .as_ref()
408 .ok_or_else(|| RuntimeError::Schema("missing schema provider".to_owned()))?;
409 provider.ensure_schema(self).await
410 }
411
412 pub fn with_language(mut self, language: Language) -> Self {
413 self.language = language;
414 self
415 }
416
417 pub fn set_language(&mut self, language: Language) {
418 self.language = language;
419 }
420
421 pub fn with_sql_log_options(mut self, options: SqlLogOptions) -> Self {
422 self.sql_log_options = options;
423 self
424 }
425
426 pub fn set_sql_log_options(&mut self, options: SqlLogOptions) {
427 self.sql_log_options = options;
428 }
429
430 pub fn enable_select_sql_log(&mut self) {
431 self.sql_log_options.select = true;
432 }
433
434 pub fn enable_mutation_sql_log(&mut self) {
435 self.sql_log_options.mutation = true;
436 }
437
438 pub fn enable_all_sql_log(&mut self) {
439 self.sql_log_options = SqlLogOptions::all();
440 }
441
442 pub fn disable_sql_log(&mut self) {
443 self.sql_log_options = SqlLogOptions::disabled();
444 self.clear_sql_logs();
445 }
446
447 pub fn sql_log_options(&self) -> SqlLogOptions {
448 self.sql_log_options
449 }
450
451 pub fn sql_logs(&self) -> Vec<SqlLogEntry> {
452 self.sql_log_entries
453 .lock()
454 .map(|entries| entries.clone())
455 .unwrap_or_default()
456 }
457
458 pub fn clear_sql_logs(&self) {
459 if let Ok(mut entries) = self.sql_log_entries.lock() {
460 entries.clear();
461 }
462 }
463
464 pub(crate) fn record_sql_log(
465 &self,
466 operation: SqlLogOperation,
467 query: &CompiledQuery,
468 database_kind: DatabaseKind,
469 started_at: SystemTime,
470 ended_at: SystemTime,
471 elapsed: Duration,
472 result_count: Option<usize>,
473 result_type: Option<String>,
474 affected_rows: Option<u64>,
475 trace_chain: Vec<teaql_core::TraceNode>,
476 ) {
477 if !self.sql_log_options.enabled_for(operation) {
478 return;
479 }
480 let debug_sql = query.debug_sql(database_kind);
481 let result_summary = sql_result_summary(
482 operation,
483 result_count,
484 result_type.as_deref(),
485 affected_rows,
486 &debug_sql,
487 );
488
489 let sql_log_entry = SqlLogEntry {
490 operation,
491 sql: query.sql.clone(),
492 params: query.params.clone(),
493 pretty_sql: pretty_sql(&debug_sql),
494 debug_sql: debug_sql.clone(),
495 started_at,
496 ended_at,
497 elapsed,
498 result_summary: result_summary.clone(),
499 result_count,
500 result_type,
501 affected_rows,
502 };
503
504 if let Ok(mut entries) = self.sql_log_entries.lock() {
505 entries.push(sql_log_entry.clone());
509 }
510
511 if let Some(buf) = self.get_resource::<UnifiedLogBuffer>() {
512 if let Ok(mut entries) = buf.entries.lock() {
513 entries.push(UnifiedLogEntry {
514 timestamp: started_at,
515 user_identifier: self.user_identifier.clone(),
516 trace_chain: trace_chain.clone(),
517 payload: LogPayload::Sql(sql_log_entry.clone()),
518 });
519 }
520 }
521
522 crate::log_formatter::LogManager::write_sql_log(&trace_chain, &sql_log_entry);
523 }
524
525 pub(crate) fn record_metadata_log(&self, metadata: &teaql_data_service::ExecutionMetadata) {
526 if let Some(debug_sql) = &metadata.debug_query {
527 let sql_log_entry = SqlLogEntry {
528 operation: match metadata.operation {
529 teaql_data_service::DataServiceOperation::Query => SqlLogOperation::Select,
530 teaql_data_service::DataServiceOperation::Insert => SqlLogOperation::Insert,
531 teaql_data_service::DataServiceOperation::Update => SqlLogOperation::Update,
532 teaql_data_service::DataServiceOperation::Delete => SqlLogOperation::Delete,
533 teaql_data_service::DataServiceOperation::Recover => SqlLogOperation::Update, teaql_data_service::DataServiceOperation::Batch => SqlLogOperation::Update,
535 teaql_data_service::DataServiceOperation::Schema => SqlLogOperation::Update,
536 },
537 sql: String::new(), params: Vec::new(), pretty_sql: pretty_sql(debug_sql),
540 debug_sql: debug_sql.clone(),
541 started_at: metadata.started_at,
542 ended_at: metadata.ended_at,
543 elapsed: metadata
544 .ended_at
545 .duration_since(metadata.started_at)
546 .unwrap_or_default(),
547 result_count: metadata.result_count,
548 result_type: None, affected_rows: metadata.affected_rows,
550 result_summary: String::new(), };
552
553 let mut summary = String::new();
555 if let Some(c) = metadata.result_count {
556 summary = format!("{} rows returned", c);
557 } else if let Some(a) = metadata.affected_rows {
558 summary = format!("{} rows affected", a);
559 }
560
561 let mut final_entry = sql_log_entry;
562 final_entry.result_summary = summary;
563
564 if let Ok(mut entries) = self.sql_log_entries.lock() {
565 entries.push(final_entry.clone());
566 }
567
568 if let Some(buf) = self.get_resource::<UnifiedLogBuffer>() {
569 if let Ok(mut entries) = buf.entries.lock() {
570 entries.push(UnifiedLogEntry {
571 timestamp: metadata.started_at,
572 user_identifier: self.user_identifier.clone(),
573 trace_chain: metadata.trace_chain.clone(),
574 payload: LogPayload::Sql(final_entry.clone()),
575 });
576 }
577 }
578
579 crate::log_formatter::LogManager::write_sql_log(&metadata.trace_chain, &final_entry);
580 }
581 }
582
583 pub fn language(&self) -> Language {
584 self.language
585 }
586
587 pub fn set_language_code(&mut self, code: &str) -> Result<(), RuntimeError> {
588 let Some(language) = Language::from_code(code) else {
589 return Err(RuntimeError::Language(format!(
590 "unsupported language code: {code}"
591 )));
592 };
593 self.language = language;
594 Ok(())
595 }
596
597 pub fn generate_id(&self, entity: &str) -> Result<Option<u64>, RuntimeError> {
598 self.internal_id_generator
599 .as_ref()
600 .map(|generator| generator.generate_id(entity))
601 .transpose()
602 }
603
604 pub fn next_id(&self, entity: &str) -> Result<u64, RuntimeError> {
605 match self.generate_id(entity)? {
606 Some(id) => Ok(id),
607 None => local_id_generator().generate_id(entity),
608 }
609 }
610
611 pub fn entity(&self, name: &str) -> Option<&EntityDescriptor> {
612 self.metadata
613 .as_ref()
614 .and_then(|metadata| metadata.entity(name))
615 }
616
617 pub fn all_entities(&self) -> Vec<&EntityDescriptor> {
618 self.metadata
619 .as_ref()
620 .map(|metadata| metadata.all_entities())
621 .unwrap_or_default()
622 }
623
624 pub fn require_entity(&self, name: &str) -> Result<&EntityDescriptor, RuntimeError> {
625 self.entity(name)
626 .ok_or_else(|| RuntimeError::MissingEntity(name.to_owned()))
627 }
628
629 pub fn insert_resource<T>(&mut self, resource: T)
630 where
631 T: Send + Sync + 'static,
632 {
633 self.typed_resources
634 .insert(TypeId::of::<T>(), Box::new(resource));
635 }
636
637 pub fn get_resource<T>(&self) -> Option<&T>
638 where
639 T: Send + Sync + 'static,
640 {
641 self.typed_resources
642 .get(&TypeId::of::<T>())
643 .and_then(|value| value.downcast_ref::<T>())
644 }
645
646 pub fn require_resource<T>(&self) -> Result<&T, ContextError>
647 where
648 T: Send + Sync + 'static,
649 {
650 self.get_resource::<T>()
651 .ok_or(ContextError::MissingTypedResource(
652 std::any::type_name::<T>(),
653 ))
654 }
655
656 pub fn insert_named_resource<T>(&mut self, name: impl Into<String>, resource: T)
657 where
658 T: Send + Sync + 'static,
659 {
660 self.named_resources.insert(name.into(), Box::new(resource));
661 }
662
663 pub fn get_named_resource<T>(&self, name: &str) -> Option<&T>
664 where
665 T: Send + Sync + 'static,
666 {
667 self.named_resources
668 .get(name)
669 .and_then(|value| value.downcast_ref::<T>())
670 }
671
672 pub fn require_named_resource<T>(&self, name: &str) -> Result<&T, ContextError>
673 where
674 T: Send + Sync + 'static,
675 {
676 self.get_named_resource::<T>(name)
677 .ok_or_else(|| ContextError::MissingResource(name.to_owned()))
678 }
679
680 pub fn put_local(&mut self, key: impl Into<String>, value: impl Into<Value>) {
681 self.locals.insert(key.into(), value.into());
682 }
683
684 pub fn local(&self, key: &str) -> Option<&Value> {
685 self.locals.get(key)
686 }
687
688 pub fn remove_local(&mut self, key: &str) -> Option<Value> {
689 self.locals.remove(key)
690 }
691
692 pub fn has_entity_data_service(&self, entity: &str) -> bool {
693 let in_registry = self
694 .entity_registry
695 .as_ref()
696 .map(|registry| registry.contains(entity))
697 .unwrap_or(false);
698 in_registry || self.entity(entity).is_some()
699 }
700
701 pub fn entity_data_service_behavior(
702 &self,
703 entity: &str,
704 ) -> Option<std::sync::Arc<dyn EntityDataServiceBehavior>> {
705 self.entity_data_service_behavior_registry
706 .as_ref()
707 .and_then(|registry| registry.behavior(entity))
708 }
709
710 pub fn has_checker(&self, entity: &str) -> bool {
711 self.checker_registry
712 .as_ref()
713 .and_then(|registry| registry.checker(entity))
714 .is_some()
715 }
716
717 pub fn check_and_fix_record(
718 &self,
719 entity: &str,
720 record: &mut Record,
721 ) -> Result<(), RuntimeError> {
722 self.check_and_fix_record_at(entity, record, &ObjectLocation::root())
723 }
724
725 pub fn check_and_fix_record_at(
726 &self,
727 entity: &str,
728 record: &mut Record,
729 location: &ObjectLocation,
730 ) -> Result<(), RuntimeError> {
731 let Some(checker) = self
732 .checker_registry
733 .as_ref()
734 .and_then(|registry| registry.checker(entity))
735 else {
736 return Ok(());
737 };
738 let mut results = CheckResults::new();
739 checker.check_and_fix(self, record, location, &mut results);
740 if results.is_empty() {
741 Ok(())
742 } else {
743 self.translate_check_results(&mut results);
744 Err(RuntimeError::Check(results))
745 }
746 }
747
748 pub fn translate_check_results(&self, results: &mut CheckResults) {
749 for result in results {
750 result.message = Some(translate_check_result(self.language, result));
751 }
752 }
753
754 pub fn send_event(&self, event: RawAuditEvent) -> Result<(), RuntimeError> {
755 if let Some(sink) = self.event_sink.as_ref() {
756 sink.on_event(self, &event)?;
757 }
758 if let Some(sink) = self.custom_event_sink.as_ref() {
759 let (mask_fields, max_len) = if let Some(metadata) = &self.metadata {
760 if let Some(desc) = metadata.entity(&event.entity) {
761 (desc.audit_mask_fields.clone(), desc.audit_value_max_len)
762 } else {
763 (vec![], None)
764 }
765 } else {
766 (vec![], None)
767 };
768
769 let safe_event = event.build_safe_event(&mask_fields, max_len);
770 sink.on_safe_event(self, &safe_event)?;
771 }
772
773 crate::log_formatter::LogManager::write_audit_log(&event);
774
775 Ok(())
776 }
777
778 pub async fn commit_changes<E>(&self) -> Result<(), DataServiceError<E::Error>>
779 where
780 E: teaql_data_service::MutationExecutor + Send + Sync + 'static,
781 {
782 let executor = self.require_resource::<E>().map_err(|err| {
783 DataServiceError::Runtime(RuntimeError::Graph(format!(
784 "cannot commit changes without executor: {err}"
785 )))
786 })?;
787 let change_set = self.entity_root.current_change_set();
788
789 for (key, changes) in change_set.changes() {
790 if changes.is_empty() {
791 continue;
792 }
793 let _entity = self
794 .require_entity(&key.entity)
795 .map_err(DataServiceError::Runtime)?;
796 let mut command = UpdateCommand::new(&key.entity, key.id.clone());
797 for (field, value) in changes {
798 command = command.value(field.clone(), value.clone());
799 }
800 let request = teaql_data_service::MutationRequest::Update(command);
801 executor
802 .mutate(request)
803 .await
804 .map_err(DataServiceError::Executor)?;
805 }
806
807 self.entity_root.clear_current_change_set();
808 Ok(())
809 }
810
811 pub async fn get_in_store(&self, key: &str) -> Option<Value> {
812 if let Some(store) = self.get_resource::<Box<dyn DataStore>>() {
813 store.get(key).await
814 } else {
815 None
816 }
817 }
818
819 pub async fn put_in_store(
820 &self,
821 key: &str,
822 value: impl Into<Value>,
823 timeout_seconds: Option<u64>,
824 ) {
825 if let Some(store) = self.get_resource::<Box<dyn DataStore>>() {
826 store.put(key, value.into(), timeout_seconds).await;
827 }
828 }
829
830 pub async fn clear_in_store(&self, key: &str) {
831 if let Some(store) = self.get_resource::<Box<dyn DataStore>>() {
832 store.remove(key).await;
833 }
834 }
835}
836
837fn extract_id_from_sql(sql: &str) -> Option<String> {
838 let sql_lower = sql.to_lowercase();
839 let where_idx = sql_lower.find("where")?;
840 let where_clause = &sql_lower[where_idx + 5..];
841
842 let bytes = where_clause.as_bytes();
843 let mut i = 0;
844 while i < bytes.len() {
845 if i + 1 < bytes.len() && &bytes[i..i + 2] == b"id" {
846 let prev_ok = if i == 0 {
848 true
849 } else {
850 let prev_char = bytes[i - 1] as char;
851 !prev_char.is_ascii_alphanumeric() && prev_char != '_' && prev_char != '.'
852 };
853 let next_ok = if i + 2 == bytes.len() {
855 true
856 } else {
857 let next_char = bytes[i + 2] as char;
858 !next_char.is_ascii_alphanumeric() && next_char != '_'
859 };
860
861 if prev_ok && next_ok {
862 let mut j = i + 2;
865 while j < bytes.len() && (bytes[j] as char).is_whitespace() {
866 j += 1;
867 }
868 if j < bytes.len() && bytes[j] == b'=' {
869 j += 1;
870 while j < bytes.len() && (bytes[j] as char).is_whitespace() {
871 j += 1;
872 }
873 let mut val_str = String::new();
875 if j < bytes.len() && bytes[j] == b'\'' {
876 j += 1; while j < bytes.len() && bytes[j] != b'\'' {
878 val_str.push(bytes[j] as char);
879 j += 1;
880 }
881 return Some(val_str);
882 } else {
883 while j < bytes.len() {
884 let c = bytes[j] as char;
885 if c.is_ascii_alphanumeric() || c == '_' || c == '-' {
886 val_str.push(c);
887 j += 1;
888 } else {
889 break;
890 }
891 }
892 if !val_str.is_empty() {
893 return Some(val_str);
894 }
895 }
896 }
897 }
898 }
899 i += 1;
900 }
901 None
902}
903
904fn sql_result_summary(
905 operation: SqlLogOperation,
906 result_count: Option<usize>,
907 result_type: Option<&str>,
908 affected_rows: Option<u64>,
909 debug_sql: &str,
910) -> String {
911 match operation {
912 SqlLogOperation::Select => {
913 let count = result_count.unwrap_or(0);
914 if count == 0 {
915 "MISS".to_owned()
916 } else if count > 1 {
917 match result_type {
918 Some(result_type) => format!("{count}*{result_type}"),
919 None => format!("{count}*rows"),
920 }
921 } else {
922 match result_type {
923 Some(result_type) => {
924 if let Some(id) = extract_id_from_sql(debug_sql) {
925 format!("{result_type}({id})")
926 } else {
927 result_type.to_owned()
928 }
929 }
930 None => "row".to_owned(),
931 }
932 }
933 }
934 _ => {
935 let affected = affected_rows.unwrap_or(0);
936 format!("{affected} UPDATED")
937 }
938 }
939}
940
941fn pretty_sql(sql: &str) -> String {
942 let mut pretty = sql.to_owned();
943 for keyword in [
944 " FROM ",
945 " WHERE ",
946 " GROUP BY ",
947 " HAVING ",
948 " ORDER BY ",
949 " LIMIT ",
950 " OFFSET ",
951 " RETURNING ",
952 ] {
953 pretty = pretty.replace(keyword, &format!("\n{}", keyword.trim_start()));
954 }
955 pretty.replace(" AND ", "\n AND ")
956}