diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..c9729b0 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,31 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Go + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + test: + strategy: + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.19 + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... diff --git a/.gitignore b/.gitignore index b45a385..58b5f43 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,4 @@ tools # Project -sqlite3/sqlite3* -embed/sqlite3.wasm \ No newline at end of file +sqlite3/sqlite3* \ No newline at end of file diff --git a/bench/crawshaw_test.go b/bench/crawshaw_test.go deleted file mode 100644 index fa7559c..0000000 --- a/bench/crawshaw_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package bench_test - -import ( - "strconv" - "testing" - - "crawshaw.io/sqlite" -) - -func BenchmarkCrawshaw(b *testing.B) { - for n := 0; n < b.N; n++ { - crawshawTest() - } -} - -func crawshawTest() { - db, err := sqlite.OpenConn(":memory:", 0) - if err != nil { - panic(err) - } - defer db.Close() - - exec := func(sql string) error { - stmt, _, err := db.PrepareTransient(sql) - if err != nil { - return err - } - _, err = stmt.Step() - if err != nil { - return err - } - return stmt.Finalize() - } - - err = exec(`CREATE TABLE users (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR, age INTEGER, rating REAL)`) - if err != nil { - panic(err) - } - - func() { - const N = 1_000_000 - - exec(`BEGIN`) - defer exec(`END`) - - stmt, _, err := db.PrepareTransient(`INSERT INTO users (id, name, age, rating) VALUES (?, ?, ?, ?)`) - if err != nil { - panic(err) - } - defer stmt.Finalize() - - for i := 0; i < N; i++ { - id := i + 1 - name := "user " + strconv.Itoa(id) - age := 33 + id - rating := 0.13 * float64(id) - stmt.BindInt64(1, int64(id)) - stmt.BindText(2, name) - stmt.BindInt64(3, int64(age)) - stmt.BindFloat(4, rating) - } - }() - - func() { - stmt, _, err := db.PrepareTransient(`SELECT id, name, age, rating FROM users ORDER BY id`) - if err != nil { - panic(err) - } - defer stmt.Finalize() - - for { - if row, err := stmt.Step(); err != nil { - panic(err) - } else if !row { - break - } - id := stmt.ColumnInt(0) - name := stmt.ColumnText(1) - age := stmt.ColumnInt64(2) - rating := stmt.ColumnFloat(3) - if id < 1 || len(name) < 5 || age < 33 || rating < 0.13 { - panic("wrong row values") - } - } - }() -} diff --git a/bench/wasm_test.go b/bench/wasm_test.go deleted file mode 100644 index 9f33f40..0000000 --- a/bench/wasm_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package bench_test - -import ( - "strconv" - "testing" - - "github.com/ncruces/go-sqlite3" - _ "github.com/ncruces/go-sqlite3/embed" -) - -func init() { - db, err := sqlite3.Open(":memory:") - if err != nil { - panic(err) - } - err = db.Close() - if err != nil { - panic(err) - } -} - -func BenchmarkWasm(b *testing.B) { - for n := 0; n < b.N; n++ { - wasmTest() - } -} - -func wasmTest() { - db, err := sqlite3.Open(":memory:") - if err != nil { - panic(err) - } - defer db.Close() - - err = db.Exec(`CREATE TABLE users (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR, age INTEGER, rating REAL)`) - if err != nil { - panic(err) - } - - func() { - const N = 1_000_000 - - db.Exec(`BEGIN`) - defer db.Exec(`END`) - - stmt, _, err := db.Prepare(`INSERT INTO users (id, name, age, rating) VALUES (?, ?, ?, ?)`) - if err != nil { - panic(err) - } - defer stmt.Close() - - for i := 0; i < N; i++ { - id := i + 1 - name := "user " + strconv.Itoa(id) - age := 33 + id - rating := 0.13 * float64(id) - stmt.BindInt(1, id) - stmt.BindText(2, name) - stmt.BindInt64(3, int64(age)) - stmt.BindFloat(4, rating) - } - }() - - func() { - stmt, _, err := db.Prepare(`SELECT id, name, age, rating FROM users ORDER BY id`) - if err != nil { - panic(err) - } - defer stmt.Close() - - for stmt.Step() { - id := stmt.ColumnInt(0) - name := stmt.ColumnText(1) - age := stmt.ColumnInt64(2) - rating := stmt.ColumnFloat(3) - if id < 1 || len(name) < 5 || age < 33 || rating < 0.13 { - panic("wrong row values") - } - } - if stmt.Err() != nil { - panic(err) - } - }() -} diff --git a/cmd/main.go b/cmd/main.go index 9f7d130..2d04bf5 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -14,7 +14,7 @@ func main() { log.Fatal(err) } - err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id int, name varchar(10))`) + err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`) if err != nil { log.Fatal(err) } @@ -32,7 +32,7 @@ func main() { for stmt.Step() { fmt.Println(stmt.ColumnInt(0), stmt.ColumnText(1)) } - if stmt.Err() != nil { + if err := stmt.Err(); err != nil { log.Fatal(err) } diff --git a/conn_test.go b/conn_test.go new file mode 100644 index 0000000..1134636 --- /dev/null +++ b/conn_test.go @@ -0,0 +1,76 @@ +package sqlite3_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/ncruces/go-sqlite3" + _ "github.com/ncruces/go-sqlite3/embed" +) + +func TestOpen_memory(t *testing.T) { + testOpen(t, ":memory:") +} + +func TestOpen_file(t *testing.T) { + dir, err := os.MkdirTemp("", "sqlite3-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + testOpen(t, filepath.Join(dir, "test.db")) +} + +func testOpen(t *testing.T, name string) { + db, err := sqlite3.Open(name) + if err != nil { + t.Fatal(err) + } + defer db.Close() + + err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`) + if err != nil { + t.Fatal(err) + } + + err = db.Exec(`INSERT INTO users(id, name) VALUES(0, 'go'), (1, 'zig'), (2, 'whatever')`) + if err != nil { + t.Fatal(err) + } + + stmt, _, err := db.Prepare(`SELECT id, name FROM users`) + if err != nil { + t.Fatal(err) + } + + ids := []int{0, 1, 2} + names := []string{"go", "zig", "whatever"} + + idx := 0 + for ; stmt.Step(); idx++ { + if ids[idx] != stmt.ColumnInt(0) { + t.Errorf("expected %d got %d", ids[idx], stmt.ColumnInt(0)) + } + if names[idx] != stmt.ColumnText(1) { + t.Errorf("expected %q got %q", names[idx], stmt.ColumnText(1)) + } + } + if err := stmt.Err(); err != nil { + t.Fatal(err) + } + if idx != 3 { + t.Errorf("expected %d rows got %d", len(ids), idx) + } + + err = stmt.Close() + if err != nil { + t.Fatal(err) + } + + err = db.Close() + if err != nil { + t.Fatal(err) + } +} diff --git a/embed/build.sh b/embed/build.sh index 70d0b3a..3109b5f 100755 --- a/embed/build.sh +++ b/embed/build.sh @@ -7,7 +7,7 @@ cd -P -- "$(dirname -- "$0")" ../sqlite3/download.sh # build SQLite -zig cc --target=wasm32-wasi -flto -g0 -O2 \ +zig cc --target=wasm32-wasi -flto -g0 -Os \ -o sqlite3.wasm ../sqlite3/*.c \ -mmutable-globals \ -mbulk-memory -mreference-types \ diff --git a/embed/sqlite3.wasm b/embed/sqlite3.wasm new file mode 100755 index 0000000..5e3b494 Binary files /dev/null and b/embed/sqlite3.wasm differ diff --git a/vfs_lock.go b/vfs_lock.go index 06a9fd1..7a1c8e4 100644 --- a/vfs_lock.go +++ b/vfs_lock.go @@ -2,6 +2,7 @@ package sqlite3 import ( "context" + "os" "github.com/tetratelabs/wazero/api" ) @@ -70,6 +71,11 @@ type vfsLocker interface { CheckReservedLock() (bool, xErrorCode) } +type vfsFileLocker struct { + *os.File + state vfsLockState +} + func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock vfsLockState) uint32 { if assert && (eLock == _NO_LOCK || eLock == _PENDING_LOCK) { panic(assertErr + " [d4oxww]") diff --git a/vfs_unix.go b/vfs_unix.go index 91c9341..5dbe889 100644 --- a/vfs_unix.go +++ b/vfs_unix.go @@ -12,11 +12,6 @@ func deleteOnClose(f *os.File) { _ = os.Remove(f.Name()) } -type vfsFileLocker struct { - *os.File - state vfsLockState -} - func (l *vfsFileLocker) LockState() vfsLockState { return l.state } diff --git a/vfs_windows.go b/vfs_windows.go index e01350a..320a851 100644 --- a/vfs_windows.go +++ b/vfs_windows.go @@ -1,5 +1,46 @@ package sqlite3 +import "os" + func deleteOnClose(f *os.File) {} -type vfsFileLocker = vfsNoopLocker +func (l *vfsFileLocker) LockState() vfsLockState { + return l.state +} + +func (l *vfsFileLocker) LockShared() xErrorCode { + l.state = _SHARED_LOCK + return _OK +} + +func (l *vfsFileLocker) LockReserved() xErrorCode { + l.state = _RESERVED_LOCK + return _OK +} + +func (l *vfsFileLocker) LockPending() xErrorCode { + l.state = _PENDING_LOCK + return _OK +} + +func (l *vfsFileLocker) LockExclusive() xErrorCode { + l.state = _EXCLUSIVE_LOCK + return _OK +} + +func (l *vfsFileLocker) DowngradeLock() xErrorCode { + l.state = _SHARED_LOCK + return _OK +} + +func (l *vfsFileLocker) Unlock() xErrorCode { + l.state = _NO_LOCK + return _OK +} + +func (l *vfsFileLocker) CheckReservedLock() (bool, xErrorCode) { + if l.state >= _RESERVED_LOCK { + return true, _OK + } + return false, _OK +}