Skip to content

Commit

Permalink
ExecuteSQL and dataset transaction support (#132)
Browse files Browse the repository at this point in the history
  • Loading branch information
apjoseph authored Jul 26, 2024
1 parent d2a4ab0 commit d610839
Show file tree
Hide file tree
Showing 7 changed files with 408 additions and 6 deletions.
9 changes: 4 additions & 5 deletions .github/workflows/build-gdal.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@ if [ -f CMakeLists.txt ]; then
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTING=OFF \
-DGDAL_USE_CURL=OFF \
-DGDAL_USE_SQLITE3=OFF \
-DGDAL_USE_SQLITE3=ON \
-DGDAL_USE_TIFF_INTERNAL=ON \
-DBUILD_PYTHON_BINDINGS=OFF \
-DENABLE_GNM=OFF \
-DGDAL_USE_XERCESC=OFF \
-DGDAL_USE_GEOS=ON \
-DBUILD_JAVA_BINDINGS=OFF \
-DBUILD_CSHARP_BINDINGS=OFF
-DBUILD_CSHARP_BINDINGS=OFF \
-DOGR_ENABLE_DRIVER_SQLITE=ON

make -j8
make install
Expand All @@ -52,7 +53,6 @@ else
--without-cryptopp \
--without-gnm \
--without-qhull \
--without-sqlite3 \
--without-pcidsk \
--without-lerc \
--without-gif \
Expand All @@ -75,8 +75,7 @@ else
--without-hdf5 \
--without-hdf4 \
--without-ogdi \
--without-exr \
--without-spatialite
--without-exr

make -j4
make install
Expand Down
25 changes: 25 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ func ErrLogger(fn ErrorHandler) interface {
SetGCPsOption
GCPsToGeoTransformOption
RegisterPluginOption
ExecuteSQLOption
StartTransactionOption
CloseResultSetOption
RollbackTransactionOption
CommitTransactionOption
} {
return errorCallback{fn}
}
Expand Down Expand Up @@ -393,6 +398,26 @@ func (ec errorCallback) setRegisterPluginOpt(o *registerPluginOpts) {
o.errorHandler = ec.fn
}

func (ec errorCallback) setExecuteSQLOpt(o *executeSQLOpts) {
o.errorHandler = ec.fn
}

func (ec errorCallback) setReleaseResultSetOpt(o *closeResultSetOpts) {
o.errorHandler = ec.fn
}

func (ec errorCallback) setStartTransactionOpt(o *startTransactionOpts) {
o.errorHandler = ec.fn
}

func (ec errorCallback) setRollbackTransactionOpt(o *rollbackTransactionOpts) {
o.errorHandler = ec.fn
}

func (ec errorCallback) setCommitTransactionOpt(o *commitTransactionOpts) {
o.errorHandler = ec.fn
}

type multiError struct {
errs []error
}
Expand Down
41 changes: 41 additions & 0 deletions godal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,47 @@ OGRLayerH godalCopyLayer(cctx *ctx, GDALDatasetH ds, OGRLayerH layer, char *name
return ret;
}

OGRLayerH godalDatasetExecuteSQL(cctx *ctx, GDALDatasetH ds, char *sql, OGRGeometryH filter, char *dialect) {
godalWrap(ctx);
OGRLayerH ret = GDALDatasetExecuteSQL(ds, sql, filter, dialect);
//a NULL return value is not always an error
godalUnwrap();
return ret;
}

void godalReleaseResultSet(cctx *ctx, GDALDatasetH ds, OGRLayerH rs) {
godalWrap(ctx);
GDALDatasetReleaseResultSet(ds,rs);
godalUnwrap();
}

void godalStartTransaction(cctx *ctx, GDALDatasetH ds, int bForce) {
godalWrap(ctx);
OGRErr gret = GDALDatasetStartTransaction(ds,bForce);
if (gret != OGRERR_NONE) {
forceOGRError(ctx,gret);
}
godalUnwrap();
}

void godalDatasetRollbackTransaction(cctx *ctx, GDALDatasetH ds) {
godalWrap(ctx);
OGRErr gret = GDALDatasetRollbackTransaction(ds);
if (gret != OGRERR_NONE) {
forceOGRError(ctx,gret);
}
godalUnwrap();
}

void godalCommitTransaction(cctx *ctx, GDALDatasetH ds) {
godalWrap(ctx);
OGRErr gret = GDALDatasetCommitTransaction(ds);
if (gret != OGRERR_NONE) {
forceOGRError(ctx,gret);
}
godalUnwrap();
}

void godalLayerGetExtent(cctx *ctx, OGRLayerH layer, OGREnvelope *envelope) {
godalWrap(ctx);
OGRErr gret = OGR_L_GetExtent(layer, envelope, 1);
Expand Down
110 changes: 110 additions & 0 deletions godal.go
Original file line number Diff line number Diff line change
Expand Up @@ -3365,6 +3365,115 @@ func (ds *Dataset) LayerByName(name string) *Layer {
return &Layer{majorObject{C.GDALMajorObjectH(hndl)}}
}

// ResultSet is a Layer generated by Dataset.ExecuteSQL
type ResultSet struct {
Layer
ds *Dataset
closed bool
}

// ExecuteSQL executes an SQL statement against the data store.
// This function may return a nil ResultSet when the SQL statement does not generate any rows to
// return (INSERT/UPDATE/DELETE/CREATE TABLE etc.)
func (ds *Dataset) ExecuteSQL(sql string, opts ...ExecuteSQLOption) (*ResultSet, error) {

eso := executeSQLOpts{}
for _, opt := range opts {
opt.setExecuteSQLOpt(&eso)
}

csql := C.CString(sql)
defer C.free(unsafe.Pointer(csql))

cDialect := C.CString(string(eso.dialect))
defer C.free(unsafe.Pointer(cDialect))

if eso.dialect == "" {
cDialect = nil
}

g := eso.spatialFilter.geom

if g == nil {
g = &Geometry{}
}
cgc := createCGOContext(nil, eso.errorHandler)
hndl := C.godalDatasetExecuteSQL(cgc.cPointer(), ds.handle(), (*C.char)(unsafe.Pointer(csql)), g.handle, (*C.char)(unsafe.Pointer(cDialect)))

if err := cgc.close(); err != nil {
return nil, err
}

layer := Layer{majorObject{C.GDALMajorObjectH(hndl)}}

return &ResultSet{layer, ds, false}, nil
}

// Close releases results of Dataset.ExecuteSQL
func (rs *ResultSet) Close(opts ...CloseResultSetOption) error {
if rs.closed {
return nil
}

crso := closeResultSetOpts{}
for _, opt := range opts {
opt.setReleaseResultSetOpt(&crso)
}
cgc := createCGOContext(nil, crso.errorHandler)
C.godalReleaseResultSet(cgc.cPointer(), rs.ds.handle(), rs.handle())
err := cgc.close()
rs.closed = true
return err
}

// StartTransaction creates a transaction for datasets which support transactions
func (ds *Dataset) StartTransaction(opts ...StartTransactionOption) error {

sto := startTransactionOpts{}
for _, opt := range opts {
opt.setStartTransactionOpt(&sto)
}

cEff := C.int(0)

if sto.bForce == EmulatedTx() {
cEff = C.int(1)
}

cgc := createCGOContext(nil, sto.errorHandler)
C.godalStartTransaction(cgc.cPointer(), ds.handle(), cEff)
err := cgc.close()
return err
}

// RollbackTransaction rolls back a Dataset to its state before the start of the current transaction
func (ds *Dataset) RollbackTransaction(opts ...RollbackTransactionOption) error {

rto := rollbackTransactionOpts{}
for _, opt := range opts {
opt.setRollbackTransactionOpt(&rto)
}

cgc := createCGOContext(nil, rto.errorHandler)
C.godalDatasetRollbackTransaction(cgc.cPointer(), ds.handle())
err := cgc.close()
return err
}

// CommitTransaction commits a transaction for a Dataset that supports transactions
func (ds *Dataset) CommitTransaction(opts ...CommitTransactionOption) error {

cto := commitTransactionOpts{}
for _, opt := range opts {
opt.setCommitTransactionOpt(&cto)
}

cgc := createCGOContext(nil, cto.errorHandler)
C.godalCommitTransaction(cgc.cPointer(), ds.handle())
err := cgc.close()
return err
}

// NewGeometryFromGeoJSON creates a new Geometry from its GeoJSON representation
func NewGeometryFromGeoJSON(geoJSON string, opts ...NewGeometryOption) (*Geometry, error) {
no := &newGeometryOpts{}
Expand Down Expand Up @@ -4226,6 +4335,7 @@ func (cgc cgoContext) close() error {
defer C.free(unsafe.Pointer(cgc.cctx.errMessage))
return errors.New(C.GoString(cgc.cctx.errMessage))
}

if cgc.cctx.handlerIdx != 0 {
defer unregisterErrorHandler(int(cgc.cctx.handlerIdx))
return getErrorHandler(int(cgc.cctx.handlerIdx)).err
Expand Down
5 changes: 5 additions & 0 deletions godal.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ extern "C" {
void godalFeatureSetFieldBinary(cctx *ctx, OGRFeatureH feat, int fieldIndex, int nbBytes, void *value);
OGRLayerH godalCreateLayer(cctx *ctx, GDALDatasetH ds, char *name, OGRSpatialReferenceH sr, OGRwkbGeometryType gtype);
OGRLayerH godalCopyLayer(cctx *ctx, GDALDatasetH ds, OGRLayerH layer, char *name);
OGRLayerH godalDatasetExecuteSQL(cctx *ctx, GDALDatasetH ds, char *sql, OGRGeometryH filter, char *dialect);
void godalReleaseResultSet(cctx *ctx, GDALDatasetH ds, OGRLayerH rs);
void godalStartTransaction(cctx *ctx, GDALDatasetH ds, int bForce);
void godalDatasetRollbackTransaction(cctx *ctx, GDALDatasetH ds);
void godalCommitTransaction(cctx *ctx, GDALDatasetH ds);
void VSIInstallGoHandler(cctx *ctx, const char *pszPrefix, size_t bufferSize, size_t cacheSize);

void godalGetColorTable(GDALRasterBandH bnd, GDALPaletteInterp *interp, int *nEntries, short **entries);
Expand Down
105 changes: 104 additions & 1 deletion godal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ func TestCreate(t *testing.T) {
err = ds.Close(ErrLogger(ehc.ErrorHandler))
assert.NoError(t, err)
} else {
assert.Error(t, err, "godal.Int8 is not supported with GDAL<3.7.0, but godal.Create did not return an error")
assert.Error(t, err, "godal.Int8 is not supported with GDAL<3.7.0, but godal.Create did not return an error")
}
}

Expand Down Expand Up @@ -2543,6 +2543,109 @@ func TestVectorTranslate(t *testing.T) {
assert.Error(t, err)
}

func TestExecuteSQL(t *testing.T) {
poly1Wkt := "POLYGON ((-72.573946 44.254648, -72.573946 44.255163, -72.573076 44.255163, -72.573076 44.254648, -72.573946 44.254648))"
poly2Wkt := "POLYGON ((-72.576558 44.25799, -72.576558 44.258213, -72.576064 44.258213, -72.576064 44.25799, -72.576558 44.25799))"
el := ErrLogger(eh().ErrorHandler)

sqld := DriverName("SQLite")
err := RegisterVector(sqld)
if err != nil {
panic(err)
}

ds, err := CreateVector(sqld, "/vsimem/test.db")
if err != nil {
panic(err)
}
defer ds.Close()

wgs84, _ := NewSpatialRef("EPSG:4326")
tl, err := ds.CreateLayer("test", wgs84, GTPolygon)
assert.NoError(t, err)

rs, err := ds.ExecuteSQL("SELECT 1", SQLiteDialect(), el)
assert.NoError(t, err)
err = rs.Close()
assert.NoError(t, err)

fc, err := tl.FeatureCount()
assert.NoError(t, err)
assert.Equal(t, 0, fc)

ins := fmt.Sprintf("INSERT INTO test VALUES (1,'%s'), (2,'%s')", poly1Wkt, poly2Wkt)

err = ds.StartTransaction(el, EmulatedTx())
assert.NoError(t, err)

rs, err = ds.ExecuteSQL(ins, el, SQLiteDialect())
assert.NoError(t, err)
err = rs.Close(el)
assert.NoError(t, err)

err = ds.RollbackTransaction(el)
assert.NoError(t, err)

fc, _ = tl.FeatureCount()
assert.Equal(t, 0, fc)

err = ds.StartTransaction(el)
assert.NoError(t, err)

rs, err = ds.ExecuteSQL(ins, el)
assert.NoError(t, err)
err = rs.Close(el)
assert.NoError(t, err)

err = ds.CommitTransaction(el)
assert.NoError(t, err)

fc, _ = tl.FeatureCount()
assert.Equal(t, 2, fc)
g, _ := NewGeometryFromWKT("POINT (-72.57349970718771 44.25492684820907)", wgs84)

rs, err = ds.ExecuteSQL("SELECT * FROM test", SpatialFilter(g), SQLiteDialect(), el)
assert.NoError(t, err)
fc, _ = rs.FeatureCount()
assert.Equal(t, 1, fc)
err = rs.Close(el)
assert.NoError(t, err)

rs, err = ds.ExecuteSQL("SELECT * FROM test", OGRSQLDialect(), el)
assert.NoError(t, err)
fc, _ = rs.FeatureCount()
assert.Equal(t, 2, fc)
err = rs.Close(el)
assert.NoError(t, err)

rs, err = ds.ExecuteSQL("SELECT * FROM test", IndirectSQLiteDialect(), el)
assert.NoError(t, err)
fc, _ = rs.FeatureCount()
assert.Equal(t, 2, fc)
err = rs.Close(el)
assert.NoError(t, err)

err = rs.Close()
assert.NoError(t, err)

// test error handling

rs, err = ds.ExecuteSQL("SELECT * FROM i_do_not_exist", el)
assert.Nil(t, rs)
assert.Error(t, err)

err = ds.RollbackTransaction(el)
assert.Error(t, err)

err = ds.CommitTransaction(el)
assert.Error(t, err)

err = ds.StartTransaction(el)
assert.NoError(t, err)
err = ds.StartTransaction(el)
assert.Error(t, err)
}

func TestVectorLayer(t *testing.T) {
rds, _ := Create(Memory, "", 3, Byte, 10, 10)
_, err := rds.CreateLayer("ff", nil, GTPolygon)
Expand Down
Loading

0 comments on commit d610839

Please sign in to comment.