use arc_swap::ArcSwapOption;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum AccessPath {
FullScan,
PartitionLookup,
MultiPartitionLookup,
ClusteringSlice,
MetadataPartitionLookup,
StreamingPartitionLookup,
FallbackFullScan {
reason: FallbackReason,
},
}
impl AccessPath {
pub fn is_full_scan(&self) -> bool {
matches!(
self,
AccessPath::FullScan | AccessPath::FallbackFullScan { .. }
)
}
pub fn is_targeted(&self) -> bool {
matches!(
self,
AccessPath::PartitionLookup
| AccessPath::MultiPartitionLookup
| AccessPath::ClusteringSlice
| AccessPath::MetadataPartitionLookup
| AccessPath::StreamingPartitionLookup
)
}
pub fn label(&self) -> &'static str {
match self {
AccessPath::FullScan => "full_scan",
AccessPath::PartitionLookup => "partition_lookup",
AccessPath::MultiPartitionLookup => "multi_partition_lookup",
AccessPath::ClusteringSlice => "clustering_slice",
AccessPath::MetadataPartitionLookup => "metadata_partition_lookup",
AccessPath::StreamingPartitionLookup => "streaming_partition_lookup",
AccessPath::FallbackFullScan { .. } => "fallback_full_scan",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum FallbackReason {
NoSchema,
PartitionKeyNotFullyConstrained,
PartitionKeyEncodingFailed,
MetadataScanPath,
LegacyExecutorPath,
ForcedFullScan,
TombstonesBuildNoPrune,
}
impl FallbackReason {
pub fn label(&self) -> &'static str {
match self {
FallbackReason::NoSchema => "no_schema",
FallbackReason::PartitionKeyNotFullyConstrained => {
"partition_key_not_fully_constrained"
}
FallbackReason::PartitionKeyEncodingFailed => "partition_key_encoding_failed",
FallbackReason::MetadataScanPath => "metadata_scan_path",
FallbackReason::ForcedFullScan => "forced_full_scan",
FallbackReason::LegacyExecutorPath => "legacy_executor_path",
FallbackReason::TombstonesBuildNoPrune => "tombstones_build_no_prune",
}
}
}
impl std::fmt::Display for AccessPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AccessPath::FallbackFullScan { reason } => {
write!(f, "{} ({})", self.label(), reason.label())
}
_ => f.write_str(self.label()),
}
}
}
static LAST_ACCESS_PATH: ArcSwapOption<AccessPath> = ArcSwapOption::const_empty();
pub fn record(path: AccessPath) {
if let AccessPath::FallbackFullScan { reason } = &path {
crate::observability::add_counter(
crate::observability::catalog::QUERY_DEGRADED_PATH,
1,
&[(
crate::observability::catalog::attr::FALLBACK_REASON,
reason.label().into(),
)],
);
}
LAST_ACCESS_PATH.store(Some(Arc::new(path)));
}
pub fn last() -> Option<AccessPath> {
LAST_ACCESS_PATH.load_full().map(|path| (*path).clone())
}
pub fn reset() {
LAST_ACCESS_PATH.store(None);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn full_scan_is_not_targeted() {
assert!(AccessPath::FullScan.is_full_scan());
assert!(!AccessPath::FullScan.is_targeted());
}
#[test]
fn fallback_is_full_scan_and_carries_reason() {
let p = AccessPath::FallbackFullScan {
reason: FallbackReason::NoSchema,
};
assert!(p.is_full_scan());
assert!(!p.is_targeted());
assert_eq!(p.to_string(), "fallback_full_scan (no_schema)");
}
#[test]
fn partition_lookup_is_targeted() {
assert!(AccessPath::PartitionLookup.is_targeted());
assert!(!AccessPath::PartitionLookup.is_full_scan());
assert_eq!(AccessPath::PartitionLookup.label(), "partition_lookup");
}
#[test]
fn serde_tag_matches_label() {
assert_eq!(
serde_json::to_string(&AccessPath::PartitionLookup).unwrap(),
"\"partition_lookup\""
);
assert_eq!(
serde_json::to_string(&AccessPath::MetadataPartitionLookup).unwrap(),
"\"metadata_partition_lookup\""
);
assert_eq!(
serde_json::to_string(&FallbackReason::MetadataScanPath).unwrap(),
"\"metadata_scan_path\""
);
let p = AccessPath::FallbackFullScan {
reason: FallbackReason::PartitionKeyNotFullyConstrained,
};
let json = serde_json::to_string(&p).unwrap();
assert_eq!(serde_json::from_str::<AccessPath>(&json).unwrap(), p);
}
#[test]
fn probe_round_trips() {
reset();
assert_eq!(last(), None);
record(AccessPath::PartitionLookup);
assert_eq!(last(), Some(AccessPath::PartitionLookup));
reset();
assert_eq!(last(), None);
}
}