use crate::schema::object::TableObjectType;
use crate::schema::relationship::RelationshipField;
use async_graphql::dynamic::{Enum, EnumItem, InputObject, InputValue, TypeRef};
use std::collections::{HashMap, HashSet};
pub const DIRECTION_ENUM: &str = "order_by";
const DIRECTIONS: &[(&str, &str)] = &[
("asc", "ASC"),
("asc_nulls_first", "ASC NULLS FIRST"),
("asc_nulls_last", "ASC NULLS LAST"),
("desc", "DESC"),
("desc_nulls_first", "DESC NULLS FIRST"),
("desc_nulls_last", "DESC NULLS LAST"),
];
pub fn order_by_type_name(base_name: &str) -> String {
format!("{}_order_by", base_name)
}
pub fn select_column_type_name(base_name: &str) -> String {
format!("{}_select_column", base_name)
}
pub fn aggregate_order_by_type_name(base_name: &str) -> String {
format!("{}_aggregate_order_by", base_name)
}
pub fn function_order_by_type_name(base_name: &str, function: &str) -> String {
format!("{}_{}_order_by", base_name, function)
}
pub fn direction_sql(name: &str) -> Option<&'static str> {
DIRECTIONS
.iter()
.find(|(enum_name, _)| *enum_name == name)
.map(|(_, sql)| *sql)
}
pub fn direction_enum() -> Enum {
let mut direction = Enum::new(DIRECTION_ENUM)
.description("Sort direction, with where nulls go. `asc` is `asc_nulls_last`.");
for (name, _) in DIRECTIONS {
direction = direction.item(EnumItem::new(*name));
}
direction
}
pub fn build_inputs(
object_types: &HashMap<String, TableObjectType>,
relationship_fields: &HashMap<String, Vec<RelationshipField>>,
) -> (Vec<InputObject>, Vec<Enum>) {
let mut inputs = Vec::new();
let mut enums = vec![direction_enum()];
for (type_name, object) in object_types {
let local_type_name = object.local_name.as_str();
if object.fields.is_empty() {
continue;
}
let mut input = InputObject::new(order_by_type_name(local_type_name)).description(format!(
"Order rows of {} by a column, by a related row's column, or by an \
aggregate of its children.",
local_type_name
));
let mut columns = Enum::new(select_column_type_name(local_type_name))
.description(format!("A column of {}.", local_type_name));
let mut taken: HashSet<String> = HashSet::new();
for field in &object.fields {
if !taken.insert(field.name.clone()) {
continue;
}
input = input.field(InputValue::new(&field.name, TypeRef::named(DIRECTION_ENUM)));
columns = columns.item(EnumItem::new(&field.name));
}
for relationship in relationship_fields.get(type_name).into_iter().flatten() {
if relationship.is_list {
let field = format!("{}_aggregate", relationship.name);
if taken.insert(field.clone()) {
input = input.field(InputValue::new(
&field,
TypeRef::named(aggregate_order_by_type_name(
&relationship.target_local_name,
)),
));
}
} else if taken.insert(relationship.name.clone()) {
input = input.field(InputValue::new(
&relationship.name,
TypeRef::named(order_by_type_name(&relationship.target_local_name)),
));
}
}
inputs.push(input);
enums.push(columns);
let mut aggregate = InputObject::new(aggregate_order_by_type_name(local_type_name))
.description(format!(
"Order by an aggregate of {} rows.",
local_type_name
))
.field(InputValue::new("count", TypeRef::named(DIRECTION_ENUM)));
for (function, _, columns) in crate::schema::aggregate::functions_for(object) {
let function_type = function_order_by_type_name(local_type_name, function);
let mut per_column = InputObject::new(&function_type).description(format!(
"Order by the `{}` of a {} column.",
function, local_type_name
));
for column in &columns {
per_column =
per_column.field(InputValue::new(column, TypeRef::named(DIRECTION_ENUM)));
}
inputs.push(per_column);
aggregate = aggregate.field(InputValue::new(function, TypeRef::named(&function_type)));
}
inputs.push(aggregate);
}
(inputs, enums)
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn a_tables_ordering_is_named_after_it() {
assert_eq!(order_by_type_name("author"), "author_order_by");
assert_eq!(select_column_type_name("author"), "author_select_column");
}
#[test]
fn plain_asc_places_nulls_where_postgres_does() {
assert_eq!(direction_sql("asc"), Some("ASC"));
assert_eq!(direction_sql("asc_nulls_last"), Some("ASC NULLS LAST"));
assert_eq!(direction_sql("asc_nulls_first"), Some("ASC NULLS FIRST"));
}
#[test]
fn a_direction_that_is_not_one_has_no_sql() {
assert_eq!(direction_sql("sideways"), None);
assert_eq!(direction_sql("ASC"), None);
}
}