1use std::sync::RwLock;
30
31use radixdb_core::{CompactArc, SmartString};
32use radixdb_core::{DataType, Result, Row, RowVec, Schema, Value};
33use radixdb_sql::ast::{Expression, SelectStatement};
34use radixdb_storage::traits::{Engine, QueryResult};
35
36use crate::compiled_plan::{CompiledExecution, CompiledPkLookup, PkValueSource};
37use crate::context::ExecutionContext;
38use crate::lookup_key::{integer_pk_admission, IntegerPkAdmission};
39use crate::mutation::host::MutationHost;
40use crate::result::ExecutorResult;
41
42#[doc(hidden)]
44pub struct PkLookupInfo {
45 table_name: String,
47 pk_value: IntegerPkAdmission,
49 pk_value_source: PkValueSource,
51 schema: CompactArc<Schema>,
53}
54
55#[doc(hidden)]
56pub trait PkFastPathExt: MutationHost {
57 fn try_fast_pk_lookup(
62 &self,
63 stmt: &SelectStatement,
64 ctx: &ExecutionContext,
65 ) -> Option<Result<Box<dyn QueryResult>>> {
66 {
73 let active_tx = match self.mutation_active_transaction().try_lock() {
74 Ok(guard) => guard,
75 Err(_) => return None, };
77 if active_tx.is_some() {
78 return None; }
80 }
81
82 let where_clause = stmt.where_clause.as_ref()?;
84 let table_expr = stmt.table_expr.as_ref()?;
85
86 if !stmt.group_by.columns.is_empty()
88 || stmt.having.is_some()
89 || !stmt.set_operations.is_empty()
90 || stmt.with.is_some()
91 || stmt.distinct
92 {
93 return None;
94 }
95
96 if !stmt.order_by.is_empty() {
98 return None;
99 }
100
101 if stmt.columns.len() != 1 || !matches!(&stmt.columns[0], Expression::Star(_)) {
103 return None;
104 }
105
106 let table_name: &str = match table_expr.as_ref() {
109 Expression::TableSource(ts) if ts.as_of.is_none() => ts.name.value_lower.as_str(),
110 _ => return None, };
112
113 let lookup_info = self.extract_pk_lookup_info(table_name, where_clause, ctx)?;
115
116 Some(self.execute_pk_lookup(lookup_info))
118 }
119
120 fn extract_pk_lookup_info(
122 &self,
123 table_name: &str,
124 where_clause: &Expression,
125 ctx: &ExecutionContext,
126 ) -> Option<PkLookupInfo> {
127 let schema = self.mutation_engine().get_table_schema(table_name).ok()?;
129 let pk_indices = schema.primary_key_indices();
130
131 if pk_indices.len() != 1 {
133 return None;
134 }
135 let pk_idx = pk_indices[0];
136 if schema.columns[pk_idx].data_type != DataType::Integer {
137 return None;
138 }
139 let pk_column = &schema.columns[pk_idx].name;
140
141 let (col_name, pk_value, pk_value_source) =
143 self.extract_pk_equality(where_clause, pk_column, ctx)?;
144
145 let col_lower = col_name.to_lowercase();
148 let pk_lower = &schema.columns[pk_idx].name_lower;
149
150 let matches_pk = col_lower == *pk_lower || col_lower.ends_with(&format!(".{}", pk_lower));
152
153 if !matches_pk {
154 return None;
155 }
156
157 Some(PkLookupInfo {
158 table_name: table_name.to_string(),
159 pk_value,
160 pk_value_source,
161 schema,
162 })
163 }
164
165 fn extract_pk_equality(
168 &self,
169 expr: &Expression,
170 _pk_column: &str,
171 ctx: &ExecutionContext,
172 ) -> Option<(String, IntegerPkAdmission, PkValueSource)> {
173 match expr {
174 Expression::Infix(infix) => {
175 if infix.operator != "=" {
177 return None;
178 }
179
180 if let Some((col, val, source)) =
182 self.extract_col_eq_val(&infix.left, &infix.right, ctx)
183 {
184 return Some((col, val, source));
185 }
186
187 if let Some((col, val, source)) =
189 self.extract_col_eq_val(&infix.right, &infix.left, ctx)
190 {
191 return Some((col, val, source));
192 }
193
194 None
195 }
196 _ => None,
197 }
198 }
199
200 fn extract_col_eq_val(
202 &self,
203 col_expr: &Expression,
204 val_expr: &Expression,
205 ctx: &ExecutionContext,
206 ) -> Option<(String, IntegerPkAdmission, PkValueSource)> {
207 let col_name = match col_expr {
209 Expression::Identifier(id) => id.value.to_string(),
210 Expression::QualifiedIdentifier(q) => format!("{}.{}", q.qualifier, q.name),
211 _ => return None,
212 };
213
214 let (pk_value, pk_value_source) = match val_expr {
216 Expression::IntegerLiteral(lit) => (
217 IntegerPkAdmission::Exact(lit.value),
218 PkValueSource::Literal(lit.value),
219 ),
220 Expression::FloatLiteral(lit) => {
221 let admission = integer_pk_admission(&Value::Float(lit.value))?;
222 let cached_literal = match admission {
223 IntegerPkAdmission::Exact(value) => value,
224 IntegerPkAdmission::NoMatch => 0,
225 };
226 (admission, PkValueSource::Literal(cached_literal))
227 }
228 Expression::Parameter(param) => {
229 if param.name.starts_with(':') {
233 let name = ¶m.name[1..];
234 let value = ctx.get_named_param(name)?;
235 let pk_value = integer_pk_admission(value)?;
236 (
237 pk_value,
238 PkValueSource::NamedParameter(SmartString::new(name)),
239 )
240 } else {
241 let params = ctx.params();
242 let param_idx = if param.index > 0 {
243 param.index - 1
244 } else {
245 return None;
246 };
247 if param_idx >= params.len() {
248 return None;
249 }
250 let pk_value = integer_pk_admission(¶ms[param_idx])?;
251 (pk_value, PkValueSource::Parameter(param_idx))
252 }
253 }
254 _ => return None,
255 };
256
257 Some((col_name, pk_value, pk_value_source))
258 }
259
260 #[inline]
266 fn normalize_row_to_schema(mut row: Row, schema: &Schema) -> Row {
267 let schema_cols = schema.columns.len();
268 let row_cols = row.len();
269
270 if row_cols < schema_cols {
271 for i in row_cols..schema_cols {
273 let col = &schema.columns[i];
274 if let Some(ref default_val) = col.default_value {
276 row.push(default_val.clone());
277 } else {
278 row.push(Value::null(col.data_type));
279 }
280 }
281 } else if row_cols > schema_cols {
282 row.truncate(schema_cols);
284 }
285
286 row
287 }
288
289 fn execute_pk_lookup(&self, info: PkLookupInfo) -> Result<Box<dyn QueryResult>> {
291 let columns = info.schema.column_names_arc();
293
294 let IntegerPkAdmission::Exact(pk_value) = info.pk_value else {
295 return Ok(Box::new(ExecutorResult::with_arc_columns(
296 columns,
297 RowVec::new(),
298 )));
299 };
300
301 let rows = self
305 .mutation_engine()
306 .fetch_rows_by_ids(&info.table_name, &[pk_value])?;
307
308 let result_rows: RowVec = rows
310 .into_iter()
311 .enumerate()
312 .map(|(i, (_, row))| (i as i64, Self::normalize_row_to_schema(row, &info.schema)))
313 .collect();
314
315 Ok(Box::new(ExecutorResult::with_arc_columns(
316 columns,
317 result_rows,
318 )))
319 }
320
321 fn try_fast_pk_lookup_compiled(
330 &self,
331 stmt: &SelectStatement,
332 ctx: &ExecutionContext,
333 compiled: &RwLock<CompiledExecution>,
334 ) -> Option<Result<Box<dyn QueryResult>>> {
335 {
337 let active_tx = match self.mutation_active_transaction().try_lock() {
338 Ok(guard) => guard,
339 Err(_) => return None, };
341 if active_tx.is_some() {
342 return None;
343 }
344 }
345
346 {
348 let compiled_guard = match compiled.read() {
349 Ok(guard) => guard,
350 Err(_) => return None,
351 };
352 match &*compiled_guard {
353 CompiledExecution::NotOptimizable(epoch)
354 if self.mutation_engine().schema_epoch() == *epoch =>
355 {
356 return None
357 }
358 CompiledExecution::PkLookup(lookup) => {
359 if self.mutation_engine().schema_epoch() == lookup.cached_epoch {
362 let pk_value = self.extract_pk_value_fast(&lookup.pk_value_source, ctx)?;
364 return Some(self.execute_compiled_pk_lookup(lookup, pk_value));
365 }
366 }
369 CompiledExecution::NotOptimizable(_) | CompiledExecution::Unknown => {} CompiledExecution::PkUpdate(_)
372 | CompiledExecution::PkDelete(_)
373 | CompiledExecution::Insert(_)
374 | CompiledExecution::CountDistinct(_)
375 | CompiledExecution::CountStar(_) => return None,
376 }
377 }
378
379 self.compile_and_execute_pk_lookup(stmt, ctx, compiled)
381 }
382
383 fn extract_pk_value_fast(
385 &self,
386 source: &PkValueSource,
387 ctx: &ExecutionContext,
388 ) -> Option<IntegerPkAdmission> {
389 match source {
390 PkValueSource::NamedParameter(name) => integer_pk_admission(ctx.get_named_param(name)?),
391 _ => Self::extract_pk_value_from_slice(source, ctx.params()),
392 }
393 }
394
395 #[inline]
397 fn extract_pk_value_from_slice(
398 source: &PkValueSource,
399 params: &[Value],
400 ) -> Option<IntegerPkAdmission> {
401 match source {
402 PkValueSource::Literal(v) => Some(IntegerPkAdmission::Exact(*v)),
403 PkValueSource::Parameter(idx) => {
404 if *idx >= params.len() {
405 return None;
406 }
407 integer_pk_admission(¶ms[*idx])
408 }
409 PkValueSource::NamedParameter(_) => None, }
411 }
412
413 fn try_fast_pk_lookup_with_params(
415 &self,
416 _stmt: &SelectStatement,
417 params: &[Value],
418 compiled: &RwLock<CompiledExecution>,
419 ) -> Option<Result<Box<dyn QueryResult>>> {
420 let compiled_guard = compiled.read().ok()?;
422 match &*compiled_guard {
423 CompiledExecution::NotOptimizable(_) => None,
424 CompiledExecution::PkLookup(lookup) => {
425 if self.mutation_engine().schema_epoch() == lookup.cached_epoch {
427 let pk_value =
429 Self::extract_pk_value_from_slice(&lookup.pk_value_source, params)?;
430 Some(self.execute_compiled_pk_lookup(lookup, pk_value))
431 } else {
432 None
434 }
435 }
436 CompiledExecution::Unknown => None, _ => None,
438 }
439 }
440
441 fn execute_compiled_pk_lookup(
443 &self,
444 lookup: &CompiledPkLookup,
445 pk_value: IntegerPkAdmission,
446 ) -> Result<Box<dyn QueryResult>> {
447 let IntegerPkAdmission::Exact(pk_value) = pk_value else {
448 return Ok(Box::new(ExecutorResult::with_arc_columns(
449 lookup.column_names.clone(),
450 RowVec::new(),
451 )));
452 };
453 let rows = self
454 .mutation_engine()
455 .fetch_rows_by_ids(&lookup.table_name, &[pk_value])?;
456 let mut result_rows = RowVec::with_capacity(1);
459 for (row_id, (_, row)) in rows.into_iter().enumerate() {
460 result_rows.push((
461 row_id as i64,
462 Self::normalize_row_to_schema(row, &lookup.schema),
463 ));
464 }
465 Ok(Box::new(ExecutorResult::with_arc_columns(
467 lookup.column_names.clone(),
468 result_rows,
469 )))
470 }
471
472 fn compile_and_execute_pk_lookup(
474 &self,
475 stmt: &SelectStatement,
476 ctx: &ExecutionContext,
477 compiled: &RwLock<CompiledExecution>,
478 ) -> Option<Result<Box<dyn QueryResult>>> {
479 let mut compiled_guard = match compiled.write() {
481 Ok(guard) => guard,
482 Err(_) => return None,
483 };
484
485 match &*compiled_guard {
488 CompiledExecution::NotOptimizable(epoch)
489 if self.mutation_engine().schema_epoch() == *epoch =>
490 {
491 return None
492 }
493 CompiledExecution::PkLookup(lookup) => {
494 if self.mutation_engine().schema_epoch() == lookup.cached_epoch {
496 let pk_value = self.extract_pk_value_fast(&lookup.pk_value_source, ctx)?;
497 return Some(self.execute_compiled_pk_lookup(lookup, pk_value));
498 }
499 }
501 CompiledExecution::NotOptimizable(_) | CompiledExecution::Unknown => {} CompiledExecution::PkUpdate(_)
504 | CompiledExecution::PkDelete(_)
505 | CompiledExecution::Insert(_)
506 | CompiledExecution::CountDistinct(_)
507 | CompiledExecution::CountStar(_) => return None,
508 }
509
510 let where_clause = stmt.where_clause.as_ref()?;
512 let table_expr = stmt.table_expr.as_ref()?;
513
514 if !stmt.group_by.columns.is_empty()
516 || stmt.having.is_some()
517 || !stmt.set_operations.is_empty()
518 || stmt.with.is_some()
519 || stmt.distinct
520 {
521 *compiled_guard =
522 CompiledExecution::NotOptimizable(self.mutation_engine().schema_epoch());
523 return None;
524 }
525
526 if !stmt.order_by.is_empty() {
528 *compiled_guard =
529 CompiledExecution::NotOptimizable(self.mutation_engine().schema_epoch());
530 return None;
531 }
532
533 if stmt.columns.len() != 1 || !matches!(&stmt.columns[0], Expression::Star(_)) {
536 return None;
537 }
538
539 let table_name: &str = match table_expr.as_ref() {
541 Expression::TableSource(ts) if ts.as_of.is_none() => ts.name.value_lower.as_str(),
542 _ => {
543 *compiled_guard =
544 CompiledExecution::NotOptimizable(self.mutation_engine().schema_epoch());
545 return None;
546 }
547 };
548
549 match self.extract_pk_lookup_info(table_name, where_clause, ctx) {
551 Some(info) => {
552 if info.pk_value == IntegerPkAdmission::NoMatch
555 && matches!(&info.pk_value_source, PkValueSource::Literal(_))
556 {
557 drop(compiled_guard);
558 return Some(self.execute_pk_lookup(info));
559 }
560 let column_names = info.schema.column_names_arc();
563 let cached_epoch = self.mutation_engine().schema_epoch();
564 let compiled_lookup = CompiledPkLookup {
565 table_name: SmartString::new(&info.table_name),
566 schema: info.schema.clone(),
567 column_names,
568 pk_value_source: info.pk_value_source.clone(),
569 cached_epoch,
570 };
571 *compiled_guard = CompiledExecution::PkLookup(compiled_lookup);
572 drop(compiled_guard);
573
574 Some(self.execute_pk_lookup(info))
576 }
577 None => {
578 *compiled_guard =
579 CompiledExecution::NotOptimizable(self.mutation_engine().schema_epoch());
580 None
581 }
582 }
583 }
584}
585
586impl<T: MutationHost + ?Sized> PkFastPathExt for T {}
587
588#[cfg(test)]
589mod tests {
590 use super::{integer_pk_admission, IntegerPkAdmission};
591 use crate::lookup_key::exact_integer_pk_value;
592 use radixdb_core::Value;
593
594 #[test]
595 fn exact_integer_pk_value_rejects_truncation_and_saturation() {
596 for value in [
597 Value::Float(1.5),
598 Value::Float(-1.5),
599 Value::Float(f64::MIN_POSITIVE),
600 Value::Float(f64::INFINITY),
601 Value::Float(f64::NEG_INFINITY),
602 Value::Float(f64::NAN),
603 Value::Float(i64::MAX as f64),
604 ] {
605 assert_eq!(exact_integer_pk_value(&value), None, "value={value:?}");
606 assert_eq!(
607 integer_pk_admission(&value),
608 Some(IntegerPkAdmission::NoMatch),
609 "value={value:?}"
610 );
611 }
612 }
613
614 #[test]
615 fn exact_integer_pk_value_accepts_only_canonical_integer_identity() {
616 let two_pow_53 = 1_i64 << 53;
617 for (value, expected) in [
618 (Value::Integer(i64::MIN), i64::MIN),
619 (Value::Integer(i64::MAX), i64::MAX),
620 (Value::Float(i64::MIN as f64), i64::MIN),
621 (Value::Float(-0.0), 0),
622 (Value::Float(0.0), 0),
623 (Value::Float(42.0), 42),
624 (Value::Float(two_pow_53 as f64), two_pow_53),
625 (Value::Float((two_pow_53 + 2) as f64), two_pow_53 + 2),
626 ] {
627 assert_eq!(
628 exact_integer_pk_value(&value),
629 Some(expected),
630 "value={value:?}"
631 );
632 }
633 }
634}