vibesql_storage/database/
point_lookup.rs

1// ============================================================================
2// Direct Point Lookup API (Performance Optimization)
3// ============================================================================
4//
5// This module provides high-performance point lookup methods that bypass
6// SQL parsing for direct primary key access.
7
8use super::Database;
9use crate::{Row, StorageError};
10
11impl Database {
12    // ============================================================================
13    // Direct Point Lookup API (Performance Optimization)
14    // ============================================================================
15
16    /// Get a row by primary key value - bypasses SQL parsing for maximum performance
17    ///
18    /// This method provides O(1) point lookups directly using the primary key index,
19    /// completely bypassing SQL parsing and the query execution pipeline.
20    ///
21    /// # Arguments
22    /// * `table_name` - Name of the table
23    /// * `pk_value` - Primary key value to look up
24    ///
25    /// # Returns
26    /// * `Ok(Some(&Row))` - The row if found
27    /// * `Ok(None)` - If no row matches the primary key
28    /// * `Err(StorageError)` - If table doesn't exist or has no primary key
29    ///
30    /// # Performance
31    /// This is ~100-300x faster than executing a SQL point SELECT query because it:
32    /// - Skips SQL parsing (~300µs)
33    /// - Skips query planning and optimization
34    /// - Uses direct HashMap lookup on the PK index
35    ///
36    /// # Example
37    /// ```text
38    /// let row = db.get_row_by_pk("users", &SqlValue::Integer(42))?;
39    /// if let Some(row) = row {
40    ///     let name = &row.values[1];
41    /// }
42    /// ```
43    pub fn get_row_by_pk(
44        &self,
45        table_name: &str,
46        pk_value: &vibesql_types::SqlValue,
47    ) -> Result<Option<&Row>, StorageError> {
48        let table = self
49            .get_table(table_name)
50            .ok_or_else(|| StorageError::TableNotFound(table_name.to_string()))?;
51
52        let pk_index = table.primary_key_index().ok_or_else(|| {
53            StorageError::Other(format!("Table '{}' has no primary key", table_name))
54        })?;
55
56        // Look up the row index using the PK value
57        let key = vec![pk_value.clone()];
58        if let Some(&row_index) = pk_index.get(&key) {
59            let rows = table.scan();
60            if row_index < rows.len() {
61                return Ok(Some(&rows[row_index]));
62            }
63        }
64
65        Ok(None)
66    }
67
68    /// Get a specific column value by primary key - bypasses SQL parsing for maximum performance
69    ///
70    /// This is even faster than `get_row_by_pk` when you only need one column value,
71    /// as it avoids returning the entire row.
72    ///
73    /// # Arguments
74    /// * `table_name` - Name of the table
75    /// * `pk_value` - Primary key value to look up
76    /// * `column_index` - Index of the column to retrieve (0-based)
77    ///
78    /// # Returns
79    /// * `Ok(Some(&SqlValue))` - The column value if found
80    /// * `Ok(None)` - If no row matches the primary key
81    /// * `Err(StorageError)` - If table doesn't exist or column index is out of bounds
82    pub fn get_column_by_pk(
83        &self,
84        table_name: &str,
85        pk_value: &vibesql_types::SqlValue,
86        column_index: usize,
87    ) -> Result<Option<&vibesql_types::SqlValue>, StorageError> {
88        let table = self
89            .get_table(table_name)
90            .ok_or_else(|| StorageError::TableNotFound(table_name.to_string()))?;
91
92        // Validate column index
93        if column_index >= table.schema.columns.len() {
94            return Err(StorageError::Other(format!(
95                "Column index {} out of bounds for table '{}' with {} columns",
96                column_index,
97                table_name,
98                table.schema.columns.len()
99            )));
100        }
101
102        let pk_index = table.primary_key_index().ok_or_else(|| {
103            StorageError::Other(format!("Table '{}' has no primary key", table_name))
104        })?;
105
106        // Look up the row index using the PK value
107        let key = vec![pk_value.clone()];
108        if let Some(&row_index) = pk_index.get(&key) {
109            let rows = table.scan();
110            if row_index < rows.len() {
111                return Ok(rows[row_index].values.get(column_index));
112            }
113        }
114
115        Ok(None)
116    }
117
118    /// Get a row by composite primary key - for tables with multi-column primary keys
119    ///
120    /// # Arguments
121    /// * `table_name` - Name of the table
122    /// * `pk_values` - Primary key values in column order
123    ///
124    /// # Returns
125    /// * `Ok(Some(&Row))` - The row if found
126    /// * `Ok(None)` - If no row matches the primary key
127    /// * `Err(StorageError)` - If table doesn't exist or has no primary key
128    pub fn get_row_by_composite_pk(
129        &self,
130        table_name: &str,
131        pk_values: &[vibesql_types::SqlValue],
132    ) -> Result<Option<&Row>, StorageError> {
133        let table = self
134            .get_table(table_name)
135            .ok_or_else(|| StorageError::TableNotFound(table_name.to_string()))?;
136
137        let pk_index = table.primary_key_index().ok_or_else(|| {
138            StorageError::Other(format!("Table '{}' has no primary key", table_name))
139        })?;
140
141        // Look up the row index using the composite PK
142        let key: Vec<vibesql_types::SqlValue> = pk_values.to_vec();
143        if let Some(&row_index) = pk_index.get(&key) {
144            let rows = table.scan();
145            if row_index < rows.len() {
146                return Ok(Some(&rows[row_index]));
147            }
148        }
149
150        Ok(None)
151    }
152}