Repository: ibis-project/ibis

Issue:
feat: better tracebacks for type comparison errors
I have an expression something like this, but with more comparisons and more operations, generally (so harder to visually parse)
```python
expr = basics.join(ratings, basics.tconst == ratings.tconst).filter(
                    [_.titleType == "movie", _.numVotes > 10, _.averageRating > 3.5]
                    )
```

When I wrote that, I didn't know that `numVotes` was actually a string column, so I get this error:

```python
         64 if not rlz.comparable(left, right):
    ---> 65     raise TypeError(
         66         f'Arguments with datatype {left.output_dtype} and '
         67         f'{right.output_dtype} are not comparable'
         68     )
         69 super().__init__(left=left, right=right)
    
    TypeError: Arguments with datatype string and int8 are not comparable
```

which is definitely valid, but even if I scroll all the way to the top of the traceback, all I'm going to find is that the offending line is the _single line_:

```python
----> 1 expr = basics.join(ratings, basics.tconst == ratings.tconst).filter(
      2     [_.titleType == "movie", _.numVotes > 10, _.averageRating > 3.5]
      3 )
```

So I'm thinking we can add in the names of these expressions to the error message.
I think we can probably iterate a bit on how the information is presented, but I think even without the exact logical operation that led to this error, giving the user a pointer to which expression names are involved should save a bunch of time when working interactively.

```python
         64 if not rlz.comparable(left, right):
    ---> 65     raise TypeError(
         66         f'Arguments "{left.name}" ({left.output_dtype}) and '
         67         f'"{right.name}" ({right.output_dtype}) are not comparable'
         68     )
         69 super().__init__(left=left, right=right)
    
    TypeError: Arguments "numVotes" (string) and "10" (int8) are not comparable
```

Are there cases where the expression would be unnamed?  Is it weird to have the literal `"10"` presented as the argument name?

Discussion:
github-actions[bot]: ## Test Results
         6 files           6 suites   6m 58s [:stopwatch:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "duration of all tests")
  3 248 tests   3 222 [:heavy_check_mark:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "passed tests")   26 [:zzz:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "skipped / disabled tests") 0 [:x:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "failed tests")
19 434 runs  19 278 [:heavy_check_mark:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "passed tests") 156 [:zzz:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "skipped / disabled tests") 0 [:x:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "failed tests")

Results for commit 4fed9aaa.

[test-results]:data:application/gzip;base64,H4sIALbwV2MC/03MyQ6DIBSF4VcxrLtguID0ZZpbhoTUoUFYmb570Sp1+X8nOSsJcfALuXfq1pGlxNzClYQ5zlNNYH2FOuVtFBxaPpZi7W6c/+0V39W4ahIwDlVoA5/SnA5JZdpumQEBR563zHDdN/vdMqlOudzufX218zjGXINA8M4gIjVaOfAotAfE3gnOJFL2lBQgaCXJ5wvloV0CDgEAAA==


Patch:
diff --git a/ibis/expr/builders.py b/ibis/expr/builders.py
index 91fe8f996..c42eb95bc 100644
--- a/ibis/expr/builders.py
+++ b/ibis/expr/builders.py
@@ -66,7 +66,10 @@ class SimpleCaseBuilder(TypedCaseBuilder):
         result_expr = rlz.any(result_expr)
 
         if not rlz.comparable(self.base, case_expr):
-            raise TypeError('Base expression and passed case are not ' 'comparable')
+            raise TypeError(
+                f'Base expression {rlz._arg_type_error_format(self.base)} and '
+                f'case {rlz._arg_type_error_format(case_expr)} are not comparable'
+            )
 
         cases = list(self.cases)
         cases.append(case_expr)
diff --git a/ibis/expr/operations/logical.py b/ibis/expr/operations/logical.py
index e5c56d615..265577ee0 100644
--- a/ibis/expr/operations/logical.py
+++ b/ibis/expr/operations/logical.py
@@ -63,8 +63,8 @@ class Comparison(Binary):
         """
         if not rlz.comparable(left, right):
             raise TypeError(
-                f'Arguments with datatype {left.output_dtype} and '
-                f'{right.output_dtype} are not comparable'
+                f'Arguments {rlz._arg_type_error_format(left)} and '
+                f'{rlz._arg_type_error_format(right)} are not comparable'
             )
         super().__init__(left=left, right=right)
 
@@ -116,13 +116,13 @@ class Between(Value):
     def __init__(self, arg, lower_bound, upper_bound):
         if not rlz.comparable(arg, lower_bound):
             raise TypeError(
-                f'Argument with datatype {arg.output_dtype} and lower bound '
-                f'with datatype {lower_bound.output_dtype} are not comparable'
+                f'Arguments {rlz._arg_type_error_format(arg)} and '
+                f'{rlz._arg_type_error_format(lower_bound)} are not comparable'
             )
         if not rlz.comparable(arg, upper_bound):
             raise TypeError(
-                f'Argument with datatype {arg.output_dtype} and upper bound '
-                f'with datatype {upper_bound.output_dtype} are not comparable'
+                f'Arguments {rlz._arg_type_error_format(arg)} and '
+                f'{rlz._arg_type_error_format(upper_bound)} are not comparable'
             )
         super().__init__(arg=arg, lower_bound=lower_bound, upper_bound=upper_bound)
 
diff --git a/ibis/expr/operations/relations.py b/ibis/expr/operations/relations.py
index d7e28a0c0..eae216792 100644
--- a/ibis/expr/operations/relations.py
+++ b/ibis/expr/operations/relations.py
@@ -14,7 +14,8 @@ import ibis.expr.types as ir
 import ibis.util as util
 from ibis.common.annotations import attribute
 from ibis.expr.operations.core import Named, Node, Value
-from ibis.expr.operations.logical import ExistsSubquery, NotExistsSubquery
+from ibis.expr.operations.generic import TableColumn
+from ibis.expr.operations.logical import Equals, ExistsSubquery, NotExistsSubquery
 
 _table_names = (f'unbound_table_{i:d}' for i in itertools.count())
 
@@ -582,24 +583,46 @@ def _dedup_join_columns(expr, suffixes: tuple[str, str]):
 
     right_columns = frozenset(right.columns)
     overlap = frozenset(column for column in left.columns if column in right_columns)
+    equal = set()
+
+    if isinstance(op, InnerJoin) and util.all_of(op.predicates, Equals):
+        # For inner joins composed exclusively of equality predicates, we can
+        # avoid renaming columns with colliding names if their values are
+        # guaranteed to be equal due to the predicate. Here we collect a set of
+        # colliding column names that are known to have equal values between
+        # the left and right tables in the join.
+        tables = {op.left, op.right}
+        for pred in op.predicates:
+            if (
+                isinstance(pred.left, TableColumn)
+                and isinstance(pred.right, TableColumn)
+                and {pred.left.table, pred.right.table} == tables
+                and pred.left.name == pred.right.name
+            ):
+                equal.add(pred.left.name)
 
     if not overlap:
         return expr
 
     left_suffix, right_suffix = suffixes
 
+    # Rename columns in the left table that overlap, unless they're known to be
+    # equal to a column in the right
     left_projections = [
         left[column].name(f"{column}{left_suffix}")
-        if column in overlap
+        if column in overlap and column not in equal
         else left[column]
         for column in left.columns
     ]
 
+    # Rename columns in the right table that overlap, dropping any columns that
+    # are known to be equal to those in the left table
     right_projections = [
         right[column].name(f"{column}{right_suffix}")
         if column in overlap
         else right[column]
         for column in right.columns
+        if column not in equal
     ]
     return expr.projection(left_projections + right_projections)
 
diff --git a/ibis/expr/rules.py b/ibis/expr/rules.py
index d234e4da0..c2dd83741 100644
--- a/ibis/expr/rules.py
+++ b/ibis/expr/rules.py
@@ -585,3 +585,12 @@ def window_from(table_ref, win, **kwargs):
         if not isinstance(order_var.output_dtype, dt.Timestamp):
             raise com.IbisInputError(error_msg)
     return win
+
+
+def _arg_type_error_format(op):
+    from ibis.expr.operations.generic import Literal
+
+    if isinstance(op, Literal):
+        return f"Literal({op.value}):{op.output_dtype}"
+    else:
+        return f"{op.name}:{op.output_dtype}"
diff --git a/poetry.lock b/poetry.lock
index 9df45c441..eb637ae60 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -476,6 +476,17 @@ category = "dev"
 optional = false
 python-versions = ">=3.6"
 
+[[package]]
+name = "exceptiongroup"
+version = "1.0.0rc9"
+description = "Backport of PEP 654 (exception groups)"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+test = ["pytest (>=6)"]
+
 [[package]]
 name = "execnet"
 version = "1.9.0"
@@ -1655,8 +1666,8 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
 name = "py-cpuinfo"
-version = "8.0.0"
-description = "Get CPU info with pure Python 2 & 3"
+version = "9.0.0"
+description = "Get CPU info with pure Python"
 category = "dev"
 optional = false
 python-versions = "*"
@@ -1838,7 +1849,7 @@ sql = ["pandas (>=1.0.5)", "pyarrow (>=1.0.0)"]
 
 [[package]]
 name = "pytest"
-version = "7.1.3"
+version = "7.2.0"
 description = "pytest: simple powerful testing with Python"
 category = "dev"
 optional = false
@@ -1847,22 +1858,22 @@ python-versions = ">=3.7"
 [package.dependencies]
 attrs = ">=19.2.0"
 colorama = {version = "*", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
 iniconfig = "*"
 packaging = "*"
 pluggy = ">=0.12,<2.0"
-py = ">=1.8.2"
-tomli = ">=1.0.0"
+tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
 
 [package.extras]
 testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
 
 [[package]]
 name = "pytest-benchmark"
-version = "3.4.1"
+version = "4.0.0"
 description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer."
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+python-versions = ">=3.7"
 
 [package.dependencies]
 py-cpuinfo = "*"
@@ -1901,18 +1912,6 @@ pytest = ">=4.6"
 [package.extras]
 testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
 
-[[package]]
-name = "pytest-forked"
-version = "1.4.0"
-description = "run tests in isolated forked subprocesses"
-category = "dev"
-optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-py = "*"
-pytest = ">=3.10"
-
 [[package]]
 name = "pytest-mock"
 version = "3.10.0"
@@ -1968,7 +1967,7 @@ pytest = ">=3.6"
 
 [[package]]
 name = "pytest-xdist"
-version = "2.5.0"
+version = "3.0.2"
 description = "pytest xdist plugin for distributed testing and loop-on-failing modes"
 category = "dev"
 optional = false
@@ -1977,7 +1976,6 @@ python-versions = ">=3.6"
 [package.dependencies]
 execnet = ">=1.1"
 pytest = ">=6.2.0"
-pytest-forked = "*"
 
 [package.extras]
 psutil = ["psutil (>=3.0)"]
@@ -2275,7 +2273,7 @@ sqlcipher = ["sqlcipher3_binary"]
 
 [[package]]
 name = "sqlglot"
-version = "9.0.3"
+version = "9.0.4"
 description = "An easily customizable SQL parser and transpiler"
 category = "main"
 optional = true
@@ -2557,7 +2555,7 @@ visualization = ["graphviz"]
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.8"
-content-hash = "8fdb5af8b251944a8b32adf4fae3e113cb9dc75c35c1df2f63affd2302c7bd55"
+content-hash = "f2a695fd64e19beed5f01c9c1b113c0d732f756b195a154eec8d38ca7371cdf3"
 
 [metadata.files]
 absolufy-imports = [
@@ -3171,6 +3169,10 @@ entrypoints = [
     {file = "entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f"},
     {file = "entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4"},
 ]
+exceptiongroup = [
+    {file = "exceptiongroup-1.0.0rc9-py3-none-any.whl", hash = "sha256:2e3c3fc1538a094aab74fad52d6c33fc94de3dfee3ee01f187c0e0c72aec5337"},
+    {file = "exceptiongroup-1.0.0rc9.tar.gz", hash = "sha256:9086a4a21ef9b31c72181c77c040a074ba0889ee56a7b289ff0afb0d97655f96"},
+]
 execnet = [
     {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"},
     {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"},
@@ -3838,7 +3840,8 @@ py = [
     {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
 ]
 py-cpuinfo = [
-    {file = "py-cpuinfo-8.0.0.tar.gz", hash = "sha256:5f269be0e08e33fd959de96b34cd4aeeeacac014dd8305f70eb28d06de2345c5"},
+    {file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"},
+    {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"},
 ]
 py4j = [
     {file = "py4j-0.10.9.5-py2.py3-none-any.whl", hash = "sha256:52d171a6a2b031d8a5d1de6efe451cf4f5baff1a2819aabc3741c8406539ba04"},
@@ -4003,12 +4006,12 @@ pyspark = [
     {file = "pyspark-3.3.1.tar.gz", hash = "sha256:e99fa7de92be406884bfd831c32b9306a3a99de44cfc39a2eefb6ed07445d5fa"},
 ]
 pytest = [
-    {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"},
-    {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"},
+    {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"},
+    {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"},
 ]
 pytest-benchmark = [
-    {file = "pytest-benchmark-3.4.1.tar.gz", hash = "sha256:40e263f912de5a81d891619032983557d62a3d85843f9a9f30b98baea0cd7b47"},
-    {file = "pytest_benchmark-3.4.1-py2.py3-none-any.whl", hash = "sha256:36d2b08c4882f6f997fd3126a3d6dfd70f3249cde178ed8bbc0b73db7c20f809"},
+    {file = "pytest-benchmark-4.0.0.tar.gz", hash = "sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1"},
+    {file = "pytest_benchmark-4.0.0-py3-none-any.whl", hash = "sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6"},
 ]
 pytest-clarity = [
     {file = "pytest-clarity-1.0.1.tar.gz", hash = "sha256:505fe345fad4fe11c6a4187fe683f2c7c52c077caa1e135f3e483fe112db7772"},
@@ -4017,10 +4020,6 @@ pytest-cov = [
     {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"},
     {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"},
 ]
-pytest-forked = [
-    {file = "pytest-forked-1.4.0.tar.gz", hash = "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e"},
-    {file = "pytest_forked-1.4.0-py3-none-any.whl", hash = "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8"},
-]
 pytest-mock = [
     {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"},
     {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"},
@@ -4038,8 +4037,8 @@ pytest-repeat = [
     {file = "pytest_repeat-0.9.1-py2.py3-none-any.whl", hash = "sha256:4474a7d9e9137f6d8cc8ae297f8c4168d33c56dd740aa78cfffe562557e6b96e"},
 ]
 pytest-xdist = [
-    {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"},
-    {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"},
+    {file = "pytest-xdist-3.0.2.tar.gz", hash = "sha256:688da9b814370e891ba5de650c9327d1a9d861721a524eb917e620eec3e90291"},
+    {file = "pytest_xdist-3.0.2-py3-none-any.whl", hash = "sha256:9feb9a18e1790696ea23e1434fa73b325ed4998b0e9fcb221f16fd1945e6df1b"},
 ]
 python-dateutil = [
     {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
@@ -4432,8 +4431,8 @@ sqlalchemy = [
     {file = "SQLAlchemy-1.4.42.tar.gz", hash = "sha256:177e41914c476ed1e1b77fd05966ea88c094053e17a85303c4ce007f88eff363"},
 ]
 sqlglot = [
-    {file = "sqlglot-9.0.3-py3-none-any.whl", hash = "sha256:0be413c6923c83664cb900cf00413f823a53c9965420e0a00fe5fbcecc69ffb6"},
-    {file = "sqlglot-9.0.3.tar.gz", hash = "sha256:8f591b119798b01158218c09cb364fbfc5ebd7f685ec171619e1ab99f6a6ede8"},
+    {file = "sqlglot-9.0.4-py3-none-any.whl", hash = "sha256:f7704c3e0bb9151c43f8676cf062cbd387dcdfd51650c7bd89b2198be0bf13fe"},
+    {file = "sqlglot-9.0.4.tar.gz", hash = "sha256:bc94ba5add2a888d085104e12f730ed93d7b7bed9def9ba53da372543d65e13f"},
 ]
 stack-data = [
     {file = "stack_data-0.5.1-py3-none-any.whl", hash = "sha256:5120731a18ba4c82cefcf84a945f6f3e62319ef413bfc210e32aca3a69310ba2"},
diff --git a/pyproject.toml b/pyproject.toml
index 540d024db..8fdf6dfc9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -79,16 +79,15 @@ pyupgrade = ">=2.26.0,<4"
 [tool.poetry.group.test.dependencies]
 filelock = ">=3.7.0,<4"
 pyarrow = ">=1,<10"
-# pytest-benchmark is broken with pytest>=7.2
-pytest = ">=7.0.0,<7.2"
-pytest-benchmark = ">=3.4.1,<4"
+pytest = ">=7.0.0,<8"
+pytest-benchmark = ">=3.4.1,<5"
 pytest-clarity = ">=1.0.1,<2"
 pytest-cov = ">=3.0.0,<5"
 pytest-mock = ">=3.6.1,<4"
 pytest-profiling = ">=1.7.0,<2"
 pytest-randomly = ">=3.10.1,<4"
 pytest-repeat = ">=0.9.1,<0.10"
-pytest-xdist = ">=2.3.0,<3"
+pytest-xdist = ">=2.3.0,<4"
 sqlalchemy = ">=1.4,<2.0"
 
 [tool.poetry.group.docs.dependencies]
diff --git a/requirements.txt b/requirements.txt
index a2c4f5b36..ea7665788 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -25,6 +25,7 @@ docformatter==1.5.0 ; python_version >= "3.8" and python_version < "4.0"
 docstring-parser==0.13 ; python_version >= "3.8" and python_version < "4.0"
 dunamai==1.13.2 ; python_version >= "3.8" and python_version < "4.0"
 entrypoints==0.4 ; python_version >= "3.8" and python_version < "4.0"
+exceptiongroup==1.0.0rc9 ; python_version >= "3.8" and python_version < "3.11"
 execnet==1.9.0 ; python_version >= "3.8" and python_version < "4.0"
 executing==1.1.1 ; python_version >= "3.8" and python_version < "4.0"
 fastjsonschema==2.16.2 ; python_version >= "3.8" and python_version < "4.0"
@@ -97,8 +98,8 @@ prompt-toolkit==3.0.31 ; python_version >= "3.8" and python_version < "4.0"
 psutil==5.9.3 ; python_version >= "3.8" and python_version < "4.0"
 ptyprocess==0.7.0 ; python_version >= "3.8" and python_version < "4.0" and sys_platform != "win32"
 pure-eval==0.2.2 ; python_version >= "3.8" and python_version < "4.0"
-py-cpuinfo==8.0.0 ; python_version >= "3.8" and python_version < "4.0"
-py==1.11.0 ; python_version >= "3.8" and python_version < "4.0"
+py-cpuinfo==9.0.0 ; python_version >= "3.8" and python_version < "4.0"
+py==1.11.0 ; python_version >= "3.8" and python_version < "4.0" and implementation_name == "pypy"
 pyarrow==9.0.0 ; python_version >= "3.8" and python_version < "4.0"
 pycodestyle==2.9.1 ; python_version >= "3.8" and python_version < "4.0"
 pycparser==2.21 ; python_version >= "3.8" and python_version < "4.0" and implementation_name == "pypy"
@@ -108,16 +109,15 @@ pygments==2.13.0 ; python_version >= "3.8" and python_version < "4.0"
 pymdown-extensions==9.7 ; python_version >= "3.8" and python_version < "4.0"
 pyparsing==3.0.9 ; python_version >= "3.8" and python_version < "4.0"
 pyrsistent==0.18.1 ; python_version >= "3.8" and python_version < "4.0"
-pytest-benchmark==3.4.1 ; python_version >= "3.8" and python_version < "4.0"
+pytest-benchmark==4.0.0 ; python_version >= "3.8" and python_version < "4.0"
 pytest-clarity==1.0.1 ; python_version >= "3.8" and python_version < "4.0"
 pytest-cov==4.0.0 ; python_version >= "3.8" and python_version < "4.0"
-pytest-forked==1.4.0 ; python_version >= "3.8" and python_version < "4.0"
 pytest-mock==3.10.0 ; python_version >= "3.8" and python_version < "4.0"
 pytest-profiling==1.7.0 ; python_version >= "3.8" and python_version < "4.0"
 pytest-randomly==3.12.0 ; python_version >= "3.8" and python_version < "4.0"
 pytest-repeat==0.9.1 ; python_version >= "3.8" and python_version < "4.0"
-pytest-xdist==2.5.0 ; python_version >= "3.8" and python_version < "4.0"
-pytest==7.1.3 ; python_version >= "3.8" and python_version < "4.0"
+pytest-xdist==3.0.2 ; python_version >= "3.8" and python_version < "4.0"
+pytest==7.2.0 ; python_version >= "3.8" and python_version < "4.0"
 python-dateutil==2.8.2 ; python_version >= "3.8" and python_version < "4.0"
 pytkdocs==0.16.1 ; python_version >= "3.8" and python_version < "4.0"
 pytkdocs[numpy-style]==0.16.1 ; python_version >= "3.8" and python_version < "4.0"


Test patch:
diff --git a/ibis/backends/tests/test_join.py b/ibis/backends/tests/test_join.py
index 68a2c28e8..af30a9293 100644
--- a/ibis/backends/tests/test_join.py
+++ b/ibis/backends/tests/test_join.py
@@ -86,19 +86,27 @@ def test_mutating_join(backend, batting, awards_players, how):
     result_order = ['playerID', 'yearID', 'lgID', 'stint']
 
     expr = left.join(right, predicate, how=how)
-    result = (
-        expr.execute()
-        .fillna(np.nan)
-        .assign(
-            playerID=lambda df: df.playerID_x.where(
-                df.playerID_x.notnull(),
-                df.playerID_y,
+    if how == "inner":
+        result = (
+            expr.execute()
+            .fillna(np.nan)[left.columns]
+            .sort_values(result_order)
+            .reset_index(drop=True)
+        )
+    else:
+        result = (
+            expr.execute()
+            .fillna(np.nan)
+            .assign(
+                playerID=lambda df: df.playerID_x.where(
+                    df.playerID_x.notnull(),
+                    df.playerID_y,
+                )
             )
+            .drop(['playerID_x', 'playerID_y'], axis=1)[left.columns]
+            .sort_values(result_order)
+            .reset_index(drop=True)
         )
-        .drop(['playerID_x', 'playerID_y'], axis=1)[left.columns]
-        .sort_values(result_order)
-        .reset_index(drop=True)
-    )
 
     expected = (
         check_eq(
diff --git a/ibis/tests/expr/test_table.py b/ibis/tests/expr/test_table.py
index e8e5f0597..a9f1e0e76 100644
--- a/ibis/tests/expr/test_table.py
+++ b/ibis/tests/expr/test_table.py
@@ -937,10 +937,9 @@ def test_self_join_no_view_convenience(table):
     # column names to join on rather than referentially-valid expressions
 
     result = table.join(table, [('g', 'g')])
-
-    assert result.columns == [f"{column}_x" for column in table.columns] + [
-        f"{column}_y" for column in table.columns
-    ]
+    expected_cols = [f"{c}_x" if c != 'g' else 'g' for c in table.columns]
+    expected_cols.extend(f"{c}_y" for c in table.columns if c != 'g')
+    assert result.columns == expected_cols
 
 
 def test_join_reference_bug(con):
@@ -1017,7 +1016,7 @@ def test_cross_join_multiple(table):
     assert joined.equals(expected)
 
 
-def test_filter_join(table):
+def test_filter_join():
     table1 = ibis.table({'key1': 'string', 'key2': 'string', 'value1': 'double'})
     table2 = ibis.table({'key3': 'string', 'value2': 'double'})
 
@@ -1027,17 +1026,27 @@ def test_filter_join(table):
     repr(filtered)
 
 
-def test_join_overlapping_column_names(table):
+def test_inner_join_overlapping_column_names():
     t1 = ibis.table([('foo', 'string'), ('bar', 'string'), ('value1', 'double')])
     t2 = ibis.table([('foo', 'string'), ('bar', 'string'), ('value2', 'double')])
 
     joined = t1.join(t2, 'foo')
     expected = t1.join(t2, t1.foo == t2.foo)
     assert_equal(joined, expected)
+    assert joined.columns == ["foo", "bar_x", "value1", "bar_y", "value2"]
 
     joined = t1.join(t2, ['foo', 'bar'])
     expected = t1.join(t2, [t1.foo == t2.foo, t1.bar == t2.bar])
     assert_equal(joined, expected)
+    assert joined.columns == ["foo", "bar", "value1", "value2"]
+
+    # Equality predicates don't have same name, need to rename
+    joined = t1.join(t2, t1.foo == t2.bar)
+    assert joined.columns == ["foo_x", "bar_x", "value1", "foo_y", "bar_y", "value2"]
+
+    # Not all predicates are equality, still need to rename
+    joined = t1.join(t2, ["foo", t1.value1 < t2.value2])
+    assert joined.columns == ["foo_x", "bar_x", "value1", "foo_y", "bar_y", "value2"]
 
 
 def test_join_key_alternatives(con):
diff --git a/ibis/tests/expr/test_value_exprs.py b/ibis/tests/expr/test_value_exprs.py
index 6cd36fb1a..d798a62e7 100644
--- a/ibis/tests/expr/test_value_exprs.py
+++ b/ibis/tests/expr/test_value_exprs.py
@@ -1623,3 +1623,29 @@ def test_numpy_ufuncs_dont_cast_columns():
     x = np.int64(1)
     for expr in [t.a + x, x + t.a]:
         assert expr.equals(t.a + ibis.literal(x))
+
+
+@pytest.mark.parametrize(
+    'operation',
+    [
+        operator.lt,
+        operator.gt,
+        operator.ge,
+        operator.le,
+        operator.eq,
+        operator.ne,
+    ],
+)
+def test_logical_comparison_rlz_incompatible_error(table, operation):
+    with pytest.raises(TypeError) as exc_info:
+        operation(table.b, "foo")
+
+    assert "b:int16 and Literal(foo):string" in str(exc_info.value)
+
+
+def test_case_rlz_incompatible_error(table):
+    case, result = "foo", table.a
+    with pytest.raises(TypeError) as exc_info:
+        table.g.case().when(case == result, result).end()
+
+    assert "a:int8 and Literal(foo):string" in str(exc_info.value)