diff options
Diffstat (limited to 'sqlite3.go')
-rw-r--r-- | sqlite3.go | 500 |
1 files changed, 452 insertions, 48 deletions
@@ -1,4 +1,5 @@ // Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>. +// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>. // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. @@ -779,36 +780,135 @@ func errorString(err Error) string { } // Open database and return a new connection. +// +// A pragma can take either zero or one argument. +// The argument is may be either in parentheses or it may be separated from +// the pragma name by an equal sign. The two syntaxes yield identical results. +// In many pragmas, the argument is a boolean. The boolean can be one of: +// 1 yes true on +// 0 no false off +// // You can specify a DSN string using a URI as the filename. // test.db // file:test.db?cache=shared&mode=memory // :memory: // file::memory: +// +// mode +// Access mode of the database. +// https://www.sqlite.org/c3ref/open.html +// Values: +// - ro +// - rw +// - rwc +// - memory +// +// shared +// SQLite Shared-Cache Mode +// https://www.sqlite.org/sharedcache.html +// Values: +// - shared +// - private +// +// immutable=Boolean +// 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. +// // go-sqlite3 adds the following query parameters to those used by SQLite: // _loc=XXX // Specify location of time format. It's possible to specify "auto". -// _busy_timeout=XXX -// Specify value for sqlite3_busy_timeout. +// +// _mutex=XXX +// Specify mutex mode. XXX can be "no", "full". +// // _txlock=XXX // Specify locking behavior for transactions. XXX can be "immediate", // "deferred", "exclusive". -// _foreign_keys=X -// Enable or disable enforcement of foreign keys. X can be 1 or 0. -// _recursive_triggers=X -// Enable or disable recursive triggers. X can be 1 or 0. -// _mutex=XXX -// Specify mutex mode. XXX can be "no", "full". +// +// _auto_vacuum=X | _vacuum=X +// 0 | none - Auto Vacuum disabled +// 1 | full - Auto Vacuum FULL +// 2 | incremental - Auto Vacuum Incremental +// +// _busy_timeout=XXX"| _timeout=XXX +// Specify value for sqlite3_busy_timeout. +// +// _case_sensitive_like=Boolean | _cslike=Boolean +// https://www.sqlite.org/pragma.html#pragma_case_sensitive_like +// Default or disabled the LIKE operation is case-insensitive. +// When enabling this options behaviour of LIKE will become case-sensitive. +// +// _defer_foreign_keys=Boolean | _defer_fk=Boolean +// Defer Foreign Keys until outermost transaction is committed. +// +// _foreign_keys=Boolean | _fk=Boolean +// Enable or disable enforcement of foreign keys. +// +// _ignore_check_constraints=Boolean +// This pragma enables or disables the enforcement of CHECK constraints. +// The default setting is off, meaning that CHECK constraints are enforced by default. +// +// _journal_mode=MODE | _journal=MODE +// Set journal mode for the databases associated with the current connection. +// https://www.sqlite.org/pragma.html#pragma_journal_mode +// +// _locking_mode=X | _locking=X +// Sets the database connection locking-mode. +// The locking-mode is either NORMAL or EXCLUSIVE. +// https://www.sqlite.org/pragma.html#pragma_locking_mode +// +// _query_only=Boolean +// The query_only pragma prevents all changes to database files when enabled. +// +// _recursive_triggers=Boolean | _rt=Boolean +// Enable or disable recursive triggers. +// +// _secure_delete=Boolean|FAST +// When secure_delete is on, SQLite overwrites deleted content with zeros. +// https://www.sqlite.org/pragma.html#pragma_secure_delete +// +// _synchronous=X | _sync=X +// Change the setting of the "synchronous" flag. +// https://www.sqlite.org/pragma.html#pragma_synchronous +// +// _writable_schema=Boolean +// When this pragma is on, the SQLITE_MASTER tables in which database +// can be changed using ordinary UPDATE, INSERT, and DELETE statements. +// Warning: misuse of this pragma can easily result in a corrupt database file. +// +// 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") } + var pkey string + + // Options var loc *time.Location + mutex := C.int(C.SQLITE_OPEN_FULLMUTEX) txlock := "BEGIN" + + // PRAGMA's + autoVacuum := -1 busyTimeout := 5000 + caseSensitiveLike := -1 + deferForeignKeys := -1 foreignKeys := -1 + ignoreCheckConstraints := -1 + journalMode := "DELETE" + lockingMode := "NORMAL" + queryOnly := -1 recursiveTriggers := -1 - mutex := C.int(C.SQLITE_OPEN_FULLMUTEX) + secureDelete := "DEFAULT" + synchronousMode := "NORMAL" + writableSchema := -1 + pos := strings.IndexRune(dsn, '?') if pos >= 1 { params, err := url.ParseQuery(dsn[pos+1:]) @@ -818,9 +918,10 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { // _loc if val := params.Get("_loc"); val != "" { - if val == "auto" { + switch strings.ToLower(val) { + case "auto": loc = time.Local - } else { + default: loc, err = time.LoadLocation(val) if err != nil { return nil, fmt.Errorf("Invalid _loc: %v: %v", val, err) @@ -828,18 +929,21 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { } } - // _busy_timeout - if val := params.Get("_busy_timeout"); val != "" { - iv, err := strconv.ParseInt(val, 10, 64) - if err != nil { - return nil, fmt.Errorf("Invalid _busy_timeout: %v: %v", val, err) + // _mutex + if val := params.Get("_mutex"); val != "" { + switch strings.ToLower(val) { + case "no": + mutex = C.SQLITE_OPEN_NOMUTEX + case "full": + mutex = C.SQLITE_OPEN_FULLMUTEX + default: + return nil, fmt.Errorf("Invalid _mutex: %v", val) } - busyTimeout = int(iv) } // _txlock if val := params.Get("_txlock"); val != "" { - switch val { + switch strings.ToLower(val) { case "immediate": txlock = "BEGIN IMMEDIATE" case "exclusive": @@ -851,39 +955,262 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { } } - // _foreign_keys - if val := params.Get("_foreign_keys"); val != "" { - switch val { - case "1": - foreignKeys = 1 - case "0": + // Auto Vacuum (_vacuum) + // + // https://www.sqlite.org/pragma.html#pragma_auto_vacuum + // + pkey = "" // Reset pkey + if _, ok := params["_auto_vacuum"]; ok { + pkey = "_auto_vacuum" + } + if _, ok := params["_vacuum"]; ok { + pkey = "_vacuum" + } + if val := params.Get(pkey); val != "" { + switch strings.ToLower(val) { + case "0", "none": + autoVacuum = 0 + case "1", "full": + autoVacuum = 1 + case "2", "incremental": + autoVacuum = 2 + default: + return nil, fmt.Errorf("Invalid _auto_vacuum: %v, expecting value of '0 NONE 1 FULL 2 INCREMENTAL'", val) + } + } + + // Busy Timeout (_busy_timeout) + // + // https://www.sqlite.org/pragma.html#pragma_busy_timeout + // + pkey = "" // Reset pkey + if _, ok := params["_busy_timeout"]; ok { + pkey = "_busy_timeout" + } + if _, ok := params["_timeout"]; ok { + pkey = "_timeout" + } + if val := params.Get(pkey); val != "" { + iv, err := strconv.ParseInt(val, 10, 64) + if err != nil { + return nil, fmt.Errorf("Invalid _busy_timeout: %v: %v", val, err) + } + busyTimeout = int(iv) + } + + // Case Sensitive Like (_cslike) + // + // https://www.sqlite.org/pragma.html#pragma_case_sensitive_like + // + pkey = "" // Reset pkey + if _, ok := params["_case_sensitive_like"]; ok { + pkey = "_case_sensitive_like" + } + if _, ok := params["_cslike"]; ok { + pkey = "_cslike" + } + if val := params.Get(pkey); val != "" { + switch strings.ToLower(val) { + case "0", "no", "false", "off": + caseSensitiveLike = 0 + case "1", "yes", "true", "on": + caseSensitiveLike = 1 + 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 (_defer_foreign_keys | _defer_fk) + // + // https://www.sqlite.org/pragma.html#pragma_defer_foreign_keys + // + pkey = "" // Reset pkey + if _, ok := params["_defer_foreign_keys"]; ok { + pkey = "_defer_foreign_keys" + } + if _, ok := params["_defer_fk"]; ok { + pkey = "_defer_fk" + } + if val := params.Get(pkey); val != "" { + switch strings.ToLower(val) { + case "0", "no", "false", "off": + deferForeignKeys = 0 + case "1", "yes", "true", "on": + deferForeignKeys = 1 + 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 (_foreign_keys | _fk) + // + // https://www.sqlite.org/pragma.html#pragma_foreign_keys + // + pkey = "" // Reset pkey + if _, ok := params["_foreign_keys"]; ok { + pkey = "_foreign_keys" + } + if _, ok := params["_fk"]; ok { + pkey = "_fk" + } + if val := params.Get(pkey); val != "" { + switch strings.ToLower(val) { + case "0", "no", "false", "off": foreignKeys = 0 + case "1", "yes", "true", "on": + foreignKeys = 1 default: - return nil, fmt.Errorf("Invalid _foreign_keys: %v", val) + return nil, fmt.Errorf("Invalid _foreign_keys: %v, expecting boolean value of '0 1 false true no yes off on'", val) } } - // _recursive_triggers - if val := params.Get("_recursive_triggers"); val != "" { - switch val { - case "1": - recursiveTriggers = 1 - case "0": + // Ignore CHECK Constrains (_ignore_check_constraints) + // + // https://www.sqlite.org/pragma.html#pragma_ignore_check_constraints + // + if val := params.Get("_ignore_check_constraints"); val != "" { + switch strings.ToLower(val) { + case "0", "no", "false", "off": + ignoreCheckConstraints = 0 + case "1", "yes", "true", "on": + ignoreCheckConstraints = 1 + default: + return nil, fmt.Errorf("Invalid _ignore_check_constraints: %v, expecting boolean value of '0 1 false true no yes off on'", val) + } + } + + // Journal Mode (_journal_mode | _journal) + // + // https://www.sqlite.org/pragma.html#pragma_journal_mode + // + pkey = "" // Reset pkey + if _, ok := params["_journal_mode"]; ok { + pkey = "_journal_mode" + } + if _, ok := params["_journal"]; ok { + pkey = "_journal" + } + if val := params.Get(pkey); val != "" { + switch strings.ToUpper(val) { + case "DELETE", "TRUNCATE", "PERSIST", "MEMORY", "OFF": + journalMode = strings.ToUpper(val) + case "WAL": + journalMode = strings.ToUpper(val) + + // For WAL Mode set Synchronous Mode to 'NORMAL' + // See https://www.sqlite.org/pragma.html#pragma_synchronous + synchronousMode = "NORMAL" + default: + return nil, fmt.Errorf("Invalid _journal: %v, expecting value of 'DELETE TRUNCATE PERSIST MEMORY WAL OFF'", val) + } + } + + // Locking Mode (_locking) + // + // https://www.sqlite.org/pragma.html#pragma_locking_mode + // + pkey = "" // Reset pkey + if _, ok := params["_locking_mode"]; ok { + pkey = "_locking_mode" + } + if _, ok := params["_locking"]; ok { + pkey = "_locking" + } + if val := params.Get("_locking"); val != "" { + switch strings.ToUpper(val) { + case "NORMAL", "EXCLUSIVE": + lockingMode = strings.ToUpper(val) + default: + return nil, fmt.Errorf("Invalid _locking_mode: %v, expecting value of 'NORMAL EXCLUSIVE", val) + } + } + + // Query Only (_query_only) + // + // https://www.sqlite.org/pragma.html#pragma_query_only + // + if val := params.Get("_query_only"); val != "" { + switch strings.ToLower(val) { + case "0", "no", "false", "off": + queryOnly = 0 + case "1", "yes", "true", "on": + queryOnly = 1 + default: + return nil, fmt.Errorf("Invalid _query_only: %v, expecting boolean value of '0 1 false true no yes off on'", val) + } + } + + // Recursive Triggers (_recursive_triggers) + // + // https://www.sqlite.org/pragma.html#pragma_recursive_triggers + // + pkey = "" // Reset pkey + if _, ok := params["_recursive_triggers"]; ok { + pkey = "_recursive_triggers" + } + if _, ok := params["_rt"]; ok { + pkey = "_rt" + } + if val := params.Get(pkey); val != "" { + switch strings.ToLower(val) { + case "0", "no", "false", "off": recursiveTriggers = 0 + case "1", "yes", "true", "on": + recursiveTriggers = 1 default: - return nil, fmt.Errorf("Invalid _recursive_triggers: %v", val) + return nil, fmt.Errorf("Invalid _recursive_triggers: %v, expecting boolean value of '0 1 false true no yes off on'", val) } } - // _mutex - if val := params.Get("_mutex"); val != "" { - switch val { - case "no": - mutex = C.SQLITE_OPEN_NOMUTEX - case "full": - mutex = C.SQLITE_OPEN_FULLMUTEX + // Secure Delete (_secure_delete) + // + // https://www.sqlite.org/pragma.html#pragma_secure_delete + // + if val := params.Get("_secure_delete"); val != "" { + switch strings.ToLower(val) { + case "0", "no", "false", "off": + secureDelete = "OFF" + case "1", "yes", "true", "on": + secureDelete = "ON" + case "fast": + secureDelete = "FAST" default: - return nil, fmt.Errorf("Invalid _mutex: %v", val) + return nil, fmt.Errorf("Invalid _secure_delete: %v, expecting boolean value of '0 1 false true no yes off on fast'", val) + } + } + + // Synchronous Mode (_synchronous | _sync) + // + // https://www.sqlite.org/pragma.html#pragma_synchronous + // + pkey = "" // Reset pkey + if _, ok := params["_synchronous"]; ok { + pkey = "_synchronous" + } + if _, ok := params["_sync"]; ok { + pkey = "_sync" + } + if val := params.Get(pkey); val != "" { + switch strings.ToUpper(val) { + case "0", "OFF", "1", "NORMAL", "2", "FULL", "3", "EXTRA": + synchronousMode = strings.ToUpper(val) + default: + return nil, fmt.Errorf("Invalid _synchronous: %v, expecting value of '0 OFF 1 NORMAL 2 FULL 3 EXTRA'", val) + } + } + + // Writable Schema (_writeable_schema) + // + // https://www.sqlite.org/pragma.html#pragma_writeable_schema + // + if val := params.Get("_writable_schema"); val != "" { + switch strings.ToLower(val) { + case "0", "no", "false", "off": + writableSchema = 0 + case "1", "yes", "true", "on": + writableSchema = 1 + default: + return nil, fmt.Errorf("Invalid _writable_schema: %v, expecting boolean value of '0 1 false true no yes off on'", val) } } @@ -920,24 +1247,101 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { } return nil } - if foreignKeys == 0 { - if err := exec("PRAGMA foreign_keys = OFF;"); err != nil { + + // Auto Vacuum + if autoVacuum > -1 { + if err := exec(fmt.Sprintf("PRAGMA auto_vacuum = %d;", autoVacuum)); err != nil { C.sqlite3_close_v2(db) return nil, err } - } else if foreignKeys == 1 { - if err := exec("PRAGMA foreign_keys = ON;"); err != nil { + } + + // Case Sensitive LIKE + if caseSensitiveLike > -1 { + if err := exec(fmt.Sprintf("PRAGMA case_sensitive_like = %d;", caseSensitiveLike)); err != nil { C.sqlite3_close_v2(db) return nil, err } } - if recursiveTriggers == 0 { - if err := exec("PRAGMA recursive_triggers = OFF;"); err != nil { + + // Defer Foreign Keys + if deferForeignKeys > -1 { + if err := exec(fmt.Sprintf("PRAGMA defer_foreign_keys = %d;", deferForeignKeys)); err != nil { + C.sqlite3_close_v2(db) + return nil, err + } + } + + // Forgein Keys + if foreignKeys > -1 { + if err := exec(fmt.Sprintf("PRAGMA foreign_keys = %d;", foreignKeys)); err != nil { + C.sqlite3_close_v2(db) + return nil, err + } + } + + // Ignore CHECK Constraints + if ignoreCheckConstraints > -1 { + if err := exec(fmt.Sprintf("PRAGMA ignore_check_constraints = %d;", ignoreCheckConstraints)); err != nil { + C.sqlite3_close_v2(db) + return nil, err + } + } + + // Journal Mode + // Because default Journal Mode is DELETE this PRAGMA can always be executed. + if err := exec(fmt.Sprintf("PRAGMA journal_mode = %s;", journalMode)); err != nil { + C.sqlite3_close_v2(db) + return nil, err + } + + // Locking Mode + // Because the default is NORMAL and this is not changed in this package + // by using the compile time SQLITE_DEFAULT_LOCKING_MODE this PRAGMA can always be executed + if err := exec(fmt.Sprintf("PRAGMA locking_mode = %s;", lockingMode)); err != nil { + C.sqlite3_close_v2(db) + return nil, err + } + + // Query Only + if queryOnly > -1 { + if err := exec(fmt.Sprintf("PRAGMA query_only = %d;", queryOnly)); err != nil { + C.sqlite3_close_v2(db) + return nil, err + } + } + + // Recursive Triggers + if recursiveTriggers > -1 { + if err := exec(fmt.Sprintf("PRAGMA recursive_triggers = %d;", recursiveTriggers)); err != nil { + C.sqlite3_close_v2(db) + return nil, err + } + } + + // Secure Delete + // + // Because this package can set the compile time flag SQLITE_SECURE_DELETE with a build tag + // the default value for secureDelete var is 'DEFAULT' this way + // you can compile with secure_delete 'ON' and disable it for a specific database connection. + if secureDelete != "DEFAULT" { + if err := exec(fmt.Sprintf("PRAGMA secure_delete = %s;", secureDelete)); err != nil { C.sqlite3_close_v2(db) return nil, err } - } else if recursiveTriggers == 1 { - if err := exec("PRAGMA recursive_triggers = ON;"); err != nil { + } + + // Synchronous Mode + // + // Because default is NORMAL this statement is always executed + if err := exec(fmt.Sprintf("PRAGMA synchronous = %s;", synchronousMode)); err != nil { + C.sqlite3_close_v2(db) + return nil, err + } + + // Writable Schema + if writableSchema > -1 { + if err := exec(fmt.Sprintf("PRAGMA writable_schema = %d;", writableSchema)); err != nil { C.sqlite3_close_v2(db) return nil, err } |