Skip to main content

amaters_net/
server.rs

1//! gRPC server implementation for AmateRS AQL Service
2//!
3//! This module provides the server implementation that connects the network layer
4//! with the storage engine to handle client requests.
5
6use crate::convert::{cipher_blob_to_proto, create_version, key_to_proto, query_from_proto};
7use crate::error::{NetError, NetResult};
8use crate::proto::{aql, query};
9use crate::server_admin::{LogEntry, push_log_entry};
10use amaters_core::Query;
11use amaters_core::Update as UpdateOp;
12use amaters_core::traits::StorageEngine;
13use amaters_core::types::{CipherBlob, Key};
14use futures::StreamExt;
15use parking_lot::RwLock;
16use std::collections::VecDeque;
17use std::sync::Arc;
18use std::time::Instant;
19use tracing::{debug, error, info, warn};
20
21#[cfg(feature = "compute")]
22use crate::circuit_cache::{CircuitCache, CircuitCacheConfig};
23#[cfg(feature = "compute")]
24use amaters_core::compute::{FheExecutor, KeyManager, PredicateCompiler};
25#[cfg(feature = "compute")]
26use std::collections::HashMap;
27
28/// AQL service implementation
29///
30/// This service handles all AQL query requests and connects them to the underlying storage engine.
31pub struct AqlServiceImpl<S: StorageEngine> {
32    /// Storage engine for executing queries
33    storage: Arc<S>,
34    /// Server start time for uptime calculation
35    start_time: Instant,
36    /// Ring buffer for recent log entries (capacity: 256).
37    recent_log: Arc<RwLock<VecDeque<LogEntry>>>,
38    /// FHE key manager for encrypted operations
39    #[cfg(feature = "compute")]
40    key_manager: Arc<KeyManager>,
41    /// LRU cache of compiled FHE circuits, keyed by predicate identity.
42    ///
43    /// Shared across requests via `Arc`-backed clone semantics.  Re-using a
44    /// previously compiled circuit skips `PredicateCompiler::compile`, which
45    /// is the dominant CPU cost in the FHE filter/update path.
46    #[cfg(feature = "compute")]
47    circuit_cache: CircuitCache,
48}
49
50impl<S: StorageEngine> AqlServiceImpl<S> {
51    /// Create a new AQL service with the given storage engine
52    #[cfg(feature = "compute")]
53    pub fn new(storage: Arc<S>) -> Self {
54        Self {
55            storage,
56            start_time: Instant::now(),
57            recent_log: Arc::new(RwLock::new(VecDeque::new())),
58            key_manager: Arc::new(KeyManager::new()),
59            circuit_cache: CircuitCache::new(CircuitCacheConfig::default()),
60        }
61    }
62
63    /// Create a new AQL service with the given storage engine (without compute)
64    #[cfg(not(feature = "compute"))]
65    pub fn new(storage: Arc<S>) -> Self {
66        Self {
67            storage,
68            start_time: Instant::now(),
69            recent_log: Arc::new(RwLock::new(VecDeque::new())),
70        }
71    }
72
73    /// Create a new AQL service with a custom key manager
74    #[cfg(feature = "compute")]
75    pub fn with_key_manager(storage: Arc<S>, key_manager: Arc<KeyManager>) -> Self {
76        Self {
77            storage,
78            start_time: Instant::now(),
79            recent_log: Arc::new(RwLock::new(VecDeque::new())),
80            key_manager,
81            circuit_cache: CircuitCache::new(CircuitCacheConfig::default()),
82        }
83    }
84
85    /// Execute a query and return the result
86    pub async fn execute_query(&self, request: aql::QueryRequest) -> aql::QueryResponse {
87        let start_time = Instant::now();
88
89        info!(
90            "ExecuteQuery request received: request_id={:?}",
91            request.request_id
92        );
93
94        // Extract and validate the query
95        let proto_query = match request.query {
96            Some(q) => q,
97            None => {
98                let execution_time_ms = start_time.elapsed().as_millis() as u64;
99                return aql::QueryResponse {
100                    response: Some(aql::query_response::Response::Error(
101                        crate::proto::errors::ErrorResponse {
102                            code: crate::proto::errors::ErrorCode::ErrorProtocolMissingField as i32,
103                            message: "Missing query in request".to_string(),
104                            category: crate::proto::errors::ErrorCategory::CategoryClientError
105                                as i32,
106                            details: None,
107                            retry_after: None,
108                        },
109                    )),
110                    request_id: request.request_id,
111                    execution_time_ms,
112                };
113            }
114        };
115
116        let query = match query_from_proto(proto_query) {
117            Ok(q) => q,
118            Err(e) => {
119                error!("Failed to parse query: {}", e);
120                let execution_time_ms = start_time.elapsed().as_millis() as u64;
121                return aql::QueryResponse {
122                    response: Some(aql::query_response::Response::Error(
123                        crate::proto::errors::ErrorResponse {
124                            code: e.error_code() as i32,
125                            message: e.to_string(),
126                            category: e.error_category() as i32,
127                            details: None,
128                            retry_after: None,
129                        },
130                    )),
131                    request_id: request.request_id,
132                    execution_time_ms,
133                };
134            }
135        };
136
137        // Span for distributed tracing (OTel-compatible field names).
138        // Using `.instrument(span)` rather than `.entered()` to keep the future `Send`.
139        let span = tracing::info_span!(
140            "amaters.execute_query",
141            "amaters.query.type" = query_type_name(&query),
142            "amaters.collection" = collection_name(&query),
143            "amaters.fhe" = uses_fhe(&query),
144        );
145
146        // Execute the query
147        let result = {
148            use tracing::Instrument as _;
149            self.execute_query_internal(query).instrument(span).await
150        };
151
152        let execution_time_ms = start_time.elapsed().as_millis() as u64;
153
154        // Build response
155        let response = match result {
156            Ok(query_result) => aql::QueryResponse {
157                response: Some(aql::query_response::Response::Result(query_result)),
158                request_id: request.request_id,
159                execution_time_ms,
160            },
161            Err(e) => {
162                error!("Query execution failed: {}", e);
163                push_log_entry(
164                    &self.recent_log,
165                    format!("ExecuteQuery elapsed={}ms error={}", execution_time_ms, e),
166                );
167                return aql::QueryResponse {
168                    response: Some(aql::query_response::Response::Error(
169                        crate::proto::errors::ErrorResponse {
170                            code: e.error_code() as i32,
171                            message: e.to_string(),
172                            category: e.error_category() as i32,
173                            details: None,
174                            retry_after: None,
175                        },
176                    )),
177                    request_id: request.request_id,
178                    execution_time_ms,
179                };
180            }
181        };
182        push_log_entry(
183            &self.recent_log,
184            format!("ExecuteQuery elapsed={}ms ok", execution_time_ms),
185        );
186        response
187    }
188
189    /// Execute a query and return the result
190    ///
191    /// This is an internal method used for testing and direct query execution.
192    /// For production use, prefer `execute_query` which handles protocol details.
193    #[doc(hidden)]
194    #[tracing::instrument(skip(self), fields(trace_id = tracing::field::Empty, duration_us = tracing::field::Empty))]
195    pub async fn execute_query_internal(&self, query: Query) -> NetResult<query::QueryResult> {
196        match query {
197            Query::Get { collection, key } => {
198                debug!(
199                    "Executing GET query: collection={}, key={:?}",
200                    collection, key
201                );
202
203                // Intercept __admin__:<command> keys and dispatch to built-in handlers.
204                // The CLI encodes admin commands as Get queries with a special key prefix so
205                // that the admin wire protocol works over the existing gRPC path without a
206                // dedicated RPC.  Keys that are not admin commands fall through to storage as
207                // normal.
208                let key_str = key.to_string_lossy();
209                if let Some(admin_cmd) = key_str.strip_prefix("__admin__:") {
210                    if let Some(json) = self.handle_admin_command(admin_cmd).await {
211                        let blob = CipherBlob::new(json.into_bytes());
212                        return Ok(query::QueryResult {
213                            result: Some(query::query_result::Result::Single(
214                                query::SingleResult {
215                                    value: Some(cipher_blob_to_proto(&blob)),
216                                },
217                            )),
218                        });
219                    }
220                    // Unrecognised admin command — return None so CLI falls back to mock data.
221                    return Ok(query::QueryResult {
222                        result: Some(query::query_result::Result::Single(query::SingleResult {
223                            value: None,
224                        })),
225                    });
226                }
227
228                let result = self.storage.get(&key).await?;
229
230                let result = match result {
231                    Some(value) => query::QueryResult {
232                        result: Some(query::query_result::Result::Single(query::SingleResult {
233                            value: Some(cipher_blob_to_proto(&value)),
234                        })),
235                    },
236                    None => query::QueryResult {
237                        result: Some(query::query_result::Result::Single(query::SingleResult {
238                            value: None,
239                        })),
240                    },
241                };
242
243                Ok(result)
244            }
245            Query::Set {
246                collection,
247                key,
248                value,
249            } => {
250                debug!(
251                    "Executing SET query: collection={}, key={:?}",
252                    collection, key
253                );
254
255                self.storage.put(&key, &value).await?;
256
257                Ok(query::QueryResult {
258                    result: Some(query::query_result::Result::Success(query::SuccessResult {
259                        affected_rows: 1,
260                    })),
261                })
262            }
263            Query::Delete { collection, key } => {
264                debug!(
265                    "Executing DELETE query: collection={}, key={:?}",
266                    collection, key
267                );
268
269                self.storage.delete(&key).await?;
270
271                Ok(query::QueryResult {
272                    result: Some(query::query_result::Result::Success(query::SuccessResult {
273                        affected_rows: 1,
274                    })),
275                })
276            }
277            Query::Range {
278                collection,
279                start,
280                end,
281            } => {
282                debug!(
283                    "Executing RANGE query: collection={}, start={:?}, end={:?}",
284                    collection, start, end
285                );
286
287                let results = self.storage.range(&start, &end).await?;
288
289                let values: Vec<query::KeyValue> = results
290                    .into_iter()
291                    .map(|(k, v)| query::KeyValue {
292                        key: Some(key_to_proto(&k)),
293                        value: Some(cipher_blob_to_proto(&v)),
294                        encrypted_predicate_result: None,
295                    })
296                    .collect();
297
298                Ok(query::QueryResult {
299                    result: Some(query::query_result::Result::Multi(query::MultiResult {
300                        values,
301                    })),
302                })
303            }
304            Query::Filter {
305                collection,
306                predicate,
307            } => {
308                #[cfg(not(feature = "compute"))]
309                {
310                    let _ = (collection, predicate);
311                    return Err(NetError::ServerInternal(
312                        "FILTER queries require the compute feature".to_string(),
313                    ));
314                }
315
316                #[cfg(feature = "compute")]
317                {
318                    // Retrieve all candidate rows for the collection via full range scan.
319                    let min_key = Key::from_slice(&[]);
320                    let max_key = Key::from_slice(&[0xFF; 256]);
321
322                    let all_rows = match self.storage.range(&min_key, &max_key).await {
323                        Ok(rows) => rows,
324                        Err(e) => {
325                            error!("Failed to retrieve rows for filter: {}", e);
326                            return Err(NetError::from(e));
327                        }
328                    };
329
330                    debug!("Filter: retrieved {} candidate rows", all_rows.len());
331
332                    if all_rows.len() > 1000 {
333                        warn!(
334                            "Filter query retrieved {} rows, which may cause performance issues",
335                            all_rows.len()
336                        );
337                    }
338
339                    // Probe the first row to decide between plaintext and FHE mode.
340                    // If evaluate_plaintext returns Some(_) for the first value, all
341                    // values are assumed to be plaintext; the server filters in-place.
342                    // If it returns None (FHE ciphertext detected), fall through to FHE.
343                    let first_is_plaintext = all_rows
344                        .first()
345                        .map(|(_, v)| predicate.evaluate_plaintext(v).is_some())
346                        .unwrap_or(true); // empty collection → treat as plaintext (return empty)
347
348                    if first_is_plaintext {
349                        info!(
350                            "Executing FILTER query with server-side plaintext predicate evaluation"
351                        );
352
353                        let mut results = Vec::new();
354                        let mut excluded: usize = 0;
355
356                        for (key, value_blob) in all_rows {
357                            match predicate.evaluate_plaintext(&value_blob) {
358                                Some(true) => {
359                                    results.push(query::KeyValue {
360                                        key: Some(key_to_proto(&key)),
361                                        value: Some(cipher_blob_to_proto(&value_blob)),
362                                        encrypted_predicate_result: None,
363                                    });
364                                }
365                                Some(false) => {
366                                    // Row does not match predicate; skip it.
367                                    excluded += 1;
368                                }
369                                None => {
370                                    // Mid-collection the encoding switched away from plaintext.
371                                    // Include the row conservatively (unknown state).
372                                    warn!(
373                                        "Plaintext evaluation returned None for key {:?} mid-scan; \
374                                         including row conservatively",
375                                        key
376                                    );
377                                    results.push(query::KeyValue {
378                                        key: Some(key_to_proto(&key)),
379                                        value: Some(cipher_blob_to_proto(&value_blob)),
380                                        encrypted_predicate_result: None,
381                                    });
382                                }
383                            }
384                        }
385
386                        info!(
387                            "FILTER query completed: {} rows matched, {} rows excluded by plaintext predicate",
388                            results.len(),
389                            excluded
390                        );
391
392                        return Ok(query::QueryResult {
393                            result: Some(query::query_result::Result::Multi(query::MultiResult {
394                                values: results,
395                            })),
396                        });
397                    }
398
399                    // FHE path — values are ciphertexts, use homomorphic evaluation.
400                    info!("Executing FILTER query with FHE predicate evaluation");
401
402                    // Key isolation: Both `PredicateCompiler` and `FheExecutor` are
403                    // created as stack-local values for each filter call. This means
404                    // concurrent filter requests do not share mutable compiler or
405                    // executor state, providing per-request isolation without
406                    // additional synchronisation overhead.
407
408                    // 1. Compile predicate to FHE circuit (cache-first).
409                    //
410                    // The circuit_cache memoises compilation keyed on the predicate's
411                    // debug representation hashed with blake3.  Repeated filter
412                    // queries with the same predicate skip recompilation entirely.
413                    let circuit = match self.circuit_cache.get_or_compile(&predicate, || {
414                        let mut compiler = PredicateCompiler::new();
415                        // For now, assume U8 type - in production, this should be
416                        // inferred from the data or provided by the client.
417                        compiler.compile(&predicate, amaters_core::compute::EncryptedType::U8)
418                    }) {
419                        Ok(c) => c,
420                        Err(e) => {
421                            error!("Failed to compile predicate: {}", e);
422                            return Err(NetError::ServerInternal(format!(
423                                "Predicate compilation failed: {}",
424                                e
425                            )));
426                        }
427                    };
428
429                    debug!(
430                        "Compiled predicate circuit: depth={}, gates={}",
431                        circuit.depth, circuit.gate_count
432                    );
433
434                    // 2. Extract RHS value from predicate
435                    let rhs = match PredicateCompiler::extract_rhs_value(&predicate) {
436                        Ok(r) => r,
437                        Err(e) => {
438                            error!("Failed to extract RHS value: {}", e);
439                            return Err(NetError::ServerInternal(format!(
440                                "RHS extraction failed: {}",
441                                e
442                            )));
443                        }
444                    };
445
446                    // 3. Set up FHE executor (per-request instance for isolation)
447                    let executor = FheExecutor::new();
448
449                    // 4. Execute circuit on each row and populate encrypted_predicate_result.
450                    // The client decrypts the encrypted boolean to learn which rows matched.
451                    let mut results = Vec::new();
452                    let mut execution_errors = 0;
453
454                    for (key, value_blob) in all_rows {
455                        // Build inputs: value from storage + RHS from predicate
456                        let mut inputs = HashMap::new();
457                        inputs.insert("value".to_string(), value_blob.clone());
458                        inputs.insert("rhs".to_string(), rhs.clone());
459
460                        // Execute FHE circuit - result is encrypted boolean
461                        // Catch execution errors and continue processing other rows
462                        match executor.execute(&circuit, &inputs) {
463                            Ok(result_blob) => {
464                                let result_bytes = result_blob.as_bytes().to_vec();
465
466                                debug!(
467                                    "Executed predicate on key {:?}, result blob size: {}",
468                                    key,
469                                    result_bytes.len()
470                                );
471
472                                results.push(query::KeyValue {
473                                    key: Some(key_to_proto(&key)),
474                                    value: Some(cipher_blob_to_proto(&value_blob)),
475                                    encrypted_predicate_result: Some(result_bytes),
476                                });
477                            }
478                            Err(e) => {
479                                execution_errors += 1;
480                                warn!("FHE execution failed for key {:?}: {}", key, e);
481                                // Continue processing other rows instead of failing the entire query
482                            }
483                        }
484                    }
485
486                    if execution_errors > 0 {
487                        warn!(
488                            "Filter query had {} FHE execution errors out of {} total rows",
489                            execution_errors,
490                            execution_errors + results.len()
491                        );
492                    }
493
494                    info!(
495                        "FILTER query completed, processed {} rows successfully",
496                        results.len()
497                    );
498
499                    Ok(query::QueryResult {
500                        result: Some(query::query_result::Result::Multi(query::MultiResult {
501                            values: results,
502                        })),
503                    })
504                }
505            }
506            Query::Update {
507                collection,
508                predicate,
509                updates,
510            } => {
511                debug!(
512                    "Executing UPDATE query: collection={}, updates_count={}",
513                    collection,
514                    updates.len()
515                );
516
517                #[cfg(feature = "compute")]
518                {
519                    // With compute feature: compile predicate (cache-first) and
520                    // evaluate against each row to determine which rows should
521                    // be updated.  A cached circuit is reused if the same
522                    // predicate was compiled in a previous filter or update.
523                    let circuit = match self.circuit_cache.get_or_compile(&predicate, || {
524                        let mut compiler = PredicateCompiler::new();
525                        compiler.compile(&predicate, amaters_core::compute::EncryptedType::U8)
526                    }) {
527                        Ok(c) => c,
528                        Err(e) => {
529                            error!("Failed to compile update predicate: {}", e);
530                            return Err(NetError::ServerInternal(format!(
531                                "Update predicate compilation failed: {}",
532                                e
533                            )));
534                        }
535                    };
536
537                    let rhs = match PredicateCompiler::extract_rhs_value(&predicate) {
538                        Ok(r) => r,
539                        Err(e) => {
540                            error!("Failed to extract RHS value for update predicate: {}", e);
541                            return Err(NetError::ServerInternal(format!(
542                                "Update RHS extraction failed: {}",
543                                e
544                            )));
545                        }
546                    };
547
548                    let executor = FheExecutor::new();
549
550                    // Get all candidate rows
551                    let min_key = Key::from_slice(&[]);
552                    let max_key = Key::from_slice(&[0xFF; 256]);
553                    let all_rows = self.storage.range(&min_key, &max_key).await?;
554
555                    let mut affected_rows: u64 = 0;
556
557                    for (key, value_blob) in &all_rows {
558                        // Build inputs for predicate evaluation
559                        let mut inputs = HashMap::new();
560                        inputs.insert("value".to_string(), value_blob.clone());
561                        inputs.insert("rhs".to_string(), rhs.clone());
562
563                        // Evaluate predicate; on error skip this row
564                        let matches = match executor.execute(&circuit, &inputs) {
565                            Ok(result_blob) => {
566                                // Check if result is truthy (any non-zero byte)
567                                result_blob.as_bytes().iter().any(|&b| b != 0)
568                            }
569                            Err(e) => {
570                                warn!("FHE predicate evaluation failed for key {:?}: {}", key, e);
571                                continue;
572                            }
573                        };
574
575                        if !matches {
576                            continue;
577                        }
578
579                        // Apply updates to matching row
580                        let mut current_value = value_blob.clone();
581                        for update_op in &updates {
582                            current_value = apply_update_operation(&current_value, update_op);
583                        }
584
585                        self.storage.put(key, &current_value).await?;
586                        affected_rows += 1;
587                    }
588
589                    info!(
590                        "UPDATE query completed: {} rows affected out of {} total",
591                        affected_rows,
592                        all_rows.len()
593                    );
594
595                    Ok(query::QueryResult {
596                        result: Some(query::query_result::Result::Success(query::SuccessResult {
597                            affected_rows,
598                        })),
599                    })
600                }
601
602                #[cfg(not(feature = "compute"))]
603                {
604                    // Without compute feature: apply updates to ALL rows in the collection.
605                    // We cannot evaluate predicates without FHE support, so we treat
606                    // the update as unconditional.
607                    let _ = predicate;
608
609                    let all_keys = self.storage.keys().await?;
610
611                    if all_keys.is_empty() {
612                        info!(
613                            "UPDATE query on collection '{}': no keys found, 0 rows affected",
614                            collection
615                        );
616                        return Ok(query::QueryResult {
617                            result: Some(query::query_result::Result::Success(
618                                query::SuccessResult { affected_rows: 0 },
619                            )),
620                        });
621                    }
622
623                    let mut affected_rows: u64 = 0;
624
625                    for key in &all_keys {
626                        let value_opt = self.storage.get(key).await?;
627                        let current_value = match value_opt {
628                            Some(v) => v,
629                            None => continue,
630                        };
631
632                        let mut updated_value = current_value;
633                        for update_op in &updates {
634                            updated_value = apply_update_operation(&updated_value, update_op);
635                        }
636
637                        self.storage.put(key, &updated_value).await?;
638                        affected_rows += 1;
639                    }
640
641                    info!(
642                        "UPDATE query completed: {} rows affected in collection '{}'",
643                        affected_rows, collection
644                    );
645
646                    Ok(query::QueryResult {
647                        result: Some(query::query_result::Result::Success(query::SuccessResult {
648                            affected_rows,
649                        })),
650                    })
651                }
652            }
653            Query::Join { .. } => Err(NetError::ServerInternal(
654                "Join queries are not yet supported server-side".to_string(),
655            )),
656        }
657    }
658
659    /// Execute a batch of queries as a transaction
660    ///
661    /// All queries are executed sequentially. If any query fails, all previously
662    /// completed write operations (Set/Delete) are rolled back, and an error
663    /// response is returned. Read-only operations (Get/Range) are not tracked
664    /// for rollback since they don't mutate state.
665    #[tracing::instrument(skip(self, request), fields(trace_id = tracing::field::Empty, query_count = request.queries.len(), duration_us = tracing::field::Empty))]
666    pub async fn execute_batch(&self, request: aql::BatchRequest) -> aql::BatchResponse {
667        let start_time = Instant::now();
668
669        info!(
670            "ExecuteBatch request received: request_id={:?}, query_count={}",
671            request.request_id,
672            request.queries.len()
673        );
674
675        // Handle empty batch
676        if request.queries.is_empty() {
677            let execution_time_ms = start_time.elapsed().as_millis() as u64;
678            return aql::BatchResponse {
679                response: Some(aql::batch_response::Response::Results(aql::BatchResult {
680                    results: Vec::new(),
681                })),
682                request_id: request.request_id,
683                execution_time_ms,
684            };
685        }
686
687        let mut results = Vec::with_capacity(request.queries.len());
688        let mut rollback_ops: Vec<RollbackOp> = Vec::new();
689
690        for (idx, proto_query) in request.queries.into_iter().enumerate() {
691            // Convert proto query to core query
692            let core_query = match query_from_proto(proto_query) {
693                Ok(q) => q,
694                Err(e) => {
695                    error!("Failed to parse query {} in batch: {}", idx, e);
696                    // Rollback all completed write operations
697                    self.rollback_operations(&rollback_ops).await;
698                    let execution_time_ms = start_time.elapsed().as_millis() as u64;
699                    push_log_entry(
700                        &self.recent_log,
701                        format!(
702                            "ExecuteBatch elapsed={}ms error=parse_query_{}: {}",
703                            execution_time_ms, idx, e
704                        ),
705                    );
706                    return aql::BatchResponse {
707                        response: Some(aql::batch_response::Response::Error(
708                            crate::proto::errors::ErrorResponse {
709                                code: e.error_code() as i32,
710                                message: format!("Query {} in batch failed to parse: {}", idx, e),
711                                category: e.error_category() as i32,
712                                details: None,
713                                retry_after: None,
714                            },
715                        )),
716                        request_id: request.request_id,
717                        execution_time_ms,
718                    };
719                }
720            };
721
722            // Track rollback info before executing write operations
723            let rollback_op = self.build_rollback_op(&core_query).await;
724
725            match self.execute_query_internal(core_query).await {
726                Ok(query_result) => {
727                    // Record the rollback operation only after successful execution
728                    if let Some(op) = rollback_op {
729                        rollback_ops.push(op);
730                    }
731                    results.push(query_result);
732                }
733                Err(e) => {
734                    error!("Query {} in batch failed: {}", idx, e);
735                    // Rollback all completed write operations
736                    self.rollback_operations(&rollback_ops).await;
737                    let execution_time_ms = start_time.elapsed().as_millis() as u64;
738                    push_log_entry(
739                        &self.recent_log,
740                        format!(
741                            "ExecuteBatch elapsed={}ms error=query_{}: {}",
742                            execution_time_ms, idx, e
743                        ),
744                    );
745                    return aql::BatchResponse {
746                        response: Some(aql::batch_response::Response::Error(
747                            crate::proto::errors::ErrorResponse {
748                                code: e.error_code() as i32,
749                                message: format!("Query {} in batch failed: {}", idx, e),
750                                category: e.error_category() as i32,
751                                details: None,
752                                retry_after: None,
753                            },
754                        )),
755                        request_id: request.request_id,
756                        execution_time_ms,
757                    };
758                }
759            }
760        }
761
762        let execution_time_ms = start_time.elapsed().as_millis() as u64;
763        info!(
764            "ExecuteBatch completed successfully: {} queries in {}ms",
765            results.len(),
766            execution_time_ms
767        );
768        push_log_entry(
769            &self.recent_log,
770            format!(
771                "ExecuteBatch elapsed={}ms queries={} ok",
772                execution_time_ms,
773                results.len()
774            ),
775        );
776
777        aql::BatchResponse {
778            response: Some(aql::batch_response::Response::Results(aql::BatchResult {
779                results,
780            })),
781            request_id: request.request_id,
782            execution_time_ms,
783        }
784    }
785
786    /// Build a rollback operation for a query (before executing it)
787    ///
788    /// For Set operations: save the old value (if any) so we can restore it
789    /// For Delete operations: save the current value so we can re-insert it
790    /// For Update operations: snapshot all current key-value pairs so we can restore them
791    /// For Get/Range/Filter: no rollback needed (read-only)
792    async fn build_rollback_op(&self, query: &Query) -> Option<RollbackOp> {
793        match query {
794            Query::Set { key, .. } => {
795                // Capture the old value before overwriting
796                let old_value = match self.storage.get(key).await {
797                    Ok(v) => v,
798                    Err(e) => {
799                        warn!("Failed to read old value for rollback tracking: {}", e);
800                        None
801                    }
802                };
803                Some(RollbackOp::UndoSet {
804                    key: key.clone(),
805                    old_value,
806                })
807            }
808            Query::Delete { key, .. } => {
809                // Capture the current value before deleting
810                let old_value = match self.storage.get(key).await {
811                    Ok(v) => v,
812                    Err(e) => {
813                        warn!("Failed to read value for rollback tracking: {}", e);
814                        None
815                    }
816                };
817                Some(RollbackOp::UndoDelete {
818                    key: key.clone(),
819                    old_value,
820                })
821            }
822            Query::Update { .. } => {
823                // Capture all current key-value pairs before the update modifies them
824                let keys = match self.storage.keys().await {
825                    Ok(k) => k,
826                    Err(e) => {
827                        warn!("Failed to list keys for update rollback tracking: {}", e);
828                        return Some(RollbackOp::UndoUpdate {
829                            snapshots: Vec::new(),
830                        });
831                    }
832                };
833                let mut snapshots = Vec::with_capacity(keys.len());
834                for key in &keys {
835                    let value = match self.storage.get(key).await {
836                        Ok(v) => v,
837                        Err(e) => {
838                            warn!(
839                                "Failed to read value for key {:?} during update rollback tracking: {}",
840                                key, e
841                            );
842                            None
843                        }
844                    };
845                    snapshots.push((key.clone(), value));
846                }
847                Some(RollbackOp::UndoUpdate { snapshots })
848            }
849            // Read-only operations don't need rollback
850            Query::Get { .. } | Query::Range { .. } | Query::Filter { .. } => None,
851            // Join is not yet executable server-side; no rollback needed
852            Query::Join { .. } => None,
853        }
854    }
855
856    /// Rollback completed write operations in reverse order
857    ///
858    /// Best-effort rollback: if a rollback operation itself fails, we log
859    /// a warning and continue rolling back remaining operations.
860    async fn rollback_operations(&self, ops: &[RollbackOp]) {
861        if ops.is_empty() {
862            return;
863        }
864
865        warn!("Rolling back {} operations due to batch failure", ops.len());
866
867        for (idx, op) in ops.iter().rev().enumerate() {
868            match op {
869                RollbackOp::UndoSet { key, old_value } => {
870                    match old_value {
871                        Some(value) => {
872                            // Restore the old value
873                            if let Err(e) = self.storage.put(key, value).await {
874                                error!(
875                                    "Rollback failed for UndoSet (restore) at index {}: {}",
876                                    idx, e
877                                );
878                            } else {
879                                debug!("Rolled back Set: restored old value for key {:?}", key);
880                            }
881                        }
882                        None => {
883                            // Key didn't exist before, so delete it
884                            if let Err(e) = self.storage.delete(key).await {
885                                error!(
886                                    "Rollback failed for UndoSet (delete) at index {}: {}",
887                                    idx, e
888                                );
889                            } else {
890                                debug!("Rolled back Set: deleted new key {:?}", key);
891                            }
892                        }
893                    }
894                }
895                RollbackOp::UndoDelete { key, old_value } => {
896                    if let Some(value) = old_value {
897                        // Re-insert the deleted value
898                        if let Err(e) = self.storage.put(key, value).await {
899                            error!("Rollback failed for UndoDelete at index {}: {}", idx, e);
900                        } else {
901                            debug!("Rolled back Delete: restored value for key {:?}", key);
902                        }
903                    }
904                    // If old_value was None, the key didn't exist before delete,
905                    // so nothing to restore
906                }
907                RollbackOp::UndoUpdate { snapshots } => {
908                    // First, collect all current keys so we can detect keys added by the update
909                    let current_keys = match self.storage.keys().await {
910                        Ok(k) => k,
911                        Err(e) => {
912                            error!(
913                                "Rollback failed for UndoUpdate at index {}: cannot list keys: {}",
914                                idx, e
915                            );
916                            continue;
917                        }
918                    };
919
920                    // Build a set of keys that existed before the update
921                    let snapshot_keys: std::collections::HashSet<&Key> =
922                        snapshots.iter().map(|(k, _)| k).collect();
923
924                    // Remove any keys that were created by the update (not in snapshot)
925                    for key in &current_keys {
926                        if !snapshot_keys.contains(key) {
927                            if let Err(e) = self.storage.delete(key).await {
928                                error!(
929                                    "Rollback failed for UndoUpdate (remove new key) at index {}: {}",
930                                    idx, e
931                                );
932                            } else {
933                                debug!("Rolled back Update: removed new key {:?}", key);
934                            }
935                        }
936                    }
937
938                    // Restore all snapshotted values
939                    for (key, old_value) in snapshots {
940                        match old_value {
941                            Some(value) => {
942                                if let Err(e) = self.storage.put(key, value).await {
943                                    error!(
944                                        "Rollback failed for UndoUpdate (restore) at index {}: {}",
945                                        idx, e
946                                    );
947                                } else {
948                                    debug!("Rolled back Update: restored value for key {:?}", key);
949                                }
950                            }
951                            None => {
952                                // Key existed in snapshot as None — delete it if it was created
953                                if let Err(e) = self.storage.delete(key).await {
954                                    error!(
955                                        "Rollback failed for UndoUpdate (delete) at index {}: {}",
956                                        idx, e
957                                    );
958                                }
959                            }
960                        }
961                    }
962                    debug!("Rolled back Update operation at index {}", idx);
963                }
964            }
965        }
966
967        info!("Rollback completed");
968    }
969
970    /// Execute a streaming query that returns results in chunks
971    ///
972    /// This method executes a range or filter query and returns results as a stream
973    /// of `StreamResponse` messages, each containing a batch of key-value pairs.
974    /// The chunk size controls how many items are included per message.
975    ///
976    /// # Arguments
977    /// * `request` - The query request to execute
978    /// * `config` - Streaming configuration (chunk size, max results, timeout)
979    ///
980    /// # Returns
981    /// A boxed stream of `Result<aql::StreamResponse, NetError>` messages
982    pub fn execute_stream(
983        &self,
984        request: aql::QueryRequest,
985        config: StreamConfig,
986    ) -> futures::stream::BoxStream<'static, Result<aql::StreamResponse, NetError>> {
987        use futures::StreamExt;
988
989        let storage = self.storage.clone();
990        let recent_log = self.recent_log.clone();
991        let request_id = request.request_id.clone();
992
993        let stream = async_stream::stream! {
994            let start_time = Instant::now();
995
996            info!(
997                "ExecuteStream request received: request_id={:?}, chunk_size={}",
998                request_id, config.chunk_size
999            );
1000
1001            // Extract and validate the query
1002            let proto_query = match request.query {
1003                Some(q) => q,
1004                None => {
1005                    yield Err(NetError::MissingField("query".to_string()));
1006                    return;
1007                }
1008            };
1009
1010            let core_query = match query_from_proto(proto_query) {
1011                Ok(q) => q,
1012                Err(e) => {
1013                    error!("Failed to parse stream query: {}", e);
1014                    yield Err(e);
1015                    return;
1016                }
1017            };
1018
1019            // Only Range queries are supported for streaming (they return multiple results)
1020            let results = match core_query {
1021                Query::Range { collection, start, end } => {
1022                    debug!(
1023                        "Executing streaming RANGE query: collection={}, start={:?}, end={:?}",
1024                        collection, start, end
1025                    );
1026                    match storage.range(&start, &end).await {
1027                        Ok(rows) => rows,
1028                        Err(e) => {
1029                            error!("Storage range query failed: {}", e);
1030                            yield Err(NetError::from(e));
1031                            return;
1032                        }
1033                    }
1034                }
1035                Query::Get { collection, key } => {
1036                    debug!(
1037                        "Executing streaming GET query: collection={}, key={:?}",
1038                        collection, key
1039                    );
1040                    match storage.get(&key).await {
1041                        Ok(Some(value)) => vec![(key, value)],
1042                        Ok(None) => Vec::new(),
1043                        Err(e) => {
1044                            error!("Storage get query failed: {}", e);
1045                            yield Err(NetError::from(e));
1046                            return;
1047                        }
1048                    }
1049                }
1050                _ => {
1051                    yield Err(NetError::InvalidRequest(
1052                        "Only Range and Get queries are supported for streaming".to_string(),
1053                    ));
1054                    return;
1055                }
1056            };
1057
1058            // Apply max_results limit if configured
1059            let results = if let Some(max) = config.max_results {
1060                if results.len() > max {
1061                    results.into_iter().take(max).collect::<Vec<_>>()
1062                } else {
1063                    results
1064                }
1065            } else {
1066                results
1067            };
1068
1069            let total_count = results.len();
1070
1071            // Check timeout before starting to stream
1072            if start_time.elapsed() > config.timeout {
1073                yield Err(NetError::Timeout(
1074                    "Query execution exceeded timeout before streaming began".to_string(),
1075                ));
1076                return;
1077            }
1078
1079            // Stream results in chunks
1080            let mut sequence: u64 = 0;
1081            let chunks_iter: Vec<Vec<(Key, CipherBlob)>> = results
1082                .chunks(config.chunk_size)
1083                .map(|c| c.to_vec())
1084                .collect();
1085            let total_chunks = chunks_iter.len();
1086
1087            for (chunk_idx, chunk) in chunks_iter.into_iter().enumerate() {
1088                // Check timeout for each chunk
1089                if start_time.elapsed() > config.timeout {
1090                    yield Err(NetError::Timeout(
1091                        format!("Streaming timed out at chunk {}/{}", chunk_idx + 1, total_chunks)
1092                    ));
1093                    return;
1094                }
1095
1096                let has_more = chunk_idx + 1 < total_chunks;
1097                let values: Vec<query::KeyValue> = chunk
1098                    .into_iter()
1099                    .map(|(k, v)| query::KeyValue {
1100                        key: Some(key_to_proto(&k)),
1101                        value: Some(cipher_blob_to_proto(&v)),
1102                        encrypted_predicate_result: None,
1103                    })
1104                    .collect();
1105
1106                yield Ok(aql::StreamResponse {
1107                    chunk: Some(aql::stream_response::Chunk::Batch(aql::StreamBatch {
1108                        values,
1109                        has_more,
1110                    })),
1111                    sequence,
1112                });
1113
1114                sequence += 1;
1115            }
1116
1117            // Send end marker
1118            yield Ok(aql::StreamResponse {
1119                chunk: Some(aql::stream_response::Chunk::End(aql::StreamEnd {
1120                    total_count: total_count as u64,
1121                })),
1122                sequence,
1123            });
1124
1125            let elapsed_ms = start_time.elapsed().as_millis() as u64;
1126            info!(
1127                "ExecuteStream completed: {} items in {} chunks, {}ms",
1128                total_count,
1129                total_chunks,
1130                elapsed_ms
1131            );
1132            push_log_entry(
1133                &recent_log,
1134                format!(
1135                    "ExecuteStream elapsed={}ms items={} chunks={} ok",
1136                    elapsed_ms, total_count, total_chunks
1137                ),
1138            );
1139        };
1140
1141        stream.boxed()
1142    }
1143
1144    /// Health check
1145    #[tracing::instrument(skip(self, _request))]
1146    pub async fn health_check(
1147        &self,
1148        _request: aql::HealthCheckRequest,
1149    ) -> aql::HealthCheckResponse {
1150        debug!("HealthCheck request received");
1151        push_log_entry(&self.recent_log, "HealthCheck ok".to_string());
1152
1153        aql::HealthCheckResponse {
1154            status: aql::HealthStatus::HealthServing as i32,
1155            message: Some("Service is healthy".to_string()),
1156        }
1157    }
1158
1159    /// Get server information
1160    #[tracing::instrument(skip(self, _request))]
1161    pub async fn get_server_info(
1162        &self,
1163        _request: aql::ServerInfoRequest,
1164    ) -> aql::ServerInfoResponse {
1165        debug!("GetServerInfo request received");
1166        push_log_entry(&self.recent_log, "GetServerInfo ok".to_string());
1167
1168        let mut capabilities = vec![
1169            "query.get".to_string(),
1170            "query.set".to_string(),
1171            "query.delete".to_string(),
1172            "query.range".to_string(),
1173            "query.update".to_string(),
1174        ];
1175
1176        #[cfg(feature = "compute")]
1177        capabilities.push("query.filter".to_string());
1178
1179        aql::ServerInfoResponse {
1180            version: Some(create_version()),
1181            supported_versions: vec![create_version()],
1182            capabilities,
1183            uptime_seconds: self.start_time.elapsed().as_secs(),
1184        }
1185    }
1186
1187    /// Handle a decoded admin command and return a JSON string if supported.
1188    ///
1189    /// Delegates to [`crate::server_admin::handle_admin_command`].  The
1190    /// interceptor in `execute_query_internal` remains here; only the logic
1191    /// moves to `admin.rs`.
1192    async fn handle_admin_command(&self, cmd: &str) -> Option<String> {
1193        crate::server_admin::handle_admin_command(
1194            cmd,
1195            self.start_time.elapsed().as_secs(),
1196            &self.recent_log,
1197            &self.storage,
1198        )
1199        .await
1200    }
1201}
1202
1203// ─── Tracing helper functions ─────────────────────────────────────────────────
1204
1205/// Returns the query type name for tracing/OTel spans
1206fn query_type_name(query: &Query) -> &'static str {
1207    match query {
1208        Query::Get { .. } => "Get",
1209        Query::Set { .. } => "Set",
1210        Query::Delete { .. } => "Delete",
1211        Query::Range { .. } => "Range",
1212        Query::Filter { .. } => "Filter",
1213        Query::Update { .. } => "Update",
1214        Query::Join { .. } => "Join",
1215    }
1216}
1217
1218/// Returns the collection name for tracing/OTel spans
1219fn collection_name(query: &Query) -> &str {
1220    match query {
1221        Query::Get { collection, .. } => collection,
1222        Query::Set { collection, .. } => collection,
1223        Query::Delete { collection, .. } => collection,
1224        Query::Range { collection, .. } => collection,
1225        Query::Filter { collection, .. } => collection,
1226        Query::Update { collection, .. } => collection,
1227        Query::Join { .. } => "",
1228    }
1229}
1230
1231/// Returns true if the query uses FHE computation
1232fn uses_fhe(query: &Query) -> bool {
1233    matches!(query, Query::Filter { .. } | Query::Update { .. })
1234}
1235
1236// `AqlServerBuilder` lives in `crate::server_builder`; re-export so existing
1237// callers can continue to write `crate::server::AqlServerBuilder`.
1238pub use crate::server_builder::AqlServerBuilder;
1239pub use crate::server_types::StreamConfig;
1240use crate::server_types::{RollbackOp, apply_update_operation};
1241
1242#[cfg(test)]
1243#[path = "server_tests.rs"]
1244mod tests;