Skip to content

Commit

Permalink
feat: Allow join side to take an ident that resolves to a literal (#4499
Browse files Browse the repository at this point in the history
)

Co-authored-by: Maximilian Roos <5635139+max-sixty@users.noreply.github.com>
  • Loading branch information
kgutwin and max-sixty authored May 23, 2024
1 parent 8fe98fd commit 0158d3f
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 8 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
generates Markdown documentation from `.prql` files. (@vanillajonathan,
#4152).
- Add `prqlc lex` command to the CLI (@max-sixty)
- Join `side` parameter can take a reference that resolves to a literal (note:
this is an experimental feature which may change in the future) (@kgutwin,
#4499)

**Fixes**:

Expand Down
38 changes: 30 additions & 8 deletions prqlc/prqlc/src/semantic/resolver/transforms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,42 @@ impl Resolver<'_> {

let side = {
let span = side.span;
let ident = side.try_cast(ExprKind::into_ident, Some("side"), "ident")?;
let ident =
side.clone()
.try_cast(ExprKind::into_ident, Some("side"), "ident")?;

// first try to match the raw ident string as a bare word
match ident.to_string().as_str() {
"inner" => JoinSide::Inner,
"left" => JoinSide::Left,
"right" => JoinSide::Right,
"full" => JoinSide::Full,

found => {
return Err(Error::new(Reason::Expected {
who: Some("`side`".to_string()),
expected: "inner, left, right or full".to_string(),
found: found.to_string(),
})
.with_span(span))
_ => {
// if that fails, fold the ident and try treating the result as a literal
// this allows the join side to be passed as a function parameter
// NOTE: this is temporary, pending discussions and implementation, tracked in #4501
let folded = self.fold_expr(side)?.try_cast(
ExprKind::into_literal,
Some("side"),
"string literal",
)?;

match folded.to_string().as_str() {
"\"inner\"" => JoinSide::Inner,
"\"left\"" => JoinSide::Left,
"\"right\"" => JoinSide::Right,
"\"full\"" => JoinSide::Full,

_ => {
return Err(Error::new(Reason::Expected {
who: Some("`side`".to_string()),
expected: "inner, left, right or full".to_string(),
found: folded.to_string(),
})
.with_span(span))
}
}
}
}
};
Expand Down
74 changes: 74 additions & 0 deletions prqlc/prqlc/tests/integration/sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2385,6 +2385,80 @@ fn test_join() {
compile("from x | join y {==x.id}").unwrap_err();
}

#[test]
fn test_join_side_literal() {
assert_snapshot!((compile(r###"
let my_side = "right"
from x
join y (==id) side:my_side
"###).unwrap()), @r###"
SELECT
x.*,
y.*
FROM
x
RIGHT JOIN y ON x.id = y.id
"###);
}

#[test]
fn test_join_side_literal_err() {
assert_snapshot!((compile(r###"
let my_side = 42
from x
join y (==id) side:my_side
"###).unwrap_err()), @r###"
Error:
╭─[:5:24]
5 │ join y (==id) side:my_side
│ ───┬───
│ ╰───── `side` expected inner, left, right or full, but found 42
───╯
"###);
}

#[test]
fn test_join_side_literal_via_func() {
assert_snapshot!((compile(r###"
let my_join = func m <relation> c <scalar> s <text>:"right" tbl <relation> -> (
join side:_param.s m (c == that.k) tbl
)
from x
my_join default_db.y this.id s:"left"
"###).unwrap()), @r###"
SELECT
x.*,
y.*
FROM
x
LEFT JOIN y ON x.id = y.k
"###);
}

#[test]
fn test_join_side_literal_via_func_err() {
assert_snapshot!((compile(r###"
let my_join = func m <relation> c <scalar> s <text>:"right" tbl <relation> -> (
join side:_param.s m (c == that.k) tbl
)
from x
my_join default_db.y this.id s:"four"
"###).unwrap_err()), @r###"
Error:
╭─[:3:25]
3 │ join side:_param.s m (c == that.k) tbl
│ ─┬
│ ╰── `side` expected inner, left, right or full, but found "four"
───╯
"###);
}

#[test]
fn test_from_json() {
// Test that the SQL generated from the JSON of the PRQL is the same as the raw PRQL
Expand Down

0 comments on commit 0158d3f

Please sign in to comment.