diff --git a/.github/workflows/go.yaml b/.github/workflows/go.yaml index 95600c65..93c5d631 100644 --- a/.github/workflows/go.yaml +++ b/.github/workflows/go.yaml @@ -19,7 +19,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ['1.22', '1.23'] + go: ['1.23', '1.24'] fail-fast: false env: OS: ${{ matrix.os }} @@ -46,8 +46,11 @@ jobs: - name: 'Tags: libsqlite3' run: go test -race -v -tags "libsqlite3" + - name: 'Tags: userauth deprecated' + run: make test_userauth_fails test_userauth_fails_libsqlite3 + - name: 'Tags: full' - run: go test -race -v -tags "sqlite_allow_uri_authority sqlite_app_armor sqlite_column_metadata sqlite_foreign_keys sqlite_fts5 sqlite_icu sqlite_introspect sqlite_json sqlite_math_functions sqlite_os_trace sqlite_preupdate_hook sqlite_secure_delete sqlite_see sqlite_stat4 sqlite_trace sqlite_unlock_notify sqlite_userauth sqlite_vacuum_incr sqlite_vtable" + run: go test -race -v -tags "sqlite_allow_uri_authority sqlite_app_armor sqlite_column_metadata sqlite_foreign_keys sqlite_fts5 sqlite_icu sqlite_introspect sqlite_json sqlite_math_functions sqlite_os_trace sqlite_preupdate_hook sqlite_secure_delete sqlite_see sqlite_stat4 sqlite_trace sqlite_unlock_notify sqlite_vacuum_incr sqlite_vtable" - name: 'Tags: vacuum' run: go test -v -tags "sqlite_vacuum_full" diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..34e74761 --- /dev/null +++ b/Makefile @@ -0,0 +1,111 @@ +# Benchmark options +NO_TESTS = ^$ +# BENCHMARKS = ^BenchmarkIndex('\$'|Hard|Torture|Periodic(Unicode)?) +BENCH_SUITE = BenchmarkSuite +BENCH_QUERY = BenchmarkSuite/.*BenchmarkQuery$ +BENCH_EXEC_STEP = BenchmarkSuite/.*BenchmarkExecStep$ + +# TODO: join these into a single list +ALL_TEST_TAGS := sqlite_allow_uri_authority +ALL_TEST_TAGS += sqlite_app_armor +ALL_TEST_TAGS += sqlite_column_metadata +ALL_TEST_TAGS += sqlite_foreign_keys +ALL_TEST_TAGS += sqlite_fts5 +ALL_TEST_TAGS += sqlite_icu +ALL_TEST_TAGS += sqlite_introspect +ALL_TEST_TAGS += sqlite_json +ALL_TEST_TAGS += sqlite_math_functions +ALL_TEST_TAGS += sqlite_preupdate_hook +ALL_TEST_TAGS += sqlite_secure_delete +ALL_TEST_TAGS += sqlite_see +ALL_TEST_TAGS += sqlite_stat4 +ALL_TEST_TAGS += sqlite_trace +ALL_TEST_TAGS += sqlite_unlock_notify +ALL_TEST_TAGS += sqlite_vacuum_incr +ALL_TEST_TAGS += sqlite_vtable + +space := $(subst ,, ) +comma := , + +ALL_TEST_TAGS_JOINED = $(subst $(space),$(comma),$(ALL_TEST_TAGS)) + +# GO_VERSION = $(shell go version | grep -oE 'go[1-9]\.[0-9]+(\.[0-9]+)?') +# ifeq (,$(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.2[2-9]')) +# export GOEXPERIMENT=cgocheck2 +# endif + +.PHONY: all +all: build + +.PHONY: build +build: + @go build -tags "libsqlite3" + +.PHONY: test +test: + @GOEXPERIMENT=cgocheck2 go test -tags "libsqlite3" -v + +.PHONY: test_all +test_all: + @GOEXPERIMENT=cgocheck2 go test -tags "$(ALL_TEST_TAGS_JOINED)" -v + +# TODO: merge with the above target +.PHONY: test_all_libsqlite3 +test_all_libsqlite3: + GOEXPERIMENT=cgocheck2 go test -tags "$(ALL_TEST_TAGS_JOINED),libsqlite3" -v + +.PHONY: qtest +qtest: + @GOEXPERIMENT=cgocheck2 go test -tags "libsqlite3" + +.PHONY: short +short: + @go test -tags "libsqlite3" -short + +.PHONY: testrace +testrace: + @GOEXPERIMENT=cgocheck2 go test -tags "libsqlite3" -race + +.PHONY: test_userauth_fails +test_userauth_fails: + @./scripts/test-userauth-fails.bash + +.PHONY: test_userauth_fails_libsqlite3 +test_userauth_fails_libsqlite3: + @./scripts/test-userauth-fails.bash -libsqlite3 + +.PHONY: testfull +testfull: + @./scripts/test-full.bash -v + +.PHONY: testfull_race +testfull_race: + @./scripts/test-full.bash -v -race + +.PHONY: bench +bench: + @go test -tags "libsqlite3 darwin" -run $(NO_TESTS) -bench $(BENCH_SUITE) -benchmem + +.PHONY: bench_stmt_rows +bench_stmt_rows: + @go test -tags "libsqlite3 darwin" -run $(NO_TESTS) -bench $(BENCH_QUERY) -benchmem + +.PHONY: bench_exec_step +bench_exec_step: + @go test -tags "libsqlite3 darwin" -run $(NO_TESTS) -bench $(BENCH_EXEC_STEP) -benchmem + +.PHONY: bench_mem +bench_mem: + @go test -tags "libsqlite3 darwin" -run $(NO_TESTS) \ + -bench $(BENCH_SUITE) \ + -benchmem -memprofilerate 1 -memprofile mem.out -benchtime 5s + +.PHONY: bench_cpu +bench_cpu: + @go test -tags "libsqlite3 darwin" -run $(NO_TESTS) \ + -bench $(BENCH_SUITE) \ + -cpuprofile cpu.out -benchtime 5s + +.PHONY: tags +tags: + @\grep -ohP '(?<=//go:build )\w+(\s+\w+)?' *.go | sort -u diff --git a/README.md b/README.md index 847dc7d5..f8edf5d7 100644 --- a/README.md +++ b/README.md @@ -468,10 +468,21 @@ For example the TDM-GCC Toolchain can be found [here](https://jmeubank.github.io # User Authentication -This package supports the SQLite User Authentication module. +### ***This is deprecated*** + +~~This package supports the SQLite User Authentication module.~~ + +User authentication deprecated as of [#28](https://github.com/charlievieth/go-sqlite3/pull/28) +and building with the `sqlite_userauth` tag will cause [`Open()`](https://pkg.go.dev/github.com/charlievieth/go-sqlite3#SQLiteDriver.Open) +to fail. + +
+Deprecated User Authentication ## Compile +### WARN: THIS IS DEPRECATED + To use the User authentication module, the package has to be compiled with the tag `sqlite_userauth`. See [Features](#features). ## Usage @@ -577,6 +588,8 @@ The following functions are available for User authentication from the `*SQLiteC When using attached databases, SQLite will use the authentication from the `main` database for the attached database(s). +
+ # Extensions If you want your own extension to be listed here, or you want to add a reference to an extension; please submit an Issue for this. diff --git a/error_test.go b/error_test.go index 618810a7..80cabe94 100644 --- a/error_test.go +++ b/error_test.go @@ -43,7 +43,10 @@ func TestCorruptDbErrors(t *testing.T) { _, err = db.Exec("drop table foo") } - sqliteErr := err.(Error) + sqliteErr, ok := err.(Error) + if !ok { + t.Fatalf("Not a sqlite3.Error: %#v", err) + } if sqliteErr.Code != ErrNotADB { t.Error("wrong error code for corrupted DB") } @@ -113,7 +116,10 @@ func TestExtendedErrorCodes_ForeignKey(t *testing.T) { if err == nil { t.Error("No error!") } else { - sqliteErr := err.(Error) + sqliteErr, ok := err.(Error) + if !ok { + t.Fatalf("Not a sqlite3.Error: %#v", err) + } if sqliteErr.Code != ErrConstraint { t.Errorf("Wrong basic error code: %d != %d", sqliteErr.Code, ErrConstraint) diff --git a/scripts/test-userauth-fails.bash b/scripts/test-userauth-fails.bash new file mode 100755 index 00000000..d4dd5d78 --- /dev/null +++ b/scripts/test-userauth-fails.bash @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +# Test that the userauth tests fail, but don't panic. +# Pass "-libsqlite3" as an argument to the script to +# run the tests with the "libsqlite3" build tag. + +set -euo pipefail + +OUT="$(mktemp -t 'go-sqlite3.XXXXXX')" +trap 'rm "${OUT}"' EXIT + +function test_sqlite_userauth() { + local tags='sqlite_userauth' + while (( $# > 0 )); do + if [[ -n $1 ]]; then + tags+=",$1" + fi + shift + done + + echo "Running: \`go test -tags=${tags}\`" + if go test -tags="${tags}" &> "${OUT}"; then + cat "${OUT}" + echo >&2 '' + echo >&2 'FAIL: tests passed: expected them to fail' + return 2 + fi + + if \grep -qF 'panic:' "${OUT}"; then + cat "${OUT}" + echo >&2 '' + echo >&2 'FAIL: test panicked' + return 2 + fi + + echo "PASS" +} + +function main() { + local tags='' + if [[ ${1:-} == '-libsqlite3' ]]; then + tags='libsqlite3' + fi + test_sqlite_userauth "${tags}" +} + +main "$@" diff --git a/sqlite3.go b/sqlite3.go index 65ac01a6..d24eba0c 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -1906,64 +1906,10 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { } // Preform Authentication - if err := conn.Authenticate(authUser, authPass); err != nil { - return 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.registerAuthFunc("authenticate", conn.authenticate, true); err != nil { - return 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.registerAuthFunc("auth_user_add", conn.authUserAdd, true); err != nil { - return 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.registerAuthFunc("auth_user_change", conn.authUserChange, true); err != nil { - return 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.registerAuthFunc("auth_user_delete", conn.authUserDelete, true); err != nil { - return err - } - - // Register: auth_enabled - // auth_enabled can be used to check if user authentication is enabled - if err := conn.registerAuthFunc("auth_enabled", conn.authEnabled, true); err != nil { + // NB: This will always fail if built with the "sqlite_userauth" + // build tag since user authentication is now deprecated. + if err := conn.Authenticate(authUser, authPass); err != nil { return err } @@ -1980,6 +1926,9 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { } // Check if user wants to activate User Authentication + // + // NB: This will always fail if built with the "sqlite_userauth" + // build tag since user authentication is now deprecated. if authCreate { // Before going any further, we need to check that the user // has provided an username and password within the DSN. @@ -1992,8 +1941,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) { } // Check if User Authentication is Enabled - authExists := conn.AuthEnabled() - if !authExists { + if !conn.AuthEnabled() { if err := conn.AuthUserAdd(authUser, authPass, true); err != nil { return err } diff --git a/sqlite3_go124_test.go b/sqlite3_go124_test.go index 3dc4f7d6..36cd0aed 100644 --- a/sqlite3_go124_test.go +++ b/sqlite3_go124_test.go @@ -6,73 +6,5 @@ package sqlite3 import "testing" func TestGGO(t *testing.T) { - const msg = ` -#cgo noescape _sqlite3_bind_blob -#cgo noescape _sqlite3_bind_text -#cgo noescape _sqlite3_column_blob -#cgo noescape _sqlite3_column_decltypes -#cgo noescape _sqlite3_column_text -#cgo noescape _sqlite3_column_types -#cgo noescape _sqlite3_create_function -#cgo noescape _sqlite3_exec_no_args -#cgo noescape _sqlite3_limit -#cgo noescape _sqlite3_open_v2 -#cgo noescape _sqlite3_prepare_query -#cgo noescape _sqlite3_prepare_v2 -#cgo noescape _sqlite3_prepare_v2_internal -#cgo noescape _sqlite3_step_internal -#cgo noescape _sqlite3_step_row_internal -#cgo noescape sqlite3_aggregate_context -#cgo noescape sqlite3_bind_double -#cgo noescape sqlite3_bind_int -#cgo noescape sqlite3_bind_int64 -#cgo noescape sqlite3_bind_null -#cgo noescape sqlite3_bind_parameter_count -#cgo noescape sqlite3_bind_parameter_index - -#cgo nocallback sqlite3_bind_double -#cgo nocallback sqlite3_bind_int -#cgo nocallback sqlite3_bind_int64 -#cgo nocallback sqlite3_bind_null -#cgo nocallback sqlite3_bind_parameter_count -#cgo nocallback sqlite3_bind_parameter_index - -#cgo noescape sqlite3_clear_bindings -#cgo noescape sqlite3_close_v2 -#cgo noescape sqlite3_column_count -#cgo noescape sqlite3_column_decltype -#cgo noescape sqlite3_column_double -#cgo noescape sqlite3_column_int64 -#cgo noescape sqlite3_column_name - -#cgo nocallback sqlite3_column_count -#cgo nocallback sqlite3_column_decltype -#cgo nocallback sqlite3_column_double -#cgo nocallback sqlite3_column_int64 -#cgo nocallback sqlite3_column_name - -#cgo noescape sqlite3_commit_hook -#cgo noescape sqlite3_create_collation -#cgo noescape sqlite3_db_filename -#cgo noescape sqlite3_errcode -#cgo noescape sqlite3_errmsg -#cgo noescape sqlite3_exec -#cgo noescape sqlite3_extended_errcode -#cgo noescape sqlite3_file_control -#cgo noescape sqlite3_finalize -#cgo noescape sqlite3_get_autocommit -#cgo noescape sqlite3_interrupt -#cgo noescape sqlite3_libversion -#cgo noescape sqlite3_libversion_number -#cgo noescape sqlite3_reset -#cgo noescape sqlite3_rollback_hook -#cgo noescape sqlite3_set_authorizer -#cgo noescape sqlite3_sourceid -#cgo noescape sqlite3_stmt_readonly -#cgo noescape sqlite3_system_errno -#cgo noescape sqlite3_threadsafe -#cgo noescape sqlite3_update_hook -` - // https://pkg.go.dev/cmd/cgo@master#hdr-Optimizing_calls_of_C_code - t.Fatal("TODO: see if adding go1.24 optimization hint helps") + t.Skip("TODO: see if adding go1.24 optimization hint helps") } diff --git a/sqlite3_opt_userauth.go b/sqlite3_opt_userauth.go index 4fda59da..c35a4e1c 100644 --- a/sqlite3_opt_userauth.go +++ b/sqlite3_opt_userauth.go @@ -8,68 +8,19 @@ package sqlite3 -/* -#cgo CFLAGS: -DSQLITE_USER_AUTHENTICATION -#cgo LDFLAGS: -lm -#ifndef USE_LIBSQLITE3 -#include "sqlite3-binding.h" -#else -#include -#endif -#include - -static int -_sqlite3_user_authenticate(sqlite3* db, const char* zUsername, const char* aPW, int nPW) -{ - return sqlite3_user_authenticate(db, zUsername, aPW, nPW); -} - -static int -_sqlite3_user_add(sqlite3* db, const char* zUsername, const char* aPW, int nPW, int isAdmin) -{ - return sqlite3_user_add(db, zUsername, aPW, nPW, isAdmin); -} - -static int -_sqlite3_user_change(sqlite3* db, const char* zUsername, const char* aPW, int nPW, int isAdmin) -{ - return sqlite3_user_change(db, zUsername, aPW, nPW, isAdmin); -} - -static int -_sqlite3_user_delete(sqlite3* db, const char* zUsername) -{ - return sqlite3_user_delete(db, zUsername); -} - -static int -_sqlite3_auth_enabled(sqlite3* db) -{ - int exists = -1; - - sqlite3_stmt *stmt; - sqlite3_prepare_v2(db, "select count(type) from sqlite_master WHERE type='table' and name='sqlite_user';", -1, &stmt, NULL); - - while ( sqlite3_step(stmt) == SQLITE_ROW) { - exists = sqlite3_column_int(stmt, 0); - } - - sqlite3_finalize(stmt); - - return exists; -} -*/ -import "C" -import ( - "errors" - "unsafe" -) +import "errors" var ( - ErrUnauthorized = errors.New("SQLITE_AUTH: Unauthorized") - ErrAdminRequired = errors.New("SQLITE_AUTH: Unauthorized; Admin Privileges Required") + ErrUnauthorized = errors.New("SQLITE_AUTH: Unauthorized") + ErrAdminRequired = errors.New("SQLITE_AUTH: Unauthorized; Admin Privileges Required") + errUserAuthNoLongerSupported = errors.New("sqlite3: the sqlite_userauth tag is no longer supported " + + "as the userauth extension is no longer supported by the SQLite authors, " + + "see https://github.com/mattn/go-sqlite3/issues/1341") ) +// NB: Even though userauth is no longer supported, we preserve +// these methods to maintain backwards compatibility. + // Authenticate will perform an authentication of the provided username // and password against the database. // @@ -82,214 +33,62 @@ var ( // 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 returning SQLITE_OK. -func (c *SQLiteConn) Authenticate(username, password string) error { - rv := c.authenticate(username, password) - switch rv { - case C.SQLITE_ERROR, C.SQLITE_AUTH: - return ErrUnauthorized - case C.SQLITE_OK: - return nil - default: - return c.lastError(int(rv)) - } -} - -// authenticate provides the actual authentication to SQLite. -// This is not exported for usage in Go. -// It is however exported for usage within SQL by the user. +// this interface is a harmless no-op returnning SQLITE_OK. // -// Returns: +// Deprecated: The sqlite3 ext/userauth module is [deprecated]. // -// C.SQLITE_OK (0) -// C.SQLITE_ERROR (1) -// C.SQLITE_AUTH (23) -func (c *SQLiteConn) authenticate(username, password string) int { - // Allocate C Variables - cuser := C.CString(username) - cpass := C.CString(password) - - // Free C Variables - defer func() { - C.free(unsafe.Pointer(cuser)) - C.free(unsafe.Pointer(cpass)) - }() - - return int(C._sqlite3_user_authenticate(c.db, cuser, cpass, C.int(len(password)))) +// [deprecated]: https://www.sqlite.org/src/artifact/ca7e9ee82ca4e1c +func (c *SQLiteConn) Authenticate(username, password string) error { + return errUserAuthNoLongerSupported } // AuthUserAdd can be used (by an admin user only) -// to create a new user. When called on a no-authentication-required +// 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. -func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error { - isAdmin := 0 - if admin { - isAdmin = 1 - } - - rv := c.authUserAdd(username, password, isAdmin) - switch rv { - case C.SQLITE_ERROR, C.SQLITE_AUTH: - return ErrAdminRequired - case C.SQLITE_OK: - return nil - default: - return c.lastError(int(rv)) - } -} - -// authUserAdd enables the User Authentication if not enabled. -// Otherwise it will add a user. // -// When user authentication is already enabled then this function -// can only be called by an admin. +// Deprecated: The sqlite3 ext/userauth module is [deprecated]. // -// This is not exported for usage in Go. -// It is however exported for usage within SQL by the user. -// -// Returns: -// -// C.SQLITE_OK (0) -// C.SQLITE_ERROR (1) -// C.SQLITE_AUTH (23) -func (c *SQLiteConn) authUserAdd(username, password string, admin int) int { - // Allocate C Variables - cuser := C.CString(username) - cpass := C.CString(password) - - // Free C Variables - defer func() { - C.free(unsafe.Pointer(cuser)) - C.free(unsafe.Pointer(cpass)) - }() - - return int(C._sqlite3_user_add(c.db, cuser, cpass, C.int(len(password)), C.int(admin))) +// [deprecated]: https://www.sqlite.org/src/artifact/ca7e9ee82ca4e1c +func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error { + return errUserAuthNoLongerSupported } // AuthUserChange 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 +// 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. -func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error { - isAdmin := 0 - if admin { - isAdmin = 1 - } - - rv := c.authUserChange(username, password, isAdmin) - switch rv { - case C.SQLITE_ERROR, C.SQLITE_AUTH: - return ErrAdminRequired - case C.SQLITE_OK: - return nil - default: - return c.lastError(int(rv)) - } -} - -// authUserChange allows to modify a user. -// Users can change their own password. -// -// Only admins can change passwords for other users -// and modify the admin flag. -// -// The admin flag of the current logged in user cannot be changed. -// THis ensures that their is always an admin. // -// This is not exported for usage in Go. -// It is however exported for usage within SQL by the user. +// Deprecated: The sqlite3 ext/userauth module is [deprecated]. // -// Returns: -// -// C.SQLITE_OK (0) -// C.SQLITE_ERROR (1) -// C.SQLITE_AUTH (23) -func (c *SQLiteConn) authUserChange(username, password string, admin int) int { - // Allocate C Variables - cuser := C.CString(username) - cpass := C.CString(password) - - // Free C Variables - defer func() { - C.free(unsafe.Pointer(cuser)) - C.free(unsafe.Pointer(cpass)) - }() - - return int(C._sqlite3_user_change(c.db, cuser, cpass, C.int(len(password)), C.int(admin))) +// [deprecated]: https://www.sqlite.org/src/artifact/ca7e9ee82ca4e1c +func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error { + return errUserAuthNoLongerSupported } // AuthUserDelete can be used (by an admin user only) -// to delete a user. The currently logged-in user cannot be deleted, +// 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. -func (c *SQLiteConn) AuthUserDelete(username string) error { - rv := c.authUserDelete(username) - switch rv { - case C.SQLITE_ERROR, C.SQLITE_AUTH: - return ErrAdminRequired - case C.SQLITE_OK: - return nil - default: - return c.lastError(int(rv)) - } -} - -// authUserDelete can be used to delete a user. // -// This function can only be executed by an admin. +// Deprecated: The sqlite3 ext/userauth module is [deprecated]. // -// This is not exported for usage in Go. -// It is however exported for usage within SQL by the user. -// -// Returns: -// -// C.SQLITE_OK (0) -// C.SQLITE_ERROR (1) -// C.SQLITE_AUTH (23) -func (c *SQLiteConn) authUserDelete(username string) int { - // Allocate C Variables - cuser := C.CString(username) - - // Free C Variables - defer func() { - C.free(unsafe.Pointer(cuser)) - }() - - return int(C._sqlite3_user_delete(c.db, cuser)) +// [deprecated]: https://www.sqlite.org/src/artifact/ca7e9ee82ca4e1c +func (c *SQLiteConn) AuthUserDelete(username string) error { + return errUserAuthNoLongerSupported } // AuthEnabled checks if the database is protected by user authentication -func (c *SQLiteConn) AuthEnabled() (exists bool) { - rv := c.authEnabled() - if rv == 1 { - exists = true - } - - return -} - -// authEnabled perform the actual check for user authentication. // -// This is not exported for usage in Go. -// It is however exported for usage within SQL by the user. +// Deprecated: The sqlite3 ext/userauth module is [deprecated]. // -// Returns: -// -// 0 - Disabled -// 1 - Enabled -func (c *SQLiteConn) authEnabled() int { - return int(C._sqlite3_auth_enabled(c.db)) -} - -func (c *SQLiteConn) registerAuthFunc(name string, impl any, pure bool) error { - return c.RegisterFunc(name, impl, pure) +// [deprecated]: https://www.sqlite.org/src/artifact/ca7e9ee82ca4e1c +func (c *SQLiteConn) AuthEnabled() (exists bool) { + return false } - -// EOF diff --git a/sqlite3_opt_userauth_omit.go b/sqlite3_opt_userauth_omit.go index 56b6140b..7a2e08ad 100644 --- a/sqlite3_opt_userauth_omit.go +++ b/sqlite3_opt_userauth_omit.go @@ -15,6 +15,9 @@ var ( ErrAdminRequired = errors.New("SQLITE_AUTH: Unauthorized; Admin Privileges Required") ) +// NB: Even though userauth is no longer supported, we preserve +// these methods to maintain backwards compatibility. + // Authenticate will perform an authentication of the provided username // and password against the database. // @@ -28,23 +31,12 @@ var ( // // If the SQLITE_USER table is not present in the database file, then // this interface is a harmless no-op returnning SQLITE_OK. -func (c *SQLiteConn) Authenticate(username, password string) error { - // NOOP - return nil -} - -// authenticate provides the actual authentication to SQLite. -// This is not exported for usage in Go. -// It is however exported for usage within SQL by the user. // -// Returns: +// Deprecated: The sqlite3 ext/userauth module is [deprecated]. // -// C.SQLITE_OK (0) -// C.SQLITE_ERROR (1) -// C.SQLITE_AUTH (23) -func (c *SQLiteConn) authenticate(username, password string) int { - // NOOP - return 0 +// [deprecated]: https://www.sqlite.org/src/artifact/ca7e9ee82ca4e1c +func (c *SQLiteConn) Authenticate(username, password string) error { + return nil } // AuthUserAdd can be used (by an admin user only) @@ -55,28 +47,12 @@ func (c *SQLiteConn) authenticate(username, password string) int { // 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. -func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error { - // NOOP - return nil -} - -// authUserAdd enables the User Authentication if not enabled. -// Otherwise it will add a user. // -// When user authentication is already enabled then this function -// can only be called by an admin. +// Deprecated: The sqlite3 ext/userauth module is [deprecated]. // -// This is not exported for usage in Go. -// It is however exported for usage within SQL by the user. -// -// Returns: -// -// C.SQLITE_OK (0) -// C.SQLITE_ERROR (1) -// C.SQLITE_AUTH (23) -func (c *SQLiteConn) authUserAdd(username, password string, admin int) int { - // NOOP - return 0 +// [deprecated]: https://www.sqlite.org/src/artifact/ca7e9ee82ca4e1c +func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error { + return nil } // AuthUserChange can be used to change a users @@ -84,31 +60,12 @@ func (c *SQLiteConn) authUserAdd(username, password string, admin int) int { // 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. -func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error { - // NOOP - return nil -} - -// authUserChange allows to modify a user. -// Users can change their own password. -// -// Only admins can change passwords for other users -// and modify the admin flag. -// -// The admin flag of the current logged in user cannot be changed. -// THis ensures that their is always an admin. // -// This is not exported for usage in Go. -// It is however exported for usage within SQL by the user. +// Deprecated: The sqlite3 ext/userauth module is [deprecated]. // -// Returns: -// -// C.SQLITE_OK (0) -// C.SQLITE_ERROR (1) -// C.SQLITE_AUTH (23) -func (c *SQLiteConn) authUserChange(username, password string, admin int) int { - // NOOP - return 0 +// [deprecated]: https://www.sqlite.org/src/artifact/ca7e9ee82ca4e1c +func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error { + return nil } // AuthUserDelete can be used (by an admin user only) @@ -116,51 +73,19 @@ func (c *SQLiteConn) authUserChange(username, password string, admin int) int { // which guarantees that there is always an admin user and hence that // the database cannot be converted into a no-authentication-required // database. +// +// Deprecated: The sqlite3 ext/userauth module is [deprecated]. +// +// [deprecated]: https://www.sqlite.org/src/artifact/ca7e9ee82ca4e1c func (c *SQLiteConn) AuthUserDelete(username string) error { - // NOOP return nil } -// authUserDelete can be used to delete a user. -// -// This function can only be executed by an admin. -// -// This is not exported for usage in Go. -// It is however exported for usage within SQL by the user. +// AuthEnabled checks if the database is protected by user authentication // -// Returns: +// Deprecated: The sqlite3 ext/userauth module is [deprecated]. // -// C.SQLITE_OK (0) -// C.SQLITE_ERROR (1) -// C.SQLITE_AUTH (23) -func (c *SQLiteConn) authUserDelete(username string) int { - // NOOP - return 0 -} - -// AuthEnabled checks if the database is protected by user authentication +// [deprecated]: https://www.sqlite.org/src/artifact/ca7e9ee82ca4e1c func (c *SQLiteConn) AuthEnabled() (exists bool) { - // NOOP return false } - -// authEnabled perform the actual check for user authentication. -// -// This is not exported for usage in Go. -// It is however exported for usage within SQL by the user. -// -// Returns: -// -// 0 - Disabled -// 1 - Enabled -func (c *SQLiteConn) authEnabled() int { - // NOOP - return 0 -} - -func (c *SQLiteConn) registerAuthFunc(_ string, _ any, _ bool) error { - // NOOP - return nil -} - -// EOF diff --git a/sqlite3_opt_userauth_test.go b/sqlite3_opt_userauth_test.go index 12e11510..48945f7f 100644 --- a/sqlite3_opt_userauth_test.go +++ b/sqlite3_opt_userauth_test.go @@ -10,27 +10,15 @@ package sqlite3 import ( "database/sql" + "errors" "fmt" "os" "testing" ) -var ( - conn *SQLiteConn - create func(t *testing.T, username, password string) (file string, err error) - createWithCrypt func(t *testing.T, username, password, crypt, salt string) (file string, err error) - connect func(t *testing.T, f string, username, password string) (file string, db *sql.DB, c *SQLiteConn, err error) - connectWithCrypt func(t *testing.T, f string, username, password string, crypt string, salt string) (file string, db *sql.DB, c *SQLiteConn, err error) - authEnabled func(db *sql.DB) (exists bool, err error) - addUser func(db *sql.DB, username, password string, admin int) (rv int, err error) - userExists func(db *sql.DB, username string) (rv int, err error) - isAdmin func(db *sql.DB, username string) (rv bool, err error) - modifyUser func(db *sql.DB, username, password string, admin int) (rv int, err error) - deleteUser func(db *sql.DB, username string) (rv int, err error) -) - -func init() { +func TestUserAuth(t *testing.T) { // Create database connection + var conn *SQLiteConn sql.Register("sqlite3_with_conn", &SQLiteDriver{ ConnectHook: func(c *SQLiteConn) error { @@ -39,21 +27,7 @@ func init() { }, }) - create = func(t *testing.T, username, password string) (file string, err error) { - var db *sql.DB - file, db, _, err = connect(t, "", username, password) - db.Close() - return - } - - createWithCrypt = func(t *testing.T, username, password, crypt, salt string) (file string, err error) { - var db *sql.DB - file, db, _, err = connectWithCrypt(t, "", "admin", "admin", crypt, salt) - db.Close() - return - } - - connect = func(t *testing.T, f string, username, password string) (file string, db *sql.DB, c *SQLiteConn, err error) { + connect := func(t *testing.T, f string, username, password string) (file string, db *sql.DB, c *SQLiteConn, err error) { conn = nil // Clear connection file = f // Copy provided file (f) => file if file == "" { @@ -75,7 +49,7 @@ func init() { } // Dummy query to force connection and database creation - // Will return ErrUnauthorized (SQLITE_AUTH) if user authentication fails + // Will return errUserAuthNoLongerSupported if user authentication fails if _, err = db.Exec("SELECT 1;"); err != nil { defer os.Remove(file) defer db.Close() @@ -86,559 +60,11 @@ func init() { return } - connectWithCrypt = func(t *testing.T, f string, username, password string, crypt string, salt string) (file string, db *sql.DB, c *SQLiteConn, err error) { - conn = nil // Clear connection - file = f // Copy provided file (f) => file - if file == "" { - // Create dummy file - 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)) - if err != nil { - defer os.Remove(file) - return file, nil, nil, err - } - - // Dummy query to force connection and database creation - // Will return ErrUnauthorized (SQLITE_AUTH) if user authentication fails - if _, err = db.Exec("SELECT 1;"); err != nil { - defer os.Remove(file) - defer db.Close() - return file, nil, nil, err - } - c = conn - - return - } - - authEnabled = func(db *sql.DB) (exists bool, err error) { - err = db.QueryRow("select count(type) from sqlite_master WHERE type='table' and name='sqlite_user';").Scan(&exists) - return - } - - addUser = func(db *sql.DB, username, password string, admin int) (rv int, err error) { - err = db.QueryRow("select auth_user_add(?, ?, ?);", username, password, admin).Scan(&rv) - return - } - - userExists = func(db *sql.DB, username string) (rv int, err error) { - err = db.QueryRow("select count(uname) from sqlite_user where uname=?", username).Scan(&rv) - return - } - - isAdmin = func(db *sql.DB, username string) (rv bool, err error) { - err = db.QueryRow("select isAdmin from sqlite_user where uname=?", username).Scan(&rv) - return - } - - modifyUser = func(db *sql.DB, username, password string, admin int) (rv int, err error) { - err = db.QueryRow("select auth_user_change(?, ?, ?);", username, password, admin).Scan(&rv) - return - } - - deleteUser = func(db *sql.DB, username string) (rv int, err error) { - err = db.QueryRow("select auth_user_delete(?);", username).Scan(&rv) - return - } -} - -func TestUserAuthCreateDatabase(t *testing.T) { - f, db, c, err := connect(t, "", "admin", "admin") - if err != nil && c == nil && db == nil { - t.Fatal(err) - } - defer db.Close() - defer os.Remove(f) - - enabled, err := authEnabled(db) - if err != nil || !enabled { - t.Fatalf("UserAuth not enabled: %s", err) - } - - e, err := userExists(db, "admin") - if err != nil { - t.Fatal(err) - } - if e != 1 { - t.Fatal("UserAuth: admin does not exists") - } - a, err := isAdmin(db, "admin") - if err != nil { - t.Fatal(err) - } - if !a { - t.Fatal("UserAuth: User is not administrator") - } -} - -func TestUserAuthCreateDatabaseWithoutArgs(t *testing.T) { - _, db, c, err := connect(t, "", "", "") - if err == nil && c == nil && db == nil { - t.Fatal("Should have failed due to missing _auth_* parameters") - } - - _, db, c, err = connect(t, "", "", "admin") - if err == nil && c == nil && db == nil { - t.Fatal("Should have failed due to missing _auth_user parameter") - } - - _, db, c, err = connect(t, "", "admin", "") - if err == nil && c == nil && db == nil { - t.Fatal("Should have failed due to missing _auth_pass parameter") - } -} - -func TestUserAuthLogin(t *testing.T) { - f1, err := create(t, "admin", "admin") - if err != nil { - t.Fatal(err) - } - defer os.Remove(f1) - - f2, db2, c2, err := connect(t, f1, "admin", "admin") - if err != nil { - t.Fatal(err) - } - defer db2.Close() - if f1 != f2 { - t.Fatal("UserAuth: Database file mismatch") - } - - // Test lower level authentication - err = c2.Authenticate("admin", "admin") - if err != nil { - t.Fatalf("UserAuth: *SQLiteConn.Authenticate() Failed: %s", err) - } - - // Test Login Failed - _, _, _, err = connect(t, f1, "admin", "invalid") + _, _, _, err := connect(t, "", "admin", "admin") if err == nil { - t.Fatal("Login successful while expecting to fail") - } - if err != ErrUnauthorized { - t.Fatal(err) - } - err = c2.Authenticate("admin", "invalid") - if err == nil { - t.Fatal("Login successful while expecting to fail") - } - if err != ErrUnauthorized { - t.Fatal(err) - } -} - -func TestUserAuthAddAdmin(t *testing.T) { - f, db, c, err := connect(t, "", "admin", "admin") - if err != nil && c == nil && db == nil { - t.Fatal(err) - } - defer db.Close() - defer os.Remove(f) - - // Add Admin User through SQL call - rv, err := addUser(db, "admin2", "admin2", 1) - if err != nil { - t.Fatal(err) - } - if rv != 0 { - t.Fatal("Failed to add user") - } - - // Check if user was created - exists, err := userExists(db, "admin2") - if err != nil { - t.Fatal(err) - } - if exists != 1 { - t.Fatal("UserAuth: 'admin2' does not exists") - } - - // Check if user was created as an Administrator - admin, err := isAdmin(db, "admin2") - if err != nil { - t.Fatal(err) - } - if !admin { - t.Fatal("UserAuth: 'admin2' is not administrator") - } - - // Test *SQLiteConn - err = c.AuthUserAdd("admin3", "admin3", true) - if err != nil { - t.Fatal(err) - } - - // Check if user was created - exists, err = userExists(db, "admin2") - if err != nil { - t.Fatal(err) - } - if exists != 1 { - t.Fatal("UserAuth: 'admin3' does not exists") - } - - // Check if the user was created as an Administrator - admin, err = isAdmin(db, "admin3") - if err != nil { - t.Fatal(err) - } - if !admin { - t.Fatal("UserAuth: 'admin3' is not administrator") - } -} - -func TestUserAuthAddUser(t *testing.T) { - f1, db1, c, err := connect(t, "", "admin", "admin") - if err != nil && c == nil && db == nil { - t.Fatal(err) - } - defer os.Remove(f1) - - // Add user through SQL call - rv, err := addUser(db1, "user", "user", 0) - if err != nil { - t.Fatal(err) - } - if rv != 0 { - t.Fatal("Failed to add user") - } - - // Check if user was created - exists, err := userExists(db1, "user") - if err != nil { - t.Fatal(err) - } - if exists != 1 { - t.Fatal("UserAuth: 'user' does not exists") - } - - // Check if user was created as an Administrator - admin, err := isAdmin(db1, "user") - if err != nil { - t.Fatal(err) - } - if admin { - t.Fatal("UserAuth: 'user' is administrator") - } - - // Test *SQLiteConn - err = c.AuthUserAdd("user2", "user2", false) - if err != nil { - t.Fatal(err) - } - - // Check if user was created - exists, err = userExists(db1, "user2") - if err != nil { - t.Fatal(err) - } - if exists != 1 { - t.Fatal("UserAuth: 'user2' does not exists") - } - - // Check if the user was created as an Administrator - admin, err = isAdmin(db1, "user2") - if err != nil { - t.Fatal(err) - } - if admin { - t.Fatal("UserAuth: 'user2' is administrator") - } - - // Reconnect as normal user - db1.Close() - _, db2, c2, err := connect(t, f1, "user", "user") - if err != nil { - t.Fatal(err) - } - defer db2.Close() - - // Try to create admin user while logged in as normal user - rv, err = addUser(db2, "admin2", "admin2", 1) - if err != nil { - t.Fatal(err) - } - if rv != SQLITE_AUTH { - t.Fatal("Created admin user while not allowed") - } - - err = c2.AuthUserAdd("admin3", "admin3", true) - if err != ErrAdminRequired { - t.Fatal("Created admin user while not allowed") - } - - // Try to create normal user while logged in as normal user - rv, err = addUser(db2, "user3", "user3", 0) - if err != nil { - t.Fatal(err) - } - if rv != SQLITE_AUTH { - t.Fatal("Created user while not allowed") - } - - err = c2.AuthUserAdd("user4", "user4", false) - if err != ErrAdminRequired { - t.Fatal("Created user while not allowed") - } -} - -func TestUserAuthModifyUser(t *testing.T) { - f1, db1, c1, err := connect(t, "", "admin", "admin") - if err != nil && c1 == nil && db == nil { - t.Fatal(err) - } - defer os.Remove(f1) - - // Modify Password for current logged in admin - // through SQL - rv, err := modifyUser(db1, "admin", "admin2", 1) - if err != nil { - t.Fatal(err) - } - if rv != 0 { - t.Fatal("Failed to modify password for admin") - } - - // Modify password for current logged in admin - // through *SQLiteConn - err = c1.AuthUserChange("admin", "admin3", true) - if err != nil { - t.Fatal(err) - } - - // Modify Administrator Flag - // Because we are current logged in as 'admin' - // Changing our own admin flag should fail. - rv, err = modifyUser(db1, "admin", "admin3", 0) - if err != nil { - t.Fatal(err) - } - if rv != SQLITE_AUTH { - t.Fatal("Successfully changed admin flag while not allowed") - } - - // Modify admin flag through (*SQLiteConn) - // Because we are current logged in as 'admin' - // Changing our own admin flag should fail. - err = c1.AuthUserChange("admin", "admin3", false) - if err != ErrAdminRequired { - t.Fatal("Successfully changed admin flag while not allowed") - } - - // Add normal user - rv, err = addUser(db1, "user", "password", 0) - if err != nil { - t.Fatal(err) - } - if rv != 0 { - t.Fatal("Failed to add user") - } - - rv, err = addUser(db1, "user2", "user2", 0) - if err != nil { - t.Fatal(err) - } - if rv != 0 { - t.Fatal("Failed to add user") - } - - // Modify other user password and flag through SQL - rv, err = modifyUser(db1, "user", "pass", 1) - if err != nil { - t.Fatal(err) - } - if rv != 0 { - t.Fatal("Failed to modify password for user") - } - - // Modify other user password and flag through *SQLiteConn - err = c1.AuthUserChange("user", "newpass", false) - if err != nil { - t.Fatal(err) - } - - // Disconnect database for reconnect - db1.Close() - _, db2, c2, err := connect(t, f1, "user", "newpass") - if err != nil { - t.Fatal(err) - } - defer db2.Close() - - // Modify other user password through SQL - rv, err = modifyUser(db2, "user2", "newpass", 0) - if err != nil { - t.Fatal(err) - } - if rv != SQLITE_AUTH { - t.Fatal("Password change successful while not allowed") - } - - // Modify other user password and flag through *SQLiteConn - err = c2.AuthUserChange("user2", "invalid", false) - if err != ErrAdminRequired { - t.Fatal("Password change successful while not allowed") - } -} - -func TestUserAuthDeleteUser(t *testing.T) { - f1, db1, c, err := connect(t, "", "admin", "admin") - if err != nil && c == nil && db == nil { - t.Fatal(err) - } - defer os.Remove(f1) - - // Add Admin User 2 - rv, err := addUser(db1, "admin2", "admin2", 1) - if err != nil { - t.Fatal(err) - } - if rv != 0 { - t.Fatal("Failed to add user") - } - - rv, err = addUser(db1, "admin3", "admin3", 1) - if err != nil { - t.Fatal(err) - } - if rv != 0 { - t.Fatal("Failed to add user") - } - - // Check if user was created - exists, err := userExists(db1, "admin2") - if err != nil { - t.Fatal(err) - } - if exists != 1 { - t.Fatal("UserAuth: 'admin2' does not exists") - } - - exists, err = userExists(db1, "admin3") - if err != nil { - t.Fatal(err) - } - if exists != 1 { - t.Fatal("UserAuth: 'admin2' does not exists") - } - - // Delete user through SQL - rv, err = deleteUser(db1, "admin2") - if err != nil { - t.Fatal(err) - } - if rv != 0 { - t.Fatal("Failed to delete admin2") - } - - // Verify user admin2 deleted - exists, err = userExists(db1, "admin2") - if err != nil { - t.Fatal(err) - } - if exists != 0 { - t.Fatal("UserAuth: 'admin2' still exists") - } - - // Delete user through *SQLiteConn - rv, err = deleteUser(db1, "admin3") - if err != nil { - t.Fatal(err) - } - if rv != 0 { - t.Fatal("Failed to delete admin3") - } - - // Verify user admin3 deleted - exists, err = userExists(db1, "admin3") - if err != nil { - t.Fatal(err) - } - if exists != 0 { - t.Fatal("UserAuth: 'admin3' still exists") - } - - // Add normal user for reconnect and privileges check - rv, err = addUser(db1, "reconnect", "reconnect", 0) - if err != nil { - t.Fatal(err) - } - if rv != 0 { - t.Fatal("Failed to add user") - } - - // Add normal user for deletion through SQL - rv, err = addUser(db1, "user", "user", 0) - if err != nil { - t.Fatal(err) - } - if rv != 0 { - t.Fatal("Failed to add user") - } - - rv, err = addUser(db1, "user2", "user2", 0) - if err != nil { - t.Fatal(err) - } - if rv != 0 { - t.Fatal("Failed to add user") - } - - // Close database for reconnect - db1.Close() - - // Reconnect as normal user - _, db2, c2, err := connect(t, f1, "reconnect", "reconnect") - if err != nil { - t.Fatal(err) + t.Fatalf("UserAuth not enabled: %s", err) } - defer db2.Close() - - // Delete user while logged in as normal user - // through SQL - rv, err = deleteUser(db2, "user") - if err != nil { + if !errors.Is(err, errUserAuthNoLongerSupported) { t.Fatal(err) } - if rv != SQLITE_AUTH { - t.Fatal("Successfully deleted user wthout proper privileges") - } - - // Delete user while logged in as normal user - // through *SQLiteConn - err = c2.AuthUserDelete("user2") - if err != ErrAdminRequired { - t.Fatal("Successfully deleted user wthout proper privileges") - } -} - -func TestUserAuthEncoders(t *testing.T) { - cases := map[string]string{ - "sha1": "", - "ssha1": "salted", - "sha256": "", - "ssha256": "salted", - "sha384": "", - "ssha384": "salted", - "sha512": "", - "ssha512": "salted", - } - - for enc, salt := range cases { - f, err := createWithCrypt(t, "admin", "admin", enc, salt) - if err != nil { - t.Fatal(err) - } - defer os.Remove(f) - - _, db, _, err := connectWithCrypt(t, f, "admin", "admin", enc, salt) - if err != nil { - t.Fatal(err) - } - defer db.Close() - if e, err := authEnabled(db); err != nil && !e { - t.Fatalf("UserAuth (%s) not enabled %s", enc, err) - } - } } diff --git a/sqlite3_test.go b/sqlite3_test.go index 6684aefd..b40eb948 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -252,39 +252,37 @@ func TestForeignKeys(t *testing.T) { func TestDeferredForeignKey(t *testing.T) { fname := TempFilename(t) + defer os.Remove(fname) uri := "file:" + fname + "?_foreign_keys=1" db, err := sql.Open("sqlite3", uri) if err != nil { - os.Remove(fname) - t.Errorf("sql.Open(\"sqlite3\", %q): %v", uri, err) + t.Fatalf("sql.Open(\"sqlite3\", %q): %v", uri, err) } + defer db.Close() _, err = db.Exec("CREATE TABLE bar (id INTEGER PRIMARY KEY)") if err != nil { - t.Errorf("failed creating tables: %v", err) + t.Fatalf("failed creating tables: %v", err) } _, err = db.Exec("CREATE TABLE foo (bar_id INTEGER, FOREIGN KEY(bar_id) REFERENCES bar(id) DEFERRABLE INITIALLY DEFERRED)") if err != nil { - t.Errorf("failed creating tables: %v", err) + t.Fatalf("failed creating tables: %v", err) } tx, err := db.Begin() if err != nil { - t.Errorf("Failed to begin transaction: %v", err) + t.Fatalf("Failed to begin transaction: %v", err) } _, err = tx.Exec("INSERT INTO foo (bar_id) VALUES (123)") if err != nil { - t.Errorf("Failed to insert row: %v", err) + t.Fatalf("Failed to insert row: %v", err) } err = tx.Commit() if err == nil { - t.Errorf("Expected an error: %v", err) + t.Fatalf("Expected an error: %v", err) } _, err = db.Begin() if err != nil { - t.Errorf("Failed to begin transaction: %v", err) + t.Fatalf("Failed to begin transaction: %v", err) } - - db.Close() - os.Remove(fname) } func TestRecursiveTriggers(t *testing.T) { @@ -1226,7 +1224,7 @@ func TestQueryer(t *testing.T) { select id from foo order by id; `) if err != nil { - t.Error("Failed to call db.Query:", err) + t.Fatal("Failed to call db.Query:", err) } defer rows.Close() n := 0 @@ -1427,7 +1425,7 @@ func TestStress(t *testing.T) { for j := 0; j < 3; j++ { rows, err := db.Query("select * from foo where id=1;") if err != nil { - t.Error("Failed to call db.Query:", err) + t.Fatal("Failed to call db.Query:", err) } for rows.Next() { var i int