use crate::schema::CqlType;
use crate::storage::sstable::reader::parsing::row_decoder::V5CompressedLegacyParser;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CellPathComponent {
#[cfg(feature = "write-support")]
MapKey,
SetElement,
}
impl CellPathComponent {
#[cfg(feature = "write-support")]
pub(crate) fn declared_shapes(self) -> &'static str {
match self {
CellPathComponent::MapKey => {
"the CQL spelling `map<K,V>` nor the Cassandra marshal one \
`org.apache.cassandra.db.marshal.MapType(K,V)`"
}
CellPathComponent::SetElement => {
"the CQL spelling `set<T>` nor the Cassandra marshal one \
`org.apache.cassandra.db.marshal.SetType(T)`"
}
}
}
}
pub(crate) fn resolve_declared_cell_path_type(
declared: &str,
component: CellPathComponent,
) -> Option<CqlType> {
fn pick(ty: CqlType, component: CellPathComponent) -> Option<CqlType> {
match (ty, component) {
#[cfg(feature = "write-support")]
(CqlType::Map(key, _), CellPathComponent::MapKey) => Some(*key),
(CqlType::Set(element), CellPathComponent::SetElement) => Some(*element),
_ => None,
}
}
if let Ok(ty) = CqlType::parse(declared) {
if let Some(resolved) = pick(ty, component) {
return Some(resolved);
}
}
match V5CompressedLegacyParser::parse_cassandra_type(declared) {
Ok(ty) => pick(ty, component),
Err(_) => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_bare_inner_marshal_name_resolves_from_the_complete_declared_type() {
assert_eq!(
resolve_declared_cell_path_type(
"org.apache.cassandra.db.marshal.SetType(Int32Type)",
CellPathComponent::SetElement
),
Some(CqlType::Int)
);
}
#[test]
fn both_spellings_resolve_a_set_element() {
for (declared, expected) in [
("set<int>", CqlType::Int),
(
"org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.Int32Type)",
CqlType::Int,
),
("set<decimal>", CqlType::Decimal),
] {
assert_eq!(
resolve_declared_cell_path_type(declared, CellPathComponent::SetElement),
Some(expected.clone()),
"{declared} as a SET element"
);
}
assert_eq!(
resolve_declared_cell_path_type("map<bigint,text>", CellPathComponent::SetElement),
None
);
}
#[test]
fn a_foreign_package_inner_name_is_refused_not_resolved() {
for declared in [
"org.apache.cassandra.db.marshal.SetType(com.acme.Int32Type)",
"org.apache.cassandra.db.marshal.SetType(notorg.apache.cassandra.db.marshal.Int32Type)",
"com.acme.SetType(org.apache.cassandra.db.marshal.Int32Type)",
] {
assert_eq!(
resolve_declared_cell_path_type(declared, CellPathComponent::SetElement),
None,
"{declared} must NOT resolve to a marshal type"
);
}
}
#[test]
fn frozen_and_non_collection_declarations_resolve_no_set_element() {
for declared in FROZEN_AND_NON_COLLECTION {
assert_eq!(
resolve_declared_cell_path_type(declared, CellPathComponent::SetElement),
None,
"{declared} must resolve no SET element"
);
}
}
const FROZEN_AND_NON_COLLECTION: &[&str] = &[
"frozen<set<int>>",
"org.apache.cassandra.db.marshal.FrozenType(org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.Int32Type))",
"int",
"list<int>",
"org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.Int32Type)",
"",
];
#[cfg(feature = "write-support")]
mod write_support {
use super::*;
#[test]
fn a_bare_inner_marshal_name_resolves_a_map_key() {
assert_eq!(
resolve_declared_cell_path_type(
"org.apache.cassandra.db.marshal.MapType(Int32Type,Int32Type)",
CellPathComponent::MapKey
),
Some(CqlType::Int)
);
}
#[test]
fn the_component_kinds_do_not_cross() {
for declared in [
"set<int>",
"org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.Int32Type)",
"set<decimal>",
] {
assert_eq!(
resolve_declared_cell_path_type(declared, CellPathComponent::MapKey),
None,
"{declared} must not resolve a MAP KEY"
);
}
assert_eq!(
resolve_declared_cell_path_type("map<bigint,text>", CellPathComponent::MapKey),
Some(CqlType::BigInt)
);
}
#[test]
fn frozen_and_non_collection_declarations_resolve_no_map_key() {
for declared in FROZEN_AND_NON_COLLECTION {
assert_eq!(
resolve_declared_cell_path_type(declared, CellPathComponent::MapKey),
None,
"{declared} must resolve no MAP key"
);
}
}
}
}