diff --git a/README.md b/README.md index b408f3f..e9f11ac 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,10 @@ embeds a build of SQLite into your application. ### Caveats +This module replaces the SQLite [OS Interface](https://www.sqlite.org/vfs.html) (aka VFS) +with a pure Go implementation. +This has numerous benefits, but also comes with some caveats. + #### Write-Ahead Logging Because WASM does not support shared memory, @@ -45,15 +49,14 @@ OFD locks are fully compatible with process-associated POSIX advisory locks, and are supported on Linux, macOS and illumos. As a work around for other Unixes, you can use [`nolock=1`](https://www.sqlite.org/uri.html). +#### Testing + +The pure Go VFS is stress tested by running an unmodified build of SQLite's +[mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c) +on Linux, macOS and Windows. + ### Roadmap -- [x] build SQLite using `zig cc --target=wasm32-wasi` -- [x] `:memory:` databases -- [x] port [`test_demovfs.c`](https://www.sqlite.org/src/doc/trunk/src/test_demovfs.c) to Go - - branch [`wasi`](https://github.com/ncruces/go-sqlite3/tree/wasi) uses `test_demovfs.c` directly -- [x] design a nice API, enough for simple use cases -- [x] provide a simple `database/sql` driver -- [x] file locking, compatible with SQLite on macOS/Linux/Windows - [ ] advanced SQLite features - [x] nested transactions - [x] incremental BLOB I/O diff --git a/conn.go b/conn.go index 2bb2d42..3bf0216 100644 --- a/conn.go +++ b/conn.go @@ -99,6 +99,8 @@ func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) { return 0, err } } + + c.call(c.api.timeCollation, uint64(handle)) return handle, nil } diff --git a/embed/exports.txt b/embed/exports.txt index 96bc471..69b5b18 100644 --- a/embed/exports.txt +++ b/embed/exports.txt @@ -46,4 +46,5 @@ sqlite3_backup_step sqlite3_backup_finish sqlite3_backup_remaining sqlite3_backup_pagecount +sqlite3_time_collation sqlite3_interrupt_offset \ No newline at end of file diff --git a/embed/sqlite3.wasm b/embed/sqlite3.wasm index fddcbce..f584eee 100755 Binary files a/embed/sqlite3.wasm and b/embed/sqlite3.wasm differ diff --git a/module.go b/module.go index a742e2d..d601e70 100644 --- a/module.go +++ b/module.go @@ -155,6 +155,7 @@ func newModule(mod api.Module) (m *module, err error) { backupFinish: getFun("sqlite3_backup_finish"), backupRemaining: getFun("sqlite3_backup_remaining"), backupPageCount: getFun("sqlite3_backup_pagecount"), + timeCollation: getFun("sqlite3_time_collation"), interrupt: getVal("sqlite3_interrupt_offset"), } if err != nil { @@ -348,5 +349,6 @@ type sqliteAPI struct { backupFinish api.Function backupRemaining api.Function backupPageCount api.Function + timeCollation api.Function interrupt uint32 } diff --git a/sqlite3/time.c b/sqlite3/time.c index a587963..3b7a78c 100644 --- a/sqlite3/time.c +++ b/sqlite3/time.c @@ -24,6 +24,5 @@ static int time_collation(void *pArg, int nKey1, const void *pKey1, int nKey2, } int sqlite3_time_collation(sqlite3 *db) { - return sqlite3_create_collation_v2(db, "TIME", SQLITE_UTF8, 0, time_collation, - 0); + return sqlite3_create_collation(db, "TIME", SQLITE_UTF8, 0, time_collation); } \ No newline at end of file diff --git a/tests/time_test.go b/tests/time_test.go index 9b8dff9..156f355 100644 --- a/tests/time_test.go +++ b/tests/time_test.go @@ -118,3 +118,52 @@ func TestTimeFormat_Decode(t *testing.T) { }) } } + +func TestDB_timeCollation(t *testing.T) { + t.Parallel() + + db, err := sqlite3.Open(":memory:") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + err = db.Exec(`CREATE TABLE IF NOT EXISTS times (tstamp COLLATE TIME)`) + if err != nil { + t.Fatal(err) + } + + stmt, _, err := db.Prepare(`INSERT INTO times VALUES (?), (?), (?)`) + if err != nil { + t.Fatal(err) + } + defer stmt.Close() + + stmt.BindTime(1, time.Unix(0, 0).UTC(), sqlite3.TimeFormatDefault) + stmt.BindTime(2, time.Unix(0, -1).UTC(), sqlite3.TimeFormatDefault) + stmt.BindTime(3, time.Unix(0, +1).UTC(), sqlite3.TimeFormatDefault) + stmt.Step() + + err = stmt.Close() + if err != nil { + t.Fatal(err) + } + + stmt, _, err = db.Prepare(`SELECT tstamp FROM times ORDER BY tstamp`) + if err != nil { + t.Fatal(err) + } + + var t0 time.Time + for stmt.Step() { + t1 := stmt.ColumnTime(0, sqlite3.TimeFormatAuto) + if t0.After(t1) { + t.Errorf("got %v after %v", t0, t1) + } + t0 = t1 + } + err = stmt.Close() + if err != nil { + t.Fatal(err) + } +}