2018-06-23 10:42:05 +03:00
// 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
/ *
# ifndef USE_LIBSQLITE3
# include < sqlite3 - binding . h >
# else
# include < sqlite3 . h >
# endif
2018-06-24 23:57:57 +03:00
# 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
}
2018-06-23 10:42:05 +03:00
* /
import "C"
import (
2018-06-24 23:57:57 +03:00
"bytes"
2018-06-23 10:42:05 +03:00
"database/sql/driver"
2018-06-24 23:57:57 +03:00
"errors"
2018-06-23 10:42:05 +03:00
"fmt"
"net/url"
2018-06-24 23:57:57 +03:00
"runtime"
2018-06-23 10:42:05 +03:00
"strconv"
"strings"
"time"
2018-06-24 23:57:57 +03:00
"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 (
// 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 )
2018-06-23 10:42:05 +03:00
)
2018-06-24 23:57:57 +03:00
// 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 )
}
2018-06-23 10:42:05 +03:00
const (
2018-06-24 23:57:57 +03:00
// MutexNo will force the database connection opens
2018-06-23 10:42:05 +03:00
// in the multi-thread threading mode as long as the
// single-thread mode has not been set at compile-time or start-time.
2018-06-24 23:57:57 +03:00
MutexNo = Mutex ( C . SQLITE_OPEN_NOMUTEX )
2018-06-23 10:42:05 +03:00
2018-06-24 23:57:57 +03:00
// MutexFull will force the database connection opens
2018-06-23 10:42:05 +03:00
// in the serialized threading mode unless single-thread
// was previously selected at compile-time or start-time.
2018-06-24 23:57:57 +03:00
MutexFull = Mutex ( C . SQLITE_OPEN_FULLMUTEX )
2018-06-23 10:42:05 +03:00
)
// TxLock defines the Transaction Lock Behaviour.
type TxLock string
func ( tx TxLock ) String ( ) string {
2018-06-24 23:57:57 +03:00
switch tx {
case TxLockDeferred :
return "deferred"
case TxLockImmediate :
return "immediate"
case TxLockExclusive :
return "exclusive"
default :
return ""
}
}
func ( tx TxLock ) Value ( ) string {
2018-06-23 10:42:05 +03:00
return string ( tx )
}
const (
// TxLockDeferred deferred transaction behaviour. (Default)
// Deferred means that no locks are acquired on the database
// until the database is first accessed.
// Thus with a deferred transaction,
// the BEGIN statement itself does nothing to the filesystem.
// Locks are not acquired until the first read or write operation.
// The first read operation against a database creates a SHARED lock
// and the first write operation creates a RESERVED lock.
// Because the acquisition of locks is deferred until they are needed,
// it is possible that another thread or process could create a separate transaction
// and write to the database after the BEGIN on the current thread has executed.
TxLockDeferred = TxLock ( "BEGIN" )
// TxLockImmediate immediate transaction behaviour.
// If the transaction is immediate,
// then RESERVED locks are acquired on all databases
// as soon as the BEGIN command is executed,
// without waiting for the database to be used.
// After a BEGIN IMMEDIATE, no other database connection
// will be able to write to the database or do a BEGIN IMMEDIATE or BEGIN EXCLUSIVE.
// Other processes can continue to read from the database however.
TxLockImmediate = TxLock ( "BEGIN IMMEDIATE" )
// TxLockExclusive exclusive transaction behaviour.
// An exclusive transaction causes EXCLUSIVE locks to be acquired on all databases.
// After a BEGIN EXCLUSIVE, no other database connection
// except for read_uncommitted connections will be able to read the database
// and no other connection without exception will be able to write the database
// until the transaction is complete.
TxLockExclusive = TxLock ( "BEGIN EXCLUSIVE" )
)
// LockingMode defines the database locking mode.
// In NORMAL locking-mode (the default unless overridden at compile-time using SQLITE_DEFAULT_LOCKING_MODE),
// a database connection unlocks the database file at the conclusion of each read or write transaction.
// When the locking-mode is set to EXCLUSIVE,
// the database connection never releases file-locks.
// The first time the database is read in EXCLUSIVE mode,
// a shared lock is obtained and held.
// The first time the database is written, an exclusive lock is obtained and held.
//
// Database locks obtained by a connection in EXCLUSIVE mode may be released
// either by closing the database connection,
// or by setting the locking-mode back to NORMAL.
// Simply setting the locking-mode to NORMAL is not enough -
// locks are not released until the next time the database file is accessed.
//
// There are three reasons to set the locking-mode to EXCLUSIVE.
//
// The application wants to prevent other processes from accessing the database file.
// The number of system calls for filesystem operations is reduced,
// possibly resulting in a small performance increase.
// WAL databases can be accessed in EXCLUSIVE mode without the use of shared memory.
// (Additional information)
// When the locking_mode pragma specifies a particular database, for example:
//
// PRAGMA main.locking_mode=EXCLUSIVE;
// Then the locking mode applies only to the named database.
// If no database name qualifier precedes the "locking_mode" keyword
// then the locking mode is applied to all databases,
// including any new databases added by subsequent ATTACH commands.
//
// The "temp" database (in which TEMP tables and indices are stored)
// and in-memory databases always uses exclusive locking mode.
// The locking mode of temp and in-memory databases cannot be changed.
// All other databases use the normal locking mode by default and are affected by this pragma.
//
// If the locking mode is EXCLUSIVE when first entering WAL journal mode,
// then the locking mode cannot be changed to NORMAL until after exiting 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
// and back again at any time and without needing to exit WAL journal mode.
2018-06-24 23:57:57 +03:00
type LockingMode string
func ( l LockingMode ) String ( ) string {
return strings . ToLower ( string ( l ) )
}
2018-06-23 10:42:05 +03:00
const (
// LockingModeNormal In NORMAL locking-mode
// (the default unless overridden at compile-time using SQLITE_DEFAULT_LOCKING_MODE),
// a database connection unlocks the database file at the conclusion
// of each read or write transaction.
2018-06-24 23:57:57 +03:00
LockingModeNormal = LockingMode ( "NORMAL" )
2018-06-23 10:42:05 +03:00
// LockingModeExclusive When the locking-mode is set to EXCLUSIVE,
// the database connection never releases file-locks.
// The first time the database is read in EXCLUSIVE mode,
// a shared lock is obtained and held.
// The first time the database is written, an exclusive lock is obtained and held.
2018-06-24 23:57:57 +03:00
LockingModeExclusive = LockingMode ( "EXCLUSIVE" )
2018-06-23 10:42:05 +03:00
)
// AutoVacuum defines the auto vacuum status of the database.
// The default setting for auto-vacuum is 0 or "none", unless the SQLITE_DEFAULT_AUTOVACUUM compile-time option is used.
// SQLITE_DEFAULT_AUTOVACUUM can be controlled within the package using
// build tags. See README for more information.
//
// Auto-vacuuming is only possible if the database stores some additional information
// that allows each database page to be traced backwards to its referrer.
// 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.
2018-06-24 23:57:57 +03:00
type AutoVacuum string
func ( av AutoVacuum ) String ( ) string {
return strings . ToLower ( string ( av ) )
}
2018-06-23 10:42:05 +03:00
const (
// AutoVacuumNone setting means that auto-vacuum is disabled.
//
// When auto-vacuum is disabled and data is deleted data from a database,
// the database file remains the same size.
// Unused database file pages are added to a "freelist" and reused for subsequent inserts.
// So no database file space is lost.
// However, the database file does not shrink.
// In this mode the VACUUM command can be used to rebuild the entire database file
// and thus reclaim unused disk space.
//
// The database connection can be changed between full
// and incremental autovacuum mode at any time.
// However, changing from "none" to "full" or "incremental"
// can only occur when the database is new (no tables have yet been created)
// or by running the VACUUM command. To change auto-vacuum modes,
// first use the auto_vacuum pragma to set the new desired mode,
// then invoke the VACUUM command to reorganize the entire database file.
// To change from "full" or "incremental" back to "none"
// always requires running VACUUM even on an empty database.
2018-06-24 23:57:57 +03:00
AutoVacuumNone = AutoVacuum ( "NONE" )
2018-06-23 10:42:05 +03:00
// AutoVacuumFull sets auto vacuum of the database to FULL.
//
// When the auto-vacuum mode is 1 or "full",
// the freelist pages are moved to the end of the database file and the database file
// is truncated to remove the freelist pages at every transaction commit.
// Note, however, that auto-vacuum only truncates the freelist pages from the file.
// Auto-vacuum does not defragment the database nor repack individual database pages
// the way that the VACUUM command does.
// In fact, because it moves pages around within the file,
// auto-vacuum can actually make fragmentation worse.
2018-06-24 23:57:57 +03:00
AutoVacuumFull = AutoVacuum ( "FULL" )
2018-06-23 10:42:05 +03:00
// AutoVacuumIncremental sets the auto vacuum of the database to INCREMENTAL.
//
// When the value of auto-vacuum is 2 or "incremental"
// then the additional information needed to do auto-vacuuming is stored
// in the database file but auto-vacuuming does not occur automatically
// at each commit as it does with auto_vacuum=full.
// In incremental mode, the separate incremental_vacuum pragma must be invoked
// to cause the auto-vacuum to occur.
2018-06-24 23:57:57 +03:00
AutoVacuumIncremental = AutoVacuum ( "INCREMENTAL" )
2018-06-23 10:42:05 +03:00
)
// JournalMode defines the journal mode associated with the current database connection.
//
// Note that the journal_mode for an in-memory database is either
// MEMORY or OFF and can not be changed to a different value.
// An attempt to change the journal_mode of an in-memory database
// to any setting other than MEMORY or OFF is ignored.
// Note also that the journal_mode cannot be changed while a transaction is active.
type JournalMode string
2018-06-24 23:57:57 +03:00
func ( j JournalMode ) String ( ) string {
return strings . ToLower ( string ( j ) )
}
2018-06-23 10:42:05 +03:00
const (
// JournalModeDelete is the normal behavior.
// In the DELETE mode, the rollback journal is deleted at the conclusion
// of each transaction.
// Indeed, the delete operation is the action that causes the transaction to commit.
// (See the document titled Atomic Commit In SQLite for additional detail.)
2018-06-24 23:57:57 +03:00
JournalModeDelete = JournalMode ( "DELETE" )
2018-06-23 10:42:05 +03:00
// JournalModeTruncate commits transactions by truncating the rollback journal
// to zero-length instead of deleting it.
// On many systems, truncating a file is much faster
// than deleting the file since the containing directory does not need to be changed.
2018-06-24 23:57:57 +03:00
JournalModeTruncate = JournalMode ( "TRUNCATE" )
2018-06-23 10:42:05 +03:00
// JournalModePersist prevents the rollback journal from being deleted
// at the end of each transaction.
// Instead, the header of the journal is overwritten with zeros.
// This will prevent other database connections from rolling the journal back.
// The PERSIST journaling mode is useful as an optimization on platforms
// where deleting or truncating a file is much more expensive
// than overwriting the first block of a file with zeros.
// See also: PRAGMA journal_size_limit and SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT.
2018-06-24 23:57:57 +03:00
JournalModePersist = JournalMode ( "PERSIST" )
2018-06-23 10:42:05 +03:00
// JournalModeMemory stores the rollback journal in volatile RAM.
// 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
// when the MEMORY journaling mode is set,
// then the database file will very likely go corrupt.
2018-06-24 23:57:57 +03:00
JournalModeMemory = JournalMode ( "MEMORY" )
2018-06-23 10:42:05 +03:00
// JournalModeWAL uses a write-ahead log instead of a rollback journal
// to implement transactions.
// The WAL journaling mode is persistent;
// after being set it stays in effect across multiple database connections
// and after closing and reopening the database.
2018-06-24 23:57:57 +03:00
JournalModeWAL = JournalMode ( "WAL" )
2018-06-23 10:42:05 +03:00
2018-06-24 23:57:57 +03:00
// JournalModeOff disables the rollback journal completely.
2018-06-23 10:42:05 +03:00
// 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.
2018-06-24 23:57:57 +03:00
JournalModeOff = JournalMode ( "OFF" )
2018-06-23 10:42:05 +03:00
)
// SecureDelete defines the secure-delete setting.
//
// When secure_delete is on, SQLite overwrites deleted content with zeros.
// The default setting for secure_delete is determined by the SQLITE_SECURE_DELETE
// compile-time option and is normally off.
// The off setting for secure_delete improves performance by reducing
// the number of CPU cycles and the amount of disk I/O.
// Applications that wish to avoid leaving forensic traces after content is deleted
// or updated should enable the secure_delete pragma prior to performing the delete or update,
// or else run VACUUM after the delete or update.
type SecureDelete string
2018-06-24 23:57:57 +03:00
func ( sd SecureDelete ) String ( ) string {
return strings . ToLower ( string ( sd ) )
}
2018-06-23 10:42:05 +03:00
const (
// SecureDeleteOff disables secure deletion of content.
SecureDeleteOff = SecureDelete ( "OFF" )
// SecureDeleteOn will cause SQLite overwrites deleted content with zeros.
SecureDeleteOn = SecureDelete ( "ON" )
// SecureDeleteFast defines the "fast" setting for secure_delete (added circa 2017-08-01) is an intermediate setting
// in between "on" and "off". When secure_delete is set to "fast",
// SQLite will overwrite deleted content with zeros only if doing so
// does not increase the amount of I/O. In other words,
// the "fast" setting uses more CPU cycles but does not use more I/O.
// This has the effect of purging all old content from b-tree pages,
// but leaving forensic traces on freelist pages.
SecureDeleteFast = SecureDelete ( "FAST" )
)
// Synchronous sync setting of the database connection.
2018-06-24 23:57:57 +03:00
type Synchronous string
func ( s Synchronous ) String ( ) string {
return strings . ToLower ( string ( s ) )
}
2018-06-23 10:42:05 +03:00
const (
// SynchronousOff sets synchronous to OFF (0),
// SQLite continues without syncing as soon as it has handed data off to the operating system.
// If the application running SQLite crashes, the data will be safe,
// 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.
// On the other hand, commits can be orders of magnitude faster with synchronous OFF.
2018-06-24 23:57:57 +03:00
SynchronousOff = Synchronous ( "OFF" )
2018-06-23 10:42:05 +03:00
// SynchronousNormal sets synchronous to NORMAL (1),
// the SQLite database engine will still sync at the most critical moments,
// but less often than in FULL mode.
// There is a very small (though non-zero) chance that a power failure
// at just the wrong time could corrupt the database in journal_mode=DELETE
// on an older filesystem. WAL mode is safe from corruption with synchronous=NORMAL,
// and probably DELETE mode is safe too on modern filesystems.
// WAL mode is always consistent with synchronous=NORMAL,
// but WAL mode does lose durability.
// A transaction committed in WAL mode with synchronous=NORMAL
// might roll back following a power loss or system crash.
// Transactions are durable across application crashes regardless
// of the synchronous setting or journal mode.
// The synchronous=NORMAL setting is a good choice for most applications running in WAL mode.
2018-06-24 23:57:57 +03:00
SynchronousNormal = Synchronous ( "NORMAL" )
2018-06-23 10:42:05 +03:00
// SynchronousFull sets synchronous to FULL (2),
// the SQLite database engine will use the xSync method of the VFS
// to ensure that all content is safely written to the disk surface prior to continuing.
// This ensures that an operating system crash or power failure
// will not corrupt the database. FULL synchronous is very safe,
// but it is also slower.
///FULL is the most commonly used synchronous setting when not in WAL mode.
2018-06-24 23:57:57 +03:00
SynchronousFull = Synchronous ( "FULL" )
2018-06-23 10:42:05 +03:00
// 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
// in DELETE mode. EXTRA provides additional durability if the commit
// is followed closely by a power loss.
2018-06-24 23:57:57 +03:00
SynchronousExtra = Synchronous ( "EXTRA" )
2018-06-23 10:42:05 +03:00
)
// Config is configuration parsed from a DSN string.
// If a new Config is created instead of being parsed from a DSN string,
// the NewConfig function should be used, which sets default values.
// Manual usage is allowed
type Config struct {
2018-06-24 23:57:57 +03:00
// Database
Database string
2018-06-23 10:42:05 +03:00
// Mode of the SQLite database
Mode Mode
// CacheMode of the SQLite Connection
Cache CacheMode
2018-06-24 23:57:57 +03:00
// Mutex flag SQLITE_OPEN_MUTEX_NO, SQLITE_OPEN_MUTEX_FULL
// Defaults to SQLITE_OPEN_MUTEX_FULL
Mutex Mutex
2018-06-23 10:42:05 +03:00
// The immutable parameter is a boolean query parameter that indicates
// that the database file is stored on read-only media. When immutable is set,
// SQLite assumes that the database file cannot be changed,
// even by a process with higher privilege,
// and so the database is opened read-only and all locking and change detection is disabled.
// Caution: Setting the immutable property on a database file that
// does in fact change can result in incorrect query results and/or SQLITE_CORRUPT errors.
Immutable bool
// TimeZone location
TimeZone * time . Location
// TransactionLock behaviour
TransactionLock TxLock
// LockingMode behaviour
LockingMode LockingMode
// Authentication holds the UserAuth configuration
Authentication * Auth
// AutoVacuum sets the auto vacuum status of the database
// Defaults to NONE
AutoVacuum AutoVacuum
// BusyTimeout defines the time a connection will wait when the
// connection is BUSY and locked by an other connection.
// BusyTimeout is defined in milliseconds
BusyTimeout time . Duration
// CaseSensitiveLike controls the behaviour of the LIKE operator.
// Default or disabled the LIKE operation is case-insensitive.
// When enabling this options behaviour of LIKE will become case-sensitive.
CaseSensitiveLike bool
// DeferForeignKeys when enabled will cause the enforcement
// of all foreign key constraints is delayed until
// the outermost transaction is committed.
// The defer_foreign_keys pragma defaults to false
// so that foreign key constraints are only deferred
// if they are created as "DEFERRABLE INITIALLY DEFERRED".
// The defer_foreign_keys pragma is automatically switched off at each COMMIT or ROLLBACK.
// Hence, the defer_foreign_keys pragma must be separately enabled for each transaction.
// This pragma is only meaningful if foreign key constraints are enabled, of course.
DeferForeignKeys bool
// ForeignKeyConstraints enable or disable the enforcement of foreign key constraints.
ForeignKeyConstraints bool
// IgnoreCheckConstraints enables or disables the enforcement of CHECK constraints.
// The default setting is off, meaning that CHECK constraints are enforced by default.
IgnoreCheckConstraints bool
// JournalMode sets the journal mode for databases associated with the current database connection.
JournalMode JournalMode
// QueryOnly prevents all changes to the database when set to true.
QueryOnly bool
// RecursiveTriggers enable or disable the recursive trigger capability.
RecursiveTriggers bool
// SecureDelete enables or disables or sets the secure deletion within the database.
SecureDelete SecureDelete
// Synchronous Mode of the database
Synchronous Synchronous
// WriteableSchema enables of disables the ability to using UPDATE, INSERT, DELETE
// Warning: misuse of this pragma can easily result in a corrupt database file.
WriteableSchema bool
2018-06-24 23:57:57 +03:00
// Extensions
Extensions [ ] string
// ConnectHook
ConnectHook func ( * SQLiteConn ) error
2018-06-23 10:42:05 +03:00
}
// Auth holds the authentication configuration for the SQLite UserAuth module.
type Auth struct {
// Username for authentication
Username string
// Password for authentication
Password string
// Salt for encryption
Salt string
// CryptEncoder used for the password encryption
Encoder CryptEncoder
}
// NewConfig creates a new Config and sets default values.
func NewConfig ( ) * Config {
return & Config {
2018-06-24 23:57:57 +03:00
// 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:" ,
2018-06-23 10:42:05 +03:00
Cache : CacheModePrivate ,
Immutable : false ,
2018-06-24 23:57:57 +03:00
Mutex : MutexFull ,
2018-06-23 10:42:05 +03:00
TransactionLock : TxLockDeferred ,
LockingMode : LockingModeNormal ,
AutoVacuum : AutoVacuumNone ,
CaseSensitiveLike : false ,
DeferForeignKeys : false ,
ForeignKeyConstraints : false ,
IgnoreCheckConstraints : false ,
JournalMode : JournalModeDelete ,
QueryOnly : false ,
RecursiveTriggers : false ,
SecureDelete : SecureDeleteOff ,
Synchronous : SynchronousNormal ,
WriteableSchema : false ,
2018-06-24 23:57:57 +03:00
Authentication : & Auth {
Encoder : NewSHA1Encoder ( ) ,
} ,
2018-06-23 10:42:05 +03:00
}
}
// FormatDSN formats the given Config into a DSN string which can be passed to
// the driver.
func ( cfg * Config ) FormatDSN ( ) string {
2018-06-24 23:57:57 +03:00
var buf bytes . Buffer
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 ( )
2018-06-23 10:42:05 +03:00
}
// Create connection from Configuration
func ( cfg * Config ) createConnection ( ) ( driver . Conn , error ) {
2018-06-24 23:57:57 +03:00
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" )
}
var db * C . sqlite3
// Configure Database URI
// Because we are adding the 'immutable' flag to the database file
// We are required to conform the database path to an URI
// 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 )
// Free CString on return
defer C . free ( unsafe . Pointer ( database ) )
// Open the database
// https://www.sqlite.org/c3ref/open.html
rv := C . _sqlite3_open_v2 (
database ,
& db ,
cfg . Mutex . C ( ) | cfg . Cache . C ( ) | cfg . Mode . C ( ) ,
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
2018-06-23 10:42:05 +03:00
}
// ParseDSN parses the DSN string to a Config
func ParseDSN ( dsn string ) ( cfg * Config , err error ) {
// New default with default values
cfg = NewConfig ( )
2018-06-24 23:57:57 +03:00
cfg . Database = dsn
2018-06-23 10:42:05 +03:00
pos := strings . IndexRune ( dsn , '?' )
if pos >= 1 {
2018-06-24 23:57:57 +03:00
// Update DatabaseURI
cfg . Database = dsn [ 0 : pos ]
2018-06-23 10:42:05 +03:00
// Parse Options
params , err := url . ParseQuery ( dsn [ pos + 1 : ] )
if err != nil {
return nil , err
}
// Normalize Params
normalizeParams ( params )
if ! strings . HasPrefix ( dsn , "file:" ) {
dsn = dsn [ : pos ]
}
// Parse Autentication
if val := params . Get ( "user" ) ; val != "" {
cfg . Authentication . Username = val
}
if val := params . Get ( "pass" ) ; val != "" {
cfg . Authentication . Password = val
}
if val := params . Get ( "salt" ) ; val != "" {
cfg . Authentication . Salt = val
}
if val := params . Get ( "crypt" ) ; val != "" {
if cfg . Authentication . Encoder , err = parseCryptEncoder ( val , cfg . Authentication . Salt ) ; err != nil {
return nil , err
}
}
// Parse Multi name options
// Multi name options are options which has multiple aliases for the same option
for k := range params {
// Cache
if k == "cache" {
val := params . Get ( k )
switch strings . ToLower ( val ) {
case "shared" :
cfg . Cache = CacheModeShared
case "private" :
cfg . Cache = CacheModePrivate
default :
return nil , fmt . Errorf ( "Unknown cache mode: %v, expecting value of 'shared, private'" , val )
}
}
// Immutable
if k == "immutable" {
val := params . Get ( k )
switch strings . ToLower ( val ) {
case "0" , "no" , "false" , "off" :
cfg . Immutable = false
case "1" , "yes" , "true" , "on" :
cfg . Immutable = true
default :
return nil , fmt . Errorf ( "Unknown immutable: %v, expecting boolean value of '0 1 false true no yes off on'" , val )
}
}
// Mode
if k == "mode" {
val := params . Get ( k )
switch strings . ToUpper ( val ) {
2018-06-24 23:57:57 +03:00
case "RO" :
cfg . Mode = ModeReadOnly
case "RW" :
cfg . Mode = ModeReadWrite
case "RWC" :
cfg . Mode = ModeReadWriteCreate
case "MEMORY" :
cfg . Mode = ModeMemory
2018-06-23 10:42:05 +03:00
default :
return nil , fmt . Errorf ( "Unknown mode: %v, expecting value of 'ro, rw, rwc, memory'" , val )
}
}
2018-06-24 23:57:57 +03:00
// 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 )
}
}
2018-06-23 10:42:05 +03:00
// Timezone
if k == "tz" || k == "timezone" || k == "loc" {
val := params . Get ( k )
switch strings . ToLower ( val ) {
case "auto" :
cfg . TimeZone = time . Local
default :
cfg . TimeZone , err = time . LoadLocation ( val )
if err != nil {
return nil , fmt . Errorf ( "Invalid tz: %v: %v" , val , err )
}
}
}
// Transaction Lock
if k == "txlock" || k == "transaction_lock" {
val := params . Get ( k )
switch strings . ToLower ( val ) {
case "immediate" :
cfg . TransactionLock = TxLockImmediate
case "exclusive" :
cfg . TransactionLock = TxLockExclusive
case "deferred" :
cfg . TransactionLock = TxLockDeferred
default :
return nil , fmt . Errorf ( "Invalid txlock: %v, expecting value of 'deferred, immediate, exclusive'" , val )
}
}
// AutoVacuum
if k == "auto_vacuum" || k == "vacuum" {
val := params . Get ( k )
switch strings . ToLower ( val ) {
case "0" , "none" :
cfg . AutoVacuum = AutoVacuumNone
case "1" , "full" :
cfg . AutoVacuum = AutoVacuumFull
case "2" , "incremental" :
cfg . AutoVacuum = AutoVacuumIncremental
default :
return nil , fmt . Errorf ( "Invalid auto_vacuum: %v, expecting value of '0 NONE 1 FULL 2 INCREMENTAL'" , val )
}
}
// Busy Timeout
if k == "busy_timeout" || k == "timeout" {
val := params . Get ( k )
iv , err := strconv . ParseInt ( val , 10 , 64 )
if err != nil {
return nil , fmt . Errorf ( "Invalid busy_timeout: %v: %v" , val , err )
}
cfg . BusyTimeout , _ = time . ParseDuration ( fmt . Sprintf ( "%dms" , iv ) )
}
// Case Sensitive LIKE
if k == "case_sensitive_like" || k == "cslike" {
val := params . Get ( k )
switch strings . ToLower ( val ) {
case "0" , "no" , "false" , "off" :
cfg . CaseSensitiveLike = false
case "1" , "yes" , "true" , "on" :
cfg . CaseSensitiveLike = true
default :
return nil , fmt . Errorf ( "Invalid case_sensitive_like: %v, expecting boolean value of '0 1 false true no yes off on'" , val )
}
}
// Defer Foreign Keys
if k == "defer_foreign_keys" || k == "defer_fk" {
val := params . Get ( k )
switch strings . ToLower ( val ) {
case "0" , "no" , "false" , "off" :
cfg . DeferForeignKeys = false
case "1" , "yes" , "true" , "on" :
cfg . DeferForeignKeys = true
default :
return nil , fmt . Errorf ( "Invalid defer_foreign_keys: %v, expecting boolean value of '0 1 false true no yes off on'" , val )
}
}
// Foreign Keys
if k == "foreign_keys" || k == "fk" {
val := params . Get ( k )
switch strings . ToLower ( val ) {
case "0" , "no" , "false" , "off" :
cfg . ForeignKeyConstraints = false
case "1" , "yes" , "true" , "on" :
cfg . ForeignKeyConstraints = true
default :
return nil , fmt . Errorf ( "Invalid foreign_keys: %v, expecting boolean value of '0 1 false true no yes off on'" , val )
}
}
// Ignore Check Constraints
if k == "ignore_check_constraints" {
val := params . Get ( k )
switch strings . ToLower ( val ) {
case "0" , "no" , "false" , "off" :
cfg . IgnoreCheckConstraints = false
case "1" , "yes" , "true" , "on" :
cfg . IgnoreCheckConstraints = true
default :
return nil , fmt . Errorf ( "Invalid ignore_check_constraints: %v, expecting boolean value of '0 1 false true no yes off on'" , val )
}
}
// Parse Synchronous before Journal Mode
// Because if WAL mode is selected for Journal
// This will change the Synchronous mode
if k == "synchronous" || k == "sync" {
val := params . Get ( k )
switch strings . ToUpper ( val ) {
case "0" , "OFF" :
cfg . Synchronous = SynchronousOff
case "1" , "NORMAL" :
cfg . Synchronous = SynchronousNormal
case "2" , "FULL" :
cfg . Synchronous = SynchronousFull
case "3" , "EXTRA" :
cfg . Synchronous = SynchronousExtra
default :
return nil , fmt . Errorf ( "Invalid synchronous: %v, expecting value of '0 OFF 1 NORMAL 2 FULL 3 EXTRA'" , val )
}
}
// Journal Mode
if k == "journal_mode" || k == "journal" {
val := params . Get ( k )
switch strings . ToUpper ( val ) {
case "DELETE" , "TRUNCATE" , "PERSIST" , "MEMORY" , "OFF" :
cfg . JournalMode = JournalMode ( strings . ToUpper ( val ) )
case "WAL" :
cfg . JournalMode = JournalModeWAL
// For WAL Mode set Synchronous Mode to 'NORMAL'
// See https://www.sqlite.org/pragma.html#pragma_synchronous
cfg . Synchronous = SynchronousNormal
default :
return nil , fmt . Errorf ( "Invalid journal: %v, expecting value of 'DELETE TRUNCATE PERSIST MEMORY WAL OFF'" , val )
}
}
// Locking Mode
if k == "locking_mode" || k == "locking" || k == "lock" {
val := params . Get ( k )
switch strings . ToUpper ( val ) {
case "NORMAL" :
cfg . LockingMode = LockingModeNormal
case "EXCLUSIVE" :
cfg . LockingMode = LockingModeExclusive
default :
return nil , fmt . Errorf ( "Invalid locking_mode: %v, expecting value of 'NORMAL EXCLUSIVE" , val )
}
}
// Query Only
if k == "query_only" {
val := params . Get ( k )
switch strings . ToLower ( val ) {
case "0" , "no" , "false" , "off" :
cfg . QueryOnly = false
case "1" , "yes" , "true" , "on" :
cfg . QueryOnly = true
default :
return nil , fmt . Errorf ( "Invalid query_only: %v, expecting boolean value of '0 1 false true no yes off on'" , val )
}
}
// Recursive Triggers
if k == "rt" || k == "recursive_triggers" {
val := params . Get ( k )
switch strings . ToLower ( val ) {
case "0" , "no" , "false" , "off" :
cfg . RecursiveTriggers = false
case "1" , "yes" , "true" , "on" :
cfg . RecursiveTriggers = true
default :
return nil , fmt . Errorf ( "Invalid recursive_triggers: %v, expecting boolean value of '0 1 false true no yes off on'" , val )
}
}
// Secure Delete
if k == "secure_delete" {
val := params . Get ( k )
switch strings . ToLower ( val ) {
case "0" , "no" , "false" , "off" :
cfg . SecureDelete = SecureDeleteOff
case "1" , "yes" , "true" , "on" :
cfg . SecureDelete = SecureDeleteOn
case "fast" :
cfg . SecureDelete = SecureDeleteFast
default :
return nil , fmt . Errorf ( "Invalid secure_delete: %v, expecting boolean value of '0 1 false true no yes off on fast'" , val )
}
}
if k == "writable_schema" {
val := params . Get ( k )
switch strings . ToLower ( val ) {
case "0" , "no" , "false" , "off" :
cfg . WriteableSchema = false
case "1" , "yes" , "true" , "on" :
cfg . WriteableSchema = true
default :
return nil , fmt . Errorf ( "Invalid writable_schema: %v, expecting boolean value of '0 1 false true no yes off on'" , val )
}
}
}
}
return cfg , nil
}
func normalizeParams ( params url . Values ) {
for k , v := range params {
params [ strings . ToLower ( k ) ] = v
}
}
func parseCryptEncoder ( crypt , salt string ) ( CryptEncoder , error ) {
switch strings . ToUpper ( crypt ) {
case "SHA1" :
return NewSHA1Encoder ( ) , nil
case "SSHA1" :
if len ( salt ) == 0 {
return nil , fmt . Errorf ( "crypt=ssha1, requires salt" )
}
return NewSSHA1Encoder ( salt ) , nil
case "SHA256" :
return NewSHA256Encoder ( ) , nil
case "SSHA256" :
if len ( salt ) == 0 {
return nil , fmt . Errorf ( "crypt=ssha256, requires salt" )
}
return NewSSHA256Encoder ( salt ) , nil
case "SHA384" :
return NewSHA384Encoder ( ) , nil
case "SSHA384" :
if len ( salt ) == 0 {
return nil , fmt . Errorf ( "crypt=ssha384, requires salt" )
}
return NewSSHA384Encoder ( salt ) , nil
case "SHA512" :
return NewSHA512Encoder ( ) , nil
case "SSHA512" :
if len ( salt ) == 0 {
return nil , fmt . Errorf ( "crypt=ssha512, requires salt" )
}
return NewSSHA512Encoder ( salt ) , nil
default :
return nil , fmt . Errorf ( "Unknown crypt encoder provider" )
}
}