use crate::{
db::{OrderByClause, OrderByFieldType, ProjectionField, projection_generator::FieldKind},
graphql::FieldSelection,
schema::CompiledSchema,
};
pub fn build_typed_projection_fields(
selections: &[FieldSelection],
schema: &CompiledSchema,
parent_type_name: &str,
depth: usize,
) -> Vec<ProjectionField> {
const MAX_DEPTH: usize = 4;
let type_def = schema.find_type(parent_type_name);
selections
.iter()
.filter(|sel| sel.name != "__typename")
.map(|sel| {
let field_def =
type_def.and_then(|td| td.fields.iter().find(|f| f.name == sel.name.as_str()));
let is_composite = field_def.is_some_and(|fd| !fd.field_type.is_scalar());
let is_list = field_def.is_some_and(|fd| fd.field_type.is_list());
let is_text = field_def.is_some_and(|fd| {
matches!(
fd.field_type,
crate::schema::FieldType::String | crate::schema::FieldType::Id
)
});
let kind = if is_composite {
FieldKind::Composite
} else if is_text {
FieldKind::Text
} else {
FieldKind::Native
};
let sub_fields =
if is_composite && !is_list && !sel.nested_fields.is_empty() && depth < MAX_DEPTH {
let child_type =
field_def.and_then(|fd| fd.field_type.type_name()).unwrap_or("");
if child_type.is_empty() {
None
} else {
Some(build_typed_projection_fields(
&sel.nested_fields,
schema,
child_type,
depth + 1,
))
}
} else {
None
};
ProjectionField {
name: sel.response_key().to_string(),
source: sel.name.clone(),
kind,
sub_fields,
}
})
.collect()
}
const fn field_type_to_order_by_type(ft: &crate::schema::FieldType) -> OrderByFieldType {
use crate::schema::FieldType as FT;
match ft {
FT::Int => OrderByFieldType::Integer,
FT::Float | FT::Decimal => OrderByFieldType::Numeric,
FT::Boolean => OrderByFieldType::Boolean,
FT::DateTime => OrderByFieldType::DateTime,
FT::Date => OrderByFieldType::Date,
FT::Time => OrderByFieldType::Time,
_ => OrderByFieldType::Text,
}
}
pub fn enrich_order_by_clauses(
mut clauses: Vec<OrderByClause>,
schema: &CompiledSchema,
return_type: &str,
native_columns: &std::collections::HashMap<String, String>,
) -> Vec<OrderByClause> {
let type_def = schema.find_type(return_type);
for clause in &mut clauses {
if let Some(td) = type_def {
if let Some(field_def) = td.find_field(&clause.field) {
clause.field_type = field_type_to_order_by_type(&field_def.field_type);
}
}
let storage_key = clause.storage_key();
if native_columns.contains_key(&storage_key) {
clause.native_column = Some(storage_key);
}
}
clauses
}
pub fn selections_contain_field(selections: &[FieldSelection], field_name: &str) -> bool {
for sel in selections {
if sel.name == field_name {
return true;
}
if sel.name.starts_with("...") && selections_contain_field(&sel.nested_fields, field_name) {
return true;
}
}
false
}
#[cfg(test)]
#[path = "query_projection_tests.rs"]
mod query_projection_tests;