Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(duckdb): allow casting to geometry from text #10221

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions ibis/backends/duckdb/tests/test_geospatial.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,3 +385,12 @@

assert geo_expr.type().is_geospatial()
assert isinstance(con.execute(geo_expr), gpd.GeoSeries)


def test_geom_from_string(con):
value = ibis.literal("POINT (1 2)")
assert value.type().is_string()

Check warning on line 392 in ibis/backends/duckdb/tests/test_geospatial.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/duckdb/tests/test_geospatial.py#L390-L392

Added lines #L390 - L392 were not covered by tests

expr = value.cast("geometry")
result = con.execute(expr)
assert result == shapely.from_wkt("POINT (1 2)")

Check warning on line 396 in ibis/backends/duckdb/tests/test_geospatial.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/duckdb/tests/test_geospatial.py#L394-L396

Added lines #L394 - L396 were not covered by tests
10 changes: 7 additions & 3 deletions ibis/backends/sql/compilers/duckdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,13 +410,17 @@
return self.f[func](*args)

def visit_Cast(self, op, *, arg, to):
dtype = op.arg.dtype
if to.is_interval():
func = self.f[f"to_{_INTERVAL_SUFFIXES[to.unit.short]}"]
return func(sg.cast(arg, to=self.type_mapper.from_ibis(dt.int32)))
elif to.is_timestamp() and op.arg.dtype.is_integer():
elif to.is_timestamp() and dtype.is_integer():
return self.f.to_timestamp(arg)
elif to.is_geospatial() and op.arg.dtype.is_binary():
return self.f.st_geomfromwkb(arg)
elif to.is_geospatial():
if dtype.is_binary():
return self.f.st_geomfromwkb(arg)

Check warning on line 421 in ibis/backends/sql/compilers/duckdb.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/sql/compilers/duckdb.py#L421

Added line #L421 was not covered by tests
elif dtype.is_string():
return self.f.st_geomfromtext(arg)

Check warning on line 423 in ibis/backends/sql/compilers/duckdb.py

View check run for this annotation

Codecov / codecov/patch

ibis/backends/sql/compilers/duckdb.py#L423

Added line #L423 was not covered by tests

return self.cast(arg, to)

Expand Down
17 changes: 9 additions & 8 deletions ibis/expr/types/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,16 +203,17 @@ def cast(self, target_type: Any) -> Value:
"""
op = ops.Cast(self, to=target_type)

if op.to == self.type():
# noop case if passed type is the same
to = op.to
dtype = self.type()

if to == dtype or (
to.is_geospatial()
and dtype.is_geospatial()
and (dtype.geotype or "geometry") == to.geotype
):
# no-op case if passed type is the same
return self

if op.to.is_geospatial() and not self.type().is_binary():
from_geotype = self.type().geotype or "geometry"
to_geotype = op.to.geotype
if from_geotype == to_geotype:
return self

return op.to_expr()

def try_cast(self, target_type: Any) -> Value:
Expand Down