use crate::relation_path::RelationHop;
pub struct OrderCatalog {
pub scalars: &'static [(&'static str, &'static str)],
pub relations: &'static [OrderRelationEdge],
}
pub struct OrderRelationEdge {
pub api_name: &'static str,
pub hop: RelationHop,
pub target: &'static OrderCatalog,
}
pub struct ResolvedOrderTarget {
pub hops: Vec<RelationHop>,
pub column: &'static str,
}
pub fn resolve_order_target(
catalog: &'static OrderCatalog,
key: &str,
) -> Option<ResolvedOrderTarget> {
let mut hops = Vec::new();
let mut current = catalog;
let mut segments = key.split('.').peekable();
loop {
let segment = segments.next()?;
if segments.peek().is_none() {
return current
.scalars
.iter()
.find(|(name, _)| *name == segment)
.map(|(_, column)| ResolvedOrderTarget { hops, column });
}
let edge = current
.relations
.iter()
.find(|edge| edge.api_name == segment)?;
hops.push(edge.hop);
current = edge.target;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::filter::RelationQuantifier;
const fn to_one_hop(
parent_table: &'static str,
parent_column: &'static str,
related_table: &'static str,
related_column: &'static str,
) -> RelationHop {
RelationHop::new(
parent_table,
parent_column,
related_table,
related_column,
RelationQuantifier::ToOne,
)
}
static PROFILE_CATALOG: OrderCatalog = OrderCatalog {
scalars: &[("nickname", "nickname")],
relations: &[],
};
static USER_CATALOG: OrderCatalog = OrderCatalog {
scalars: &[("email", "email")],
relations: &[OrderRelationEdge {
api_name: "profile",
hop: to_one_hop("users", "profile_id", "profiles", "id"),
target: &PROFILE_CATALOG,
}],
};
static POST_CATALOG: OrderCatalog = OrderCatalog {
scalars: &[("id", "id"), ("title", "title")],
relations: &[OrderRelationEdge {
api_name: "author",
hop: to_one_hop("posts", "author_id", "users", "id"),
target: &USER_CATALOG,
}],
};
#[test]
fn resolves_own_scalar_with_no_hops() {
let resolved = resolve_order_target(&POST_CATALOG, "title").expect("known scalar");
assert!(resolved.hops.is_empty());
assert_eq!(resolved.column, "title");
}
#[test]
fn resolves_single_hop_relation_scalar() {
let resolved = resolve_order_target(&POST_CATALOG, "author.email").expect("known path");
assert_eq!(
resolved.hops,
vec![to_one_hop("posts", "author_id", "users", "id")]
);
assert_eq!(resolved.column, "email");
}
#[test]
fn resolves_nested_two_hop_relation_scalar() {
let resolved =
resolve_order_target(&POST_CATALOG, "author.profile.nickname").expect("known path");
assert_eq!(
resolved.hops,
vec![
to_one_hop("posts", "author_id", "users", "id"),
to_one_hop("users", "profile_id", "profiles", "id"),
]
);
assert_eq!(resolved.column, "nickname");
}
#[test]
fn resolved_hops_render_the_expected_nested_correlated_subquery() {
let resolved =
resolve_order_target(&POST_CATALOG, "author.profile.nickname").expect("known path");
assert_eq!(
crate::order_value_sql(&resolved.hops, resolved.column),
"(SELECT profiles.nickname FROM profiles \
WHERE profiles.id = users.profile_id LIMIT 1)",
);
}
#[test]
fn rejects_unknown_top_level_field() {
assert!(resolve_order_target(&POST_CATALOG, "unknownField").is_none());
}
#[test]
fn rejects_unknown_relation_segment() {
assert!(resolve_order_target(&POST_CATALOG, "editor.email").is_none());
}
#[test]
fn rejects_a_relation_named_key_with_no_terminal_scalar() {
assert!(resolve_order_target(&POST_CATALOG, "author").is_none());
}
#[test]
fn rejects_a_to_many_hop_because_it_is_never_in_the_catalog() {
assert!(resolve_order_target(&USER_CATALOG, "sessions.label").is_none());
}
}