@@ -2198,7 +2198,9 @@ impl<M: cranelift_module::Module> Codegen<M> {
21982198 let db = builder.block_params(entry)[0];
21992199 let rel = builder.block_params(entry)[1];
22002200 let is_float = builder.ins().iconst(types::I64, 0);
2201− let result = cg.call_rt(builder, "knot_relation_sum_direct", &[db, rel, is_float]);
2201+ let n = cg.call_rt(builder, "knot_relation_sum_direct", &[db, rel, is_float]);
2202+ // sum : Rel a -> Rel a — a one-element relation.
2203+ let result = cg.call_rt(builder, "knot_relation_singleton", &[n]);
22022204 builder.ins().return_(&[result]);
22032205 });
22042206 }
@@ -2304,6 +2306,17 @@ impl<M: cranelift_module::Module> Codegen<M> {
23042306 /// outer(db, arg1) -> Function(inner, arg1, name) — arg1 passed directly as env
23052307 /// inner(db, env=arg1, arg2) -> rt_fn(db, arg1, arg2)
23062308 fn define_stdlib_fn_2(&mut self, name: &str, rt_name: &str, rt_needs_db: bool) {
2309+ self.define_stdlib_fn_2_wrap(name, rt_name, rt_needs_db, false);
2310+ }
2311+
2312+ /// Like `define_stdlib_fn_2`, but wraps the scalar result in a one-element
2313+ /// relation (for the scalar aggregates — sum/avg/minOn/maxOn/countWhere —
2314+ /// whose result is a `Rel <scalar>`).
2315+ fn define_stdlib_fn_2_rel(&mut self, name: &str, rt_name: &str, rt_needs_db: bool) {
2316+ self.define_stdlib_fn_2_wrap(name, rt_name, rt_needs_db, true);
2317+ }
2318+
2319+ fn define_stdlib_fn_2_wrap(&mut self, name: &str, rt_name: &str, rt_needs_db: bool, wrap_singleton: bool) {
23072320 // Stdlib fns are always under the flattened `base.<name>` key — no
23082321 // shadow branch; the bare name is the user's alone.
23092322 let inner_id = self.declare_closure_fn(&format!("__stdlib_{}_apply", name));
@@ -2349,6 +2362,11 @@ impl<M: cranelift_module::Module> Codegen<M> {
23492362 } else {
23502363 cg.call_rt(builder, &rt_name, &[arg1, arg2])
23512364 };
2365+ let result = if wrap_singleton {
2366+ cg.call_rt(builder, "knot_relation_singleton", &[result])
2367+ } else {
2368+ result
2369+ };
23522370 builder.ins().return_(&[result]);
23532371 });
23542372 }
@@ -2358,6 +2376,16 @@ impl<M: cranelift_module::Module> Codegen<M> {
23582376 /// middle(db, {arg1}, arg2) -> Function(inner, {arg1, arg2})
23592377 /// inner(db, {arg1, arg2}, arg3) -> rt_fn(db, arg1, arg2, arg3)
23602378 fn define_stdlib_fn_3(&mut self, name: &str, rt_name: &str) {
2379+ self.define_stdlib_fn_3_wrap(name, rt_name, false);
2380+ }
2381+
2382+ /// Like `define_stdlib_fn_3`, but wraps the scalar result in a one-element
2383+ /// relation (for `fold`, whose result is a `Rel b`).
2384+ fn define_stdlib_fn_3_rel(&mut self, name: &str, rt_name: &str) {
2385+ self.define_stdlib_fn_3_wrap(name, rt_name, true);
2386+ }
2387+
2388+ fn define_stdlib_fn_3_wrap(&mut self, name: &str, rt_name: &str, wrap_singleton: bool) {
23612389 // Stdlib fns are always under the flattened `base.<name>` key — no
23622390 // shadow branch; the bare name is the user's alone.
23632391 let middle_id = self.declare_closure_fn(&format!("__stdlib_{}_mid", name));
@@ -2450,6 +2478,11 @@ impl<M: cranelift_module::Module> Codegen<M> {
24502478 let arg2 = cg.call_rt(builder, "knot_record_field_by_index", &[env, idx1]);
24512479
24522480 let result = cg.call_rt(builder, &rt_name, &[db, arg1, arg2, arg3]);
2481+ let result = if wrap_singleton {
2482+ cg.call_rt(builder, "knot_relation_singleton", &[result])
2483+ } else {
2484+ result
2485+ };
24532486 builder.ins().return_(&[result]);
24542487 });
24552488 }
@@ -3405,7 +3438,7 @@ impl<M: cranelift_module::Module> Codegen<M> {
34053438 self.define_stdlib_fn_2("map", "knot_relation_map", true);
34063439 self.define_stdlib_fn_2("forEach", "knot_relation_for_each", true);
34073440 self.define_stdlib_fn_2("findFirst", "knot_relation_find_first", true);
3408− self.define_stdlib_fn_3("fold", "knot_relation_fold");
3441+ self.define_stdlib_fn_3_rel("fold", "knot_relation_fold");
34093442 self.define_stdlib_fn_2("traverse", "knot_relation_traverse", true);
34103443 self.define_stdlib_fn_2("take", "knot_take", false);
34113444 self.define_stdlib_fn_2("drop", "knot_drop", false);
@@ -3426,10 +3459,10 @@ impl<M: cranelift_module::Module> Codegen<M> {
34263459 self.define_stdlib_fn_1("valueNegate", "knot_value_negate");
34273460 self.define_stdlib_fn_2("union", "knot_relation_union", true);
34283461 self.define_stdlib_fn_2("bind", "knot_relation_bind", true);
3429− self.define_stdlib_fn_2("avg", "knot_relation_avg", true);
3430− self.define_stdlib_fn_2("minOn", "knot_relation_min", true);
3431− self.define_stdlib_fn_2("maxOn", "knot_relation_max", true);
3432− self.define_stdlib_fn_2("countWhere", "knot_relation_count_where", true);
3462+ self.define_stdlib_fn_2_rel("avg", "knot_relation_avg", true);
3463+ self.define_stdlib_fn_2_rel("minOn", "knot_relation_min", true);
3464+ self.define_stdlib_fn_2_rel("maxOn", "knot_relation_max", true);
3465+ self.define_stdlib_fn_2_rel("countWhere", "knot_relation_count_where", true);
34333466 self.define_stdlib_fn_2("any", "knot_relation_any", true);
34343467 self.define_stdlib_fn_2("all", "knot_relation_all", true);
34353468
@@ -8914,11 +8947,13 @@ impl<M: cranelift_module::Module> Codegen<M> {
89148947 let is_float = builder
89158948 .ins()
89168949 .iconst(types::I64, self.sum_float_spans.contains(&expr.span) as i64);
8917− return self.call_rt(
8950+ let n = self.call_rt(
89188951 builder,
89198952 "knot_relation_sum_direct",
89208953 &[db, rel_val, is_float],
89218954 );
8955+ // sum : Rel a -> Rel a — a one-element relation.
8956+ return self.call_rt(builder, "knot_relation_singleton", &[n]);
89228957 }
89238958
89248959 // Implicit dictionary: prepend the record resolved during inference as
@@ -16871,12 +16906,15 @@ impl<M: cranelift_module::Module> Codegen<M> {
1687116906 let rt_fn = query.runtime_fn();
1687216907 // SUM and MIN/MAX take an extra I64 flag (is_float / is_text);
1687316908 // AVG (`knot_source_query_float`) takes none.
16874− if matches!(rt_fn, "knot_source_query_sum" | "knot_source_query_value") {
16909+ let n = if matches!(rt_fn, "knot_source_query_sum" | "knot_source_query_value") {
1687516910 let flag = builder.ins().iconst(types::I64, *result_flag as i64);
1687616911 self.call_rt(builder, rt_fn, &[db, sql_ptr, sql_len, params_rel, flag])
1687716912 } else {
1687816913 self.call_rt(builder, rt_fn, &[db, sql_ptr, sql_len, params_rel])
16879− }
16914+ };
16915+ // The scalar aggregates (sum/avg/minOn/maxOn) yield a one-element
16916+ // relation (Rel <scalar>), so they compose into further queries.
16917+ self.call_rt(builder, "knot_relation_singleton", &[n])
1688016918 }
1688116919 }
1688216920 }