go-sqlite3 icon indicating copy to clipboard operation
go-sqlite3 copied to clipboard

SQLiteConn: Add 'Raw' method to give access to underlying sqlite3 con…

Open gwd opened this issue 6 years ago • 12 comments

…text structure

This is necessary, for instance, to allow users of the package to make their own CGo callbacks, and have the underlying sqlite3 library call those C functions directly.

Note that the intention from the sqlite3 library seems to be that there should be a measure of binary compatibility between versions: even extensions compiled against an older different version of sqlite3 should be able to call sqlite3_create_function with no problems. So this should work even if users of the package have a slightly different version of the sqlite3.h header.

Note that the code itself was written by @rittneje. I tested it and wrote the comments.

Signed-off-by: George Dunlap [email protected]

v2: Just copy @rittneje's code exactly:

  • Grab the lock while the function is running
  • Replace the error code with our own if it's of type ErrNo

gwd avatar Feb 09 '20 17:02 gwd

If you give me some guidance, I could write a test case for adding a Cgo callback. It seems like it might be easier to manage in its own file; sqlite_raw_test.go maybe?

gwd avatar Feb 09 '20 17:02 gwd

You cannot do cgo directly in a test file unfortunately. You’ll need to make an auxiliary package that, for example, calls Raw and registers a custom function. Then your unit test can leverage that auxiliary package, and then run a query to verify the function got registered properly. To avoid an import cycle, your test will need to be in the sqlite3_test package (but should still be in the go-sqlite3 directory).

rittneje avatar Feb 09 '20 17:02 rittneje

@rittneje Ah yes, I remember that now. Well, I think making a new package is a bit beyond my enthusiasm at the moment. :-) I'm not sure it will make the coverage bot happier anyway.

gwd avatar Feb 09 '20 17:02 gwd

Coverage Status

Coverage increased (+0.08%) to 53.48% when pulling b062b0fb018b6000b15d1a1e911ee831fa395dc3 on gwd:out/raw-context/v2 into 4c2df3cc4614a8001c1afdebcbda0fcf6e045719 on mattn:master.

coveralls avatar Feb 09 '20 18:02 coveralls

I think writing the test is useful, especially because it serves as a convenient example for anyone wishing to do what you are doing. Also, it should count towards coverage as long as you do something like this.

github.com/mattn/go-sqlite3/internal/sqlite3test/raw.go

package sqlite3test

/*
#include <stdlib.h>
#include <sqlite3.h>

static void one(sqlite3_context* ctx, int n, sqlite3_value** vals) {
    sqlite3_result_int(ctx, 1);
}

static inline int _create_function(sqlite3* c) {
    return sqlite3_create_function(c, "one", 0, SQLITE_UTF8|SQLITE_DETERMINISTIC, NULL, one, NULL, NULL);
}
*/
import "C"
import (
    "unsafe"
    sqlite3 "github.com/mattn/go-sqlite3"
)

func RegisterFunction(conn *sqlite3.SQLiteConn) error {
    return conn.Raw(func(raw unsafe.Pointer) error) error {
        rawConn := (*C.sqlite3)(raw)
        return C._create_function(rawConn)
    }
}

github.com/mattn/go-sqlite3/raw_test.go

package sqlite3_test

import (
    "context"
    "database/sql/driver"
    sqlite3 "github.com/mattn/go-sqlite3"
    "github.com/mattn/go-sqlite3/internal/sqlite3test"
)

type connector struct{
    filename string
    d *sqlite3.Driver
}

func (rc rawConnector) Connect(context.Context) (driver.Conn, error) {
    return rc.d.Open(rc.filename)
}

func (rc rawConnector) Driver() driver.Driver {
    return rc.d
}

func TestRaw(t *testing.T) {
    connector := rawConnector{
        filename: "...",
        d:  &sqlite3.Driver{
            ConnectHook(func(c *sqlite3.Conn) error {
                return sqlite3test.RegisterFunction(c)
            },
        },
    }

    db := sql.OpenDB(connector)
    defer db.Close()

    if err := db.Ping(); err != nil {
        t.Fatal(err)
    }

    var result int
    if err := db.QueryRow(`SELECT one()`).Scan(&result); err != nil {
        t.Fatal(err)
    }

    if result != 1 {
        t.Errorf("expected custom one() function to return 1, but got %d", result)
    }
}

Note that the raw_test.go file is in the go-sqlite3 directory, but the sqlite3_test package. This allows it to contribute the the coverage results, and not cause an important cycle.

rittneje avatar Feb 09 '20 19:02 rittneje

@rittneje OK, I've finally gotten around to taking your advice regarding the testing. Hopefully this gets a green check for coverage from Travis.

gwd avatar Jul 22 '20 21:07 gwd

OK, v4 to fix the build in Golang 1.9, which doesn't have sql.OpenDB()

gwd avatar Jul 22 '20 22:07 gwd

@mattn Any update on this PR? It's a pretty self-contained change, which I've been using for several months now. It's had some really good input from @rittneje, and has testing coverage.

gwd avatar Aug 03 '20 14:08 gwd

@mattn @rittneje Any more comments?

This has a pretty massive impact on my query, which makes heavy use of a custom function. Below are microbenchmark results; C-12 is the direct C callback implemented using the functionality this PR provides. As you can see, it's over 15x faster than the golang callbacks.

BenchmarkDcalc/Go-12                      809586	      1533 ns/op
BenchmarkDcalc/GoCompat-12         	  771516	      1579 ns/op
BenchmarkDcalc/C-12                	12472881	       105 ns/op

gwd avatar Sep 15 '20 08:09 gwd

Codecov Report

Merging #785 (7807bfa) into master (7476442) will decrease coverage by 0.14%. The diff coverage is 42.85%.

@@            Coverage Diff             @@
##           master     #785      +/-   ##
==========================================
- Coverage   46.09%   45.94%   -0.15%     
==========================================
  Files          11       11              
  Lines        1499     1506       +7     
==========================================
+ Hits          691      692       +1     
- Misses        669      673       +4     
- Partials      139      141       +2     
Impacted Files Coverage Δ
sqlite3.go 52.56% <42.85%> (-0.30%) :arrow_down:

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

codecov-commenter avatar Sep 07 '22 20:09 codecov-commenter

@rittneje Hey, it's been like 2 and a half years now, and I'm still rebasing my one little patch. What needs to be done to get this merged? 😁

gwd avatar Sep 07 '22 20:09 gwd

@gwd This PR is on my to-do list and I will take a look when I have time.

rittneje avatar Sep 19 '22 02:09 rittneje