twpayne / go-geom

Package geom implements efficient geometry types for geospatial applications.
BSD 2-Clause "Simplified" License
818 stars 105 forks source link

encoding/wkb should support NULL values #92

Open twpayne opened 6 years ago

twpayne commented 6 years ago

See #90 and #91 for discussion and initial code.

zachcoleman commented 11 months ago

6 years later... but I have run into another flavor of this I'm wondering if the community or @twpayne have an opinion or experience with this.

I just ran into this problem when dealing w/ optional/nullable geometry types and the Value() implementation null pointer dereferencing.

It happens if you have a data model like so (example in postgres):

...
my_optional_point geometry(POINT, 4326)
...

and an equivalent go model:

type MyRow struct{
...
MyOptionalPoint    *ewkb.Point
...
}

So when using a pointer like this the Value method dereferences this here: https://github.com/twpayne/go-geom/blob/c56c06dcc8693824100b638c85bb3d6026e7a8ef/encoding/ewkb/sql.go#L92

Instead a small check would have to be added additionally such as:

func (p *Point) Value() (driver.Value, error) {
    if p == nil{
        return nil, nil
    }
    if p.Point == nil {
        return nil, nil
    }
    return value(p.Point)
}

and shortening it (to leverage short circuit &&):

func (p *Point) Value() (driver.Value, error) {
    if p != nil && p.Point != nil {
        return value(p.Point)
    }
    return nil, nil
}

So while allowing the encoding to be nullable the entire pointer is not allowed to be nullable. I'm curious if this should be handled on the user side or the Value interface side. There is lengthy discussions on this topic.

zachcoleman commented 11 months ago

If anyone else runs into this issue a workaround on the SQL insertion side is something like this:

func DisambiguatePointer(ptr interface{}) interface{} {
    if !reflect.ValueOf(ptr).IsNil() {
        return ptr
    }
    return nil
}

...
db.Exec(
    context.Background(),
    "INSERT INTO my_table ( my_optional_point ) VALUES ( $1 )",
    DisambiguatePointer(MyRow.MyOptionalPoint),
)
...

This code will successfully take a nil pointer of a type like *ewkb.Point and ensure that Value is not called on it during a SQL insertion or other SQL code.