diff --git a/README.md b/README.md index c3f8d15..9804793 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is very much a WIP. -TODO: +Roadmap: - [x] build SQLite using `zig cc --target=wasm32-wasi` - [x] `:memory:` databases - [x] use [`test_demovfs.c`](sqlite3/test_demovfs.c) for file databases @@ -13,6 +13,7 @@ TODO: - [`github.com/bvinc/go-sqlite-lite`](https://github.com/bvinc/go-sqlite-lite) - [`github.com/crawshaw/sqlite`](https://github.com/crawshaw/sqlite) - [`github.com/zombiezen/go-sqlite`](https://github.com/zombiezen/go-sqlite) + - [`github.com/cvilsmeier/sqinn-go`](https://github.com/cvilsmeier/sqinn-go) - [`database/sql`](https://pkg.go.dev/database/sql) drivers can come later, if ever - [ ] implement own VFS to sidestep WASI syscalls: - locking will be a pain point: @@ -21,6 +22,14 @@ TODO: - file access needs to be synchronized, both in-process and out-of-process - out-of-process should be compatible with SQLite on Windows/Unix - [ ] benchmark to see if this is usefull at all: + - [`github.com/cvilsmeier/sqinn-go`](https://github.com/cvilsmeier/sqinn-go-bench) - [`github.com/bvinc/go-sqlite-lite`](https://github.com/bvinc/go-sqlite-lite) - [`github.com/mattn/go-sqlite3`](https://github.com/mattn/go-sqlite3) - [`modernc.org/sqlite`](https://modernc.org/sqlite) + +Random TODO list: +- wrap more of the SQLite API, enough to run useful queries; +- create a Go VFS that's enough to use `:memory:` databases without WASI; +- expand that VFS to wrap an `io.ReaderAt`; +- optimize small allocations that last a single function call; +- … \ No newline at end of file diff --git a/build_deps.sh b/build_deps.sh index f2c1062..e4a3510 100755 --- a/build_deps.sh +++ b/build_deps.sh @@ -44,6 +44,5 @@ zig cc --target=wasm32-wasi -flto -g0 -O2 \ -Wl,--export=sqlite3_errstr \ -Wl,--export=sqlite3_errmsg \ -Wl,--export=sqlite3_error_offset \ - -Wl,--export=sqlite3_extended_errcode \ -Wl,--export=malloc \ -Wl,--export=free diff --git a/cmd/main.go b/cmd/main.go index 1c33bb3..a517408 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -8,7 +8,7 @@ import ( ) func main() { - db, err := sqlite3.Open(":memory:", 0, "") + db, err := sqlite3.Open(":memory:") if err != nil { log.Fatal(err) } @@ -23,7 +23,7 @@ func main() { log.Fatal(err) } - stmt, _, err := db.Prepare(`SELECT id, name FROM users`, 0) + stmt, _, err := db.Prepare(`SELECT id, name FROM users`) if err != nil { log.Fatal(err) } diff --git a/conn.go b/conn.go index 3a6c249..08c9a0d 100644 --- a/conn.go +++ b/conn.go @@ -19,7 +19,11 @@ type Conn struct { api sqliteAPI } -func Open(name string, flags uint64, vfs string) (conn *Conn, err error) { +func Open(name string) (conn *Conn, err error) { + return OpenFlags(name, OPEN_READWRITE|OPEN_CREATE) +} + +func OpenFlags(name string, flags OpenFlag) (conn *Conn, err error) { once.Do(compile) var fs fs.FS @@ -53,7 +57,6 @@ func Open(name string, flags uint64, vfs string) (conn *Conn, err error) { errstr: module.ExportedFunction("sqlite3_errstr"), errmsg: module.ExportedFunction("sqlite3_errmsg"), erroff: module.ExportedFunction("sqlite3_error_offset"), - errext: module.ExportedFunction("sqlite3_extended_errcode"), open: module.ExportedFunction("sqlite3_open_v2"), close: module.ExportedFunction("sqlite3_close"), prepare: module.ExportedFunction("sqlite3_prepare_v3"), @@ -75,16 +78,7 @@ func Open(name string, flags uint64, vfs string) (conn *Conn, err error) { namePtr := c.newString(name) connPtr := c.newBytes(ptrSize) - if flags == 0 { - flags = OPEN_READWRITE | OPEN_CREATE - } - - var vfsPtr uint32 - if vfs != "" { - vfsPtr = c.newString(vfs) - } - - r, err := c.api.open.Call(ctx, uint64(namePtr), uint64(connPtr), flags, uint64(vfsPtr)) + r, err := c.api.open.Call(ctx, uint64(namePtr), uint64(connPtr), uint64(flags), 0) if err != nil { return nil, err } @@ -92,9 +86,8 @@ func Open(name string, flags uint64, vfs string) (conn *Conn, err error) { c.handle, _ = c.memory.ReadUint32Le(connPtr) c.free(connPtr) c.free(namePtr) - c.free(vfsPtr) - if r[0] != OK { + if r[0] != _OK { return nil, c.error(r[0]) } return &c, nil @@ -106,7 +99,7 @@ func (c *Conn) Close() error { return err } - if r[0] != OK { + if r[0] != _OK { return c.error(r[0]) } return c.module.Close(context.TODO()) @@ -122,19 +115,23 @@ func (c *Conn) Exec(sql string) error { c.free(sqlPtr) - if r[0] != OK { + if r[0] != _OK { return c.error(r[0]) } return nil } -func (c *Conn) Prepare(sql string, flags uint64, args ...any) (stmt *Stmt, tail string, err error) { +func (c *Conn) Prepare(sql string) (stmt *Stmt, tail string, err error) { + return c.PrepareFlags(sql, 0) +} + +func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail string, err error) { sqlPtr := c.newString(sql) stmtPtr := c.newBytes(ptrSize) tailPtr := c.newBytes(ptrSize) r, err := c.api.prepare.Call(context.TODO(), uint64(c.handle), - uint64(sqlPtr), uint64(len(sql)+1), flags, + uint64(sqlPtr), uint64(len(sql)+1), uint64(flags), uint64(stmtPtr), uint64(tailPtr)) if err != nil { return nil, "", err @@ -149,7 +146,7 @@ func (c *Conn) Prepare(sql string, flags uint64, args ...any) (stmt *Stmt, tail c.free(stmtPtr) c.free(sqlPtr) - if r[0] != OK { + if r[0] != _OK { return nil, "", c.error(r[0]) } if stmt.handle == 0 { @@ -159,12 +156,15 @@ func (c *Conn) Prepare(sql string, flags uint64, args ...any) (stmt *Stmt, tail } func (c *Conn) error(rc uint64) *Error { - serr := Error{Code: int(rc)} + serr := Error{ + Code: ErrorCode(rc & 0xFF), + ExtendedCode: ExtendedErrorCode(rc), + } var r []uint64 // string - r, _ = c.api.errstr.Call(context.TODO(), uint64(rc)) + r, _ = c.api.errstr.Call(context.TODO(), rc) if r != nil { serr.str = c.getString(uint32(r[0]), 512) } @@ -175,12 +175,6 @@ func (c *Conn) error(rc uint64) *Error { serr.msg = c.getString(uint32(r[0]), 512) } - // extended code - r, _ = c.api.errext.Call(context.TODO(), uint64(c.handle)) - if r != nil { - serr.ExtendedCode = int(r[0]) - } - if serr.str == serr.msg { serr.msg = "" } @@ -247,7 +241,6 @@ type sqliteAPI struct { free api.Function errstr api.Function errmsg api.Function - errext api.Function erroff api.Function open api.Function close api.Function diff --git a/const.go b/const.go index f064bc6..09e5727 100644 --- a/const.go +++ b/const.go @@ -1,12 +1,153 @@ package sqlite3 const ( - OK = 0 - ROW = 100 - DONE = 101 + _OK = 0 /* Successful result */ + _ROW = 100 /* sqlite3_step() has another row ready */ + _DONE = 101 /* sqlite3_step() has finished executing */ ) +type ErrorCode int + const ( - OPEN_READWRITE = 0x00000002 - OPEN_CREATE = 0x00000004 + ERROR ErrorCode = 1 /* Generic error */ + INTERNAL ErrorCode = 2 /* Internal logic error in SQLite */ + PERM ErrorCode = 3 /* Access permission denied */ + ABORT ErrorCode = 4 /* Callback routine requested an abort */ + BUSY ErrorCode = 5 /* The database file is locked */ + LOCKED ErrorCode = 6 /* A table in the database is locked */ + NOMEM ErrorCode = 7 /* A malloc() failed */ + READONLY ErrorCode = 8 /* Attempt to write a readonly database */ + INTERRUPT ErrorCode = 9 /* Operation terminated by sqlite3_interrupt()*/ + IOERR ErrorCode = 10 /* Some kind of disk I/O error occurred */ + CORRUPT ErrorCode = 11 /* The database disk image is malformed */ + NOTFOUND ErrorCode = 12 /* Unknown opcode in sqlite3_file_control() */ + FULL ErrorCode = 13 /* Insertion failed because database is full */ + CANTOPEN ErrorCode = 14 /* Unable to open the database file */ + PROTOCOL ErrorCode = 15 /* Database lock protocol error */ + EMPTY ErrorCode = 16 /* Internal use only */ + SCHEMA ErrorCode = 17 /* The database schema changed */ + TOOBIG ErrorCode = 18 /* String or BLOB exceeds size limit */ + CONSTRAINT ErrorCode = 19 /* Abort due to constraint violation */ + MISMATCH ErrorCode = 20 /* Data type mismatch */ + MISUSE ErrorCode = 21 /* Library used incorrectly */ + NOLFS ErrorCode = 22 /* Uses OS features not supported on host */ + AUTH ErrorCode = 23 /* Authorization denied */ + FORMAT ErrorCode = 24 /* Not used */ + RANGE ErrorCode = 25 /* 2nd parameter to sqlite3_bind out of range */ + NOTADB ErrorCode = 26 /* File opened that is not a database file */ + NOTICE ErrorCode = 27 /* Notifications from sqlite3_log() */ + WARNING ErrorCode = 28 /* Warnings from sqlite3_log() */ +) + +type ExtendedErrorCode int + +const ( + ERROR_MISSING_COLLSEQ = ExtendedErrorCode(ERROR | (1 << 8)) + ERROR_RETRY = ExtendedErrorCode(ERROR | (2 << 8)) + ERROR_SNAPSHOT = ExtendedErrorCode(ERROR | (3 << 8)) + IOERR_READ = ExtendedErrorCode(IOERR | (1 << 8)) + IOERR_SHORT_READ = ExtendedErrorCode(IOERR | (2 << 8)) + IOERR_WRITE = ExtendedErrorCode(IOERR | (3 << 8)) + IOERR_FSYNC = ExtendedErrorCode(IOERR | (4 << 8)) + IOERR_DIR_FSYNC = ExtendedErrorCode(IOERR | (5 << 8)) + IOERR_TRUNCATE = ExtendedErrorCode(IOERR | (6 << 8)) + IOERR_FSTAT = ExtendedErrorCode(IOERR | (7 << 8)) + IOERR_UNLOCK = ExtendedErrorCode(IOERR | (8 << 8)) + IOERR_RDLOCK = ExtendedErrorCode(IOERR | (9 << 8)) + IOERR_DELETE = ExtendedErrorCode(IOERR | (10 << 8)) + IOERR_BLOCKED = ExtendedErrorCode(IOERR | (11 << 8)) + IOERR_NOMEM = ExtendedErrorCode(IOERR | (12 << 8)) + IOERR_ACCESS = ExtendedErrorCode(IOERR | (13 << 8)) + IOERR_CHECKRESERVEDLOCK = ExtendedErrorCode(IOERR | (14 << 8)) + IOERR_LOCK = ExtendedErrorCode(IOERR | (15 << 8)) + IOERR_CLOSE = ExtendedErrorCode(IOERR | (16 << 8)) + IOERR_DIR_CLOSE = ExtendedErrorCode(IOERR | (17 << 8)) + IOERR_SHMOPEN = ExtendedErrorCode(IOERR | (18 << 8)) + IOERR_SHMSIZE = ExtendedErrorCode(IOERR | (19 << 8)) + IOERR_SHMLOCK = ExtendedErrorCode(IOERR | (20 << 8)) + IOERR_SHMMAP = ExtendedErrorCode(IOERR | (21 << 8)) + IOERR_SEEK = ExtendedErrorCode(IOERR | (22 << 8)) + IOERR_DELETE_NOENT = ExtendedErrorCode(IOERR | (23 << 8)) + IOERR_MMAP = ExtendedErrorCode(IOERR | (24 << 8)) + IOERR_GETTEMPPATH = ExtendedErrorCode(IOERR | (25 << 8)) + IOERR_CONVPATH = ExtendedErrorCode(IOERR | (26 << 8)) + IOERR_VNODE = ExtendedErrorCode(IOERR | (27 << 8)) + IOERR_AUTH = ExtendedErrorCode(IOERR | (28 << 8)) + IOERR_BEGIN_ATOMIC = ExtendedErrorCode(IOERR | (29 << 8)) + IOERR_COMMIT_ATOMIC = ExtendedErrorCode(IOERR | (30 << 8)) + IOERR_ROLLBACK_ATOMIC = ExtendedErrorCode(IOERR | (31 << 8)) + IOERR_DATA = ExtendedErrorCode(IOERR | (32 << 8)) + IOERR_CORRUPTFS = ExtendedErrorCode(IOERR | (33 << 8)) + LOCKED_SHAREDCACHE = ExtendedErrorCode(LOCKED | (1 << 8)) + LOCKED_VTAB = ExtendedErrorCode(LOCKED | (2 << 8)) + BUSY_RECOVERY = ExtendedErrorCode(BUSY | (1 << 8)) + BUSY_SNAPSHOT = ExtendedErrorCode(BUSY | (2 << 8)) + BUSY_TIMEOUT = ExtendedErrorCode(BUSY | (3 << 8)) + CANTOPEN_NOTEMPDIR = ExtendedErrorCode(CANTOPEN | (1 << 8)) + CANTOPEN_ISDIR = ExtendedErrorCode(CANTOPEN | (2 << 8)) + CANTOPEN_FULLPATH = ExtendedErrorCode(CANTOPEN | (3 << 8)) + CANTOPEN_CONVPATH = ExtendedErrorCode(CANTOPEN | (4 << 8)) + CANTOPEN_DIRTYWAL = ExtendedErrorCode(CANTOPEN | (5 << 8)) /* Not Used */ + CANTOPEN_SYMLINK = ExtendedErrorCode(CANTOPEN | (6 << 8)) + CORRUPT_VTAB = ExtendedErrorCode(CORRUPT | (1 << 8)) + CORRUPT_SEQUENCE = ExtendedErrorCode(CORRUPT | (2 << 8)) + CORRUPT_INDEX = ExtendedErrorCode(CORRUPT | (3 << 8)) + READONLY_RECOVERY = ExtendedErrorCode(READONLY | (1 << 8)) + READONLY_CANTLOCK = ExtendedErrorCode(READONLY | (2 << 8)) + READONLY_ROLLBACK = ExtendedErrorCode(READONLY | (3 << 8)) + READONLY_DBMOVED = ExtendedErrorCode(READONLY | (4 << 8)) + READONLY_CANTINIT = ExtendedErrorCode(READONLY | (5 << 8)) + READONLY_DIRECTORY = ExtendedErrorCode(READONLY | (6 << 8)) + ABORT_ROLLBACK = ExtendedErrorCode(ABORT | (2 << 8)) + CONSTRAINT_CHECK = ExtendedErrorCode(CONSTRAINT | (1 << 8)) + CONSTRAINT_COMMITHOOK = ExtendedErrorCode(CONSTRAINT | (2 << 8)) + CONSTRAINT_FOREIGNKEY = ExtendedErrorCode(CONSTRAINT | (3 << 8)) + CONSTRAINT_FUNCTION = ExtendedErrorCode(CONSTRAINT | (4 << 8)) + CONSTRAINT_NOTNULL = ExtendedErrorCode(CONSTRAINT | (5 << 8)) + CONSTRAINT_PRIMARYKEY = ExtendedErrorCode(CONSTRAINT | (6 << 8)) + CONSTRAINT_TRIGGER = ExtendedErrorCode(CONSTRAINT | (7 << 8)) + CONSTRAINT_UNIQUE = ExtendedErrorCode(CONSTRAINT | (8 << 8)) + CONSTRAINT_VTAB = ExtendedErrorCode(CONSTRAINT | (9 << 8)) + CONSTRAINT_ROWID = ExtendedErrorCode(CONSTRAINT | (10 << 8)) + CONSTRAINT_PINNED = ExtendedErrorCode(CONSTRAINT | (11 << 8)) + CONSTRAINT_DATATYPE = ExtendedErrorCode(CONSTRAINT | (12 << 8)) + NOTICE_RECOVER_WAL = ExtendedErrorCode(NOTICE | (1 << 8)) + NOTICE_RECOVER_ROLLBACK = ExtendedErrorCode(NOTICE | (2 << 8)) + WARNING_AUTOINDEX = ExtendedErrorCode(WARNING | (1 << 8)) + AUTH_USER = ExtendedErrorCode(AUTH | (1 << 8)) +) + +type OpenFlag uint + +const ( + OPEN_READONLY OpenFlag = 0x00000001 /* Ok for sqlite3_open_v2() */ + OPEN_READWRITE OpenFlag = 0x00000002 /* Ok for sqlite3_open_v2() */ + OPEN_CREATE OpenFlag = 0x00000004 /* Ok for sqlite3_open_v2() */ + OPEN_DELETEONCLOSE OpenFlag = 0x00000008 /* VFS only */ + OPEN_EXCLUSIVE OpenFlag = 0x00000010 /* VFS only */ + OPEN_AUTOPROXY OpenFlag = 0x00000020 /* VFS only */ + OPEN_URI OpenFlag = 0x00000040 /* Ok for sqlite3_open_v2() */ + OPEN_MEMORY OpenFlag = 0x00000080 /* Ok for sqlite3_open_v2() */ + OPEN_MAIN_DB OpenFlag = 0x00000100 /* VFS only */ + OPEN_TEMP_DB OpenFlag = 0x00000200 /* VFS only */ + OPEN_TRANSIENT_DB OpenFlag = 0x00000400 /* VFS only */ + OPEN_MAIN_JOURNAL OpenFlag = 0x00000800 /* VFS only */ + OPEN_TEMP_JOURNAL OpenFlag = 0x00001000 /* VFS only */ + OPEN_SUBJOURNAL OpenFlag = 0x00002000 /* VFS only */ + OPEN_SUPER_JOURNAL OpenFlag = 0x00004000 /* VFS only */ + OPEN_NOMUTEX OpenFlag = 0x00008000 /* Ok for sqlite3_open_v2() */ + OPEN_FULLMUTEX OpenFlag = 0x00010000 /* Ok for sqlite3_open_v2() */ + OPEN_SHAREDCACHE OpenFlag = 0x00020000 /* Ok for sqlite3_open_v2() */ + OPEN_PRIVATECACHE OpenFlag = 0x00040000 /* Ok for sqlite3_open_v2() */ + OPEN_WAL OpenFlag = 0x00080000 /* VFS only */ + OPEN_NOFOLLOW OpenFlag = 0x01000000 /* Ok for sqlite3_open_v2() */ + OPEN_EXRESCODE OpenFlag = 0x02000000 /* Extended result codes */ +) + +type PrepareFlag uint + +const ( + PREPARE_PERSISTENT PrepareFlag = 0x01 + PREPARE_NORMALIZE PrepareFlag = 0x02 + PREPARE_NO_VTAB PrepareFlag = 0x04 ) diff --git a/error.go b/error.go index e492eb9..36b5158 100644 --- a/error.go +++ b/error.go @@ -6,8 +6,8 @@ import ( ) type Error struct { - Code int - ExtendedCode int + Code ErrorCode + ExtendedCode ExtendedErrorCode str string msg string } @@ -19,7 +19,7 @@ func (e Error) Error() string { if e.str != "" { b.WriteString(e.str) } else { - b.WriteString(strconv.Itoa(e.Code)) + b.WriteString(strconv.Itoa(int(e.Code))) } if e.msg != "" { diff --git a/stmt.go b/stmt.go index 90d1b5e..f311802 100644 --- a/stmt.go +++ b/stmt.go @@ -14,7 +14,7 @@ func (s *Stmt) Close() error { } s.handle = 0 - if r[0] != OK { + if r[0] != _OK { return s.c.error(r[0]) } return nil