diff --git a/fixtures/queryer.json b/fixtures/queryer.json index 1346153..d98f8bd 100644 --- a/fixtures/queryer.json +++ b/fixtures/queryer.json @@ -1424,7 +1424,7 @@ " NOT entity_7.archived", " AND relationship_5.target_id = entity_7.id", " )", - " )), '[]'::jsonb)", + " ) ORDER BY entity_4.created_at, entity_4.id), '[]'::jsonb)", " FROM agreego.entity entity_4", " JOIN agreego.relationship relationship_5", " JOIN agreego.contact contact_6", @@ -1454,7 +1454,7 @@ " NOT entity_12.archived", " AND relationship_10.target_id = entity_12.id", " )", - " )), '[]'::jsonb)", + " ) ORDER BY entity_9.created_at, entity_9.id), '[]'::jsonb)", " FROM agreego.entity entity_9", " JOIN agreego.relationship relationship_10", " JOIN agreego.contact contact_11", @@ -1484,7 +1484,7 @@ " NOT entity_17.archived", " AND relationship_15.target_id = entity_17.id", " )", - " )), '[]'::jsonb)", + " ) ORDER BY entity_14.created_at, entity_14.id), '[]'::jsonb)", " FROM agreego.entity entity_14", " JOIN agreego.relationship relationship_15", " JOIN agreego.contact contact_16", @@ -1545,7 +1545,7 @@ " ))", " ELSE NULL", " END", - " )), '[]'::jsonb)", + " ) ORDER BY entity_19.created_at, entity_19.id), '[]'::jsonb)", " FROM agreego.entity entity_19", " JOIN agreego.relationship relationship_20", " JOIN agreego.contact contact_21", @@ -1678,7 +1678,7 @@ " AND phone_number_8.number ILIKE $32 #>> '{}'", " AND relationship_5.target_id = entity_7.id", " )", - " )), '[]'::jsonb)", + " ) ORDER BY entity_4.created_at, entity_4.id), '[]'::jsonb)", " FROM agreego.entity entity_4", " JOIN agreego.relationship relationship_5", " JOIN agreego.contact contact_6", @@ -1708,7 +1708,7 @@ " NOT entity_12.archived", " AND relationship_10.target_id = entity_12.id", " )", - " )), '[]'::jsonb)", + " ) ORDER BY entity_9.created_at, entity_9.id), '[]'::jsonb)", " FROM agreego.entity entity_9", " JOIN agreego.relationship relationship_10", " JOIN agreego.contact contact_11", @@ -1738,7 +1738,7 @@ " NOT entity_17.archived", " AND relationship_15.target_id = entity_17.id", " )", - " )), '[]'::jsonb)", + " ) ORDER BY entity_14.created_at, entity_14.id), '[]'::jsonb)", " FROM agreego.entity entity_14", " JOIN agreego.relationship relationship_15", " JOIN agreego.contact contact_16", @@ -1799,7 +1799,7 @@ " ))", " ELSE NULL", " END", - " )), '[]'::jsonb)", + " ) ORDER BY entity_19.created_at, entity_19.id), '[]'::jsonb)", " FROM agreego.entity entity_19", " JOIN agreego.relationship relationship_20", " JOIN agreego.contact contact_21", @@ -1878,7 +1878,7 @@ " NOT entity_4.archived", " AND relationship_2.target_id = entity_4.id", " )", - " )), '[]'::jsonb)", + " ) ORDER BY entity_1.created_at, entity_1.id), '[]'::jsonb)", " FROM agreego.entity entity_1", " JOIN agreego.relationship relationship_2", " JOIN agreego.contact contact_3", @@ -1936,7 +1936,7 @@ " 'order_id', order_line_7.order_id,", " 'product', order_line_7.product,", " 'price', order_line_7.price", - " )), '[]'::jsonb)", + " ) ORDER BY entity_6.created_at, entity_6.id), '[]'::jsonb)", " FROM agreego.entity entity_6", " JOIN agreego.order_line order_line_7", " WHERE", @@ -2041,7 +2041,7 @@ " AND entity_8.id = entity_1.id", " ))", " ELSE NULL", - " END), '[]'::jsonb)", + " END ORDER BY entity_1.created_at, entity_1.id), '[]'::jsonb)", " FROM agreego.entity entity_1", " JOIN agreego.organization organization_2", " WHERE", @@ -2152,7 +2152,7 @@ " NOT entity_9.archived", " AND relationship_7.target_id = entity_9.id", " )", - " )), '[]'::jsonb)", + " ) ORDER BY entity_6.created_at, entity_6.id), '[]'::jsonb)", " FROM agreego.entity entity_6", " JOIN agreego.relationship relationship_7", " JOIN agreego.contact contact_8", @@ -2182,7 +2182,7 @@ " NOT entity_14.archived", " AND relationship_12.target_id = entity_14.id", " )", - " )), '[]'::jsonb)", + " ) ORDER BY entity_11.created_at, entity_11.id), '[]'::jsonb)", " FROM agreego.entity entity_11", " JOIN agreego.relationship relationship_12", " JOIN agreego.contact contact_13", @@ -2212,7 +2212,7 @@ " NOT entity_19.archived", " AND relationship_17.target_id = entity_19.id", " )", - " )), '[]'::jsonb)", + " ) ORDER BY entity_16.created_at, entity_16.id), '[]'::jsonb)", " FROM agreego.entity entity_16", " JOIN agreego.relationship relationship_17", " JOIN agreego.contact contact_18", @@ -2273,7 +2273,7 @@ " ))", " ELSE NULL", " END", - " )), '[]'::jsonb)", + " ) ORDER BY entity_21.created_at, entity_21.id), '[]'::jsonb)", " FROM agreego.entity entity_21", " JOIN agreego.relationship relationship_22", " JOIN agreego.contact contact_23", @@ -2337,7 +2337,7 @@ " NOT entity_3.archived", " AND order_2.customer_id = person_5.id", " )", - " )), '[]'::jsonb)", + " ) ORDER BY entity_1.created_at, entity_1.id), '[]'::jsonb)", " FROM agreego.entity entity_1", " JOIN agreego.order order_2", " WHERE", @@ -2372,7 +2372,7 @@ " 'kind', widget_2.kind", " ))", " ELSE NULL", - " END), '[]'::jsonb)", + " END ORDER BY entity_1.created_at, entity_1.id), '[]'::jsonb)", " FROM agreego.entity entity_1", " JOIN agreego.widget widget_2", " WHERE", @@ -2405,7 +2405,7 @@ " 'created_at', entity_3.created_at,", " 'invoice_id', invoice_line_4.invoice_id,", " 'price', invoice_line_4.price", - " )), '[]'::jsonb)", + " ) ORDER BY entity_3.created_at, entity_3.id), '[]'::jsonb)", " FROM agreego.entity entity_3", " JOIN agreego.invoice_line invoice_line_4", " WHERE", @@ -2531,7 +2531,7 @@ " NOT entity_3.archived", " AND order_2.counterparty_id = entity_3.id", " )", - " )), '[]'::jsonb)", + " ) ORDER BY entity_1.created_at, entity_1.id), '[]'::jsonb)", " FROM agreego.entity entity_1", " JOIN agreego.order order_2", " WHERE", @@ -2599,7 +2599,7 @@ " NOT entity_7.archived", " AND relationship_5.target_id = entity_7.id", " )", - " )), '[]'::jsonb)", + " ) ORDER BY entity_4.created_at, entity_4.id), '[]'::jsonb)", " FROM agreego.entity entity_4", " JOIN agreego.relationship relationship_5", " JOIN agreego.contact contact_6", @@ -2629,7 +2629,7 @@ " NOT entity_12.archived", " AND relationship_10.target_id = entity_12.id", " )", - " )), '[]'::jsonb)", + " ) ORDER BY entity_9.created_at, entity_9.id), '[]'::jsonb)", " FROM agreego.entity entity_9", " JOIN agreego.relationship relationship_10", " JOIN agreego.contact contact_11", @@ -2659,7 +2659,7 @@ " NOT entity_17.archived", " AND relationship_15.target_id = entity_17.id", " )", - " )), '[]'::jsonb)", + " ) ORDER BY entity_14.created_at, entity_14.id), '[]'::jsonb)", " FROM agreego.entity entity_14", " JOIN agreego.relationship relationship_15", " JOIN agreego.contact contact_16", @@ -2720,7 +2720,7 @@ " ))", " ELSE NULL", " END", - " )), '[]'::jsonb)", + " ) ORDER BY entity_19.created_at, entity_19.id), '[]'::jsonb)", " FROM agreego.entity entity_19", " JOIN agreego.relationship relationship_20", " JOIN agreego.contact contact_21", @@ -2778,7 +2778,7 @@ " 'rating', person_3.rating,", " 'tags', person_3.tags,", " 'labels', person_3.labels", - " )), '[]'::jsonb)", + " ) ORDER BY entity_1.created_at, entity_1.id), '[]'::jsonb)", " FROM agreego.entity entity_1", " JOIN agreego.organization organization_2", " JOIN agreego.person person_3", diff --git a/src/queryer/compiler.rs b/src/queryer/compiler.rs index 1364ca9..ae04aa2 100644 --- a/src/queryer/compiler.rs +++ b/src/queryer/compiler.rs @@ -234,7 +234,17 @@ impl<'a> Compiler<'a> { let where_clauses = self.compile_where_clause(r#type, &table_aliases, node)?; let selection = if is_array { - format!("COALESCE(jsonb_agg({}), '[]'::jsonb)", jsonb_obj_sql) + // Deterministic order: aggregation over an unordered heap made result + // order vary run-to-run (flaky reads, unstable grids). Creation order + // with id as the tiebreaker; the entity alias exists for every + // entity-family type. + match table_aliases.get("entity") { + Some(entity_alias) => format!( + "COALESCE(jsonb_agg({} ORDER BY {}.created_at, {}.id), '[]'::jsonb)", + jsonb_obj_sql, entity_alias, entity_alias + ), + None => format!("COALESCE(jsonb_agg({}), '[]'::jsonb)", jsonb_obj_sql), + } } else { jsonb_obj_sql }; diff --git a/src/tests/formatter.rs b/src/tests/formatter.rs index f81c963..80a2d40 100644 --- a/src/tests/formatter.rs +++ b/src/tests/formatter.rs @@ -389,6 +389,7 @@ impl SqlFormatter { i += 2; } self.indent -= 2; + self.format_function_clauses(list); self.push_line(")"); } else { for (i, arg) in list.args.iter().enumerate() { @@ -396,6 +397,7 @@ impl SqlFormatter { self.format_function_arg(arg); self.push_str(comma); } + self.format_function_clauses(list); self.push_str(")"); } } else { @@ -403,6 +405,25 @@ impl SqlFormatter { } } + // Aggregate clauses (e.g. jsonb_agg(x ORDER BY y)) — without this the + // snapshot silently drops the ORDER BY the compiler emits. + fn format_function_clauses(&mut self, list: &sqlparser::ast::FunctionArgumentList) { + for clause in &list.clauses { + if let sqlparser::ast::FunctionArgumentClause::OrderBy(order) = clause { + self.push_str(" ORDER BY "); + for (i, ob) in order.iter().enumerate() { + if i > 0 { + self.push_str(", "); + } + self.format_expr(&ob.expr); + if let Some(asc) = ob.options.asc { + self.push_str(if asc { " ASC" } else { " DESC" }); + } + } + } + } + } + fn format_function_arg(&mut self, arg: &FunctionArg) { match arg { FunctionArg::Unnamed(sqlparser::ast::FunctionArgExpr::Expr(expr)) => self.format_expr(expr),