forked from mirror/go-sqlite3
Update
* Increase coverage of backup tests * FormatDSN * Rewrite C.in to types for Stringer implementation * Add PRAGMA func * Fix TestPinger * Driver, DriverContext => Fix Extensions, ConnectHook * Add Stringer to CryptEncoder, CryptSaltedEncoder * Fix OpenConnector * Renamed context to vtable_context and include build tag
This commit is contained in:
parent
b80de55e55
commit
7c7d9c8ce5
File diff suppressed because it is too large
Load Diff
|
@ -80,7 +80,7 @@ func (b *SQLiteBackup) Close() error {
|
||||||
b.b = nil
|
b.b = nil
|
||||||
runtime.SetFinalizer(b, nil)
|
runtime.SetFinalizer(b, nil)
|
||||||
|
|
||||||
if ret != 0 {
|
if ret != C.SQLITE_OK {
|
||||||
return Error{Code: ErrNo(ret)}
|
return Error{Code: ErrNo(ret)}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build cgo
|
// +build cgo
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
package sqlite3
|
package sqlite3
|
||||||
|
|
||||||
|
@ -255,10 +256,10 @@ func TestBackupError(t *testing.T) {
|
||||||
const driverName = "sqlite3_TestBackupError"
|
const driverName = "sqlite3_TestBackupError"
|
||||||
|
|
||||||
// The driver's connection will be needed in order to perform the backup.
|
// The driver's connection will be needed in order to perform the backup.
|
||||||
var dbDriverConn *SQLiteConn
|
var dbDriverConn []*SQLiteConn
|
||||||
sql.Register(driverName, &SQLiteDriver{
|
sql.Register(driverName, &SQLiteDriver{
|
||||||
ConnectHook: func(conn *SQLiteConn) error {
|
ConnectHook: func(conn *SQLiteConn) error {
|
||||||
dbDriverConn = conn
|
dbDriverConn = append(dbDriverConn, conn)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -266,21 +267,23 @@ func TestBackupError(t *testing.T) {
|
||||||
// Connect to the database.
|
// Connect to the database.
|
||||||
dbTempFilename := TempFilename(t)
|
dbTempFilename := TempFilename(t)
|
||||||
defer os.Remove(dbTempFilename)
|
defer os.Remove(dbTempFilename)
|
||||||
db, err := sql.Open(driverName, dbTempFilename)
|
srcDb, err := sql.Open(driverName, dbTempFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Failed to open the database:", err)
|
t.Fatal("Failed to open the database:", err)
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer srcDb.Close()
|
||||||
db.Ping()
|
srcDb.Ping()
|
||||||
|
|
||||||
|
srcDriverConn := dbDriverConn[0]
|
||||||
|
|
||||||
// Need the driver connection in order to perform the backup.
|
// Need the driver connection in order to perform the backup.
|
||||||
if dbDriverConn == nil {
|
if srcDriverConn == nil {
|
||||||
t.Fatal("Failed to get the driver connection.")
|
t.Fatal("Failed to get the driver connection.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare to perform the backup.
|
// Prepare to perform the backup.
|
||||||
// Intentionally using the same connection for both the source and destination databases, to trigger an error result.
|
// Intentionally using the same connection for both the source and destination databases, to trigger an error result.
|
||||||
backup, err := dbDriverConn.Backup("main", dbDriverConn, "main")
|
backup, err := srcDriverConn.Backup("main", srcDriverConn, "main")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Failed to get the expected error result.")
|
t.Fatal("Failed to get the expected error result.")
|
||||||
}
|
}
|
||||||
|
@ -291,4 +294,65 @@ func TestBackupError(t *testing.T) {
|
||||||
if backup != nil {
|
if backup != nil {
|
||||||
t.Fatal("Failed to get the expected nil backup result.")
|
t.Fatal("Failed to get the expected nil backup result.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate some test data for the given ID.
|
||||||
|
var generateTestData = func(id int) string {
|
||||||
|
return fmt.Sprintf("test-%v", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the source database with a test table containing some test data.
|
||||||
|
tx, err := srcDb.Begin()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Failed to begin a transaction when populating the source database:", err)
|
||||||
|
}
|
||||||
|
_, err = srcDb.Exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
t.Fatal("Failed to create the source database \"test\" table:", err)
|
||||||
|
}
|
||||||
|
for id := 0; id < testRowCount; id++ {
|
||||||
|
_, err = srcDb.Exec("INSERT INTO test (id, value) VALUES (?, ?)", id, generateTestData(id))
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
t.Fatal("Failed to insert a row into the source database \"test\" table:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Failed to populate the source database:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
destTempFilename := TempFilename(t)
|
||||||
|
defer os.Remove(destTempFilename)
|
||||||
|
|
||||||
|
// TODO: Rewrite for Golang:1.8
|
||||||
|
// Current part of test uses golang:1.10
|
||||||
|
destCfg := NewConfig()
|
||||||
|
destCfg.Database = destTempFilename
|
||||||
|
destDB := sql.OpenDB(destCfg) // Needs to be rewritten
|
||||||
|
destDB.Close()
|
||||||
|
|
||||||
|
// Reconfigure to open READ-ONLY
|
||||||
|
destCfg.Mode = ModeReadOnly
|
||||||
|
var destConn *SQLiteConn
|
||||||
|
destCfg.ConnectHook = func(conn *SQLiteConn) error {
|
||||||
|
destConn = conn
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenDB with Config
|
||||||
|
destDB = sql.OpenDB(destCfg)
|
||||||
|
destDB.Ping()
|
||||||
|
defer destDB.Close()
|
||||||
|
|
||||||
|
backup, err = destConn.Backup("main", srcDriverConn, "main")
|
||||||
|
_, err = backup.Step(0)
|
||||||
|
if err == nil || err.(Error).Code != ErrReadonly {
|
||||||
|
t.Fatalf("Expected read-only error; received: (%d) %s", err.(Error).Code, err.(Error).Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = backup.Close()
|
||||||
|
if err == nil || err.(Error).Code != ErrReadonly {
|
||||||
|
t.Fatalf("Expected read-only error; received: (%d) %s", err.(Error).Code, err.(Error).Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
687
driver/config.go
687
driver/config.go
|
@ -13,33 +13,159 @@ package sqlite3
|
||||||
#else
|
#else
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifndef SQLITE_OPEN_READWRITE
|
||||||
|
# define SQLITE_OPEN_READWRITE 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SQLITE_OPEN_FULLMUTEX
|
||||||
|
# define SQLITE_OPEN_FULLMUTEX 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SQLITE_DETERMINISTIC
|
||||||
|
# define SQLITE_DETERMINISTIC 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int
|
||||||
|
_sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs) {
|
||||||
|
#ifdef SQLITE_OPEN_URI
|
||||||
|
return sqlite3_open_v2(filename, ppDb, flags | SQLITE_OPEN_URI, zVfs);
|
||||||
|
#else
|
||||||
|
return sqlite3_open_v2(filename, ppDb, flags, zVfs);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Mode represents the open open for the database connection
|
||||||
|
type Mode C.int
|
||||||
|
|
||||||
|
func (m Mode) String() string {
|
||||||
|
switch m {
|
||||||
|
case ModeReadOnly:
|
||||||
|
return "ro"
|
||||||
|
case ModeReadWrite:
|
||||||
|
return "rw"
|
||||||
|
case ModeReadWriteCreate:
|
||||||
|
return "rwc"
|
||||||
|
case ModeMemory:
|
||||||
|
return "memory"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// C returns the C.int of Mode
|
||||||
|
func (m Mode) C() C.int {
|
||||||
|
return C.int(m)
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// SQLITE_OPEN_MUTEX_NO will force the database connection opens
|
// ModeReadOnly defines SQLITE_OPEN_READONLY for the database connection.
|
||||||
|
ModeReadOnly = Mode(C.SQLITE_OPEN_READONLY)
|
||||||
|
|
||||||
|
// ModeReadWrite defines SQLITE_OPEN_READWRITE for the database connection.
|
||||||
|
ModeReadWrite = Mode(C.SQLITE_OPEN_READWRITE)
|
||||||
|
|
||||||
|
// ModeReadWriteCreate defines SQLITE_OPEN_READWRITE and SQLITE_OPEN_CREATE.
|
||||||
|
ModeReadWriteCreate = Mode(C.SQLITE_OPEN_READWRITE | C.SQLITE_OPEN_CREATE)
|
||||||
|
|
||||||
|
// ModeMemory defines mode=memory which will
|
||||||
|
// create a pure in-memory database that never reads or writes from disk
|
||||||
|
ModeMemory = Mode(C.SQLITE_OPEN_MEMORY)
|
||||||
|
)
|
||||||
|
|
||||||
|
// CacheMode represents the current CacheMode
|
||||||
|
type CacheMode C.int
|
||||||
|
|
||||||
|
func (c CacheMode) String() string {
|
||||||
|
switch c {
|
||||||
|
case CacheModeShared:
|
||||||
|
return "shared"
|
||||||
|
case CacheModePrivate:
|
||||||
|
return "private"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// C returns the C.int of CacheMode
|
||||||
|
func (c CacheMode) C() C.int {
|
||||||
|
return C.int(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CacheModeShared sets the cache mode of SQLite to 'shared'
|
||||||
|
CacheModeShared = CacheMode(C.SQLITE_OPEN_SHAREDCACHE)
|
||||||
|
|
||||||
|
// CacheModePrivate sets the cache mode of SQLite to 'private'
|
||||||
|
CacheModePrivate = CacheMode(C.SQLITE_OPEN_PRIVATECACHE)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mutex represents how the database opens connections within
|
||||||
|
// single or multi-threading
|
||||||
|
type Mutex C.int
|
||||||
|
|
||||||
|
func (m Mutex) String() string {
|
||||||
|
switch m {
|
||||||
|
case MutexNo:
|
||||||
|
return "no"
|
||||||
|
case MutexFull:
|
||||||
|
return "full"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// C returns the C.int of Mutex
|
||||||
|
func (m Mutex) C() C.int {
|
||||||
|
return C.int(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MutexNo will force the database connection opens
|
||||||
// in the multi-thread threading mode as long as the
|
// in the multi-thread threading mode as long as the
|
||||||
// single-thread mode has not been set at compile-time or start-time.
|
// single-thread mode has not been set at compile-time or start-time.
|
||||||
SQLITE_OPEN_MUTEX_NO = C.SQLITE_OPEN_NOMUTEX
|
MutexNo = Mutex(C.SQLITE_OPEN_NOMUTEX)
|
||||||
|
|
||||||
// SQLITE_OPEN_MUTEX_FULL will force the database connection opens
|
// MutexFull will force the database connection opens
|
||||||
// in the serialized threading mode unless single-thread
|
// in the serialized threading mode unless single-thread
|
||||||
// was previously selected at compile-time or start-time.
|
// was previously selected at compile-time or start-time.
|
||||||
SQLITE_OPEN_MUTEX_FULL = C.SQLITE_OPEN_FULLMUTEX
|
MutexFull = Mutex(C.SQLITE_OPEN_FULLMUTEX)
|
||||||
)
|
)
|
||||||
|
|
||||||
// TxLock defines the Transaction Lock Behaviour.
|
// TxLock defines the Transaction Lock Behaviour.
|
||||||
type TxLock string
|
type TxLock string
|
||||||
|
|
||||||
func (tx TxLock) String() string {
|
func (tx TxLock) String() string {
|
||||||
|
switch tx {
|
||||||
|
case TxLockDeferred:
|
||||||
|
return "deferred"
|
||||||
|
case TxLockImmediate:
|
||||||
|
return "immediate"
|
||||||
|
case TxLockExclusive:
|
||||||
|
return "exclusive"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx TxLock) Value() string {
|
||||||
return string(tx)
|
return string(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,21 +242,25 @@ const (
|
||||||
// If the locking mode is NORMAL when first entering WAL journal mode,
|
// If the locking mode is NORMAL when first entering WAL journal mode,
|
||||||
//then the locking mode can be changed between NORMAL and EXCLUSIVE
|
//then the locking mode can be changed between NORMAL and EXCLUSIVE
|
||||||
// and back again at any time and without needing to exit WAL journal mode.
|
// and back again at any time and without needing to exit WAL journal mode.
|
||||||
type LockingMode uint8
|
type LockingMode string
|
||||||
|
|
||||||
|
func (l LockingMode) String() string {
|
||||||
|
return strings.ToLower(string(l))
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// LockingModeNormal In NORMAL locking-mode
|
// LockingModeNormal In NORMAL locking-mode
|
||||||
// (the default unless overridden at compile-time using SQLITE_DEFAULT_LOCKING_MODE),
|
// (the default unless overridden at compile-time using SQLITE_DEFAULT_LOCKING_MODE),
|
||||||
// a database connection unlocks the database file at the conclusion
|
// a database connection unlocks the database file at the conclusion
|
||||||
// of each read or write transaction.
|
// of each read or write transaction.
|
||||||
LockingModeNormal LockingMode = iota
|
LockingModeNormal = LockingMode("NORMAL")
|
||||||
|
|
||||||
// LockingModeExclusive When the locking-mode is set to EXCLUSIVE,
|
// LockingModeExclusive When the locking-mode is set to EXCLUSIVE,
|
||||||
// the database connection never releases file-locks.
|
// the database connection never releases file-locks.
|
||||||
// The first time the database is read in EXCLUSIVE mode,
|
// The first time the database is read in EXCLUSIVE mode,
|
||||||
// a shared lock is obtained and held.
|
// a shared lock is obtained and held.
|
||||||
// The first time the database is written, an exclusive lock is obtained and held.
|
// The first time the database is written, an exclusive lock is obtained and held.
|
||||||
LockingModeExclusive
|
LockingModeExclusive = LockingMode("EXCLUSIVE")
|
||||||
)
|
)
|
||||||
|
|
||||||
// AutoVacuum defines the auto vacuum status of the database.
|
// AutoVacuum defines the auto vacuum status of the database.
|
||||||
|
@ -142,7 +272,11 @@ const (
|
||||||
// that allows each database page to be traced backwards to its referrer.
|
// that allows each database page to be traced backwards to its referrer.
|
||||||
// Therefore, auto-vacuuming must be turned on before any tables are created.
|
// Therefore, auto-vacuuming must be turned on before any tables are created.
|
||||||
// It is not possible to enable or disable auto-vacuum after a table has been created.
|
// It is not possible to enable or disable auto-vacuum after a table has been created.
|
||||||
type AutoVacuum uint8
|
type AutoVacuum string
|
||||||
|
|
||||||
|
func (av AutoVacuum) String() string {
|
||||||
|
return strings.ToLower(string(av))
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// AutoVacuumNone setting means that auto-vacuum is disabled.
|
// AutoVacuumNone setting means that auto-vacuum is disabled.
|
||||||
|
@ -164,7 +298,7 @@ const (
|
||||||
// then invoke the VACUUM command to reorganize the entire database file.
|
// then invoke the VACUUM command to reorganize the entire database file.
|
||||||
// To change from "full" or "incremental" back to "none"
|
// To change from "full" or "incremental" back to "none"
|
||||||
// always requires running VACUUM even on an empty database.
|
// always requires running VACUUM even on an empty database.
|
||||||
AutoVacuumNone = AutoVacuum(0)
|
AutoVacuumNone = AutoVacuum("NONE")
|
||||||
|
|
||||||
// AutoVacuumFull sets auto vacuum of the database to FULL.
|
// AutoVacuumFull sets auto vacuum of the database to FULL.
|
||||||
//
|
//
|
||||||
|
@ -176,7 +310,7 @@ const (
|
||||||
// the way that the VACUUM command does.
|
// the way that the VACUUM command does.
|
||||||
// In fact, because it moves pages around within the file,
|
// In fact, because it moves pages around within the file,
|
||||||
// auto-vacuum can actually make fragmentation worse.
|
// auto-vacuum can actually make fragmentation worse.
|
||||||
AutoVacuumFull = AutoVacuum(1)
|
AutoVacuumFull = AutoVacuum("FULL")
|
||||||
|
|
||||||
// AutoVacuumIncremental sets the auto vacuum of the database to INCREMENTAL.
|
// AutoVacuumIncremental sets the auto vacuum of the database to INCREMENTAL.
|
||||||
//
|
//
|
||||||
|
@ -186,7 +320,7 @@ const (
|
||||||
// at each commit as it does with auto_vacuum=full.
|
// at each commit as it does with auto_vacuum=full.
|
||||||
// In incremental mode, the separate incremental_vacuum pragma must be invoked
|
// In incremental mode, the separate incremental_vacuum pragma must be invoked
|
||||||
// to cause the auto-vacuum to occur.
|
// to cause the auto-vacuum to occur.
|
||||||
AutoVacuumIncremental = AutoVacuum(2)
|
AutoVacuumIncremental = AutoVacuum("INCREMENTAL")
|
||||||
)
|
)
|
||||||
|
|
||||||
// JournalMode defines the journal mode associated with the current database connection.
|
// JournalMode defines the journal mode associated with the current database connection.
|
||||||
|
@ -198,19 +332,23 @@ const (
|
||||||
// Note also that the journal_mode cannot be changed while a transaction is active.
|
// Note also that the journal_mode cannot be changed while a transaction is active.
|
||||||
type JournalMode string
|
type JournalMode string
|
||||||
|
|
||||||
|
func (j JournalMode) String() string {
|
||||||
|
return strings.ToLower(string(j))
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// JournalModeDelete is the normal behavior.
|
// JournalModeDelete is the normal behavior.
|
||||||
// In the DELETE mode, the rollback journal is deleted at the conclusion
|
// In the DELETE mode, the rollback journal is deleted at the conclusion
|
||||||
// of each transaction.
|
// of each transaction.
|
||||||
// Indeed, the delete operation is the action that causes the transaction to commit.
|
// Indeed, the delete operation is the action that causes the transaction to commit.
|
||||||
// (See the document titled Atomic Commit In SQLite for additional detail.)
|
// (See the document titled Atomic Commit In SQLite for additional detail.)
|
||||||
JournalModeDelete JournalMode = "DELETE"
|
JournalModeDelete = JournalMode("DELETE")
|
||||||
|
|
||||||
// JournalModeTruncate commits transactions by truncating the rollback journal
|
// JournalModeTruncate commits transactions by truncating the rollback journal
|
||||||
// to zero-length instead of deleting it.
|
// to zero-length instead of deleting it.
|
||||||
// On many systems, truncating a file is much faster
|
// On many systems, truncating a file is much faster
|
||||||
// than deleting the file since the containing directory does not need to be changed.
|
// than deleting the file since the containing directory does not need to be changed.
|
||||||
JournalModeTruncate JournalMode = "TRUNCATE"
|
JournalModeTruncate = JournalMode("TRUNCATE")
|
||||||
|
|
||||||
// JournalModePersist prevents the rollback journal from being deleted
|
// JournalModePersist prevents the rollback journal from being deleted
|
||||||
// at the end of each transaction.
|
// at the end of each transaction.
|
||||||
|
@ -220,26 +358,26 @@ const (
|
||||||
// where deleting or truncating a file is much more expensive
|
// where deleting or truncating a file is much more expensive
|
||||||
// than overwriting the first block of a file with zeros.
|
// than overwriting the first block of a file with zeros.
|
||||||
// See also: PRAGMA journal_size_limit and SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT.
|
// See also: PRAGMA journal_size_limit and SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT.
|
||||||
JournalModePersist JournalMode = "PERSIST"
|
JournalModePersist = JournalMode("PERSIST")
|
||||||
|
|
||||||
// JournalModeMemory stores the rollback journal in volatile RAM.
|
// JournalModeMemory stores the rollback journal in volatile RAM.
|
||||||
// This saves disk I/O but at the expense of database safety and integrity.
|
// This saves disk I/O but at the expense of database safety and integrity.
|
||||||
// If the application using SQLite crashes in the middle of a transaction
|
// If the application using SQLite crashes in the middle of a transaction
|
||||||
// when the MEMORY journaling mode is set,
|
// when the MEMORY journaling mode is set,
|
||||||
// then the database file will very likely go corrupt.
|
// then the database file will very likely go corrupt.
|
||||||
JournalModeMemory JournalMode = "MEMORY"
|
JournalModeMemory = JournalMode("MEMORY")
|
||||||
|
|
||||||
// JournalModeWAL uses a write-ahead log instead of a rollback journal
|
// JournalModeWAL uses a write-ahead log instead of a rollback journal
|
||||||
// to implement transactions.
|
// to implement transactions.
|
||||||
// The WAL journaling mode is persistent;
|
// The WAL journaling mode is persistent;
|
||||||
// after being set it stays in effect across multiple database connections
|
// after being set it stays in effect across multiple database connections
|
||||||
// and after closing and reopening the database.
|
// and after closing and reopening the database.
|
||||||
JournalModeWAL JournalMode = "WAL"
|
JournalModeWAL = JournalMode("WAL")
|
||||||
|
|
||||||
// JournalModeDisabled disables the rollback journal completely.
|
// JournalModeOff disables the rollback journal completely.
|
||||||
// No rollback journal is ever created and hence there is never a rollback journal to delete.
|
// No rollback journal is ever created and hence there is never a rollback journal to delete.
|
||||||
// The OFF journaling mode disables the atomic commit and rollback capabilities of SQLite. The ROLLBACK command no longer works; it behaves in an undefined way. Applications must avoid using the ROLLBACK command when the journal mode is OFF. If the application crashes in the middle of a transaction when the OFF journaling mode is set, then the database file will very likely go corrupt.
|
// The OFF journaling mode disables the atomic commit and rollback capabilities of SQLite. The ROLLBACK command no longer works; it behaves in an undefined way. Applications must avoid using the ROLLBACK command when the journal mode is OFF. If the application crashes in the middle of a transaction when the OFF journaling mode is set, then the database file will very likely go corrupt.
|
||||||
JournalModeDisabled JournalMode = "OFF"
|
JournalModeOff = JournalMode("OFF")
|
||||||
)
|
)
|
||||||
|
|
||||||
// SecureDelete defines the secure-delete setting.
|
// SecureDelete defines the secure-delete setting.
|
||||||
|
@ -254,6 +392,10 @@ const (
|
||||||
// or else run VACUUM after the delete or update.
|
// or else run VACUUM after the delete or update.
|
||||||
type SecureDelete string
|
type SecureDelete string
|
||||||
|
|
||||||
|
func (sd SecureDelete) String() string {
|
||||||
|
return strings.ToLower(string(sd))
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// SecureDeleteOff disables secure deletion of content.
|
// SecureDeleteOff disables secure deletion of content.
|
||||||
SecureDeleteOff = SecureDelete("OFF")
|
SecureDeleteOff = SecureDelete("OFF")
|
||||||
|
@ -272,7 +414,11 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Synchronous sync setting of the database connection.
|
// Synchronous sync setting of the database connection.
|
||||||
type Synchronous uint8
|
type Synchronous string
|
||||||
|
|
||||||
|
func (s Synchronous) String() string {
|
||||||
|
return strings.ToLower(string(s))
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// SynchronousOff sets synchronous to OFF (0),
|
// SynchronousOff sets synchronous to OFF (0),
|
||||||
|
@ -281,7 +427,7 @@ const (
|
||||||
// but the database might become corrupted if the operating system crashes
|
// but the database might become corrupted if the operating system crashes
|
||||||
// or the computer loses power before that data has been written to the disk surface.
|
// or the computer loses power before that data has been written to the disk surface.
|
||||||
// On the other hand, commits can be orders of magnitude faster with synchronous OFF.
|
// On the other hand, commits can be orders of magnitude faster with synchronous OFF.
|
||||||
SynchronousOff = Synchronous(0)
|
SynchronousOff = Synchronous("OFF")
|
||||||
|
|
||||||
// SynchronousNormal sets synchronous to NORMAL (1),
|
// SynchronousNormal sets synchronous to NORMAL (1),
|
||||||
// the SQLite database engine will still sync at the most critical moments,
|
// the SQLite database engine will still sync at the most critical moments,
|
||||||
|
@ -297,7 +443,7 @@ const (
|
||||||
// Transactions are durable across application crashes regardless
|
// Transactions are durable across application crashes regardless
|
||||||
// of the synchronous setting or journal mode.
|
// of the synchronous setting or journal mode.
|
||||||
// The synchronous=NORMAL setting is a good choice for most applications running in WAL mode.
|
// The synchronous=NORMAL setting is a good choice for most applications running in WAL mode.
|
||||||
SynchronousNormal = Synchronous(1)
|
SynchronousNormal = Synchronous("NORMAL")
|
||||||
|
|
||||||
// SynchronousFull sets synchronous to FULL (2),
|
// SynchronousFull sets synchronous to FULL (2),
|
||||||
// the SQLite database engine will use the xSync method of the VFS
|
// the SQLite database engine will use the xSync method of the VFS
|
||||||
|
@ -306,42 +452,13 @@ const (
|
||||||
// will not corrupt the database. FULL synchronous is very safe,
|
// will not corrupt the database. FULL synchronous is very safe,
|
||||||
// but it is also slower.
|
// but it is also slower.
|
||||||
///FULL is the most commonly used synchronous setting when not in WAL mode.
|
///FULL is the most commonly used synchronous setting when not in WAL mode.
|
||||||
SynchronousFull = Synchronous(2)
|
SynchronousFull = Synchronous("FULL")
|
||||||
|
|
||||||
// SynchronousExtra is like FULL with the addition that the directory containing
|
// SynchronousExtra is like FULL with the addition that the directory containing
|
||||||
// a rollback journal is synced after that journal is unlinked to commit a transaction
|
// a rollback journal is synced after that journal is unlinked to commit a transaction
|
||||||
// in DELETE mode. EXTRA provides additional durability if the commit
|
// in DELETE mode. EXTRA provides additional durability if the commit
|
||||||
// is followed closely by a power loss.
|
// is followed closely by a power loss.
|
||||||
SynchronousExtra = Synchronous(3)
|
SynchronousExtra = Synchronous("EXTRA")
|
||||||
)
|
|
||||||
|
|
||||||
// CacheMode defines the shared-cache mode of SQLite.
|
|
||||||
type CacheMode string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// CacheModeShared sets the cache mode of SQLite to 'shared'
|
|
||||||
CacheModeShared = CacheMode("SHARED")
|
|
||||||
|
|
||||||
// CacheModePrivate sets the cache mode of SQLite to 'private'
|
|
||||||
CacheModePrivate = CacheMode("PRIVATE")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Mode defines the open mode of the SQLite database.
|
|
||||||
type Mode string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ModeReadOnly defines SQLITE_OPEN_READONLY for the database connection.
|
|
||||||
ModeReadOnly = Mode("RO")
|
|
||||||
|
|
||||||
// ModeReadWrite defines SQLITE_OPEN_READWRITE for the database connection.
|
|
||||||
ModeReadWrite = Mode("RW")
|
|
||||||
|
|
||||||
// ModeReadWriteCreate defines SQLITE_OPEN_READWRITE and SQLITE_OPEN_CREATE.
|
|
||||||
ModeReadWriteCreate = Mode("RWC")
|
|
||||||
|
|
||||||
// ModeMemory defines mode=memory which will
|
|
||||||
// create a pure in-memory database that never reads or writes from disk
|
|
||||||
ModeMemory = Mode("MEMORY")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config is configuration parsed from a DSN string.
|
// Config is configuration parsed from a DSN string.
|
||||||
|
@ -349,12 +466,19 @@ const (
|
||||||
// the NewConfig function should be used, which sets default values.
|
// the NewConfig function should be used, which sets default values.
|
||||||
// Manual usage is allowed
|
// Manual usage is allowed
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
// Database
|
||||||
|
Database string
|
||||||
|
|
||||||
// Mode of the SQLite database
|
// Mode of the SQLite database
|
||||||
Mode Mode
|
Mode Mode
|
||||||
|
|
||||||
// CacheMode of the SQLite Connection
|
// CacheMode of the SQLite Connection
|
||||||
Cache CacheMode
|
Cache CacheMode
|
||||||
|
|
||||||
|
// Mutex flag SQLITE_OPEN_MUTEX_NO, SQLITE_OPEN_MUTEX_FULL
|
||||||
|
// Defaults to SQLITE_OPEN_MUTEX_FULL
|
||||||
|
Mutex Mutex
|
||||||
|
|
||||||
// The immutable parameter is a boolean query parameter that indicates
|
// The immutable parameter is a boolean query parameter that indicates
|
||||||
// that the database file is stored on read-only media. When immutable is set,
|
// that the database file is stored on read-only media. When immutable is set,
|
||||||
// SQLite assumes that the database file cannot be changed,
|
// SQLite assumes that the database file cannot be changed,
|
||||||
|
@ -364,10 +488,6 @@ type Config struct {
|
||||||
// does in fact change can result in incorrect query results and/or SQLITE_CORRUPT errors.
|
// does in fact change can result in incorrect query results and/or SQLITE_CORRUPT errors.
|
||||||
Immutable bool
|
Immutable bool
|
||||||
|
|
||||||
// Mutex flag SQLITE_OPEN_MUTEX_NO, SQLITE_OPEN_MUTEX_FULL
|
|
||||||
// Defaults to SQLITE_OPEN_MUTEX_FULL
|
|
||||||
Mutex int
|
|
||||||
|
|
||||||
// TimeZone location
|
// TimeZone location
|
||||||
TimeZone *time.Location
|
TimeZone *time.Location
|
||||||
|
|
||||||
|
@ -430,6 +550,12 @@ type Config struct {
|
||||||
// WriteableSchema enables of disables the ability to using UPDATE, INSERT, DELETE
|
// WriteableSchema enables of disables the ability to using UPDATE, INSERT, DELETE
|
||||||
// Warning: misuse of this pragma can easily result in a corrupt database file.
|
// Warning: misuse of this pragma can easily result in a corrupt database file.
|
||||||
WriteableSchema bool
|
WriteableSchema bool
|
||||||
|
|
||||||
|
// Extensions
|
||||||
|
Extensions []string
|
||||||
|
|
||||||
|
// ConnectHook
|
||||||
|
ConnectHook func(*SQLiteConn) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth holds the authentication configuration for the SQLite UserAuth module.
|
// Auth holds the authentication configuration for the SQLite UserAuth module.
|
||||||
|
@ -450,10 +576,15 @@ type Auth struct {
|
||||||
// NewConfig creates a new Config and sets default values.
|
// NewConfig creates a new Config and sets default values.
|
||||||
func NewConfig() *Config {
|
func NewConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
|
// This is the behavior that is always used
|
||||||
|
// for sqlite3_open() and sqlite3_open16().
|
||||||
|
// This is way it is set as default.
|
||||||
|
Mode: ModeReadWriteCreate,
|
||||||
|
|
||||||
|
Database: ":memory:",
|
||||||
Cache: CacheModePrivate,
|
Cache: CacheModePrivate,
|
||||||
Immutable: false,
|
Immutable: false,
|
||||||
Authentication: &Auth{},
|
Mutex: MutexFull,
|
||||||
Mutex: SQLITE_OPEN_MUTEX_FULL,
|
|
||||||
TransactionLock: TxLockDeferred,
|
TransactionLock: TxLockDeferred,
|
||||||
LockingMode: LockingModeNormal,
|
LockingMode: LockingModeNormal,
|
||||||
AutoVacuum: AutoVacuumNone,
|
AutoVacuum: AutoVacuumNone,
|
||||||
|
@ -467,57 +598,386 @@ func NewConfig() *Config {
|
||||||
SecureDelete: SecureDeleteOff,
|
SecureDelete: SecureDeleteOff,
|
||||||
Synchronous: SynchronousNormal,
|
Synchronous: SynchronousNormal,
|
||||||
WriteableSchema: false,
|
WriteableSchema: false,
|
||||||
|
Authentication: &Auth{
|
||||||
|
Encoder: NewSHA1Encoder(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatDSN formats the given Config into a DSN string which can be passed to
|
// FormatDSN formats the given Config into a DSN string which can be passed to
|
||||||
// the driver.
|
// the driver.
|
||||||
func (cfg *Config) FormatDSN() string {
|
func (cfg *Config) FormatDSN() string {
|
||||||
// TODO: FormatDSN
|
var buf bytes.Buffer
|
||||||
return ""
|
|
||||||
|
params := url.Values{}
|
||||||
|
params.Set("mode", cfg.Mode.String())
|
||||||
|
params.Set("cache", cfg.Cache.String())
|
||||||
|
params.Set("mutex", cfg.Mutex.String())
|
||||||
|
|
||||||
|
if cfg.Immutable {
|
||||||
|
params.Set("immutable", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.TimeZone != nil {
|
||||||
|
if cfg.TimeZone == time.Local {
|
||||||
|
params.Set("tz", "auto")
|
||||||
|
} else {
|
||||||
|
params.Set("tz", cfg.TimeZone.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.TransactionLock != TxLockDeferred {
|
||||||
|
params.Set("txlock", cfg.TransactionLock.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.LockingMode != LockingModeNormal {
|
||||||
|
params.Set("lock", cfg.LockingMode.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.AutoVacuum != AutoVacuumNone {
|
||||||
|
params.Set("vacuum", cfg.AutoVacuum.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.CaseSensitiveLike {
|
||||||
|
params.Set("cslike", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.DeferForeignKeys {
|
||||||
|
params.Set("defer_fk", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.ForeignKeyConstraints {
|
||||||
|
params.Set("fk", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.IgnoreCheckConstraints {
|
||||||
|
params.Set("ignore_check_contraints", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.JournalMode != JournalModeDelete {
|
||||||
|
params.Set("journal", cfg.JournalMode.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.QueryOnly {
|
||||||
|
params.Set("query_only", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.RecursiveTriggers {
|
||||||
|
params.Set("recursive_triggers", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.SecureDelete != SecureDeleteOff {
|
||||||
|
params.Set("secure_delete", cfg.SecureDelete.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Synchronous != SynchronousNormal {
|
||||||
|
params.Set("syn", cfg.Synchronous.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.WriteableSchema {
|
||||||
|
params.Set("writable_schema", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.Authentication.Username) > 0 && len(cfg.Authentication.Password) > 0 {
|
||||||
|
params.Set("user", cfg.Authentication.Username)
|
||||||
|
params.Set("pass", cfg.Authentication.Password)
|
||||||
|
|
||||||
|
if len(cfg.Authentication.Salt) > 0 {
|
||||||
|
params.Set("salt", cfg.Authentication.Salt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Authentication.Encoder != nil {
|
||||||
|
params.Set("crypt", cfg.Authentication.Encoder.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(cfg.Database, "file:") {
|
||||||
|
buf.WriteString("file:")
|
||||||
|
}
|
||||||
|
buf.WriteString(cfg.Database)
|
||||||
|
|
||||||
|
// Append Options
|
||||||
|
buf.WriteRune('?')
|
||||||
|
buf.WriteString(params.Encode())
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create connection from Configuration
|
// Create connection from Configuration
|
||||||
func (cfg *Config) createConnection() (driver.Conn, error) {
|
func (cfg *Config) createConnection() (driver.Conn, error) {
|
||||||
//var db *C.sqlite3
|
if C.sqlite3_threadsafe() == 0 {
|
||||||
|
return nil, errors.New("sqlite library was not compiled for thread-safe operation")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.Database) == 0 {
|
||||||
|
return nil, fmt.Errorf("No database configured")
|
||||||
|
}
|
||||||
|
|
||||||
// name := C.CString(dsn)
|
var db *C.sqlite3
|
||||||
// defer C.free(unsafe.Pointer(name))
|
|
||||||
// rv := C._sqlite3_open_v2(name, &db,
|
|
||||||
// mutex|C.SQLITE_OPEN_READWRITE|C.SQLITE_OPEN_CREATE,
|
|
||||||
// nil)
|
|
||||||
// if rv != 0 {
|
|
||||||
// return nil, Error{Code: ErrNo(rv)}
|
|
||||||
// }
|
|
||||||
// if db == nil {
|
|
||||||
// return nil, errors.New("sqlite succeeded without returning a database")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// rv = C.sqlite3_busy_timeout(db, C.int(busyTimeout))
|
// Configure Database URI
|
||||||
// if rv != C.SQLITE_OK {
|
// Because we are adding the 'immutable' flag to the database file
|
||||||
// C.sqlite3_close_v2(db)
|
// We are required to conform the database path to an URI
|
||||||
// return nil, Error{Code: ErrNo(rv)}
|
// The immutable flag is an query parameter which means that the URI needs
|
||||||
// }
|
// to start with 'file:'. Regardless if it is an in-memory database or not.
|
||||||
|
uri := cfg.Database
|
||||||
|
if !strings.HasPrefix(uri, "file:") {
|
||||||
|
uri = fmt.Sprintf("file:%s", uri)
|
||||||
|
}
|
||||||
|
if !strings.Contains(uri, ":memory:") {
|
||||||
|
uri = fmt.Sprintf("%s?immutable=%t", uri, cfg.Immutable)
|
||||||
|
}
|
||||||
|
database := C.CString(uri)
|
||||||
|
|
||||||
// exec := func(s string) error {
|
// Free CString on return
|
||||||
// cs := C.CString(s)
|
defer C.free(unsafe.Pointer(database))
|
||||||
// rv := C.sqlite3_exec(db, cs, nil, nil, nil)
|
|
||||||
// C.free(unsafe.Pointer(cs))
|
|
||||||
// if rv != C.SQLITE_OK {
|
|
||||||
// fmt.Printf("-Open-Exec() %d\n", rv)
|
|
||||||
// return lastError(db)
|
|
||||||
// }
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// &SQLiteConn{
|
// Open the database
|
||||||
// db: db,
|
// https://www.sqlite.org/c3ref/open.html
|
||||||
// tz: cfg.TimeZone,
|
rv := C._sqlite3_open_v2(
|
||||||
// txlock: cfg.TransactionLock.String(),
|
database,
|
||||||
// }
|
&db,
|
||||||
|
cfg.Mutex.C()|cfg.Cache.C()|cfg.Mode.C(),
|
||||||
|
nil)
|
||||||
|
|
||||||
return nil, nil
|
// Check if the database was opened succesful.
|
||||||
|
if rv != C.SQLITE_OK {
|
||||||
|
fmt.Println(Error{Code: ErrNo(rv)})
|
||||||
|
return nil, Error{Code: ErrNo(rv)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify we have a database pointer
|
||||||
|
if db == nil {
|
||||||
|
return nil, errors.New("sqlite succeeded without returning a database")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set SQLITE Busy Timeout Handler
|
||||||
|
rv = C.sqlite3_busy_timeout(db, C.int(cfg.BusyTimeout))
|
||||||
|
if rv != C.SQLITE_OK {
|
||||||
|
// Failed to set busy timeout
|
||||||
|
// close the database and return the error
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
|
||||||
|
return nil, Error{Code: ErrNo(rv)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create basic connection
|
||||||
|
conn := &SQLiteConn{
|
||||||
|
db: db,
|
||||||
|
tz: cfg.TimeZone,
|
||||||
|
txlock: cfg.TransactionLock.Value(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point we have the following
|
||||||
|
// - database pointer
|
||||||
|
// - basic connection
|
||||||
|
//
|
||||||
|
// Now we need to configure the connection according to the *Config
|
||||||
|
|
||||||
|
// USER AUTHENTICATION
|
||||||
|
//
|
||||||
|
// User Authentication is always performed even when
|
||||||
|
// sqlite_userauth is not compiled in, because without user authentication
|
||||||
|
// the authentication is a no-op.
|
||||||
|
//
|
||||||
|
// Workflow
|
||||||
|
// - Authenticate
|
||||||
|
// ON::SUCCESS => Continue
|
||||||
|
// ON::SQLITE_AUTH => Return error and exit Open(...)
|
||||||
|
//
|
||||||
|
// - Activate User Authentication
|
||||||
|
// Check if the user wants to activate User Authentication.
|
||||||
|
// If so then first create a temporary AuthConn to the database
|
||||||
|
// This is possible because we are already succesfully authenticated.
|
||||||
|
//
|
||||||
|
// - Check if `sqlite_user`` table exists
|
||||||
|
// YES => Add the provided user from DSN as Admin User and
|
||||||
|
// activate user authentication.
|
||||||
|
// NO => Continue
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Because we need to perform authentication we need to register
|
||||||
|
// the required functions on the connection.
|
||||||
|
|
||||||
|
// Register sqlite_crypt function with the CryptEncoder provided
|
||||||
|
// within *Config.Authentication
|
||||||
|
if err := conn.RegisterFunc("sqlite_crypt", cfg.Authentication.Encoder.Encode, true); err != nil {
|
||||||
|
return nil, fmt.Errorf("CryptEncoderSHA1: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register: authenticate
|
||||||
|
// Authenticate will perform an authentication of the provided username
|
||||||
|
// and password against the database.
|
||||||
|
//
|
||||||
|
// If a database contains the SQLITE_USER table, then the
|
||||||
|
// call to Authenticate must be invoked with an
|
||||||
|
// appropriate username and password prior to enable read and write
|
||||||
|
// access to the database.
|
||||||
|
//
|
||||||
|
// Return SQLITE_OK on success or SQLITE_ERROR if the username/password
|
||||||
|
// combination is incorrect or unknown.
|
||||||
|
//
|
||||||
|
// If the SQLITE_USER table is not present in the database file, then
|
||||||
|
// this interface is a harmless no-op returnning SQLITE_OK.
|
||||||
|
if err := conn.RegisterFunc("authenticate", conn.authenticate, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register: auth_user_add
|
||||||
|
// auth_user_add can be used (by an admin user only)
|
||||||
|
// to create a new user. When called on a no-authentication-required
|
||||||
|
// database, this routine converts the database into an authentication-
|
||||||
|
// required database, automatically makes the added user an
|
||||||
|
// administrator, and logs in the current connection as that user.
|
||||||
|
// The AuthUserAdd only works for the "main" database, not
|
||||||
|
// for any ATTACH-ed databases. Any call to AuthUserAdd by a
|
||||||
|
// non-admin user results in an error.
|
||||||
|
if err := conn.RegisterFunc("auth_user_add", conn.authUserAdd, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register: auth_user_change
|
||||||
|
// auth_user_change can be used to change a users
|
||||||
|
// login credentials or admin privilege. Any user can change their own
|
||||||
|
// login credentials. Only an admin user can change another users login
|
||||||
|
// credentials or admin privilege setting. No user may change their own
|
||||||
|
// admin privilege setting.
|
||||||
|
if err := conn.RegisterFunc("auth_user_change", conn.authUserChange, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register: auth_user_delete
|
||||||
|
// auth_user_delete can be used (by an admin user only)
|
||||||
|
// to delete a user. The currently logged-in user cannot be deleted,
|
||||||
|
// which guarantees that there is always an admin user and hence that
|
||||||
|
// the database cannot be converted into a no-authentication-required
|
||||||
|
// database.
|
||||||
|
if err := conn.RegisterFunc("auth_user_delete", conn.authUserDelete, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register: auth_enabled
|
||||||
|
// auth_enabled can be used to check if user authentication is enabled
|
||||||
|
if err := conn.RegisterFunc("auth_enabled", conn.authEnabled, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preform Authentication only if username and password are provided
|
||||||
|
// If authentication is not enabled on the database
|
||||||
|
// this call is a NO-OP.
|
||||||
|
// Only call this when Username and Password are provided in the *Config
|
||||||
|
if len(cfg.Authentication.Username) > 0 && len(cfg.Authentication.Password) > 0 {
|
||||||
|
if err := conn.Authenticate(cfg.Authentication.Username, cfg.Authentication.Password); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AUTO VACUUM
|
||||||
|
// The user preference for auto_vacuum needs to be implemented directly after
|
||||||
|
// the authentication and before the sqlite_user table gets created if the user
|
||||||
|
// decides to activate User Authentication because
|
||||||
|
// auto_vacuum needs to be set before any tables are created
|
||||||
|
// and activating user authentication creates the internal table `sqlite_user`.
|
||||||
|
if err := conn.PRAGMA(PRAGMA_AUTO_VACUUM, cfg.AutoVacuum.String()); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if authentication is enabled
|
||||||
|
// This can now be succesfully checked because we are successfully connected.
|
||||||
|
// Only issue this when Username and Password are configured.
|
||||||
|
// If this is an unauthenticated database
|
||||||
|
// the provided user will be created as Admin.
|
||||||
|
if len(cfg.Authentication.Username) > 0 && len(cfg.Authentication.Password) > 0 {
|
||||||
|
authExists := conn.AuthEnabled()
|
||||||
|
if !authExists {
|
||||||
|
if err := conn.AuthUserAdd(cfg.Authentication.Username, cfg.Authentication.Password, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case Sensitive LIKE
|
||||||
|
if err := conn.PRAGMA(PRAGMA_CASE_SENSITIVE_LIKE, strconv.FormatBool(cfg.CaseSensitiveLike)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer Foreign Keys
|
||||||
|
if err := conn.PRAGMA(PRAGMA_DEFER_FOREIGN_KEYS, strconv.FormatBool(cfg.DeferForeignKeys)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore CHECK constraints
|
||||||
|
if err := conn.PRAGMA(PRAGMA_IGNORE_CHECK_CONTRAINTS, strconv.FormatBool(cfg.IgnoreCheckConstraints)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Journal Mode
|
||||||
|
if err := conn.PRAGMA(PRAGMA_JOURNAL_MODE, cfg.JournalMode.String()); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locking Mode
|
||||||
|
if err := conn.PRAGMA(PRAGMA_LOCKING_MODE, cfg.LockingMode.String()); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query Only
|
||||||
|
if err := conn.PRAGMA(PRAGMA_QUERY_ONLY, strconv.FormatBool(cfg.QueryOnly)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursive Triggers
|
||||||
|
if err := conn.PRAGMA(PRAGMA_RECURSIVE_TRIGGERS, strconv.FormatBool(cfg.RecursiveTriggers)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secure Delete
|
||||||
|
if err := conn.PRAGMA(PRAGMA_SECURE_DELETE, cfg.SecureDelete.String()); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronous
|
||||||
|
if err := conn.PRAGMA(PRAGMA_SYNCHRONOUS, cfg.SecureDelete.String()); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writable Schema
|
||||||
|
if err := conn.PRAGMA(PRAGMA_WRITABLE_SCHEMA, strconv.FormatBool(cfg.WriteableSchema)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load Extensions
|
||||||
|
if len(cfg.Extensions) > 0 {
|
||||||
|
if err := conn.loadExtensions(cfg.Extensions); err != nil {
|
||||||
|
//fmt.Println("Error while loading Extensions")
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure Connect Hooks
|
||||||
|
if cfg.ConnectHook != nil {
|
||||||
|
if err := cfg.ConnectHook(conn); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure Finalizer
|
||||||
|
runtime.SetFinalizer(conn, (*SQLiteConn).Close)
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseDSN parses the DSN string to a Config
|
// ParseDSN parses the DSN string to a Config
|
||||||
|
@ -525,8 +985,13 @@ func ParseDSN(dsn string) (cfg *Config, err error) {
|
||||||
// New default with default values
|
// New default with default values
|
||||||
cfg = NewConfig()
|
cfg = NewConfig()
|
||||||
|
|
||||||
|
cfg.Database = dsn
|
||||||
|
|
||||||
pos := strings.IndexRune(dsn, '?')
|
pos := strings.IndexRune(dsn, '?')
|
||||||
if pos >= 1 {
|
if pos >= 1 {
|
||||||
|
// Update DatabaseURI
|
||||||
|
cfg.Database = dsn[0:pos]
|
||||||
|
|
||||||
// Parse Options
|
// Parse Options
|
||||||
params, err := url.ParseQuery(dsn[pos+1:])
|
params, err := url.ParseQuery(dsn[pos+1:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -592,13 +1057,32 @@ func ParseDSN(dsn string) (cfg *Config, err error) {
|
||||||
if k == "mode" {
|
if k == "mode" {
|
||||||
val := params.Get(k)
|
val := params.Get(k)
|
||||||
switch strings.ToUpper(val) {
|
switch strings.ToUpper(val) {
|
||||||
case "RO", "RW", "RWC", "MEMORY":
|
case "RO":
|
||||||
cfg.Mode = Mode(strings.ToUpper(val))
|
cfg.Mode = ModeReadOnly
|
||||||
|
case "RW":
|
||||||
|
cfg.Mode = ModeReadWrite
|
||||||
|
case "RWC":
|
||||||
|
cfg.Mode = ModeReadWriteCreate
|
||||||
|
case "MEMORY":
|
||||||
|
cfg.Mode = ModeMemory
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unknown mode: %v, expecting value of 'ro, rw, rwc, memory'", val)
|
return nil, fmt.Errorf("Unknown mode: %v, expecting value of 'ro, rw, rwc, memory'", val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mutex
|
||||||
|
if k == "mutex" {
|
||||||
|
val := params.Get(k)
|
||||||
|
switch strings.ToLower(val) {
|
||||||
|
case "no":
|
||||||
|
cfg.Mutex = MutexNo
|
||||||
|
case "full":
|
||||||
|
cfg.Mutex = MutexFull
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Invalid mutex: %v, expecting value of 'no, full", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Timezone
|
// Timezone
|
||||||
if k == "tz" || k == "timezone" || k == "loc" {
|
if k == "tz" || k == "timezone" || k == "loc" {
|
||||||
val := params.Get(k)
|
val := params.Get(k)
|
||||||
|
@ -613,19 +1097,6 @@ func ParseDSN(dsn string) (cfg *Config, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mutex
|
|
||||||
if k == "mutex" {
|
|
||||||
val := params.Get(k)
|
|
||||||
switch strings.ToLower(val) {
|
|
||||||
case "no":
|
|
||||||
cfg.Mutex = SQLITE_OPEN_MUTEX_NO
|
|
||||||
case "full":
|
|
||||||
cfg.Mutex = SQLITE_OPEN_MUTEX_FULL
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("Invalid mutex: %v, expecting value of 'no, full", val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transaction Lock
|
// Transaction Lock
|
||||||
if k == "txlock" || k == "transaction_lock" {
|
if k == "txlock" || k == "transaction_lock" {
|
||||||
val := params.Get(k)
|
val := params.Get(k)
|
||||||
|
|
|
@ -0,0 +1,627 @@
|
||||||
|
// Copyright (C) 2018 The Go-SQLite3 Authors.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by an MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build cgo
|
||||||
|
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseDSN(t *testing.T) {
|
||||||
|
// URI
|
||||||
|
uriCases := map[string]*Config{
|
||||||
|
"file:test.db": &Config{
|
||||||
|
Database: "file:test.db",
|
||||||
|
},
|
||||||
|
"file::memory:": &Config{
|
||||||
|
Database: "file::memory:",
|
||||||
|
},
|
||||||
|
"test.db": &Config{
|
||||||
|
Database: "test.db",
|
||||||
|
},
|
||||||
|
":memory:": &Config{
|
||||||
|
Database: ":memory:",
|
||||||
|
},
|
||||||
|
"test.db?%35%2%%43?test=false": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range uriCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.Database != c.Database {
|
||||||
|
t.Fatalf("Failed to parse database uri; expected: %s, got: %s", c.Database, cfg.Database)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth
|
||||||
|
authCases := map[string]*Config{
|
||||||
|
"test.db?user=admin&pass=admin&salt=test": &Config{
|
||||||
|
Authentication: &Auth{
|
||||||
|
Username: "admin",
|
||||||
|
Password: "admin",
|
||||||
|
Salt: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range authCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if cfg.Authentication.Username != c.Authentication.Username {
|
||||||
|
t.Fatalf("Failed to parse 'user'; expected: %s, got %s", c.Authentication.Username, cfg.Authentication.Username)
|
||||||
|
}
|
||||||
|
if cfg.Authentication.Password != c.Authentication.Password {
|
||||||
|
t.Fatalf("Failed to parse 'pass'; expected: %s, got %s", c.Authentication.Password, cfg.Authentication.Password)
|
||||||
|
}
|
||||||
|
if cfg.Authentication.Salt != c.Authentication.Salt {
|
||||||
|
t.Fatalf("Failed to parse 'salt'; expected: %s, got %s", c.Authentication.Salt, cfg.Authentication.Salt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crypt
|
||||||
|
cryptCases := map[string]*Config{
|
||||||
|
"test.db?crypt=auto": nil,
|
||||||
|
"test.db?crypt=sha1": &Config{
|
||||||
|
Authentication: &Auth{
|
||||||
|
Encoder: NewSHA1Encoder(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test.db?crypt=ssha1": nil,
|
||||||
|
"test.db?crypt=ssha1&salt=salt": &Config{
|
||||||
|
Authentication: &Auth{
|
||||||
|
Salt: "salt",
|
||||||
|
Encoder: NewSSHA1Encoder("salt"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test.db?crypt=sha256": &Config{
|
||||||
|
Authentication: &Auth{
|
||||||
|
Encoder: NewSHA256Encoder(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test.db?crypt=ssha256": nil,
|
||||||
|
"test.db?crypt=ssha256&salt=salt": &Config{
|
||||||
|
Authentication: &Auth{
|
||||||
|
Salt: "salt",
|
||||||
|
Encoder: NewSSHA256Encoder("salt"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test.db?crypt=sha384": &Config{
|
||||||
|
Authentication: &Auth{
|
||||||
|
Encoder: NewSHA384Encoder(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test.db?crypt=ssha384": nil,
|
||||||
|
"test.db?crypt=ssha384&salt=salt": &Config{
|
||||||
|
Authentication: &Auth{
|
||||||
|
Salt: "salt",
|
||||||
|
Encoder: NewSSHA384Encoder("salt"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test.db?crypt=sha512": &Config{
|
||||||
|
Authentication: &Auth{
|
||||||
|
Encoder: NewSHA512Encoder(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test.db?crypt=ssha512": nil,
|
||||||
|
"test.db?crypt=ssha512&salt=salt": &Config{
|
||||||
|
Authentication: &Auth{
|
||||||
|
Salt: "salt",
|
||||||
|
Encoder: NewSSHA512Encoder("salt"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range cryptCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'crypt'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if reflect.TypeOf(cfg.Authentication.Encoder).String() != reflect.TypeOf(c.Authentication.Encoder).String() {
|
||||||
|
t.Fatal("Failed to parse 'crypt'")
|
||||||
|
}
|
||||||
|
if len(cfg.Authentication.Salt) > 0 {
|
||||||
|
if cfg.Authentication.Salt != c.Authentication.Salt {
|
||||||
|
t.Fatal("Failed to parse: 'salt'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache
|
||||||
|
cacheCases := map[string]*Config{
|
||||||
|
"test.db?cache=shared": &Config{
|
||||||
|
Cache: CacheModeShared,
|
||||||
|
},
|
||||||
|
"test.db?cache=private": &Config{
|
||||||
|
Cache: CacheModePrivate,
|
||||||
|
},
|
||||||
|
"test.db?cache=bogus": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range cacheCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'cache'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.Cache != c.Cache {
|
||||||
|
t.Fatalf("Failed to parse 'cache'; expected: %d, got: %d", c.Cache, cfg.Cache)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Immutable
|
||||||
|
immutableCases := map[string]*Config{
|
||||||
|
"test.db?immutable=false": &Config{
|
||||||
|
Immutable: false,
|
||||||
|
},
|
||||||
|
"test.db?immutable=true": &Config{
|
||||||
|
Immutable: true,
|
||||||
|
},
|
||||||
|
"test.db?immutable=active": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range immutableCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'immutable'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.Immutable != c.Immutable {
|
||||||
|
t.Fatalf("Failed to parse 'immutable'; expected: %t, got: %t", c.Immutable, cfg.Immutable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode
|
||||||
|
modeCases := map[string]*Config{
|
||||||
|
"test.db?mode=ro": &Config{
|
||||||
|
Mode: ModeReadOnly,
|
||||||
|
},
|
||||||
|
"test.db?mode=rw": &Config{
|
||||||
|
Mode: ModeReadWrite,
|
||||||
|
},
|
||||||
|
"test.db?mode=rwc": &Config{
|
||||||
|
Mode: ModeReadWriteCreate,
|
||||||
|
},
|
||||||
|
"test.db?mode=memory": &Config{
|
||||||
|
Mode: ModeMemory,
|
||||||
|
},
|
||||||
|
"test.db?mode=full": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range modeCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'mode'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.Mode != c.Mode {
|
||||||
|
t.Fatalf("Failed to parse 'mode'; expected: %d, got: %d", c.Mode, cfg.Mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutex
|
||||||
|
mutexCases := map[string]*Config{
|
||||||
|
"test.db?mutex=no": &Config{
|
||||||
|
Mutex: MutexNo,
|
||||||
|
},
|
||||||
|
"test.db?mutex=full": &Config{
|
||||||
|
Mutex: MutexFull,
|
||||||
|
},
|
||||||
|
"test.db?mutex=bogus": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range mutexCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.Mutex != c.Mutex {
|
||||||
|
t.Fatalf("Failed to parse 'mutex'; expected: %d, got: %d", c.Mutex, cfg.Mutex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timezone
|
||||||
|
ams, _ := time.LoadLocation("Europe/Amsterdam")
|
||||||
|
tzCases := map[string]*Config{
|
||||||
|
"test.db?tz=auto": &Config{
|
||||||
|
TimeZone: time.Local,
|
||||||
|
},
|
||||||
|
"test.db?tz=Europe/Amsterdam": &Config{
|
||||||
|
TimeZone: ams,
|
||||||
|
},
|
||||||
|
"test.db?tz=Atlantis": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range tzCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.TimeZone.String() != c.TimeZone.String() {
|
||||||
|
t.Fatal("Failed to parse timezone")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction Lock
|
||||||
|
txLockCases := map[string]*Config{
|
||||||
|
"test.db?txlock=deferred": &Config{
|
||||||
|
TransactionLock: TxLockDeferred,
|
||||||
|
},
|
||||||
|
"test.db?txlock=immediate": &Config{
|
||||||
|
TransactionLock: TxLockImmediate,
|
||||||
|
},
|
||||||
|
":memory:?txlock=exclusive": &Config{
|
||||||
|
TransactionLock: TxLockExclusive,
|
||||||
|
},
|
||||||
|
"test.db?txlock=bogus": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range txLockCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.TransactionLock != c.TransactionLock {
|
||||||
|
t.Fatalf("Failed to parse txlock; expected: %s, got: %s", c.TransactionLock, cfg.Database)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto Vacuum
|
||||||
|
vacuumCases := map[string]*Config{
|
||||||
|
"test.db?vacuum=none": &Config{
|
||||||
|
AutoVacuum: AutoVacuumNone,
|
||||||
|
},
|
||||||
|
"test.db?vacuum=full": &Config{
|
||||||
|
AutoVacuum: AutoVacuumFull,
|
||||||
|
},
|
||||||
|
"test.db?vacuum=incremental": &Config{
|
||||||
|
AutoVacuum: AutoVacuumIncremental,
|
||||||
|
},
|
||||||
|
"test.db?vacuum=bogus": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range vacuumCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'autovacuum, vacuum'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.AutoVacuum != c.AutoVacuum {
|
||||||
|
t.Fatalf("Failed to parse 'autovacuum'; expected: %s, got: %s", c.AutoVacuum, cfg.AutoVacuum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Busy Timeout
|
||||||
|
timeoutCases := map[string]*Config{
|
||||||
|
"test.db?timeout=5000": &Config{
|
||||||
|
BusyTimeout: 5000 * time.Millisecond,
|
||||||
|
},
|
||||||
|
"test.db?timeout=never": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range timeoutCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'timeout'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.BusyTimeout != c.BusyTimeout {
|
||||||
|
t.Fatalf("Failed to parse 'timeout'; expected: %d, got: %d", c.BusyTimeout, cfg.BusyTimeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case sensitive LIKE
|
||||||
|
cslikeCases := map[string]*Config{
|
||||||
|
"test.db?cslike=false": &Config{
|
||||||
|
CaseSensitiveLike: false,
|
||||||
|
},
|
||||||
|
"test.db?cslike=true": &Config{
|
||||||
|
CaseSensitiveLike: true,
|
||||||
|
},
|
||||||
|
"test.db?cslike=active": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range cslikeCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'cslike'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.CaseSensitiveLike != c.CaseSensitiveLike {
|
||||||
|
t.Fatalf("Failed to parse 'cslike'; expected: %t, got: %t", c.CaseSensitiveLike, cfg.CaseSensitiveLike)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer Foreign Keys
|
||||||
|
dfkCases := map[string]*Config{
|
||||||
|
"test.db?defer_fk=false": &Config{
|
||||||
|
DeferForeignKeys: false,
|
||||||
|
},
|
||||||
|
"test.db?defer_fk=true": &Config{
|
||||||
|
DeferForeignKeys: true,
|
||||||
|
},
|
||||||
|
"test.db?defer_fk=active": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range dfkCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'defer_fk'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.DeferForeignKeys != c.DeferForeignKeys {
|
||||||
|
t.Fatalf("Failed to parse 'defer_fk'; expected: %t, got: %t", c.DeferForeignKeys, cfg.DeferForeignKeys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foreign Key
|
||||||
|
fkCases := map[string]*Config{
|
||||||
|
"test.db?fk=false": &Config{
|
||||||
|
ForeignKeyConstraints: false,
|
||||||
|
},
|
||||||
|
"test.db?fk=true": &Config{
|
||||||
|
ForeignKeyConstraints: true,
|
||||||
|
},
|
||||||
|
"test.db?fk=active": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range fkCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'fk'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.ForeignKeyConstraints != c.ForeignKeyConstraints {
|
||||||
|
t.Fatalf("Failed to parse 'fk'; expected: %t, got: %t", c.ForeignKeyConstraints, cfg.ForeignKeyConstraints)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore CHECK constraints
|
||||||
|
iCases := map[string]*Config{
|
||||||
|
"test.db?ignore_check_constraints=false": &Config{
|
||||||
|
IgnoreCheckConstraints: false,
|
||||||
|
},
|
||||||
|
"test.db?ignore_check_constraints=true": &Config{
|
||||||
|
IgnoreCheckConstraints: true,
|
||||||
|
},
|
||||||
|
"test.db?ignore_check_constraints=active": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range iCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'ignore_check_constraints'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.IgnoreCheckConstraints != c.IgnoreCheckConstraints {
|
||||||
|
t.Fatalf("Failed to parse 'ignore_check_constraints'; expected: %t, got: %t", c.IgnoreCheckConstraints, cfg.IgnoreCheckConstraints)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronous
|
||||||
|
syncCases := map[string]*Config{
|
||||||
|
"test.db?sync=off": &Config{
|
||||||
|
Synchronous: SynchronousOff,
|
||||||
|
},
|
||||||
|
"test.db?sync=normal": &Config{
|
||||||
|
Synchronous: SynchronousNormal,
|
||||||
|
},
|
||||||
|
"test.db?sync=full": &Config{
|
||||||
|
Synchronous: SynchronousFull,
|
||||||
|
},
|
||||||
|
"test.db?sync=extra": &Config{
|
||||||
|
Synchronous: SynchronousExtra,
|
||||||
|
},
|
||||||
|
"test.db?sync=bogus": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range syncCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'sync'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.Synchronous != c.Synchronous {
|
||||||
|
t.Fatalf("Failed to parse 'sync'; expected: %s, got: %s", c.Synchronous, cfg.Synchronous)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Journal Mode
|
||||||
|
journalCases := map[string]*Config{
|
||||||
|
"test.db?journal=delete": &Config{
|
||||||
|
JournalMode: JournalModeDelete,
|
||||||
|
},
|
||||||
|
"test.db?journal=truncate": &Config{
|
||||||
|
JournalMode: JournalModeTruncate,
|
||||||
|
},
|
||||||
|
"test.db?journal=persist": &Config{
|
||||||
|
JournalMode: JournalModePersist,
|
||||||
|
},
|
||||||
|
"test.db?journal=memory": &Config{
|
||||||
|
JournalMode: JournalModeMemory,
|
||||||
|
},
|
||||||
|
"test.db?journal=off": &Config{
|
||||||
|
JournalMode: JournalModeOff,
|
||||||
|
},
|
||||||
|
"test.db?journal=wal": &Config{
|
||||||
|
JournalMode: JournalModeWAL,
|
||||||
|
Synchronous: SynchronousNormal,
|
||||||
|
},
|
||||||
|
"test.db?journal=auto": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range journalCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'journal'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.JournalMode != c.JournalMode {
|
||||||
|
t.Fatalf("Failed to parse 'journal'; expected: %s, got: %s", c.JournalMode, cfg.JournalMode)
|
||||||
|
} else {
|
||||||
|
if c.JournalMode == JournalModeWAL {
|
||||||
|
if cfg.Synchronous != c.Synchronous {
|
||||||
|
t.Fatal("Failed to auto adjust Synchronous mode to normal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locking Mode
|
||||||
|
lockingModeCases := map[string]*Config{
|
||||||
|
"test.db?lock=normal": &Config{
|
||||||
|
LockingMode: LockingModeNormal,
|
||||||
|
},
|
||||||
|
"test.db?lock=exclusive": &Config{
|
||||||
|
LockingMode: LockingModeExclusive,
|
||||||
|
},
|
||||||
|
"test.db?lock=auto": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range lockingModeCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'locking_mode'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.LockingMode != c.LockingMode {
|
||||||
|
t.Fatalf("Failed to parse 'locking_mode'; expected: %s, got: %s", c.LockingMode, cfg.LockingMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query Only
|
||||||
|
qCases := map[string]*Config{
|
||||||
|
"test.db?query_only=false": &Config{
|
||||||
|
QueryOnly: false,
|
||||||
|
},
|
||||||
|
"test.db?query_only=true": &Config{
|
||||||
|
QueryOnly: true,
|
||||||
|
},
|
||||||
|
"test.db?query_only=active": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range qCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'query_only'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.QueryOnly != c.QueryOnly {
|
||||||
|
t.Fatalf("Failed to parse 'query_only'; expected: %t, got: %t", c.QueryOnly, cfg.QueryOnly)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursive Triggers
|
||||||
|
rtCases := map[string]*Config{
|
||||||
|
"test.db?rt=false": &Config{
|
||||||
|
RecursiveTriggers: false,
|
||||||
|
},
|
||||||
|
"test.db?rt=true": &Config{
|
||||||
|
RecursiveTriggers: true,
|
||||||
|
},
|
||||||
|
"test.db?rt=active": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range rtCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'recursive_triggers'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.RecursiveTriggers != c.RecursiveTriggers {
|
||||||
|
t.Fatalf("Failed to parse 'recursive_triggers'; expected: %t, got: %t", c.RecursiveTriggers, cfg.RecursiveTriggers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secure Delete
|
||||||
|
scCases := map[string]*Config{
|
||||||
|
"test.db?secure_delete=off": &Config{
|
||||||
|
SecureDelete: SecureDeleteOff,
|
||||||
|
},
|
||||||
|
"test.db?secure_delete=on": &Config{
|
||||||
|
SecureDelete: SecureDeleteOn,
|
||||||
|
},
|
||||||
|
"test.db?secure_delete=fast": &Config{
|
||||||
|
SecureDelete: SecureDeleteFast,
|
||||||
|
},
|
||||||
|
"test.db?secure_delete=auto": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range scCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'secure_delete'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.SecureDelete != c.SecureDelete {
|
||||||
|
t.Fatalf("Failed to parse 'secure_delete'; expected: %s, got: %s", c.SecureDelete, cfg.SecureDelete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writable Schema
|
||||||
|
wsCases := map[string]*Config{
|
||||||
|
"test.db?writable_schema=false": &Config{
|
||||||
|
WriteableSchema: false,
|
||||||
|
},
|
||||||
|
"test.db?writable_schema=true": &Config{
|
||||||
|
WriteableSchema: true,
|
||||||
|
},
|
||||||
|
"test.db?writable_schema=active": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
for dsn, c := range wsCases {
|
||||||
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil && c != nil {
|
||||||
|
t.Fatal("Failed to parse 'writable_schema'")
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
if cfg.WriteableSchema != c.WriteableSchema {
|
||||||
|
t.Fatalf("Failed to parse 'writable_schema'; expected: %t, got: %t", c.WriteableSchema, cfg.WriteableSchema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatDSN(t *testing.T) {
|
||||||
|
// TODO: TestFormatDSN
|
||||||
|
cfg := NewConfig()
|
||||||
|
cfg.FormatDSN()
|
||||||
|
}
|
|
@ -45,6 +45,20 @@ type SQLiteConn struct {
|
||||||
aggregators []*aggInfo
|
aggregators []*aggInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *SQLiteConn) PRAGMA(name, value string) error {
|
||||||
|
stmt := fmt.Sprintf("PRAGMA %s = %s;", name, value)
|
||||||
|
|
||||||
|
cs := C.CString(stmt)
|
||||||
|
rv := C.sqlite3_exec(c.db, cs, nil, nil, nil)
|
||||||
|
C.free(unsafe.Pointer(cs))
|
||||||
|
|
||||||
|
if rv != C.SQLITE_OK {
|
||||||
|
return lastError(c.db)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type functionInfo struct {
|
type functionInfo struct {
|
||||||
f reflect.Value
|
f reflect.Value
|
||||||
argConverters []callbackArgConverter
|
argConverters []callbackArgConverter
|
||||||
|
|
|
@ -29,6 +29,7 @@ func (c *SQLiteConn) Ping(ctx context.Context) error {
|
||||||
if c.db == nil {
|
if c.db == nil {
|
||||||
return errors.New("Connection was closed")
|
return errors.New("Connection was closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -156,17 +156,37 @@ func TestExecCancel(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPinger(t *testing.T) {
|
func TestPinger(t *testing.T) {
|
||||||
db, err := sql.Open("sqlite3", ":memory:")
|
driverName := "sqlite3_pinger"
|
||||||
|
|
||||||
|
var dbDriverConn []*SQLiteConn
|
||||||
|
sql.Register(driverName, &SQLiteDriver{
|
||||||
|
ConnectHook: func(conn *SQLiteConn) error {
|
||||||
|
dbDriverConn = append(dbDriverConn, conn)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
db, err := sql.Open(driverName, ":memory:")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ping Database
|
||||||
err = db.Ping()
|
err = db.Ping()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
db.Close()
|
db.Close()
|
||||||
|
|
||||||
|
// Ping database
|
||||||
|
// response should be: database closed
|
||||||
err = db.Ping()
|
err = db.Ping()
|
||||||
fmt.Println(err)
|
if err == nil {
|
||||||
|
t.Fatal("Should be closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping Database through connection
|
||||||
|
err = dbDriverConn[0].Ping(context.Background())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Should be closed")
|
t.Fatal("Should be closed")
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,8 @@ func (c *Config) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
// Driver returns &SQLiteDriver{}.
|
// Driver returns &SQLiteDriver{}.
|
||||||
func (c *Config) Driver() driver.Driver {
|
func (c *Config) Driver() driver.Driver {
|
||||||
return &SQLiteDriver{
|
return &SQLiteDriver{
|
||||||
Config: c,
|
Config: c,
|
||||||
|
Extensions: c.Extensions,
|
||||||
|
ConnectHook: c.ConnectHook,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright (C) 2018 The Go-SQLite3 Authors.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by an MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build cgo
|
||||||
|
// +build go1.10
|
||||||
|
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConnectorDriver(t *testing.T) {
|
||||||
|
// Create default Config
|
||||||
|
cfg := NewConfig()
|
||||||
|
cfg.ConnectHook = func(conn *SQLiteConn) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Driver from Config
|
||||||
|
drv := cfg.Driver()
|
||||||
|
if drv.(*SQLiteDriver).ConnectHook == nil {
|
||||||
|
t.Fatal("Failed to created Driver from Config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectorConnect(t *testing.T) {
|
||||||
|
// Create default Config
|
||||||
|
cfg := NewConfig()
|
||||||
|
|
||||||
|
// Create Connection to database from Config
|
||||||
|
conn, err := cfg.Connect(context.Background())
|
||||||
|
if err != nil || conn == nil {
|
||||||
|
t.Fatal("Failed to create connection from Config")
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,6 +46,7 @@ import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Force Implementation
|
// Force Implementation
|
||||||
|
@ -63,6 +64,7 @@ var (
|
||||||
// CryptEncoder provides the interface for implementing
|
// CryptEncoder provides the interface for implementing
|
||||||
// a sqlite_crypt encoder.
|
// a sqlite_crypt encoder.
|
||||||
type CryptEncoder interface {
|
type CryptEncoder interface {
|
||||||
|
fmt.Stringer
|
||||||
Encode(pass []byte, hash interface{}) []byte
|
Encode(pass []byte, hash interface{}) []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +82,10 @@ func (e *sha1Encoder) Encode(pass []byte, hash interface{}) []byte {
|
||||||
return h[:]
|
return h[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *sha1Encoder) String() string {
|
||||||
|
return "sha1"
|
||||||
|
}
|
||||||
|
|
||||||
// NewSHA1Encoder returns a new SHA1 Encoder.
|
// NewSHA1Encoder returns a new SHA1 Encoder.
|
||||||
func NewSHA1Encoder() CryptEncoder {
|
func NewSHA1Encoder() CryptEncoder {
|
||||||
return &sha1Encoder{}
|
return &sha1Encoder{}
|
||||||
|
@ -100,6 +106,10 @@ func (e *ssha1Encoder) Salt() string {
|
||||||
return e.salt
|
return e.salt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *ssha1Encoder) String() string {
|
||||||
|
return "ssha1"
|
||||||
|
}
|
||||||
|
|
||||||
// NewSSHA1Encoder returns a new salted SHA1 Encoder.
|
// NewSSHA1Encoder returns a new salted SHA1 Encoder.
|
||||||
func NewSSHA1Encoder(salt string) CryptSaltedEncoder {
|
func NewSSHA1Encoder(salt string) CryptSaltedEncoder {
|
||||||
return &ssha1Encoder{
|
return &ssha1Encoder{
|
||||||
|
@ -114,6 +124,10 @@ func (e *sha256Encoder) Encode(pass []byte, hash interface{}) []byte {
|
||||||
return h[:]
|
return h[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *sha256Encoder) String() string {
|
||||||
|
return "sha256"
|
||||||
|
}
|
||||||
|
|
||||||
// NewSHA256Encoder returns a new SHA256 Encoder.
|
// NewSHA256Encoder returns a new SHA256 Encoder.
|
||||||
func NewSHA256Encoder() CryptEncoder {
|
func NewSHA256Encoder() CryptEncoder {
|
||||||
return &sha256Encoder{}
|
return &sha256Encoder{}
|
||||||
|
@ -134,6 +148,10 @@ func (e *ssha256Encoder) Salt() string {
|
||||||
return e.salt
|
return e.salt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *ssha256Encoder) String() string {
|
||||||
|
return "ssha256"
|
||||||
|
}
|
||||||
|
|
||||||
// NewSSHA256Encoder returns a new salted SHA256 Encoder.
|
// NewSSHA256Encoder returns a new salted SHA256 Encoder.
|
||||||
func NewSSHA256Encoder(salt string) CryptSaltedEncoder {
|
func NewSSHA256Encoder(salt string) CryptSaltedEncoder {
|
||||||
return &ssha256Encoder{
|
return &ssha256Encoder{
|
||||||
|
@ -148,6 +166,10 @@ func (e *sha384Encoder) Encode(pass []byte, hash interface{}) []byte {
|
||||||
return h[:]
|
return h[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *sha384Encoder) String() string {
|
||||||
|
return "sha384"
|
||||||
|
}
|
||||||
|
|
||||||
// NewSHA384Encoder returns a new SHA384 Encoder.
|
// NewSHA384Encoder returns a new SHA384 Encoder.
|
||||||
func NewSHA384Encoder() CryptEncoder {
|
func NewSHA384Encoder() CryptEncoder {
|
||||||
return &sha384Encoder{}
|
return &sha384Encoder{}
|
||||||
|
@ -168,6 +190,10 @@ func (e *ssha384Encoder) Salt() string {
|
||||||
return e.salt
|
return e.salt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *ssha384Encoder) String() string {
|
||||||
|
return "ssha384"
|
||||||
|
}
|
||||||
|
|
||||||
// NewSSHA384Encoder returns a new salted SHA384 Encoder.
|
// NewSSHA384Encoder returns a new salted SHA384 Encoder.
|
||||||
func NewSSHA384Encoder(salt string) CryptSaltedEncoder {
|
func NewSSHA384Encoder(salt string) CryptSaltedEncoder {
|
||||||
return &ssha384Encoder{
|
return &ssha384Encoder{
|
||||||
|
@ -182,6 +208,10 @@ func (e *sha512Encoder) Encode(pass []byte, hash interface{}) []byte {
|
||||||
return h[:]
|
return h[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *sha512Encoder) String() string {
|
||||||
|
return "sha512"
|
||||||
|
}
|
||||||
|
|
||||||
// NewSHA512Encoder returns a new SHA512 Encoder.
|
// NewSHA512Encoder returns a new SHA512 Encoder.
|
||||||
func NewSHA512Encoder() CryptEncoder {
|
func NewSHA512Encoder() CryptEncoder {
|
||||||
return &sha512Encoder{}
|
return &sha512Encoder{}
|
||||||
|
@ -202,7 +232,11 @@ func (e *ssha512Encoder) Salt() string {
|
||||||
return e.salt
|
return e.salt
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSSHA384Encoder returns a new salted SHA512 Encoder.
|
func (e *ssha512Encoder) String() string {
|
||||||
|
return "ssha512"
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSSHA512Encoder returns a new salted SHA512 Encoder.
|
||||||
func NewSSHA512Encoder(salt string) CryptSaltedEncoder {
|
func NewSSHA512Encoder(salt string) CryptSaltedEncoder {
|
||||||
return &ssha512Encoder{
|
return &ssha512Encoder{
|
||||||
salt: salt,
|
salt: salt,
|
||||||
|
|
|
@ -28,39 +28,11 @@ package sqlite3
|
||||||
#else
|
#else
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
#endif
|
#endif
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#ifdef __CYGWIN__
|
|
||||||
# include <errno.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef SQLITE_OPEN_READWRITE
|
|
||||||
# define SQLITE_OPEN_READWRITE 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef SQLITE_OPEN_FULLMUTEX
|
|
||||||
# define SQLITE_OPEN_FULLMUTEX 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef SQLITE_DETERMINISTIC
|
|
||||||
# define SQLITE_DETERMINISTIC 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int
|
|
||||||
_sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs) {
|
|
||||||
#ifdef SQLITE_OPEN_URI
|
|
||||||
return sqlite3_open_v2(filename, ppDb, flags | SQLITE_OPEN_URI, zVfs);
|
|
||||||
#else
|
|
||||||
return sqlite3_open_v2(filename, ppDb, flags, zVfs);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -80,14 +52,19 @@ type SQLiteDriver struct {
|
||||||
|
|
||||||
// Open database and return a new connection.
|
// Open database and return a new connection.
|
||||||
func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
||||||
if C.sqlite3_threadsafe() == 0 {
|
|
||||||
return nil, errors.New("sqlite library was not compiled for thread-safe operation")
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := ParseDSN(dsn)
|
cfg, err := ParseDSN(dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configure Extensions
|
||||||
|
cfg.Extensions = d.Extensions
|
||||||
|
|
||||||
|
// Configure ConnectHook
|
||||||
|
cfg.ConnectHook = d.ConnectHook
|
||||||
|
|
||||||
|
// Set Configuration
|
||||||
|
d.Config = cfg
|
||||||
|
|
||||||
return cfg.createConnection()
|
return cfg.createConnection()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
-tags=sqlite_userauth
|
||||||
|
|
||||||
|
-cover
|
|
@ -8,7 +8,9 @@
|
||||||
|
|
||||||
package sqlite3
|
package sqlite3
|
||||||
|
|
||||||
import "database/sql/driver"
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ driver.DriverContext = (*SQLiteDriver)(nil)
|
_ driver.DriverContext = (*SQLiteDriver)(nil)
|
||||||
|
@ -20,5 +22,19 @@ var (
|
||||||
// The two-step sequence allows drivers to parse the name just once and also provides
|
// The two-step sequence allows drivers to parse the name just once and also provides
|
||||||
// access to per-Conn contexts.
|
// access to per-Conn contexts.
|
||||||
func (d *SQLiteDriver) OpenConnector(dsn string) (driver.Connector, error) {
|
func (d *SQLiteDriver) OpenConnector(dsn string) (driver.Connector, error) {
|
||||||
return ParseDSN(dsn)
|
cfg, err := ParseDSN(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure Extensions
|
||||||
|
cfg.Extensions = d.Extensions
|
||||||
|
|
||||||
|
// Configure ConnectHook
|
||||||
|
cfg.ConnectHook = d.ConnectHook
|
||||||
|
|
||||||
|
// Set Configuration
|
||||||
|
d.Config = cfg
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -21,9 +21,6 @@ package sqlite3
|
||||||
#endif
|
#endif
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrNo inherit errno.
|
// ErrNo inherit errno.
|
||||||
type ErrNo int
|
type ErrNo int
|
||||||
|
@ -165,7 +162,6 @@ func lastError(db *C.sqlite3) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorString(err Error) string {
|
func errorString(err Error) string {
|
||||||
fmt.Println("errorString")
|
|
||||||
return C.GoString(C.sqlite3_errstr(C.int(err.Code)))
|
return C.GoString(C.sqlite3_errstr(C.int(err.Code)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,6 @@ func TestStat4(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if exists != 1 {
|
if exists != 1 {
|
||||||
t.Fatal("Failed to enabled STAT4")
|
t.Fatal("Failed to enable STAT4")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ func init() {
|
||||||
file = TempFilename(t)
|
file = TempFilename(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err = sql.Open("sqlite3_with_conn", "file:"+file+fmt.Sprintf("?_auth&_auth_user=%s&_auth_pass=%s", username, password))
|
db, err = sql.Open("sqlite3_with_conn", "file:"+file+fmt.Sprintf("?user=%s&pass=%s", username, password))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
return file, nil, nil, err
|
return file, nil, nil, err
|
||||||
|
@ -87,7 +87,7 @@ func init() {
|
||||||
file = TempFilename(t)
|
file = TempFilename(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err = sql.Open("sqlite3_with_conn", "file:"+file+fmt.Sprintf("?_auth&_auth_user=%s&_auth_pass=%s&_auth_crypt=%s&_auth_salt=%s", username, password, crypt, salt))
|
db, err = sql.Open("sqlite3_with_conn", "file:"+file+fmt.Sprintf("?user=%s&pass=%s&crypt=%s&salt=%s", username, password, crypt, salt))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
return file, nil, nil, err
|
return file, nil, nil, err
|
||||||
|
@ -270,7 +270,7 @@ func TestUserAuthAddAdmin(t *testing.T) {
|
||||||
|
|
||||||
func TestUserAuthAddUser(t *testing.T) {
|
func TestUserAuthAddUser(t *testing.T) {
|
||||||
f1, db1, c, err := connect(t, "", "admin", "admin")
|
f1, db1, c, err := connect(t, "", "admin", "admin")
|
||||||
if err != nil && c == nil && db == nil {
|
if err != nil && c == nil && db1 == nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.Remove(f1)
|
defer os.Remove(f1)
|
||||||
|
@ -365,7 +365,7 @@ func TestUserAuthAddUser(t *testing.T) {
|
||||||
|
|
||||||
func TestUserAuthModifyUser(t *testing.T) {
|
func TestUserAuthModifyUser(t *testing.T) {
|
||||||
f1, db1, c1, err := connect(t, "", "admin", "admin")
|
f1, db1, c1, err := connect(t, "", "admin", "admin")
|
||||||
if err != nil && c1 == nil && db == nil {
|
if err != nil && c1 == nil && db1 == nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.Remove(f1)
|
defer os.Remove(f1)
|
||||||
|
@ -464,7 +464,7 @@ func TestUserAuthModifyUser(t *testing.T) {
|
||||||
|
|
||||||
func TestUserAuthDeleteUser(t *testing.T) {
|
func TestUserAuthDeleteUser(t *testing.T) {
|
||||||
f1, db1, c, err := connect(t, "", "admin", "admin")
|
f1, db1, c, err := connect(t, "", "admin", "admin")
|
||||||
if err != nil && c == nil && db == nil {
|
if err != nil && c == nil && db1 == nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.Remove(f1)
|
defer os.Remove(f1)
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (C) 2018 The Go-SQLite3 Authors.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by an MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build cgo
|
||||||
|
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
const (
|
||||||
|
PRAGMA_AUTO_VACUUM = "auto_vacuum"
|
||||||
|
PRAGMA_CASE_SENSITIVE_LIKE = "case_sensitive_like"
|
||||||
|
PRAGMA_DEFER_FOREIGN_KEYS = "defer_foreign_keys"
|
||||||
|
PRAGMA_FOREIGN_KEYS = "foreign_keys"
|
||||||
|
PRAGMA_IGNORE_CHECK_CONTRAINTS = "ignore_check_constraints"
|
||||||
|
PRAGMA_JOURNAL_MODE = "journal_mode"
|
||||||
|
PRAGMA_LOCKING_MODE = "locking_mode"
|
||||||
|
PRAGMA_QUERY_ONLY = "query_only"
|
||||||
|
PRAGMA_RECURSIVE_TRIGGERS = "recursive_triggers"
|
||||||
|
PRAGMA_SECURE_DELETE = "secure_delete"
|
||||||
|
PRAGMA_SYNCHRONOUS = "synchronous"
|
||||||
|
PRAGMA_WRITABLE_SCHEMA = "writable_schema"
|
||||||
|
)
|
|
@ -4,6 +4,7 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build cgo
|
// +build cgo
|
||||||
|
// +build sqlite_vtable
|
||||||
|
|
||||||
package sqlite3
|
package sqlite3
|
||||||
|
|
Loading…
Reference in New Issue