Skip to main content

reddb_server/runtime/
impl_fast_path.rs

1//! Fast-path entity lookup, plan-cache invalidation, and DDL epoch.
2//!
3//! Extracted verbatim from `impl_core.rs` (impl_core slice 9/10, issue #1630).
4//! Houses the string-pattern `try_fast_entity_lookup`, `invalidate_plan_cache`,
5//! the monotonic `ddl_epoch` reader, and `clear_table_planner_stats`.
6use super::execution_context::entity_visible_under_current_snapshot;
7use super::*;
8
9impl RedDBRuntime {
10    /// Ultra-fast path: detect `SELECT * FROM table WHERE _entity_id = N` by string pattern
11    /// and execute it without SQL parsing or planning. Returns None if pattern doesn't match.
12    pub(crate) fn try_fast_entity_lookup(
13        &self,
14        query: &str,
15    ) -> Option<RedDBResult<RuntimeQueryResult>> {
16        // Budgeted reads use the normal typed execution path and checkpoints.
17        if self.resource_limits().read_execution_limits_enabled() {
18            return None;
19        }
20        // Pattern: "SELECT * FROM <table> WHERE _entity_id = <id>"
21        // or "SELECT * FROM <table> WHERE _entity_id =<id>"
22        let q = query.trim();
23        if !q.starts_with("SELECT") && !q.starts_with("select") {
24            return None;
25        }
26
27        // Find "WHERE _entity_id = " or "WHERE _entity_id ="
28        let where_pos = q
29            .find("WHERE _entity_id")
30            .or_else(|| q.find("where _entity_id"))?;
31        let after_field = &q[where_pos + 16..].trim_start(); // skip "WHERE _entity_id"
32        let after_eq = after_field.strip_prefix('=')?.trim_start();
33
34        // Parse the entity ID number
35        let id_str = after_eq.trim();
36        let entity_id: u64 = id_str.parse().ok()?;
37
38        // Extract table name: between "FROM " and " WHERE"
39        let from_pos = q.find("FROM ").or_else(|| q.find("from "))? + 5;
40        let table = q[from_pos..where_pos].trim();
41        if table.is_empty()
42            || table.contains(' ') && !table.contains(" AS ") && !table.contains(" as ")
43        {
44            return None; // complex query, fall through
45        }
46        let table_name = table.split_whitespace().next()?;
47
48        // Direct entity lookup — skips SQL parse, plan cache, result
49        // cache, view rewriter, RLS gate. Safe because the gating in
50        // `execute_query` guarantees no scope override / no
51        // transaction context is active. MVCC visibility is still
52        // honoured against the current snapshot.
53        let store = self.inner.db.store();
54        let entity = store
55            .get(
56                table_name,
57                crate::storage::unified::EntityId::new(entity_id),
58            )
59            .filter(entity_visible_under_current_snapshot)
60            .filter(|entity| {
61                self.inner
62                    .db
63                    .replica_allows_entity_at_read(table_name, entity)
64            });
65
66        let count = if entity.is_some() { 1u64 } else { 0 };
67
68        // Materialize a record so downstream consumers that walk
69        // `result.records` (embedded runtime API, decrypt pass, CLI)
70        // see the row. Previously only `pre_serialized_json` was
71        // filled, which caused those consumers to see zero rows and
72        // skewed benchmarks.
73        let records: Vec<crate::storage::query::unified::UnifiedRecord> = entity
74            .as_ref()
75            .and_then(|e| runtime_table_record_from_entity(e.clone()))
76            .into_iter()
77            .collect();
78
79        let json = match entity {
80            Some(ref e) => execute_runtime_serialize_single_entity(e),
81            None => r#"{"columns":[],"record_count":0,"selection":{"scope":"any"},"records":[]}"#
82                .to_string(),
83        };
84
85        Some(Ok(RuntimeQueryResult {
86            query: query.to_string(),
87            mode: reddb_rql::modes::QueryMode::Sql,
88            statement: "select",
89            engine: "fast-entity-lookup",
90            result: crate::storage::query::unified::UnifiedResult {
91                columns: Vec::new(),
92                records,
93                stats: crate::storage::query::unified::QueryStats {
94                    rows_scanned: count,
95                    ..Default::default()
96                },
97                pre_serialized_json: Some(json),
98            },
99            affected_rows: 0,
100            statement_type: "select",
101            bookmark: None,
102            notice: None,
103        }))
104    }
105
106    pub(crate) fn invalidate_plan_cache(&self) {
107        self.inner.query_cache.write().clear();
108        self.inner
109            .ddl_epoch
110            .fetch_add(1, std::sync::atomic::Ordering::Release);
111    }
112
113    /// Read the monotonic DDL epoch counter. Bumped by every
114    /// `invalidate_plan_cache` call so prepared-statement holders can
115    /// detect schema drift between PREPARE and EXECUTE.
116    pub fn ddl_epoch(&self) -> u64 {
117        self.inner
118            .ddl_epoch
119            .load(std::sync::atomic::Ordering::Acquire)
120    }
121
122    pub(crate) fn clear_table_planner_stats(&self, table: &str) {
123        let store = self.inner.db.store();
124        crate::storage::query::planner::stats_catalog::clear_table_stats(store.as_ref(), table);
125        self.invalidate_plan_cache();
126    }
127}