diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..0665cac --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,17 @@ +### Issue description +Tell us what should happen and what happens instead + +### Example code +```go +If possible, please enter some example code here to reproduce the issue. +``` + +### Error log +``` +If you have an error log, please paste it here. +``` + +### Configuration +*Driver version (or git SHA):* + +*Go version:* run `go version` in your console diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..a60941e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ +### Description +Please explain the changes you made here. + +### Checklist +- [ ] Code compiles correctly +- [ ] Created tests which fail without the change (if possible) +- [ ] All tests passing +- [ ] Extended the README / documentation / Wiki, if necessary +- [ ] Added myself / the copyright holder to the AUTHORS file diff --git a/.gitignore b/.gitignore index fccccc3..c27a2f8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,17 @@ tools/upgrade/*.h # Exclude upgrade binary cmd/upgrade/upgrade +# Exclude example binaries +examples/*/*.db +examples/custom_func/custom_func +examples/hook/hook +examples/limit/limit +examples/*/*.so +examples/*/extension +examples/simple/simple +examples/trace/trace +examples/vtable/vtable + debug.test .DS_Store .DS_Store? diff --git a/.travis.yml b/.travis.yml index 45f1dac..f81e513 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ env: matrix: - GOTAGS= - GOTAGS=libsqlite3 - - GOTAGS="sqlite_allow_uri_authority sqlite_app_armor sqlite_foreign_keys sqlite_fts5 sqlite_icu sqlite_introspect sqlite_json sqlite_secure_delete sqlite_see sqlite_stat4 sqlite_trace sqlite_userauth sqlite_vacuum_incr sqlite_vtable" + - GOTAGS="sqlite_allow_uri_authority sqlite_app_armor sqlite_foreign_keys sqlite_fts5 sqlite_icu sqlite_introspect sqlite_json sqlite_preupdate_hook sqlite_secure_delete sqlite_see sqlite_stat4 sqlite_trace sqlite_userauth sqlite_vacuum_incr sqlite_vtable" - GOTAGS=sqlite_vacuum_full go: diff --git a/AUTHORS b/AUTHORS index c833e61..1399d4c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,7 +6,7 @@ # Names should be added to this file as # Name # The email address is not required for organizations. -# Please keep the list sorted. +# Please keep the list sorted (First Name Ascending). # Individual Persons G.J.R. Timmer diff --git a/CHANGELOG.md b/CHANGELOG.md index 42125a0..909ede7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,16 @@ -## Version 2.0.0 (2018-xx-xx) +## Version 2.0.0 (2018-07-20) Changes: - Rewrite of package - Documentation fixes - Moved `crypt` encoders to subpackage +- Additional Tests New Features: - Golang:1.10 `sql/driver` support - Crypt - `Encoder` interface for implementing custom encoder - `Salter` interface for custom encoder - -Bug Fixes: -- TODO \ No newline at end of file + `CryptEncoder` interface for implementing custom encoder + `CryptSaltedEncoder` interface for implementing custom encoder with a salt +- Wiki Pages diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c0e2de..cf37724 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,4 +20,4 @@ Before merging the Pull Request, at least one team member must have commented wi ## Development Ideas -If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/mattn/go-sqlite3/wiki/Development-Ideas) Wiki page. \ No newline at end of file +If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/mattn/go-sqlite3/wiki/Development-Ideas) Wiki page. diff --git a/README.md b/README.md index 48c5f09..c18ec24 100644 --- a/README.md +++ b/README.md @@ -1 +1,69 @@ - # TODO Rewrite \ No newline at end of file +# Go-SQLite3 + +[![GoDoc Reference](https://godoc.org/github.com/mattn/go-sqlite3?status.svg)](http://godoc.org/github.com/mattn/go-sqlite3) +[![Build Status](https://travis-ci.org/mattn/go-sqlite3.svg?branch=master)](https://travis-ci.org/mattn/go-sqlite3) +[![Coverage Status](https://coveralls.io/repos/mattn/go-sqlite3/badge.svg?branch=master)](https://coveralls.io/r/mattn/go-sqlite3?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-sqlite3)](https://goreportcard.com/report/github.com/mattn/go-sqlite3) + +**Current Version: 2.0.0** + +**Please note that version 2.0.0 is not backwards compatible** + +## Documentation + +[More documentation is available in our Wiki](https://github.com/mattn/go-sqlite3/wiki) + +## Description + +sqlite3 driver conforming to the built-in database/sql interface + +Supported Golang version: +- 1.9.x +- 1.10.x + +[This package follows the official Golang Release Policy.](https://golang.org/doc/devel/release.html#policy) + +## Requirements + +* Go: 1.9 or higher. +* GCC (Go-SQLite3 is a `CGO` package) + +_go-sqlite3_ is *cgo* package. +If you want to build your app using go-sqlite3, you need gcc. +However, after you have built and installed _go-sqlite3_ with `go install github.com/mattn/go-sqlite3` (which requires gcc), you can build your app without relying on gcc in future. + +**Important: because this is a `CGO` enabled package you are required to set the environment variable `CGO_ENABLED=1` and have a `gcc` compile present within your path.** + +## Installation + +```bash +go get github.com/mattn/go-sqlite3/driver +``` + +## Usage + +`Go-SQLite3 is an implementation of Go's database/sql/driver interface. You only need to import the driver and can use the full database/sql API then. + +Use `sqlite3` as `drivername` and a valid [DSN](https://github.com/mattn/go-sqlite3/wiki/DSN) as `dataSourceName`: + +```go +import ( + "database/sql" + _ "github.com/mattn/go-sqlite3/driver" +) + +db, err := sql.Open("sqlite3", "file:test.db") +``` + +[Examples are available in our Wiki](https://github.com/mattn/go-sqlite3/wiki/Examples) + +# License + +MIT: http://mattn.mit-license.org/2018 + +sqlite3-binding.c, sqlite3-binding.h, sqlite3ext.h + +The -binding suffix was added to avoid build failures under gccgo. + +In this repository, those files are an amalgamation of code that was copied from SQLite3. +The license of that code is the same as the license of SQLite3. diff --git a/_driver/README.md b/_driver/README.md deleted file mode 100644 index 37d017a..0000000 --- a/_driver/README.md +++ /dev/null @@ -1,518 +0,0 @@ -go-sqlite3 -========== - -[![GoDoc Reference](https://godoc.org/github.com/mattn/go-sqlite3?status.svg)](http://godoc.org/github.com/mattn/go-sqlite3) -[![Build Status](https://travis-ci.org/mattn/go-sqlite3.svg?branch=master)](https://travis-ci.org/mattn/go-sqlite3) -[![Coverage Status](https://coveralls.io/repos/mattn/go-sqlite3/badge.svg?branch=master)](https://coveralls.io/r/mattn/go-sqlite3?branch=master) -[![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-sqlite3)](https://goreportcard.com/report/github.com/mattn/go-sqlite3) - -# Description - -sqlite3 driver conforming to the built-in database/sql interface - -Supported Golang version: -- 1.9.x -- 1.10.x - -[This package follows the official Golang Release Policy.](https://golang.org/doc/devel/release.html#policy) - -### Overview - -- [Installation](#installation) -- [API Reference](#api-reference) -- [Connection String](#connection-string) -- [Features](#features) -- [Compilation](#compilation) - - [Android](#android) - - [ARM](#arm) - - [Cross Compile](#cross-compile) - - [Google Cloud Platform](#google-cloud-platform) - - [Linux](#linux) - - [Alpine](#alpine) - - [Fedora](#fedora) - - [Ubuntu](#ubuntu) - - [Mac OSX](#mac-osx) - - [Windows](#windows) - - [Errors](#errors) -- [User Authentication](#user-authentication) - - [Compile](#compile) - - [Usage](#usage) -- [Extensions](#extensions) - - [Spatialite](#spatialite) -- [FAQ](#faq) -- [License](#license) - -# Installation - -This package can be installed with the go get command: - - go get github.com/mattn/go-sqlite3 - -_go-sqlite3_ is *cgo* package. -If you want to build your app using go-sqlite3, you need gcc. -However, after you have built and installed _go-sqlite3_ with `go install github.com/mattn/go-sqlite3` (which requires gcc), you can build your app without relying on gcc in future. - -***Important: because this is a `CGO` enabled package you are required to set the environment variable `CGO_ENABLED=1` and have a `gcc` compile present within your path.*** - -# API Reference - -API documentation can be found here: http://godoc.org/github.com/mattn/go-sqlite3 - -Examples can be found under the [examples](./_example) directory - -# Connection String - -When creating a new SQLite database or connection to an existing one, with the file name additional options can be given. -This is also known as a DSN string. (Data Source Name). - -Options are append after the filename of the SQLite database. -The database filename and options are seperated by an `?` (Question Mark). - -This also applies when using an in-memory database instead of a file. - -Options can be given using the following format: `KEYWORD=VALUE` and multiple options can be combined with the `&` ampersand. - -This library supports dsn options of SQLite itself and provides additional options. - -Boolean values can be one of: -* `0` `no` `false` `off` -* `1` `yes` `true` `on` - -| Name | Key | Value(s) | Description | -|------|-----|----------|-------------| -| UA - Create | `_auth` | - | Create User Authentication, for more information see [User Authentication](#user-authentication) | -| UA - Username | `_auth_user` | `string` | Username for User Authentication, for more information see [User Authentication](#user-authentication) | -| UA - Password | `_auth_pass` | `string` | Password for User Authentication, for more information see [User Authentication](#user-authentication) | -| UA - Crypt | `_auth_crypt` | | Password encoder to use for User Authentication, for more information see [User Authentication](#user-authentication) | -| UA - Salt | `_auth_salt` | `string` | Salt to use if the configure password encoder requires a salt, for User Authentication, for more information see [User Authentication](#user-authentication) | -| Auto Vacuum | `_auto_vacuum` \| `_vacuum` | | For more information see [PRAGMA auto_vacuum](https://www.sqlite.org/pragma.html#pragma_auto_vacuum) | -| Busy Timeout | `_busy_timeout` \| `_timeout` | `int` | Specify value for sqlite3_busy_timeout. For more information see [PRAGMA busy_timeout](https://www.sqlite.org/pragma.html#pragma_busy_timeout) | -| Case Sensitive LIKE | `_case_sensitive_like` \| `_cslike` | `boolean` | For more information see [PRAGMA case_sensitive_like](https://www.sqlite.org/pragma.html#pragma_case_sensitive_like) | -| Defer Foreign Keys | `_defer_foreign_keys` \| `_defer_fk` | `boolean` | For more information see [PRAGMA defer_foreign_keys](https://www.sqlite.org/pragma.html#pragma_defer_foreign_keys) | -| Foreign Keys | `_foreign_keys` \| `_fk` | `boolean` | For more information see [PRAGMA foreign_keys](https://www.sqlite.org/pragma.html#pragma_foreign_keys) | -| Ignore CHECK Constraints | `_ignore_check_constraints` | `boolean` | For more information see [PRAGMA ignore_check_constraints](https://www.sqlite.org/pragma.html#pragma_ignore_check_constraints) | -| Immutable | `immutable` | `boolean` | For more information see [Immutable](https://www.sqlite.org/c3ref/open.html) | -| Journal Mode | `_journal_mode` \| `_journal` | | For more information see [PRAGMA journal_mode](https://www.sqlite.org/pragma.html#pragma_journal_mode) | -| Locking Mode | `_locking_mode` \| `_locking` | | For more information see [PRAGMA locking_mode](https://www.sqlite.org/pragma.html#pragma_locking_mode) | -| Mode | `mode` | | Access Mode of the database. For more information see [SQLite Open](https://www.sqlite.org/c3ref/open.html) | -| Mutex Locking | `_mutex` | | Specify mutex mode. | -| Query Only | `_query_only` | `boolean` | For more information see [PRAGMA query_only](https://www.sqlite.org/pragma.html#pragma_query_only) | -| Recursive Triggers | `_recursive_triggers` \| `_rt` | `boolean` | For more information see [PRAGMA recursive_triggers](https://www.sqlite.org/pragma.html#pragma_recursive_triggers) | -| Secure Delete | `_secure_delete` | `boolean` \| `FAST` | For more information see [PRAGMA secure_delete](https://www.sqlite.org/pragma.html#pragma_secure_delete) | -| Shared-Cache Mode | `cache` | | Set cache mode for more information see [sqlite.org](https://www.sqlite.org/sharedcache.html) | -| Synchronous | `_synchronous` \| `_sync` | | For more information see [PRAGMA synchronous](https://www.sqlite.org/pragma.html#pragma_synchronous) | -| Time Zone Location | `_loc` | auto | Specify location of time format. | -| Transaction Lock | `_txlock` | | Specify locking behavior for transactions. | -| Writable Schema | `_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. | - -## DSN Examples - -``` -file:test.db?cache=shared&mode=memory -``` - -# Features - -This package allows additional configuration of features available within SQLite3 to be enabled or disabled by golang build constraints also known as build `tags`. - -[Click here for more information about build tags / constraints.](https://golang.org/pkg/go/build/#hdr-Build_Constraints) - -### Usage - -If you wish to build this library with additional extensions / features. -Use the following command. - -```bash -go build --tags "" -``` - -For available features see the extension list. -When using multiple build tags, all the different tags should be space delimted. - -Example: - -```bash -go build --tags "icu json1 fts5 secure_delete" -``` - -### Feature / Extension List - -| Extension | Build Tag | Description | -|-----------|-----------|-------------| -| Additional Statistics | sqlite_stat4 | This option adds additional logic to the ANALYZE command and to the query planner that can help SQLite to chose a better query plan under certain situations. The ANALYZE command is enhanced to collect histogram data from all columns of every index and store that data in the sqlite_stat4 table.

The query planner will then use the histogram data to help it make better index choices. The downside of this compile-time option is that it violates the query planner stability guarantee making it more difficult to ensure consistent performance in mass-produced applications.

SQLITE_ENABLE_STAT4 is an enhancement of SQLITE_ENABLE_STAT3. STAT3 only recorded histogram data for the left-most column of each index whereas the STAT4 enhancement records histogram data from all columns of each index.

The SQLITE_ENABLE_STAT3 compile-time option is a no-op and is ignored if the SQLITE_ENABLE_STAT4 compile-time option is used | -| Allow URI Authority | sqlite_allow_uri_authority | URI filenames normally throws an error if the authority section is not either empty or "localhost".

However, if SQLite is compiled with the SQLITE_ALLOW_URI_AUTHORITY compile-time option, then the URI is converted into a Uniform Naming Convention (UNC) filename and passed down to the underlying operating system that way | -| App Armor | sqlite_app_armor | When defined, this C-preprocessor macro activates extra code that attempts to detect misuse of the SQLite API, such as passing in NULL pointers to required parameters or using objects after they have been destroyed.

App Armor is not available under `Windows`. | -| Disable Load Extensions | sqlite_omit_load_extension | Loading of external extensions is enabled by default.

To disable extension loading add the build tag `sqlite_omit_load_extension`. | -| Foreign Keys | sqlite_foreign_keys | This macro determines whether enforcement of foreign key constraints is enabled or disabled by default for new database connections.

Each database connection can always turn enforcement of foreign key constraints on and off and run-time using the foreign_keys pragma.

Enforcement of foreign key constraints is normally off by default, but if this compile-time parameter is set to 1, enforcement of foreign key constraints will be on by default | -| Full Auto Vacuum | sqlite_vacuum_full | Set the default auto vacuum to full | -| Incremental Auto Vacuum | sqlite_vacuum_incr | Set the default auto vacuum to incremental | -| Full Text Search Engine | sqlite_fts5 | When this option is defined in the amalgamation, versions 5 of the full-text search engine (fts5) is added to the build automatically | -| International Components for Unicode | sqlite_icu | This option causes the International Components for Unicode or "ICU" extension to SQLite to be added to the build | -| Introspect PRAGMAS | sqlite_introspect | This option adds some extra PRAGMA statements.
  • PRAGMA function_list
  • PRAGMA module_list
  • PRAGMA pragma_list
| -| JSON SQL Functions | sqlite_json | When this option is defined in the amalgamation, the JSON SQL functions are added to the build automatically | -| Secure Delete | sqlite_secure_delete | This compile-time option changes the default setting of the secure_delete pragma.

When this option is not used, secure_delete defaults to off. When this option is present, secure_delete defaults to on.

The secure_delete setting causes deleted content to be overwritten with zeros. There is a small performance penalty since additional I/O must occur.

On the other hand, secure_delete can prevent fragments of sensitive information from lingering in unused parts of the database file after it has been deleted. See the documentation on the secure_delete pragma for additional information | -| Secure Delete (FAST) | sqlite_secure_delete_fast | For more information see [PRAGMA secure_delete](https://www.sqlite.org/pragma.html#pragma_secure_delete) | -| Tracing / Debug | sqlite_trace | Activate trace functions | -| User Authentication | sqlite_userauth | SQLite User Authentication see [User Authentication](#user-authentication) for more information. | - -# Compilation - -This package requires `CGO_ENABLED=1` ennvironment variable if not set by default, and the presence of the `gcc` compiler. - -If you need to add additional CFLAGS or LDFLAGS to the build command, and do not want to modify this package. Then this can be achieved by using the `CGO_CFLAGS` and `CGO_LDFLAGS` environment variables. - -## Android - -This package can be compiled for android. -Compile with: - -```bash -go build --tags "android" -``` - -For more information see [#201](https://github.com/mattn/go-sqlite3/issues/201) - -# ARM - -To compile for `ARM` use the following environment. - -```bash -env CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ \ - CGO_ENABLED=1 GOOS=linux GOARCH=arm GOARM=7 \ - go build -v -``` - -Additional information: -- [#242](https://github.com/mattn/go-sqlite3/issues/242) -- [#504](https://github.com/mattn/go-sqlite3/issues/504) - -# Cross Compile - -This library can be cross-compiled. - -In some cases you are required to the `CC` environment variable with the cross compiler. - -Additional information: -- [#491](https://github.com/mattn/go-sqlite3/issues/491) -- [#560](https://github.com/mattn/go-sqlite3/issues/560) - -# Google Cloud Platform - -Building on GCP is not possible because `Google Cloud Platform does not allow `gcc` to be executed. - -Please work only with compiled final binaries. - -## Linux - -To compile this package on Linux you must install the development tools for your linux distribution. - -To compile under linux use the build tag `linux`. - -```bash -go build --tags "linux" -``` - -If you wish to link directly to libsqlite3 then you can use the `libsqlite3` build tag. - -``` -go build --tags "libsqlite3 linux" -``` - -### Alpine - -When building in an `alpine` container run the following command before building. - -``` -apk add --update gcc musl-dev -``` - -### Fedora - -```bash -sudo yum groupinstall "Development Tools" "Development Libraries" -``` - -### Ubuntu - -```bash -sudo apt-get install build-essential -``` - -## Mac OSX - -OSX should have all the tools present to compile this package, if not install XCode this will add all the developers tools. - -Required dependency - -```bash -brew install sqlite3 -``` - -For OSX there is an additional package install which is required if you whish to build the `icu` extension. - -This additional package can be installed with `homebrew`. - -```bash -brew upgrade icu4c -``` - -To compile for Mac OSX. - -```bash -go build --tags "darwin" -``` - -If you wish to link directly to libsqlite3 then you can use the `libsqlite3` build tag. - -``` -go build --tags "libsqlite3 darwin" -``` - -Additional information: -- [#206](https://github.com/mattn/go-sqlite3/issues/206) -- [#404](https://github.com/mattn/go-sqlite3/issues/404) - -## Windows - -To compile this package on Windows OS you must have the `gcc` compiler installed. - -1) Install a Windows `gcc` toolchain. -2) Add the `bin` folders to the Windows path if the installer did not do this by default. -3) Open a terminal for the TDM-GCC toolchain, can be found in the Windows Start menu. -4) Navigate to your project folder and run the `go build ...` command for this package. - -For example the TDM-GCC Toolchain can be found [here](ttps://sourceforge.net/projects/tdm-gcc/). - -## Errors - -- Compile error: `can not be used when making a shared object; recompile with -fPIC` - - When receiving a compile time error referencing recompile with `-FPIC` then you - are probably using a hardend system. - - You can copile the library on a hardend system with the following command. - - ```bash - go build -ldflags '-extldflags=-fno-PIC' - ``` - - More details see [#120](https://github.com/mattn/go-sqlite3/issues/120) - -- Can't build go-sqlite3 on windows 64bit. - - > Probably, you are using go 1.0, go1.0 has a problem when it comes to compiling/linking on windows 64bit. - > See: [#27](https://github.com/mattn/go-sqlite3/issues/27) - -- `go get github.com/mattn/go-sqlite3` throws compilation error. - - `gcc` throws: `internal compiler error` - - Remove the download repository from your disk and try re-install with: - - ```bash - go install github.com/mattn/go-sqlite3 - ``` - -# User Authentication - -This package supports the SQLite User Authentication module. - -## Compile - -To use the User authentication module the package has to be compiled with the tag `sqlite_userauth`. See [Features](#features). - -## Usage - -### Create protected database - -To create a database protected by user authentication provide the following argument to the connection string `_auth`. -This will enable user authentication within the database. This option however requires two additional arguments: - -- `_auth_user` -- `_auth_pass` - -When `_auth` is present on the connection string user authentication will be enabled and the provided user will be created -as an `admin` user. After initial creation, the parameter `_auth` has no effect anymore and can be omitted from the connection string. - -Example connection string: - -Create an user authentication database with user `admin` and password `admin`. - -`file:test.s3db?_auth&_auth_user=admin&_auth_pass=admin` - -Create an user authentication database with user `admin` and password `admin` and use `SHA1` for the password encoding. - -`file:test.s3db?_auth&_auth_user=admin&_auth_pass=admin&_auth_crypt=sha1` - -### Password Encoding - -The passwords within the user authentication module of SQLite are encoded with the SQLite function `sqlite_cryp`. -This function uses a ceasar-cypher which is quite insecure. -This library provides several additional password encoders which can be configured through the connection string. - -The password cypher can be configured with the key `_auth_crypt`. And if the configured password encoder also requires an -salt this can be configured with `_auth_salt`. - -#### Available Encoders - -- SHA1 -- SSHA1 (Salted SHA1) -- SHA256 -- SSHA256 (salted SHA256) -- SHA384 -- SSHA384 (salted SHA384) -- SHA512 -- SSHA512 (salted SHA512) - -### Restrictions - -Operations on the database regarding to user management can only be preformed by an administrator user. - -### Support - -The user authentication supports two kinds of users - -- administrators -- regular users - -### User Management - -User management can be done by directly using the `*SQLiteConn` or by SQL. - -#### SQL - -The following sql functions are available for user management. - -| Function | Arguments | Description | -|----------|-----------|-------------| -| `authenticate` | username `string`, password `string` | Will authenticate an user, this is done by the connection; and should not be used manually. | -| `auth_user_add` | username `string`, password `string`, admin `int` | This function will add an user to the database.
if the database is not protected by user authentication it will enable it. Argument `admin` is an integer identifying if the added user should be an administrator. Only Administrators can add administrators. | -| `auth_user_change` | username `string`, password `string`, admin `int` | Function to modify an user. Users can change their own password, but only an administrator can change the administrator flag. | -| `authUserDelete` | username `string` | Delete an user from the database. Can only be used by an administrator. The current logged in administrator cannot be deleted. This is to make sure their is always an administrator remaining. | - -These functions will return an integer. - -- 0 (SQLITE_OK) -- 23 (SQLITE_AUTH) Failed to perform due to authentication or insufficient privileges - -##### Examples - -```sql -// Autheticate user -// Create Admin User -SELECT auth_user_add('admin2', 'admin2', 1); - -// Change password for user -SELECT auth_user_change('user', 'userpassword', 0); - -// Delete user -SELECT user_delete('user'); -``` - -#### *SQLiteConn - -The following functions are available for User authentication from the `*SQLiteConn`. - -| Function | Description | -|----------|-------------| -| `Authenticate(username, password string) error` | Authenticate user | -| `AuthUserAdd(username, password string, admin bool) error` | Add user | -| `AuthUserChange(username, password string, admin bool) error` | Modify user | -| `AuthUserDelete(username string) error` | Delete user | - -### Attached database - -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. - -## Spatialite - -Spatialite is available as an extension to SQLite, and can be used in combination with this repository. -For an example see [shaxbee/go-spatialite](https://github.com/shaxbee/go-spatialite). - -# FAQ - -- Getting insert error while query is opened. - - > You can pass some arguments into the connection string, for example, a URI. - > See: [#39](https://github.com/mattn/go-sqlite3/issues/39) - -- Do you want to cross compile? mingw on Linux or Mac? - - > See: [#106](https://github.com/mattn/go-sqlite3/issues/106) - > See also: http://www.limitlessfx.com/cross-compile-golang-app-for-windows-from-linux.html - -- Want to get time.Time with current locale - - Use `_loc=auto` in SQLite3 filename schema like `file:foo.db?_loc=auto`. - -- Can I use this in multiple routines concurrently? - - Yes for readonly. But, No for writable. See [#50](https://github.com/mattn/go-sqlite3/issues/50), [#51](https://github.com/mattn/go-sqlite3/issues/51), [#209](https://github.com/mattn/go-sqlite3/issues/209), [#274](https://github.com/mattn/go-sqlite3/issues/274). - -- Why I'm getting `no such table` error? - - Why is it racy if I use a `sql.Open("sqlite3", ":memory:")` database? - - Each connection to :memory: opens a brand new in-memory sql database, so if - the stdlib's sql engine happens to open another connection and you've only - specified ":memory:", that connection will see a brand new database. A - workaround is to use "file::memory:?mode=memory&cache=shared". Every - connection to this string will point to the same in-memory database. - - For more information see - * [#204](https://github.com/mattn/go-sqlite3/issues/204) - * [#511](https://github.com/mattn/go-sqlite3/issues/511) - -- Reading from database with large amount of goroutines fails on OSX. - - OS X limits OS-wide to not have more than 1000 files open simultaneously by default. - - For more information see [#289](https://github.com/mattn/go-sqlite3/issues/289) - -- Trying to execure a `.` (dot) command throws an error. - - Error: `Error: near ".": syntax error` - Dot command are part of SQLite3 CLI not of this library. - - You need to implement the feature or call the sqlite3 cli. - - More infomation see [#305](https://github.com/mattn/go-sqlite3/issues/305) - -- Error: `database is locked` - - When you get an database is locked. Please use the following options. - - Add to DSN: `cache=shared` - - Example: - ```go - db, err := sql.Open("sqlite3", "file:locked.sqlite?cache=shared") - ``` - - Second please set the database connections of the SQL package to 1. - - ```go - db.SetMaxOpenConn(1) - ``` - - More information see [#209](https://github.com/mattn/go-sqlite3/issues/209) - -# License - -MIT: http://mattn.mit-license.org/2018 - -sqlite3-binding.c, sqlite3-binding.h, sqlite3ext.h - -The -binding suffix was added to avoid build failures under gccgo. - -In this repository, those files are an amalgamation of code that was copied from SQLite3. The license of that code is the same as the license of SQLite3. - -# Author - -Yasuhiro Matsumoto (a.k.a mattn) - -G.J.R. Timmer diff --git a/_driver/driver_test.go b/_driver/driver_test.go deleted file mode 100644 index bbd46ba..0000000 --- a/_driver/driver_test.go +++ /dev/null @@ -1,2067 +0,0 @@ -// 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. - -package sqlite3 - -import ( - "bytes" - "database/sql" - "database/sql/driver" - "errors" - "fmt" - "io/ioutil" - "math/rand" - "net/url" - "os" - "reflect" - "regexp" - "strconv" - "strings" - "sync" - "testing" - "time" -) - -func TempFilename(t *testing.T) string { - f, err := ioutil.TempFile("", "go-sqlite3-test-") - if err != nil { - t.Fatal(err) - } - f.Close() - return f.Name() -} - -func doTestOpen(t *testing.T, option string) (string, error) { - var url string - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - if option != "" { - url = tempFilename + option - } else { - url = tempFilename - } - db, err := sql.Open("sqlite3", url) - if err != nil { - return "Failed to open database:", err - } - defer os.Remove(tempFilename) - defer db.Close() - - _, err = db.Exec("drop table foo") - _, err = db.Exec("create table foo (id integer)") - if err != nil { - return "Failed to create table:", err - } - - if stat, err := os.Stat(tempFilename); err != nil || stat.IsDir() { - return "Failed to create ./foo.db", nil - } - - return "", nil -} - -func TestOpen(t *testing.T) { - cases := map[string]bool{ - "": true, - "?txlock=immediate": true, - "?txlock=deferred": true, - "?txlock=exclusive": true, - "?txlock=bogus": false, - } - for option, expectedPass := range cases { - result, err := doTestOpen(t, option) - if result == "" { - if !expectedPass { - errmsg := fmt.Sprintf("_txlock error not caught at dbOpen with option: %s", option) - t.Fatal(errmsg) - } - } else if expectedPass { - if err == nil { - t.Fatal(result) - } else { - t.Fatal(result, err) - } - } - } -} - -func TestReadonly(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - - db1, err := sql.Open("sqlite3", "file:"+tempFilename) - if err != nil { - t.Fatal(err) - } - db1.Exec("CREATE TABLE test (x int, y float)") - - db2, err := sql.Open("sqlite3", "file:"+tempFilename+"?mode=ro") - if err != nil { - t.Fatal(err) - } - _ = db2 - _, err = db2.Exec("INSERT INTO test VALUES (1, 3.14)") - if err == nil { - t.Fatal("didn't expect INSERT into read-only database to work") - } -} - -func TestForeignKeys(t *testing.T) { - cases := map[string]bool{ - "?foreign_keys=1": true, - "?foreign_keys=0": false, - } - for option, want := range cases { - fname := TempFilename(t) - uri := "file:" + fname + option - db, err := sql.Open("sqlite3", uri) - if err != nil { - os.Remove(fname) - t.Errorf("sql.Open(\"sqlite3\", %q): %v", uri, err) - continue - } - var enabled bool - err = db.QueryRow("PRAGMA foreign_keys;").Scan(&enabled) - db.Close() - os.Remove(fname) - if err != nil { - t.Errorf("query foreign_keys for %s: %v", uri, err) - continue - } - if enabled != want { - t.Errorf("\"PRAGMA foreign_keys;\" for %q = %t; want %t", uri, enabled, want) - continue - } - } -} - -func TestRecursiveTriggers(t *testing.T) { - cases := map[string]bool{ - "?_recursive_triggers=1": true, - "?_recursive_triggers=0": false, - } - for option, want := range cases { - fname := TempFilename(t) - uri := "file:" + fname + option - db, err := sql.Open("sqlite3", uri) - if err != nil { - os.Remove(fname) - t.Errorf("sql.Open(\"sqlite3\", %q): %v", uri, err) - continue - } - var enabled bool - err = db.QueryRow("PRAGMA recursive_triggers;").Scan(&enabled) - db.Close() - os.Remove(fname) - if err != nil { - t.Errorf("query recursive_triggers for %s: %v", uri, err) - continue - } - if enabled != want { - t.Errorf("\"PRAGMA recursive_triggers;\" for %q = %t; want %t", uri, enabled, want) - continue - } - } -} - -func TestClose(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - - _, err = db.Exec("drop table foo") - _, err = db.Exec("create table foo (id integer)") - if err != nil { - t.Fatal("Failed to create table:", err) - } - - stmt, err := db.Prepare("select id from foo where id = ?") - if err != nil { - t.Fatal("Failed to select records:", err) - } - - db.Close() - _, err = stmt.Exec(1) - if err == nil { - t.Fatal("Failed to operate closed statement") - } -} - -func TestInsert(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - _, err = db.Exec("drop table foo") - _, err = db.Exec("create table foo (id integer)") - if err != nil { - t.Fatal("Failed to create table:", err) - } - - res, err := db.Exec("insert into foo(id) values(123)") - if err != nil { - t.Fatal("Failed to insert record:", err) - } - affected, _ := res.RowsAffected() - if affected != 1 { - t.Fatalf("Expected %d for affected rows, but %d:", 1, affected) - } - - rows, err := db.Query("select id from foo") - if err != nil { - t.Fatal("Failed to select records:", err) - } - defer rows.Close() - - rows.Next() - - var result int - rows.Scan(&result) - if result != 123 { - t.Errorf("Expected %d for fetched result, but %d:", 123, result) - } -} - -func TestUpsert(t *testing.T) { - _, n, _ := Version() - if !(n >= 3024000) { - t.Skip("UPSERT requires sqlite3 => 3.24.0") - } - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - _, err = db.Exec("drop table foo") - _, err = db.Exec("create table foo (name string primary key, counter integer)") - if err != nil { - t.Fatal("Failed to create table:", err) - } - - for i := 0; i < 10; i++ { - res, err := db.Exec("insert into foo(name, counter) values('key', 1) on conflict (name) do update set counter=counter+1") - if err != nil { - t.Fatal("Failed to upsert record:", err) - } - affected, _ := res.RowsAffected() - if affected != 1 { - t.Fatalf("Expected %d for affected rows, but %d:", 1, affected) - } - } - rows, err := db.Query("select name, counter from foo") - if err != nil { - t.Fatal("Failed to select records:", err) - } - defer rows.Close() - - rows.Next() - - var resultName string - var resultCounter int - rows.Scan(&resultName, &resultCounter) - if resultName != "key" { - t.Errorf("Expected %s for fetched result, but %s:", "key", resultName) - } - if resultCounter != 10 { - t.Errorf("Expected %d for fetched result, but %d:", 10, resultCounter) - } - -} - -func TestUpdate(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - _, err = db.Exec("drop table foo") - _, err = db.Exec("create table foo (id integer)") - if err != nil { - t.Fatal("Failed to create table:", err) - } - - res, err := db.Exec("insert into foo(id) values(123)") - if err != nil { - t.Fatal("Failed to insert record:", err) - } - expected, err := res.LastInsertId() - if err != nil { - t.Fatal("Failed to get LastInsertId:", err) - } - affected, _ := res.RowsAffected() - if err != nil { - t.Fatal("Failed to get RowsAffected:", err) - } - if affected != 1 { - t.Fatalf("Expected %d for affected rows, but %d:", 1, affected) - } - - res, err = db.Exec("update foo set id = 234") - if err != nil { - t.Fatal("Failed to update record:", err) - } - lastID, err := res.LastInsertId() - if err != nil { - t.Fatal("Failed to get LastInsertId:", err) - } - if expected != lastID { - t.Errorf("Expected %q for last Id, but %q:", expected, lastID) - } - affected, _ = res.RowsAffected() - if err != nil { - t.Fatal("Failed to get RowsAffected:", err) - } - if affected != 1 { - t.Fatalf("Expected %d for affected rows, but %d:", 1, affected) - } - - rows, err := db.Query("select id from foo") - if err != nil { - t.Fatal("Failed to select records:", err) - } - defer rows.Close() - - rows.Next() - - var result int - rows.Scan(&result) - if result != 234 { - t.Errorf("Expected %d for fetched result, but %d:", 234, result) - } -} - -func TestDelete(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - _, err = db.Exec("drop table foo") - _, err = db.Exec("create table foo (id integer)") - if err != nil { - t.Fatal("Failed to create table:", err) - } - - res, err := db.Exec("insert into foo(id) values(123)") - if err != nil { - t.Fatal("Failed to insert record:", err) - } - expected, err := res.LastInsertId() - if err != nil { - t.Fatal("Failed to get LastInsertId:", err) - } - affected, err := res.RowsAffected() - if err != nil { - t.Fatal("Failed to get RowsAffected:", err) - } - if affected != 1 { - t.Errorf("Expected %d for cout of affected rows, but %q:", 1, affected) - } - - res, err = db.Exec("delete from foo where id = 123") - if err != nil { - t.Fatal("Failed to delete record:", err) - } - lastID, err := res.LastInsertId() - if err != nil { - t.Fatal("Failed to get LastInsertId:", err) - } - if expected != lastID { - t.Errorf("Expected %q for last Id, but %q:", expected, lastID) - } - affected, err = res.RowsAffected() - if err != nil { - t.Fatal("Failed to get RowsAffected:", err) - } - if affected != 1 { - t.Errorf("Expected %d for cout of affected rows, but %q:", 1, affected) - } - - rows, err := db.Query("select id from foo") - if err != nil { - t.Fatal("Failed to select records:", err) - } - defer rows.Close() - - if rows.Next() { - t.Error("Fetched row but expected not rows") - } -} - -func TestBooleanRoundtrip(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - _, err = db.Exec("DROP TABLE foo") - _, err = db.Exec("CREATE TABLE foo(id INTEGER, value BOOL)") - if err != nil { - t.Fatal("Failed to create table:", err) - } - - _, err = db.Exec("INSERT INTO foo(id, value) VALUES(1, ?)", true) - if err != nil { - t.Fatal("Failed to insert true value:", err) - } - - _, err = db.Exec("INSERT INTO foo(id, value) VALUES(2, ?)", false) - if err != nil { - t.Fatal("Failed to insert false value:", err) - } - - rows, err := db.Query("SELECT id, value FROM foo") - if err != nil { - t.Fatal("Unable to query foo table:", err) - } - defer rows.Close() - - for rows.Next() { - var id int - var value bool - - if err := rows.Scan(&id, &value); err != nil { - t.Error("Unable to scan results:", err) - continue - } - - if id == 1 && !value { - t.Error("Value for id 1 should be true, not false") - - } else if id == 2 && value { - t.Error("Value for id 2 should be false, not true") - } - } -} - -func timezone(t time.Time) string { return t.Format("-07:00") } - -func TestTimestamp(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - _, err = db.Exec("DROP TABLE foo") - _, err = db.Exec("CREATE TABLE foo(id INTEGER, ts timeSTAMP, dt DATETIME)") - if err != nil { - t.Fatal("Failed to create table:", err) - } - - timestamp1 := time.Date(2012, time.April, 6, 22, 50, 0, 0, time.UTC) - timestamp2 := time.Date(2006, time.January, 2, 15, 4, 5, 123456789, time.UTC) - timestamp3 := time.Date(2012, time.November, 4, 0, 0, 0, 0, time.UTC) - tzTest := time.FixedZone("TEST", -9*3600-13*60) - tests := []struct { - value interface{} - expected time.Time - }{ - {"nonsense", time.Time{}}, - {"0000-00-00 00:00:00", time.Time{}}, - {time.Time{}.Unix(), time.Time{}}, - {timestamp1, timestamp1}, - {timestamp2.Unix(), timestamp2.Truncate(time.Second)}, - {timestamp2.UnixNano() / int64(time.Millisecond), timestamp2.Truncate(time.Millisecond)}, - {timestamp1.In(tzTest), timestamp1.In(tzTest)}, - {timestamp1.Format("2006-01-02 15:04:05.000"), timestamp1}, - {timestamp1.Format("2006-01-02T15:04:05.000"), timestamp1}, - {timestamp1.Format("2006-01-02 15:04:05"), timestamp1}, - {timestamp1.Format("2006-01-02T15:04:05"), timestamp1}, - {timestamp2, timestamp2}, - {"2006-01-02 15:04:05.123456789", timestamp2}, - {"2006-01-02T15:04:05.123456789", timestamp2}, - {"2006-01-02T05:51:05.123456789-09:13", timestamp2.In(tzTest)}, - {"2012-11-04", timestamp3}, - {"2012-11-04 00:00", timestamp3}, - {"2012-11-04 00:00:00", timestamp3}, - {"2012-11-04 00:00:00.000", timestamp3}, - {"2012-11-04T00:00", timestamp3}, - {"2012-11-04T00:00:00", timestamp3}, - {"2012-11-04T00:00:00.000", timestamp3}, - {"2006-01-02T15:04:05.123456789Z", timestamp2}, - {"2012-11-04Z", timestamp3}, - {"2012-11-04 00:00Z", timestamp3}, - {"2012-11-04 00:00:00Z", timestamp3}, - {"2012-11-04 00:00:00.000Z", timestamp3}, - {"2012-11-04T00:00Z", timestamp3}, - {"2012-11-04T00:00:00Z", timestamp3}, - {"2012-11-04T00:00:00.000Z", timestamp3}, - } - for i := range tests { - _, err = db.Exec("INSERT INTO foo(id, ts, dt) VALUES(?, ?, ?)", i, tests[i].value, tests[i].value) - if err != nil { - t.Fatal("Failed to insert timestamp:", err) - } - } - - rows, err := db.Query("SELECT id, ts, dt FROM foo ORDER BY id ASC") - if err != nil { - t.Fatal("Unable to query foo table:", err) - } - defer rows.Close() - - seen := 0 - for rows.Next() { - var id int - var ts, dt time.Time - - if err := rows.Scan(&id, &ts, &dt); err != nil { - t.Error("Unable to scan results:", err) - continue - } - if id < 0 || id >= len(tests) { - t.Error("Bad row id: ", id) - continue - } - seen++ - if !tests[id].expected.Equal(ts) { - t.Errorf("Timestamp value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, dt) - } - if !tests[id].expected.Equal(dt) { - t.Errorf("Datetime value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, dt) - } - if timezone(tests[id].expected) != timezone(ts) { - t.Errorf("Timezone for id %v (%v) should be %v, not %v", id, tests[id].value, - timezone(tests[id].expected), timezone(ts)) - } - if timezone(tests[id].expected) != timezone(dt) { - t.Errorf("Timezone for id %v (%v) should be %v, not %v", id, tests[id].value, - timezone(tests[id].expected), timezone(dt)) - } - } - - if seen != len(tests) { - t.Errorf("Expected to see %d rows", len(tests)) - } -} - -func TestBoolean(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - - defer db.Close() - - _, err = db.Exec("CREATE TABLE foo(id INTEGER, fbool BOOLEAN)") - if err != nil { - t.Fatal("Failed to create table:", err) - } - - bool1 := true - _, err = db.Exec("INSERT INTO foo(id, fbool) VALUES(1, ?)", bool1) - if err != nil { - t.Fatal("Failed to insert boolean:", err) - } - - bool2 := false - _, err = db.Exec("INSERT INTO foo(id, fbool) VALUES(2, ?)", bool2) - if err != nil { - t.Fatal("Failed to insert boolean:", err) - } - - bool3 := "nonsense" - _, err = db.Exec("INSERT INTO foo(id, fbool) VALUES(3, ?)", bool3) - if err != nil { - t.Fatal("Failed to insert nonsense:", err) - } - - rows, err := db.Query("SELECT id, fbool FROM foo where fbool = ?", bool1) - if err != nil { - t.Fatal("Unable to query foo table:", err) - } - counter := 0 - - var id int - var fbool bool - - for rows.Next() { - if err := rows.Scan(&id, &fbool); err != nil { - t.Fatal("Unable to scan results:", err) - } - counter++ - } - - if counter != 1 { - t.Fatalf("Expected 1 row but %v", counter) - } - - if id != 1 && !fbool { - t.Fatalf("Value for id 1 should be %v, not %v", bool1, fbool) - } - - rows, err = db.Query("SELECT id, fbool FROM foo where fbool = ?", bool2) - if err != nil { - t.Fatal("Unable to query foo table:", err) - } - - counter = 0 - - for rows.Next() { - if err := rows.Scan(&id, &fbool); err != nil { - t.Fatal("Unable to scan results:", err) - } - counter++ - } - - if counter != 1 { - t.Fatalf("Expected 1 row but %v", counter) - } - - if id != 2 && fbool { - t.Fatalf("Value for id 2 should be %v, not %v", bool2, fbool) - } - - // make sure "nonsense" triggered an error - rows, err = db.Query("SELECT id, fbool FROM foo where id=?;", 3) - if err != nil { - t.Fatal("Unable to query foo table:", err) - } - - rows.Next() - err = rows.Scan(&id, &fbool) - if err == nil { - t.Error("Expected error from \"nonsense\" bool") - } -} - -func TestFloat32(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - _, err = db.Exec("CREATE TABLE foo(id INTEGER)") - if err != nil { - t.Fatal("Failed to create table:", err) - } - - _, err = db.Exec("INSERT INTO foo(id) VALUES(null)") - if err != nil { - t.Fatal("Failed to insert null:", err) - } - - rows, err := db.Query("SELECT id FROM foo") - if err != nil { - t.Fatal("Unable to query foo table:", err) - } - - if !rows.Next() { - t.Fatal("Unable to query results:", err) - } - - var id interface{} - if err := rows.Scan(&id); err != nil { - t.Fatal("Unable to scan results:", err) - } - if id != nil { - t.Error("Expected nil but not") - } -} - -func TestNull(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - rows, err := db.Query("SELECT 3.141592") - if err != nil { - t.Fatal("Unable to query foo table:", err) - } - - if !rows.Next() { - t.Fatal("Unable to query results:", err) - } - - var v interface{} - if err := rows.Scan(&v); err != nil { - t.Fatal("Unable to scan results:", err) - } - f, ok := v.(float64) - if !ok { - t.Error("Expected float but not") - } - if f != 3.141592 { - t.Error("Expected 3.141592 but not") - } -} - -func TestWAL(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - if _, err = db.Exec("PRAGMA journal_mode=WAL;"); err != nil { - t.Fatal("Failed to Exec PRAGMA journal_mode:", err) - } - if _, err = db.Exec("PRAGMA locking_mode=EXCLUSIVE;"); err != nil { - t.Fatal("Failed to Exec PRAGMA locking_mode:", err) - } - if _, err = db.Exec("CREATE TABLE test (id SERIAL, user TEXT NOT NULL, name TEXT NOT NULL);"); err != nil { - t.Fatal("Failed to Exec CREATE TABLE:", err) - } - if _, err = db.Exec("INSERT INTO test (user, name) VALUES ('user','name');"); err != nil { - t.Fatal("Failed to Exec INSERT:", err) - } - - trans, err := db.Begin() - if err != nil { - t.Fatal("Failed to Begin:", err) - } - s, err := trans.Prepare("INSERT INTO test (user, name) VALUES (?, ?);") - if err != nil { - t.Fatal("Failed to Prepare:", err) - } - - var count int - if err = trans.QueryRow("SELECT count(user) FROM test;").Scan(&count); err != nil { - t.Fatal("Failed to QueryRow:", err) - } - if _, err = s.Exec("bbbb", "aaaa"); err != nil { - t.Fatal("Failed to Exec prepared statement:", err) - } - if err = s.Close(); err != nil { - t.Fatal("Failed to Close prepared statement:", err) - } - if err = trans.Commit(); err != nil { - t.Fatal("Failed to Commit:", err) - } -} - -func TestTimezoneConversion(t *testing.T) { - zones := []string{"UTC", "US/Central", "US/Pacific", "Local"} - for _, tz := range zones { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename+"?_loc="+url.QueryEscape(tz)) - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - _, err = db.Exec("DROP TABLE foo") - _, err = db.Exec("CREATE TABLE foo(id INTEGER, ts TIMESTAMP, dt DATETIME)") - if err != nil { - t.Fatal("Failed to create table:", err) - } - - loc, err := time.LoadLocation(tz) - if err != nil { - t.Fatal("Failed to load location:", err) - } - - timestamp1 := time.Date(2012, time.April, 6, 22, 50, 0, 0, time.UTC) - timestamp2 := time.Date(2006, time.January, 2, 15, 4, 5, 123456789, time.UTC) - timestamp3 := time.Date(2012, time.November, 4, 0, 0, 0, 0, time.UTC) - tests := []struct { - value interface{} - expected time.Time - }{ - {"nonsense", time.Time{}.In(loc)}, - {"0000-00-00 00:00:00", time.Time{}.In(loc)}, - {timestamp1, timestamp1.In(loc)}, - {timestamp1.Unix(), timestamp1.In(loc)}, - {timestamp1.In(time.FixedZone("TEST", -7*3600)), timestamp1.In(loc)}, - {timestamp1.Format("2006-01-02 15:04:05.000"), timestamp1.In(loc)}, - {timestamp1.Format("2006-01-02T15:04:05.000"), timestamp1.In(loc)}, - {timestamp1.Format("2006-01-02 15:04:05"), timestamp1.In(loc)}, - {timestamp1.Format("2006-01-02T15:04:05"), timestamp1.In(loc)}, - {timestamp2, timestamp2.In(loc)}, - {"2006-01-02 15:04:05.123456789", timestamp2.In(loc)}, - {"2006-01-02T15:04:05.123456789", timestamp2.In(loc)}, - {"2012-11-04", timestamp3.In(loc)}, - {"2012-11-04 00:00", timestamp3.In(loc)}, - {"2012-11-04 00:00:00", timestamp3.In(loc)}, - {"2012-11-04 00:00:00.000", timestamp3.In(loc)}, - {"2012-11-04T00:00", timestamp3.In(loc)}, - {"2012-11-04T00:00:00", timestamp3.In(loc)}, - {"2012-11-04T00:00:00.000", timestamp3.In(loc)}, - } - for i := range tests { - _, err = db.Exec("INSERT INTO foo(id, ts, dt) VALUES(?, ?, ?)", i, tests[i].value, tests[i].value) - if err != nil { - t.Fatal("Failed to insert timestamp:", err) - } - } - - rows, err := db.Query("SELECT id, ts, dt FROM foo ORDER BY id ASC") - if err != nil { - t.Fatal("Unable to query foo table:", err) - } - defer rows.Close() - - seen := 0 - for rows.Next() { - var id int - var ts, dt time.Time - - if err := rows.Scan(&id, &ts, &dt); err != nil { - t.Error("Unable to scan results:", err) - continue - } - if id < 0 || id >= len(tests) { - t.Error("Bad row id: ", id) - continue - } - seen++ - if !tests[id].expected.Equal(ts) { - t.Errorf("Timestamp value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, ts) - } - if !tests[id].expected.Equal(dt) { - t.Errorf("Datetime value for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected, dt) - } - if tests[id].expected.Location().String() != ts.Location().String() { - t.Errorf("Location for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected.Location().String(), ts.Location().String()) - } - if tests[id].expected.Location().String() != dt.Location().String() { - t.Errorf("Location for id %v (%v) should be %v, not %v", id, tests[id].value, tests[id].expected.Location().String(), dt.Location().String()) - } - } - - if seen != len(tests) { - t.Errorf("Expected to see %d rows", len(tests)) - } - } -} - -func TestExecer(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - _, err = db.Exec(` - create table foo (id integer); -- one comment - insert into foo(id) values(?); - insert into foo(id) values(?); - insert into foo(id) values(?); -- another comment - `, 1, 2, 3) - if err != nil { - t.Error("Failed to call db.Exec:", err) - } -} - -func TestQueryer(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - _, err = db.Exec(` - create table foo (id integer); - `) - if err != nil { - t.Error("Failed to call db.Query:", err) - } - - rows, err := db.Query(` - insert into foo(id) values(?); - insert into foo(id) values(?); - insert into foo(id) values(?); - select id from foo order by id; - `, 3, 2, 1) - if err != nil { - t.Error("Failed to call db.Query:", err) - } - defer rows.Close() - n := 1 - if rows != nil { - for rows.Next() { - var id int - err = rows.Scan(&id) - if err != nil { - t.Error("Failed to db.Query:", err) - } - if id != n { - t.Error("Failed to db.Query: not matched results") - } - } - } -} - -func TestStress(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - db.Exec("CREATE TABLE foo (id int);") - db.Exec("INSERT INTO foo VALUES(1);") - db.Exec("INSERT INTO foo VALUES(2);") - db.Close() - - for i := 0; i < 10000; i++ { - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - - 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) - } - for rows.Next() { - var i int - if err := rows.Scan(&i); err != nil { - t.Errorf("Scan failed: %v\n", err) - } - } - if err := rows.Err(); err != nil { - t.Errorf("Post-scan failed: %v\n", err) - } - rows.Close() - } - db.Close() - } -} - -func TestDateTimeLocal(t *testing.T) { - zone := "Asia/Tokyo" - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename+"?_loc="+zone) - if err != nil { - t.Fatal("Failed to open database:", err) - } - db.Exec("CREATE TABLE foo (dt datetime);") - db.Exec("INSERT INTO foo VALUES('2015-03-05 15:16:17');") - - row := db.QueryRow("select * from foo") - var d time.Time - err = row.Scan(&d) - if err != nil { - t.Fatal("Failed to scan datetime:", err) - } - if d.Hour() == 15 || !strings.Contains(d.String(), "JST") { - t.Fatal("Result should have timezone", d) - } - db.Close() - - db, err = sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - - row = db.QueryRow("select * from foo") - err = row.Scan(&d) - if err != nil { - t.Fatal("Failed to scan datetime:", err) - } - if d.UTC().Hour() != 15 || !strings.Contains(d.String(), "UTC") { - t.Fatalf("Result should not have timezone %v %v", zone, d.String()) - } - - _, err = db.Exec("DELETE FROM foo") - if err != nil { - t.Fatal("Failed to delete table:", err) - } - dt, err := time.Parse("2006/1/2 15/4/5 -0700 MST", "2015/3/5 15/16/17 +0900 JST") - if err != nil { - t.Fatal("Failed to parse datetime:", err) - } - db.Exec("INSERT INTO foo VALUES(?);", dt) - - db.Close() - db, err = sql.Open("sqlite3", tempFilename+"?_loc="+zone) - if err != nil { - t.Fatal("Failed to open database:", err) - } - - row = db.QueryRow("select * from foo") - err = row.Scan(&d) - if err != nil { - t.Fatal("Failed to scan datetime:", err) - } - if d.Hour() != 15 || !strings.Contains(d.String(), "JST") { - t.Fatalf("Result should have timezone %v %v", zone, d.String()) - } -} - -func TestStringContainingZero(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - _, err = db.Exec(` - create table foo (id integer, name, extra text); - `) - if err != nil { - t.Error("Failed to call db.Query:", err) - } - - const text = "foo\x00bar" - - _, err = db.Exec(`insert into foo(id, name, extra) values($1, $2, $2)`, 1, text) - if err != nil { - t.Error("Failed to call db.Exec:", err) - } - - row := db.QueryRow(`select id, extra from foo where id = $1 and extra = $2`, 1, text) - if row == nil { - t.Error("Failed to call db.QueryRow") - } - - var id int - var extra string - err = row.Scan(&id, &extra) - if err != nil { - t.Error("Failed to db.Scan:", err) - } - if id != 1 || extra != text { - t.Error("Failed to db.QueryRow: not matched results") - } -} - -const CurrentTimeStamp = "2006-01-02 15:04:05" - -type TimeStamp struct{ *time.Time } - -func (t TimeStamp) Scan(value interface{}) error { - var err error - switch v := value.(type) { - case string: - *t.Time, err = time.Parse(CurrentTimeStamp, v) - case []byte: - *t.Time, err = time.Parse(CurrentTimeStamp, string(v)) - default: - err = errors.New("invalid type for current_timestamp") - } - return err -} - -func (t TimeStamp) Value() (driver.Value, error) { - return t.Time.Format(CurrentTimeStamp), nil -} - -func TestDateTimeNow(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - db, err := sql.Open("sqlite3", tempFilename) - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - var d time.Time - err = db.QueryRow("SELECT datetime('now')").Scan(TimeStamp{&d}) - if err != nil { - t.Fatal("Failed to scan datetime:", err) - } -} - -func TestFunctionRegistration(t *testing.T) { - addi8_16_32 := func(a int8, b int16) int32 { return int32(a) + int32(b) } - addi64 := func(a, b int64) int64 { return a + b } - addu8_16_32 := func(a uint8, b uint16) uint32 { return uint32(a) + uint32(b) } - addu64 := func(a, b uint64) uint64 { return a + b } - addiu := func(a int, b uint) int64 { return int64(a) + int64(b) } - addf32_64 := func(a float32, b float64) float64 { return float64(a) + b } - not := func(a bool) bool { return !a } - regex := func(re, s string) (bool, error) { - return regexp.MatchString(re, s) - } - generic := func(a interface{}) int64 { - switch a.(type) { - case int64: - return 1 - case float64: - return 2 - case []byte: - return 3 - case string: - return 4 - default: - panic("unreachable") - } - } - variadic := func(a, b int64, c ...int64) int64 { - ret := a + b - for _, d := range c { - ret += d - } - return ret - } - variadicGeneric := func(a ...interface{}) int64 { - return int64(len(a)) - } - - sql.Register("sqlite3_FunctionRegistration", &SQLiteDriver{ - ConnectHook: func(conn *SQLiteConn) error { - if err := conn.RegisterFunc("addi8_16_32", addi8_16_32, true); err != nil { - return err - } - if err := conn.RegisterFunc("addi64", addi64, true); err != nil { - return err - } - if err := conn.RegisterFunc("addu8_16_32", addu8_16_32, true); err != nil { - return err - } - if err := conn.RegisterFunc("addu64", addu64, true); err != nil { - return err - } - if err := conn.RegisterFunc("addiu", addiu, true); err != nil { - return err - } - if err := conn.RegisterFunc("addf32_64", addf32_64, true); err != nil { - return err - } - if err := conn.RegisterFunc("not", not, true); err != nil { - return err - } - if err := conn.RegisterFunc("regex", regex, true); err != nil { - return err - } - if err := conn.RegisterFunc("generic", generic, true); err != nil { - return err - } - if err := conn.RegisterFunc("variadic", variadic, true); err != nil { - return err - } - if err := conn.RegisterFunc("variadicGeneric", variadicGeneric, true); err != nil { - return err - } - return nil - }, - }) - db, err := sql.Open("sqlite3_FunctionRegistration", ":memory:") - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - ops := []struct { - query string - expected interface{} - }{ - {"SELECT addi8_16_32(1,2)", int32(3)}, - {"SELECT addi64(1,2)", int64(3)}, - {"SELECT addu8_16_32(1,2)", uint32(3)}, - {"SELECT addu64(1,2)", uint64(3)}, - {"SELECT addiu(1,2)", int64(3)}, - {"SELECT addf32_64(1.5,1.5)", float64(3)}, - {"SELECT not(1)", false}, - {"SELECT not(0)", true}, - {`SELECT regex("^foo.*", "foobar")`, true}, - {`SELECT regex("^foo.*", "barfoobar")`, false}, - {"SELECT generic(1)", int64(1)}, - {"SELECT generic(1.1)", int64(2)}, - {`SELECT generic(NULL)`, int64(3)}, - {`SELECT generic("foo")`, int64(4)}, - {"SELECT variadic(1,2)", int64(3)}, - {"SELECT variadic(1,2,3,4)", int64(10)}, - {"SELECT variadic(1,1,1,1,1,1,1,1,1,1)", int64(10)}, - {`SELECT variadicGeneric(1,"foo",2.3, NULL)`, int64(4)}, - } - - for _, op := range ops { - ret := reflect.New(reflect.TypeOf(op.expected)) - err = db.QueryRow(op.query).Scan(ret.Interface()) - if err != nil { - t.Errorf("Query %q failed: %s", op.query, err) - } else if !reflect.DeepEqual(ret.Elem().Interface(), op.expected) { - t.Errorf("Query %q returned wrong value: got %v (%T), want %v (%T)", op.query, ret.Elem().Interface(), ret.Elem().Interface(), op.expected, op.expected) - } - } -} - -type sumAggregator int64 - -func (s *sumAggregator) Step(x int64) { - *s += sumAggregator(x) -} - -func (s *sumAggregator) Done() int64 { - return int64(*s) -} - -func TestAggregatorRegistration(t *testing.T) { - customSum := func() *sumAggregator { - var ret sumAggregator - return &ret - } - - sql.Register("sqlite3_AggregatorRegistration", &SQLiteDriver{ - ConnectHook: func(conn *SQLiteConn) error { - if err := conn.RegisterAggregator("customSum", customSum, true); err != nil { - return err - } - return nil - }, - }) - db, err := sql.Open("sqlite3_AggregatorRegistration", ":memory:") - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - _, err = db.Exec("create table foo (department integer, profits integer)") - if err != nil { - // trace feature is not implemented - t.Skip("Failed to create table:", err) - } - - _, err = db.Exec("insert into foo values (1, 10), (1, 20), (2, 42)") - if err != nil { - t.Fatal("Failed to insert records:", err) - } - - tests := []struct { - dept, sum int64 - }{ - {1, 30}, - {2, 42}, - } - - for _, test := range tests { - var ret int64 - err = db.QueryRow("select customSum(profits) from foo where department = $1 group by department", test.dept).Scan(&ret) - if err != nil { - t.Fatal("Query failed:", err) - } - if ret != test.sum { - t.Fatalf("Custom sum returned wrong value, got %d, want %d", ret, test.sum) - } - } -} - -func rot13(r rune) rune { - switch { - case r >= 'A' && r <= 'Z': - return 'A' + (r-'A'+13)%26 - case r >= 'a' && r <= 'z': - return 'a' + (r-'a'+13)%26 - } - return r -} - -func TestCollationRegistration(t *testing.T) { - collateRot13 := func(a, b string) int { - ra, rb := strings.Map(rot13, a), strings.Map(rot13, b) - return strings.Compare(ra, rb) - } - collateRot13Reverse := func(a, b string) int { - return collateRot13(b, a) - } - - sql.Register("sqlite3_CollationRegistration", &SQLiteDriver{ - ConnectHook: func(conn *SQLiteConn) error { - if err := conn.RegisterCollation("rot13", collateRot13); err != nil { - return err - } - if err := conn.RegisterCollation("rot13reverse", collateRot13Reverse); err != nil { - return err - } - return nil - }, - }) - - db, err := sql.Open("sqlite3_CollationRegistration", ":memory:") - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - populate := []string{ - `CREATE TABLE test (s TEXT)`, - `INSERT INTO test VALUES ("aaaa")`, - `INSERT INTO test VALUES ("ffff")`, - `INSERT INTO test VALUES ("qqqq")`, - `INSERT INTO test VALUES ("tttt")`, - `INSERT INTO test VALUES ("zzzz")`, - } - for _, stmt := range populate { - if _, err := db.Exec(stmt); err != nil { - t.Fatal("Failed to populate test DB:", err) - } - } - - ops := []struct { - query string - want []string - }{ - { - "SELECT * FROM test ORDER BY s COLLATE rot13 ASC", - []string{ - "qqqq", - "tttt", - "zzzz", - "aaaa", - "ffff", - }, - }, - { - "SELECT * FROM test ORDER BY s COLLATE rot13 DESC", - []string{ - "ffff", - "aaaa", - "zzzz", - "tttt", - "qqqq", - }, - }, - { - "SELECT * FROM test ORDER BY s COLLATE rot13reverse ASC", - []string{ - "ffff", - "aaaa", - "zzzz", - "tttt", - "qqqq", - }, - }, - { - "SELECT * FROM test ORDER BY s COLLATE rot13reverse DESC", - []string{ - "qqqq", - "tttt", - "zzzz", - "aaaa", - "ffff", - }, - }, - } - - for _, op := range ops { - rows, err := db.Query(op.query) - if err != nil { - t.Fatalf("Query %q failed: %s", op.query, err) - } - got := []string{} - defer rows.Close() - for rows.Next() { - var s string - if err = rows.Scan(&s); err != nil { - t.Fatalf("Reading row for %q: %s", op.query, err) - } - got = append(got, s) - } - if err = rows.Err(); err != nil { - t.Fatalf("Reading rows for %q: %s", op.query, err) - } - - if !reflect.DeepEqual(got, op.want) { - t.Fatalf("Unexpected output from %q\ngot:\n%s\n\nwant:\n%s", op.query, strings.Join(got, "\n"), strings.Join(op.want, "\n")) - } - } -} - -func TestDeclTypes(t *testing.T) { - - d := SQLiteDriver{} - - conn, err := d.Open(":memory:") - if err != nil { - t.Fatal("Failed to begin transaction:", err) - } - defer conn.Close() - - sqlite3conn := conn.(*SQLiteConn) - - _, err = sqlite3conn.Exec("create table foo (id integer not null primary key, name text)", nil) - if err != nil { - t.Fatal("Failed to create table:", err) - } - - _, err = sqlite3conn.Exec("insert into foo(name) values(\"bar\")", nil) - if err != nil { - t.Fatal("Failed to insert:", err) - } - - rs, err := sqlite3conn.Query("select * from foo", nil) - if err != nil { - t.Fatal("Failed to select:", err) - } - defer rs.Close() - - declTypes := rs.(*SQLiteRows).DeclTypes() - - if !reflect.DeepEqual(declTypes, []string{"integer", "text"}) { - t.Fatal("Unexpected declTypes:", declTypes) - } -} - -func TestUpdateAndTransactionHooks(t *testing.T) { - var events []string - var commitHookReturn = 0 - - sql.Register("sqlite3_UpdateHook", &SQLiteDriver{ - ConnectHook: func(conn *SQLiteConn) error { - conn.RegisterCommitHook(func() int { - events = append(events, "commit") - return commitHookReturn - }) - conn.RegisterRollbackHook(func() { - events = append(events, "rollback") - }) - conn.RegisterUpdateHook(func(op int, db string, table string, rowid int64) { - events = append(events, fmt.Sprintf("update(op=%v db=%v table=%v rowid=%v)", op, db, table, rowid)) - }) - return nil - }, - }) - db, err := sql.Open("sqlite3_UpdateHook", ":memory:") - if err != nil { - t.Fatal("Failed to open database:", err) - } - defer db.Close() - - statements := []string{ - "create table foo (id integer primary key)", - "insert into foo values (9)", - "update foo set id = 99 where id = 9", - "delete from foo where id = 99", - } - for _, statement := range statements { - _, err = db.Exec(statement) - if err != nil { - t.Fatalf("Unable to prepare test data [%v]: %v", statement, err) - } - } - - commitHookReturn = 1 - _, err = db.Exec("insert into foo values (5)") - if err == nil { - t.Error("Commit hook failed to rollback transaction") - } - - var expected = []string{ - "commit", - fmt.Sprintf("update(op=%v db=main table=foo rowid=9)", SQLITE_INSERT), - "commit", - fmt.Sprintf("update(op=%v db=main table=foo rowid=99)", SQLITE_UPDATE), - "commit", - fmt.Sprintf("update(op=%v db=main table=foo rowid=99)", SQLITE_DELETE), - "commit", - fmt.Sprintf("update(op=%v db=main table=foo rowid=5)", SQLITE_INSERT), - "commit", - "rollback", - } - if !reflect.DeepEqual(events, expected) { - t.Errorf("Expected notifications %v but got %v", expected, events) - } -} - -func TestNilAndEmptyBytes(t *testing.T) { - db, err := sql.Open("sqlite3", ":memory:") - if err != nil { - t.Fatal(err) - } - defer db.Close() - actualNil := []byte("use this to use an actual nil not a reference to nil") - emptyBytes := []byte{} - for tsti, tst := range []struct { - name string - columnType string - insertBytes []byte - expectedBytes []byte - }{ - {"actual nil blob", "blob", actualNil, nil}, - {"referenced nil blob", "blob", nil, nil}, - {"empty blob", "blob", emptyBytes, emptyBytes}, - {"actual nil text", "text", actualNil, nil}, - {"referenced nil text", "text", nil, nil}, - {"empty text", "text", emptyBytes, emptyBytes}, - } { - if _, err = db.Exec(fmt.Sprintf("create table tbl%d (txt %s)", tsti, tst.columnType)); err != nil { - t.Fatal(tst.name, err) - } - if bytes.Equal(tst.insertBytes, actualNil) { - if _, err = db.Exec(fmt.Sprintf("insert into tbl%d (txt) values (?)", tsti), nil); err != nil { - t.Fatal(tst.name, err) - } - } else { - if _, err = db.Exec(fmt.Sprintf("insert into tbl%d (txt) values (?)", tsti), &tst.insertBytes); err != nil { - t.Fatal(tst.name, err) - } - } - rows, err := db.Query(fmt.Sprintf("select txt from tbl%d", tsti)) - if err != nil { - t.Fatal(tst.name, err) - } - if !rows.Next() { - t.Fatal(tst.name, "no rows") - } - var scanBytes []byte - if err = rows.Scan(&scanBytes); err != nil { - t.Fatal(tst.name, err) - } - if err = rows.Err(); err != nil { - t.Fatal(tst.name, err) - } - if tst.expectedBytes == nil && scanBytes != nil { - t.Errorf("%s: %#v != %#v", tst.name, scanBytes, tst.expectedBytes) - } else if !bytes.Equal(scanBytes, tst.expectedBytes) { - t.Errorf("%s: %#v != %#v", tst.name, scanBytes, tst.expectedBytes) - } - } -} - -func TestInsertNilByteSlice(t *testing.T) { - db, err := sql.Open("sqlite3", ":memory:") - if err != nil { - t.Fatal(err) - } - defer db.Close() - if _, err := db.Exec("create table blob_not_null (b blob not null)"); err != nil { - t.Fatal(err) - } - var nilSlice []byte - if _, err := db.Exec("insert into blob_not_null (b) values (?)", nilSlice); err == nil { - t.Fatal("didn't expect INSERT to 'not null' column with a nil []byte slice to work") - } - zeroLenSlice := []byte{} - if _, err := db.Exec("insert into blob_not_null (b) values (?)", zeroLenSlice); err != nil { - t.Fatal("failed to insert zero-length slice") - } -} - -var customFunctionOnce sync.Once - -func BenchmarkCustomFunctions(b *testing.B) { - customFunctionOnce.Do(func() { - customAdd := func(a, b int64) int64 { - return a + b - } - - sql.Register("sqlite3_BenchmarkCustomFunctions", &SQLiteDriver{ - ConnectHook: func(conn *SQLiteConn) error { - // Impure function to force sqlite to reexecute it each time. - return conn.RegisterFunc("custom_add", customAdd, false) - }, - }) - }) - - db, err := sql.Open("sqlite3_BenchmarkCustomFunctions", ":memory:") - if err != nil { - b.Fatal("Failed to open database:", err) - } - defer db.Close() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - var i int64 - err = db.QueryRow("SELECT custom_add(1,2)").Scan(&i) - if err != nil { - b.Fatal("Failed to run custom add:", err) - } - } -} - -func TestSuite(t *testing.T) { - tempFilename := TempFilename(t) - defer os.Remove(tempFilename) - d, err := sql.Open("sqlite3", tempFilename+"?_busy_timeout=99999") - if err != nil { - t.Fatal(err) - } - defer d.Close() - - db = &TestDB{t, d, SQLITE, sync.Once{}} - testing.RunTests(func(string, string) (bool, error) { return true, nil }, tests) - - if !testing.Short() { - for _, b := range benchmarks { - fmt.Printf("%-20s", b.Name) - r := testing.Benchmark(b.F) - fmt.Printf("%10d %10.0f req/s\n", r.N, float64(r.N)/r.T.Seconds()) - } - } - db.tearDown() -} - -// Dialect is a type of dialect of databases. -type Dialect int - -// Dialects for databases. -const ( - SQLITE Dialect = iota // SQLITE mean SQLite3 dialect - POSTGRESQL // POSTGRESQL mean PostgreSQL dialect - MYSQL // MYSQL mean MySQL dialect -) - -// DB provide context for the tests -type TestDB struct { - *testing.T - *sql.DB - dialect Dialect - once sync.Once -} - -var db *TestDB - -// the following tables will be created and dropped during the test -var testTables = []string{"foo", "bar", "t", "bench"} - -var tests = []testing.InternalTest{ - {Name: "TestResult", F: testResult}, - {Name: "TestBlobs", F: testBlobs}, - {Name: "TestMultiBlobs", F: testMultiBlobs}, - {Name: "TestManyQueryRow", F: testManyQueryRow}, - {Name: "TestTxQuery", F: testTxQuery}, - {Name: "TestPreparedStmt", F: testPreparedStmt}, -} - -var benchmarks = []testing.InternalBenchmark{ - {Name: "BenchmarkExec", F: benchmarkExec}, - {Name: "BenchmarkQuery", F: benchmarkQuery}, - {Name: "BenchmarkParams", F: benchmarkParams}, - {Name: "BenchmarkStmt", F: benchmarkStmt}, - {Name: "BenchmarkRows", F: benchmarkRows}, - {Name: "BenchmarkStmtRows", F: benchmarkStmtRows}, -} - -func (db *TestDB) mustExec(sql string, args ...interface{}) sql.Result { - res, err := db.Exec(sql, args...) - if err != nil { - db.Fatalf("Error running %q: %v", sql, err) - } - return res -} - -func (db *TestDB) tearDown() { - for _, tbl := range testTables { - switch db.dialect { - case SQLITE: - db.mustExec("drop table if exists " + tbl) - case MYSQL, POSTGRESQL: - db.mustExec("drop table if exists " + tbl) - default: - db.Fatal("unknown dialect") - } - } -} - -// q replaces ? parameters if needed -func (db *TestDB) q(sql string) string { - switch db.dialect { - case POSTGRESQL: // replace with $1, $2, .. - qrx := regexp.MustCompile(`\?`) - n := 0 - return qrx.ReplaceAllStringFunc(sql, func(string) string { - n++ - return "$" + strconv.Itoa(n) - }) - } - return sql -} - -func (db *TestDB) blobType(size int) string { - switch db.dialect { - case SQLITE: - return fmt.Sprintf("blob[%d]", size) - case POSTGRESQL: - return "bytea" - case MYSQL: - return fmt.Sprintf("VARBINARY(%d)", size) - } - panic("unknown dialect") -} - -func (db *TestDB) serialPK() string { - switch db.dialect { - case SQLITE: - return "integer primary key autoincrement" - case POSTGRESQL: - return "serial primary key" - case MYSQL: - return "integer primary key auto_increment" - } - panic("unknown dialect") -} - -func (db *TestDB) now() string { - switch db.dialect { - case SQLITE: - return "datetime('now')" - case POSTGRESQL: - return "now()" - case MYSQL: - return "now()" - } - panic("unknown dialect") -} - -func makeBench() { - if _, err := db.Exec("create table bench (n varchar(32), i integer, d double, s varchar(32), t datetime)"); err != nil { - panic(err) - } - st, err := db.Prepare("insert into bench values (?, ?, ?, ?, ?)") - if err != nil { - panic(err) - } - defer st.Close() - for i := 0; i < 100; i++ { - if _, err = st.Exec(nil, i, float64(i), fmt.Sprintf("%d", i), time.Now()); err != nil { - panic(err) - } - } -} - -// testResult is test for result -func testResult(t *testing.T) { - db.tearDown() - db.mustExec("create temporary table test (id " + db.serialPK() + ", name varchar(10))") - - for i := 1; i < 3; i++ { - r := db.mustExec(db.q("insert into test (name) values (?)"), fmt.Sprintf("row %d", i)) - n, err := r.RowsAffected() - if err != nil { - t.Fatal(err) - } - if n != 1 { - t.Errorf("got %v, want %v", n, 1) - } - n, err = r.LastInsertId() - if err != nil { - t.Fatal(err) - } - if n != int64(i) { - t.Errorf("got %v, want %v", n, i) - } - } - if _, err := db.Exec("error!"); err == nil { - t.Fatalf("expected error") - } -} - -// testBlobs is test for blobs -func testBlobs(t *testing.T) { - db.tearDown() - var blob = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} - db.mustExec("create table foo (id integer primary key, bar " + db.blobType(16) + ")") - db.mustExec(db.q("insert into foo (id, bar) values(?,?)"), 0, blob) - - want := fmt.Sprintf("%x", blob) - - b := make([]byte, 16) - err := db.QueryRow(db.q("select bar from foo where id = ?"), 0).Scan(&b) - got := fmt.Sprintf("%x", b) - if err != nil { - t.Errorf("[]byte scan: %v", err) - } else if got != want { - t.Errorf("for []byte, got %q; want %q", got, want) - } - - err = db.QueryRow(db.q("select bar from foo where id = ?"), 0).Scan(&got) - want = string(blob) - if err != nil { - t.Errorf("string scan: %v", err) - } else if got != want { - t.Errorf("for string, got %q; want %q", got, want) - } -} - -func testMultiBlobs(t *testing.T) { - db.tearDown() - db.mustExec("create table foo (id integer primary key, bar " + db.blobType(16) + ")") - var blob0 = []byte{0, 1, 2, 3, 4, 5, 6, 7} - db.mustExec(db.q("insert into foo (id, bar) values(?,?)"), 0, blob0) - var blob1 = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} - db.mustExec(db.q("insert into foo (id, bar) values(?,?)"), 1, blob1) - - r, err := db.Query(db.q("select bar from foo order by id")) - if err != nil { - t.Fatal(err) - } - defer r.Close() - if !r.Next() { - if r.Err() != nil { - t.Fatal(err) - } - t.Fatal("expected one rows") - } - - want0 := fmt.Sprintf("%x", blob0) - b0 := make([]byte, 8) - err = r.Scan(&b0) - if err != nil { - t.Fatal(err) - } - got0 := fmt.Sprintf("%x", b0) - - if !r.Next() { - if r.Err() != nil { - t.Fatal(err) - } - t.Fatal("expected one rows") - } - - want1 := fmt.Sprintf("%x", blob1) - b1 := make([]byte, 16) - err = r.Scan(&b1) - if err != nil { - t.Fatal(err) - } - got1 := fmt.Sprintf("%x", b1) - if got0 != want0 { - t.Errorf("for []byte, got %q; want %q", got0, want0) - } - if got1 != want1 { - t.Errorf("for []byte, got %q; want %q", got1, want1) - } -} - -// testManyQueryRow is test for many query row -func testManyQueryRow(t *testing.T) { - if testing.Short() { - t.Log("skipping in short mode") - return - } - db.tearDown() - db.mustExec("create table foo (id integer primary key, name varchar(50))") - db.mustExec(db.q("insert into foo (id, name) values(?,?)"), 1, "bob") - var name string - for i := 0; i < 10000; i++ { - err := db.QueryRow(db.q("select name from foo where id = ?"), 1).Scan(&name) - if err != nil || name != "bob" { - t.Fatalf("on query %d: err=%v, name=%q", i, err, name) - } - } -} - -// testTxQuery is test for transactional query -func testTxQuery(t *testing.T) { - db.tearDown() - tx, err := db.Begin() - if err != nil { - t.Fatal(err) - } - defer tx.Rollback() - - _, err = tx.Exec("create table foo (id integer primary key, name varchar(50))") - if err != nil { - t.Fatal(err) - } - - _, err = tx.Exec(db.q("insert into foo (id, name) values(?,?)"), 1, "bob") - if err != nil { - t.Fatal(err) - } - - r, err := tx.Query(db.q("select name from foo where id = ?"), 1) - if err != nil { - t.Fatal(err) - } - defer r.Close() - - if !r.Next() { - if r.Err() != nil { - t.Fatal(err) - } - t.Fatal("expected one rows") - } - - var name string - err = r.Scan(&name) - if err != nil { - t.Fatal(err) - } -} - -// testPreparedStmt is test for prepared statement -func testPreparedStmt(t *testing.T) { - db.tearDown() - db.mustExec("CREATE TABLE t (count INT)") - sel, err := db.Prepare("SELECT count FROM t ORDER BY count DESC") - if err != nil { - t.Fatalf("prepare 1: %v", err) - } - ins, err := db.Prepare(db.q("INSERT INTO t (count) VALUES (?)")) - if err != nil { - t.Fatalf("prepare 2: %v", err) - } - - for n := 1; n <= 3; n++ { - if _, err := ins.Exec(n); err != nil { - t.Fatalf("insert(%d) = %v", n, err) - } - } - - const nRuns = 10 - var wg sync.WaitGroup - for i := 0; i < nRuns; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for j := 0; j < 10; j++ { - count := 0 - if err := sel.QueryRow().Scan(&count); err != nil && err != sql.ErrNoRows { - t.Errorf("Query: %v", err) - return - } - if _, err := ins.Exec(rand.Intn(100)); err != nil { - t.Errorf("Insert: %v", err) - return - } - } - }() - } - wg.Wait() -} - -// Benchmarks need to use panic() since b.Error errors are lost when -// running via testing.Benchmark() I would like to run these via go -// test -bench but calling Benchmark() from a benchmark test -// currently hangs go. - -// benchmarkExec is benchmark for exec -func benchmarkExec(b *testing.B) { - for i := 0; i < b.N; i++ { - if _, err := db.Exec("select 1"); err != nil { - panic(err) - } - } -} - -// benchmarkQuery is benchmark for query -func benchmarkQuery(b *testing.B) { - for i := 0; i < b.N; i++ { - var n sql.NullString - var i int - var f float64 - var s string - // var t time.Time - if err := db.QueryRow("select null, 1, 1.1, 'foo'").Scan(&n, &i, &f, &s); err != nil { - panic(err) - } - } -} - -// benchmarkParams is benchmark for params -func benchmarkParams(b *testing.B) { - for i := 0; i < b.N; i++ { - var n sql.NullString - var i int - var f float64 - var s string - // var t time.Time - if err := db.QueryRow("select ?, ?, ?, ?", nil, 1, 1.1, "foo").Scan(&n, &i, &f, &s); err != nil { - panic(err) - } - } -} - -// benchmarkStmt is benchmark for statement -func benchmarkStmt(b *testing.B) { - st, err := db.Prepare("select ?, ?, ?, ?") - if err != nil { - panic(err) - } - defer st.Close() - - for n := 0; n < b.N; n++ { - var n sql.NullString - var i int - var f float64 - var s string - // var t time.Time - if err := st.QueryRow(nil, 1, 1.1, "foo").Scan(&n, &i, &f, &s); err != nil { - panic(err) - } - } -} - -// benchmarkRows is benchmark for rows -func benchmarkRows(b *testing.B) { - db.once.Do(makeBench) - - for n := 0; n < b.N; n++ { - var n sql.NullString - var i int - var f float64 - var s string - var t time.Time - r, err := db.Query("select * from bench") - if err != nil { - panic(err) - } - for r.Next() { - if err = r.Scan(&n, &i, &f, &s, &t); err != nil { - panic(err) - } - } - if err = r.Err(); err != nil { - panic(err) - } - } -} - -// benchmarkStmtRows is benchmark for statement rows -func benchmarkStmtRows(b *testing.B) { - db.once.Do(makeBench) - - st, err := db.Prepare("select * from bench") - if err != nil { - panic(err) - } - defer st.Close() - - for n := 0; n < b.N; n++ { - var n sql.NullString - var i int - var f float64 - var s string - var t time.Time - r, err := st.Query() - if err != nil { - panic(err) - } - for r.Next() { - if err = r.Scan(&n, &i, &f, &s, &t); err != nil { - panic(err) - } - } - if err = r.Err(); err != nil { - panic(err) - } - } -} diff --git a/_driver/sqlite3.go b/_driver/sqlite3.go deleted file mode 100644 index 6dfa135..0000000 --- a/_driver/sqlite3.go +++ /dev/null @@ -1,1992 +0,0 @@ -// Copyright (C) 2014 Yasuhiro Matsumoto . -// Copyright (C) 2018 G.J.R. Timmer . -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -// +build cgo - -package sqlite3 - -/* -#cgo CFLAGS: -std=gnu99 -#cgo CFLAGS: -DSQLITE_ENABLE_RTREE -#cgo CFLAGS: -DSQLITE_THREADSAFE=1 -#cgo CFLAGS: -DHAVE_USLEEP=1 -#cgo CFLAGS: -DSQLITE_ENABLE_FTS3 -#cgo CFLAGS: -DSQLITE_ENABLE_FTS3_PARENTHESIS -#cgo CFLAGS: -DSQLITE_ENABLE_FTS4_UNICODE61 -#cgo CFLAGS: -DSQLITE_TRACE_SIZE_LIMIT=15 -#cgo CFLAGS: -DSQLITE_OMIT_DEPRECATED -#cgo CFLAGS: -DSQLITE_DISABLE_INTRINSIC -#cgo CFLAGS: -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -#cgo CFLAGS: -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT -#cgo CFLAGS: -Wno-deprecated-declarations -#cgo linux,!android CFLAGS: -DHAVE_PREAD64=1 -DHAVE_PWRITE64=1 - -#ifndef USE_LIBSQLITE3 -#include -#else -#include -#endif -#include -#include - -#ifdef __CYGWIN__ -# include -#endif - -#ifndef SQLITE_OPEN_READWRITE -# define SQLITE_OPEN_READWRITE 0 -#endif - -#ifndef SQLITE_OPEN_FULLMUTEX -# define SQLITE_OPEN_FULLMUTEX 0 -#endif - -#ifndef SQLITE_DETERMINISTIC -# define SQLITE_DETERMINISTIC 0 -#endif - -static int -_sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs) { -#ifdef SQLITE_OPEN_URI - return sqlite3_open_v2(filename, ppDb, flags | SQLITE_OPEN_URI, zVfs); -#else - return sqlite3_open_v2(filename, ppDb, flags, zVfs); -#endif -} - -static int -_sqlite3_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) { - return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT); -} - -static int -_sqlite3_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) { - return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT); -} - -#include -#include - -static int -_sqlite3_exec(sqlite3* db, const char* pcmd, long long* rowid, long long* changes) -{ - int rv = sqlite3_exec(db, pcmd, 0, 0, 0); - *rowid = (long long) sqlite3_last_insert_rowid(db); - *changes = (long long) sqlite3_changes(db); - return rv; -} - -static int -_sqlite3_step(sqlite3_stmt* stmt, long long* rowid, long long* changes) -{ - int rv = sqlite3_step(stmt); - sqlite3* db = sqlite3_db_handle(stmt); - *rowid = (long long) sqlite3_last_insert_rowid(db); - *changes = (long long) sqlite3_changes(db); - return rv; -} - -void _sqlite3_result_text(sqlite3_context* ctx, const char* s) { - sqlite3_result_text(ctx, s, -1, &free); -} - -void _sqlite3_result_blob(sqlite3_context* ctx, const void* b, int l) { - sqlite3_result_blob(ctx, b, l, SQLITE_TRANSIENT); -} - - -int _sqlite3_create_function( - sqlite3 *db, - const char *zFunctionName, - int nArg, - int eTextRep, - uintptr_t pApp, - void (*xFunc)(sqlite3_context*,int,sqlite3_value**), - void (*xStep)(sqlite3_context*,int,sqlite3_value**), - void (*xFinal)(sqlite3_context*) -) { - return sqlite3_create_function(db, zFunctionName, nArg, eTextRep, (void*) pApp, xFunc, xStep, xFinal); -} - -void callbackTrampoline(sqlite3_context*, int, sqlite3_value**); -void stepTrampoline(sqlite3_context*, int, sqlite3_value**); -void doneTrampoline(sqlite3_context*); - -int compareTrampoline(void*, int, char*, int, char*); -int commitHookTrampoline(void*); -void rollbackHookTrampoline(void*); -void updateHookTrampoline(void*, int, char*, char*, sqlite3_int64); - -#ifdef SQLITE_LIMIT_WORKER_THREADS -# define _SQLITE_HAS_LIMIT -# define SQLITE_LIMIT_LENGTH 0 -# define SQLITE_LIMIT_SQL_LENGTH 1 -# define SQLITE_LIMIT_COLUMN 2 -# define SQLITE_LIMIT_EXPR_DEPTH 3 -# define SQLITE_LIMIT_COMPOUND_SELECT 4 -# define SQLITE_LIMIT_VDBE_OP 5 -# define SQLITE_LIMIT_FUNCTION_ARG 6 -# define SQLITE_LIMIT_ATTACHED 7 -# define SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8 -# define SQLITE_LIMIT_VARIABLE_NUMBER 9 -# define SQLITE_LIMIT_TRIGGER_DEPTH 10 -# define SQLITE_LIMIT_WORKER_THREADS 11 -# else -# define SQLITE_LIMIT_WORKER_THREADS 11 -#endif - -static int _sqlite3_limit(sqlite3* db, int limitId, int newLimit) { -#ifndef _SQLITE_HAS_LIMIT - return -1; -#else - return sqlite3_limit(db, limitId, newLimit); -#endif -} -*/ -import "C" -import ( - "context" - "database/sql" - "database/sql/driver" - "errors" - "fmt" - "io" - "net/url" - "reflect" - "runtime" - "strconv" - "strings" - "sync" - "time" - "unsafe" -) - -// SQLiteTimestampFormats is timestamp formats understood by both this module -// and SQLite. The first format in the slice will be used when saving time -// values into the database. When parsing a string from a timestamp or datetime -// column, the formats are tried in order. -var SQLiteTimestampFormats = []string{ - // By default, store timestamps with whatever timezone they come with. - // When parsed, they will be returned with the same timezone. - "2006-01-02 15:04:05.999999999-07:00", - "2006-01-02T15:04:05.999999999-07:00", - "2006-01-02 15:04:05.999999999", - "2006-01-02T15:04:05.999999999", - "2006-01-02 15:04:05", - "2006-01-02T15:04:05", - "2006-01-02 15:04", - "2006-01-02T15:04", - "2006-01-02", -} - -const ( - columnDate string = "date" - columnDatetime string = "datetime" - columnTimestamp string = "timestamp" -) - -func init() { - sql.Register("sqlite3", &SQLiteDriver{}) -} - -// Version returns SQLite library version information. -func Version() (libVersion string, libVersionNumber int, sourceID string) { - libVersion = C.GoString(C.sqlite3_libversion()) - libVersionNumber = int(C.sqlite3_libversion_number()) - sourceID = C.GoString(C.sqlite3_sourceid()) - return libVersion, libVersionNumber, sourceID -} - -const ( - SQLITE_DELETE = C.SQLITE_DELETE - SQLITE_INSERT = C.SQLITE_INSERT - SQLITE_UPDATE = C.SQLITE_UPDATE -) - -// SQLiteDriver implement sql.Driver. -type SQLiteDriver struct { - Extensions []string - ConnectHook func(*SQLiteConn) error -} - -// SQLiteConn implement sql.Conn. -type SQLiteConn struct { - mu sync.Mutex - db *C.sqlite3 - loc *time.Location - txlock string - funcs []*functionInfo - aggregators []*aggInfo -} - -// SQLiteTx implemen sql.Tx. -type SQLiteTx struct { - c *SQLiteConn -} - -// SQLiteStmt implement sql.Stmt. -type SQLiteStmt struct { - mu sync.Mutex - c *SQLiteConn - s *C.sqlite3_stmt - t string - closed bool - cls bool -} - -// SQLiteResult implement sql.Result. -type SQLiteResult struct { - id int64 - changes int64 -} - -// SQLiteRows implement sql.Rows. -type SQLiteRows struct { - s *SQLiteStmt - nc int - cols []string - decltype []string - cls bool - closed bool - done chan struct{} -} - -type functionInfo struct { - f reflect.Value - argConverters []callbackArgConverter - variadicConverter callbackArgConverter - retConverter callbackRetConverter -} - -func (fi *functionInfo) Call(ctx *C.sqlite3_context, argv []*C.sqlite3_value) { - args, err := callbackConvertArgs(argv, fi.argConverters, fi.variadicConverter) - if err != nil { - callbackError(ctx, err) - return - } - - ret := fi.f.Call(args) - - if len(ret) == 2 && ret[1].Interface() != nil { - callbackError(ctx, ret[1].Interface().(error)) - return - } - - err = fi.retConverter(ctx, ret[0]) - if err != nil { - callbackError(ctx, err) - return - } -} - -type aggInfo struct { - constructor reflect.Value - - // Active aggregator objects for aggregations in flight. The - // aggregators are indexed by a counter stored in the aggregation - // user data space provided by sqlite. - active map[int64]reflect.Value - next int64 - - stepArgConverters []callbackArgConverter - stepVariadicConverter callbackArgConverter - - doneRetConverter callbackRetConverter -} - -func (ai *aggInfo) agg(ctx *C.sqlite3_context) (int64, reflect.Value, error) { - aggIdx := (*int64)(C.sqlite3_aggregate_context(ctx, C.int(8))) - if *aggIdx == 0 { - *aggIdx = ai.next - ret := ai.constructor.Call(nil) - if len(ret) == 2 && ret[1].Interface() != nil { - return 0, reflect.Value{}, ret[1].Interface().(error) - } - if ret[0].IsNil() { - return 0, reflect.Value{}, errors.New("aggregator constructor returned nil state") - } - ai.next++ - ai.active[*aggIdx] = ret[0] - } - return *aggIdx, ai.active[*aggIdx], nil -} - -func (ai *aggInfo) Step(ctx *C.sqlite3_context, argv []*C.sqlite3_value) { - _, agg, err := ai.agg(ctx) - if err != nil { - callbackError(ctx, err) - return - } - - args, err := callbackConvertArgs(argv, ai.stepArgConverters, ai.stepVariadicConverter) - if err != nil { - callbackError(ctx, err) - return - } - - ret := agg.MethodByName("Step").Call(args) - if len(ret) == 1 && ret[0].Interface() != nil { - callbackError(ctx, ret[0].Interface().(error)) - return - } -} - -func (ai *aggInfo) Done(ctx *C.sqlite3_context) { - idx, agg, err := ai.agg(ctx) - if err != nil { - callbackError(ctx, err) - return - } - defer func() { delete(ai.active, idx) }() - - ret := agg.MethodByName("Done").Call(nil) - if len(ret) == 2 && ret[1].Interface() != nil { - callbackError(ctx, ret[1].Interface().(error)) - return - } - - err = ai.doneRetConverter(ctx, ret[0]) - if err != nil { - callbackError(ctx, err) - return - } -} - -// Commit transaction. -func (tx *SQLiteTx) Commit() error { - _, err := tx.c.exec(context.Background(), "COMMIT", nil) - if err != nil && err.(Error).Code == C.SQLITE_BUSY { - // sqlite3 will leave the transaction open in this scenario. - // However, database/sql considers the transaction complete once we - // return from Commit() - we must clean up to honour its semantics. - tx.c.exec(context.Background(), "ROLLBACK", nil) - } - return err -} - -// Rollback transaction. -func (tx *SQLiteTx) Rollback() error { - _, err := tx.c.exec(context.Background(), "ROLLBACK", nil) - return err -} - -// RegisterCollation makes a Go function available as a collation. -// -// cmp receives two UTF-8 strings, a and b. The result should be 0 if -// a==b, -1 if a < b, and +1 if a > b. -// -// cmp must always return the same result given the same -// inputs. Additionally, it must have the following properties for all -// strings A, B and C: if A==B then B==A; if A==B and B==C then A==C; -// if AA; if A= 1 { - params, err := url.ParseQuery(dsn[pos+1:]) - if err != nil { - return nil, err - } - - // Authentication - if _, ok := params["_auth"]; ok { - authCreate = true - } - if val := params.Get("_auth_user"); val != "" { - authUser = val - } - if val := params.Get("_auth_pass"); val != "" { - authPass = val - } - if val := params.Get("_auth_crypt"); val != "" { - authCrypt = val - } - if val := params.Get("_auth_salt"); val != "" { - authSalt = val - } - - // _loc - if val := params.Get("_loc"); val != "" { - switch strings.ToLower(val) { - case "auto": - loc = time.Local - default: - loc, err = time.LoadLocation(val) - if err != nil { - return nil, fmt.Errorf("Invalid _loc: %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) - } - } - - // _txlock - if val := params.Get("_txlock"); val != "" { - switch strings.ToLower(val) { - case "immediate": - txlock = "BEGIN IMMEDIATE" - case "exclusive": - txlock = "BEGIN EXCLUSIVE" - case "deferred": - txlock = "BEGIN" - default: - return nil, fmt.Errorf("Invalid _txlock: %v", val) - } - } - - // 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, expecting boolean value of '0 1 false true no yes off on'", val) - } - } - - // 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, expecting boolean value of '0 1 false true no yes off on'", val) - } - } - - // 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 _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) - } - } - - if !strings.HasPrefix(dsn, "file:") { - dsn = dsn[:pos] - } - } - - var db *C.sqlite3 - name := C.CString(dsn) - defer C.free(unsafe.Pointer(name)) - rv := C._sqlite3_open_v2(name, &db, - mutex|C.SQLITE_OPEN_READWRITE|C.SQLITE_OPEN_CREATE, - nil) - if rv != 0 { - return nil, Error{Code: ErrNo(rv)} - } - if db == nil { - return nil, errors.New("sqlite succeeded without returning a database") - } - - rv = C.sqlite3_busy_timeout(db, C.int(busyTimeout)) - if rv != C.SQLITE_OK { - C.sqlite3_close_v2(db) - return nil, Error{Code: ErrNo(rv)} - } - - exec := func(s string) error { - cs := C.CString(s) - rv := C.sqlite3_exec(db, cs, nil, nil, nil) - C.free(unsafe.Pointer(cs)) - if rv != C.SQLITE_OK { - fmt.Printf("-Open-Exec() %d\n", rv) - return lastError(db) - } - return nil - } - - // 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 - // - - // Create connection to SQLite - conn := &SQLiteConn{db: db, loc: loc, txlock: txlock} - - // Password Cipher has to be registerd before authentication - if len(authCrypt) > 0 { - switch strings.ToUpper(authCrypt) { - case "SHA1": - if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA1, true); err != nil { - return nil, fmt.Errorf("CryptEncoderSHA1: %s", err) - } - case "SSHA1": - if len(authSalt) == 0 { - return nil, fmt.Errorf("_auth_crypt=ssha1, requires _auth_salt") - } - if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA1(authSalt), true); err != nil { - return nil, fmt.Errorf("CryptEncoderSSHA1: %s", err) - } - case "SHA256": - if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA256, true); err != nil { - return nil, fmt.Errorf("CryptEncoderSHA256: %s", err) - } - case "SSHA256": - if len(authSalt) == 0 { - return nil, fmt.Errorf("_auth_crypt=ssha256, requires _auth_salt") - } - if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA256(authSalt), true); err != nil { - return nil, fmt.Errorf("CryptEncoderSSHA256: %s", err) - } - case "SHA384": - if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA384, true); err != nil { - return nil, fmt.Errorf("CryptEncoderSHA384: %s", err) - } - case "SSHA384": - if len(authSalt) == 0 { - return nil, fmt.Errorf("_auth_crypt=ssha384, requires _auth_salt") - } - if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA384(authSalt), true); err != nil { - return nil, fmt.Errorf("CryptEncoderSSHA384: %s", err) - } - case "SHA512": - if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA512, true); err != nil { - return nil, fmt.Errorf("CryptEncoderSHA512: %s", err) - } - case "SSHA512": - if len(authSalt) == 0 { - return nil, fmt.Errorf("_auth_crypt=ssha512, requires _auth_salt") - } - if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA512(authSalt), true); err != nil { - return nil, fmt.Errorf("CryptEncoderSSHA512: %s", err) - } - } - } - - // Preform Authentication - if err := conn.Authenticate(authUser, authPass); err != nil { - return nil, err - } - fmt.Println("+") - // 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 - } - fmt.Println("+") - // - // 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 - } - fmt.Println("+") - // - // 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 - } - fmt.Println("+") - // - // 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 - } - fmt.Println("+") - // 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 - } - fmt.Println("+") - // Auto Vacuum - // Moved auto_vacuum command, 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 autoVacuum > -1 { - if err := exec(fmt.Sprintf("PRAGMA auto_vacuum = %d;", autoVacuum)); err != nil { - C.sqlite3_close_v2(db) - return nil, err - } - } - fmt.Println("+") - // Check if user wants to activate User Authentication - if authCreate { - // Before going any further, we need to check that the user - // has provided an username and password within the DSN. - // We are not allowed to continue. - if len(authUser) < 0 { - return nil, fmt.Errorf("Missing '_auth_user' while user authentication was requested with '_auth'") - } - if len(authPass) < 0 { - return nil, fmt.Errorf("Missing '_auth_pass' while user authentication was requested with '_auth'") - } - - // Check if User Authentication is Enabled - authExists := conn.AuthEnabled() - if !authExists { - if err := conn.AuthUserAdd(authUser, authPass, true); err != nil { - return nil, err - } - } - } - fmt.Println("+") - // 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 - } - } - fmt.Println("+") - // 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 - } - } - fmt.Println("+") - // 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 - } - } - fmt.Println("+") - // 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 - } - fmt.Println("+") - // 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 - } - fmt.Println("+") - // 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 - } - } - fmt.Println("+") - // 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 - } - } - fmt.Println("+") - // 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 - } - fmt.Println("+") - // 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 - } - } - fmt.Println("+") - if len(d.Extensions) > 0 { - fmt.Println("Loading Extensions") - if err := conn.loadExtensions(d.Extensions); err != nil { - fmt.Println("Error while loading Extensions") - conn.Close() - return nil, err - } - } - fmt.Println("+") - if d.ConnectHook != nil { - if err := d.ConnectHook(conn); err != nil { - conn.Close() - return nil, err - } - } - runtime.SetFinalizer(conn, (*SQLiteConn).Close) - fmt.Println("< Open") - return conn, nil -} - -// Close the connection. -func (c *SQLiteConn) Close() error { - rv := C.sqlite3_close_v2(c.db) - if rv != C.SQLITE_OK { - return c.lastError() - } - deleteHandles(c) - c.mu.Lock() - c.db = nil - c.mu.Unlock() - runtime.SetFinalizer(c, nil) - return nil -} - -func (c *SQLiteConn) dbConnOpen() bool { - if c == nil { - return false - } - c.mu.Lock() - defer c.mu.Unlock() - return c.db != nil -} - -// Prepare the query string. Return a new statement. -func (c *SQLiteConn) Prepare(query string) (driver.Stmt, error) { - return c.prepare(context.Background(), query) -} - -func (c *SQLiteConn) prepare(ctx context.Context, query string) (driver.Stmt, error) { - pquery := C.CString(query) - defer C.free(unsafe.Pointer(pquery)) - var s *C.sqlite3_stmt - var tail *C.char - rv := C.sqlite3_prepare_v2(c.db, pquery, -1, &s, &tail) - if rv != C.SQLITE_OK { - return nil, c.lastError() - } - var t string - if tail != nil && *tail != '\000' { - t = strings.TrimSpace(C.GoString(tail)) - } - ss := &SQLiteStmt{c: c, s: s, t: t} - runtime.SetFinalizer(ss, (*SQLiteStmt).Close) - return ss, nil -} - -// Run-Time Limit Categories. -// See: http://www.sqlite.org/c3ref/c_limit_attached.html -const ( - SQLITE_LIMIT_LENGTH = C.SQLITE_LIMIT_LENGTH - SQLITE_LIMIT_SQL_LENGTH = C.SQLITE_LIMIT_SQL_LENGTH - SQLITE_LIMIT_COLUMN = C.SQLITE_LIMIT_COLUMN - SQLITE_LIMIT_EXPR_DEPTH = C.SQLITE_LIMIT_EXPR_DEPTH - SQLITE_LIMIT_COMPOUND_SELECT = C.SQLITE_LIMIT_COMPOUND_SELECT - SQLITE_LIMIT_VDBE_OP = C.SQLITE_LIMIT_VDBE_OP - SQLITE_LIMIT_FUNCTION_ARG = C.SQLITE_LIMIT_FUNCTION_ARG - SQLITE_LIMIT_ATTACHED = C.SQLITE_LIMIT_ATTACHED - SQLITE_LIMIT_LIKE_PATTERN_LENGTH = C.SQLITE_LIMIT_LIKE_PATTERN_LENGTH - SQLITE_LIMIT_VARIABLE_NUMBER = C.SQLITE_LIMIT_VARIABLE_NUMBER - SQLITE_LIMIT_TRIGGER_DEPTH = C.SQLITE_LIMIT_TRIGGER_DEPTH - SQLITE_LIMIT_WORKER_THREADS = C.SQLITE_LIMIT_WORKER_THREADS -) - -// GetFilename returns the absolute path to the file containing -// the requested schema. When passed an empty string, it will -// instead use the database's default schema: "main". -// See: sqlite3_db_filename, https://www.sqlite.org/c3ref/db_filename.html -func (c *SQLiteConn) GetFilename(schemaName string) string { - if schemaName == "" { - schemaName = "main" - } - return C.GoString(C.sqlite3_db_filename(c.db, C.CString(schemaName))) -} - -// GetLimit returns the current value of a run-time limit. -// See: sqlite3_limit, http://www.sqlite.org/c3ref/limit.html -func (c *SQLiteConn) GetLimit(id int) int { - return int(C._sqlite3_limit(c.db, C.int(id), -1)) -} - -// SetLimit changes the value of a run-time limits. -// Then this method returns the prior value of the limit. -// See: sqlite3_limit, http://www.sqlite.org/c3ref/limit.html -func (c *SQLiteConn) SetLimit(id int, newVal int) int { - return int(C._sqlite3_limit(c.db, C.int(id), C.int(newVal))) -} - -// Close the statement. -func (s *SQLiteStmt) Close() error { - s.mu.Lock() - defer s.mu.Unlock() - if s.closed { - return nil - } - s.closed = true - if !s.c.dbConnOpen() { - return errors.New("sqlite statement with already closed database connection") - } - rv := C.sqlite3_finalize(s.s) - s.s = nil - if rv != C.SQLITE_OK { - return s.c.lastError() - } - runtime.SetFinalizer(s, nil) - return nil -} - -// NumInput return a number of parameters. -func (s *SQLiteStmt) NumInput() int { - return int(C.sqlite3_bind_parameter_count(s.s)) -} - -type bindArg struct { - n int - v driver.Value -} - -var placeHolder = []byte{0} - -func (s *SQLiteStmt) bind(args []namedValue) error { - rv := C.sqlite3_reset(s.s) - if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE { - return s.c.lastError() - } - - for i, v := range args { - if v.Name != "" { - cname := C.CString(":" + v.Name) - args[i].Ordinal = int(C.sqlite3_bind_parameter_index(s.s, cname)) - C.free(unsafe.Pointer(cname)) - } - } - - for _, arg := range args { - n := C.int(arg.Ordinal) - switch v := arg.Value.(type) { - case nil: - rv = C.sqlite3_bind_null(s.s, n) - case string: - if len(v) == 0 { - rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&placeHolder[0])), C.int(0)) - } else { - b := []byte(v) - rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b))) - } - case int64: - rv = C.sqlite3_bind_int64(s.s, n, C.sqlite3_int64(v)) - case bool: - if v { - rv = C.sqlite3_bind_int(s.s, n, 1) - } else { - rv = C.sqlite3_bind_int(s.s, n, 0) - } - case float64: - rv = C.sqlite3_bind_double(s.s, n, C.double(v)) - case []byte: - if v == nil { - rv = C.sqlite3_bind_null(s.s, n) - } else { - ln := len(v) - if ln == 0 { - v = placeHolder - } - rv = C._sqlite3_bind_blob(s.s, n, unsafe.Pointer(&v[0]), C.int(ln)) - } - case time.Time: - b := []byte(v.Format(SQLiteTimestampFormats[0])) - rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b))) - } - if rv != C.SQLITE_OK { - return s.c.lastError() - } - } - return nil -} - -// Query the statement with arguments. Return records. -func (s *SQLiteStmt) Query(args []driver.Value) (driver.Rows, error) { - list := make([]namedValue, len(args)) - for i, v := range args { - list[i] = namedValue{ - Ordinal: i + 1, - Value: v, - } - } - return s.query(context.Background(), list) -} - -func (s *SQLiteStmt) query(ctx context.Context, args []namedValue) (driver.Rows, error) { - if err := s.bind(args); err != nil { - return nil, err - } - - rows := &SQLiteRows{ - s: s, - nc: int(C.sqlite3_column_count(s.s)), - cols: nil, - decltype: nil, - cls: s.cls, - closed: false, - done: make(chan struct{}), - } - - if ctxdone := ctx.Done(); ctxdone != nil { - go func(db *C.sqlite3) { - select { - case <-ctxdone: - select { - case <-rows.done: - default: - C.sqlite3_interrupt(db) - rows.Close() - } - case <-rows.done: - } - }(s.c.db) - } - - return rows, nil -} - -// LastInsertId teturn last inserted ID. -func (r *SQLiteResult) LastInsertId() (int64, error) { - return r.id, nil -} - -// RowsAffected return how many rows affected. -func (r *SQLiteResult) RowsAffected() (int64, error) { - return r.changes, nil -} - -// Exec execute the statement with arguments. Return result object. -func (s *SQLiteStmt) Exec(args []driver.Value) (driver.Result, error) { - list := make([]namedValue, len(args)) - for i, v := range args { - list[i] = namedValue{ - Ordinal: i + 1, - Value: v, - } - } - return s.exec(context.Background(), list) -} - -func (s *SQLiteStmt) exec(ctx context.Context, args []namedValue) (driver.Result, error) { - if err := s.bind(args); err != nil { - C.sqlite3_reset(s.s) - C.sqlite3_clear_bindings(s.s) - return nil, err - } - - if ctxdone := ctx.Done(); ctxdone != nil { - done := make(chan struct{}) - defer close(done) - go func(db *C.sqlite3) { - select { - case <-done: - case <-ctxdone: - select { - case <-done: - default: - C.sqlite3_interrupt(db) - } - } - }(s.c.db) - } - - var rowid, changes C.longlong - rv := C._sqlite3_step(s.s, &rowid, &changes) - if rv != C.SQLITE_ROW && rv != C.SQLITE_OK && rv != C.SQLITE_DONE { - err := s.c.lastError() - C.sqlite3_reset(s.s) - C.sqlite3_clear_bindings(s.s) - return nil, err - } - - return &SQLiteResult{id: int64(rowid), changes: int64(changes)}, nil -} - -// Close the rows. -func (rc *SQLiteRows) Close() error { - rc.s.mu.Lock() - if rc.s.closed || rc.closed { - rc.s.mu.Unlock() - return nil - } - rc.closed = true - if rc.done != nil { - close(rc.done) - } - if rc.cls { - rc.s.mu.Unlock() - return rc.s.Close() - } - rv := C.sqlite3_reset(rc.s.s) - if rv != C.SQLITE_OK { - rc.s.mu.Unlock() - return rc.s.c.lastError() - } - rc.s.mu.Unlock() - return nil -} - -// Columns return column names. -func (rc *SQLiteRows) Columns() []string { - rc.s.mu.Lock() - defer rc.s.mu.Unlock() - if rc.s.s != nil && rc.nc != len(rc.cols) { - rc.cols = make([]string, rc.nc) - for i := 0; i < rc.nc; i++ { - rc.cols[i] = C.GoString(C.sqlite3_column_name(rc.s.s, C.int(i))) - } - } - return rc.cols -} - -func (rc *SQLiteRows) declTypes() []string { - if rc.s.s != nil && rc.decltype == nil { - rc.decltype = make([]string, rc.nc) - for i := 0; i < rc.nc; i++ { - rc.decltype[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(rc.s.s, C.int(i)))) - } - } - return rc.decltype -} - -// DeclTypes return column types. -func (rc *SQLiteRows) DeclTypes() []string { - rc.s.mu.Lock() - defer rc.s.mu.Unlock() - return rc.declTypes() -} - -// Next move cursor to next. -func (rc *SQLiteRows) Next(dest []driver.Value) error { - if rc.s.closed { - return io.EOF - } - rc.s.mu.Lock() - defer rc.s.mu.Unlock() - rv := C.sqlite3_step(rc.s.s) - if rv == C.SQLITE_DONE { - return io.EOF - } - if rv != C.SQLITE_ROW { - rv = C.sqlite3_reset(rc.s.s) - if rv != C.SQLITE_OK { - return rc.s.c.lastError() - } - return nil - } - - rc.declTypes() - - for i := range dest { - switch C.sqlite3_column_type(rc.s.s, C.int(i)) { - case C.SQLITE_INTEGER: - val := int64(C.sqlite3_column_int64(rc.s.s, C.int(i))) - switch rc.decltype[i] { - case columnTimestamp, columnDatetime, columnDate: - var t time.Time - // Assume a millisecond unix timestamp if it's 13 digits -- too - // large to be a reasonable timestamp in seconds. - if val > 1e12 || val < -1e12 { - val *= int64(time.Millisecond) // convert ms to nsec - t = time.Unix(0, val) - } else { - t = time.Unix(val, 0) - } - t = t.UTC() - if rc.s.c.loc != nil { - t = t.In(rc.s.c.loc) - } - dest[i] = t - case "boolean": - dest[i] = val > 0 - default: - dest[i] = val - } - case C.SQLITE_FLOAT: - dest[i] = float64(C.sqlite3_column_double(rc.s.s, C.int(i))) - case C.SQLITE_BLOB: - p := C.sqlite3_column_blob(rc.s.s, C.int(i)) - if p == nil { - dest[i] = nil - continue - } - n := int(C.sqlite3_column_bytes(rc.s.s, C.int(i))) - switch dest[i].(type) { - default: - slice := make([]byte, n) - copy(slice[:], (*[1 << 30]byte)(p)[0:n]) - dest[i] = slice - } - case C.SQLITE_NULL: - dest[i] = nil - case C.SQLITE_TEXT: - var err error - var timeVal time.Time - - n := int(C.sqlite3_column_bytes(rc.s.s, C.int(i))) - s := C.GoStringN((*C.char)(unsafe.Pointer(C.sqlite3_column_text(rc.s.s, C.int(i)))), C.int(n)) - - switch rc.decltype[i] { - case columnTimestamp, columnDatetime, columnDate: - var t time.Time - s = strings.TrimSuffix(s, "Z") - for _, format := range SQLiteTimestampFormats { - if timeVal, err = time.ParseInLocation(format, s, time.UTC); err == nil { - t = timeVal - break - } - } - if err != nil { - // The column is a time value, so return the zero time on parse failure. - t = time.Time{} - } - if rc.s.c.loc != nil { - t = t.In(rc.s.c.loc) - } - dest[i] = t - default: - dest[i] = []byte(s) - } - - } - } - return nil -} diff --git a/_examples/README.md b/_examples/README.md new file mode 100644 index 0000000..2ff6c24 --- /dev/null +++ b/_examples/README.md @@ -0,0 +1,8 @@ +## Examples + +* [Custom Function](./custom_func) +* [Hook](./hook) +* [Limit](./limit) +* [Simple](./simple) +* [Trace](./trace) +* [VTable](./vtable) diff --git a/_examples/custom_func/README.md b/_examples/custom_func/README.md new file mode 100644 index 0000000..b9432f8 --- /dev/null +++ b/_examples/custom_func/README.md @@ -0,0 +1,5 @@ +## How to compile + +```bash +go build -v github.com/mattn/go-sqlite3/examples/custom_func +``` diff --git a/_examples/custom_func/custom_func b/_examples/custom_func/custom_func new file mode 100644 index 0000000..b214bed Binary files /dev/null and b/_examples/custom_func/custom_func differ diff --git a/_example/custom_func/main.go b/_examples/custom_func/main.go similarity index 98% rename from _example/custom_func/main.go rename to _examples/custom_func/main.go index 85657e6..59c23b2 100644 --- a/_example/custom_func/main.go +++ b/_examples/custom_func/main.go @@ -7,7 +7,7 @@ import ( "math" "math/rand" - sqlite "github.com/mattn/go-sqlite3" + sqlite "github.com/mattn/go-sqlite3/driver" ) // Computes x^y diff --git a/_examples/hook/README.md b/_examples/hook/README.md new file mode 100644 index 0000000..3bb4353 --- /dev/null +++ b/_examples/hook/README.md @@ -0,0 +1,5 @@ +## How to compile + +```bash +go build -v github.com/mattn/go-sqlite3/examples/hook +``` diff --git a/_examples/hook/hook b/_examples/hook/hook new file mode 100644 index 0000000..ed688cc Binary files /dev/null and b/_examples/hook/hook differ diff --git a/_example/hook/hook.go b/_examples/hook/hook.go similarity index 97% rename from _example/hook/hook.go rename to _examples/hook/hook.go index 6023181..dfb4539 100644 --- a/_example/hook/hook.go +++ b/_examples/hook/hook.go @@ -5,7 +5,7 @@ import ( "log" "os" - "github.com/mattn/go-sqlite3" + "github.com/mattn/go-sqlite3/driver" ) func main() { diff --git a/_examples/limit/README.md b/_examples/limit/README.md new file mode 100644 index 0000000..b7f4f28 --- /dev/null +++ b/_examples/limit/README.md @@ -0,0 +1,5 @@ +## How to compile + +```bash +go build -v github.com/mattn/go-sqlite3/examples/limit +``` diff --git a/_examples/limit/limit b/_examples/limit/limit new file mode 100644 index 0000000..f21dcb4 Binary files /dev/null and b/_examples/limit/limit differ diff --git a/_example/limit/limit.go b/_examples/limit/limit.go similarity index 98% rename from _example/limit/limit.go rename to _examples/limit/limit.go index 4e4b897..42af3f8 100644 --- a/_example/limit/limit.go +++ b/_examples/limit/limit.go @@ -7,7 +7,7 @@ import ( "os" "strings" - "github.com/mattn/go-sqlite3" + "github.com/mattn/go-sqlite3/driver" ) func createBulkInsertQuery(n int, start int) (query string, args []interface{}) { diff --git a/_example/mod_regexp/Makefile b/_examples/mod_regexp/Makefile similarity index 100% rename from _example/mod_regexp/Makefile rename to _examples/mod_regexp/Makefile diff --git a/_examples/mod_regexp/README.md b/_examples/mod_regexp/README.md new file mode 100644 index 0000000..b890726 --- /dev/null +++ b/_examples/mod_regexp/README.md @@ -0,0 +1,17 @@ +## How to compile + +```bash +cd ${GOPATH}/src/github.com/mattn/go-sqlite3/examples/mod_regexp +make all +``` + +## How to run + +The OS has to be able to find the compiled extension. +In normal cases this library (.so) should be added to the LD_LIBRARY_PATH. + +Run run from current compiled directory. + +```bash +LD_LIBRARY_PATH=$(pwd) ./extension +``` \ No newline at end of file diff --git a/_examples/mod_regexp/extension b/_examples/mod_regexp/extension new file mode 100644 index 0000000..1fc8abd Binary files /dev/null and b/_examples/mod_regexp/extension differ diff --git a/_example/mod_regexp/extension.go b/_examples/mod_regexp/extension.go similarity index 95% rename from _example/mod_regexp/extension.go rename to _examples/mod_regexp/extension.go index 61ceb55..59fb8cd 100644 --- a/_example/mod_regexp/extension.go +++ b/_examples/mod_regexp/extension.go @@ -3,8 +3,9 @@ package main import ( "database/sql" "fmt" - "github.com/mattn/go-sqlite3" "log" + + "github.com/mattn/go-sqlite3/driver" ) func main() { diff --git a/_example/mod_regexp/sqlite3_mod_regexp.c b/_examples/mod_regexp/sqlite3_mod_regexp.c similarity index 100% rename from _example/mod_regexp/sqlite3_mod_regexp.c rename to _examples/mod_regexp/sqlite3_mod_regexp.c diff --git a/_examples/mod_regexp/sqlite3_mod_regexp.so b/_examples/mod_regexp/sqlite3_mod_regexp.so new file mode 100644 index 0000000..9535c05 Binary files /dev/null and b/_examples/mod_regexp/sqlite3_mod_regexp.so differ diff --git a/_example/mod_vtable/Makefile b/_examples/mod_vtable/Makefile similarity index 86% rename from _example/mod_vtable/Makefile rename to _examples/mod_vtable/Makefile index f5bf61d..d5574fc 100644 --- a/_example/mod_vtable/Makefile +++ b/_examples/mod_vtable/Makefile @@ -16,12 +16,12 @@ LIBCURL=-lcurl endif # Include source from repository -CFLAGS=-I$(CURDIR)/../../ +CFLAGS=-I$(CURDIR)/../../driver all : $(EXT) $(EXE) $(EXE) : extension.go - go build -v -a -tags=sqlite_vtable $< + go build -v -tags=sqlite_vtable $< $(EXT) : sqlite3_mod_vtable.cc g++ $(LDFLAG) $(CFLAGS) -shared -o $@ $< $(LIBCURL) diff --git a/_examples/mod_vtable/README.md b/_examples/mod_vtable/README.md new file mode 100644 index 0000000..bd830a7 --- /dev/null +++ b/_examples/mod_vtable/README.md @@ -0,0 +1,17 @@ +## How to compile + +```bash +cd ${GOPATH}/src/github.com/mattn/go-sqlite3/examples/mod_vtable +make all +``` + +## How to run + +The OS has to be able to find the compiled extension. +In normal cases this library (.so) should be added to the LD_LIBRARY_PATH. + +Run run from current compiled directory. + +```bash +LD_LIBRARY_PATH=$(pwd) ./extension +``` \ No newline at end of file diff --git a/_examples/mod_vtable/extension b/_examples/mod_vtable/extension new file mode 100644 index 0000000..044b293 Binary files /dev/null and b/_examples/mod_vtable/extension differ diff --git a/_example/mod_vtable/extension.go b/_examples/mod_vtable/extension.go similarity index 87% rename from _example/mod_vtable/extension.go rename to _examples/mod_vtable/extension.go index 08f6953..5cee277 100644 --- a/_example/mod_vtable/extension.go +++ b/_examples/mod_vtable/extension.go @@ -6,7 +6,7 @@ import ( "log" "os" - "github.com/mattn/go-sqlite3" + "github.com/mattn/go-sqlite3/driver" ) func main() { @@ -22,22 +22,17 @@ func main() { log.Fatal(err) } defer db.Close() - fmt.Println(">") _, err = db.Exec("create virtual table repo using github(id, full_name, description, html_url)") if err != nil { - fmt.Println("-") log.Fatal(err) } - fmt.Println("-") - fmt.Println("-") rows, err := db.Query("select id, full_name, description, html_url from repo") if err != nil { log.Fatal(err) } defer rows.Close() - fmt.Println("-") for rows.Next() { var id, fullName, description, htmlURL string rows.Scan(&id, &fullName, &description, &htmlURL) diff --git a/_example/mod_vtable/picojson.h b/_examples/mod_vtable/picojson.h similarity index 100% rename from _example/mod_vtable/picojson.h rename to _examples/mod_vtable/picojson.h diff --git a/_example/mod_vtable/sqlite3_mod_vtable.cc b/_examples/mod_vtable/sqlite3_mod_vtable.cc similarity index 100% rename from _example/mod_vtable/sqlite3_mod_vtable.cc rename to _examples/mod_vtable/sqlite3_mod_vtable.cc diff --git a/_example/mod_vtable/sqlite3_mod_vtable.so b/_examples/mod_vtable/sqlite3_mod_vtable.so similarity index 100% rename from _example/mod_vtable/sqlite3_mod_vtable.so rename to _examples/mod_vtable/sqlite3_mod_vtable.so diff --git a/_examples/simple/README.md b/_examples/simple/README.md new file mode 100644 index 0000000..c15a190 --- /dev/null +++ b/_examples/simple/README.md @@ -0,0 +1,5 @@ +## How to compile + +```bash +go build -v github.com/mattn/go-sqlite3/examples/simple +``` diff --git a/_example/mod_vtable/extension b/_examples/simple/simple similarity index 54% rename from _example/mod_vtable/extension rename to _examples/simple/simple index 6e35c14..cebe810 100644 Binary files a/_example/mod_vtable/extension and b/_examples/simple/simple differ diff --git a/_example/simple/simple.go b/_examples/simple/simple.go similarity index 97% rename from _example/simple/simple.go rename to _examples/simple/simple.go index 261ed4d..7ed8dda 100644 --- a/_example/simple/simple.go +++ b/_examples/simple/simple.go @@ -3,7 +3,7 @@ package main import ( "database/sql" "fmt" - _ "github.com/mattn/go-sqlite3" + _ "github.com/mattn/go-sqlite3/driver" "log" "os" ) diff --git a/_examples/trace/README.md b/_examples/trace/README.md new file mode 100644 index 0000000..b940661 --- /dev/null +++ b/_examples/trace/README.md @@ -0,0 +1,5 @@ +## How to compile + +```bash +go build -v -tags=sqlite_trace github.com/mattn/go-sqlite3/examples/trace +``` diff --git a/_example/trace/main.go b/_examples/trace/main.go similarity index 99% rename from _example/trace/main.go rename to _examples/trace/main.go index bef3d15..0cb199e 100644 --- a/_example/trace/main.go +++ b/_examples/trace/main.go @@ -6,7 +6,7 @@ import ( "log" "os" - sqlite3 "github.com/mattn/go-sqlite3" + sqlite3 "github.com/mattn/go-sqlite3/driver" ) func traceCallback(info sqlite3.TraceInfo) int { diff --git a/_examples/trace/trace b/_examples/trace/trace new file mode 100644 index 0000000..c87353b Binary files /dev/null and b/_examples/trace/trace differ diff --git a/_examples/vtable/README.md b/_examples/vtable/README.md new file mode 100644 index 0000000..8d194c2 --- /dev/null +++ b/_examples/vtable/README.md @@ -0,0 +1,5 @@ +## How to compile + +```bash +go build -v -tags=sqlite_vtable github.com/mattn/go-sqlite3/examples/vtable +``` diff --git a/_example/vtable/main.go b/_examples/vtable/main.go similarity index 95% rename from _example/vtable/main.go rename to _examples/vtable/main.go index aad8dda..fc300e1 100644 --- a/_example/vtable/main.go +++ b/_examples/vtable/main.go @@ -5,7 +5,7 @@ import ( "fmt" "log" - "github.com/mattn/go-sqlite3" + "github.com/mattn/go-sqlite3/driver" ) func main() { diff --git a/_examples/vtable/vtable b/_examples/vtable/vtable new file mode 100644 index 0000000..1bee89a Binary files /dev/null and b/_examples/vtable/vtable differ diff --git a/_example/vtable/vtable.go b/_examples/vtable/vtable.go similarity index 98% rename from _example/vtable/vtable.go rename to _examples/vtable/vtable.go index 1d6d824..12f46ed 100644 --- a/_example/vtable/vtable.go +++ b/_examples/vtable/vtable.go @@ -6,7 +6,7 @@ import ( "io/ioutil" "net/http" - "github.com/mattn/go-sqlite3" + "github.com/mattn/go-sqlite3/driver" ) type githubRepo struct { diff --git a/driver/callback.go b/driver/callback.go index 3070f91..e3285d0 100644 --- a/driver/callback.go +++ b/driver/callback.go @@ -100,7 +100,7 @@ func newHandle(db *SQLiteConn, v interface{}) uintptr { return i } -func lookupHandle(handle uintptr) interface{} { +func lookupHandleVal(handle uintptr) handleVal { handleLock.Lock() defer handleLock.Unlock() r, ok := handleVals[handle] @@ -111,7 +111,11 @@ func lookupHandle(handle uintptr) interface{} { panic("invalid handle") } } - return r.val + return r +} + +func lookupHandle(handle uintptr) interface{} { + return lookupHandleVal(handle).val } func deleteHandles(db *SQLiteConn) { diff --git a/driver/config.go b/driver/config.go index 6f4dbf1..7ffba2b 100644 --- a/driver/config.go +++ b/driver/config.go @@ -627,6 +627,10 @@ func (cfg *Config) FormatDSN() string { } } + if cfg.BusyTimeout > 0 { + params.Set("timeout", cfg.BusyTimeout.String()) + } + if len(cfg.TransactionLock.String()) > 0 && cfg.TransactionLock != TxLockDeferred { params.Set("txlock", cfg.TransactionLock.String()) } @@ -756,13 +760,15 @@ func (cfg *Config) createConnection() (driver.Conn, error) { } // 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) + if cfg.BusyTimeout > 0 { + 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)} + return nil, Error{Code: ErrNo(rv)} + } } // Create basic connection diff --git a/_driver/doc.go b/driver/doc.go similarity index 100% rename from _driver/doc.go rename to driver/doc.go diff --git a/driver/opt_preupdate_hook.go b/driver/opt_preupdate_hook.go new file mode 100644 index 0000000..c407df2 --- /dev/null +++ b/driver/opt_preupdate_hook.go @@ -0,0 +1,388 @@ +// Copyright (C) 2018 The Go-SQLite3 Authors. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build cgo +// +build !libsqlite3 + +package sqlite3 + +/* +#cgo CFLAGS: -DSQLITE_ENABLE_PREUPDATE_HOOK +#cgo LDFLAGS: -lm + +#include +#include +*/ +import "C" +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "reflect" + "strconv" + "time" + "unsafe" +) + +var errNilPtr = errors.New("destination pointer is nil") // embedded in descriptive error + +// SQLitePreUpdateData represents all of the data available during a +// pre-update hook call. +type SQLitePreUpdateData struct { + Conn *SQLiteConn + Op int + DatabaseName string + TableName string + OldRowID int64 + NewRowID int64 +} + +// Depth returns the source path of the write, see sqlite3_preupdate_depth() +func (d *SQLitePreUpdateData) Depth() int { + return int(C.sqlite3_preupdate_depth(d.Conn.db)) +} + +// Count returns the number of columns in the row +func (d *SQLitePreUpdateData) Count() int { + return int(C.sqlite3_preupdate_count(d.Conn.db)) +} + +func (d *SQLitePreUpdateData) row(dest []interface{}, new bool) error { + for i := 0; i < d.Count() && i < len(dest); i++ { + var val *C.sqlite3_value + var src interface{} + + // Initially I tried making this just a function pointer argument, but + // it's absurdly complicated to pass C function pointers. + if new { + C.sqlite3_preupdate_new(d.Conn.db, C.int(i), &val) + } else { + C.sqlite3_preupdate_old(d.Conn.db, C.int(i), &val) + } + + switch C.sqlite3_value_type(val) { + case C.SQLITE_INTEGER: + src = int64(C.sqlite3_value_int64(val)) + case C.SQLITE_FLOAT: + src = float64(C.sqlite3_value_double(val)) + case C.SQLITE_BLOB: + len := C.sqlite3_value_bytes(val) + blobptr := C.sqlite3_value_blob(val) + src = C.GoBytes(blobptr, len) + case C.SQLITE_TEXT: + len := C.sqlite3_value_bytes(val) + cstrptr := unsafe.Pointer(C.sqlite3_value_text(val)) + src = C.GoBytes(cstrptr, len) + case C.SQLITE_NULL: + src = nil + } + + err := convertAssign(&dest[i], src) + if err != nil { + return err + } + } + + return nil +} + +// Old populates dest with the row data to be replaced. This works similar to +// database/sql's Rows.Scan() +func (d *SQLitePreUpdateData) Old(dest ...interface{}) error { + if d.Op == SQLITE_INSERT { + return errors.New("There is no old row for INSERT operations") + } + return d.row(dest, false) +} + +// New populates dest with the replacement row data. This works similar to +// database/sql's Rows.Scan() +func (d *SQLitePreUpdateData) New(dest ...interface{}) error { + if d.Op == SQLITE_DELETE { + return errors.New("There is no new row for DELETE operations") + } + return d.row(dest, true) +} + +// convertAssign copies to dest the value in src, converting it if possible. +// An error is returned if the copy would result in loss of information. +// dest should be a pointer type. +func convertAssign(dest, src interface{}) error { + // Common cases, without reflect. + switch s := src.(type) { + case string: + switch d := dest.(type) { + case *string: + if d == nil { + return errNilPtr + } + *d = s + return nil + case *[]byte: + if d == nil { + return errNilPtr + } + *d = []byte(s) + return nil + case *sql.RawBytes: + if d == nil { + return errNilPtr + } + *d = append((*d)[:0], s...) + return nil + } + case []byte: + switch d := dest.(type) { + case *string: + if d == nil { + return errNilPtr + } + *d = string(s) + return nil + case *interface{}: + if d == nil { + return errNilPtr + } + *d = cloneBytes(s) + return nil + case *[]byte: + if d == nil { + return errNilPtr + } + *d = cloneBytes(s) + return nil + case *sql.RawBytes: + if d == nil { + return errNilPtr + } + *d = s + return nil + } + case time.Time: + switch d := dest.(type) { + case *time.Time: + *d = s + return nil + case *string: + *d = s.Format(time.RFC3339Nano) + return nil + case *[]byte: + if d == nil { + return errNilPtr + } + *d = []byte(s.Format(time.RFC3339Nano)) + return nil + case *sql.RawBytes: + if d == nil { + return errNilPtr + } + *d = s.AppendFormat((*d)[:0], time.RFC3339Nano) + return nil + } + case nil: + switch d := dest.(type) { + case *interface{}: + if d == nil { + return errNilPtr + } + *d = nil + return nil + case *[]byte: + if d == nil { + return errNilPtr + } + *d = nil + return nil + case *sql.RawBytes: + if d == nil { + return errNilPtr + } + *d = nil + return nil + } + } + + var sv reflect.Value + + switch d := dest.(type) { + case *string: + sv = reflect.ValueOf(src) + switch sv.Kind() { + case reflect.Bool, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Float32, reflect.Float64: + *d = asString(src) + return nil + } + case *[]byte: + sv = reflect.ValueOf(src) + if b, ok := asBytes(nil, sv); ok { + *d = b + return nil + } + case *sql.RawBytes: + sv = reflect.ValueOf(src) + if b, ok := asBytes([]byte(*d)[:0], sv); ok { + *d = sql.RawBytes(b) + return nil + } + case *bool: + bv, err := driver.Bool.ConvertValue(src) + if err == nil { + *d = bv.(bool) + } + return err + case *interface{}: + *d = src + return nil + } + + if scanner, ok := dest.(sql.Scanner); ok { + return scanner.Scan(src) + } + + dpv := reflect.ValueOf(dest) + if dpv.Kind() != reflect.Ptr { + return errors.New("destination not a pointer") + } + if dpv.IsNil() { + return errNilPtr + } + + if !sv.IsValid() { + sv = reflect.ValueOf(src) + } + + dv := reflect.Indirect(dpv) + if sv.IsValid() && sv.Type().AssignableTo(dv.Type()) { + switch b := src.(type) { + case []byte: + dv.Set(reflect.ValueOf(cloneBytes(b))) + default: + dv.Set(sv) + } + return nil + } + + if dv.Kind() == sv.Kind() && sv.Type().ConvertibleTo(dv.Type()) { + dv.Set(sv.Convert(dv.Type())) + return nil + } + + // The following conversions use a string value as an intermediate representation + // to convert between various numeric types. + // + // This also allows scanning into user defined types such as "type Int int64". + // For symmetry, also check for string destination types. + switch dv.Kind() { + case reflect.Ptr: + if src == nil { + dv.Set(reflect.Zero(dv.Type())) + return nil + } else { + dv.Set(reflect.New(dv.Type().Elem())) + return convertAssign(dv.Interface(), src) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + s := asString(src) + i64, err := strconv.ParseInt(s, 10, dv.Type().Bits()) + if err != nil { + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) + } + dv.SetInt(i64) + return nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + s := asString(src) + u64, err := strconv.ParseUint(s, 10, dv.Type().Bits()) + if err != nil { + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) + } + dv.SetUint(u64) + return nil + case reflect.Float32, reflect.Float64: + s := asString(src) + f64, err := strconv.ParseFloat(s, dv.Type().Bits()) + if err != nil { + err = strconvErr(err) + return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) + } + dv.SetFloat(f64) + return nil + case reflect.String: + switch v := src.(type) { + case string: + dv.SetString(v) + return nil + case []byte: + dv.SetString(string(v)) + return nil + } + } + + return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, dest) +} + +func strconvErr(err error) error { + if ne, ok := err.(*strconv.NumError); ok { + return ne.Err + } + return err +} + +func cloneBytes(b []byte) []byte { + if b == nil { + return nil + } else { + c := make([]byte, len(b)) + copy(c, b) + return c + } +} + +func asString(src interface{}) string { + switch v := src.(type) { + case string: + return v + case []byte: + return string(v) + } + rv := reflect.ValueOf(src) + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(rv.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return strconv.FormatUint(rv.Uint(), 10) + case reflect.Float64: + return strconv.FormatFloat(rv.Float(), 'g', -1, 64) + case reflect.Float32: + return strconv.FormatFloat(rv.Float(), 'g', -1, 32) + case reflect.Bool: + return strconv.FormatBool(rv.Bool()) + } + return fmt.Sprintf("%v", src) +} + +func asBytes(buf []byte, rv reflect.Value) (b []byte, ok bool) { + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.AppendInt(buf, rv.Int(), 10), true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return strconv.AppendUint(buf, rv.Uint(), 10), true + case reflect.Float32: + return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 32), true + case reflect.Float64: + return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 64), true + case reflect.Bool: + return strconv.AppendBool(buf, rv.Bool()), true + case reflect.String: + s := rv.String() + return append(buf, s...), true + } + return +} diff --git a/driver/opt_preupdate_hook_callback.go b/driver/opt_preupdate_hook_callback.go new file mode 100644 index 0000000..0e10330 --- /dev/null +++ b/driver/opt_preupdate_hook_callback.go @@ -0,0 +1,34 @@ +// Copyright (C) 2018 The Go-SQLite3 Authors. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build cgo +// +build !libsqlite3 +// +build sqlite_preupdate_hook + +package sqlite3 + +/* +#cgo CFLAGS: -DSQLITE_ENABLE_PREUPDATE_HOOK +#cgo LDFLAGS: -lm + +#include +#include +*/ +import "C" + +//export preUpdateHookTrampoline +func preUpdateHookTrampoline(handle uintptr, dbHandle uintptr, op int, db *C.char, table *C.char, oldrowid int64, newrowid int64) { + hval := lookupHandleVal(handle) + data := SQLitePreUpdateData{ + Conn: hval.db, + Op: op, + DatabaseName: C.GoString(db), + TableName: C.GoString(table), + OldRowID: oldrowid, + NewRowID: newrowid, + } + callback := hval.val.(func(SQLitePreUpdateData)) + callback(data) +} diff --git a/driver/opt_preupdate_hook_conn.go b/driver/opt_preupdate_hook_conn.go new file mode 100644 index 0000000..4509161 --- /dev/null +++ b/driver/opt_preupdate_hook_conn.go @@ -0,0 +1,40 @@ +// Copyright (C) 2018 The Go-SQLite3 Authors. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build cgo +// +build !libsqlite3 +// +build sqlite_preupdate_hook + +package sqlite3 + +/* +#cgo CFLAGS: -DSQLITE_ENABLE_PREUPDATE_HOOK +#cgo LDFLAGS: -lm + +#include +#include + +void preUpdateHookTrampoline(void*, sqlite3 *, int, char *, char *, sqlite3_int64, sqlite3_int64); +*/ +import "C" +import ( + "unsafe" +) + +// RegisterPreUpdateHook sets the pre-update hook for a connection. +// +// The callback is passed a SQLitePreUpdateData struct with the data for +// the update, as well as methods for fetching copies of impacted data. +// +// If there is an existing update hook for this connection, it will be +// removed. If callback is nil the existing hook (if any) will be removed +// without creating a new one. +func (c *SQLiteConn) RegisterPreUpdateHook(callback func(SQLitePreUpdateData)) { + if callback == nil { + C.sqlite3_preupdate_hook(c.db, nil, nil) + } else { + C.sqlite3_preupdate_hook(c.db, (*[0]byte)(unsafe.Pointer(C.preUpdateHookTrampoline)), unsafe.Pointer(newHandle(c, callback))) + } +} diff --git a/driver/opt_preupdate_hook_omit.go b/driver/opt_preupdate_hook_omit.go new file mode 100644 index 0000000..d6d1dbb --- /dev/null +++ b/driver/opt_preupdate_hook_omit.go @@ -0,0 +1,22 @@ +// Copyright (C) 2018 The Go-SQLite3 Authors. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build cgo +// +build !libsqlite3 +// +build !sqlite_preupdate_hook + +package sqlite3 + +// RegisterPreUpdateHook sets the pre-update hook for a connection. +// +// The callback is passed a SQLitePreUpdateData struct with the data for +// the update, as well as methods for fetching copies of impacted data. +// +// If there is an existing update hook for this connection, it will be +// removed. If callback is nil the existing hook (if any) will be removed +// without creating a new one. +func (c *SQLiteConn) RegisterPreUpdateHook(callback func(SQLitePreUpdateData)) { + // NO-OP +} diff --git a/driver/opt_preupdate_hook_test.go b/driver/opt_preupdate_hook_test.go new file mode 100644 index 0000000..8b256ff --- /dev/null +++ b/driver/opt_preupdate_hook_test.go @@ -0,0 +1,129 @@ +// Copyright (C) 2018 The Go-SQLite3 Authors. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// +build cgo +// +build !libsqlite3 +// +build sqlite_preupdate_hook + +package sqlite3 + +import ( + "database/sql" + "testing" +) + +type preUpdateHookDataForTest struct { + databaseName string + tableName string + count int + op int + oldRow []interface{} + newRow []interface{} +} + +func TestPreUpdateHook(t *testing.T) { + var events []preUpdateHookDataForTest + + sql.Register("sqlite3_PreUpdateHook", &SQLiteDriver{ + ConnectHook: func(conn *SQLiteConn) error { + conn.RegisterPreUpdateHook(func(data SQLitePreUpdateData) { + eval := -1 + oldRow := []interface{}{eval} + if data.Op != SQLITE_INSERT { + err := data.Old(oldRow...) + if err != nil { + t.Fatalf("Unexpected error calling SQLitePreUpdateData.Old: %v", err) + } + } + + eval2 := -1 + newRow := []interface{}{eval2} + if data.Op != SQLITE_DELETE { + err := data.New(newRow...) + if err != nil { + t.Fatalf("Unexpected error calling SQLitePreUpdateData.New: %v", err) + } + } + + // tests dest bound checks in loop + var tooSmallRow []interface{} + if data.Op != SQLITE_INSERT { + err := data.Old(tooSmallRow...) + if err != nil { + t.Fatalf("Unexpected error calling SQLitePreUpdateData.Old: %v", err) + } + if len(tooSmallRow) != 0 { + t.Errorf("Expected tooSmallRow to be empty, got: %v", tooSmallRow) + } + } + + events = append(events, preUpdateHookDataForTest{ + databaseName: data.DatabaseName, + tableName: data.TableName, + count: data.Count(), + op: data.Op, + oldRow: oldRow, + newRow: newRow, + }) + }) + return nil + }, + }) + + db, err := sql.Open("sqlite3_PreUpdateHook", ":memory:") + if err != nil { + t.Fatal("Failed to open database:", err) + } + defer db.Close() + + statements := []string{ + "create table foo (id integer primary key)", + "insert into foo values (9)", + "update foo set id = 99 where id = 9", + "delete from foo where id = 99", + } + for _, statement := range statements { + _, err = db.Exec(statement) + if err != nil { + t.Fatalf("Unable to prepare test data [%v]: %v", statement, err) + } + } + + if len(events) != 3 { + t.Errorf("Events should be 3 entries, got: %d", len(events)) + } + + if events[0].op != SQLITE_INSERT { + t.Errorf("Op isn't as expected: %v", events[0].op) + } + + if events[1].op != SQLITE_UPDATE { + t.Errorf("Op isn't as expected: %v", events[1].op) + } + + if events[1].count != 1 { + t.Errorf("Expected event row 1 to have 1 column, had: %v", events[1].count) + } + + newRow_0_0 := events[0].newRow[0].(int64) + if newRow_0_0 != 9 { + t.Errorf("Expected event row 0 new column 0 to be == 9, got: %v", newRow_0_0) + } + + oldRow_1_0 := events[1].oldRow[0].(int64) + if oldRow_1_0 != 9 { + t.Errorf("Expected event row 1 old column 0 to be == 9, got: %v", oldRow_1_0) + } + + newRow_1_0 := events[1].newRow[0].(int64) + if newRow_1_0 != 99 { + t.Errorf("Expected event row 1 new column 0 to be == 99, got: %v", newRow_1_0) + } + + oldRow_2_0 := events[2].oldRow[0].(int64) + if oldRow_2_0 != 99 { + t.Errorf("Expected event row 1 new column 0 to be == 99, got: %v", oldRow_2_0) + } +} diff --git a/driver/pragma.go b/driver/pragma.go index f4afd5c..fede374 100644 --- a/driver/pragma.go +++ b/driver/pragma.go @@ -8,6 +8,7 @@ package sqlite3 const ( + PRAGMA_SSE_KEY = "key" PRAGMA_AUTO_VACUUM = "auto_vacuum" PRAGMA_CASE_SENSITIVE_LIKE = "case_sensitive_like" PRAGMA_DEFER_FOREIGN_KEYS = "defer_foreign_keys"