diff options
author | Ben Johnson <benbjohnson@yahoo.com> | 2022-01-28 08:37:05 -0700 |
---|---|---|
committer | mattn <mattn.jp@gmail.com> | 2022-01-29 01:58:27 +0900 |
commit | ae2a61f847e10e6dd771ecd4e1c55e0421cdc7f9 (patch) | |
tree | 7afef61544d9d1b6a4e08e13a847fee8201cbe1f | |
parent | Add example using driverName (diff) | |
download | golite-ae2a61f847e10e6dd771ecd4e1c55e0421cdc7f9.tar.gz golite-ae2a61f847e10e6dd771ecd4e1c55e0421cdc7f9.tar.xz |
Add sqlite3_file_control() support
This commit adds the SQLiteConn.FileControlInt() method which calls the
underlying sqlite3_file_control() function with an int argument. This can
be used for low-level operations on SQLite databases such as persisting
the WAL file after database close.
-rw-r--r-- | sqlite3.go | 70 | ||||
-rw-r--r-- | sqlite3_test.go | 38 |
2 files changed, 108 insertions, 0 deletions
@@ -297,6 +297,51 @@ const ( /*SQLITE_RECURSIVE = C.SQLITE_RECURSIVE*/ ) +// Standard File Control Opcodes +// See: https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html +const ( + SQLITE_FCNTL_LOCKSTATE = int(1) + SQLITE_FCNTL_GET_LOCKPROXYFILE = int(2) + SQLITE_FCNTL_SET_LOCKPROXYFILE = int(3) + SQLITE_FCNTL_LAST_ERRNO = int(4) + SQLITE_FCNTL_SIZE_HINT = int(5) + SQLITE_FCNTL_CHUNK_SIZE = int(6) + SQLITE_FCNTL_FILE_POINTER = int(7) + SQLITE_FCNTL_SYNC_OMITTED = int(8) + SQLITE_FCNTL_WIN32_AV_RETRY = int(9) + SQLITE_FCNTL_PERSIST_WAL = int(10) + SQLITE_FCNTL_OVERWRITE = int(11) + SQLITE_FCNTL_VFSNAME = int(12) + SQLITE_FCNTL_POWERSAFE_OVERWRITE = int(13) + SQLITE_FCNTL_PRAGMA = int(14) + SQLITE_FCNTL_BUSYHANDLER = int(15) + SQLITE_FCNTL_TEMPFILENAME = int(16) + SQLITE_FCNTL_MMAP_SIZE = int(18) + SQLITE_FCNTL_TRACE = int(19) + SQLITE_FCNTL_HAS_MOVED = int(20) + SQLITE_FCNTL_SYNC = int(21) + SQLITE_FCNTL_COMMIT_PHASETWO = int(22) + SQLITE_FCNTL_WIN32_SET_HANDLE = int(23) + SQLITE_FCNTL_WAL_BLOCK = int(24) + SQLITE_FCNTL_ZIPVFS = int(25) + SQLITE_FCNTL_RBU = int(26) + SQLITE_FCNTL_VFS_POINTER = int(27) + SQLITE_FCNTL_JOURNAL_POINTER = int(28) + SQLITE_FCNTL_WIN32_GET_HANDLE = int(29) + SQLITE_FCNTL_PDB = int(30) + SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = int(31) + SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = int(32) + SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = int(33) + SQLITE_FCNTL_LOCK_TIMEOUT = int(34) + SQLITE_FCNTL_DATA_VERSION = int(35) + SQLITE_FCNTL_SIZE_LIMIT = int(36) + SQLITE_FCNTL_CKPT_DONE = int(37) + SQLITE_FCNTL_RESERVE_BYTES = int(38) + SQLITE_FCNTL_CKPT_START = int(39) + SQLITE_FCNTL_EXTERNAL_READER = int(40) + SQLITE_FCNTL_CKSM_FILE = int(41) +) + // SQLiteDriver implements driver.Driver. type SQLiteDriver struct { Extensions []string @@ -1813,6 +1858,31 @@ func (c *SQLiteConn) SetLimit(id int, newVal int) int { return int(C._sqlite3_limit(c.db, C.int(id), C.int(newVal))) } +// SetFileControlInt invokes the xFileControl method on a given database. The +// dbName is the name of the database. It will default to "main" if left blank. +// The op is one of the opcodes prefixed by "SQLITE_FCNTL_". The arg argument +// and return code are both opcode-specific. Please see the SQLite documentation. +// +// This method is not thread-safe as the returned error code can be changed by +// another call if invoked concurrently. +// +// See: sqlite3_file_control, https://www.sqlite.org/c3ref/file_control.html +func (c *SQLiteConn) SetFileControlInt(dbName string, op int, arg int) error { + if dbName == "" { + dbName = "main" + } + + cDBName := C.CString(dbName) + defer C.free(unsafe.Pointer(cDBName)) + + cArg := C.int(arg) + rv := C.sqlite3_file_control(c.db, cDBName, C.int(op), unsafe.Pointer(&cArg)) + if rv != C.SQLITE_OK { + return c.lastError() + } + return nil +} + // Close the statement. func (s *SQLiteStmt) Close() error { s.mu.Lock() diff --git a/sqlite3_test.go b/sqlite3_test.go index c5deb3e..c86aba4 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -3,6 +3,7 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. +//go:build cgo // +build cgo package sqlite3 @@ -1722,6 +1723,43 @@ func TestAuthorizer(t *testing.T) { } } +func TestSetFileControlInt(t *testing.T) { + t.Run("PERSIST_WAL", func(t *testing.T) { + tempFilename := TempFilename(t) + defer os.Remove(tempFilename) + + sql.Register("sqlite3_FCNTL_PERSIST_WAL", &SQLiteDriver{ + ConnectHook: func(conn *SQLiteConn) error { + if err := conn.SetFileControlInt("", SQLITE_FCNTL_PERSIST_WAL, 1); err != nil { + return fmt.Errorf("Unexpected error from SetFileControlInt(): %w", err) + } + return nil + }, + }) + + db, err := sql.Open("sqlite3_FCNTL_PERSIST_WAL", tempFilename) + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer db.Close() + + // Set to WAL mode & write a page. + if _, err := db.Exec(`PRAGMA journal_mode = wal`); err != nil { + t.Fatal("Failed to set journal mode:", err) + } else if _, err := db.Exec(`CREATE TABLE t (x)`); err != nil { + t.Fatal("Failed to create table:", err) + } + if err := db.Close(); err != nil { + t.Fatal("Failed to close database", err) + } + + // Ensure WAL file persists after close. + if _, err := os.Stat(tempFilename + "-wal"); err != nil { + t.Fatal("Expected WAL file to be persisted after close", err) + } + }) +} + func TestNonColumnString(t *testing.T) { db, err := sql.Open("sqlite3", ":memory:") if err != nil { |