postrust_graphql/input/
order_by.rs1use crate::schema::object::TableObjectType;
25use crate::schema::relationship::RelationshipField;
26use async_graphql::dynamic::{Enum, EnumItem, InputObject, InputValue, TypeRef};
27use std::collections::{HashMap, HashSet};
28
29pub const DIRECTION_ENUM: &str = "order_by";
31
32const DIRECTIONS: &[(&str, &str)] = &[
34 ("asc", "ASC"),
35 ("asc_nulls_first", "ASC NULLS FIRST"),
36 ("asc_nulls_last", "ASC NULLS LAST"),
37 ("desc", "DESC"),
38 ("desc_nulls_first", "DESC NULLS FIRST"),
39 ("desc_nulls_last", "DESC NULLS LAST"),
40];
41
42pub fn order_by_type_name(base_name: &str) -> String {
44 format!("{}_order_by", base_name)
45}
46
47pub fn select_column_type_name(base_name: &str) -> String {
49 format!("{}_select_column", base_name)
50}
51
52pub fn aggregate_order_by_type_name(base_name: &str) -> String {
54 format!("{}_aggregate_order_by", base_name)
55}
56
57pub fn function_order_by_type_name(base_name: &str, function: &str) -> String {
59 format!("{}_{}_order_by", base_name, function)
60}
61
62pub fn direction_sql(name: &str) -> Option<&'static str> {
64 DIRECTIONS
65 .iter()
66 .find(|(enum_name, _)| *enum_name == name)
67 .map(|(_, sql)| *sql)
68}
69
70pub fn direction_enum() -> Enum {
72 let mut direction = Enum::new(DIRECTION_ENUM)
73 .description("Sort direction, with where nulls go. `asc` is `asc_nulls_last`.");
74 for (name, _) in DIRECTIONS {
75 direction = direction.item(EnumItem::new(*name));
76 }
77 direction
78}
79
80pub fn build_inputs(
82 object_types: &HashMap<String, TableObjectType>,
83 relationship_fields: &HashMap<String, Vec<RelationshipField>>,
84) -> (Vec<InputObject>, Vec<Enum>) {
85 let mut inputs = Vec::new();
86 let mut enums = vec![direction_enum()];
87
88 for (type_name, object) in object_types {
89 let local_type_name = object.local_name.as_str();
90 if object.fields.is_empty() {
93 continue;
94 }
95
96 let mut input = InputObject::new(order_by_type_name(local_type_name)).description(format!(
97 "Order rows of {} by a column, by a related row's column, or by an \
98 aggregate of its children.",
99 local_type_name
100 ));
101 let mut columns = Enum::new(select_column_type_name(local_type_name))
102 .description(format!("A column of {}.", local_type_name));
103
104 let mut taken: HashSet<String> = HashSet::new();
105 for field in &object.fields {
106 if !taken.insert(field.name.clone()) {
107 continue;
108 }
109 input = input.field(InputValue::new(&field.name, TypeRef::named(DIRECTION_ENUM)));
110 columns = columns.item(EnumItem::new(&field.name));
111 }
112
113 for relationship in relationship_fields.get(type_name).into_iter().flatten() {
119 if relationship.is_list {
120 let field = format!("{}_aggregate", relationship.name);
121 if taken.insert(field.clone()) {
122 input = input.field(InputValue::new(
123 &field,
124 TypeRef::named(aggregate_order_by_type_name(
125 &relationship.target_local_name,
126 )),
127 ));
128 }
129 } else if taken.insert(relationship.name.clone()) {
130 input = input.field(InputValue::new(
131 &relationship.name,
132 TypeRef::named(order_by_type_name(&relationship.target_local_name)),
133 ));
134 }
135 }
136
137 inputs.push(input);
138 enums.push(columns);
139
140 let mut aggregate = InputObject::new(aggregate_order_by_type_name(local_type_name))
142 .description(format!(
143 "Order by an aggregate of {} rows.",
144 local_type_name
145 ))
146 .field(InputValue::new("count", TypeRef::named(DIRECTION_ENUM)));
147
148 for (function, _, columns) in crate::schema::aggregate::functions_for(object) {
149 let function_type = function_order_by_type_name(local_type_name, function);
150 let mut per_column = InputObject::new(&function_type).description(format!(
151 "Order by the `{}` of a {} column.",
152 function, local_type_name
153 ));
154 for column in &columns {
155 per_column =
156 per_column.field(InputValue::new(column, TypeRef::named(DIRECTION_ENUM)));
157 }
158 inputs.push(per_column);
159 aggregate = aggregate.field(InputValue::new(function, TypeRef::named(&function_type)));
160 }
161 inputs.push(aggregate);
162 }
163
164 (inputs, enums)
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use pretty_assertions::assert_eq;
171
172 #[test]
173 fn a_tables_ordering_is_named_after_it() {
174 assert_eq!(order_by_type_name("author"), "author_order_by");
175 assert_eq!(select_column_type_name("author"), "author_select_column");
176 }
177
178 #[test]
179 fn plain_asc_places_nulls_where_postgres_does() {
180 assert_eq!(direction_sql("asc"), Some("ASC"));
184 assert_eq!(direction_sql("asc_nulls_last"), Some("ASC NULLS LAST"));
185 assert_eq!(direction_sql("asc_nulls_first"), Some("ASC NULLS FIRST"));
186 }
187
188 #[test]
189 fn a_direction_that_is_not_one_has_no_sql() {
190 assert_eq!(direction_sql("sideways"), None);
191 assert_eq!(direction_sql("ASC"), None);
192 }
193}