diff --git a/prqlc/prqlc/src/semantic/resolver/transforms.rs b/prqlc/prqlc/src/semantic/resolver/transforms.rs index c6a66aa62bb0..0fdd06e3b323 100644 --- a/prqlc/prqlc/src/semantic/resolver/transforms.rs +++ b/prqlc/prqlc/src/semantic/resolver/transforms.rs @@ -303,18 +303,45 @@ impl Resolver<'_> { .with_span(pattern.span)); } - "tuple_every" => { + "tuple_reduce" => { // yes, this is not a transform, but this is the most appropriate place for it - let [list] = unpack::<1>(func.args); - let list = list.kind.into_tuple().unwrap(); + let [init, func, list] = unpack::<3>(func.args); + let list_items = list.kind.into_tuple().unwrap(); + let num_items = list_items.len(); + let mut list_iter = list_items.into_iter(); + + let mut res = init.clone(); + + if let ExprKind::Literal(Literal::String(init_val)) = &init.kind { + if init_val == "__missing" { + match num_items { + 0 => return Err(Error::new(Reason::Expected { + who: Some("tuple".to_string()), + expected: + "to have at least one entry when initial value is not provided" + .to_string(), + found: "empty tuple".to_string(), + }) + .with_span(list.span) + .push_hint("try adding an initial: parameter")), + 1 => { + let item = list_iter.next().unwrap(); + return Ok(item); + } + _ => { + res = list_iter.next().unwrap(); + } + } + } + } - let mut res = None; - for item in list { - res = maybe_binop(res, &["std", "and"], Some(item)); + for item in list_iter { + res = self.fold_expr(Expr::new(ExprKind::FuncCall(FuncCall::new_simple( + func.clone(), + vec![res, item], + ))))?; } - let res = - res.unwrap_or_else(|| Expr::new(ExprKind::Literal(Literal::Boolean(true)))); return Ok(res); } diff --git a/prqlc/prqlc/src/semantic/std.prql b/prqlc/prqlc/src/semantic/std.prql index 51a51f5552b9..24b709f0cd4f 100644 --- a/prqlc/prqlc/src/semantic/std.prql +++ b/prqlc/prqlc/src/semantic/std.prql @@ -121,13 +121,13 @@ let window = func let append = `default_db.bottom` top -> internal append let intersect = `default_db.bottom` top -> ( t = top - join (b = bottom) (tuple_every (tuple_map _eq (tuple_zip t.* b.*))) + join (b = bottom) (tuple_reduce std.and (tuple_map _eq (tuple_zip t.* b.*))) select t.* ) let remove = `default_db.bottom` top -> ( t = top - join side:left (b = bottom) (tuple_every (tuple_map _eq (tuple_zip t.* b.*))) - filter (tuple_every (tuple_map _is_null b.*)) + join side:left (b = bottom) (tuple_reduce std.and (tuple_map _eq (tuple_zip t.* b.*))) + filter (tuple_reduce std.and (tuple_map _is_null b.*)) select t.* ) let loop = func @@ -199,7 +199,7 @@ let as = `noresolve.type` column -> internal std.as let in = pattern value -> internal in ## Tuple functions -let tuple_every = func list -> internal tuple_every +let tuple_reduce = func initial:"__missing" fn list -> internal tuple_reduce let tuple_map = func fn list -> internal tuple_map let tuple_zip = func a b -> internal tuple_zip let _eq = func a -> internal _eq diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__set_ops_remove.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__set_ops_remove.snap index b1b336caf6d3..cd69dacc93d0 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__set_ops_remove.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__debug_lineage__set_ops_remove.snap @@ -18,7 +18,7 @@ frames: table: - default_db - _literal_127 -- - 0:3163-3240 +- - 0:3172-3258 - columns: - !Single name: @@ -43,7 +43,7 @@ frames: table: - default_db - _literal_122 -- - 0:3243-3288 +- - 0:3261-3315 - columns: - !Single name: @@ -74,7 +74,7 @@ frames: name: - t - a - target_id: 207 + target_id: 213 target_name: null inputs: - id: 127 @@ -93,7 +93,7 @@ frames: name: - t - a - target_id: 207 + target_id: 213 target_name: null inputs: - id: 127 @@ -110,7 +110,7 @@ nodes: - id: 122 kind: Array span: 1:173-237 - parent: 189 + parent: 192 - id: 127 kind: Array span: 1:36-55 @@ -135,11 +135,11 @@ nodes: children: - 127 - 155 - parent: 189 + parent: 192 - id: 155 kind: Literal parent: 154 -- id: 178 +- id: 181 kind: Ident ident: !Ident - this @@ -147,7 +147,7 @@ nodes: - a targets: - 136 -- id: 181 +- id: 184 kind: Ident ident: !Ident - that @@ -155,48 +155,48 @@ nodes: - a targets: - 122 -- id: 187 +- id: 190 kind: RqOperator - span: 0:3192-3239 + span: 0:3201-3257 targets: - - 178 - 181 - parent: 189 -- id: 189 + - 184 + parent: 192 +- id: 192 kind: 'TransformCall: Join' - span: 0:3163-3240 + span: 0:3172-3258 children: - 154 - 122 - - 187 - parent: 205 -- id: 197 + - 190 + parent: 211 +- id: 203 kind: Ident - span: 0:5981-5989 + span: 0:6033-6041 ident: !Ident - this - b - a targets: - 122 -- id: 201 +- id: 207 kind: RqOperator - span: 0:3251-3287 + span: 0:3269-3314 targets: - - 197 - - 204 - parent: 205 -- id: 204 + - 203 + - 210 + parent: 211 +- id: 210 kind: Literal - span: 0:5993-5997 -- id: 205 + span: 0:6045-6049 +- id: 211 kind: 'TransformCall: Filter' - span: 0:3243-3288 + span: 0:3261-3315 children: - - 189 - - 201 - parent: 209 -- id: 207 + - 192 + - 207 + parent: 215 +- id: 213 kind: Ident ident: !Ident - this @@ -204,21 +204,21 @@ nodes: - a targets: - 136 - parent: 208 -- id: 208 + parent: 214 +- id: 214 kind: Tuple - span: 0:3298-3301 + span: 0:3325-3328 children: - - 207 - parent: 209 -- id: 209 + - 213 + parent: 215 +- id: 215 kind: 'TransformCall: Select' span: 1:165-238 children: - - 205 - - 208 - parent: 212 -- id: 210 + - 211 + - 214 + parent: 218 +- id: 216 kind: Ident span: 1:244-245 ident: !Ident @@ -226,14 +226,14 @@ nodes: - t - a targets: - - 207 - parent: 212 -- id: 212 + - 213 + parent: 218 +- id: 218 kind: 'TransformCall: Sort' span: 1:239-245 children: - - 209 - - 210 + - 215 + - 216 ast: name: Project stmts: diff --git a/prqlc/prqlc/tests/integration/sql.rs b/prqlc/prqlc/tests/integration/sql.rs index 314742e6916a..611a2b9c9ec3 100644 --- a/prqlc/prqlc/tests/integration/sql.rs +++ b/prqlc/prqlc/tests/integration/sql.rs @@ -6230,6 +6230,54 @@ fn test_import() { "); } +#[test] +fn test_tuple_reduce() { + assert_snapshot!(compile( + r###" +from foo +select { + with_initial = tuple_reduce initial:4 add {1, 2, 3}, + with_initial_one = tuple_reduce initial:4 add {3}, + with_initial_zero = tuple_reduce initial:4 add {}, + no_initial = tuple_reduce add {1, 2, 3}, + no_initial_one = tuple_reduce add {3}, +} + "###, + ) + .unwrap(), @" +SELECT + 4 + 1 + 2 + 3 AS with_initial, + 4 + 3 AS with_initial_one, + 4 AS with_initial_zero, + 1 + 2 + 3 AS no_initial, + 3 AS no_initial_one +FROM + foo + "); +} + +#[test] +fn test_tuple_reduce_err() { + assert_snapshot!(compile( + r###" +from foo +select { + no_initial_err = tuple_reduce add {} +} +"###, + ).unwrap_err(), @r###" + Error: + ╭─[ :4:37 ] + │ + 4 │ no_initial_err = tuple_reduce add {} + │ ─┬ + │ ╰── tuple expected to have at least one entry when initial value is not provided, but found empty tuple + │ + │ Help: try adding an initial: parameter + ───╯ + "###); +} + #[test] fn unstable_ordering() { // https://github.com/PRQL/prql/issues/5053