Repository: ibis-project/ibis

Issue:
mutate geo column causes TypeError: geo_equals() got an unexpected keyword argument 'cache'
Using `mutate` with the same GEO column more than once causes an error.  The error happens when printing or using `compile()`.  If I only reference the same column once, the result is correct.

I'm using this with omniscidb.

The `geo_equals` function does not have a cache arg: https://github.com/ibis-project/ibis/blob/cc0ebb6242e75670d282476d6ea10bf5b8583fc4/ibis/expr/api.py#L1845

```
tm = t.mutate([
    t['Location'].y().name('Location_Latitude'),
    t['Location'].y().name('Latitude')])
tm.compile()
```

```
/opt/conda/lib/python3.7/site-packages/ibis/expr/operations.py in all_equal(left, right, cache)
    171 
    172     if hasattr(left, 'equals'):
--> 173         return left.equals(right, cache=cache)
    174     return left == right
    175 

TypeError: geo_equals() got an unexpected keyword argument 'cache'
```



Discussion:
@xmnlab the commit I made above does not fix the problem, just brings it to the next problem `ValueError: The truth value of an Ibis expression is not defined`.

Now I'm wondering if it's conflating equals with geo_equals, where equals means something different as used in `all_equals` in `operations.py` above.  Do you have any hints how this might be addressed?

https://github.com/ibis-project/ibis/blob/b9f344ba39cacc98136f63d8f48f445052b7b2af/ibis/expr/api.py#L2562

Removing that api mapping of `equals` to `geo_equals` fixes my cases because `all_equals` checks for an `equals` method, else uses `==`. However, that would break alchemy which defines GeoEquals and api binds to `equals`. I don't see how to fix this without changing the api to call the method something like `geo_equals`.
@mikehinchey I can check that today at the end of the day.

Resolving patch:
diff --git a/ibis/expr/api.py b/ibis/expr/api.py
--- a/ibis/expr/api.py
+++ b/ibis/expr/api.py
@@ -2571,7 +2571,7 @@ def geo_transform(arg, srid):
     'd_within': geo_d_within,
     'end_point': geo_end_point,
     'envelope': geo_envelope,
-    'equals': geo_equals,
+    'geo_equals': geo_equals,
     'geometry_n': geo_geometry_n,
     'geometry_type': geo_geometry_type,
     'intersection': geo_intersection,
diff --git a/ibis/expr/types.py b/ibis/expr/types.py
--- a/ibis/expr/types.py
+++ b/ibis/expr/types.py
@@ -53,7 +53,7 @@ def __hash__(self):
 
     def __bool__(self):
         raise ValueError(
-            "The truth value of an Ibis expression is not " "defined"
+            "The truth value of an Ibis expression is not defined"
         )
 
     __nonzero__ = __bool__


Test patch:
diff --git a/ibis/backends/postgres/tests/test_postgis.py b/ibis/backends/postgres/tests/test_postgis.py
--- a/ibis/backends/postgres/tests/test_postgis.py
+++ b/ibis/backends/postgres/tests/test_postgis.py
@@ -14,7 +14,7 @@ def test_load_geodata(con):
 
 
 def test_empty_select(geotable):
-    expr = geotable[geotable.geo_point.equals(geotable.geo_linestring)]
+    expr = geotable[geotable.geo_point.geo_equals(geotable.geo_linestring)]
     result = expr.execute()
     assert len(result) == 0
 
@@ -149,9 +149,16 @@ def test_geo_disjoint(geotable):
 
 
 def test_geo_equals(geotable):
-    expr = geotable.geo_point.equals(geotable.geo_point)
+    # note: == operation just works for comparison between same datatypes
+    expr = geotable.geo_point == geotable.geo_point
     assert expr.execute().all()
 
+    expr = geotable.geo_point.geo_equals(geotable.geo_point)
+    assert expr.execute().all()
+
+    expr = geotable.geo_point.geo_equals(geotable.geo_linestring)
+    assert not expr.execute().any()
+
 
 def test_geo_geometry_n(geotable, gdf):
     expr = geotable.geo_multipolygon.geometry_n(1)  # PostGIS is one-indexed.
diff --git a/ibis/backends/tests/test_geospatial.py b/ibis/backends/tests/test_geospatial.py
--- a/ibis/backends/tests/test_geospatial.py
+++ b/ibis/backends/tests/test_geospatial.py
@@ -686,3 +686,43 @@ def test_geo_ops_smoke(backend, fn_expr):
     """Smoke tests for geo spatial operations."""
     geo_table = backend.geo
     assert fn_expr(geo_table).compile() != ''
+
+
+@pytest.mark.only_on_backends(["postgres"])
+def test_geo_equals(backend, geo):
+    # Fix https://github.com/ibis-project/ibis/pull/2956
+    expr = geo.mutate(
+        [
+            geo.geo_point.y().name('Location_Latitude'),
+            geo.geo_point.y().name('Latitude'),
+        ]
+    )
+
+    result = str(expr.compile().compile())
+
+    assert result == (
+        'SELECT t0.id, ST_AsEWKB(t0.geo_point) AS geo_point, '
+        'ST_AsEWKB(t0.geo_linestring) AS geo_linestring, '
+        'ST_AsEWKB(t0.geo_polygon) AS geo_polygon, '
+        'ST_AsEWKB(t0.geo_multipolygon) AS geo_multipolygon, '
+        'ST_Y(t0.geo_point) AS "Location_Latitude", '
+        'ST_Y(t0.geo_point) AS "Latitude" \n'
+        'FROM geo AS t0'
+    )
+
+    # simple test using ==
+    expected = 'SELECT t0.geo_point = t0.geo_point AS tmp \nFROM geo AS t0'
+    expr = geo.geo_point == geo.geo_point
+    assert str(expr.compile().compile()) == expected
+    assert expr.execute().all()
+
+    # using geo_equals
+    expected = (
+        'SELECT ST_Equals(t0.geo_point, t0.geo_point) AS tmp \nFROM geo AS t0'
+    )
+    expr = geo.geo_point.geo_equals(geo.geo_point)
+    assert str(expr.compile().compile()) == expected
+    assert expr.execute().all()
+
+    # equals returns a boolean object
+    assert geo.geo_point.equals(geo.geo_point)