Skip to content

Commit

Permalink
feat(api): add positional joins (#9533)
Browse files Browse the repository at this point in the history
Co-authored-by: Phillip Cloud <417981+cpcloud@users.noreply.github.com>
  • Loading branch information
NickCrews and cpcloud authored Jul 15, 2024
1 parent 43b116b commit 85ea9da
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 5 deletions.
3 changes: 3 additions & 0 deletions ibis/backends/pandas/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,9 @@ def visit(cls, op: PandasJoin, how, left, right, left_on, right_on):
if how == "cross":
assert not left_on and not right_on
return cls.merge(left, right, how="cross")
elif how == "positional":
assert not left_on and not right_on
return cls.concat([left, right], axis=1)
elif how == "anti":
df = cls.merge(
left,
Expand Down
3 changes: 3 additions & 0 deletions ibis/backends/polars/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,9 @@ def join(op, **kw):
left = translate(op.left, **kw)
right = translate(op.right, **kw)

if how == "positional":
return pl.concat([left, right], how="horizontal")

# workaround required for https://github.com/pola-rs/polars/issues/13130
prefix = gen_name("on")
left_on = {f"{prefix}_{i}": translate(v, **kw) for i, v in enumerate(op.left_on)}
Expand Down
10 changes: 6 additions & 4 deletions ibis/backends/sql/compilers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1259,6 +1259,7 @@ def visit_JoinLink(self, op, *, how, table, predicates):
"asof": "asof",
"any_left": "left",
"any_inner": None,
"positional": None,
}
kinds = {
"any_left": "any",
Expand All @@ -1271,11 +1272,12 @@ def visit_JoinLink(self, op, *, how, table, predicates):
"anti": "anti",
"cross": "cross",
"outer": "outer",
"positional": "positional",
}
assert (
predicates or how == "cross"
), "expected non-empty predicates when not a cross join"

assert predicates or how in {
"cross",
"positional",
}, "expected non-empty predicates when not a cross join"
on = sg.and_(*predicates) if predicates else None
return sge.Join(this=table, side=sides[how], kind=kinds[how], on=on)

Expand Down
30 changes: 30 additions & 0 deletions ibis/backends/tests/test_join.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,3 +385,33 @@ def test_join_conflicting_columns(backend, con):
}
)
backend.assert_frame_equal(result, expected)


@pytest.mark.never(
[
"bigquery",
"clickhouse",
"datafusion",
"druid",
"exasol",
"flink",
"impala",
"mssql",
"mysql",
"oracle",
"postgres",
"pyspark",
"risingwave",
"snowflake",
"sqlite",
"trino",
],
reason="Users can implement this with ibis.row_number(): https://github.com/ibis-project/ibis/issues/9486",
)
def test_positional_join(backend, con):
t1 = ibis.memtable({"x": [1, 2, 3]})
t2 = ibis.memtable({"x": [3, 2, 1]})
j = t1.join(t2, how="positional")
result = con.execute(j)
expected = pd.DataFrame({"x": [1, 2, 3], "x_right": [3, 2, 1]})
backend.assert_frame_equal(result, expected)
1 change: 1 addition & 0 deletions ibis/expr/operations/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ def values(self):
"any_inner",
"any_left",
"cross",
"positional",
]


Expand Down
2 changes: 1 addition & 1 deletion ibis/expr/types/joins.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ def join(
# bind and dereference the predicates
preds = prepare_predicates(chain, right, predicates)
preds = flatten_predicates(preds)
if not preds and how != "cross":
if not preds and how not in {"cross", "positional"}:
# if there are no predicates, default to every row matching unless
# the join is a cross join, because a cross join already has this
# behavior
Expand Down

0 comments on commit 85ea9da

Please sign in to comment.