mattn / go-sqlite3

sqlite3 driver for go using database/sql
http://mattn.github.io/go-sqlite3
MIT License
8.04k stars 1.11k forks source link

access to C struct to support statically linking spatialite #1155

Open vcabbage opened 1 year ago

vcabbage commented 1 year ago

Background

Spatialite can be initialized in two ways. Most commonly mod_spatialite is loaded via sqlite APIs, which eventually use dlopen to dynamically load the shared library. This doesn't allow for statically linking spatialite. Additionally if sqlite is statically linked it can't load extensions because dlopen doesn't work.

The other option is to link libspatialite into the binary and initialize spatialite on the sqlite3 handle via spatialite's C API.

Roughly:

var db *C.sqlite3
C.sqlite3_open_v2(..., &db, ...)

cache := C.spatialite_alloc_connection()
C.spatialite_init_ex(db, cache, 0)

This method does allow for static linking.

Request

I have a need to build fully static binaries including sqlite/spatialite. I don't think it would be reasonable to ask that this be directly supported in this library since spatialite isn't designed to be self contained and easy to compile like the sqlite amalgamation. It would be helpful to be able to access the *C.sqlite3 struct without forking the library though.

Would you consider providing a way to access *C.sqlite3 from the connection? Even if it's a method like UnsafeDontAskForSupport() *C.sqlite3 :grinning:.

Thanks for the library and considering this request!


Aside: If you're wondering about the potential leak of the allocation by C.spatialite_alloc_connection(), my solution is to wrap the go-sqlite3 driver.Driver and driver.Conn so that it can be freed when the connection is closed.

riyaz-ali commented 1 year ago

+1

Having access to underlying connection object (*C.struct_sqlite3) allows to register extensions built externally, a use-case I ran into when building sqlean.go.

There I've managed to use reflect package access conn.db field from struct SQLiteConn, like:

func init() {
  sql.Register("sqlean", &sqlite3.SQLiteDriver{
    ConnectHook: func(conn *sqlite3.SQLiteConn) error {
      // db points to the underlying *C.struct_sqlite3
      var db = reflect.Indirect(reflect.ValueOf(conn)).FieldByName("db").UnsafePointer()
      return nil
    },
  })
}

But this rely on reflect and, I feel, is a bit fragile (what if the layout or name of the field changes?)

vcabbage commented 1 year ago

@riyaz-ali I've been doing something similar and agree that it's fragile. FWIW, I also put in this guard as a best effort way of detecting changes instead of silently corrupting memory or panicking.

// Check the field is a *C.sqlite3. C types are unusual in that the
// reflect.Types can't be compared directly due to having the namespace of
// the importing package. This appears to be the most reliable way to
// validate the type in this case.
field := reflect.ValueOf(c).Elem().FieldByName("db")
if field.Type().String() != "*sqlite3._Ctype_struct_sqlite3" {
    return nil, fmt.Errorf(`field "db" %s not *C.sqlite3`, field.Type())
}