├── .github └── workflows │ └── go.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── appveyor.yml ├── auth.go ├── auth_test.go ├── backup.go ├── backup_test.go ├── blob.go ├── blob_test.go ├── blocking_step.c ├── blocking_step.h ├── c ├── dummy.go └── sqlite3.c ├── doc.go ├── dummy.go ├── error.go ├── export_test.go ├── extension.go ├── extension_test.go ├── func.go ├── func_test.go ├── go.mod ├── go.sum ├── incrementor.go ├── incrementor_test.go ├── link.go ├── openflags.go ├── session.go ├── session_test.go ├── snapshot.go ├── snapshot_test.go ├── sqlite.go ├── sqlite3.h ├── sqlite3ext.h ├── sqlite_test.go ├── sqlitex ├── blob-readwriter.go ├── exec.go ├── exec_test.go ├── pool.go ├── pool_test.go ├── query.go ├── query_test.go ├── savepoint.go ├── savepoint_test.go └── snapshot.go ├── static.go ├── wrappers.c ├── wrappers.h └── zombiezen-compat.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v3 21 | with: 22 | go-version: 1.19 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | 27 | - name: Test 28 | run: go test -v ./... 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | sqlite3.o 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: "go" 2 | 3 | go: 4 | - "1.13.x" 5 | 6 | os: 7 | - "linux" 8 | - "osx" 9 | 10 | script: 11 | - make test 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 David Crawshaw 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ## Copyright (c) 2018 David Crawshaw 2 | ## 3 | ## Permission to use, copy, modify, and distribute this software for any 4 | ## purpose with or without fee is hereby granted, provided that the above 5 | ## copyright notice and this permission notice appear in all copies. 6 | ## 7 | ## THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | ## WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | ## MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ## ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | ## WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ## ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | ## OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | # This Makefile is simply for development purposes. Normally, when this package 16 | # is imported, Go will build the ./c/sqlite.c file that is included directly by 17 | # static.go. However this is pretty slow ~30s. When developing this is very 18 | # annoying. Use this Makefile to pre-build the sqlite3.o object and then build 19 | # the package with the build tag linksqlite3, which will ignore static.go and 20 | # use link.go instead to link against sqlite.o. This reduces compilation times 21 | # down to <3 sec! 22 | # 23 | # If you are using an editor that builds the project as you work on it, you'll 24 | # want to build the sqlite3.o object and tell your editor to use the 25 | # linksqlite3 go build tag when working on this project. 26 | # For vim-go, use the command `GoBuildTags linksqlite3` or 27 | # `let g:go_build_tags = # 'linksqlite3'` 28 | 29 | export GOFLAGS=-tags=linksqlite3 30 | 31 | .PHONY: clean all env test release 32 | all: sqlite3.o 33 | go build ./... 34 | 35 | test: sqlite3.o 36 | go test ./... 37 | 38 | test-race: sqlite3.o 39 | go test -race ./... 40 | env: 41 | go env 42 | 43 | ## This builds the package statically. 44 | release: 45 | go build -tags=!linksqlite3 46 | 47 | VPATH = ./c # Look in ./c for source files 48 | 49 | # !!! THESE DEFINES SHOULD MATCH sqlite.go for linux !!! 50 | CFLAGS += -std=c99 51 | CFLAGS += -DSQLITE_THREADSAFE=2 52 | CFLAGS += -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 53 | CFLAGS += -DSQLITE_ENABLE_UNLOCK_NOTIFY 54 | CFLAGS += -DSQLITE_ENABLE_FTS5 55 | CFLAGS += -DSQLITE_ENABLE_RTREE 56 | CFLAGS += -DSQLITE_LIKE_DOESNT_MATCH_BLOBS 57 | CFLAGS += -DSQLITE_OMIT_DEPRECATED 58 | CFLAGS += -DSQLITE_ENABLE_JSON1 59 | CFLAGS += -DSQLITE_ENABLE_SESSION 60 | CFLAGS += -DSQLITE_ENABLE_SNAPSHOT 61 | CFLAGS += -DSQLITE_ENABLE_PREUPDATE_HOOK 62 | CFLAGS += -DSQLITE_USE_ALLOCA 63 | CFLAGS += -DSQLITE_ENABLE_COLUMN_METADATA 64 | CFLAGS += -DHAVE_USLEEP=1 65 | CFLAGS += -DSQLITE_DQS=0 66 | CFLAGS += -DSQLITE_ENABLE_GEOPOLY 67 | LDFLAGS = -ldl -lm 68 | # !!! THESE DEFINES SHOULD MATCH sqlite.go !!! 69 | 70 | sqlite3.o: sqlite3.c sqlite3.h sqlite3ext.h 71 | 72 | 73 | clean: 74 | rm -f sqlite3.o 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Low-level Go interface to SQLite 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/go-llsqlite/llsqlite.svg)](https://pkg.go.dev/github.com/go-llsqlite/llsqlite) 4 | 5 | This project is a community-managed fork of https://github.com/crawshaw/sqlite. 6 | 7 | This package provides a low-level Go interface to SQLite 3. Connections are [pooled](https://pkg.go.dev/github.com/go-llsqlite/llsqlite#Pool) and if the SQLite [shared cache](https://www.sqlite.org/sharedcache.html) mode is enabled the package takes advantage of the [unlock-notify API](https://www.sqlite.org/unlock_notify.html) to minimize the amount of handling user code needs for dealing with database lock contention. 8 | 9 | It has interfaces for some of SQLite's more interesting extensions, such as [incremental BLOB I/O](https://www.sqlite.org/c3ref/blob_open.html) and the [session extension](https://www.sqlite.org/sessionintro.html). 10 | 11 | A utility package, [sqlitex](https://pkg.go.dev/github.com/go-llsqlite/llsqlite/sqlitex), provides some higher-level tools for making it easier to perform common tasks with SQLite. In particular it provides support to make nested transactions easy to use via [sqlitex.Save](https://pkg.go.dev/github.com/go-llsqlite/llsqlite/sqlitex#Save). 12 | 13 | This is not a database/sql driver. 14 | 15 | ```go get -u github.com/go-llsqlite/llsqlite``` 16 | 17 | ## Example 18 | 19 | A HTTP handler that uses a multi-threaded pool of SQLite connections via a shared cache. 20 | 21 | ```go 22 | var dbpool *sqlitex.Pool 23 | 24 | func main() { 25 | var err error 26 | dbpool, err = sqlitex.Open("file:memory:?mode=memory", 0, 10) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | http.HandleFunc("/", handler) 31 | log.Fatal(http.ListenAndServe(":8080", nil)) 32 | } 33 | 34 | func handler(w http.ResponseWriter, r *http.Request) { 35 | conn := dbpool.Get(r.Context()) 36 | if conn == nil { 37 | return 38 | } 39 | defer dbpool.Put(conn) 40 | stmt := conn.Prep("SELECT foo FROM footable WHERE id = $id;") 41 | stmt.SetText("$id", "_user_id_") 42 | for { 43 | if hasRow, err := stmt.Step(); err != nil { 44 | // ... handle error 45 | } else if !hasRow { 46 | break 47 | } 48 | foo := stmt.GetText("foo") 49 | // ... use foo 50 | } 51 | } 52 | ``` 53 | 54 | https://pkg.go.dev/github.com/go-llsqlite/llsqlite 55 | 56 | ## Platform specific considerations 57 | 58 | By default it requires some pthreads DLL on Windows. To avoid it, supply `CGO_LDFLAGS="-static"` when building your application. 59 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | platform: "mingw" 3 | 4 | # Source Config 5 | 6 | clone_folder: C:\gopath\src\crawshaw.io\sqlite 7 | 8 | # Build host 9 | 10 | environment: 11 | GOPATH: C:\gopath 12 | 13 | # Build 14 | 15 | install: 16 | - set PATH=C:\go\bin;C:\gopath\bin;C:\mingw-w64\x86_64-7.3.0-posix-seh-rt_v5-rev0\mingw64\bin;%PATH% 17 | - go version 18 | - go env 19 | 20 | build: false 21 | deploy: false 22 | 23 | test_script: 24 | - go get -v -t ./... 25 | - go test -v ./... -------------------------------------------------------------------------------- /auth.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | // #include 4 | // #include 5 | // #include "wrappers.h" 6 | // 7 | // static int sqlite3_go_set_authorizer(sqlite3* conn, uintptr_t id) { 8 | // return sqlite3_set_authorizer(conn, c_auth_tramp, (void*)id); 9 | // } 10 | import "C" 11 | import ( 12 | "errors" 13 | "fmt" 14 | "strings" 15 | "sync" 16 | ) 17 | 18 | // An Authorizer is called during statement preparation to see whether an action 19 | // is allowed by the application. See https://sqlite.org/c3ref/set_authorizer.html 20 | type Authorizer interface { 21 | Authorize(info ActionInfo) AuthResult 22 | } 23 | 24 | // SetAuthorizer registers an authorizer for the database connection. 25 | // SetAuthorizer(nil) clears any authorizer previously set. 26 | func (conn *Conn) SetAuthorizer(auth Authorizer) error { 27 | if auth == nil { 28 | if conn.authorizer == -1 { 29 | return nil 30 | } 31 | conn.releaseAuthorizer() 32 | res := C.sqlite3_set_authorizer(conn.conn, nil, nil) 33 | return reserr("SetAuthorizer", "", "", res) 34 | } 35 | 36 | authFuncs.mu.Lock() 37 | id := authFuncs.next 38 | next := authFuncs.next + 1 39 | if next < 0 { 40 | authFuncs.mu.Unlock() 41 | return errors.New("sqlite: authorizer function id overflow") 42 | } 43 | authFuncs.next = next 44 | authFuncs.m[id] = auth 45 | authFuncs.mu.Unlock() 46 | 47 | res := C.sqlite3_go_set_authorizer(conn.conn, C.uintptr_t(id)) 48 | return reserr("SetAuthorizer", "", "", res) 49 | } 50 | 51 | func (conn *Conn) releaseAuthorizer() { 52 | if conn.authorizer == -1 { 53 | return 54 | } 55 | authFuncs.mu.Lock() 56 | delete(authFuncs.m, conn.authorizer) 57 | authFuncs.mu.Unlock() 58 | conn.authorizer = -1 59 | } 60 | 61 | var authFuncs = struct { 62 | mu sync.RWMutex 63 | m map[int]Authorizer 64 | next int 65 | }{ 66 | m: make(map[int]Authorizer), 67 | } 68 | 69 | //export go_sqlite_auth_tramp 70 | func go_sqlite_auth_tramp(id uintptr, action C.int, cArg1, cArg2 *C.char, cDB *C.char, cTrigger *C.char) C.int { 71 | authFuncs.mu.RLock() 72 | auth := authFuncs.m[int(id)] 73 | authFuncs.mu.RUnlock() 74 | var arg1, arg2, database, trigger string 75 | if cArg1 != nil { 76 | arg1 = C.GoString(cArg1) 77 | } 78 | if cArg2 != nil { 79 | arg2 = C.GoString(cArg2) 80 | } 81 | if cDB != nil { 82 | database = C.GoString(cDB) 83 | } 84 | if cTrigger != nil { 85 | trigger = C.GoString(cTrigger) 86 | } 87 | return C.int(auth.Authorize(newActionInfo(OpType(action), arg1, arg2, database, trigger))) 88 | } 89 | 90 | // AuthorizeFunc is a function that implements Authorizer. 91 | type AuthorizeFunc func(info ActionInfo) AuthResult 92 | 93 | // Authorize calls f. 94 | func (f AuthorizeFunc) Authorize(info ActionInfo) AuthResult { 95 | return f(info) 96 | } 97 | 98 | // AuthResult is the result of a call to an Authorizer. The zero value is 99 | // SQLITE_OK. 100 | type AuthResult int 101 | 102 | // Possible return values of an Authorizer. 103 | const ( 104 | // Cause the entire SQL statement to be rejected with an error. 105 | SQLITE_DENY = AuthResult(C.SQLITE_DENY) 106 | // Disallow the specific action but allow the SQL statement to continue to 107 | // be compiled. 108 | SQLITE_IGNORE = AuthResult(C.SQLITE_IGNORE) 109 | ) 110 | 111 | // String returns the C constant name of the result. 112 | func (result AuthResult) String() string { 113 | switch result { 114 | default: 115 | var buf [20]byte 116 | return "SQLITE_UNKNOWN_AUTH_RESULT(" + string(itoa(buf[:], int64(result))) + ")" 117 | case AuthResult(C.SQLITE_OK): 118 | return "SQLITE_OK" 119 | case SQLITE_DENY: 120 | return "SQLITE_DENY" 121 | case SQLITE_IGNORE: 122 | return "SQLITE_IGNORE" 123 | } 124 | } 125 | 126 | // ActionInfo holds information about an action to be authorized. 127 | // 128 | // Only the fields relevant to the Action are populated when this is passed to 129 | // an Authorizer. 130 | // 131 | // https://sqlite.org/c3ref/c_alter_table.html 132 | type ActionInfo struct { 133 | Action OpType 134 | 135 | Index string 136 | Table string 137 | Column string 138 | Trigger string 139 | View string 140 | Function string 141 | Pragma string 142 | PragmaArg string 143 | Operation string 144 | Filename string 145 | Module string 146 | Database string 147 | Savepoint string 148 | 149 | InnerMostTrigger string 150 | } 151 | 152 | // newActionInfo returns an ActionInfo with the correct fields relevant to the 153 | // action. 154 | func newActionInfo(action OpType, arg1, arg2, database, trigger string) ActionInfo { 155 | 156 | // We use the blank identifier with unused args below simply for visual 157 | // consistency between the cases. 158 | 159 | a := ActionInfo{Action: action, Database: database, InnerMostTrigger: trigger} 160 | switch action { 161 | case SQLITE_DROP_INDEX, 162 | SQLITE_DROP_TEMP_INDEX, 163 | SQLITE_CREATE_INDEX, 164 | SQLITE_CREATE_TEMP_INDEX: 165 | /* Index Name Table Name */ 166 | a.Index = arg1 167 | a.Table = arg2 168 | 169 | case SQLITE_DELETE, 170 | SQLITE_DROP_TABLE, 171 | SQLITE_DROP_TEMP_TABLE, 172 | SQLITE_INSERT, 173 | SQLITE_ANALYZE, 174 | SQLITE_CREATE_TABLE, 175 | SQLITE_CREATE_TEMP_TABLE: 176 | /* Table Name NULL */ 177 | a.Table = arg1 178 | _ = arg2 179 | 180 | case SQLITE_CREATE_TEMP_TRIGGER, 181 | SQLITE_CREATE_TRIGGER, 182 | SQLITE_DROP_TEMP_TRIGGER, 183 | SQLITE_DROP_TRIGGER: 184 | /* Trigger Name Table Name */ 185 | a.Trigger = arg1 186 | a.Table = arg2 187 | 188 | case SQLITE_CREATE_TEMP_VIEW, 189 | SQLITE_CREATE_VIEW, 190 | SQLITE_DROP_TEMP_VIEW, 191 | SQLITE_DROP_VIEW: 192 | /* View Name NULL */ 193 | a.View = arg1 194 | _ = arg2 195 | 196 | case SQLITE_PRAGMA: 197 | /* Pragma Name 1st arg or NULL */ 198 | a.Pragma = arg1 199 | a.PragmaArg = arg2 200 | 201 | case SQLITE_READ, 202 | SQLITE_UPDATE: 203 | /* Table Name Column Name */ 204 | a.Table = arg1 205 | a.Column = arg2 206 | 207 | case SQLITE_TRANSACTION: 208 | /* Operation NULL */ 209 | a.Operation = arg1 210 | _ = arg2 211 | 212 | case SQLITE_ATTACH: 213 | /* Filename NULL */ 214 | a.Filename = arg1 215 | _ = arg2 216 | 217 | case SQLITE_DETACH: 218 | /* Database Name NULL */ 219 | a.Database = arg1 220 | _ = arg2 221 | 222 | case SQLITE_ALTER_TABLE: 223 | /* Database Name Table Name */ 224 | a.Database = arg1 225 | a.Table = arg2 226 | 227 | case SQLITE_REINDEX: 228 | /* Index Name NULL */ 229 | a.Index = arg1 230 | _ = arg2 231 | 232 | case SQLITE_CREATE_VTABLE, 233 | SQLITE_DROP_VTABLE: 234 | /* Table Name Module Name */ 235 | a.Table = arg1 236 | a.Module = arg2 237 | 238 | case SQLITE_FUNCTION: 239 | /* NULL Function Name */ 240 | _ = arg1 241 | a.Function = arg2 242 | 243 | case SQLITE_SAVEPOINT: 244 | /* Operation Savepoint Name */ 245 | a.Operation = arg1 246 | a.Savepoint = arg2 247 | 248 | case SQLITE_RECURSIVE, 249 | SQLITE_SELECT: 250 | /* NULL NULL */ 251 | _ = arg1 252 | _ = arg2 253 | 254 | case SQLITE_COPY: 255 | /* No longer used */ 256 | default: 257 | panic(fmt.Errorf("unknown action: %v", action)) 258 | } 259 | return a 260 | } 261 | 262 | // String returns a string describing only the fields relevant to `a.Action`. 263 | func (a ActionInfo) String() string { 264 | 265 | switch a.Action { 266 | case SQLITE_DROP_INDEX, 267 | SQLITE_DROP_TEMP_INDEX, 268 | SQLITE_CREATE_INDEX, 269 | SQLITE_CREATE_TEMP_INDEX: 270 | /* Index Name Table Name */ 271 | return fmt.Sprintf("%v: Index: %q Table: %q", 272 | a.Action, a.Index, a.Table) 273 | 274 | case SQLITE_DELETE, 275 | SQLITE_DROP_TABLE, 276 | SQLITE_DROP_TEMP_TABLE, 277 | SQLITE_INSERT, 278 | SQLITE_ANALYZE, 279 | SQLITE_CREATE_TABLE, 280 | SQLITE_CREATE_TEMP_TABLE: 281 | /* Table Name NULL */ 282 | return fmt.Sprintf("%v: Table: %q", a.Action, a.Table) 283 | 284 | case SQLITE_CREATE_TEMP_TRIGGER, 285 | SQLITE_CREATE_TRIGGER, 286 | SQLITE_DROP_TEMP_TRIGGER, 287 | SQLITE_DROP_TRIGGER: 288 | /* Trigger Name Table Name */ 289 | return fmt.Sprintf("%v: Trigger: %q Table: %q", 290 | a.Action, a.Trigger, a.Table) 291 | 292 | case SQLITE_CREATE_TEMP_VIEW, 293 | SQLITE_CREATE_VIEW, 294 | SQLITE_DROP_TEMP_VIEW, 295 | SQLITE_DROP_VIEW: 296 | /* View Name NULL */ 297 | return fmt.Sprintf("%v: View: %q", a.Action, a.View) 298 | 299 | case SQLITE_PRAGMA: 300 | /* Pragma Name 1st arg or NULL */ 301 | return fmt.Sprintf("%v: Pragma: %q", 302 | a.Action, strings.TrimSpace(a.Pragma+" "+a.PragmaArg)) 303 | 304 | case SQLITE_READ, 305 | SQLITE_UPDATE: 306 | /* Table Name Column Name */ 307 | return fmt.Sprintf("%v: Table: %q Column: %q", 308 | a.Action, a.Table, a.Column) 309 | 310 | case SQLITE_TRANSACTION: 311 | /* Operation NULL */ 312 | return fmt.Sprintf("%v: Operation: %q", a.Action, a.Operation) 313 | 314 | case SQLITE_ATTACH: 315 | /* Filename NULL */ 316 | return fmt.Sprintf("%v: Filename: %q", a.Action, a.Filename) 317 | 318 | case SQLITE_DETACH: 319 | /* Database Name NULL */ 320 | return fmt.Sprintf("%v: Database: %q", a.Action, a.Database) 321 | 322 | case SQLITE_ALTER_TABLE: 323 | /* Database Name Table Name */ 324 | return fmt.Sprintf("%v: Database: %q Table: %q", 325 | a.Action, a.Database, a.Table) 326 | 327 | case SQLITE_REINDEX: 328 | /* Index Name NULL */ 329 | return fmt.Sprintf("%v: Index: %q", a.Action, a.Index) 330 | 331 | case SQLITE_CREATE_VTABLE, 332 | SQLITE_DROP_VTABLE: 333 | /* Table Name Module Name */ 334 | return fmt.Sprintf("%v: Table: %q Module: %q", 335 | a.Action, a.Table, a.Module) 336 | 337 | case SQLITE_FUNCTION: 338 | /* NULL Function Name */ 339 | return fmt.Sprintf("%v: Function: %q", a.Action, a.Function) 340 | 341 | case SQLITE_SAVEPOINT: 342 | /* Operation Savepoint Name */ 343 | return fmt.Sprintf("%v: Operation: %q Savepoint: %q", 344 | a.Action, a.Operation, a.Savepoint) 345 | 346 | case SQLITE_RECURSIVE, 347 | SQLITE_SELECT: 348 | /* NULL NULL */ 349 | return fmt.Sprintf("%v:", a.Action) 350 | 351 | case SQLITE_COPY: 352 | /* No longer used */ 353 | return fmt.Sprintf("%v:", a.Action) 354 | default: 355 | return fmt.Sprintf("unknown action: %v", a.Action) 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /auth_test.go: -------------------------------------------------------------------------------- 1 | package sqlite_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/go-llsqlite/crawshaw" 7 | ) 8 | 9 | func TestSetAuthorizer(t *testing.T) { 10 | c, err := sqlite.OpenConn(":memory:", 0) 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | defer func() { 15 | if err := c.Close(); err != nil { 16 | t.Error(err) 17 | } 18 | }() 19 | 20 | authResult := sqlite.AuthResult(0) 21 | var lastAction sqlite.OpType 22 | auth := sqlite.AuthorizeFunc(func(info sqlite.ActionInfo) sqlite.AuthResult { 23 | lastAction = info.Action 24 | return authResult 25 | }) 26 | c.SetAuthorizer(auth) 27 | 28 | t.Run("Allowed", func(t *testing.T) { 29 | authResult = 0 30 | stmt, _, err := c.PrepareTransient("SELECT 1;") 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | stmt.Finalize() 35 | if lastAction != sqlite.SQLITE_SELECT { 36 | t.Errorf("action = %q; want SQLITE_SELECT", lastAction) 37 | } 38 | }) 39 | 40 | t.Run("Denied", func(t *testing.T) { 41 | authResult = sqlite.SQLITE_DENY 42 | stmt, _, err := c.PrepareTransient("SELECT 1;") 43 | if err == nil { 44 | stmt.Finalize() 45 | t.Fatal("PrepareTransient did not return an error") 46 | } 47 | if got, want := sqlite.ErrCode(err), sqlite.SQLITE_AUTH; got != want { 48 | t.Errorf("sqlite.ErrCode(err) = %v; want %v", got, want) 49 | } 50 | if lastAction != sqlite.SQLITE_SELECT { 51 | t.Errorf("action = %q; want SQLITE_SELECT", lastAction) 52 | } 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /backup.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlite 16 | 17 | // #include 18 | // #include 19 | import "C" 20 | import ( 21 | "runtime" 22 | "unsafe" 23 | ) 24 | 25 | // A Backup copies data between two databases. 26 | // 27 | // It is used to backup file based or in-memory databases. 28 | // 29 | // Equivalent to the sqlite3_backup* C object. 30 | // 31 | // https://www.sqlite.org/c3ref/backup_finish.html 32 | type Backup struct { 33 | ptr *C.sqlite3_backup 34 | } 35 | 36 | // BackupToDB creates a complete backup of the srcDB on the src Conn to a new 37 | // database Conn at dstPath. The resulting dst connection is returned. This 38 | // will block until the entire backup is complete. 39 | // 40 | // If srcDB is "", then a default of "main" is used. 41 | // 42 | // This is very similar to the first example function implemented on the 43 | // following page. 44 | // 45 | // https://www.sqlite.org/backup.html 46 | func (src *Conn) BackupToDB(srcDB, dstPath string) (dst *Conn, err error) { 47 | if dst, err = OpenConn(dstPath, 0); err != nil { 48 | return 49 | } 50 | defer func() { 51 | if err != nil { 52 | dst.Close() 53 | } 54 | }() 55 | b, err := src.BackupInit(srcDB, "", dst) 56 | if err != nil { 57 | return 58 | } 59 | defer b.Finish() 60 | err = b.Step(-1) 61 | return 62 | } 63 | 64 | // BackupInit initializes a new Backup object to copy from src to dst. 65 | // 66 | // If srcDB or dstDB is "", then a default of "main" is used. 67 | // 68 | // https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupinit 69 | func (src *Conn) BackupInit(srcDB, dstDB string, dst *Conn) (*Backup, error) { 70 | var srcCDB, dstCDB *C.char 71 | defer setCDB(dstDB, &dstCDB)() 72 | defer setCDB(srcDB, &srcCDB)() 73 | var b Backup 74 | b.ptr = C.sqlite3_backup_init(dst.conn, dstCDB, src.conn, srcCDB) 75 | if b.ptr == nil { 76 | res := C.sqlite3_errcode(dst.conn) 77 | return nil, dst.extreserr("Conn.BackupInit", "", res) 78 | } 79 | runtime.SetFinalizer(&b, func(b *Backup) { 80 | if b.ptr != nil { 81 | panic("open *sqlite.Backup garbage collected, call Finish method") 82 | } 83 | }) 84 | 85 | return &b, nil 86 | } 87 | func setCDB(db string, cdb **C.char) func() { 88 | if db == "" || db == "main" { 89 | *cdb = cmain 90 | return func() {} 91 | } 92 | *cdb = C.CString(db) 93 | return func() { C.free(unsafe.Pointer(cdb)) } 94 | } 95 | 96 | // Step is called one or more times to transfer nPage pages at a time between 97 | // databases. 98 | // 99 | // Use -1 to transfer the entire database at once. 100 | // 101 | // https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupstep 102 | func (b *Backup) Step(nPage int) error { 103 | res := C.sqlite3_backup_step(b.ptr, C.int(nPage)) 104 | if res != C.SQLITE_DONE { 105 | return reserr("Backup.Step", "", "", res) 106 | } 107 | return nil 108 | } 109 | 110 | // Finish is called to clean up the resources allocated by BackupInit. 111 | // 112 | // https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish 113 | func (b *Backup) Finish() error { 114 | res := C.sqlite3_backup_finish(b.ptr) 115 | b.ptr = nil 116 | return reserr("Backup.Finish", "", "", res) 117 | } 118 | 119 | // Remaining returns the number of pages still to be backed up at the 120 | // conclusion of the most recent b.Step(). 121 | // 122 | // https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupremaining 123 | func (b *Backup) Remaining() int { 124 | return int(C.sqlite3_backup_remaining(b.ptr)) 125 | } 126 | 127 | // PageCount returns the total number of pages in the source database at the 128 | // conclusion of the most recent b.Step(). 129 | // 130 | // https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backuppagecount 131 | func (b *Backup) PageCount() int { 132 | return int(C.sqlite3_backup_pagecount(b.ptr)) 133 | } 134 | -------------------------------------------------------------------------------- /backup_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlite_test 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/go-llsqlite/crawshaw" 21 | "github.com/go-llsqlite/crawshaw/sqlitex" 22 | ) 23 | 24 | func initSrc(t *testing.T) *sqlite.Conn { 25 | conn, err := sqlite.OpenConn(`:memory:`, 0) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | if err := sqlitex.ExecScript(conn, `CREATE TABLE t (c1 PRIMARY KEY, c2, c3); 30 | INSERT INTO t (c1, c2, c3) VALUES (1, 2, 3); 31 | INSERT INTO t (c1, c2, c3) VALUES (2, 4, 5);`); err != nil { 32 | conn.Close() 33 | t.Fatal(err) 34 | } 35 | return conn 36 | } 37 | 38 | func TestBackup(t *testing.T) { 39 | conn := initSrc(t) 40 | defer conn.Close() 41 | 42 | copyConn, err := conn.BackupToDB("", ":memory:") 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | defer copyConn.Close() 47 | 48 | count, err := sqlitex.ResultInt(copyConn.Prep(`SELECT count(*) FROM t;`)) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | if count != 2 { 53 | t.Fatalf("expected 2 rows but found %v", count) 54 | } 55 | c2, err := sqlitex.ResultInt(copyConn.Prep(`SELECT c2 FROM t WHERE c1 = 1;`)) 56 | if c2 != 2 { 57 | t.Fatalf("expected row1 c2 to be 2 but found %v", c2) 58 | } 59 | 60 | c2, err = sqlitex.ResultInt(copyConn.Prep(`SELECT c2 FROM t WHERE c1 = 2;`)) 61 | if c2 != 4 { 62 | t.Fatalf("expected row2 c2 to be 4 but found %v", c2) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /blob.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlite 16 | 17 | // #include 18 | // #include 19 | // #include 20 | // #include 21 | import "C" 22 | import ( 23 | "errors" 24 | "fmt" 25 | "io" 26 | "unsafe" 27 | ) 28 | 29 | var cmain = C.CString("main") 30 | var ctemp = C.CString("temp") 31 | 32 | // OpenBlob opens a blob in a particular {database,table,column,row}. 33 | // 34 | // https://www.sqlite.org/c3ref/blob_open.html 35 | func (conn *Conn) OpenBlob(dbn, table, column string, row int64, write bool) (*Blob, error) { 36 | var cdb *C.char 37 | switch dbn { 38 | case "", "main": 39 | cdb = cmain 40 | case "temp": 41 | cdb = ctemp 42 | default: 43 | cdb = C.CString(dbn) 44 | defer C.free(unsafe.Pointer(cdb)) 45 | } 46 | var flags C.int 47 | if write { 48 | flags = 1 49 | } 50 | 51 | ctable := C.CString(table) 52 | ccolumn := C.CString(column) 53 | defer func() { 54 | C.free(unsafe.Pointer(ctable)) 55 | C.free(unsafe.Pointer(ccolumn)) 56 | }() 57 | 58 | blob := &Blob{conn: conn} 59 | 60 | for { 61 | conn.count++ 62 | if err := conn.interrupted("Conn.OpenBlob", ""); err != nil { 63 | return nil, err 64 | } 65 | switch res := C.sqlite3_blob_open(conn.conn, cdb, ctable, ccolumn, 66 | C.sqlite3_int64(row), flags, &blob.blob); res { 67 | case C.SQLITE_LOCKED_SHAREDCACHE: 68 | if res := C.wait_for_unlock_notify( 69 | conn.conn, conn.unlockNote); res != C.SQLITE_OK { 70 | return nil, conn.reserr("Conn.OpenBlob(Wait)", "", res) 71 | } 72 | // loop 73 | case C.SQLITE_OK: 74 | blob.size = int64(C.sqlite3_blob_bytes(blob.blob)) 75 | return blob, nil 76 | default: 77 | return nil, conn.extreserr("Conn.OpenBlob", "", res) 78 | } 79 | } 80 | } 81 | 82 | // Blob provides streaming access to SQLite blobs. 83 | type Blob struct { 84 | conn *Conn 85 | blob *C.sqlite3_blob 86 | size int64 87 | } 88 | 89 | func (blob *Blob) Reopen(rowid int64) (err error) { 90 | rc := C.sqlite3_blob_reopen(blob.blob, C.sqlite3_int64(rowid)) 91 | err = blob.conn.reserr("Blob.Reopen", "", rc) 92 | if err != nil { 93 | return 94 | } 95 | blob.setSize() 96 | return 97 | } 98 | 99 | func (blob *Blob) setSize() { 100 | blob.size = int64(C.sqlite3_blob_bytes(blob.blob)) 101 | } 102 | 103 | // https://www.sqlite.org/c3ref/blob_read.html 104 | func (blob *Blob) ReadAt(p []byte, off int64) (n int, err error) { 105 | if blob.blob == nil { 106 | return 0, ErrBlobClosed 107 | } 108 | if off < 0 { 109 | err = fmt.Errorf("bad offset %v", off) 110 | return 111 | } 112 | if off >= blob.size { 113 | err = io.EOF 114 | return 115 | } 116 | if err := blob.conn.interrupted("Blob.ReadAt", ""); err != nil { 117 | return 0, err 118 | } 119 | if int64(len(p)) > blob.size-off { 120 | p = p[:blob.size-off] 121 | } 122 | lenp := C.int(len(p)) 123 | res := C.sqlite3_blob_read(blob.blob, unsafe.Pointer(&p[0]), lenp, C.int(off)) 124 | if err := blob.conn.reserr("Blob.ReadAt", "", res); err != nil { 125 | return 0, err 126 | } 127 | n = len(p) 128 | if off+int64(len(p)) >= blob.size { 129 | err = io.EOF 130 | } 131 | return 132 | } 133 | 134 | // https://www.sqlite.org/c3ref/blob_write.html 135 | func (blob *Blob) WriteAt(p []byte, off int64) (n int, err error) { 136 | if blob.blob == nil { 137 | return 0, ErrBlobClosed 138 | } 139 | if err := blob.conn.interrupted("Blob.WriteAt", ""); err != nil { 140 | return 0, err 141 | } 142 | lenp := C.int(len(p)) 143 | res := C.sqlite3_blob_write(blob.blob, unsafe.Pointer(&p[0]), lenp, C.int(off)) 144 | if err := blob.conn.reserr("Blob.WriteAt", "", res); err != nil { 145 | return 0, err 146 | } 147 | return len(p), nil 148 | } 149 | 150 | // Size returns the total size of a blob. 151 | func (blob *Blob) Size() int64 { 152 | return blob.size 153 | } 154 | 155 | // https://www.sqlite.org/c3ref/blob_close.html 156 | func (blob *Blob) Close() error { 157 | if blob.blob == nil { 158 | return ErrBlobClosed 159 | } 160 | err := blob.conn.reserr("Blob.Close", "", C.sqlite3_blob_close(blob.blob)) 161 | blob.blob = nil 162 | return err 163 | } 164 | 165 | var ErrBlobClosed = errors.New("blob closed") 166 | -------------------------------------------------------------------------------- /blob_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlite_test 16 | 17 | import ( 18 | "bytes" 19 | "compress/gzip" 20 | "io" 21 | "io/ioutil" 22 | "reflect" 23 | "sync" 24 | "testing" 25 | "time" 26 | 27 | "github.com/go-llsqlite/crawshaw" 28 | "github.com/go-llsqlite/crawshaw/sqlitex" 29 | ) 30 | 31 | func TestBlob(t *testing.T) { 32 | c, err := sqlite.OpenConn(":memory:", 0) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | defer func() { 37 | if err := c.Close(); err != nil { 38 | t.Error(err) 39 | } 40 | }() 41 | 42 | if _, err := c.Prep("DROP TABLE IF EXISTS blobs;").Step(); err != nil { 43 | t.Fatal(err) 44 | } 45 | if _, err := c.Prep("CREATE TABLE blobs (col BLOB);").Step(); err != nil { 46 | t.Fatal(err) 47 | } 48 | 49 | stmt := c.Prep("INSERT INTO blobs (col) VALUES ($col);") 50 | stmt.SetZeroBlob("$col", 5) 51 | if _, err := stmt.Step(); err != nil { 52 | t.Fatal(err) 53 | } 54 | if err := stmt.Finalize(); err != nil { 55 | t.Error(err) 56 | } 57 | rowid := c.LastInsertRowID() 58 | t.Logf("blobs rowid: %d", rowid) 59 | 60 | b, err := c.OpenBlob("", "blobs", "col", rowid, true) 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | n, err := b.WriteAt([]byte{1, 2, 3}, 0) 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | if n != 3 { 69 | t.Fatalf("b.WriteAt n=%d, want 3", n) 70 | } 71 | n, err = b.WriteAt([]byte{2, 3, 4, 5}, 1) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | if n != 4 { 76 | t.Fatalf("b.WriteAt n=%d, want 4", n) 77 | } 78 | if size := b.Size(); size != 5 { 79 | t.Fatalf("b.Size=%d, want 5", size) 80 | } 81 | n, err = b.WriteAt([]byte{2, 3, 4, 5, 6}, 1) // too long 82 | if err == nil { 83 | t.Fatalf("WriteAt too long, but no error") 84 | } 85 | if err := b.Close(); err != nil { 86 | t.Error(err) 87 | } 88 | 89 | b, err = c.OpenBlob("", "blobs", "col", rowid, false) 90 | if err != nil { 91 | t.Fatal(err) 92 | } 93 | defer b.Close() 94 | got := make([]byte, 5) 95 | n, err = b.ReadAt(got, 0) 96 | if n != len(got) { 97 | t.Fatalf("b.ReadAt=%d, want len(got)=%d", n, len(got)) 98 | } 99 | want := []byte{1, 2, 3, 4, 5} 100 | if !reflect.DeepEqual(got, want) { 101 | t.Errorf("b.ReadAt got %v, want %v", got, want) 102 | } 103 | n, err = b.ReadAt(got[3:], 3) 104 | if n != len(got)-3 { 105 | t.Fatalf("b.ReadAt(got, 3)=%d, want len(got)-3=%d", n, len(got)-3) 106 | } 107 | if !reflect.DeepEqual(got, want) { 108 | t.Errorf("b.ReadAt(got, 3) %v, want %v", got, want) 109 | } 110 | } 111 | 112 | func TestConcurrentBlobSpins(t *testing.T) { 113 | flags := sqlite.SQLITE_OPEN_READWRITE | sqlite.SQLITE_OPEN_CREATE | sqlite.SQLITE_OPEN_URI | sqlite.SQLITE_OPEN_NOMUTEX | sqlite.SQLITE_OPEN_SHAREDCACHE 114 | c, err := sqlite.OpenConn("file::memory:?mode=memory", flags) 115 | if err != nil { 116 | t.Fatal(err) 117 | } 118 | c2, err := sqlite.OpenConn("file::memory:?mode=memory", flags) 119 | if err != nil { 120 | t.Fatal(err) 121 | } 122 | defer c.Close() 123 | defer c2.Close() 124 | 125 | if _, err := c.Prep("DROP TABLE IF EXISTS blobs;").Step(); err != nil { 126 | t.Fatal(err) 127 | } 128 | if _, err := c.Prep("CREATE TABLE blobs (col BLOB);").Step(); err != nil { 129 | t.Fatal(err) 130 | } 131 | 132 | stmt := c.Prep("INSERT INTO blobs (col) VALUES ($col);") 133 | stmt.SetZeroBlob("$col", 1024) 134 | if _, err := stmt.Step(); err != nil { 135 | t.Fatal(err) 136 | } 137 | blobRow1 := c.LastInsertRowID() 138 | if err := stmt.Reset(); err != nil { 139 | t.Fatal(err) 140 | } 141 | if _, err := stmt.Step(); err != nil { 142 | t.Fatal(err) 143 | } 144 | blobRow2 := c.LastInsertRowID() 145 | if err := stmt.Finalize(); err != nil { 146 | t.Error(err) 147 | } 148 | 149 | blob1, err := c.OpenBlob("", "blobs", "col", blobRow1, true) 150 | if err != nil { 151 | t.Errorf("OpenBlob: %v", err) 152 | return 153 | } 154 | go func() { 155 | time.Sleep(50 * time.Millisecond) 156 | blob1.Close() 157 | }() 158 | 159 | countBefore := sqlite.ConnCount(c2) 160 | blob2, err := c2.OpenBlob("", "blobs", "col", blobRow2, true) 161 | countAfter := sqlite.ConnCount(c2) 162 | if err != nil { 163 | t.Errorf("OpenBlob: %v", err) 164 | return 165 | } 166 | blob2.Close() 167 | 168 | if spins := countAfter - countBefore - 1; spins > 1 { 169 | t.Errorf("expecting no more than 1 spin, got %d", spins) 170 | } 171 | } 172 | 173 | // TestConcurrentBlobWrites looks for unexpected SQLITE_LOCKED errors 174 | // when using the (default) shared cache. 175 | func TestConcurrentBlobWrites(t *testing.T) { 176 | flags := sqlite.SQLITE_OPEN_READWRITE | sqlite.SQLITE_OPEN_CREATE | sqlite.SQLITE_OPEN_URI | sqlite.SQLITE_OPEN_NOMUTEX | sqlite.SQLITE_OPEN_SHAREDCACHE 177 | 178 | const numBlobs = 5 179 | c, err := sqlite.OpenConn("file::memory:?mode=memory", flags) 180 | if err != nil { 181 | t.Fatal(err) 182 | } 183 | defer c.Close() 184 | 185 | if _, err := c.Prep("DROP TABLE IF EXISTS blobs;").Step(); err != nil { 186 | t.Fatal(err) 187 | } 188 | if _, err := c.Prep("CREATE TABLE blobs (col BLOB);").Step(); err != nil { 189 | t.Fatal(err) 190 | } 191 | 192 | stmt := c.Prep("INSERT INTO blobs (col) VALUES ($col);") 193 | stmt.SetZeroBlob("$col", 1024) 194 | var blobRowIDs []int64 195 | for i := 0; i < numBlobs; i++ { 196 | if _, err := stmt.Step(); err != nil { 197 | t.Fatal(err) 198 | } 199 | blobRowIDs = append(blobRowIDs, c.LastInsertRowID()) 200 | if err := stmt.Reset(); err != nil { 201 | t.Fatal(err) 202 | } 203 | } 204 | if err := stmt.Finalize(); err != nil { 205 | t.Error(err) 206 | } 207 | 208 | var wg sync.WaitGroup 209 | for i := 0; i < numBlobs; i++ { 210 | wg.Add(1) 211 | go func(i int) { 212 | defer wg.Done() 213 | b := make([]byte, 1024) 214 | b[0] = byte(i) 215 | 216 | c, err := sqlite.OpenConn("file::memory:?mode=memory", flags) 217 | if err != nil { 218 | t.Fatal(err) 219 | } 220 | defer c.Close() 221 | 222 | blob, err := c.OpenBlob("", "blobs", "col", blobRowIDs[i], true) 223 | if err != nil { 224 | t.Errorf("OpenBlob: %v (i=%d)", err, i) 225 | return 226 | } 227 | defer blob.Close() 228 | for j := 0; j < 10; j++ { 229 | b[1] = byte(j) 230 | 231 | n, err := blob.WriteAt(b, 0) 232 | if err != nil { 233 | t.Errorf("Blob.WriteAt: %v (i=%d, j=%d)", err, i, j) 234 | return 235 | } 236 | if n != len(b) { 237 | t.Fatalf("n=%d, want %d (i=%d, j=%d)", n, len(b), i, j) 238 | } 239 | } 240 | }(i) 241 | } 242 | wg.Wait() 243 | } 244 | 245 | func TestBlobClose(t *testing.T) { 246 | c, err := sqlite.OpenConn(":memory:", 0) 247 | if err != nil { 248 | t.Fatal(err) 249 | } 250 | defer func() { 251 | if err := c.Close(); err != nil { 252 | t.Error(err) 253 | } 254 | }() 255 | 256 | if _, err := c.Prep("DROP TABLE IF EXISTS blobs;").Step(); err != nil { 257 | t.Fatal(err) 258 | } 259 | if _, err := c.Prep("CREATE TABLE blobs (col BLOB);").Step(); err != nil { 260 | t.Fatal(err) 261 | } 262 | 263 | stmt := c.Prep("INSERT INTO blobs (col) VALUES ($col);") 264 | stmt.SetZeroBlob("$col", 5) 265 | if _, err := stmt.Step(); err != nil { 266 | t.Fatal(err) 267 | } 268 | rowid := c.LastInsertRowID() 269 | t.Logf("blobs rowid: %d", rowid) 270 | 271 | b, err := c.OpenBlob("", "blobs", "col", rowid, true) 272 | if err != nil { 273 | t.Fatal(err) 274 | } 275 | 276 | if err := b.Close(); err != nil { 277 | t.Fatal(err) 278 | } 279 | if err := b.Close(); err == nil { 280 | t.Error("no error on second close") 281 | } 282 | if _, err := b.WriteAt([]byte{1}, 0); err == nil { 283 | t.Error("want error on write-after-close") 284 | } 285 | if _, err = b.ReadAt(make([]byte, 1), 0); err == nil { 286 | t.Error("want error on read-after-close") 287 | } 288 | } 289 | 290 | func TestBlobReadWrite(t *testing.T) { 291 | c, err := sqlite.OpenConn(":memory:", 0) 292 | if err != nil { 293 | t.Fatal(err) 294 | } 295 | defer func() { 296 | if err := c.Close(); err != nil { 297 | t.Error(err) 298 | } 299 | }() 300 | 301 | if _, err := c.Prep("DROP TABLE IF EXISTS blobs;").Step(); err != nil { 302 | t.Fatal(err) 303 | } 304 | if _, err := c.Prep("CREATE TABLE blobs (col BLOB);").Step(); err != nil { 305 | t.Fatal(err) 306 | } 307 | 308 | stmt := c.Prep("INSERT INTO blobs (col) VALUES ($col);") 309 | stmt.SetZeroBlob("$col", 5) 310 | if _, err := stmt.Step(); err != nil { 311 | t.Fatal(err) 312 | } 313 | rowid := c.LastInsertRowID() 314 | t.Logf("blobs rowid: %d", rowid) 315 | 316 | b, err := c.OpenBlob("", "blobs", "col", rowid, true) 317 | if err != nil { 318 | t.Fatal(err) 319 | } 320 | defer b.Close() 321 | 322 | bs := sqlitex.NewBlobSeeker(b) 323 | 324 | if _, err := bs.Write([]byte{1, 2, 3}); err != nil { 325 | t.Fatal(err) 326 | } 327 | if _, err := bs.Write([]byte{4, 5}); err != nil { 328 | t.Fatal(err) 329 | } 330 | if _, err := bs.Write([]byte{6}); err != io.ErrShortWrite { 331 | t.Errorf("Write past end of blob, want io.ErrShortWrite got: %v", err) 332 | } 333 | if _, err := bs.Seek(0, 0); err != nil { 334 | t.Fatal(err) 335 | } 336 | if got, err := ioutil.ReadAll(bs); err != nil { 337 | t.Fatal(err) 338 | } else if want := []byte{1, 2, 3, 4, 5}; !reflect.DeepEqual(got, want) { 339 | t.Errorf("want %v, got %v", want, got) 340 | } 341 | } 342 | 343 | // See https://github.com/golang/go/issues/28606 344 | func TestBlobPtrs(t *testing.T) { 345 | c, err := sqlite.OpenConn(":memory:", 0) 346 | if err != nil { 347 | t.Fatal(err) 348 | } 349 | defer func() { 350 | if err := c.Close(); err != nil { 351 | t.Error(err) 352 | } 353 | }() 354 | 355 | if _, err := c.Prep("DROP TABLE IF EXISTS blobs;").Step(); err != nil { 356 | t.Fatal(err) 357 | } 358 | if _, err := c.Prep("CREATE TABLE blobs (col BLOB);").Step(); err != nil { 359 | t.Fatal(err) 360 | } 361 | 362 | buf := new(bytes.Buffer) 363 | gzw := gzip.NewWriter(buf) 364 | gzw.Write([]byte("hello")) 365 | gzw.Close() 366 | n := buf.Len() 367 | 368 | stmt := c.Prep("INSERT INTO blobs (col) VALUES ($col);") 369 | stmt.SetZeroBlob("$col", int64(n)) 370 | if _, err := stmt.Step(); err != nil { 371 | t.Fatal(err) 372 | } 373 | rowid := c.LastInsertRowID() 374 | t.Logf("blobs rowid: %d", rowid) 375 | 376 | blob, err := c.OpenBlob("", "blobs", "col", rowid, true) 377 | if err != nil { 378 | t.Fatal(err) 379 | } 380 | defer blob.Close() 381 | 382 | bs := sqlitex.NewBlobSeeker(blob) 383 | 384 | gzw = gzip.NewWriter(bs) 385 | gzw.Write([]byte("hello")) 386 | gzw.Close() 387 | 388 | bs.Seek(0, 0) 389 | 390 | gzr, err := gzip.NewReader(bs) 391 | if err != nil { 392 | t.Fatal(err) 393 | } 394 | b, err := ioutil.ReadAll(gzr) 395 | if err != nil { 396 | t.Fatal(err) 397 | } 398 | if got := string(b); got != "hello" { 399 | t.Errorf("read %q, want %q", got, "hello") 400 | } 401 | if err := gzr.Close(); err != nil { 402 | t.Fatal(err) 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /blocking_step.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // This file defines the wait_for_unlock_notify function. 16 | // See the documentation on Stmt.Step. 17 | 18 | #include 19 | #include 20 | 21 | unlock_note* unlock_note_alloc() { 22 | unlock_note* un = (unlock_note*)malloc(sizeof(unlock_note)); 23 | pthread_mutex_init(&un->mu, 0); 24 | pthread_cond_init(&un->cond, 0); 25 | return un; 26 | } 27 | 28 | void unlock_note_free(unlock_note* un) { 29 | pthread_cond_destroy(&un->cond); 30 | pthread_mutex_destroy(&un->mu); 31 | free(un); 32 | } 33 | 34 | void unlock_note_fire(unlock_note* un) { 35 | pthread_mutex_lock(&un->mu); 36 | un->fired = 1; 37 | pthread_cond_signal(&un->cond); 38 | pthread_mutex_unlock(&un->mu); 39 | } 40 | 41 | static void unlock_notify_cb(void **apArg, int nArg) { 42 | for(int i=0; i < nArg; i++) { 43 | unlock_note_fire((unlock_note*)apArg[i]); 44 | } 45 | } 46 | 47 | int wait_for_unlock_notify(sqlite3 *db, unlock_note* un) { 48 | un->fired = 0; 49 | 50 | int res = sqlite3_unlock_notify(db, unlock_notify_cb, (void *)un); 51 | 52 | if (res == SQLITE_OK) { 53 | pthread_mutex_lock(&un->mu); 54 | if (!un->fired) { 55 | pthread_cond_wait(&un->cond, &un->mu); 56 | } 57 | pthread_mutex_unlock(&un->mu); 58 | } 59 | 60 | return res; 61 | } 62 | -------------------------------------------------------------------------------- /blocking_step.h: -------------------------------------------------------------------------------- 1 | // This file declares the wait_for_unlock_notify function. 2 | // See the documentation on Stmt.Step. 3 | 4 | #include 5 | #include 6 | 7 | typedef struct unlock_note { 8 | int fired; 9 | pthread_cond_t cond; 10 | pthread_mutex_t mu; 11 | } unlock_note; 12 | 13 | unlock_note* unlock_note_alloc(); 14 | void unlock_note_fire(unlock_note* un); 15 | void unlock_note_free(unlock_note* un); 16 | 17 | int wait_for_unlock_notify(sqlite3 *db, unlock_note* un); 18 | -------------------------------------------------------------------------------- /c/dummy.go: -------------------------------------------------------------------------------- 1 | //go:build !linksqlite3 2 | // +build !linksqlite3 3 | 4 | // Package c contains only a C file. 5 | // 6 | // This Go file is part of a workaround for `go mod vendor`. 7 | // Please see the file dummy.go at the root of the module for more information. 8 | package c 9 | 10 | /* 11 | // !!! UPDATE THE Makefile WITH THESE DEFINES !!! 12 | #cgo CFLAGS: -DSQLITE_THREADSAFE=2 13 | #cgo CFLAGS: -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 14 | #cgo CFLAGS: -DSQLITE_ENABLE_UNLOCK_NOTIFY 15 | #cgo CFLAGS: -DSQLITE_ENABLE_FTS5 16 | #cgo CFLAGS: -DSQLITE_ENABLE_RTREE 17 | #cgo CFLAGS: -DSQLITE_LIKE_DOESNT_MATCH_BLOBS 18 | #cgo CFLAGS: -DSQLITE_OMIT_DEPRECATED 19 | #cgo CFLAGS: -DSQLITE_ENABLE_JSON1 20 | #cgo CFLAGS: -DSQLITE_ENABLE_SESSION 21 | #cgo CFLAGS: -DSQLITE_ENABLE_SNAPSHOT 22 | #cgo CFLAGS: -DSQLITE_ENABLE_PREUPDATE_HOOK 23 | #cgo CFLAGS: -DSQLITE_USE_ALLOCA 24 | #cgo CFLAGS: -DSQLITE_ENABLE_COLUMN_METADATA 25 | #cgo CFLAGS: -DHAVE_USLEEP=1 26 | #cgo CFLAGS: -DSQLITE_DQS=0 27 | #cgo CFLAGS: -DSQLITE_ENABLE_GEOPOLY 28 | #cgo CFLAGS: -DSQLITE_DIRECT_OVERFLOW_READ 29 | #cgo windows LDFLAGS: -lwinpthread 30 | #cgo linux LDFLAGS: -ldl -lm 31 | #cgo linux CFLAGS: -std=c99 32 | #cgo openbsd LDFLAGS: -lm 33 | #cgo openbsd CFLAGS: -std=c99 34 | #cgo freebsd LDFLAGS: -lm 35 | #cgo freebsd CFLAGS: -std=c99 36 | // !!! UPDATE THE Makefile WITH THESE DEFINES !!! 37 | */ 38 | import "C" 39 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | /* 16 | Package sqlite provides a Go interface to SQLite 3. 17 | 18 | The semantics of this package are deliberately close to the 19 | SQLite3 C API, so it is helpful to be familiar with 20 | http://www.sqlite.org/c3ref/intro.html. 21 | 22 | An SQLite connection is represented by a *sqlite.Conn. 23 | Connections cannot be used concurrently. 24 | A typical Go program will create a pool of connections 25 | (using Open to create a *sqlitex.Pool) so goroutines can 26 | borrow a connection while they need to talk to the database. 27 | 28 | This package assumes SQLite will be used concurrently by the 29 | process through several connections, so the build options for 30 | SQLite enable multi-threading and the shared cache: 31 | https://www.sqlite.org/sharedcache.html 32 | 33 | The implementation automatically handles shared cache locking, 34 | see the documentation on Stmt.Step for details. 35 | 36 | The optional SQLite3 compiled in are: FTS5, RTree, JSON1, Session, GeoPoly 37 | 38 | This is not a database/sql driver. 39 | 40 | # Statement Caching 41 | 42 | Statements are prepared with the Prepare and PrepareTransient methods. 43 | When using Prepare, statements are keyed inside a connection by the 44 | original query string used to create them. This means long-running 45 | high-performance code paths can write: 46 | 47 | stmt, err := conn.Prepare("SELECT ...") 48 | 49 | After all the connections in a pool have been warmed up by passing 50 | through one of these Prepare calls, subsequent calls are simply a 51 | map lookup that returns an existing statement. 52 | 53 | # Streaming Blobs 54 | 55 | The sqlite package supports the SQLite incremental I/O interface for 56 | streaming blob data into and out of the the database without loading 57 | the entire blob into a single []byte. 58 | (This is important when working either with very large blobs, or 59 | more commonly, a large number of moderate-sized blobs concurrently.) 60 | 61 | To write a blob, first use an INSERT statement to set the size of the 62 | blob and assign a rowid: 63 | 64 | "INSERT INTO blobs (myblob) VALUES (?);" 65 | 66 | Use BindZeroBlob or SetZeroBlob to set the size of myblob. 67 | Then you can open the blob with: 68 | 69 | b, err := conn.OpenBlob("", "blobs", "myblob", conn.LastInsertRowID(), true) 70 | 71 | # Deadlines and Cancellation 72 | 73 | Every connection can have a done channel associated with it using 74 | the SetInterrupt method. This is typically the channel returned by 75 | a context.Context Done method. 76 | 77 | For example, a timeout can be associated with a connection session: 78 | 79 | ctx := context.WithTimeout(context.Background(), 100*time.Millisecond) 80 | conn.SetInterrupt(ctx.Done()) 81 | 82 | As database connections are long-lived, the SetInterrupt method can 83 | be called multiple times to reset the associated lifetime. 84 | 85 | When using pools, the shorthand for associating a context with a 86 | connection is: 87 | 88 | conn := dbpool.Get(ctx) 89 | if conn == nil { 90 | // ... handle error 91 | } 92 | defer dbpool.Put(c) 93 | 94 | # Transactions 95 | 96 | SQLite transactions have to be managed manually with this package 97 | by directly calling BEGIN / COMMIT / ROLLBACK or 98 | SAVEPOINT / RELEASE/ ROLLBACK. The sqlitex has a Savepoint 99 | function that helps automate this. 100 | 101 | # A typical HTTP Handler 102 | 103 | Using a Pool to execute SQL in a concurrent HTTP handler. 104 | 105 | var dbpool *sqlitex.Pool 106 | 107 | func main() { 108 | var err error 109 | dbpool, err = sqlitex.Open("file:memory:?mode=memory", 0, 10) 110 | if err != nil { 111 | log.Fatal(err) 112 | } 113 | http.HandleFunc("/", handle) 114 | log.Fatal(http.ListenAndServe(":8080", nil)) 115 | } 116 | 117 | func handle(w http.ResponseWriter, r *http.Request) { 118 | conn := dbpool.Get(r.Context()) 119 | if conn == nil { 120 | return 121 | } 122 | defer dbpool.Put(conn) 123 | stmt := conn.Prep("SELECT foo FROM footable WHERE id = $id;") 124 | stmt.SetText("$id", "_user_id_") 125 | for { 126 | if hasRow, err := stmt.Step(); err != nil { 127 | // ... handle error 128 | } else if !hasRow { 129 | break 130 | } 131 | foo := stmt.GetText("foo") 132 | // ... use foo 133 | } 134 | } 135 | 136 | For helper functions that make some kinds of statements easier to 137 | write see the sqlitex package. 138 | */ 139 | package sqlite // import "github.com/go-llsqlite/crawshaw" 140 | -------------------------------------------------------------------------------- /dummy.go: -------------------------------------------------------------------------------- 1 | //go:build dummy 2 | // +build dummy 3 | 4 | // This file is part of a workaround for `go mod vendor` which won't vendor 5 | // C files if there's no Go file in the same directory. 6 | // This would prevent the c/sqlite3.c file to be vendored. 7 | // 8 | // This Go file imports the c directory where there is another dummy.go file which 9 | // is the second part of this workaround. 10 | // 11 | // These two files combined make it so `go mod vendor` behaves correctly. 12 | // 13 | // See this issue for reference: https://github.com/golang/go/issues/26366 14 | 15 | package sqlite 16 | 17 | import ( 18 | _ "github.com/go-llsqlite/crawshaw/c" 19 | ) 20 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlite 16 | 17 | // #include 18 | import "C" 19 | import "errors" 20 | 21 | // Error is an error produced by SQLite. 22 | type Error struct { 23 | Code ErrorCode // SQLite extended error code (SQLITE_OK is an invalid value) 24 | Loc string // method name that generated the error 25 | Query string // original SQL query text 26 | Msg string // value of sqlite3_errmsg, set sqlite.ErrMsg = true 27 | } 28 | 29 | func (err Error) Error() string { 30 | str := "sqlite" 31 | if err.Loc != "" { 32 | str += "." + err.Loc 33 | } 34 | str += ": " + err.Code.String() 35 | if err.Msg != "" { 36 | str += ": " + err.Msg 37 | } 38 | if err.Query != "" { 39 | str += " (" + err.Query + ")" 40 | } 41 | return str 42 | } 43 | 44 | // ErrorCode is an SQLite extended error code. 45 | // 46 | // The three SQLite result codes (SQLITE_OK, SQLITE_ROW, and SQLITE_DONE), 47 | // are not errors so they should not be used in an Error. 48 | type ErrorCode int 49 | 50 | func (code ErrorCode) ToError() error { 51 | return Error{Code: code} 52 | } 53 | 54 | func (code ErrorCode) String() string { 55 | switch code { 56 | default: 57 | var buf [20]byte 58 | return "SQLITE_UNKNOWN_ERR(" + string(itoa(buf[:], int64(code))) + ")" 59 | case SQLITE_OK: 60 | return "SQLITE_OK(not an error)" 61 | case SQLITE_ROW: 62 | return "SQLITE_ROW(not an error)" 63 | case SQLITE_DONE: 64 | return "SQLITE_DONE(not an error)" 65 | case SQLITE_ERROR: 66 | return "SQLITE_ERROR" 67 | case SQLITE_INTERNAL: 68 | return "SQLITE_INTERNAL" 69 | case SQLITE_PERM: 70 | return "SQLITE_PERM" 71 | case SQLITE_ABORT: 72 | return "SQLITE_ABORT" 73 | case SQLITE_BUSY: 74 | return "SQLITE_BUSY" 75 | case SQLITE_LOCKED: 76 | return "SQLITE_LOCKED" 77 | case SQLITE_NOMEM: 78 | return "SQLITE_NOMEM" 79 | case SQLITE_READONLY: 80 | return "SQLITE_READONLY" 81 | case SQLITE_INTERRUPT: 82 | return "SQLITE_INTERRUPT" 83 | case SQLITE_IOERR: 84 | return "SQLITE_IOERR" 85 | case SQLITE_CORRUPT: 86 | return "SQLITE_CORRUPT" 87 | case SQLITE_NOTFOUND: 88 | return "SQLITE_NOTFOUND" 89 | case SQLITE_FULL: 90 | return "SQLITE_FULL" 91 | case SQLITE_CANTOPEN: 92 | return "SQLITE_CANTOPEN" 93 | case SQLITE_PROTOCOL: 94 | return "SQLITE_PROTOCOL" 95 | case SQLITE_EMPTY: 96 | return "SQLITE_EMPTY" 97 | case SQLITE_SCHEMA: 98 | return "SQLITE_SCHEMA" 99 | case SQLITE_TOOBIG: 100 | return "SQLITE_TOOBIG" 101 | case SQLITE_CONSTRAINT: 102 | return "SQLITE_CONSTRAINT" 103 | case SQLITE_MISMATCH: 104 | return "SQLITE_MISMATCH" 105 | case SQLITE_MISUSE: 106 | return "SQLITE_MISUSE" 107 | case SQLITE_NOLFS: 108 | return "SQLITE_NOLFS" 109 | case SQLITE_AUTH: 110 | return "SQLITE_AUTH" 111 | case SQLITE_FORMAT: 112 | return "SQLITE_FORMAT" 113 | case SQLITE_RANGE: 114 | return "SQLITE_RANGE" 115 | case SQLITE_NOTADB: 116 | return "SQLITE_NOTADB" 117 | case SQLITE_NOTICE: 118 | return "SQLITE_NOTICE" 119 | case SQLITE_WARNING: 120 | return "SQLITE_WARNING" 121 | 122 | case SQLITE_ERROR_MISSING_COLLSEQ: 123 | return "SQLITE_ERROR_MISSING_COLLSEQ" 124 | case SQLITE_ERROR_RETRY: 125 | return "SQLITE_ERROR_RETRY" 126 | case SQLITE_ERROR_SNAPSHOT: 127 | return "SQLITE_ERROR_SNAPSHOT" 128 | case SQLITE_IOERR_READ: 129 | return "SQLITE_IOERR_READ" 130 | case SQLITE_IOERR_SHORT_READ: 131 | return "SQLITE_IOERR_SHORT_READ" 132 | case SQLITE_IOERR_WRITE: 133 | return "SQLITE_IOERR_WRITE" 134 | case SQLITE_IOERR_FSYNC: 135 | return "SQLITE_IOERR_FSYNC" 136 | case SQLITE_IOERR_DIR_FSYNC: 137 | return "SQLITE_IOERR_DIR_FSYNC" 138 | case SQLITE_IOERR_TRUNCATE: 139 | return "SQLITE_IOERR_TRUNCATE" 140 | case SQLITE_IOERR_FSTAT: 141 | return "SQLITE_IOERR_FSTAT" 142 | case SQLITE_IOERR_UNLOCK: 143 | return "SQLITE_IOERR_UNLOCK" 144 | case SQLITE_IOERR_RDLOCK: 145 | return "SQLITE_IOERR_RDLOCK" 146 | case SQLITE_IOERR_DELETE: 147 | return "SQLITE_IOERR_DELETE" 148 | case SQLITE_IOERR_BLOCKED: 149 | return "SQLITE_IOERR_BLOCKED" 150 | case SQLITE_IOERR_NOMEM: 151 | return "SQLITE_IOERR_NOMEM" 152 | case SQLITE_IOERR_ACCESS: 153 | return "SQLITE_IOERR_ACCESS" 154 | case SQLITE_IOERR_CHECKRESERVEDLOCK: 155 | return "SQLITE_IOERR_CHECKRESERVEDLOCK" 156 | case SQLITE_IOERR_LOCK: 157 | return "SQLITE_IOERR_LOCK" 158 | case SQLITE_IOERR_CLOSE: 159 | return "SQLITE_IOERR_CLOSE" 160 | case SQLITE_IOERR_DIR_CLOSE: 161 | return "SQLITE_IOERR_DIR_CLOSE" 162 | case SQLITE_IOERR_SHMOPEN: 163 | return "SQLITE_IOERR_SHMOPEN" 164 | case SQLITE_IOERR_SHMSIZE: 165 | return "SQLITE_IOERR_SHMSIZE" 166 | case SQLITE_IOERR_SHMLOCK: 167 | return "SQLITE_IOERR_SHMLOCK" 168 | case SQLITE_IOERR_SHMMAP: 169 | return "SQLITE_IOERR_SHMMAP" 170 | case SQLITE_IOERR_SEEK: 171 | return "SQLITE_IOERR_SEEK" 172 | case SQLITE_IOERR_DELETE_NOENT: 173 | return "SQLITE_IOERR_DELETE_NOENT" 174 | case SQLITE_IOERR_MMAP: 175 | return "SQLITE_IOERR_MMAP" 176 | case SQLITE_IOERR_GETTEMPPATH: 177 | return "SQLITE_IOERR_GETTEMPPATH" 178 | case SQLITE_IOERR_CONVPATH: 179 | return "SQLITE_IOERR_CONVPATH" 180 | case SQLITE_IOERR_VNODE: 181 | return "SQLITE_IOERR_VNODE" 182 | case SQLITE_IOERR_AUTH: 183 | return "SQLITE_IOERR_AUTH" 184 | case SQLITE_IOERR_BEGIN_ATOMIC: 185 | return "SQLITE_IOERR_BEGIN_ATOMIC" 186 | case SQLITE_IOERR_COMMIT_ATOMIC: 187 | return "SQLITE_IOERR_COMMIT_ATOMIC" 188 | case SQLITE_IOERR_ROLLBACK_ATOMIC: 189 | return "SQLITE_IOERR_ROLLBACK_ATOMIC" 190 | case SQLITE_LOCKED_SHAREDCACHE: 191 | return "SQLITE_LOCKED_SHAREDCACHE" 192 | case SQLITE_BUSY_RECOVERY: 193 | return "SQLITE_BUSY_RECOVERY" 194 | case SQLITE_BUSY_SNAPSHOT: 195 | return "SQLITE_BUSY_SNAPSHOT" 196 | case SQLITE_CANTOPEN_NOTEMPDIR: 197 | return "SQLITE_CANTOPEN_NOTEMPDIR" 198 | case SQLITE_CANTOPEN_ISDIR: 199 | return "SQLITE_CANTOPEN_ISDIR" 200 | case SQLITE_CANTOPEN_FULLPATH: 201 | return "SQLITE_CANTOPEN_FULLPATH" 202 | case SQLITE_CANTOPEN_CONVPATH: 203 | return "SQLITE_CANTOPEN_CONVPATH" 204 | case SQLITE_CORRUPT_VTAB: 205 | return "SQLITE_CORRUPT_VTAB" 206 | case SQLITE_READONLY_RECOVERY: 207 | return "SQLITE_READONLY_RECOVERY" 208 | case SQLITE_READONLY_CANTLOCK: 209 | return "SQLITE_READONLY_CANTLOCK" 210 | case SQLITE_READONLY_ROLLBACK: 211 | return "SQLITE_READONLY_ROLLBACK" 212 | case SQLITE_READONLY_DBMOVED: 213 | return "SQLITE_READONLY_DBMOVED" 214 | case SQLITE_READONLY_CANTINIT: 215 | return "SQLITE_READONLY_CANTINIT" 216 | case SQLITE_READONLY_DIRECTORY: 217 | return "SQLITE_READONLY_DIRECTORY" 218 | case SQLITE_ABORT_ROLLBACK: 219 | return "SQLITE_ABORT_ROLLBACK" 220 | case SQLITE_CONSTRAINT_CHECK: 221 | return "SQLITE_CONSTRAINT_CHECK" 222 | case SQLITE_CONSTRAINT_COMMITHOOK: 223 | return "SQLITE_CONSTRAINT_COMMITHOOK" 224 | case SQLITE_CONSTRAINT_FOREIGNKEY: 225 | return "SQLITE_CONSTRAINT_FOREIGNKEY" 226 | case SQLITE_CONSTRAINT_FUNCTION: 227 | return "SQLITE_CONSTRAINT_FUNCTION" 228 | case SQLITE_CONSTRAINT_NOTNULL: 229 | return "SQLITE_CONSTRAINT_NOTNULL" 230 | case SQLITE_CONSTRAINT_PRIMARYKEY: 231 | return "SQLITE_CONSTRAINT_PRIMARYKEY" 232 | case SQLITE_CONSTRAINT_TRIGGER: 233 | return "SQLITE_CONSTRAINT_TRIGGER" 234 | case SQLITE_CONSTRAINT_UNIQUE: 235 | return "SQLITE_CONSTRAINT_UNIQUE" 236 | case SQLITE_CONSTRAINT_VTAB: 237 | return "SQLITE_CONSTRAINT_VTAB" 238 | case SQLITE_CONSTRAINT_ROWID: 239 | return "SQLITE_CONSTRAINT_ROWID" 240 | case SQLITE_NOTICE_RECOVER_WAL: 241 | return "SQLITE_NOTICE_RECOVER_WAL" 242 | case SQLITE_NOTICE_RECOVER_ROLLBACK: 243 | return "SQLITE_NOTICE_RECOVER_ROLLBACK" 244 | case SQLITE_WARNING_AUTOINDEX: 245 | return "SQLITE_WARNING_AUTOINDEX" 246 | case SQLITE_AUTH_USER: 247 | return "SQLITE_AUTH_USER" 248 | } 249 | } 250 | 251 | const ( 252 | SQLITE_OK = ErrorCode(C.SQLITE_OK) // do not use in Error 253 | SQLITE_ERROR = ErrorCode(C.SQLITE_ERROR) 254 | SQLITE_INTERNAL = ErrorCode(C.SQLITE_INTERNAL) 255 | SQLITE_PERM = ErrorCode(C.SQLITE_PERM) 256 | SQLITE_ABORT = ErrorCode(C.SQLITE_ABORT) 257 | SQLITE_BUSY = ErrorCode(C.SQLITE_BUSY) 258 | SQLITE_LOCKED = ErrorCode(C.SQLITE_LOCKED) 259 | SQLITE_NOMEM = ErrorCode(C.SQLITE_NOMEM) 260 | SQLITE_READONLY = ErrorCode(C.SQLITE_READONLY) 261 | SQLITE_INTERRUPT = ErrorCode(C.SQLITE_INTERRUPT) 262 | SQLITE_IOERR = ErrorCode(C.SQLITE_IOERR) 263 | SQLITE_CORRUPT = ErrorCode(C.SQLITE_CORRUPT) 264 | SQLITE_NOTFOUND = ErrorCode(C.SQLITE_NOTFOUND) 265 | SQLITE_FULL = ErrorCode(C.SQLITE_FULL) 266 | SQLITE_CANTOPEN = ErrorCode(C.SQLITE_CANTOPEN) 267 | SQLITE_PROTOCOL = ErrorCode(C.SQLITE_PROTOCOL) 268 | SQLITE_EMPTY = ErrorCode(C.SQLITE_EMPTY) 269 | SQLITE_SCHEMA = ErrorCode(C.SQLITE_SCHEMA) 270 | SQLITE_TOOBIG = ErrorCode(C.SQLITE_TOOBIG) 271 | SQLITE_CONSTRAINT = ErrorCode(C.SQLITE_CONSTRAINT) 272 | SQLITE_MISMATCH = ErrorCode(C.SQLITE_MISMATCH) 273 | SQLITE_MISUSE = ErrorCode(C.SQLITE_MISUSE) 274 | SQLITE_NOLFS = ErrorCode(C.SQLITE_NOLFS) 275 | SQLITE_AUTH = ErrorCode(C.SQLITE_AUTH) 276 | SQLITE_FORMAT = ErrorCode(C.SQLITE_FORMAT) 277 | SQLITE_RANGE = ErrorCode(C.SQLITE_RANGE) 278 | SQLITE_NOTADB = ErrorCode(C.SQLITE_NOTADB) 279 | SQLITE_NOTICE = ErrorCode(C.SQLITE_NOTICE) 280 | SQLITE_WARNING = ErrorCode(C.SQLITE_WARNING) 281 | SQLITE_ROW = ErrorCode(C.SQLITE_ROW) // do not use in Error 282 | SQLITE_DONE = ErrorCode(C.SQLITE_DONE) // do not use in Error 283 | 284 | SQLITE_ERROR_MISSING_COLLSEQ = ErrorCode(C.SQLITE_ERROR_MISSING_COLLSEQ) 285 | SQLITE_ERROR_RETRY = ErrorCode(C.SQLITE_ERROR_RETRY) 286 | SQLITE_ERROR_SNAPSHOT = ErrorCode(C.SQLITE_ERROR_SNAPSHOT) 287 | SQLITE_IOERR_READ = ErrorCode(C.SQLITE_IOERR_READ) 288 | SQLITE_IOERR_SHORT_READ = ErrorCode(C.SQLITE_IOERR_SHORT_READ) 289 | SQLITE_IOERR_WRITE = ErrorCode(C.SQLITE_IOERR_WRITE) 290 | SQLITE_IOERR_FSYNC = ErrorCode(C.SQLITE_IOERR_FSYNC) 291 | SQLITE_IOERR_DIR_FSYNC = ErrorCode(C.SQLITE_IOERR_DIR_FSYNC) 292 | SQLITE_IOERR_TRUNCATE = ErrorCode(C.SQLITE_IOERR_TRUNCATE) 293 | SQLITE_IOERR_FSTAT = ErrorCode(C.SQLITE_IOERR_FSTAT) 294 | SQLITE_IOERR_UNLOCK = ErrorCode(C.SQLITE_IOERR_UNLOCK) 295 | SQLITE_IOERR_RDLOCK = ErrorCode(C.SQLITE_IOERR_RDLOCK) 296 | SQLITE_IOERR_DELETE = ErrorCode(C.SQLITE_IOERR_DELETE) 297 | SQLITE_IOERR_BLOCKED = ErrorCode(C.SQLITE_IOERR_BLOCKED) 298 | SQLITE_IOERR_NOMEM = ErrorCode(C.SQLITE_IOERR_NOMEM) 299 | SQLITE_IOERR_ACCESS = ErrorCode(C.SQLITE_IOERR_ACCESS) 300 | SQLITE_IOERR_CHECKRESERVEDLOCK = ErrorCode(C.SQLITE_IOERR_CHECKRESERVEDLOCK) 301 | SQLITE_IOERR_LOCK = ErrorCode(C.SQLITE_IOERR_LOCK) 302 | SQLITE_IOERR_CLOSE = ErrorCode(C.SQLITE_IOERR_CLOSE) 303 | SQLITE_IOERR_DIR_CLOSE = ErrorCode(C.SQLITE_IOERR_DIR_CLOSE) 304 | SQLITE_IOERR_SHMOPEN = ErrorCode(C.SQLITE_IOERR_SHMOPEN) 305 | SQLITE_IOERR_SHMSIZE = ErrorCode(C.SQLITE_IOERR_SHMSIZE) 306 | SQLITE_IOERR_SHMLOCK = ErrorCode(C.SQLITE_IOERR_SHMLOCK) 307 | SQLITE_IOERR_SHMMAP = ErrorCode(C.SQLITE_IOERR_SHMMAP) 308 | SQLITE_IOERR_SEEK = ErrorCode(C.SQLITE_IOERR_SEEK) 309 | SQLITE_IOERR_DELETE_NOENT = ErrorCode(C.SQLITE_IOERR_DELETE_NOENT) 310 | SQLITE_IOERR_MMAP = ErrorCode(C.SQLITE_IOERR_MMAP) 311 | SQLITE_IOERR_GETTEMPPATH = ErrorCode(C.SQLITE_IOERR_GETTEMPPATH) 312 | SQLITE_IOERR_CONVPATH = ErrorCode(C.SQLITE_IOERR_CONVPATH) 313 | SQLITE_IOERR_VNODE = ErrorCode(C.SQLITE_IOERR_VNODE) 314 | SQLITE_IOERR_AUTH = ErrorCode(C.SQLITE_IOERR_AUTH) 315 | SQLITE_IOERR_BEGIN_ATOMIC = ErrorCode(C.SQLITE_IOERR_BEGIN_ATOMIC) 316 | SQLITE_IOERR_COMMIT_ATOMIC = ErrorCode(C.SQLITE_IOERR_COMMIT_ATOMIC) 317 | SQLITE_IOERR_ROLLBACK_ATOMIC = ErrorCode(C.SQLITE_IOERR_ROLLBACK_ATOMIC) 318 | SQLITE_LOCKED_SHAREDCACHE = ErrorCode(C.SQLITE_LOCKED_SHAREDCACHE) 319 | SQLITE_BUSY_RECOVERY = ErrorCode(C.SQLITE_BUSY_RECOVERY) 320 | SQLITE_BUSY_SNAPSHOT = ErrorCode(C.SQLITE_BUSY_SNAPSHOT) 321 | SQLITE_CANTOPEN_NOTEMPDIR = ErrorCode(C.SQLITE_CANTOPEN_NOTEMPDIR) 322 | SQLITE_CANTOPEN_ISDIR = ErrorCode(C.SQLITE_CANTOPEN_ISDIR) 323 | SQLITE_CANTOPEN_FULLPATH = ErrorCode(C.SQLITE_CANTOPEN_FULLPATH) 324 | SQLITE_CANTOPEN_CONVPATH = ErrorCode(C.SQLITE_CANTOPEN_CONVPATH) 325 | SQLITE_CORRUPT_VTAB = ErrorCode(C.SQLITE_CORRUPT_VTAB) 326 | SQLITE_READONLY_RECOVERY = ErrorCode(C.SQLITE_READONLY_RECOVERY) 327 | SQLITE_READONLY_CANTLOCK = ErrorCode(C.SQLITE_READONLY_CANTLOCK) 328 | SQLITE_READONLY_ROLLBACK = ErrorCode(C.SQLITE_READONLY_ROLLBACK) 329 | SQLITE_READONLY_DBMOVED = ErrorCode(C.SQLITE_READONLY_DBMOVED) 330 | SQLITE_READONLY_CANTINIT = ErrorCode(C.SQLITE_READONLY_CANTINIT) 331 | SQLITE_READONLY_DIRECTORY = ErrorCode(C.SQLITE_READONLY_DIRECTORY) 332 | SQLITE_ABORT_ROLLBACK = ErrorCode(C.SQLITE_ABORT_ROLLBACK) 333 | SQLITE_CONSTRAINT_CHECK = ErrorCode(C.SQLITE_CONSTRAINT_CHECK) 334 | SQLITE_CONSTRAINT_COMMITHOOK = ErrorCode(C.SQLITE_CONSTRAINT_COMMITHOOK) 335 | SQLITE_CONSTRAINT_FOREIGNKEY = ErrorCode(C.SQLITE_CONSTRAINT_FOREIGNKEY) 336 | SQLITE_CONSTRAINT_FUNCTION = ErrorCode(C.SQLITE_CONSTRAINT_FUNCTION) 337 | SQLITE_CONSTRAINT_NOTNULL = ErrorCode(C.SQLITE_CONSTRAINT_NOTNULL) 338 | SQLITE_CONSTRAINT_PRIMARYKEY = ErrorCode(C.SQLITE_CONSTRAINT_PRIMARYKEY) 339 | SQLITE_CONSTRAINT_TRIGGER = ErrorCode(C.SQLITE_CONSTRAINT_TRIGGER) 340 | SQLITE_CONSTRAINT_UNIQUE = ErrorCode(C.SQLITE_CONSTRAINT_UNIQUE) 341 | SQLITE_CONSTRAINT_VTAB = ErrorCode(C.SQLITE_CONSTRAINT_VTAB) 342 | SQLITE_CONSTRAINT_ROWID = ErrorCode(C.SQLITE_CONSTRAINT_ROWID) 343 | SQLITE_NOTICE_RECOVER_WAL = ErrorCode(C.SQLITE_NOTICE_RECOVER_WAL) 344 | SQLITE_NOTICE_RECOVER_ROLLBACK = ErrorCode(C.SQLITE_NOTICE_RECOVER_ROLLBACK) 345 | SQLITE_WARNING_AUTOINDEX = ErrorCode(C.SQLITE_WARNING_AUTOINDEX) 346 | SQLITE_AUTH_USER = ErrorCode(C.SQLITE_AUTH_USER) 347 | ) 348 | 349 | // ErrCode extracts the SQLite error code from err. 350 | // If err is not a sqlite Error, SQLITE_ERROR is returned. 351 | // If err is nil, SQLITE_OK is returned. 352 | func ErrCode(err error) ErrorCode { 353 | var sqliteError Error 354 | if errors.As(err, &sqliteError) { 355 | return sqliteError.Code 356 | } 357 | if err != nil { 358 | return SQLITE_ERROR 359 | } 360 | return SQLITE_OK 361 | } 362 | 363 | func itoa(buf []byte, val int64) []byte { 364 | i := len(buf) - 1 365 | neg := false 366 | if val < 0 { 367 | neg = true 368 | val = 0 - val 369 | } 370 | for val >= 10 { 371 | buf[i] = byte(val%10 + '0') 372 | i-- 373 | val /= 10 374 | } 375 | buf[i] = byte(val + '0') 376 | if neg { 377 | i-- 378 | buf[i] = '-' 379 | } 380 | return buf[i:] 381 | } 382 | -------------------------------------------------------------------------------- /export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // Expose sqlite internals to tests in sqlite_test package. 16 | 17 | package sqlite 18 | 19 | func ConnCount(conn *Conn) int { return conn.count } 20 | 21 | func InterruptedStmt(conn *Conn, query string) *Stmt { 22 | return &Stmt{ 23 | conn: conn, 24 | query: query, 25 | colNames: make(map[string]int), 26 | prepInterrupt: true, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /extension.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlite 16 | 17 | import "unsafe" 18 | 19 | // #include 20 | // #include 21 | // static int db_config_onoff(sqlite3* db, int op, int onoff) { 22 | // return sqlite3_db_config(db, op, onoff, NULL); 23 | // } 24 | import "C" 25 | 26 | const ( 27 | SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = C.int(C.SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION) 28 | ) 29 | 30 | // EnableLoadExtension allows extensions to be loaded via LoadExtension(). The 31 | // SQL interface is left disabled as recommended. 32 | // 33 | // https://www.sqlite.org/c3ref/enable_load_extension.html 34 | func (conn *Conn) EnableLoadExtension(on bool) error { 35 | var enable C.int 36 | if on { 37 | enable = 1 38 | } 39 | res := C.db_config_onoff(conn.conn, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, enable) 40 | return reserr("Conn.EnableLoadExtension", "", "", res) 41 | } 42 | 43 | // LoadExtension attempts to load a runtime-loadable extension. 44 | // 45 | // https://www.sqlite.org/c3ref/load_extension.html 46 | func (conn *Conn) LoadExtension(ext, entry string) error { 47 | cext := C.CString(ext) 48 | defer C.free(unsafe.Pointer(cext)) 49 | var centry *C.char 50 | if entry != "" { 51 | centry = C.CString(entry) 52 | defer C.free(unsafe.Pointer(centry)) 53 | } 54 | var cerr *C.char 55 | res := C.sqlite3_load_extension(conn.conn, cext, centry, &cerr) 56 | err := C.GoString(cerr) 57 | C.sqlite3_free(unsafe.Pointer(cerr)) 58 | return reserr("Conn.LoadExtension", "", err, res) 59 | } 60 | -------------------------------------------------------------------------------- /extension_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlite_test 16 | 17 | import ( 18 | "io" 19 | "io/ioutil" 20 | "os" 21 | "os/exec" 22 | "path/filepath" 23 | "runtime" 24 | "strings" 25 | "testing" 26 | 27 | "github.com/go-llsqlite/crawshaw" 28 | ) 29 | 30 | const ( 31 | extCode = ` 32 | #include "sqlite3ext.h" 33 | SQLITE_EXTENSION_INIT1 34 | 35 | #include 36 | 37 | static void hellofunc( 38 | sqlite3_context *context, 39 | int argc, 40 | sqlite3_value **argv){ 41 | (void)argc; 42 | (void)argv; 43 | sqlite3_result_text(context, "Hello, World!", -1, SQLITE_STATIC); 44 | } 45 | 46 | #ifdef _WIN32 47 | __declspec(dllexport) 48 | #endif 49 | int sqlite3_hello_init( 50 | sqlite3 *db, 51 | char **pzErrMsg, 52 | const sqlite3_api_routines *pApi 53 | ){ 54 | int rc = SQLITE_OK; 55 | SQLITE_EXTENSION_INIT2(pApi); 56 | (void)pzErrMsg; /* Unused parameter */ 57 | return sqlite3_create_function(db, "hello", 0, SQLITE_UTF8, 0, hellofunc, 0, 0); 58 | }` 59 | ) 60 | 61 | func TestLoadExtension(t *testing.T) { 62 | tmpdir, err := ioutil.TempDir("", "sqlite") 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | defer os.RemoveAll(tmpdir) 67 | fout, err := os.Create(filepath.Join(tmpdir, "ext.c")) 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | if _, err = io.Copy(fout, strings.NewReader(extCode)); err != nil { 72 | t.Fatal(err) 73 | } 74 | if err = fout.Close(); err != nil { 75 | t.Error(err) 76 | } 77 | include, err := os.Getwd() 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | var args []string 82 | switch runtime.GOOS { 83 | // See https://www.sqlite.org/loadext.html#build 84 | case "darwin": 85 | args = []string{"gcc", "-g", "-fPIC", "-I" + include, "-dynamiclib", "ext.c", "-o", "libhello.dylib"} 86 | case "linux": 87 | args = []string{"gcc", "-g", "-fPIC", "-I" + include, "-shared", "ext.c", "-o", "libhello.so"} 88 | case "windows": 89 | // TODO: add windows support 90 | fallthrough 91 | default: 92 | t.Skipf("unsupported OS: %s", runtime.GOOS) 93 | } 94 | cmd := exec.Command(args[0], args[1:]...) 95 | cmd.Dir = tmpdir 96 | out, err := cmd.CombinedOutput() 97 | if err != nil { 98 | t.Skipf("no gcc support: %s, %s", string(out), err) 99 | } 100 | c, err := sqlite.OpenConn(":memory:", 0) 101 | if err != nil { 102 | t.Fatal(err) 103 | } 104 | defer func() { 105 | err := c.Close() 106 | if err != nil { 107 | t.Error(err) 108 | } 109 | }() 110 | libPath := filepath.Join(tmpdir, args[len(args)-1]) 111 | err = c.LoadExtension(libPath, "") 112 | if err == nil { 113 | t.Error("loaded extension without enabling load extension") 114 | } 115 | err = c.EnableLoadExtension(true) 116 | if err != nil { 117 | t.Fatal(err) 118 | } 119 | err = c.LoadExtension(libPath, "") 120 | if err != nil { 121 | t.Fatal(err) 122 | } 123 | stmt := c.Prep("SELECT hello();") 124 | if _, err := stmt.Step(); err != nil { 125 | t.Fatal(err) 126 | } 127 | if got, want := stmt.ColumnText(0), "Hello, World!"; got != want { 128 | t.Errorf("failed to load extension, got: %s, want: %s", got, want) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /func.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlite 16 | 17 | // #include 18 | // #include 19 | // #include 20 | // #include "wrappers.h" 21 | // 22 | // static int go_sqlite3_create_function_v2( 23 | // sqlite3 *db, 24 | // const char *zFunctionName, 25 | // int nArg, 26 | // int eTextRep, 27 | // uintptr_t pApp, 28 | // void (*xFunc)(sqlite3_context*,int,sqlite3_value**), 29 | // void (*xStep)(sqlite3_context*,int,sqlite3_value**), 30 | // void (*xFinal)(sqlite3_context*), 31 | // void(*xDestroy)(void*) 32 | // ) { 33 | // return sqlite3_create_function_v2( 34 | // db, 35 | // zFunctionName, 36 | // nArg, 37 | // eTextRep, 38 | // (void *)pApp, 39 | // xFunc, 40 | // xStep, 41 | // xFinal, 42 | // xDestroy); 43 | // } 44 | import "C" 45 | import ( 46 | "sync" 47 | "unsafe" 48 | ) 49 | 50 | // Context is an *sqlite3_context. 51 | // It is used by custom functions to return result values. 52 | // An SQLite context is in no way related to a Go context.Context. 53 | type Context struct { 54 | ptr *C.sqlite3_context 55 | } 56 | 57 | func (ctx Context) UserData() interface{} { 58 | return getxfuncs(ctx.ptr).data 59 | } 60 | 61 | func (ctx Context) SetUserData(data interface{}) { 62 | getxfuncs(ctx.ptr).data = data 63 | } 64 | 65 | func (ctx Context) ResultInt(v int) { C.sqlite3_result_int(ctx.ptr, C.int(v)) } 66 | func (ctx Context) ResultInt64(v int64) { C.sqlite3_result_int64(ctx.ptr, C.sqlite3_int64(v)) } 67 | func (ctx Context) ResultFloat(v float64) { C.sqlite3_result_double(ctx.ptr, C.double(v)) } 68 | func (ctx Context) ResultNull() { C.sqlite3_result_null(ctx.ptr) } 69 | func (ctx Context) ResultValue(v Value) { C.sqlite3_result_value(ctx.ptr, v.ptr) } 70 | func (ctx Context) ResultZeroBlob(n int64) { C.sqlite3_result_zeroblob64(ctx.ptr, C.sqlite3_uint64(n)) } 71 | func (ctx Context) ResultBlob(v []byte) { 72 | C.sqlite3_result_blob(ctx.ptr, C.CBytes(v), C.int(len(v)), (*[0]byte)(C.cfree)) 73 | } 74 | func (ctx Context) ResultText(v string) { 75 | var cv *C.char 76 | if len(v) != 0 { 77 | cv = C.CString(v) 78 | } 79 | C.sqlite3_result_text(ctx.ptr, cv, C.int(len(v)), (*[0]byte)(C.cfree)) 80 | } 81 | func (ctx Context) ResultError(err error) { 82 | if err, isError := err.(Error); isError { 83 | C.sqlite3_result_error_code(ctx.ptr, C.int(err.Code)) 84 | return 85 | } 86 | errstr := err.Error() 87 | cerrstr := C.CString(errstr) 88 | defer C.free(unsafe.Pointer(cerrstr)) 89 | C.sqlite3_result_error(ctx.ptr, cerrstr, C.int(len(errstr))) 90 | } 91 | 92 | type Value struct { 93 | ptr *C.sqlite3_value 94 | } 95 | 96 | func (v Value) IsNil() bool { return v.ptr == nil } 97 | func (v Value) Int() int { return int(C.sqlite3_value_int(v.ptr)) } 98 | func (v Value) Int64() int64 { return int64(C.sqlite3_value_int64(v.ptr)) } 99 | func (v Value) Float() float64 { return float64(C.sqlite3_value_double(v.ptr)) } 100 | func (v Value) Len() int { return int(C.sqlite3_value_bytes(v.ptr)) } 101 | func (v Value) Type() ColumnType { return ColumnType(C.sqlite3_value_type(v.ptr)) } 102 | func (v Value) Text() string { 103 | ptr := unsafe.Pointer(C.sqlite3_value_text(v.ptr)) 104 | n := v.Len() 105 | return C.GoStringN((*C.char)(ptr), C.int(n)) 106 | } 107 | func (v Value) Blob() []byte { 108 | ptr := unsafe.Pointer(C.sqlite3_value_blob(v.ptr)) 109 | n := v.Len() 110 | return C.GoBytes(ptr, C.int(n)) 111 | } 112 | 113 | type xfunc struct { 114 | id int 115 | name string 116 | conn *Conn 117 | xFunc func(Context, ...Value) 118 | xStep func(Context, ...Value) 119 | xFinal func(Context) 120 | data interface{} 121 | } 122 | 123 | var xfuncs = struct { 124 | mu sync.RWMutex 125 | m map[int]*xfunc 126 | next int 127 | }{ 128 | m: make(map[int]*xfunc), 129 | } 130 | 131 | // CreateFunction registers a Go function with SQLite 132 | // for use in SQL queries. 133 | // 134 | // To define a scalar function, provide a value for 135 | // xFunc and set xStep/xFinal to nil. 136 | // 137 | // To define an aggregation set xFunc to nil and 138 | // provide values for xStep and xFinal. 139 | // 140 | // State can be stored across function calls by 141 | // using the Context UserData/SetUserData methods. 142 | // 143 | // https://sqlite.org/c3ref/create_function.html 144 | func (conn *Conn) CreateFunction(name string, deterministic bool, numArgs int, xFunc, xStep func(Context, ...Value), xFinal func(Context)) error { 145 | cname := C.CString(name) // TODO: free? 146 | eTextRep := C.int(C.SQLITE_UTF8) 147 | if deterministic { 148 | eTextRep |= C.SQLITE_DETERMINISTIC 149 | } 150 | 151 | x := &xfunc{ 152 | conn: conn, 153 | name: name, 154 | xFunc: xFunc, 155 | xStep: xStep, 156 | xFinal: xFinal, 157 | } 158 | 159 | xfuncs.mu.Lock() 160 | xfuncs.next++ 161 | x.id = xfuncs.next 162 | xfuncs.m[x.id] = x 163 | xfuncs.mu.Unlock() 164 | 165 | pApp := C.uintptr_t(x.id) 166 | 167 | var funcfn, stepfn, finalfn *[0]byte 168 | if xFunc == nil { 169 | stepfn = (*[0]byte)(C.c_step_tramp) 170 | finalfn = (*[0]byte)(C.c_final_tramp) 171 | } else { 172 | funcfn = (*[0]byte)(C.c_func_tramp) 173 | } 174 | 175 | res := C.go_sqlite3_create_function_v2( 176 | conn.conn, 177 | cname, 178 | C.int(numArgs), 179 | eTextRep, 180 | pApp, 181 | funcfn, 182 | stepfn, 183 | finalfn, 184 | (*[0]byte)(C.c_destroy_tramp), 185 | ) 186 | return conn.reserr("Conn.CreateFunction", name, res) 187 | } 188 | 189 | func getxfuncs(ctx *C.sqlite3_context) *xfunc { 190 | id := int(uintptr(C.sqlite3_user_data(ctx))) 191 | 192 | xfuncs.mu.RLock() 193 | x := xfuncs.m[id] 194 | xfuncs.mu.RUnlock() 195 | 196 | return x 197 | } 198 | 199 | //export go_func_tramp 200 | func go_func_tramp(ctx *C.sqlite3_context, n C.int, valarray **C.sqlite3_value) { 201 | x := getxfuncs(ctx) 202 | var vals []Value 203 | if n > 0 { 204 | vals = (*[127]Value)(unsafe.Pointer(valarray))[:n:n] 205 | } 206 | x.xFunc(Context{ptr: ctx}, vals...) 207 | } 208 | 209 | //export go_step_tramp 210 | func go_step_tramp(ctx *C.sqlite3_context, n C.int, valarray **C.sqlite3_value) { 211 | x := getxfuncs(ctx) 212 | var vals []Value 213 | if n > 0 { 214 | vals = (*[127]Value)(unsafe.Pointer(valarray))[:n:n] 215 | } 216 | x.xStep(Context{ptr: ctx}, vals...) 217 | } 218 | 219 | //export go_final_tramp 220 | func go_final_tramp(ctx *C.sqlite3_context) { 221 | x := getxfuncs(ctx) 222 | x.xFinal(Context{ptr: ctx}) 223 | } 224 | 225 | //export go_destroy_tramp 226 | func go_destroy_tramp(ptr uintptr) { 227 | id := int(ptr) 228 | 229 | xfuncs.mu.Lock() 230 | delete(xfuncs.m, id) 231 | xfuncs.mu.Unlock() 232 | } 233 | -------------------------------------------------------------------------------- /func_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlite_test 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "strings" 21 | "testing" 22 | 23 | "github.com/go-llsqlite/crawshaw" 24 | ) 25 | 26 | func TestFunc(t *testing.T) { 27 | c, err := sqlite.OpenConn(":memory:", 0) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | defer func() { 32 | if err := c.Close(); err != nil { 33 | t.Error(err) 34 | } 35 | }() 36 | 37 | xFunc := func(ctx sqlite.Context, values ...sqlite.Value) { 38 | v := values[0].Int() + values[1].Int() 39 | ctx.ResultInt(v) 40 | } 41 | if err := c.CreateFunction("addints", true, 2, xFunc, nil, nil); err != nil { 42 | t.Fatal(err) 43 | } 44 | 45 | stmt, _, err := c.PrepareTransient("SELECT addints(2, 3);") 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | if _, err := stmt.Step(); err != nil { 50 | t.Fatal(err) 51 | } 52 | if got, want := stmt.ColumnInt(0), 5; got != want { 53 | t.Errorf("addints(2, 3)=%d, want %d", got, want) 54 | } 55 | stmt.Finalize() 56 | } 57 | 58 | func TestAggFunc(t *testing.T) { 59 | c, err := sqlite.OpenConn(":memory:", 0) 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | defer func() { 64 | if err := c.Close(); err != nil { 65 | t.Error(err) 66 | } 67 | }() 68 | 69 | stmt, _, err := c.PrepareTransient("CREATE TABLE t (c integer);") 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | if _, err := stmt.Step(); err != nil { 74 | t.Fatal(err) 75 | } 76 | if err := stmt.Finalize(); err != nil { 77 | t.Error(err) 78 | } 79 | 80 | cVals := []int{3, 5, 7} 81 | want := 3 + 5 + 7 82 | 83 | stmt, err = c.Prepare("INSERT INTO t (c) VALUES ($c);") 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | for _, val := range cVals { 88 | stmt.SetInt64("$c", int64(val)) 89 | if _, err = stmt.Step(); err != nil { 90 | t.Errorf("INSERT %q: %v", val, err) 91 | } 92 | if err = stmt.Reset(); err != nil { 93 | t.Errorf("INSERT reset %q: %v", val, err) 94 | } 95 | } 96 | stmt.Finalize() 97 | 98 | xStep := func(ctx sqlite.Context, values ...sqlite.Value) { 99 | var sum int 100 | if data := ctx.UserData(); data != nil { 101 | sum = data.(int) 102 | } 103 | sum += values[0].Int() 104 | ctx.SetUserData(sum) 105 | } 106 | xFinal := func(ctx sqlite.Context) { 107 | var sum int 108 | if data := ctx.UserData(); data != nil { 109 | sum = data.(int) 110 | } 111 | ctx.ResultInt(sum) 112 | } 113 | if err := c.CreateFunction("sumints", true, 2, nil, xStep, xFinal); err != nil { 114 | t.Fatal(err) 115 | } 116 | 117 | stmt, _, err = c.PrepareTransient("SELECT sum(c) FROM t;") 118 | if err != nil { 119 | t.Fatal(err) 120 | } 121 | if _, err := stmt.Step(); err != nil { 122 | t.Fatal(err) 123 | } 124 | if got := stmt.ColumnInt(0); got != want { 125 | t.Errorf("sum(c)=%d, want %d", got, want) 126 | } 127 | stmt.Finalize() 128 | } 129 | 130 | func TestBlobFunc(t *testing.T) { 131 | c, err := sqlite.OpenConn(":memory:", 0) 132 | if err != nil { 133 | t.Fatal(err) 134 | } 135 | defer func() { 136 | if err := c.Close(); err != nil { 137 | t.Error(err) 138 | } 139 | }() 140 | 141 | xFunc := func(ctx sqlite.Context, values ...sqlite.Value) { 142 | var buf bytes.Buffer 143 | for _, v := range values { 144 | buf.Write(v.Blob()) 145 | } 146 | ctx.ResultBlob(buf.Bytes()) 147 | } 148 | if err := c.CreateFunction("blobcat", true, -1, xFunc, nil, nil); err != nil { 149 | t.Fatal(err) 150 | } 151 | 152 | stmt, _, err := c.PrepareTransient("SELECT blobcat(x'ff00',x'00ba');") 153 | if err != nil { 154 | t.Fatal(err) 155 | } 156 | if _, err := stmt.Step(); err != nil { 157 | t.Fatal(err) 158 | } 159 | got := make([]byte, 4) 160 | want := []byte{0xFF, 0x00, 0x00, 0xBA} 161 | if stmt.ColumnBytes(0, got) != len(want) || !bytes.Equal(got, want) { 162 | t.Errorf("blobcat(x'ff00',x'00ba')='%x', want '%x'", got, want) 163 | } 164 | stmt.Finalize() 165 | } 166 | 167 | func TestStringFunc(t *testing.T) { 168 | c, err := sqlite.OpenConn(":memory:", 0) 169 | if err != nil { 170 | t.Fatal(err) 171 | } 172 | defer func() { 173 | if err := c.Close(); err != nil { 174 | t.Error(err) 175 | } 176 | }() 177 | 178 | xFunc := func(ctx sqlite.Context, values ...sqlite.Value) { 179 | var buf strings.Builder 180 | for _, v := range values { 181 | buf.WriteString(v.Text()) 182 | } 183 | ctx.ResultText(buf.String()) 184 | } 185 | if err := c.CreateFunction("strcat", true, -1, xFunc, nil, nil); err != nil { 186 | t.Fatal(err) 187 | } 188 | 189 | stmt, _, err := c.PrepareTransient("SELECT strcat('str','','cat');") 190 | if err != nil { 191 | t.Fatal(err) 192 | } 193 | if _, err := stmt.Step(); err != nil { 194 | t.Fatal(err) 195 | } 196 | if got, want := stmt.ColumnText(0), "strcat"; got != want { 197 | t.Errorf("strcat('str','','cat')='%s', want '%s'", got, want) 198 | } 199 | stmt.Finalize() 200 | } 201 | 202 | func TestErrorFunc(t *testing.T) { 203 | c, err := sqlite.OpenConn(":memory:", 0) 204 | if err != nil { 205 | t.Fatal(err) 206 | } 207 | defer func() { 208 | if err := c.Close(); err != nil { 209 | t.Error(err) 210 | } 211 | }() 212 | 213 | nilValueError := errors.New("nil value encountered") 214 | xFunc := func(ctx sqlite.Context, values ...sqlite.Value) { 215 | if values[0].Type() == sqlite.SQLITE_NULL { 216 | ctx.ResultError(nilValueError) 217 | } else { 218 | ctx.ResultValue(values[0]) 219 | } 220 | } 221 | 222 | if err := c.CreateFunction("rejectnull", true, 1, xFunc, nil, nil); err != nil { 223 | t.Fatal(err) 224 | } 225 | stmt, _, err := c.PrepareTransient("SELECT rejectnull(NULL);") 226 | if err != nil { 227 | t.Fatal(err) 228 | } 229 | 230 | _, err = stmt.Step() 231 | if err == nil { 232 | t.Fatal("rejectnull(NULL) failed to produce an error") 233 | } 234 | if sqlErr, ok := err.(sqlite.Error); !ok || sqlErr.Msg != nilValueError.Error() { 235 | t.Fatal("Error does not match expected description") 236 | } 237 | 238 | stmt.Finalize() 239 | } 240 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-llsqlite/crawshaw 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/anacrolix/chansync v0.3.0 7 | github.com/frankban/quicktest v1.14.6 8 | ) 9 | 10 | require ( 11 | github.com/anacrolix/missinggo v1.2.1 // indirect 12 | github.com/anacrolix/missinggo/perf v1.0.0 // indirect 13 | github.com/anacrolix/sync v0.3.0 // indirect 14 | github.com/google/go-cmp v0.5.9 // indirect 15 | github.com/huandu/xstrings v1.2.0 // indirect 16 | github.com/kr/pretty v0.3.1 // indirect 17 | github.com/kr/text v0.2.0 // indirect 18 | github.com/rogpeppe/go-internal v1.9.0 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w= 2 | github.com/anacrolix/chansync v0.3.0 h1:lRu9tbeuw3wl+PhMu/r+JJCRu5ArFXIluOgdF0ao6/U= 3 | github.com/anacrolix/chansync v0.3.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k= 4 | github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= 5 | github.com/anacrolix/envpprof v1.0.0 h1:AwZ+mBP4rQ5f7JSsrsN3h7M2xDW/xSE66IPVOqlnuUc= 6 | github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= 7 | github.com/anacrolix/missinggo v1.1.2-0.20190815015349-b888af804467/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo= 8 | github.com/anacrolix/missinggo v1.2.1 h1:0IE3TqX5y5D0IxeMwTyIgqdDew4QrzcXaaEnJQyjHvw= 9 | github.com/anacrolix/missinggo v1.2.1/go.mod h1:J5cMhif8jPmFoC3+Uvob3OXXNIhOUikzMt+uUjeM21Y= 10 | github.com/anacrolix/missinggo/perf v1.0.0 h1:7ZOGYziGEBytW49+KmYGTaNfnwUqP1HBsy6BqESAJVw= 11 | github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ= 12 | github.com/anacrolix/sync v0.3.0 h1:ZPjTrkqQWEfnYVGTQHh5qNjokWaXnjsyXTJSMsKY0TA= 13 | github.com/anacrolix/sync v0.3.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g= 14 | github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= 15 | github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= 16 | github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c h1:FUUopH4brHNO2kJoNN3pV+OBEYmgraLT/KHZrMM69r0= 17 | github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= 18 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 19 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 20 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 21 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 22 | github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 23 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 24 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 25 | github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= 26 | github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= 27 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 28 | github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 29 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 30 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 31 | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 32 | github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= 33 | github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= 34 | github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= 35 | github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 36 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 37 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 38 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 39 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 40 | github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= 41 | github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 42 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 43 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 44 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 45 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 46 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 47 | github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= 48 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 49 | github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= 50 | github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U= 51 | github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 52 | github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= 53 | github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= 54 | -------------------------------------------------------------------------------- /incrementor.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | // BindIndexStart is the index of the first parameter when using the Stmt.Bind* 4 | // functions. 5 | const BindIndexStart = 1 6 | 7 | // BindIncrementor returns an Incrementor that starts on 1, the first index 8 | // used in Stmt.Bind* functions. This is provided as syntactic sugar for 9 | // binding parameter values to a Stmt. It allows for easily changing query 10 | // parameters without manually fixing up the bind indexes, which can be error 11 | // prone. For example, 12 | // 13 | // stmt := conn.Prep(`INSERT INTO test (a, b, c) VALUES (?, ?, ?);`) 14 | // i := BindIncrementor() 15 | // stmt.BindInt64(i(), a) // i() == 1 16 | // if b > 0 { 17 | // stmt.BindInt64(i(), b) // i() == 2 18 | // } else { 19 | // // Remember to increment the index even if a param is NULL 20 | // stmt.BindNull(i()) // i() == 2 21 | // } 22 | // stmt.BindText(i(), c) // i() == 3 23 | func BindIncrementor() Incrementor { 24 | return NewIncrementor(BindIndexStart) 25 | } 26 | 27 | // ColumnIndexStart is the index of the first column when using the 28 | // Stmt.Column* functions. 29 | const ColumnIndexStart = 0 30 | 31 | // ColumnIncrementor returns an Incrementor that starts on 0, the first index 32 | // used in Stmt.Column* functions. This is provided as syntactic sugar for 33 | // parsing column values from a Stmt. It allows for easily changing queried 34 | // columns without manually fixing up the column indexes, which can be error 35 | // prone. For example, 36 | // 37 | // stmt := conn.Prep(`SELECT a, b, c FROM test;`) 38 | // stmt.Step() 39 | // i := ColumnIncrementor() 40 | // a := stmt.ColumnInt64(i()) // i() == 1 41 | // b := stmt.ColumnInt64(i()) // i() == 2 42 | // c := stmt.ColumnText(i()) // i() == 3 43 | func ColumnIncrementor() Incrementor { 44 | return NewIncrementor(ColumnIndexStart) 45 | } 46 | 47 | // NewIncrementor returns an Incrementor that starts on start. 48 | func NewIncrementor(start int) Incrementor { 49 | return func() int { 50 | start++ 51 | return start - 1 52 | } 53 | } 54 | 55 | // Incrementor is a closure around a value that returns and increments the 56 | // value on each call. For example, the boolean statments in the following code 57 | // snippet would all be true. 58 | // 59 | // i := NewIncrementor(3) 60 | // i() == 3 61 | // i() == 4 62 | // i() == 5 63 | // 64 | // This is provided as syntactic sugar for dealing with bind param and column 65 | // indexes. See BindIncrementor and ColumnIncrementor for small examples. 66 | type Incrementor func() int 67 | -------------------------------------------------------------------------------- /incrementor_test.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIncrementor(t *testing.T) { 8 | start := 5 9 | i := NewIncrementor(start) 10 | if i == nil { 11 | t.Fatal("Incrementor returned nil") 12 | } 13 | if i() != start { 14 | t.Fatalf("first call did not start at %v", start) 15 | } 16 | for j := 1; j < 10; j++ { 17 | if i() != start+j { 18 | t.Fatalf("%v call did not return %v+%v", j, start, j) 19 | } 20 | } 21 | 22 | b := BindIncrementor() 23 | if b() != 1 { 24 | t.Fatal("BindIncrementor does not start at 1") 25 | } 26 | 27 | c := ColumnIncrementor() 28 | if c() != 0 { 29 | t.Fatal("ColumnIncrementor does not start at 0") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /link.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | //go:build linksqlite3 16 | // +build linksqlite3 17 | 18 | package sqlite 19 | 20 | // #cgo LDFLAGS: sqlite3.o 21 | import "C" 22 | -------------------------------------------------------------------------------- /openflags.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlite 16 | 17 | // #include 18 | import "C" 19 | import ( 20 | "fmt" 21 | "strings" 22 | ) 23 | 24 | // OpenFlags are flags used when opening a Conn. 25 | // 26 | // https://www.sqlite.org/c3ref/c_open_autoproxy.html 27 | type OpenFlags int 28 | 29 | const ( 30 | SQLITE_OPEN_READONLY OpenFlags = C.SQLITE_OPEN_READONLY 31 | SQLITE_OPEN_READWRITE OpenFlags = C.SQLITE_OPEN_READWRITE 32 | SQLITE_OPEN_CREATE OpenFlags = C.SQLITE_OPEN_CREATE 33 | SQLITE_OPEN_DELETEONCLOSE OpenFlags = C.SQLITE_OPEN_DELETEONCLOSE 34 | SQLITE_OPEN_EXCLUSIVE OpenFlags = C.SQLITE_OPEN_EXCLUSIVE 35 | SQLITE_OPEN_AUTOPROXY OpenFlags = C.SQLITE_OPEN_AUTOPROXY 36 | SQLITE_OPEN_URI OpenFlags = C.SQLITE_OPEN_URI 37 | SQLITE_OPEN_MEMORY OpenFlags = C.SQLITE_OPEN_MEMORY 38 | SQLITE_OPEN_MAIN_DB OpenFlags = C.SQLITE_OPEN_MAIN_DB 39 | SQLITE_OPEN_TEMP_DB OpenFlags = C.SQLITE_OPEN_TEMP_DB 40 | SQLITE_OPEN_TRANSIENT_DB OpenFlags = C.SQLITE_OPEN_TRANSIENT_DB 41 | SQLITE_OPEN_MAIN_JOURNAL OpenFlags = C.SQLITE_OPEN_MAIN_JOURNAL 42 | SQLITE_OPEN_TEMP_JOURNAL OpenFlags = C.SQLITE_OPEN_TEMP_JOURNAL 43 | SQLITE_OPEN_SUBJOURNAL OpenFlags = C.SQLITE_OPEN_SUBJOURNAL 44 | SQLITE_OPEN_MASTER_JOURNAL OpenFlags = C.SQLITE_OPEN_MASTER_JOURNAL 45 | SQLITE_OPEN_NOMUTEX OpenFlags = C.SQLITE_OPEN_NOMUTEX 46 | SQLITE_OPEN_FULLMUTEX OpenFlags = C.SQLITE_OPEN_FULLMUTEX 47 | SQLITE_OPEN_SHAREDCACHE OpenFlags = C.SQLITE_OPEN_SHAREDCACHE 48 | SQLITE_OPEN_PRIVATECACHE OpenFlags = C.SQLITE_OPEN_PRIVATECACHE 49 | SQLITE_OPEN_WAL OpenFlags = C.SQLITE_OPEN_WAL 50 | SQLITE_OPEN_NOFOLLOW OpenFlags = C.SQLITE_OPEN_NOFOLLOW 51 | 52 | OpenFlagsDefault = SQLITE_OPEN_READWRITE | 53 | SQLITE_OPEN_CREATE | 54 | SQLITE_OPEN_WAL | 55 | SQLITE_OPEN_URI | 56 | SQLITE_OPEN_NOMUTEX 57 | ) 58 | 59 | var allOpenFlags = []OpenFlags{ 60 | SQLITE_OPEN_READONLY, 61 | SQLITE_OPEN_READWRITE, 62 | SQLITE_OPEN_CREATE, 63 | SQLITE_OPEN_DELETEONCLOSE, 64 | SQLITE_OPEN_EXCLUSIVE, 65 | SQLITE_OPEN_AUTOPROXY, 66 | SQLITE_OPEN_URI, 67 | SQLITE_OPEN_MEMORY, 68 | SQLITE_OPEN_MAIN_DB, 69 | SQLITE_OPEN_TEMP_DB, 70 | SQLITE_OPEN_TRANSIENT_DB, 71 | SQLITE_OPEN_MAIN_JOURNAL, 72 | SQLITE_OPEN_TEMP_JOURNAL, 73 | SQLITE_OPEN_SUBJOURNAL, 74 | SQLITE_OPEN_MASTER_JOURNAL, 75 | SQLITE_OPEN_NOMUTEX, 76 | SQLITE_OPEN_FULLMUTEX, 77 | SQLITE_OPEN_SHAREDCACHE, 78 | SQLITE_OPEN_PRIVATECACHE, 79 | SQLITE_OPEN_WAL, 80 | SQLITE_OPEN_NOFOLLOW, 81 | } 82 | 83 | var openFlagsStrings = map[OpenFlags]string{ 84 | SQLITE_OPEN_READONLY: "SQLITE_OPEN_READONLY", 85 | SQLITE_OPEN_READWRITE: "SQLITE_OPEN_READWRITE", 86 | SQLITE_OPEN_CREATE: "SQLITE_OPEN_CREATE", 87 | SQLITE_OPEN_DELETEONCLOSE: "SQLITE_OPEN_DELETEONCLOSE", 88 | SQLITE_OPEN_EXCLUSIVE: "SQLITE_OPEN_EXCLUSIVE", 89 | SQLITE_OPEN_AUTOPROXY: "SQLITE_OPEN_AUTOPROXY", 90 | SQLITE_OPEN_URI: "SQLITE_OPEN_URI", 91 | SQLITE_OPEN_MEMORY: "SQLITE_OPEN_MEMORY", 92 | SQLITE_OPEN_MAIN_DB: "SQLITE_OPEN_MAIN_DB", 93 | SQLITE_OPEN_TEMP_DB: "SQLITE_OPEN_TEMP_DB", 94 | SQLITE_OPEN_TRANSIENT_DB: "SQLITE_OPEN_TRANSIENT_DB", 95 | SQLITE_OPEN_MAIN_JOURNAL: "SQLITE_OPEN_MAIN_JOURNAL", 96 | SQLITE_OPEN_TEMP_JOURNAL: "SQLITE_OPEN_TEMP_JOURNAL", 97 | SQLITE_OPEN_SUBJOURNAL: "SQLITE_OPEN_SUBJOURNAL", 98 | SQLITE_OPEN_MASTER_JOURNAL: "SQLITE_OPEN_MASTER_JOURNAL", 99 | SQLITE_OPEN_NOMUTEX: "SQLITE_OPEN_NOMUTEX", 100 | SQLITE_OPEN_FULLMUTEX: "SQLITE_OPEN_FULLMUTEX", 101 | SQLITE_OPEN_SHAREDCACHE: "SQLITE_OPEN_SHAREDCACHE", 102 | SQLITE_OPEN_PRIVATECACHE: "SQLITE_OPEN_PRIVATECACHE", 103 | SQLITE_OPEN_WAL: "SQLITE_OPEN_WAL", 104 | SQLITE_OPEN_NOFOLLOW: "SQLITE_OPEN_NOFOLLOW", 105 | } 106 | 107 | func (o OpenFlags) String() string { 108 | var flags []string 109 | for _, flag := range allOpenFlags { 110 | if o&flag == 0 { 111 | continue 112 | } 113 | flagStr, ok := openFlagsStrings[flag] 114 | if !ok { 115 | flagStr = fmt.Sprintf("UNKNOWN FLAG: %x", flag) 116 | } 117 | flags = append(flags, flagStr) 118 | } 119 | return strings.Join(flags, "|") 120 | } 121 | -------------------------------------------------------------------------------- /session_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlite_test 16 | 17 | import ( 18 | "bytes" 19 | "reflect" 20 | "testing" 21 | 22 | "github.com/go-llsqlite/crawshaw" 23 | "github.com/go-llsqlite/crawshaw/sqlitex" 24 | ) 25 | 26 | func initT(t *testing.T, conn *sqlite.Conn) { 27 | if _, err := conn.Prep(`INSERT INTO t (c1, c2, c3) VALUES ('1', '2', '3');`).Step(); err != nil { 28 | t.Fatal(err) 29 | } 30 | if _, err := conn.Prep(`INSERT INTO t (c1, c2, c3) VALUES ('4', '5', '6');`).Step(); err != nil { 31 | t.Fatal(err) 32 | } 33 | } 34 | 35 | func fillSession(t *testing.T) (*sqlite.Conn, *sqlite.Session) { 36 | conn, err := sqlite.OpenConn(":memory:", 0) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | 41 | if _, err := conn.Prep("CREATE TABLE t (c1 PRIMARY KEY, c2, c3);").Step(); err != nil { 42 | t.Fatal(err) 43 | } 44 | initT(t, conn) // two rows that predate the session 45 | 46 | s, err := conn.CreateSession("") 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | if err := s.Attach(""); err != nil { 51 | t.Fatal(err) 52 | } 53 | 54 | stmts := []string{ 55 | `UPDATE t SET c1='one' WHERE c1='1';`, 56 | `UPDATE t SET c2='two', c3='three' WHERE c1='one';`, 57 | `UPDATE t SET c1='noop' WHERE c2='2';`, 58 | `DELETE FROM t WHERE c1='4';`, 59 | `INSERT INTO t (c1, c2, c3) VALUES ('four', 'five', 'six');`, 60 | } 61 | 62 | for _, stmt := range stmts { 63 | if _, err := conn.Prep(stmt).Step(); err != nil { 64 | t.Fatal(err) 65 | } 66 | } 67 | 68 | if _, err := conn.Prep("BEGIN;").Step(); err != nil { 69 | t.Fatal(err) 70 | } 71 | stmt, err := conn.Prepare("INSERT INTO t (c1, c2, c3) VALUES (?,?,?);") 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | for i := int64(2); i < 100; i++ { 76 | stmt.Reset() 77 | stmt.BindInt64(1, i) 78 | stmt.BindText(2, "column2") 79 | stmt.BindText(3, "column3") 80 | if _, err := stmt.Step(); err != nil { 81 | t.Fatal(err) 82 | } 83 | } 84 | if _, err := conn.Prep("COMMIT;").Step(); err != nil { 85 | t.Fatal(err) 86 | } 87 | 88 | return conn, s 89 | } 90 | 91 | func TestFillSession(t *testing.T) { 92 | conn, s := fillSession(t) 93 | s.Delete() 94 | conn.Close() 95 | } 96 | 97 | func TestChangeset(t *testing.T) { 98 | conn, s := fillSession(t) 99 | defer func() { 100 | s.Delete() 101 | if err := conn.Close(); err != nil { 102 | t.Error(err) 103 | } 104 | }() 105 | 106 | buf := new(bytes.Buffer) 107 | if err := s.Changeset(buf); err != nil { 108 | t.Fatal(err) 109 | } 110 | b := buf.Bytes() 111 | if len(b) == 0 { 112 | t.Errorf("changeset has no length") 113 | } 114 | 115 | iter, err := sqlite.ChangesetIterStart(bytes.NewReader(b)) 116 | if err != nil { 117 | t.Fatal(err) 118 | } 119 | numChanges := 0 120 | num3Cols := 0 121 | opTypes := make(map[sqlite.OpType]int) 122 | for { 123 | hasRow, err := iter.Next() 124 | if err != nil { 125 | t.Fatal(err) 126 | } 127 | if !hasRow { 128 | break 129 | } 130 | table, numCols, opType, _, err := iter.Op() 131 | if err != nil { 132 | t.Fatalf("numChanges=%d, Op err: %v", numChanges, err) 133 | } 134 | if table != "t" { 135 | t.Errorf("table=%q, want t", table) 136 | } 137 | opTypes[opType]++ 138 | if numCols == 3 { 139 | num3Cols++ 140 | } 141 | numChanges++ 142 | } 143 | if numChanges != 102 { 144 | t.Errorf("numChanges=%d, want 102", numChanges) 145 | } 146 | if num3Cols != 102 { 147 | t.Errorf("num3Cols=%d, want 102", num3Cols) 148 | } 149 | if got := opTypes[sqlite.SQLITE_INSERT]; got != 100 { 150 | t.Errorf("num inserts=%d, want 100", got) 151 | } 152 | if err := iter.Finalize(); err != nil { 153 | t.Fatal(err) 154 | } 155 | } 156 | 157 | func TestChangesetInvert(t *testing.T) { 158 | conn, s := fillSession(t) 159 | defer func() { 160 | s.Delete() 161 | if err := conn.Close(); err != nil { 162 | t.Error(err) 163 | } 164 | }() 165 | 166 | buf := new(bytes.Buffer) 167 | if err := s.Changeset(buf); err != nil { 168 | t.Fatal(err) 169 | } 170 | b := buf.Bytes() 171 | 172 | buf = new(bytes.Buffer) 173 | if err := sqlite.ChangesetInvert(buf, bytes.NewReader(b)); err != nil { 174 | t.Fatal(err) 175 | } 176 | invB := buf.Bytes() 177 | if len(invB) == 0 { 178 | t.Error("no inverted changeset") 179 | } 180 | if bytes.Equal(b, invB) { 181 | t.Error("inverted changeset is unchanged") 182 | } 183 | 184 | buf = new(bytes.Buffer) 185 | if err := sqlite.ChangesetInvert(buf, bytes.NewReader(invB)); err != nil { 186 | t.Fatal(err) 187 | } 188 | invinvB := buf.Bytes() 189 | if !bytes.Equal(b, invinvB) { 190 | t.Error("inv(inv(b)) != b") 191 | } 192 | } 193 | 194 | func TestChangesetApply(t *testing.T) { 195 | conn, s := fillSession(t) 196 | defer func() { 197 | s.Delete() 198 | if err := conn.Close(); err != nil { 199 | t.Error(err) 200 | } 201 | }() 202 | 203 | buf := new(bytes.Buffer) 204 | if err := s.Changeset(buf); err != nil { 205 | t.Fatal(err) 206 | } 207 | b := buf.Bytes() 208 | 209 | invBuf := new(bytes.Buffer) 210 | if err := sqlite.ChangesetInvert(invBuf, bytes.NewReader(b)); err != nil { 211 | t.Fatal(err) 212 | } 213 | 214 | // Undo the entire session. 215 | if err := conn.ChangesetApply(invBuf, nil, nil); err != nil { 216 | t.Fatal(err) 217 | } 218 | 219 | // Table t should now be equivalent to the first two statements: 220 | // INSERT INTO t (c1, c2, c3) VALUES ("1", "2", "3"); 221 | // INSERT INTO t (c1, c2, c3) VALUES ("4", "5", "6"); 222 | want := []string{"1,2,3", "4,5,6"} 223 | var got []string 224 | fn := func(stmt *sqlite.Stmt) error { 225 | got = append(got, stmt.ColumnText(0)+","+stmt.ColumnText(1)+","+stmt.ColumnText(2)) 226 | return nil 227 | } 228 | if err := sqlitex.Exec(conn, "SELECT c1, c2, c3 FROM t ORDER BY c1;", fn); err != nil { 229 | t.Fatal(err) 230 | } 231 | if !reflect.DeepEqual(got, want) { 232 | t.Errorf("got=%v, want=%v", got, want) 233 | } 234 | } 235 | 236 | func TestPatchsetApply(t *testing.T) { 237 | conn, s := fillSession(t) 238 | defer func() { 239 | if s != nil { 240 | s.Delete() 241 | } 242 | if err := conn.Close(); err != nil { 243 | t.Error(err) 244 | } 245 | }() 246 | 247 | var rowCountBefore int 248 | fn := func(stmt *sqlite.Stmt) error { 249 | rowCountBefore = stmt.ColumnInt(0) 250 | return nil 251 | } 252 | if err := sqlitex.Exec(conn, "SELECT COUNT(*) FROM t;", fn); err != nil { 253 | t.Fatal(err) 254 | } 255 | 256 | buf := new(bytes.Buffer) 257 | if err := s.Patchset(buf); err != nil { 258 | t.Fatal(err) 259 | } 260 | b := buf.Bytes() 261 | 262 | s.Delete() 263 | s = nil 264 | 265 | if _, err := conn.Prep("DELETE FROM t;").Step(); err != nil { 266 | t.Fatal(err) 267 | } 268 | initT(t, conn) 269 | 270 | filterFnCalled := false 271 | filterFn := func(tableName string) bool { 272 | if tableName == "t" { 273 | filterFnCalled = true 274 | return true 275 | } else { 276 | t.Errorf("unexpected table in filter fn: %q", tableName) 277 | return false 278 | } 279 | } 280 | conflictFn := func(sqlite.ConflictType, sqlite.ChangesetIter) sqlite.ConflictAction { 281 | t.Error("conflict applying patchset") 282 | return sqlite.SQLITE_CHANGESET_ABORT 283 | } 284 | if err := conn.ChangesetApply(bytes.NewReader(b), filterFn, conflictFn); err != nil { 285 | t.Fatal(err) 286 | } 287 | if !filterFnCalled { 288 | t.Error("filter function not called") 289 | } 290 | 291 | var rowCountAfter int 292 | fn = func(stmt *sqlite.Stmt) error { 293 | rowCountAfter = stmt.ColumnInt(0) 294 | return nil 295 | } 296 | if err := sqlitex.Exec(conn, "SELECT COUNT(*) FROM t;", fn); err != nil { 297 | t.Fatal(err) 298 | } 299 | 300 | if rowCountBefore != rowCountAfter { 301 | t.Errorf("row count is %d, want %d", rowCountAfter, rowCountBefore) 302 | } 303 | 304 | // Second application of patchset should fail. 305 | haveConflict := false 306 | conflictFn = func(ct sqlite.ConflictType, iter sqlite.ChangesetIter) sqlite.ConflictAction { 307 | if ct == sqlite.SQLITE_CHANGESET_CONFLICT { 308 | haveConflict = true 309 | } else { 310 | t.Errorf("unexpected conflict type: %v", ct) 311 | } 312 | _, _, opType, _, err := iter.Op() 313 | if err != nil { 314 | t.Errorf("conflict iter.Op() error: %v", err) 315 | } 316 | if opType != sqlite.SQLITE_INSERT { 317 | t.Errorf("unexpected conflict op type: %v", opType) 318 | } 319 | return sqlite.SQLITE_CHANGESET_ABORT 320 | } 321 | err := conn.ChangesetApply(bytes.NewReader(b), nil, conflictFn) 322 | if code := sqlite.ErrCode(err); code != sqlite.SQLITE_ABORT { 323 | t.Errorf("conflicting changeset Apply error is %v, want SQLITE_ABORT", err) 324 | } 325 | if !haveConflict { 326 | t.Error("no conflict found") 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /snapshot.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlite 16 | 17 | // #include 18 | // #include 19 | import "C" 20 | import ( 21 | "runtime" 22 | "unsafe" 23 | ) 24 | 25 | // A Snapshot records the state of a WAL mode database for some specific point 26 | // in history. 27 | // 28 | // Equivalent to the sqlite3_snapshot* C object. 29 | // 30 | // https://www.sqlite.org/c3ref/snapshot.html 31 | type Snapshot struct { 32 | ptr *C.sqlite3_snapshot 33 | schema *C.char 34 | } 35 | 36 | // GetSnapshot attempts to make a new Snapshot that records the current state 37 | // of the given schema in conn. If successful, a *Snapshot and a func() is 38 | // returned, and the conn will have an open READ transaction which will 39 | // continue to reflect the state of the Snapshot until the returned func() is 40 | // called. No WRITE transaction may occur on conn until the returned func() is 41 | // called. 42 | // 43 | // The returned *Snapshot is threadsafe for creating additional read 44 | // transactions that reflect its state with Conn.StartSnapshotRead. 45 | // 46 | // In theory, so long as at least one read transaction is open on the Snapshot, 47 | // then the WAL file will not be checkpointed past that point, and the Snapshot 48 | // will continue to be available for creating additional read transactions. 49 | // However, if no read transaction is open on the Snapshot, then it is possible 50 | // for the WAL to be checkpointed past the point of the Snapshot. If this 51 | // occurs then there is no way to start a read on the Snapshot. In order to 52 | // ensure that a Snapshot remains readable, always maintain at least one open 53 | // read transaction on the Snapshot. 54 | // 55 | // In practice, this is generally reliable but sometimes the Snapshot can 56 | // sometimes become unavailable for reads unless automatic checkpointing is 57 | // entirely disabled from the start. 58 | // 59 | // The returned *Snapshot has a finalizer that calls Free if it has not been 60 | // called, so it is safe to allow a Snapshot to be garbage collected. However, 61 | // if you are sure that a Snapshot will never be used again by any thread, you 62 | // may call Free once to release the memory earlier. No reads will be possible 63 | // on the Snapshot after Free is called on it, however any open read 64 | // transactions will not be interrupted. 65 | // 66 | // See sqlitex.Pool.GetSnapshot for a helper function for automatically keeping 67 | // an open read transaction on a set aside connection until a Snapshot is GC'd. 68 | // 69 | // The following must be true for this function to succeed: 70 | // 71 | // - The schema of conn must be a WAL mode database. 72 | // 73 | // - There must not be any transaction open on schema of conn. 74 | // 75 | // - At least one transaction must have been written to the current WAL file 76 | // since it was created on disk (by any connection). You can run the following 77 | // SQL to ensure that a WAL file has been created. 78 | // 79 | // BEGIN IMMEDIATE; 80 | // COMMIT; 81 | // 82 | // https://www.sqlite.org/c3ref/snapshot_get.html 83 | func (conn *Conn) GetSnapshot(schema string) (*Snapshot, func(), error) { 84 | var s Snapshot 85 | if schema == "" || schema == "main" { 86 | s.schema = cmain 87 | } else { 88 | s.schema = C.CString(schema) 89 | } 90 | 91 | endRead, err := conn.disableAutoCommitMode() 92 | if err != nil { 93 | return nil, nil, err 94 | } 95 | 96 | res := C.sqlite3_snapshot_get(conn.conn, s.schema, &s.ptr) 97 | if res != 0 { 98 | endRead() 99 | return nil, nil, reserr("Conn.CreateSnapshot", "", "", res) 100 | } 101 | 102 | runtime.SetFinalizer(&s, func(s *Snapshot) { 103 | s.Free() 104 | }) 105 | 106 | return &s, endRead, nil 107 | } 108 | 109 | // Free destroys a Snapshot. Free is not threadsafe but may be called more than 110 | // once. However, it is not necessary to call Free on a Snapshot returned by 111 | // conn.GetSnapshot or pool.GetSnapshot as these set a finalizer that calls 112 | // free which will be run automatically by the GC in a finalizer. However if it 113 | // is guaranteed that a Snapshot will never be used again, calling Free will 114 | // allow memory to be freed earlier. 115 | // 116 | // A Snapshot may become unavailable for reads before Free is called if the WAL 117 | // is checkpointed into the DB past the point of the Snapshot. 118 | // 119 | // https://www.sqlite.org/c3ref/snapshot_free.html 120 | func (s *Snapshot) Free() { 121 | if s.ptr == nil { 122 | return 123 | } 124 | C.sqlite3_snapshot_free(s.ptr) 125 | if s.schema != cmain { 126 | C.free(unsafe.Pointer(s.schema)) 127 | } 128 | s.ptr = nil 129 | } 130 | 131 | // CompareAges returns whether s1 is older, newer or the same age as s2. Age 132 | // refers to writes on the database, not time since creation. 133 | // 134 | // If s is older than s2, a negative number is returned. If s and s2 are the 135 | // same age, zero is returned. If s is newer than s2, a positive number is 136 | // returned. 137 | // 138 | // The result is valid only if both of the following are true: 139 | // 140 | // - The two snapshot handles are associated with the same database file. 141 | // 142 | // - Both of the Snapshots were obtained since the last time the wal file was 143 | // deleted. 144 | // 145 | // https://www.sqlite.org/c3ref/snapshot_cmp.html 146 | func (s *Snapshot) CompareAges(s2 *Snapshot) int { 147 | return int(C.sqlite3_snapshot_cmp(s.ptr, s2.ptr)) 148 | } 149 | 150 | // StartSnapshotRead starts a new read transaction on conn such that the read 151 | // transaction refers to historical Snapshot s, rather than the most recent 152 | // change to the database. 153 | // 154 | // There must be no open transaction on conn. Free must not have been called on 155 | // s prior to or during this function call. 156 | // 157 | // If err is nil, then endRead is a function that will end the read transaction 158 | // and return conn to its original state. Until endRead is called, no writes 159 | // may occur on conn, and all reads on conn will refer to the Snapshot. 160 | // 161 | // https://www.sqlite.org/c3ref/snapshot_open.html 162 | func (conn *Conn) StartSnapshotRead(s *Snapshot) (endRead func(), err error) { 163 | endRead, err = conn.disableAutoCommitMode() 164 | if err != nil { 165 | return 166 | } 167 | 168 | res := C.sqlite3_snapshot_open(conn.conn, s.schema, s.ptr) 169 | if res != 0 { 170 | endRead() 171 | return nil, reserr("Conn.StartSnapshotRead", "", "", res) 172 | } 173 | 174 | return endRead, nil 175 | } 176 | 177 | // disableAutoCommitMode starts a read transaction with `BEGIN;`, disabling 178 | // autocommit mode, and returns a function which when called will end the read 179 | // transaction with `ROLLBACK;`, re-enabling autocommit mode. 180 | // 181 | // https://sqlite.org/c3ref/get_autocommit.html 182 | func (conn *Conn) disableAutoCommitMode() (func(), error) { 183 | begin := conn.Prep("BEGIN;") 184 | defer begin.Reset() 185 | if _, err := begin.Step(); err != nil { 186 | return nil, err 187 | } 188 | rollback := conn.Prep("ROLLBACK;") 189 | return func() { 190 | defer rollback.Reset() 191 | rollback.Step() 192 | }, nil 193 | } 194 | -------------------------------------------------------------------------------- /snapshot_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlite_test 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "os" 21 | "runtime" 22 | "testing" 23 | "time" 24 | 25 | "github.com/go-llsqlite/crawshaw" 26 | "github.com/go-llsqlite/crawshaw/sqlitex" 27 | ) 28 | 29 | var db = fmt.Sprintf("%v/snapshot_test_%v.sqlite3", 30 | os.TempDir(), time.Now().UnixNano()) 31 | 32 | func initDB(t *testing.T) (*sqlite.Conn, *sqlitex.Pool, func()) { 33 | conn, err := sqlite.OpenConn(db, 0) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | if err := sqlitex.ExecScript(conn, `CREATE TABLE t (c1 PRIMARY KEY, c2, c3); 38 | INSERT INTO t (c1, c2, c3) VALUES (1, 2, 3); 39 | INSERT INTO t (c1, c2, c3) VALUES (2, 4, 5);`); err != nil { 40 | conn.Close() 41 | os.Remove(db) 42 | t.Fatal(err) 43 | } 44 | 45 | poolFlags := sqlite.SQLITE_OPEN_READONLY | 46 | sqlite.SQLITE_OPEN_WAL | 47 | sqlite.SQLITE_OPEN_URI | 48 | sqlite.SQLITE_OPEN_NOMUTEX 49 | poolSize := 2 50 | pool, err := sqlitex.Open(db, poolFlags, poolSize) 51 | if err != nil { 52 | conn.Close() 53 | os.Remove(db) 54 | t.Fatal(err) 55 | } 56 | cleanup := func() { 57 | pool.Close() 58 | conn.Close() 59 | os.Remove(db) 60 | } 61 | 62 | for i := 0; i < poolSize; i++ { 63 | c := pool.Get(nil) 64 | if err := sqlitex.ExecScript(c, `PRAGMA application_id;`); err != nil { 65 | cleanup() 66 | t.Fatal(err) 67 | } 68 | pool.Put(c) 69 | } 70 | 71 | return conn, pool, cleanup 72 | } 73 | 74 | func TestSnapshot(t *testing.T) { 75 | conn, pool, cleanup := initDB(t) 76 | defer cleanup() 77 | 78 | s1, err := pool.GetSnapshot(nil, "") 79 | if err != nil { 80 | t.Fatal(err) 81 | } 82 | 83 | if err := sqlitex.ExecScript(conn, `UPDATE t SET c2 = 100; 84 | INSERT INTO t (c2, c3) VALUES (2, 3); 85 | INSERT INTO t (c2, c3) VALUES (2, 3); 86 | INSERT INTO t (c2, c3) VALUES (2, 3); 87 | INSERT INTO t (c2, c3) VALUES (2, 3); 88 | INSERT INTO t (c2, c3) VALUES (2, 3); 89 | INSERT INTO t (c2, c3) VALUES (2, 3); `); err != nil { 90 | t.Fatal(err) 91 | } 92 | if err := sqlitex.Exec(conn, `PRAGMA wal_checkpoint;`, nil); err != nil { 93 | t.Fatal(err) 94 | } 95 | 96 | read := pool.Get(nil) 97 | defer pool.Put(read) 98 | 99 | endRead, err := read.StartSnapshotRead(s1) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | 104 | c2, err := sqlitex.ResultInt(read.Prep(`SELECT c2 FROM t WHERE c1 = 1;`)) 105 | if c2 != 2 { 106 | t.Fatalf("expected row1 c2 to be 2 but found %v", c2) 107 | } 108 | endRead() 109 | 110 | c2, err = sqlitex.ResultInt(read.Prep(`SELECT c2 FROM t WHERE c1 = 1;`)) 111 | if c2 != 100 { 112 | t.Fatalf("expected row1 c2 to be 100 but found %v", c2) 113 | } 114 | 115 | s2, release, err := read.GetSnapshot("") 116 | if err != nil { 117 | t.Fatal(err) 118 | } 119 | defer s2.Free() 120 | defer release() 121 | 122 | if cmp := s1.CompareAges(s2); cmp >= 0 { 123 | t.Fatalf("expected s1 to be older than s2 but CompareAges returned %v", cmp) 124 | } 125 | 126 | s1 = nil 127 | runtime.GC() 128 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 129 | defer cancel() 130 | read2 := pool.Get(ctx) 131 | if read2 == nil { 132 | t.Fatalf("expected to get another conn from the pool, but it was nil") 133 | } 134 | defer pool.Put(read2) 135 | } 136 | -------------------------------------------------------------------------------- /sqlite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // Copyright (c) 2021 Ross Light 3 | // 4 | // Permission to use, copy, modify, and distribute this software for any 5 | // purpose with or without fee is hereby granted, provided that the above 6 | // copyright notice and this permission notice appear in all copies. 7 | // 8 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | // 16 | // SPDX-License-Identifier: ISC 17 | 18 | package sqlite_test 19 | 20 | import ( 21 | "context" 22 | "fmt" 23 | "io" 24 | "io/ioutil" 25 | "os" 26 | "path/filepath" 27 | "reflect" 28 | "sort" 29 | "strings" 30 | "testing" 31 | "time" 32 | 33 | qt "github.com/frankban/quicktest" 34 | 35 | "github.com/go-llsqlite/crawshaw" 36 | "github.com/go-llsqlite/crawshaw/sqlitex" 37 | ) 38 | 39 | func TestConn(t *testing.T) { 40 | c, err := sqlite.OpenConn(":memory:", 0) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | defer func() { 45 | if err := c.Close(); err != nil { 46 | t.Error(err) 47 | } 48 | }() 49 | 50 | stmt, _, err := c.PrepareTransient("CREATE TABLE bartable (foo1 string, foo2 integer);") 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | hasRow, err := stmt.Step() 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | if hasRow { 59 | t.Errorf("CREATE TABLE reports having a row") 60 | } 61 | if err := stmt.Finalize(); err != nil { 62 | t.Error(err) 63 | } 64 | 65 | fooVals := []string{ 66 | "bar", 67 | "baz", 68 | "bop", 69 | } 70 | 71 | for i, val := range fooVals { 72 | stmt, err := c.Prepare("INSERT INTO bartable (foo1, foo2) VALUES ($f1, $f2);") 73 | if err != nil { 74 | t.Fatal(err) 75 | } 76 | stmt.SetText("$f1", val) 77 | stmt.SetInt64("$f2", int64(i)) 78 | hasRow, err = stmt.Step() 79 | if err != nil { 80 | t.Errorf("INSERT %q: %v", val, err) 81 | } 82 | if hasRow { 83 | t.Errorf("INSERT %q: has row", val) 84 | } 85 | } 86 | 87 | stmt, err = c.Prepare("SELECT foo1, foo2 FROM bartable;") 88 | if err != nil { 89 | t.Fatal(err) 90 | } 91 | gotVals := []string{} 92 | gotInts := []int64{} 93 | for { 94 | hasRow, err := stmt.Step() 95 | if err != nil { 96 | t.Errorf("SELECT: %v", err) 97 | } 98 | if !hasRow { 99 | break 100 | } 101 | val := stmt.ColumnText(0) 102 | if getVal := stmt.GetText("foo1"); getVal != val { 103 | t.Errorf(`GetText("foo1")=%q, want %q`, getVal, val) 104 | } 105 | intVal := stmt.ColumnInt64(1) 106 | if getIntVal := stmt.GetInt64("foo2"); getIntVal != intVal { 107 | t.Errorf(`GetText("foo2")=%q, want %q`, getIntVal, intVal) 108 | } 109 | typ := stmt.ColumnType(0) 110 | if typ != sqlite.SQLITE_TEXT { 111 | t.Errorf(`ColumnType(0)=%q, want %q`, typ, sqlite.SQLITE_TEXT) 112 | } 113 | if getType := stmt.GetType("foo1"); getType != typ { 114 | t.Errorf(`GetType("foo1")=%q, want %q`, getType, typ) 115 | } 116 | intTyp := stmt.ColumnType(1) 117 | if intTyp != sqlite.SQLITE_INTEGER { 118 | t.Errorf(`ColumnType(1)=%q, want %q`, intTyp, sqlite.SQLITE_INTEGER) 119 | } 120 | if getIntType := stmt.GetType("foo2"); getIntType != intTyp { 121 | t.Errorf(`GetType("foo2")=%q, want %q`, getIntType, intTyp) 122 | } 123 | gotVals = append(gotVals, val) 124 | gotInts = append(gotInts, intVal) 125 | } 126 | 127 | if !reflect.DeepEqual(fooVals, gotVals) { 128 | t.Errorf("SELECT foo1: got %v, want %v", gotVals, fooVals) 129 | } 130 | 131 | wantInts := []int64{0, 1, 2} 132 | if !reflect.DeepEqual(wantInts, gotInts) { 133 | t.Errorf("SELECT foo2: got %v, want %v", gotInts, wantInts) 134 | } 135 | } 136 | 137 | func TestEarlyInterrupt(t *testing.T) { 138 | c, err := sqlite.OpenConn(":memory:", 0) 139 | if err != nil { 140 | t.Fatal(err) 141 | } 142 | defer func() { 143 | if err := c.Close(); err != nil { 144 | t.Error(err) 145 | } 146 | }() 147 | 148 | ctx, cancel := context.WithCancel(context.Background()) 149 | c.SetInterrupt(ctx.Done()) 150 | 151 | stmt, _, err := c.PrepareTransient("CREATE TABLE bartable (foo1 string, foo2 integer);") 152 | if err != nil { 153 | t.Fatal(err) 154 | } 155 | if _, err := stmt.Step(); err != nil { 156 | t.Fatal(err) 157 | } 158 | stmt.Finalize() 159 | 160 | cancel() 161 | 162 | stmt, err = c.Prepare("INSERT INTO bartable (foo1, foo2) VALUES ($f1, $f2);") 163 | if err == nil { 164 | t.Fatal("Prepare err=nil, want prepare to fail") 165 | } 166 | if code := sqlite.ErrCode(err); code != sqlite.SQLITE_INTERRUPT { 167 | t.Fatalf("Prepare err=%s, want SQLITE_INTERRUPT", code) 168 | } 169 | } 170 | 171 | func TestStmtInterrupt(t *testing.T) { 172 | conn, err := sqlite.OpenConn(":memory:", 0) 173 | if err != nil { 174 | t.Fatal(err) 175 | } 176 | defer conn.Close() 177 | stmt := sqlite.InterruptedStmt(conn, "CREATE TABLE intt (c);") 178 | 179 | _, err = stmt.Step() 180 | if err == nil { 181 | t.Fatal("interrupted stmt Step succeeded") 182 | } 183 | if got := sqlite.ErrCode(err); got != sqlite.SQLITE_INTERRUPT { 184 | t.Errorf("Step err=%v, want SQLITE_INTERRUPT", got) 185 | } 186 | } 187 | 188 | func TestInterruptStepReset(t *testing.T) { 189 | c, err := sqlite.OpenConn(":memory:", 0) 190 | if err != nil { 191 | t.Fatal(err) 192 | } 193 | defer func() { 194 | if err := c.Close(); err != nil { 195 | t.Error(err) 196 | } 197 | }() 198 | 199 | err = sqlitex.ExecScript(c, `CREATE TABLE resetint (c); 200 | INSERT INTO resetint (c) VALUES (1); 201 | INSERT INTO resetint (c) VALUES (2); 202 | INSERT INTO resetint (c) VALUES (3);`) 203 | if err != nil { 204 | t.Fatal(err) 205 | } 206 | 207 | ctx, cancel := context.WithCancel(context.Background()) 208 | c.SetInterrupt(ctx.Done()) 209 | 210 | stmt := c.Prep("SELECT * FROM resetint;") 211 | if _, err := stmt.Step(); err != nil { 212 | t.Fatal(err) 213 | } 214 | cancel() 215 | // next Step needs to reset stmt 216 | if _, err := stmt.Step(); sqlite.ErrCode(err) != sqlite.SQLITE_INTERRUPT { 217 | t.Fatalf("want SQLITE_INTERRUPT, got %v", err) 218 | } 219 | c.SetInterrupt(nil) 220 | stmt = c.Prep("SELECT c FROM resetint ORDER BY c;") 221 | if _, err := stmt.Step(); err != nil { 222 | t.Fatalf("statement after interrupt reset failed: %v", err) 223 | } 224 | stmt.Reset() 225 | } 226 | 227 | func TestInterruptReset(t *testing.T) { 228 | c, err := sqlite.OpenConn(":memory:", 0) 229 | if err != nil { 230 | t.Fatal(err) 231 | } 232 | defer func() { 233 | if err := c.Close(); err != nil { 234 | t.Error(err) 235 | } 236 | }() 237 | 238 | err = sqlitex.ExecScript(c, `CREATE TABLE resetint (c); 239 | INSERT INTO resetint (c) VALUES (1); 240 | INSERT INTO resetint (c) VALUES (2); 241 | INSERT INTO resetint (c) VALUES (3);`) 242 | if err != nil { 243 | t.Fatal(err) 244 | } 245 | 246 | ctx, cancel := context.WithCancel(context.Background()) 247 | c.SetInterrupt(ctx.Done()) 248 | 249 | stmt := c.Prep("SELECT * FROM resetint;") 250 | if _, err := stmt.Step(); err != nil { 251 | t.Fatal(err) 252 | } 253 | cancel() 254 | c.SetInterrupt(nil) 255 | stmt = c.Prep("SELECT c FROM resetint ORDER BY c;") 256 | if _, err := stmt.Step(); err != nil { 257 | t.Fatalf("statement after interrupt reset failed: %v", err) 258 | } 259 | stmt.Reset() 260 | } 261 | 262 | func TestTrailingBytes(t *testing.T) { 263 | conn, err := sqlite.OpenConn(":memory:", 0) 264 | if err != nil { 265 | t.Fatal(err) 266 | } 267 | defer func() { 268 | if err := conn.Close(); err != nil { 269 | t.Error(err) 270 | } 271 | }() 272 | 273 | stmt, trailingBytes, err := conn.PrepareTransient("BEGIN; -- 56") 274 | if err != nil { 275 | t.Error(err) 276 | } 277 | stmt.Finalize() 278 | const want = 6 279 | if trailingBytes != want { 280 | t.Errorf("trailingBytes=%d, want %d", trailingBytes, want) 281 | } 282 | } 283 | 284 | func TestTrailingBytesError(t *testing.T) { 285 | conn, err := sqlite.OpenConn(":memory:", 0) 286 | if err != nil { 287 | t.Fatal(err) 288 | } 289 | defer func() { 290 | if err := conn.Close(); err != nil { 291 | t.Error(err) 292 | } 293 | }() 294 | 295 | if _, err := conn.Prepare("BEGIN; -- 56"); err == nil { 296 | t.Error("expecting error on trailing bytes") 297 | } 298 | } 299 | 300 | func TestBadParam(t *testing.T) { 301 | c, err := sqlite.OpenConn(":memory:", 0) 302 | if err != nil { 303 | t.Fatal(err) 304 | } 305 | defer func() { 306 | if err := c.Close(); err != nil { 307 | t.Error(err) 308 | } 309 | }() 310 | 311 | stmt, err := c.Prepare("CREATE TABLE IF NOT EXISTS badparam (a, b, c);") 312 | if err != nil { 313 | t.Fatal(err) 314 | } 315 | if _, err := stmt.Step(); err != nil { 316 | t.Fatal(err) 317 | } 318 | 319 | stmt, err = c.Prepare("INSERT INTO badparam (a, b, c) VALUES ($a, $b, $c);") 320 | if err != nil { 321 | t.Fatal(err) 322 | } 323 | stmt.SetText("$a", "col_a") 324 | stmt.SetText("$b", "col_b") 325 | stmt.SetText("$badparam", "notaval") 326 | stmt.SetText("$c", "col_c") 327 | _, err = stmt.Step() 328 | if err == nil { 329 | t.Fatal("expecting error from bad param name, got no error") 330 | } 331 | if got := err.Error(); !strings.Contains(got, "$badparam") { 332 | t.Errorf(`error does not mention "$badparam": %v`, got) 333 | } 334 | stmt.Finalize() 335 | } 336 | 337 | func TestParallel(t *testing.T) { 338 | c, err := sqlite.OpenConn(":memory:", 0) 339 | if err != nil { 340 | t.Fatal(err) 341 | } 342 | defer func() { 343 | if err := c.Close(); err != nil { 344 | t.Error(err) 345 | } 346 | }() 347 | 348 | stmt := c.Prep("CREATE TABLE testparallel (c);") 349 | if _, err := stmt.Step(); err != nil { 350 | t.Fatal(err) 351 | } 352 | 353 | stmt = c.Prep("INSERT INTO testparallel (c) VALUES ($c);") 354 | stmt.SetText("$c", "text") 355 | if _, err := stmt.Step(); err != nil { 356 | t.Fatal(err) 357 | } 358 | stmt.Reset() 359 | if _, err := stmt.Step(); err != nil { 360 | t.Fatal(err) 361 | } 362 | 363 | stmt = c.Prep("SELECT * from testparallel;") 364 | if _, err := stmt.Step(); err != nil { 365 | t.Fatal(err) 366 | } 367 | 368 | stmt2 := c.Prep("SELECT count(*) from testparallel;") 369 | if hasRow, err := stmt2.Step(); err != nil { 370 | t.Fatal(err) 371 | } else if !hasRow { 372 | t.Error("expecting count row") 373 | } 374 | 375 | if hasRow, err := stmt.Step(); err != nil { 376 | t.Fatal(err) 377 | } else if !hasRow { 378 | t.Error("expecting second row") 379 | } 380 | } 381 | 382 | func TestBindBytes(t *testing.T) { 383 | tests := []struct { 384 | name string 385 | val []byte 386 | wantNull bool 387 | }{ 388 | { 389 | name: "Data with NUL bytes", 390 | val: []byte("\x00\x00hello world\x00\x00\x00"), 391 | }, 392 | { 393 | name: "Empty non-nil slice", 394 | val: []byte{}, 395 | }, 396 | { 397 | name: "Nil slice stores NULL", 398 | wantNull: true, 399 | }, 400 | } 401 | for _, test := range tests { 402 | t.Run(test.name, func(t *testing.T) { 403 | c, err := sqlite.OpenConn(":memory:", 0) 404 | if err != nil { 405 | t.Fatal(err) 406 | } 407 | defer func() { 408 | if err := c.Close(); err != nil { 409 | t.Error(err) 410 | } 411 | }() 412 | 413 | stmt := c.Prep("CREATE TABLE IF NOT EXISTS bindbytes (c);") 414 | if _, err := stmt.Step(); err != nil { 415 | t.Fatal(err) 416 | } 417 | stmt = c.Prep("INSERT INTO bindbytes (c) VALUES ($bytes);") 418 | stmt.SetBytes("$bytes", test.val) 419 | if _, err := stmt.Step(); err != nil { 420 | t.Fatal(err) 421 | } 422 | 423 | if !test.wantNull { 424 | stmt = c.Prep("SELECT count(*) FROM bindbytes WHERE c = $bytes;") 425 | stmt.SetBytes("$bytes", test.val) 426 | } else { 427 | stmt = c.Prep("SELECT count(*) FROM bindbytes WHERE c IS NULL") 428 | } 429 | if hasRow, err := stmt.Step(); err != nil { 430 | t.Fatal(err) 431 | } else if !hasRow { 432 | t.Error("SetBytes: result has no row") 433 | } 434 | if got := stmt.ColumnInt(0); got != 1 { 435 | t.Errorf("SetBytes: count is %d, want 1", got) 436 | } 437 | 438 | stmt.Reset() 439 | 440 | if test.wantNull { 441 | // Skip blob test, won't work with a NULL value 442 | return 443 | } 444 | 445 | blob, err := c.OpenBlob("", "bindbytes", "c", 1, false) 446 | if err != nil { 447 | t.Fatalf("SetBytes: OpenBlob: %v", err) 448 | } 449 | defer func() { 450 | if err := blob.Close(); err != nil { 451 | t.Error(err) 452 | } 453 | }() 454 | 455 | bs := sqlitex.NewBlobSeeker(blob) 456 | qtc := qt.New(t) 457 | storedVal, err := io.ReadAll(bs) 458 | qtc.Assert(err, qt.IsNil) 459 | qtc.Assert(storedVal, qt.DeepEquals, test.val) 460 | }) 461 | } 462 | } 463 | 464 | func TestBindText(t *testing.T) { 465 | c, err := sqlite.OpenConn(":memory:", 0) 466 | if err != nil { 467 | t.Fatal(err) 468 | } 469 | defer func() { 470 | if err := c.Close(); err != nil { 471 | t.Error(err) 472 | } 473 | }() 474 | 475 | const val = "column_value" 476 | 477 | stmt := c.Prep("CREATE TABLE IF NOT EXISTS bindtext (c);") 478 | if _, err := stmt.Step(); err != nil { 479 | t.Fatal(err) 480 | } 481 | stmt = c.Prep("INSERT INTO bindtext (c) VALUES ($text);") 482 | stmt.SetText("$text", val) 483 | if _, err := stmt.Step(); err != nil { 484 | t.Fatal(err) 485 | } 486 | 487 | stmt = c.Prep("SELECT count(*) FROM bindtext WHERE c = $text;") 488 | stmt.SetText("$text", val) 489 | if hasRow, err := stmt.Step(); err != nil { 490 | t.Fatal(err) 491 | } else if !hasRow { 492 | t.Error("SetText: result has no row") 493 | } 494 | if got := stmt.ColumnInt(0); got != 1 { 495 | t.Errorf("SetText: count is %d, want 1", got) 496 | } 497 | 498 | stmt.Reset() 499 | 500 | stmt.SetText("$text", val) 501 | if hasRow, err := stmt.Step(); err != nil { 502 | t.Fatal(err) 503 | } else if !hasRow { 504 | t.Error("SetText: result has no row") 505 | } 506 | if got := stmt.ColumnInt(0); got != 1 { 507 | t.Errorf("SetText: count is %d, want 1", got) 508 | } 509 | } 510 | 511 | func TestExtendedCodes(t *testing.T) { 512 | c, err := sqlite.OpenConn(":memory:", 0) 513 | if err != nil { 514 | t.Fatal(err) 515 | } 516 | defer func() { 517 | if err := c.Close(); err != nil { 518 | t.Error(err) 519 | } 520 | }() 521 | 522 | stmt := c.Prep("CREATE TABLE IF NOT EXISTS extcodes (c UNIQUE);") 523 | if _, err := stmt.Step(); err != nil { 524 | t.Fatal(err) 525 | } 526 | stmt = c.Prep("INSERT INTO extcodes (c) VALUES ($c);") 527 | stmt.SetText("$c", "value1") 528 | if _, err := stmt.Step(); err != nil { 529 | t.Fatal(err) 530 | } 531 | stmt.Reset() 532 | stmt.SetText("$c", "value1") 533 | _, err = stmt.Step() 534 | if err == nil { 535 | t.Fatal("expected UNIQUE error, got nothing") 536 | } 537 | if got, want := sqlite.ErrCode(err), sqlite.SQLITE_CONSTRAINT_UNIQUE; got != want { 538 | t.Errorf("got err=%s, want %s", got, want) 539 | } 540 | } 541 | 542 | func TestWrappedErrors(t *testing.T) { 543 | rawErr := sqlite.Error{ 544 | Code: sqlite.SQLITE_INTERRUPT, 545 | Loc: "chao", 546 | Query: "SELECT * FROM thieves", 547 | } 548 | if got, want := sqlite.ErrCode(rawErr), sqlite.SQLITE_INTERRUPT; got != want { 549 | t.Errorf("got err=%s, want %s", got, want) 550 | } 551 | 552 | wrappedErr := fmt.Errorf("Do something: %w", rawErr) 553 | if got, want := sqlite.ErrCode(wrappedErr), sqlite.SQLITE_INTERRUPT; got != want { 554 | t.Errorf("got err=%s, want %s", got, want) 555 | } 556 | } 557 | 558 | func TestJournalMode(t *testing.T) { 559 | dir, err := ioutil.TempDir("", "crawshaw.io") 560 | if err != nil { 561 | t.Fatal(err) 562 | } 563 | defer os.RemoveAll(dir) 564 | 565 | tests := []struct { 566 | db string 567 | mode string 568 | flags sqlite.OpenFlags 569 | }{ 570 | { 571 | "test-delete.db", 572 | "delete", 573 | sqlite.SQLITE_OPEN_READWRITE | sqlite.SQLITE_OPEN_CREATE, 574 | }, 575 | { 576 | "test-wal.db", 577 | "wal", 578 | sqlite.SQLITE_OPEN_READWRITE | sqlite.SQLITE_OPEN_CREATE | sqlite.SQLITE_OPEN_WAL, 579 | }, 580 | { 581 | "test-default-wal.db", 582 | "wal", 583 | 0, 584 | }, 585 | // memory databases can't have wal, only journal_mode=memory 586 | { 587 | ":memory:", 588 | "memory", 589 | 0, 590 | }, 591 | // temp databases can't have wal, only journal_mode=delete 592 | { 593 | "", 594 | "delete", 595 | 0, 596 | }, 597 | } 598 | 599 | for _, test := range tests { 600 | if test.db != ":memory:" && test.db != "" { 601 | test.db = filepath.Join(dir, test.db) 602 | } 603 | c, err := sqlite.OpenConn(test.db, test.flags) 604 | if err != nil { 605 | t.Fatal(err) 606 | } 607 | defer func() { 608 | if err := c.Close(); err != nil { 609 | t.Error(err) 610 | } 611 | }() 612 | stmt := c.Prep("PRAGMA journal_mode;") 613 | if hasRow, err := stmt.Step(); err != nil { 614 | t.Fatal(err) 615 | } else if !hasRow { 616 | t.Error("PRAGMA journal_mode: has no row") 617 | } 618 | if got := stmt.GetText("journal_mode"); got != test.mode { 619 | t.Errorf("journal_mode not set properly, got: %s, want: %s", got, test.mode) 620 | } 621 | } 622 | } 623 | 624 | func TestBusyTimeout(t *testing.T) { 625 | dir, err := ioutil.TempDir("", "crawshaw.io") 626 | if err != nil { 627 | t.Fatal(err) 628 | } 629 | defer os.RemoveAll(dir) 630 | 631 | db := filepath.Join(dir, "busytest.db") 632 | 633 | flags := sqlite.SQLITE_OPEN_READWRITE | sqlite.SQLITE_OPEN_CREATE | sqlite.SQLITE_OPEN_WAL 634 | 635 | conn0, err := sqlite.OpenConn(db, flags) 636 | if err != nil { 637 | t.Fatal(err) 638 | } 639 | defer func() { 640 | if err := conn0.Close(); err != nil { 641 | t.Error(err) 642 | } 643 | }() 644 | 645 | conn1, err := sqlite.OpenConn(db, flags) 646 | if err != nil { 647 | t.Fatal(err) 648 | } 649 | defer func() { 650 | if err := conn1.Close(); err != nil { 651 | t.Error(err) 652 | } 653 | }() 654 | 655 | err = sqlitex.ExecScript(conn0, ` 656 | CREATE TABLE t (c); 657 | INSERT INTO t (c) VALUES (1); 658 | `) 659 | if err != nil { 660 | t.Fatal(err) 661 | } 662 | 663 | c0Lock := func() { 664 | if _, err := conn0.Prep("BEGIN;").Step(); err != nil { 665 | t.Fatal(err) 666 | } 667 | if _, err := conn0.Prep("INSERT INTO t (c) VALUES (2);").Step(); err != nil { 668 | t.Fatal(err) 669 | } 670 | } 671 | c0Unlock := func() { 672 | if _, err := conn0.Prep("COMMIT;").Step(); err != nil { 673 | t.Fatal(err) 674 | } 675 | } 676 | 677 | c0Lock() 678 | done := make(chan struct{}) 679 | go func() { 680 | _, err = conn1.Prep("INSERT INTO t (c) VALUES (3);").Step() 681 | if err != nil { 682 | t.Errorf("insert failed: %v", err) 683 | } 684 | close(done) 685 | }() 686 | 687 | time.Sleep(10 * time.Millisecond) 688 | select { 689 | case <-done: 690 | t.Errorf("done before unlock") 691 | default: 692 | } 693 | 694 | c0Unlock() 695 | <-done 696 | 697 | c0Lock() 698 | done = make(chan struct{}) 699 | go func() { 700 | conn1.SetBusyTimeout(5 * time.Millisecond) 701 | _, err = conn1.Prep("INSERT INTO t (c) VALUES (4);").Step() 702 | if sqlite.ErrCode(err) != sqlite.SQLITE_BUSY { 703 | t.Errorf("want SQLITE_BUSY got %v", err) 704 | } 705 | close(done) 706 | }() 707 | 708 | select { 709 | case <-done: 710 | case <-time.After(1 * time.Second): 711 | t.Errorf("short busy timeout got stuck") 712 | } 713 | 714 | c0Unlock() 715 | <-done 716 | } 717 | 718 | func TestBlockOnBusy(t *testing.T) { 719 | dir := t.TempDir() 720 | db := filepath.Join(dir, "busytest.db") 721 | const flags = sqlite.SQLITE_OPEN_READWRITE | sqlite.SQLITE_OPEN_CREATE | sqlite.SQLITE_OPEN_WAL 722 | 723 | conn0, err := sqlite.OpenConn(db, flags) 724 | if err != nil { 725 | t.Fatal(err) 726 | } 727 | defer func() { 728 | if err := conn0.Close(); err != nil { 729 | t.Error(err) 730 | } 731 | }() 732 | 733 | conn1, err := sqlite.OpenConn(db, flags) 734 | if err != nil { 735 | t.Fatal(err) 736 | } 737 | defer func() { 738 | if err := conn1.Close(); err != nil { 739 | t.Error(err) 740 | } 741 | }() 742 | 743 | if _, err := conn0.Prep("BEGIN EXCLUSIVE;").Step(); err != nil { 744 | t.Fatal(err) 745 | } 746 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 747 | conn1.SetInterrupt(ctx.Done()) 748 | _, err = conn1.Prep("BEGIN EXCLUSIVE;").Step() 749 | cancel() 750 | if code := sqlite.ErrCode(err); code != sqlite.SQLITE_BUSY { 751 | t.Errorf("Concurrent transaction error: %v (code=%v); want code=%v", err, code, sqlite.SQLITE_BUSY) 752 | } 753 | } 754 | 755 | func TestColumnIndex(t *testing.T) { 756 | c, err := sqlite.OpenConn(":memory:", 0) 757 | if err != nil { 758 | t.Fatal(err) 759 | } 760 | defer func() { 761 | if err := c.Close(); err != nil { 762 | t.Error(err) 763 | } 764 | }() 765 | 766 | stmt, err := c.Prepare("CREATE TABLE IF NOT EXISTS columnindex (a, b, c);") 767 | if err != nil { 768 | t.Fatal(err) 769 | } 770 | if _, err := stmt.Step(); err != nil { 771 | t.Fatal(err) 772 | } 773 | 774 | stmt, err = c.Prepare("SELECT b, 1 AS d, a, c FROM columnindex") 775 | if err != nil { 776 | t.Fatal(err) 777 | } 778 | 779 | cols := []struct { 780 | name string 781 | idx int 782 | }{ 783 | {"a", 2}, 784 | {"b", 0}, 785 | {"c", 3}, 786 | {"d", 1}, 787 | {"badcol", -1}, 788 | } 789 | 790 | for _, col := range cols { 791 | if got := stmt.ColumnIndex(col.name); got != col.idx { 792 | t.Errorf("expected column %s to have index %d, got %d", col.name, col.idx, got) 793 | } 794 | } 795 | 796 | stmt.Finalize() 797 | } 798 | 799 | func TestBindParamName(t *testing.T) { 800 | c, err := sqlite.OpenConn(":memory:", 0) 801 | if err != nil { 802 | t.Fatal(err) 803 | } 804 | defer func() { 805 | if err := c.Close(); err != nil { 806 | t.Error(err) 807 | } 808 | }() 809 | 810 | stmt, err := c.Prepare("SELECT :foo, :bar;") 811 | if err != nil { 812 | t.Fatal(err) 813 | } 814 | defer stmt.Finalize() 815 | 816 | var got []string 817 | for i, n := 1, stmt.BindParamCount(); i <= n; i++ { 818 | got = append(got, stmt.BindParamName(i)) 819 | } 820 | // We don't care what indices SQLite picked, so sort returned names. 821 | sort.Strings(got) 822 | want := []string{":bar", ":foo"} 823 | if len(got) != len(want) || got[0] != want[0] || got[1] != want[1] { 824 | t.Errorf("names = %q; want %q", got, want) 825 | } 826 | } 827 | 828 | func TestLimit(t *testing.T) { 829 | c, err := sqlite.OpenConn(":memory:", 0) 830 | if err != nil { 831 | t.Fatal(err) 832 | } 833 | defer func() { 834 | if err := c.Close(); err != nil { 835 | t.Error(err) 836 | } 837 | }() 838 | 839 | c.Limit(sqlite.SQLITE_LIMIT_SQL_LENGTH, 1) 840 | stmt, err := c.Prepare("SELECT 1;") 841 | if err == nil { 842 | stmt.Finalize() 843 | t.Fatal("Prepare did not return an error") 844 | } 845 | if got, want := sqlite.ErrCode(err), sqlite.SQLITE_TOOBIG; got != want { 846 | t.Errorf("sqlite.ErrCode(err) = %v; want %v", got, want) 847 | } 848 | } 849 | 850 | func TestReturningClause(t *testing.T) { 851 | // The RETURNING syntax has been supported by SQLite since version 3.35.0 (2021-03-12). 852 | // https://www.sqlite.org/lang_returning.html 853 | c, err := sqlite.OpenConn(":memory:", 0) 854 | if err != nil { 855 | t.Fatal(err) 856 | } 857 | defer func() { 858 | if err := c.Close(); err != nil { 859 | t.Error(err) 860 | } 861 | }() 862 | 863 | stmt := c.Prep(` 864 | CREATE TABLE fruits ( 865 | id INTEGER PRIMARY KEY AUTOINCREMENT, 866 | name TEXT NOT NULL 867 | ) 868 | `) 869 | if _, err = stmt.Step(); err != nil { 870 | t.Fatal(err) 871 | } 872 | _ = stmt.Finalize() 873 | 874 | stmt = c.Prep(` 875 | INSERT INTO fruits (name) 876 | VALUES (:fruit) 877 | RETURNING id 878 | `) 879 | stmt.SetText(":fruit", "bananananana") 880 | if ok, err := stmt.Step(); err != nil { 881 | t.Fatal(err) 882 | } else if !ok { 883 | t.Fatal("no fruit id returned") 884 | } 885 | if id := stmt.GetInt64("id"); id != 1 { 886 | t.Fatalf("want returned fruit id to be 1, got %d", id) 887 | } 888 | } 889 | 890 | func TestVirtualTableWithTokenizer(t *testing.T) { 891 | c, err := sqlite.OpenConn(":memory:", 0) 892 | if err != nil { 893 | t.Fatal(err) 894 | } 895 | defer func() { 896 | if err := c.Close(); err != nil { 897 | t.Error(err) 898 | } 899 | }() 900 | stmt := c.Prep(`CREATE VIRTUAL TABLE IF NOT EXISTS fts_table USING fts5(text, tokenize = 'porter trigram')`) 901 | if _, err := stmt.Step(); err != nil { 902 | t.Fatal(err) 903 | } 904 | } 905 | -------------------------------------------------------------------------------- /sqlitex/blob-readwriter.go: -------------------------------------------------------------------------------- 1 | package sqlitex 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | 7 | sqlite "github.com/go-llsqlite/crawshaw" 8 | ) 9 | 10 | // This wraps a sqlite.Blob with positional state provide extra io implementations. 11 | func NewBlobSeeker(blob *sqlite.Blob) *blobSeeker { 12 | return &blobSeeker{ 13 | size: blob.Size(), 14 | blob: blob, 15 | ReaderAt: blob, 16 | WriterAt: blob, 17 | } 18 | } 19 | 20 | var _ interface { 21 | io.ReadWriteSeeker 22 | } = (*blobSeeker)(nil) 23 | 24 | type blobSeeker struct { 25 | io.ReaderAt 26 | io.WriterAt 27 | blob *sqlite.Blob 28 | off int64 29 | size int64 30 | } 31 | 32 | func (blob *blobSeeker) Read(p []byte) (n int, err error) { 33 | if blob.off >= blob.size { 34 | return 0, io.EOF 35 | } 36 | if rem := blob.size - blob.off; int64(len(p)) > rem { 37 | p = p[:rem] 38 | } 39 | n, err = blob.ReadAt(p, blob.off) 40 | blob.off += int64(n) 41 | return n, err 42 | } 43 | 44 | func (blob *blobSeeker) Write(p []byte) (n int, err error) { 45 | if rem := blob.size - blob.off; int64(len(p)) > rem { 46 | return 0, io.ErrShortWrite 47 | } 48 | n, err = blob.WriteAt(p, blob.off) 49 | blob.off += int64(n) 50 | return n, err 51 | } 52 | 53 | func (blob *blobSeeker) Seek(offset int64, whence int) (int64, error) { 54 | switch whence { 55 | case io.SeekStart: 56 | // use offset directly 57 | case io.SeekCurrent: 58 | offset += blob.off 59 | case io.SeekEnd: 60 | offset += blob.size 61 | } 62 | if offset < 0 { 63 | return blob.off, errors.New("seek to offset < 0") 64 | } 65 | blob.off = offset 66 | return offset, nil 67 | } 68 | -------------------------------------------------------------------------------- /sqlitex/exec.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // Copyright (c) 2021 Ross Light 3 | // 4 | // Permission to use, copy, modify, and distribute this software for any 5 | // purpose with or without fee is hereby granted, provided that the above 6 | // copyright notice and this permission notice appear in all copies. 7 | // 8 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | // 16 | // SPDX-License-Identifier: ISC 17 | 18 | // Package sqlitex provides utilities for working with SQLite. 19 | package sqlitex 20 | 21 | import ( 22 | "fmt" 23 | "io" 24 | "reflect" 25 | "strings" 26 | 27 | "github.com/go-llsqlite/crawshaw" 28 | "io/fs" 29 | ) 30 | 31 | // ExecOptions is the set of optional arguments executing a statement. 32 | type ExecOptions struct { 33 | // Args is the set of positional arguments to bind to the statement. 34 | // The first element in the slice is ?1. 35 | // See https://sqlite.org/lang_expr.html for more details. 36 | // 37 | // Basic reflection on Args is used to map: 38 | // 39 | // integers to BindInt64 40 | // floats to BindFloat 41 | // []byte to BindBytes 42 | // string to BindText 43 | // bool to BindBool 44 | // 45 | // All other kinds are printed using fmt.Sprint(v) and passed to BindText. 46 | Args []interface{} 47 | 48 | // Named is the set of named arguments to bind to the statement. Keys must 49 | // start with ':', '@', or '$'. See https://sqlite.org/lang_expr.html for more 50 | // details. 51 | // 52 | // Basic reflection on Named is used to map: 53 | // 54 | // integers to BindInt64 55 | // floats to BindFloat 56 | // []byte to BindBytes 57 | // string to BindText 58 | // bool to BindBool 59 | // 60 | // All other kinds are printed using fmt.Sprint(v) and passed to BindText. 61 | Named map[string]interface{} 62 | 63 | // ResultFunc is called for each result row. 64 | // If ResultFunc returns an error then iteration ceases 65 | // and the execution function returns the error value. 66 | ResultFunc func(stmt *sqlite.Stmt) error 67 | 68 | // Allow unused parameters. SQLite normally treats these as null anyway, so this reverts to the 69 | // default behaviour. 70 | AllowUnused bool 71 | } 72 | 73 | // Exec executes an SQLite query. 74 | // 75 | // For each result row, the resultFn is called. 76 | // Result values can be read by resultFn using stmt.Column* methods. 77 | // If resultFn returns an error then iteration ceases and Exec returns 78 | // the error value. 79 | // 80 | // Any args provided to Exec are bound to numbered parameters of the 81 | // query using the Stmt Bind* methods. Basic reflection on args is used 82 | // to map: 83 | // 84 | // integers to BindInt64 85 | // floats to BindFloat 86 | // []byte to BindBytes 87 | // string to BindText 88 | // bool to BindBool 89 | // 90 | // All other kinds are printed using fmt.Sprintf("%v", v) and passed 91 | // to BindText. 92 | // 93 | // Exec is implemented using the Stmt prepare mechanism which allows 94 | // better interactions with Go's type system and avoids pitfalls of 95 | // passing a Go closure to cgo. 96 | // 97 | // As Exec is implemented using Conn.Prepare, subsequent calls to Exec 98 | // with the same statement will reuse the cached statement object. 99 | // 100 | // Deprecated: Use Execute. 101 | // Exec skips some argument checks for compatibility with crawshaw.io/sqlite. 102 | func Exec(conn *sqlite.Conn, query string, resultFn func(stmt *sqlite.Stmt) error, args ...interface{}) error { 103 | stmt, err := conn.Prepare(query) 104 | if err != nil { 105 | return annotateErr(err) 106 | } 107 | err = exec(stmt, 0, &ExecOptions{ 108 | Args: args, 109 | ResultFunc: resultFn, 110 | }) 111 | resetErr := stmt.Reset() 112 | if err == nil { 113 | err = resetErr 114 | } 115 | return err 116 | } 117 | 118 | // Execute executes an SQLite query. 119 | // 120 | // As Execute is implemented using Conn.Prepare, 121 | // subsequent calls to Execute with the same statement 122 | // will reuse the cached statement object. 123 | func Execute(conn *sqlite.Conn, query string, opts *ExecOptions) error { 124 | stmt, err := conn.Prepare(query) 125 | if err != nil { 126 | return annotateErr(err) 127 | } 128 | err = exec(stmt, forbidMissing|forbidExtra, opts) 129 | resetErr := stmt.Reset() 130 | if err == nil { 131 | err = resetErr 132 | } 133 | return err 134 | } 135 | 136 | // ExecFS is an alias for ExecuteFS. 137 | // 138 | // Deprecated: Call ExecuteFS directly. 139 | func ExecFS(conn *sqlite.Conn, fsys fs.FS, filename string, opts *ExecOptions) error { 140 | return ExecuteFS(conn, fsys, filename, opts) 141 | } 142 | 143 | // ExecuteFS executes the single statement in the given SQL file. 144 | // ExecuteFS is implemented using Conn.Prepare, 145 | // so subsequent calls to ExecuteFS with the same statement 146 | // will reuse the cached statement object. 147 | func ExecuteFS(conn *sqlite.Conn, fsys fs.FS, filename string, opts *ExecOptions) error { 148 | query, err := readString(fsys, filename) 149 | if err != nil { 150 | return fmt.Errorf("exec: %w", err) 151 | } 152 | 153 | stmt, err := conn.Prepare(strings.TrimSpace(query)) 154 | if err != nil { 155 | return fmt.Errorf("exec %s: %w", filename, err) 156 | } 157 | err = exec(stmt, forbidMissing|forbidExtra, opts) 158 | resetErr := stmt.Reset() 159 | if err != nil { 160 | // Don't strip the error query: we already do this inside exec. 161 | return fmt.Errorf("exec %s: %w", filename, err) 162 | } 163 | if resetErr != nil { 164 | return fmt.Errorf("exec %s: %w", filename, err) 165 | } 166 | return nil 167 | } 168 | 169 | // ExecTransient executes an SQLite query without caching the underlying query. 170 | // The interface is exactly the same as Exec. 171 | // It is the spiritual equivalent of sqlite3_exec. 172 | // 173 | // Deprecated: Use ExecuteTransient. 174 | // ExecTransient skips some argument checks for compatibility with crawshaw.io/sqlite. 175 | func ExecTransient(conn *sqlite.Conn, query string, resultFn func(stmt *sqlite.Stmt) error, args ...interface{}) (err error) { 176 | var stmt *sqlite.Stmt 177 | var trailingBytes int 178 | stmt, trailingBytes, err = conn.PrepareTransient(query) 179 | if err != nil { 180 | return annotateErr(err) 181 | } 182 | defer func() { 183 | ferr := stmt.Finalize() 184 | if err == nil { 185 | err = ferr 186 | } 187 | }() 188 | if trailingBytes != 0 { 189 | return fmt.Errorf("sqlitex.Exec: query %q has trailing bytes", query) 190 | } 191 | return exec(stmt, 0, &ExecOptions{ 192 | Args: args, 193 | ResultFunc: resultFn, 194 | }) 195 | } 196 | 197 | // ExecuteTransient executes an SQLite query without caching the underlying query. 198 | // It is the spiritual equivalent of sqlite3_exec: 199 | // https://www.sqlite.org/c3ref/exec.html 200 | func ExecuteTransient(conn *sqlite.Conn, query string, opts *ExecOptions) (err error) { 201 | var stmt *sqlite.Stmt 202 | var trailingBytes int 203 | stmt, trailingBytes, err = conn.PrepareTransient(query) 204 | if err != nil { 205 | return annotateErr(err) 206 | } 207 | defer func() { 208 | ferr := stmt.Finalize() 209 | if err == nil { 210 | err = ferr 211 | } 212 | }() 213 | if trailingBytes != 0 { 214 | return fmt.Errorf("sqlitex.Exec: query %q has trailing bytes", query) 215 | } 216 | return exec(stmt, forbidMissing|forbidExtra, opts) 217 | } 218 | 219 | // ExecTransientFS is an alias for ExecuteTransientFS. 220 | // 221 | // Deprecated: Call ExecuteTransientFS directly. 222 | func ExecTransientFS(conn *sqlite.Conn, fsys fs.FS, filename string, opts *ExecOptions) error { 223 | return ExecuteTransientFS(conn, fsys, filename, opts) 224 | } 225 | 226 | // ExecuteTransientFS executes the single statement in the given SQL file without 227 | // caching the underlying query. 228 | func ExecuteTransientFS(conn *sqlite.Conn, fsys fs.FS, filename string, opts *ExecOptions) error { 229 | query, err := readString(fsys, filename) 230 | if err != nil { 231 | return fmt.Errorf("exec: %w", err) 232 | } 233 | 234 | stmt, _, err := conn.PrepareTransient(strings.TrimSpace(query)) 235 | if err != nil { 236 | return fmt.Errorf("exec %s: %w", filename, err) 237 | } 238 | defer stmt.Finalize() 239 | err = exec(stmt, forbidMissing|forbidExtra, opts) 240 | resetErr := stmt.Reset() 241 | if err != nil { 242 | // Don't strip the error query: we already do this inside exec. 243 | return fmt.Errorf("exec %s: %w", filename, err) 244 | } 245 | if resetErr != nil { 246 | return fmt.Errorf("exec %s: %w", filename, err) 247 | } 248 | return nil 249 | } 250 | 251 | // PrepareTransientFS prepares an SQL statement from a file that is not cached by 252 | // the Conn. Subsequent calls with the same query will create new Stmts. 253 | // The caller is responsible for calling Finalize on the returned Stmt when the 254 | // Stmt is no longer needed. 255 | func PrepareTransientFS(conn *sqlite.Conn, fsys fs.FS, filename string) (*sqlite.Stmt, error) { 256 | query, err := readString(fsys, filename) 257 | if err != nil { 258 | return nil, fmt.Errorf("prepare: %w", err) 259 | } 260 | stmt, _, err := conn.PrepareTransient(strings.TrimSpace(query)) 261 | if err != nil { 262 | return nil, fmt.Errorf("prepare %s: %w", filename, err) 263 | } 264 | return stmt, nil 265 | } 266 | 267 | const ( 268 | forbidMissing = 1 << iota 269 | forbidExtra 270 | ) 271 | 272 | func exec(stmt *sqlite.Stmt, flags uint8, opts *ExecOptions) (err error) { 273 | paramCount := stmt.BindParamCount() 274 | provided := newBitset(paramCount) 275 | if opts != nil { 276 | if len(opts.Args) > paramCount { 277 | return fmt.Errorf("sqlitex.Exec: %w (len(Args) > BindParamCount(); %d > %d)", 278 | sqlite.ResultRange.ToError(), len(opts.Args), paramCount) 279 | } 280 | for i, arg := range opts.Args { 281 | provided.set(i) 282 | setArg(stmt, i+1, reflect.ValueOf(arg)) 283 | } 284 | if err := setNamed(stmt, provided, flags, opts.Named); err != nil { 285 | return err 286 | } 287 | } 288 | if flags&forbidMissing != 0 && !provided.hasAll(paramCount) { 289 | i := provided.firstMissing() + 1 290 | name := stmt.BindParamName(i) 291 | if name == "" { 292 | name = fmt.Sprintf("?%d", i) 293 | } 294 | return fmt.Errorf("sqlitex.Exec: missing argument for %s", name) 295 | } 296 | for { 297 | hasRow, err := stmt.Step() 298 | if err != nil { 299 | return err 300 | } 301 | if !hasRow { 302 | break 303 | } 304 | if opts != nil && opts.ResultFunc != nil { 305 | if err := opts.ResultFunc(stmt); err != nil { 306 | return err 307 | } 308 | } 309 | } 310 | return nil 311 | } 312 | 313 | func setArg(stmt *sqlite.Stmt, i int, v reflect.Value) { 314 | switch v.Kind() { 315 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 316 | stmt.BindInt64(i, v.Int()) 317 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 318 | stmt.BindInt64(i, int64(v.Uint())) 319 | case reflect.Float32, reflect.Float64: 320 | stmt.BindFloat(i, v.Float()) 321 | case reflect.String: 322 | stmt.BindText(i, v.String()) 323 | case reflect.Bool: 324 | stmt.BindBool(i, v.Bool()) 325 | case reflect.Invalid: 326 | stmt.BindNull(i) 327 | case reflect.Slice, reflect.Array: 328 | if v.Type().Elem().Kind() == reflect.Uint8 { 329 | // Array values are not addressable. Pass a pointer or slice it before passing it to 330 | // this API. 331 | stmt.BindBytes(i, v.Bytes()) 332 | return 333 | } 334 | fallthrough 335 | default: 336 | stmt.BindText(i, fmt.Sprint(v.Interface())) 337 | } 338 | } 339 | 340 | func setNamed(stmt *sqlite.Stmt, provided bitset, flags uint8, args map[string]interface{}) error { 341 | if len(args) == 0 { 342 | return nil 343 | } 344 | var unused map[string]struct{} 345 | if flags&forbidExtra != 0 { 346 | unused = make(map[string]struct{}, len(args)) 347 | for k := range args { 348 | unused[k] = struct{}{} 349 | } 350 | } 351 | for i, count := 1, stmt.BindParamCount(); i <= count; i++ { 352 | name := stmt.BindParamName(i) 353 | if name == "" { 354 | continue 355 | } 356 | arg, present := args[name] 357 | if !present { 358 | if flags&forbidMissing != 0 { 359 | // TODO(maybe): Check provided as well? 360 | return fmt.Errorf("missing parameter %s", name) 361 | } 362 | continue 363 | } 364 | delete(unused, name) 365 | provided.set(i - 1) 366 | setArg(stmt, i, reflect.ValueOf(arg)) 367 | } 368 | if len(unused) > 0 { 369 | return fmt.Errorf("%w: unknown argument %s", sqlite.ResultRange.ToError(), minStringInSet(unused)) 370 | } 371 | return nil 372 | } 373 | 374 | func annotateErr(err error) error { 375 | // TODO(maybe) 376 | // if err, isError := err.(sqlite.Error); isError { 377 | // if err.Loc == "" { 378 | // err.Loc = "Exec" 379 | // } else { 380 | // err.Loc = "Exec: " + err.Loc 381 | // } 382 | // return err 383 | // } 384 | return fmt.Errorf("sqlitex.Exec: %w", err) 385 | } 386 | 387 | // ExecScript executes a script of SQL statements. 388 | // It is the same as calling ExecuteScript without options. 389 | func ExecScript(conn *sqlite.Conn, queries string) (err error) { 390 | return ExecuteScript(conn, queries, nil) 391 | } 392 | 393 | // ExecuteScript executes a script of SQL statements. 394 | // The script is wrapped in a SAVEPOINT transaction, 395 | // which is rolled back on any error. 396 | // 397 | // opts.ResultFunc is ignored. 398 | func ExecuteScript(conn *sqlite.Conn, queries string, opts *ExecOptions) (err error) { 399 | defer Save(conn)(&err) 400 | 401 | unused := make(map[string]struct{}) 402 | if opts != nil { 403 | for k := range opts.Named { 404 | unused[k] = struct{}{} 405 | } 406 | } 407 | for { 408 | queries = strings.TrimSpace(queries) 409 | if queries == "" { 410 | break 411 | } 412 | var stmt *sqlite.Stmt 413 | var trailingBytes int 414 | stmt, trailingBytes, err = conn.PrepareTransient(queries) 415 | if err != nil { 416 | return err 417 | } 418 | for i, n := 1, stmt.BindParamCount(); i <= n; i++ { 419 | if name := stmt.BindParamName(i); name != "" { 420 | delete(unused, name) 421 | } 422 | } 423 | usedBytes := len(queries) - trailingBytes 424 | queries = queries[usedBytes:] 425 | err = exec(stmt, forbidMissing, opts) 426 | stmt.Finalize() 427 | if err != nil { 428 | return err 429 | } 430 | } 431 | if len(unused) > 0 && !opts.AllowUnused { 432 | return fmt.Errorf("%w: unknown argument %s", sqlite.ResultRange.ToError(), minStringInSet(unused)) 433 | } 434 | return nil 435 | } 436 | 437 | // ExecScriptFS is an alias for ExecuteScriptFS. 438 | // 439 | // Deprecated: Call ExecuteScriptFS directly. 440 | func ExecScriptFS(conn *sqlite.Conn, fsys fs.FS, filename string, opts *ExecOptions) (err error) { 441 | return ExecuteScriptFS(conn, fsys, filename, opts) 442 | } 443 | 444 | // ExecuteScriptFS executes a script of SQL statements from a file. 445 | // The script is wrapped in a SAVEPOINT transaction, 446 | // which is rolled back on any error. 447 | func ExecuteScriptFS(conn *sqlite.Conn, fsys fs.FS, filename string, opts *ExecOptions) (err error) { 448 | queries, err := readString(fsys, filename) 449 | if err != nil { 450 | return fmt.Errorf("exec: %w", err) 451 | } 452 | if err := ExecuteScript(conn, queries, opts); err != nil { 453 | return fmt.Errorf("exec %s: %w", filename, err) 454 | } 455 | return nil 456 | } 457 | 458 | type bitset []uint64 459 | 460 | func newBitset(n int) bitset { 461 | return make([]uint64, (n+63)/64) 462 | } 463 | 464 | // hasAll reports whether the bitset is a superset of [0, n). 465 | func (bs bitset) hasAll(n int) bool { 466 | nbytes := (n + 63) / 64 467 | if len(bs) < nbytes { 468 | return false 469 | } 470 | fullBytes := n / 64 471 | for _, b := range bs[:fullBytes] { 472 | if b != ^uint64(0) { 473 | return false 474 | } 475 | } 476 | if fullBytes == nbytes { 477 | return true 478 | } 479 | mask := uint64(1)<<(n%64) - 1 480 | return bs[nbytes-1]&mask == mask 481 | } 482 | 483 | func (bs bitset) firstMissing() int { 484 | for i, b := range bs { 485 | if b == ^uint64(0) { 486 | continue 487 | } 488 | for j := 0; j < 64; j++ { 489 | if b&(1<= 0; i-- { 504 | fmt.Fprintf(sb, "%08b", bs[i]) 505 | } 506 | return sb.String() 507 | } 508 | 509 | func minStringInSet(set map[string]struct{}) string { 510 | min := "" 511 | for k := range set { 512 | if min == "" || k < min { 513 | min = k 514 | } 515 | } 516 | return min 517 | } 518 | 519 | func readString(fsys fs.FS, filename string) (string, error) { 520 | f, err := fsys.Open(filename) 521 | if err != nil { 522 | return "", err 523 | } 524 | content := new(strings.Builder) 525 | _, err = io.Copy(content, f) 526 | f.Close() 527 | if err != nil { 528 | return "", fmt.Errorf("%s: %w", filename, err) 529 | } 530 | return content.String(), nil 531 | } 532 | -------------------------------------------------------------------------------- /sqlitex/exec_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // Copyright (c) 2021 Ross Light 3 | // 4 | // Permission to use, copy, modify, and distribute this software for any 5 | // purpose with or without fee is hereby granted, provided that the above 6 | // copyright notice and this permission notice appear in all copies. 7 | // 8 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | // 16 | // SPDX-License-Identifier: ISC 17 | 18 | package sqlitex 19 | 20 | import ( 21 | "fmt" 22 | "reflect" 23 | "testing" 24 | 25 | "github.com/go-llsqlite/crawshaw" 26 | ) 27 | 28 | func TestExec(t *testing.T) { 29 | conn, err := sqlite.OpenConn(":memory:", 0) 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | defer conn.Close() 34 | 35 | if err := ExecTransient(conn, "CREATE TABLE t (a TEXT, b INTEGER);", nil); err != nil { 36 | t.Fatal(err) 37 | } 38 | if err := Exec(conn, "INSERT INTO t (a, b) VALUES (?, ?);", nil, "a1", 1); err != nil { 39 | t.Error(err) 40 | } 41 | if err := Exec(conn, "INSERT INTO t (a, b) VALUES (?, ?);", nil, "a2", 2); err != nil { 42 | t.Error(err) 43 | } 44 | 45 | var a []string 46 | var b []int64 47 | fn := func(stmt *sqlite.Stmt) error { 48 | a = append(a, stmt.ColumnText(0)) 49 | b = append(b, stmt.ColumnInt64(1)) 50 | return nil 51 | } 52 | if err := ExecTransient(conn, "SELECT a, b FROM t;", fn); err != nil { 53 | t.Fatal(err) 54 | } 55 | if want := []string{"a1", "a2"}; !reflect.DeepEqual(a, want) { 56 | t.Errorf("a=%v, want %v", a, want) 57 | } 58 | if want := []int64{1, 2}; !reflect.DeepEqual(b, want) { 59 | t.Errorf("b=%v, want %v", b, want) 60 | } 61 | } 62 | 63 | func TestExecErr(t *testing.T) { 64 | conn, err := sqlite.OpenConn(":memory:", 0) 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | defer conn.Close() 69 | 70 | err = Exec(conn, "INVALID SQL STMT", nil) 71 | if err == nil { 72 | t.Error("invalid SQL did not return an error code") 73 | } 74 | if got, want := sqlite.ErrCode(err), sqlite.ResultError; got != want { 75 | t.Errorf("INVALID err code=%s, want %s", got, want) 76 | } 77 | 78 | if err := Exec(conn, "CREATE TABLE t (c1, c2);", nil); err != nil { 79 | t.Error(err) 80 | } 81 | if err := Exec(conn, "INSERT INTO t (c1, c2) VALUES (?, ?);", nil, 1, 1); err != nil { 82 | t.Error(err) 83 | } 84 | if err := Exec(conn, "INSERT INTO t (c1, c2) VALUES (?, ?);", nil, 2, 2); err != nil { 85 | t.Error(err) 86 | } 87 | err = Exec(conn, "INSERT INTO t (c1, c2) VALUES (?, ?);", nil, 1, 1, 1) 88 | if got, want := sqlite.ErrCode(err), sqlite.ResultRange; got != want { 89 | t.Errorf("INSERT err code=%s, want %s", got, want) 90 | } 91 | 92 | calls := 0 93 | customErr := fmt.Errorf("custom err") 94 | fn := func(stmt *sqlite.Stmt) error { 95 | calls++ 96 | return customErr 97 | } 98 | err = Exec(conn, "SELECT c1 FROM t;", fn) 99 | if err != customErr { 100 | t.Errorf("SELECT want err=customErr, got: %v", err) 101 | } 102 | if calls != 1 { 103 | t.Errorf("SELECT want truncated callback calls, got calls=%d", calls) 104 | } 105 | } 106 | 107 | func TestExecArgsErrors(t *testing.T) { 108 | conn, err := sqlite.OpenConn(":memory:", 0) 109 | if err != nil { 110 | t.Fatal(err) 111 | } 112 | defer conn.Close() 113 | 114 | t.Run("TooManyPositional", func(t *testing.T) { 115 | err := Exec(conn, `SELECT ?;`, nil, 1, 2) 116 | t.Log(err) 117 | if got, want := sqlite.ErrCode(err), sqlite.ResultRange; got != want { 118 | t.Errorf("code = %v; want %v", got, want) 119 | } 120 | }) 121 | 122 | t.Run("Missing", func(t *testing.T) { 123 | // Compatibility: crawshaw.io/sqlite does not check for this condition. 124 | err := Exec(conn, `SELECT ?;`, nil) 125 | t.Log(err) 126 | if got, want := sqlite.ErrCode(err), sqlite.ResultOK; got != want { 127 | t.Errorf("code = %v; want %v", got, want) 128 | } 129 | }) 130 | } 131 | 132 | func TestExecuteArgsErrors(t *testing.T) { 133 | conn, err := sqlite.OpenConn(":memory:", 0) 134 | if err != nil { 135 | t.Fatal(err) 136 | } 137 | defer conn.Close() 138 | 139 | t.Run("TooManyPositional", func(t *testing.T) { 140 | err := Execute(conn, `SELECT ?;`, &ExecOptions{ 141 | Args: []interface{}{1, 2}, 142 | }) 143 | t.Log(err) 144 | if got, want := sqlite.ErrCode(err), sqlite.ResultRange; got != want { 145 | t.Errorf("code = %v; want %v", got, want) 146 | } 147 | }) 148 | 149 | t.Run("ExtraNamed", func(t *testing.T) { 150 | err := Execute(conn, `SELECT :foo;`, &ExecOptions{ 151 | Named: map[string]interface{}{ 152 | ":foo": 42, 153 | ":bar": "hi", 154 | }, 155 | }) 156 | t.Log(err) 157 | if got, want := sqlite.ErrCode(err), sqlite.ResultRange; got != want { 158 | t.Errorf("code = %v; want %v", got, want) 159 | } 160 | }) 161 | 162 | t.Run("Missing", func(t *testing.T) { 163 | err := Execute(conn, `SELECT ?;`, &ExecOptions{ 164 | Args: []interface{}{}, 165 | }) 166 | t.Log(err) 167 | if got, want := sqlite.ErrCode(err), sqlite.ResultError; got != want { 168 | t.Errorf("code = %v; want %v", got, want) 169 | } 170 | }) 171 | 172 | t.Run("MissingNamed", func(t *testing.T) { 173 | err := Execute(conn, `SELECT :foo;`, nil) 174 | t.Log(err) 175 | if got, want := sqlite.ErrCode(err), sqlite.ResultError; got != want { 176 | t.Errorf("code = %v; want %v", got, want) 177 | } 178 | }) 179 | } 180 | 181 | func TestExecScript(t *testing.T) { 182 | conn, err := sqlite.OpenConn(":memory:", 0) 183 | if err != nil { 184 | t.Fatal(err) 185 | } 186 | defer conn.Close() 187 | 188 | script := ` 189 | CREATE TABLE t (a TEXT, b INTEGER); 190 | INSERT INTO t (a, b) VALUES ('a1', 1); 191 | INSERT INTO t (a, b) VALUES ('a2', 2); 192 | ` 193 | 194 | if err := ExecScript(conn, script); err != nil { 195 | t.Error(err) 196 | } 197 | 198 | sum := 0 199 | fn := func(stmt *sqlite.Stmt) error { 200 | sum = stmt.ColumnInt(0) 201 | return nil 202 | } 203 | if err := Exec(conn, "SELECT sum(b) FROM t;", fn); err != nil { 204 | t.Fatal(err) 205 | } 206 | 207 | if sum != 3 { 208 | t.Errorf("sum=%d, want 3", sum) 209 | } 210 | } 211 | 212 | func TestExecuteScript(t *testing.T) { 213 | conn, err := sqlite.OpenConn(":memory:", 0) 214 | if err != nil { 215 | t.Fatal(err) 216 | } 217 | defer conn.Close() 218 | 219 | script := ` 220 | CREATE TABLE t (a TEXT, b INTEGER); 221 | INSERT INTO t (a, b) VALUES ('a1', :a1); 222 | INSERT INTO t (a, b) VALUES ('a2', :a2); 223 | ` 224 | 225 | err = ExecuteScript(conn, script, &ExecOptions{ 226 | Named: map[string]interface{}{ 227 | ":a1": 1, 228 | ":a2": 2, 229 | }, 230 | }) 231 | if err != nil { 232 | t.Error(err) 233 | } 234 | 235 | sum := 0 236 | fn := func(stmt *sqlite.Stmt) error { 237 | sum = stmt.ColumnInt(0) 238 | return nil 239 | } 240 | if err := Exec(conn, "SELECT sum(b) FROM t;", fn); err != nil { 241 | t.Fatal(err) 242 | } 243 | 244 | if sum != 3 { 245 | t.Errorf("sum=%d, want 3", sum) 246 | } 247 | 248 | t.Run("ExtraNamed", func(t *testing.T) { 249 | conn, err := sqlite.OpenConn(":memory:", 0) 250 | if err != nil { 251 | t.Fatal(err) 252 | } 253 | defer conn.Close() 254 | 255 | script := ` 256 | CREATE TABLE t (a TEXT, b INTEGER); 257 | INSERT INTO t (a, b) VALUES ('a1', :a1); 258 | INSERT INTO t (a, b) VALUES ('a2', :a2); 259 | ` 260 | 261 | err = ExecuteScript(conn, script, &ExecOptions{ 262 | Named: map[string]interface{}{ 263 | ":a1": 1, 264 | ":a2": 2, 265 | ":a3": 3, 266 | }, 267 | }) 268 | t.Log(err) 269 | if got, want := sqlite.ErrCode(err), sqlite.ResultRange; got != want { 270 | t.Errorf("code = %v; want %v", got, want) 271 | } 272 | }) 273 | 274 | t.Run("MissingNamed", func(t *testing.T) { 275 | conn, err := sqlite.OpenConn(":memory:", 0) 276 | if err != nil { 277 | t.Fatal(err) 278 | } 279 | defer conn.Close() 280 | 281 | script := ` 282 | CREATE TABLE t (a TEXT, b INTEGER); 283 | INSERT INTO t (a, b) VALUES ('a1', :a1); 284 | INSERT INTO t (a, b) VALUES ('a2', :a2); 285 | ` 286 | 287 | err = ExecuteScript(conn, script, &ExecOptions{ 288 | Named: map[string]interface{}{ 289 | ":a1": 1, 290 | }, 291 | }) 292 | t.Log(err) 293 | if got, want := sqlite.ErrCode(err), sqlite.ResultError; got != want { 294 | t.Errorf("code = %v; want %v", got, want) 295 | } 296 | }) 297 | } 298 | 299 | func TestBitsetHasAll(t *testing.T) { 300 | tests := []struct { 301 | bs bitset 302 | n int 303 | want bool 304 | }{ 305 | { 306 | bs: bitset{}, 307 | n: 0, 308 | want: true, 309 | }, 310 | { 311 | bs: bitset{0}, 312 | n: 1, 313 | want: false, 314 | }, 315 | { 316 | bs: bitset{0x0000000000000001}, 317 | n: 1, 318 | want: true, 319 | }, 320 | { 321 | bs: bitset{0x8000000000000001}, 322 | n: 1, 323 | want: true, 324 | }, 325 | { 326 | bs: bitset{0x0000000000000001}, 327 | n: 2, 328 | want: false, 329 | }, 330 | { 331 | bs: bitset{0xffffffffffffffff}, 332 | n: 64, 333 | want: true, 334 | }, 335 | { 336 | bs: bitset{0xffffffffffffffff}, 337 | n: 65, 338 | want: false, 339 | }, 340 | { 341 | bs: bitset{0xffffffffffffffff, 0x0000000000000000}, 342 | n: 65, 343 | want: false, 344 | }, 345 | { 346 | bs: bitset{0xffffffffffffffff, 0x0000000000000001}, 347 | n: 65, 348 | want: true, 349 | }, 350 | { 351 | bs: bitset{0x7fffffffffffffff, 0x0000000000000001}, 352 | n: 65, 353 | want: false, 354 | }, 355 | } 356 | for _, test := range tests { 357 | if got := test.bs.hasAll(test.n); got != test.want { 358 | t.Errorf("%v.hasAll(%d) = %t; want %t", test.bs, test.n, got, test.want) 359 | } 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /sqlitex/pool.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlitex 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "runtime/trace" 21 | "sync" 22 | "time" 23 | 24 | "github.com/go-llsqlite/crawshaw" 25 | ) 26 | 27 | // Pool is a pool of SQLite connections. 28 | // 29 | // It is safe for use by multiple goroutines concurrently. 30 | // 31 | // Typically, a goroutine that needs to use an SQLite *Conn 32 | // Gets it from the pool and defers its return: 33 | // 34 | // conn := dbpool.Get(nil) 35 | // defer dbpool.Put(conn) 36 | // 37 | // As Get may block, a context can be used to return if a task 38 | // is cancelled. In this case the Conn returned will be nil: 39 | // 40 | // conn := dbpool.Get(ctx) 41 | // if conn == nil { 42 | // return context.Canceled 43 | // } 44 | // defer dbpool.Put(conn) 45 | type Pool struct { 46 | // If checkReset, the Put method checks all of the connection's 47 | // prepared statements and ensures they were correctly cleaned up. 48 | // If they were not, Put will panic with details. 49 | // 50 | // TODO: export this? Is it enough of a performance concern? 51 | checkReset bool 52 | 53 | free chan *sqlite.Conn 54 | closed chan struct{} 55 | 56 | all map[*sqlite.Conn]context.CancelFunc 57 | 58 | mu sync.RWMutex 59 | } 60 | 61 | // Open opens a fixed-size pool of SQLite connections. 62 | // 63 | // A flags value of 0 defaults to: 64 | // 65 | // SQLITE_OPEN_READWRITE 66 | // SQLITE_OPEN_CREATE 67 | // SQLITE_OPEN_WAL 68 | // SQLITE_OPEN_URI 69 | // SQLITE_OPEN_NOMUTEX 70 | func Open(uri string, flags sqlite.OpenFlags, poolSize int) (pool *Pool, err error) { 71 | return OpenInit(nil, uri, flags, poolSize, "") 72 | } 73 | 74 | // OpenInit opens a fixed-size pool of SQLite connections, each initialized 75 | // with initScript. 76 | // 77 | // A flags value of 0 defaults to: 78 | // 79 | // SQLITE_OPEN_READWRITE 80 | // SQLITE_OPEN_CREATE 81 | // SQLITE_OPEN_WAL 82 | // SQLITE_OPEN_URI 83 | // SQLITE_OPEN_NOMUTEX 84 | // 85 | // Each initScript is run an all Conns in the Pool. This is intended for PRAGMA 86 | // or CREATE TEMP VIEW which need to be run on all connections. 87 | // 88 | // WARNING: Ensure all queries in initScript are completely idempotent, meaning 89 | // that running it multiple times is the same as running it once. For example 90 | // do not run INSERT in any of the initScripts or else it may create duplicate 91 | // data unintentionally or fail. 92 | func OpenInit(ctx context.Context, uri string, flags sqlite.OpenFlags, poolSize int, initScript string) (pool *Pool, err error) { 93 | if uri == ":memory:" { 94 | return nil, strerror{msg: `sqlite: ":memory:" does not work with multiple connections, use "file::memory:?mode=memory"`} 95 | } 96 | 97 | p := &Pool{ 98 | checkReset: true, 99 | free: make(chan *sqlite.Conn, poolSize), 100 | closed: make(chan struct{}), 101 | } 102 | defer func() { 103 | // If an error occurred, call Close outside the lock so this doesn't deadlock. 104 | if err != nil { 105 | p.Close() 106 | } 107 | }() 108 | 109 | if flags == 0 { 110 | flags = sqlite.SQLITE_OPEN_READWRITE | 111 | sqlite.SQLITE_OPEN_CREATE | 112 | sqlite.SQLITE_OPEN_WAL | 113 | sqlite.SQLITE_OPEN_URI | 114 | sqlite.SQLITE_OPEN_NOMUTEX 115 | } 116 | 117 | // sqlitex_pool is also defined in package sqlite 118 | const sqlitex_pool = sqlite.OpenFlags(0x01000000) 119 | flags |= sqlitex_pool 120 | 121 | p.all = make(map[*sqlite.Conn]context.CancelFunc) 122 | for i := 0; i < poolSize; i++ { 123 | conn, err := sqlite.OpenConn(uri, flags) 124 | if err != nil { 125 | return nil, err 126 | } 127 | p.free <- conn 128 | p.all[conn] = func() {} 129 | 130 | if initScript != "" { 131 | conn.SetInterrupt(ctx.Done()) 132 | if err := ExecScript(conn, initScript); err != nil { 133 | return nil, err 134 | } 135 | conn.SetInterrupt(nil) 136 | } 137 | } 138 | 139 | return p, nil 140 | } 141 | 142 | // Get returns an SQLite connection from the Pool. 143 | // 144 | // If no Conn is available, Get will block until one is, or until either the 145 | // Pool is closed or the context expires. If no Conn can be obtained, nil is 146 | // returned. 147 | // 148 | // The provided context is used to control the execution lifetime of the 149 | // connection. See Conn.SetInterrupt for details. 150 | // 151 | // Applications must ensure that all non-nil Conns returned from Get are 152 | // returned to the same Pool with Put. 153 | func (p *Pool) Get(ctx context.Context) *sqlite.Conn { 154 | var tr sqlite.Tracer 155 | if ctx != nil { 156 | tr = &tracer{ctx: ctx} 157 | } else { 158 | ctx = context.Background() 159 | } 160 | var cancel context.CancelFunc 161 | ctx, cancel = context.WithCancel(ctx) 162 | 163 | outer: 164 | select { 165 | case conn := <-p.free: 166 | p.mu.Lock() 167 | defer p.mu.Unlock() 168 | 169 | select { 170 | case <-p.closed: 171 | p.free <- conn 172 | break outer 173 | default: 174 | } 175 | 176 | conn.SetTracer(tr) 177 | conn.SetInterrupt(ctx.Done()) 178 | 179 | p.all[conn] = cancel 180 | 181 | return conn 182 | case <-ctx.Done(): 183 | case <-p.closed: 184 | } 185 | cancel() 186 | return nil 187 | } 188 | 189 | // Put puts an SQLite connection back into the Pool. 190 | // 191 | // Put will panic if conn is nil or if the conn was not originally created by 192 | // p. 193 | // 194 | // Applications must ensure that all non-nil Conns returned from Get are 195 | // returned to the same Pool with Put. 196 | func (p *Pool) Put(conn *sqlite.Conn) { 197 | if conn == nil { 198 | panic("attempted to Put a nil Conn into Pool") 199 | } 200 | if p.checkReset { 201 | query := conn.CheckReset() 202 | if query != "" { 203 | panic(fmt.Sprintf( 204 | "connection returned to pool has active statement: %q", 205 | query)) 206 | } 207 | } 208 | 209 | p.mu.RLock() 210 | cancel, found := p.all[conn] 211 | p.mu.RUnlock() 212 | 213 | if !found { 214 | panic("sqlite.Pool.Put: connection not created by this pool") 215 | } 216 | 217 | cancel() 218 | p.free <- conn 219 | } 220 | 221 | // PoolCloseTimeout is the maximum time for Pool.Close to wait for all Conns to 222 | // be returned to the Pool. 223 | // 224 | // Do not modify this concurrently with calling Pool.Close. 225 | var PoolCloseTimeout = 5 * time.Second 226 | 227 | // Close interrupts and closes all the connections in the Pool. 228 | // 229 | // Close blocks until all connections are returned to the Pool. 230 | // 231 | // Close will panic if not all connections are returned before 232 | // PoolCloseTimeout. 233 | func (p *Pool) Close() (err error) { 234 | close(p.closed) 235 | 236 | p.mu.RLock() 237 | for _, cancel := range p.all { 238 | cancel() 239 | } 240 | p.mu.RUnlock() 241 | 242 | timeout := time.After(PoolCloseTimeout) 243 | for closed := 0; closed < len(p.all); closed++ { 244 | select { 245 | case conn := <-p.free: 246 | err2 := conn.Close() 247 | if err == nil { 248 | err = err2 249 | } 250 | case <-timeout: 251 | panic("not all connections returned to Pool before timeout") 252 | } 253 | } 254 | return 255 | } 256 | 257 | type strerror struct { 258 | msg string 259 | } 260 | 261 | func (err strerror) Error() string { return err.msg } 262 | 263 | type tracer struct { 264 | ctx context.Context 265 | ctxStack []context.Context 266 | taskStack []*trace.Task 267 | } 268 | 269 | func (t *tracer) pctx() context.Context { 270 | if len(t.ctxStack) != 0 { 271 | return t.ctxStack[len(t.ctxStack)-1] 272 | } 273 | return t.ctx 274 | } 275 | 276 | func (t *tracer) Push(name string) { 277 | ctx, task := trace.NewTask(t.pctx(), name) 278 | t.ctxStack = append(t.ctxStack, ctx) 279 | t.taskStack = append(t.taskStack, task) 280 | } 281 | 282 | func (t *tracer) Pop() { 283 | t.taskStack[len(t.taskStack)-1].End() 284 | t.taskStack = t.taskStack[:len(t.taskStack)-1] 285 | t.ctxStack = t.ctxStack[:len(t.ctxStack)-1] 286 | } 287 | 288 | func (t *tracer) NewTask(name string) sqlite.TracerTask { 289 | ctx, task := trace.NewTask(t.pctx(), name) 290 | return &tracerTask{ 291 | ctx: ctx, 292 | task: task, 293 | } 294 | } 295 | 296 | type tracerTask struct { 297 | ctx context.Context 298 | task *trace.Task 299 | region *trace.Region 300 | } 301 | 302 | func (t *tracerTask) StartRegion(regionType string) { 303 | if t.region != nil { 304 | panic("sqlitex.tracerTask.StartRegion: already in region") 305 | } 306 | t.region = trace.StartRegion(t.ctx, regionType) 307 | } 308 | 309 | func (t *tracerTask) EndRegion() { 310 | t.region.End() 311 | t.region = nil 312 | } 313 | 314 | func (t *tracerTask) End() { 315 | t.task.End() 316 | } 317 | -------------------------------------------------------------------------------- /sqlitex/pool_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlitex_test 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "io/ioutil" 21 | "os" 22 | "path/filepath" 23 | "sync" 24 | "testing" 25 | "time" 26 | 27 | "github.com/go-llsqlite/crawshaw" 28 | "github.com/go-llsqlite/crawshaw/sqlitex" 29 | ) 30 | 31 | const ( 32 | poolSize = 20 33 | poolURI = "file::memory:?mode=memory&cache=shared" 34 | poolFlags = sqlite.SQLITE_OPEN_READWRITE | sqlite.SQLITE_OPEN_CREATE | sqlite.SQLITE_OPEN_URI | sqlite.SQLITE_OPEN_NOMUTEX | sqlite.SQLITE_OPEN_SHAREDCACHE 35 | ) 36 | 37 | // newMemPool returns new sqlitex.Pool attached to new database opened in memory. 38 | // 39 | // the pool is initialized with size=poolSize. 40 | // any error is t.Fatal. 41 | func newMemPool(t *testing.T) *sqlitex.Pool { 42 | t.Helper() 43 | dbpool, err := sqlitex.Open(poolURI, poolFlags, poolSize) 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | return dbpool 48 | } 49 | 50 | func TestPool(t *testing.T) { 51 | dbpool := newMemPool(t) 52 | defer func() { 53 | if err := dbpool.Close(); err != nil { 54 | t.Error(err) 55 | } 56 | }() 57 | 58 | c := dbpool.Get(nil) 59 | c.Prep("DROP TABLE IF EXISTS footable;").Step() 60 | if hasRow, err := c.Prep("CREATE TABLE footable (col1 integer);").Step(); err != nil { 61 | t.Fatal(err) 62 | } else if hasRow { 63 | t.Errorf("CREATE TABLE reports having a row") 64 | } 65 | dbpool.Put(c) 66 | c = nil 67 | 68 | var wg sync.WaitGroup 69 | for i := 0; i < poolSize; i++ { 70 | wg.Add(1) 71 | go func(i int) { 72 | for j := 0; j < 10; j++ { 73 | testInsert(t, fmt.Sprintf("%d-%d", i, j), dbpool) 74 | } 75 | wg.Done() 76 | }(i) 77 | } 78 | wg.Wait() 79 | 80 | c = dbpool.Get(nil) 81 | defer dbpool.Put(c) 82 | stmt := c.Prep("SELECT COUNT(*) FROM footable;") 83 | if hasRow, err := stmt.Step(); err != nil { 84 | t.Fatal(err) 85 | } else if hasRow { 86 | count := int(stmt.ColumnInt64(0)) 87 | if want := poolSize * 10 * insertCount; count != want { 88 | t.Errorf("SELECT COUNT(*) = %d, want %d", count, want) 89 | } 90 | } else { 91 | t.Errorf("SELECT COUNT(*) reports not having a row") 92 | } 93 | stmt.Reset() 94 | } 95 | 96 | const insertCount = 120 97 | 98 | func testInsert(t *testing.T, id string, dbpool *sqlitex.Pool) { 99 | c := dbpool.Get(nil) 100 | defer dbpool.Put(c) 101 | 102 | begin := c.Prep("BEGIN;") 103 | commit := c.Prep("COMMIT;") 104 | stmt := c.Prep("INSERT INTO footable (col1) VALUES (?);") 105 | 106 | if _, err := begin.Step(); err != nil { 107 | t.Errorf("id=%s: BEGIN step: %v", id, err) 108 | } 109 | for i := int64(0); i < insertCount; i++ { 110 | if err := stmt.Reset(); err != nil { 111 | t.Errorf("id=%s: reset: %v", id, err) 112 | } 113 | stmt.BindInt64(1, i) 114 | if _, err := stmt.Step(); err != nil { 115 | t.Errorf("id=%s: step: %v", id, err) 116 | } 117 | } 118 | if _, err := commit.Step(); err != nil { 119 | t.Errorf("id=%s: COMMIT step: %v", id, err) 120 | } 121 | } 122 | 123 | func TestPoolAfterClose(t *testing.T) { 124 | // verify that Get after close never try to initialize a Conn and segfault 125 | dbpool := newMemPool(t) 126 | 127 | err := dbpool.Close() 128 | if err != nil { 129 | t.Fatal(err) 130 | } 131 | 132 | for i := 0; i < 10*poolSize; i++ { 133 | conn := dbpool.Get(nil) 134 | if conn != nil { 135 | t.Fatal("dbpool: Get after Close -> !nil conn") 136 | } 137 | } 138 | } 139 | 140 | func TestSharedCacheLock(t *testing.T) { 141 | dir, err := ioutil.TempDir("", "sqlite-test-") 142 | if err != nil { 143 | t.Fatal(err) 144 | } 145 | defer os.RemoveAll(dir) 146 | 147 | dbFile := filepath.Join(dir, "awal.db") 148 | 149 | c0, err := sqlite.OpenConn(dbFile, 0) 150 | if err != nil { 151 | t.Fatal(err) 152 | } 153 | defer func() { 154 | if err := c0.Close(); err != nil { 155 | t.Error(err) 156 | } 157 | }() 158 | 159 | err = sqlitex.ExecScript(c0, ` 160 | DROP TABLE IF EXISTS t; 161 | CREATE TABLE t (c, content BLOB); 162 | DROP TABLE IF EXISTS t2; 163 | CREATE TABLE t2 (c); 164 | INSERT INTO t2 (c) VALUES ('hello'); 165 | `) 166 | if err != nil { 167 | t.Fatal(err) 168 | } 169 | 170 | c1, err := sqlite.OpenConn(dbFile, 0) 171 | if err != nil { 172 | t.Fatal(err) 173 | } 174 | defer func() { 175 | if err := c1.Close(); err != nil { 176 | t.Error(err) 177 | } 178 | }() 179 | c1.SetBusyTimeout(10 * time.Second) 180 | 181 | c0Lock := func() { 182 | if _, err := c0.Prep("BEGIN;").Step(); err != nil { 183 | t.Fatal(err) 184 | } 185 | if _, err := c0.Prep("INSERT INTO t (c, content) VALUES (0, 'hi');").Step(); err != nil { 186 | t.Fatal(err) 187 | } 188 | } 189 | c0Unlock := func() { 190 | if err := sqlitex.Exec(c0, "COMMIT;", nil); err != nil { 191 | t.Fatal(err) 192 | } 193 | } 194 | 195 | c0Lock() 196 | 197 | stmt := c1.Prep("INSERT INTO t (c) VALUES (1);") 198 | 199 | done := make(chan struct{}) 200 | go func() { 201 | if _, err := stmt.Step(); err != nil { 202 | t.Fatal(err) 203 | } 204 | close(done) 205 | }() 206 | 207 | time.Sleep(10 * time.Millisecond) 208 | select { 209 | case <-done: 210 | t.Error("insert done while transaction was held") 211 | default: 212 | } 213 | 214 | c0Unlock() 215 | 216 | // End the initial transaction, allowing the goroutine to complete 217 | select { 218 | case <-done: 219 | case <-time.After(500 * time.Millisecond): 220 | t.Error("second connection insert not completing") 221 | } 222 | 223 | // TODO: It is possible for stmt.Reset to return SQLITE_LOCKED. 224 | // Work out why and find a way to test it. 225 | } 226 | 227 | func TestPoolPutMatch(t *testing.T) { 228 | dbpool0 := newMemPool(t) 229 | dbpool1 := newMemPool(t) 230 | defer func() { 231 | if err := dbpool0.Close(); err != nil { 232 | t.Error(err) 233 | } 234 | if err := dbpool1.Close(); err != nil { 235 | t.Error(err) 236 | } 237 | }() 238 | 239 | func() { 240 | c := dbpool0.Get(nil) 241 | defer func() { 242 | if r := recover(); r == nil { 243 | t.Error("expect put mismatch panic, got none") 244 | } 245 | dbpool0.Put(c) 246 | }() 247 | 248 | dbpool1.Put(c) 249 | }() 250 | } 251 | 252 | func TestPoolOpenInit(t *testing.T) { 253 | t.Run("ok", func(t *testing.T) { 254 | ctx := context.Background() 255 | initScript := ` 256 | CREATE TABLE IF NOT EXISTS t(a INT, b INT); 257 | CREATE TEMP VIEW v AS SELECT a FROM t; 258 | ` 259 | dbpool, err := sqlitex.OpenInit(ctx, poolURI, poolFlags, poolSize, initScript) 260 | if err != nil { 261 | t.Fatal(err) 262 | } 263 | defer func() { 264 | if err := dbpool.Close(); err != nil { 265 | t.Error(err) 266 | } 267 | }() 268 | 269 | for i := 0; i < poolSize; i++ { 270 | conn := dbpool.Get(nil) 271 | defer dbpool.Put(conn) 272 | if err := sqlitex.ExecScript(conn, `SELECT * FROM v;`); err != nil { 273 | t.Fatalf("initScript not run on connection: %s", err) 274 | } 275 | } 276 | }) 277 | t.Run("invalid initScript", func(t *testing.T) { 278 | ctx := context.Background() 279 | initScript := `invalid script` 280 | dbpool, err := sqlitex.OpenInit(ctx, poolURI, poolFlags, poolSize, initScript) 281 | if err != nil { 282 | return 283 | } 284 | if err := dbpool.Close(); err != nil { 285 | t.Error(err) 286 | } 287 | t.Fatal("an invalid script must fail initialization") 288 | }) 289 | t.Run("interrupted", func(t *testing.T) { 290 | ctx, cancel := context.WithCancel(context.Background()) 291 | cancel() 292 | initScript := ` 293 | CREATE TABLE IF NOT EXISTS t(a INT, b INT); 294 | CREATE TEMP VIEW v AS SELECT a FROM t; 295 | ` 296 | dbpool, err := sqlitex.OpenInit(ctx, poolURI, poolFlags, poolSize, initScript) 297 | if err != nil { 298 | return 299 | } 300 | if err := dbpool.Close(); err != nil { 301 | t.Error(err) 302 | } 303 | t.Fatal("a cancelled context should interrupt initialization") 304 | }) 305 | } 306 | -------------------------------------------------------------------------------- /sqlitex/query.go: -------------------------------------------------------------------------------- 1 | package sqlitex 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/go-llsqlite/crawshaw" 7 | ) 8 | 9 | var ErrNoResults = errors.New("sqlite: statement has no results") 10 | var ErrMultipleResults = errors.New("sqlite: statement has multiple result rows") 11 | 12 | func resultSetup(stmt *sqlite.Stmt) error { 13 | hasRow, err := stmt.Step() 14 | if err != nil { 15 | stmt.Reset() 16 | return err 17 | } 18 | if !hasRow { 19 | stmt.Reset() 20 | return ErrNoResults 21 | } 22 | return nil 23 | } 24 | 25 | func resultTeardown(stmt *sqlite.Stmt) error { 26 | hasRow, err := stmt.Step() 27 | if err != nil { 28 | stmt.Reset() 29 | return err 30 | } 31 | if hasRow { 32 | stmt.Reset() 33 | return ErrMultipleResults 34 | } 35 | return stmt.Reset() 36 | } 37 | 38 | // ResultInt steps the Stmt once and returns the first column as an int. 39 | // 40 | // If there are no rows in the result set, ErrNoResults is returned. 41 | // 42 | // If there are multiple rows, ErrMultipleResults is returned with the first 43 | // result. 44 | // 45 | // The Stmt is always Reset, so repeated calls will always return the first 46 | // result. 47 | func ResultInt(stmt *sqlite.Stmt) (int, error) { 48 | res, err := ResultInt64(stmt) 49 | return int(res), err 50 | } 51 | 52 | // ResultInt64 steps the Stmt once and returns the first column as an int64. 53 | // 54 | // If there are no rows in the result set, ErrNoResults is returned. 55 | // 56 | // If there are multiple rows, ErrMultipleResults is returned with the first 57 | // result. 58 | // 59 | // The Stmt is always Reset, so repeated calls will always return the first 60 | // result. 61 | func ResultInt64(stmt *sqlite.Stmt) (int64, error) { 62 | if err := resultSetup(stmt); err != nil { 63 | return 0, err 64 | } 65 | return stmt.ColumnInt64(0), resultTeardown(stmt) 66 | } 67 | 68 | // ResultText steps the Stmt once and returns the first column as a string. 69 | // 70 | // If there are no rows in the result set, ErrNoResults is returned. 71 | // 72 | // If there are multiple rows, ErrMultipleResults is returned with the first 73 | // result. 74 | // 75 | // The Stmt is always Reset, so repeated calls will always return the first 76 | // result. 77 | func ResultText(stmt *sqlite.Stmt) (string, error) { 78 | if err := resultSetup(stmt); err != nil { 79 | return "", err 80 | } 81 | return stmt.ColumnText(0), resultTeardown(stmt) 82 | } 83 | 84 | // ResultFloat steps the Stmt once and returns the first column as a float64. 85 | // 86 | // If there are no rows in the result set, ErrNoResults is returned. 87 | // 88 | // If there are multiple rows, ErrMultipleResults is returned with the first 89 | // result. 90 | // 91 | // The Stmt is always Reset, so repeated calls will always return the first 92 | // result. 93 | func ResultFloat(stmt *sqlite.Stmt) (float64, error) { 94 | if err := resultSetup(stmt); err != nil { 95 | return 0, err 96 | } 97 | return stmt.ColumnFloat(0), resultTeardown(stmt) 98 | } 99 | -------------------------------------------------------------------------------- /sqlitex/query_test.go: -------------------------------------------------------------------------------- 1 | package sqlitex 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/go-llsqlite/crawshaw" 7 | ) 8 | 9 | func TestResult(t *testing.T) { 10 | conn, err := sqlite.OpenConn(":memory:", 0) 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | defer conn.Close() 15 | 16 | if err := Exec(conn, "CREATE TABLE t (c1);", nil); err != nil { 17 | t.Fatal(err) 18 | } 19 | 20 | insert := func(t *testing.T, value interface{}) { 21 | t.Helper() 22 | err := Exec(conn, "INSERT INTO t (c1) VALUES (?);", nil, value) 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | } 27 | 28 | t.Helper() 29 | sel, err := conn.Prepare("SELECT c1 FROM t;") 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | 34 | cleanup := func(tt *testing.T) { 35 | tt.Helper() 36 | if sel.DataCount() != 0 { 37 | tt.Fatal("Stmt was not reset") 38 | } 39 | 40 | // The following should immediately fail the parent test 41 | // because we cannot continue without this essential cleanup. 42 | if err := sel.Reset(); err != nil { 43 | t.Fatal(err) 44 | } 45 | if err := Exec(conn, "DELETE FROM t;", nil); err != nil { 46 | t.Fatal(err) 47 | } 48 | } 49 | 50 | t.Run("Int64/ok", func(t *testing.T) { 51 | defer cleanup(t) 52 | 53 | input := int64(1234) 54 | insert(t, input) 55 | 56 | result, err := ResultInt64(sel) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | if result != input { 61 | t.Fatal("result != input") 62 | } 63 | }) 64 | 65 | t.Run("Int64/ErrMultipleResults", func(t *testing.T) { 66 | defer cleanup(t) 67 | 68 | input := int64(1234) 69 | insert(t, input) 70 | insert(t, input) 71 | 72 | result, err := ResultInt64(sel) 73 | if err != ErrMultipleResults { 74 | t.Fatal("err != ErrMultipleResults", err) 75 | } 76 | if result != input { 77 | t.Fatal("result != input") 78 | } 79 | }) 80 | 81 | t.Run("Int64/ErrNoResult", func(t *testing.T) { 82 | defer cleanup(t) 83 | 84 | _, err := ResultInt64(sel) 85 | if err != ErrNoResults { 86 | t.Fatal("err != ErrNoResults", err) 87 | } 88 | }) 89 | 90 | t.Run("Float/ok", func(t *testing.T) { 91 | defer cleanup(t) 92 | 93 | input := float64(1234) 94 | insert(t, input) 95 | 96 | result, err := ResultFloat(sel) 97 | if err != nil { 98 | t.Fatal(err) 99 | } 100 | if result != input { 101 | t.Fatal("result != input") 102 | } 103 | }) 104 | 105 | t.Run("Float/ErrMultipleResults", func(t *testing.T) { 106 | defer cleanup(t) 107 | 108 | input := float64(1234) 109 | insert(t, input) 110 | insert(t, input) 111 | 112 | result, err := ResultFloat(sel) 113 | if err != ErrMultipleResults { 114 | t.Fatal("err != ErrMultipleResults", err) 115 | } 116 | if result != input { 117 | t.Fatal() 118 | } 119 | }) 120 | 121 | t.Run("Float/ErrNoResult", func(t *testing.T) { 122 | defer cleanup(t) 123 | 124 | _, err := ResultFloat(sel) 125 | if err != ErrNoResults { 126 | t.Fatal("err != ErrNoResults", err) 127 | } 128 | }) 129 | 130 | t.Run("Text/ok", func(t *testing.T) { 131 | defer cleanup(t) 132 | 133 | input := "test" 134 | insert(t, input) 135 | 136 | result, err := ResultText(sel) 137 | if err != nil { 138 | t.Fatal(err) 139 | } 140 | if result != input { 141 | t.Fatal() 142 | } 143 | }) 144 | 145 | t.Run("Text/ErrMultipleResults", func(t *testing.T) { 146 | defer cleanup(t) 147 | 148 | input := "test" 149 | insert(t, input) 150 | insert(t, input) 151 | 152 | result, err := ResultText(sel) 153 | if err != ErrMultipleResults { 154 | t.Fatal("err != ErrMultipleResults", err) 155 | } 156 | if result != input { 157 | t.Fatal("first result was not returned") 158 | } 159 | }) 160 | 161 | t.Run("Text/ErrNoResult", func(t *testing.T) { 162 | defer cleanup(t) 163 | 164 | _, err := ResultText(sel) 165 | if err != ErrNoResults { 166 | t.Fatal("err != ErrNoResults", err) 167 | } 168 | }) 169 | } 170 | -------------------------------------------------------------------------------- /sqlitex/savepoint.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlitex 16 | 17 | import ( 18 | "fmt" 19 | "runtime" 20 | "strings" 21 | 22 | "github.com/go-llsqlite/crawshaw" 23 | ) 24 | 25 | // Save creates a named SQLite transaction using SAVEPOINT. 26 | // 27 | // On success Savepoint returns a releaseFn that will call either 28 | // RELEASE or ROLLBACK depending on whether the parameter *error 29 | // points to a nil or non-nil error. This is designed to be deferred. 30 | // 31 | // Example: 32 | // 33 | // func doWork(conn *sqlite.Conn) (err error) { 34 | // defer sqlitex.Save(conn)(&err) 35 | // 36 | // // ... do work in the transaction 37 | // } 38 | // 39 | // https://www.sqlite.org/lang_savepoint.html 40 | func Save(conn *sqlite.Conn) (releaseFn func(*error)) { 41 | name := "sqlitex.Save" // safe as names can be reused 42 | var pc [3]uintptr 43 | if n := runtime.Callers(0, pc[:]); n > 0 { 44 | frames := runtime.CallersFrames(pc[:n]) 45 | if _, more := frames.Next(); more { // runtime.Callers 46 | if _, more := frames.Next(); more { // savepoint.Save 47 | frame, _ := frames.Next() // caller we care about 48 | if frame.Function != "" { 49 | name = frame.Function 50 | } 51 | } 52 | } 53 | } 54 | 55 | releaseFn, err := savepoint(conn, name) 56 | if err != nil { 57 | if sqlite.ErrCode(err) == sqlite.SQLITE_INTERRUPT { 58 | return func(errp *error) { 59 | if *errp == nil { 60 | *errp = err 61 | } 62 | } 63 | } 64 | panic(err) 65 | } 66 | return releaseFn 67 | } 68 | 69 | func savepoint(conn *sqlite.Conn, name string) (releaseFn func(*error), err error) { 70 | if strings.Contains(name, `"`) { 71 | return nil, fmt.Errorf("sqlitex.Savepoint: invalid name: %q", name) 72 | } 73 | if err := Exec(conn, fmt.Sprintf("SAVEPOINT %q;", name), nil); err != nil { 74 | return nil, err 75 | } 76 | tracer := conn.Tracer() 77 | if tracer != nil { 78 | tracer.Push("TX " + name) 79 | } 80 | releaseFn = func(errp *error) { 81 | if tracer != nil { 82 | tracer.Pop() 83 | } 84 | recoverP := recover() 85 | 86 | // If a query was interrupted or if a user exec'd COMMIT or 87 | // ROLLBACK, then everything was already rolled back 88 | // automatically, thus returning the connection to autocommit 89 | // mode. 90 | if conn.GetAutocommit() { 91 | // There is nothing to rollback. 92 | if recoverP != nil { 93 | panic(recoverP) 94 | } 95 | return 96 | } 97 | 98 | if *errp == nil && recoverP == nil { 99 | // Success path. Release the savepoint successfully. 100 | *errp = Exec(conn, fmt.Sprintf("RELEASE %q;", name), nil) 101 | if *errp == nil { 102 | return 103 | } 104 | // Possible interrupt. Fall through to the error path. 105 | if conn.GetAutocommit() { 106 | // There is nothing to rollback. 107 | if recoverP != nil { 108 | panic(recoverP) 109 | } 110 | return 111 | } 112 | } 113 | 114 | orig := "" 115 | if *errp != nil { 116 | orig = (*errp).Error() + "\n\t" 117 | } 118 | 119 | // Error path. 120 | 121 | // Always run ROLLBACK even if the connection has been interrupted. 122 | oldDoneCh := conn.SetInterrupt(nil) 123 | defer conn.SetInterrupt(oldDoneCh) 124 | 125 | err := Exec(conn, fmt.Sprintf("ROLLBACK TO %q;", name), nil) 126 | if err != nil { 127 | panic(orig + err.Error()) 128 | } 129 | err = Exec(conn, fmt.Sprintf("RELEASE %q;", name), nil) 130 | if err != nil { 131 | panic(orig + err.Error()) 132 | } 133 | 134 | if recoverP != nil { 135 | panic(recoverP) 136 | } 137 | } 138 | return releaseFn, nil 139 | } 140 | -------------------------------------------------------------------------------- /sqlitex/savepoint_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | package sqlitex 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "io/ioutil" 21 | "path/filepath" 22 | "strings" 23 | "testing" 24 | "time" 25 | 26 | "github.com/go-llsqlite/crawshaw" 27 | ) 28 | 29 | func TestSaveExec(t *testing.T) { 30 | conn, err := sqlite.OpenConn(":memory:", 0) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | defer conn.Close() 35 | 36 | if err := Exec(conn, "CREATE TABLE t (c1);", nil); err != nil { 37 | t.Fatal(err) 38 | } 39 | countFn := func() int { 40 | var count int 41 | fn := func(stmt *sqlite.Stmt) error { 42 | count = stmt.ColumnInt(0) 43 | return nil 44 | } 45 | if err := Exec(conn, "SELECT count(*) FROM t;", fn); err != nil { 46 | t.Fatal(err) 47 | } 48 | return count 49 | } 50 | errNoSuccess := errors.New("succeed=false") 51 | insert := func(succeed bool) (err error) { 52 | defer Save(conn)(&err) 53 | 54 | if err := Exec(conn, `INSERT INTO t VALUES ('hello');`, nil); err != nil { 55 | t.Fatal(err) 56 | } 57 | 58 | if succeed { 59 | return nil 60 | } 61 | return errNoSuccess 62 | } 63 | 64 | if err := insert(true); err != nil { 65 | t.Fatal(err) 66 | } 67 | if got := countFn(); got != 1 { 68 | t.Errorf("expecting 1 row, got %d", got) 69 | } 70 | if err := insert(true); err != nil { 71 | t.Fatal(err) 72 | } 73 | if got := countFn(); got != 2 { 74 | t.Errorf("expecting 2 rows, got %d", got) 75 | } 76 | if err := insert(false); err != errNoSuccess { 77 | t.Errorf("expecting insert to fail with errNoSuccess, got %v", err) 78 | } 79 | if got := countFn(); got != 2 { 80 | t.Errorf("expecting 2 rows, got %d", got) 81 | } 82 | } 83 | 84 | func TestPanic(t *testing.T) { 85 | conn, err := sqlite.OpenConn(":memory:", 0) 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | defer conn.Close() 90 | 91 | if err := Exec(conn, "CREATE TABLE t (c1);", nil); err != nil { 92 | t.Fatal(err) 93 | } 94 | if err := Exec(conn, `INSERT INTO t VALUES ('one');`, nil); err != nil { 95 | t.Fatal(err) 96 | } 97 | 98 | defer func() { 99 | p := recover() 100 | if p == nil { 101 | t.Errorf("panic expected") 102 | } 103 | if err, isErr := p.(error); !isErr || !strings.Contains(err.Error(), "sqlite") { 104 | t.Errorf("panic is not an sqlite error: %v", err) 105 | } 106 | 107 | count := 0 108 | fn := func(stmt *sqlite.Stmt) error { 109 | count = stmt.ColumnInt(0) 110 | return nil 111 | } 112 | if err := Exec(conn, "SELECT count(*) FROM t;", fn); err != nil { 113 | t.Error(err) 114 | } 115 | if count != 1 { 116 | t.Errorf("got %d rows, want 1", count) 117 | } 118 | }() 119 | 120 | if err := doPanic(conn); err != nil { 121 | t.Errorf("unexpected error: %v", err) 122 | } 123 | } 124 | 125 | func doPanic(conn *sqlite.Conn) (err error) { 126 | defer Save(conn)(&err) 127 | 128 | if err := Exec(conn, `INSERT INTO t VALUES ('hello');`, nil); err != nil { 129 | return err 130 | } 131 | 132 | conn.Prep("SELECT bad query") // panics 133 | return nil 134 | } 135 | 136 | func TestDone(t *testing.T) { 137 | doneCh := make(chan struct{}) 138 | 139 | conn, err := sqlite.OpenConn(":memory:", 0) 140 | if err != nil { 141 | t.Fatal(err) 142 | } 143 | defer conn.Close() 144 | 145 | conn.SetInterrupt(doneCh) 146 | close(doneCh) 147 | 148 | relFn := Save(conn) 149 | relFn(&err) 150 | if code := sqlite.ErrCode(err); code != sqlite.SQLITE_INTERRUPT { 151 | t.Errorf("savepoint release function error code is %v, want SQLITE_INTERRUPT", code) 152 | } 153 | } 154 | 155 | func TestReleaseTx(t *testing.T) { 156 | conn1, err := sqlite.OpenConn("file::memory:?mode=memory&cache=shared", 0) 157 | if err != nil { 158 | t.Fatal(err) 159 | } 160 | defer conn1.Close() 161 | 162 | conn2, err := sqlite.OpenConn("file::memory:?mode=memory&cache=shared", 0) 163 | if err != nil { 164 | t.Fatal(err) 165 | } 166 | defer conn2.Close() 167 | 168 | Exec(conn1, "DROP TABLE t;", nil) 169 | if err := Exec(conn1, "CREATE TABLE t (c1);", nil); err != nil { 170 | t.Fatal(err) 171 | } 172 | countFn := func() int { 173 | var count int 174 | fn := func(stmt *sqlite.Stmt) error { 175 | count = stmt.ColumnInt(0) 176 | return nil 177 | } 178 | if err := Exec(conn2, "SELECT count(*) FROM t;", fn); err != nil { 179 | t.Fatal(err) 180 | } 181 | return count 182 | } 183 | errNoSuccess := errors.New("succeed=false") 184 | insert := func(succeed bool) (err error) { 185 | defer Save(conn1)(&err) 186 | 187 | if err := Exec(conn1, `INSERT INTO t VALUES ('hello');`, nil); err != nil { 188 | t.Fatal(err) 189 | } 190 | 191 | if succeed { 192 | return nil 193 | } 194 | return errNoSuccess 195 | } 196 | 197 | if err := insert(true); err != nil { 198 | t.Fatal(err) 199 | } 200 | if got := countFn(); got != 1 { 201 | t.Errorf("expecting 1 row, got %d", got) 202 | } 203 | 204 | if err := insert(false); err == nil { 205 | t.Fatal(err) 206 | } 207 | // If the transaction is still open, countFn will get stuck 208 | // on conn2 waiting for conn1's write lock to release. 209 | if got := countFn(); got != 1 { 210 | t.Errorf("expecting 1 row, got %d", got) 211 | } 212 | } 213 | 214 | func TestInterruptRollback(t *testing.T) { 215 | conn, err := sqlite.OpenConn("file::memory:?mode=memory&cache=shared", 0) 216 | if err != nil { 217 | t.Fatal(err) 218 | } 219 | defer conn.Close() 220 | 221 | if err = ExecScript(conn, ` 222 | DROP TABLE IF EXISTS t; 223 | CREATE TABLE t (c); 224 | `); err != nil { 225 | t.Fatal(err) 226 | } 227 | 228 | releaseFn := Save(conn) 229 | if err := Exec(conn, `INSERT INTO t (c) VALUES (1);`, nil); err != nil { 230 | t.Fatal(err) 231 | } 232 | releaseFn(&err) 233 | if err != nil { 234 | t.Fatalf("relaseFn err: %v", err) 235 | } 236 | 237 | ctx, cancel := context.WithCancel(context.Background()) 238 | conn.SetInterrupt(ctx.Done()) 239 | 240 | releaseFn1 := Save(conn) 241 | if err := Exec(conn, `INSERT INTO t (c) VALUES (2);`, nil); err != nil { 242 | t.Fatal(err) 243 | } 244 | releaseFn2 := Save(conn) 245 | if err := Exec(conn, `INSERT INTO t (c) VALUES (3);`, nil); err != nil { 246 | t.Fatal(err) 247 | } 248 | cancel() 249 | if err := Exec(conn, `INSERT INTO t (c) VALUES (3);`, nil); err == nil || sqlite.ErrCode(err) != sqlite.SQLITE_INTERRUPT { 250 | t.Fatalf("want SQLITE_INTERRUPT, got %v", err) 251 | } 252 | err = context.Canceled 253 | releaseFn2(&err) // given a real error, should rollback 254 | if err != context.Canceled { 255 | t.Fatalf("relaseFn2 err: %v", err) 256 | } 257 | var errNil error 258 | releaseFn1(&errNil) // given no error, but we are interrupted, so should rollback 259 | if errNil == nil { 260 | t.Fatal("relaseFn1 errNil is still nil, should have been set to interrupt") 261 | } 262 | if sqlite.ErrCode(errNil) != sqlite.SQLITE_INTERRUPT { 263 | t.Fatalf("relaseFn1 errNil=%v, want SQLITE_INTERRUPT", errNil) 264 | } 265 | 266 | conn.SetInterrupt(nil) 267 | got, err := ResultInt(conn.Prep("SELECT count(*) FROM t;")) 268 | if err != nil { 269 | t.Fatal(err) 270 | } 271 | if got != 1 { 272 | t.Errorf("want 1 row, got %d", got) 273 | } 274 | } 275 | 276 | var veryLongScript = ` 277 | drop table if exists naturals; 278 | create table naturals 279 | ( n integer unique primary key asc, 280 | isprime bool, 281 | factor integer); 282 | 283 | with recursive 284 | nn (n) 285 | as ( 286 | select 2 287 | union all 288 | select n+1 as newn from nn 289 | where newn < 1e10 290 | ) 291 | insert into naturals 292 | select n, 1, null from nn; 293 | 294 | insert or replace into naturals 295 | with recursive 296 | product (prime,composite) 297 | as ( 298 | select n, n*n as sqr 299 | from naturals 300 | where sqr <= (select max(n) from naturals) 301 | union all 302 | select prime, composite+prime as prod 303 | from 304 | product 305 | where 306 | prod <= (select max(n) from naturals) 307 | ) 308 | select n, 0, prime 309 | from product join naturals 310 | on (product.composite = naturals.n) 311 | ; 312 | ` 313 | 314 | func TestInterruptRollbackLongQuery(t *testing.T) { 315 | conn, err := sqlite.OpenConn("file::memory:?mode=memory&cache=shared", 0) 316 | if err != nil { 317 | t.Fatal(err) 318 | } 319 | defer conn.Close() 320 | 321 | if err = ExecScript(conn, ` 322 | DROP TABLE IF EXISTS t; 323 | CREATE TABLE t (c); 324 | `); err != nil { 325 | t.Fatal(err) 326 | } 327 | 328 | releaseFn := Save(conn) 329 | if err := Exec(conn, `INSERT INTO t (c) VALUES (1);`, nil); err != nil { 330 | t.Fatal(err) 331 | } 332 | releaseFn(&err) 333 | if err != nil { 334 | t.Fatalf("relaseFn err: %v", err) 335 | } 336 | 337 | ctx, cancel := context.WithCancel(context.Background()) 338 | conn.SetInterrupt(ctx.Done()) 339 | testDone := make(chan struct{}) 340 | go func() { 341 | defer close(testDone) 342 | defer func() { 343 | if err := recover(); err != nil { 344 | t.Log("a panic occurred during rollback\n", err) 345 | t.Fail() 346 | } 347 | }() 348 | defer Save(conn)(&err) 349 | if err := Exec(conn, `INSERT INTO t (c) VALUES (3);`, nil); err != nil { 350 | t.Fatalf("interrupted too early") 351 | } 352 | err = ExecScript(conn, veryLongScript) 353 | }() 354 | time.Sleep(20 * time.Millisecond) 355 | cancel() 356 | <-testDone 357 | conn.SetInterrupt(nil) 358 | got, err := ResultInt(conn.Prep("SELECT count(*) FROM t;")) 359 | if err != nil { 360 | t.Fatal(err) 361 | } 362 | if got != 1 { 363 | t.Errorf("want 1 row, got %d", got) 364 | } 365 | } 366 | 367 | func TestBusySnapshot(t *testing.T) { 368 | dir, err := ioutil.TempDir("", "sqlitex-test-") 369 | if err != nil { 370 | t.Fatal(err) 371 | } 372 | db := filepath.Join(dir, "busysnapshot.db") 373 | 374 | // No SQLITE_OPEN_SHAREDCACHE. 375 | flags := sqlite.SQLITE_OPEN_READWRITE | sqlite.SQLITE_OPEN_CREATE | sqlite.SQLITE_OPEN_WAL 376 | conn0, err := sqlite.OpenConn(db, flags) 377 | if err != nil { 378 | t.Fatal(err) 379 | } 380 | defer conn0.Close() 381 | 382 | conn1, err := sqlite.OpenConn(db, flags) 383 | if err != nil { 384 | t.Fatal(err) 385 | } 386 | defer conn1.Close() 387 | 388 | if err = ExecScript(conn0, ` 389 | DROP TABLE IF EXISTS t; 390 | CREATE TABLE t (c, b BLOB); 391 | INSERT INTO t (c, b) VALUES (4, 'hi'); 392 | `); err != nil { 393 | t.Fatal(err) 394 | } 395 | 396 | conn0Release0 := Save(conn0) 397 | 398 | c, err := ResultInt(conn0.Prep("SELECT count(*) FROM t WHERE c > 3;")) 399 | if err != nil { 400 | t.Fatal(err) 401 | } 402 | 403 | // An insert on conn1 invalidates the deferred transaction on conn0. 404 | if err := Exec(conn1, "INSERT INTO t (c) VALUES (4);", nil); err != nil { 405 | t.Fatal(err) 406 | } 407 | 408 | stmt := conn0.Prep("UPDATE t SET c = $c WHERE c = 4;") 409 | stmt.SetInt64("$c", int64(c)) 410 | _, conn0Err := stmt.Step() 411 | if sqlite.ErrCode(conn0Err) != sqlite.SQLITE_BUSY_SNAPSHOT { 412 | t.Fatalf("want SQLITE_BUSY_SNAPSHOT, got: %v", conn0Err) 413 | } 414 | 415 | conn0Release0(&conn0Err) 416 | if sqlite.ErrCode(conn0Err) != sqlite.SQLITE_BUSY_SNAPSHOT { 417 | t.Fatalf("after savepoint want SQLITE_BUSY_SNAPSHOT, got: %v", conn0Err) 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /sqlitex/snapshot.go: -------------------------------------------------------------------------------- 1 | package sqlitex 2 | 3 | import ( 4 | "context" 5 | "runtime" 6 | 7 | "github.com/go-llsqlite/crawshaw" 8 | ) 9 | 10 | // GetSnapshot returns a Snapshot that should remain available for reads until 11 | // it is garbage collected. 12 | // 13 | // This sets aside a Conn from the Pool with an open read transaction until the 14 | // Snapshot is garbage collected or the Pool is closed. Thus, until the 15 | // returned Snapshot is garbage collected, the Pool will have one fewer Conn, 16 | // and it should not be possible for the WAL to be checkpointed beyond the 17 | // point of the Snapshot. 18 | // 19 | // See sqlite.Conn.GetSnapshot and sqlite.Snapshot for more details. 20 | func (p *Pool) GetSnapshot(ctx context.Context, schema string) (*sqlite.Snapshot, error) { 21 | conn := p.Get(ctx) 22 | if conn == nil { 23 | return nil, context.Canceled 24 | } 25 | conn.SetInterrupt(nil) 26 | s, release, err := conn.GetSnapshot(schema) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | snapshotGCd := make(chan struct{}) 32 | runtime.SetFinalizer(s, nil) 33 | runtime.SetFinalizer(s, func(s *sqlite.Snapshot) { 34 | // Free the C resources associated with the Snapshot. 35 | s.Free() 36 | close(snapshotGCd) 37 | }) 38 | 39 | go func() { 40 | select { 41 | case <-p.closed: 42 | case <-snapshotGCd: 43 | } 44 | // Allow the WAL to be checkpointed past the point of 45 | // the Snapshot. 46 | release() 47 | // Return the conn to the Pool for reuse. 48 | p.Put(conn) 49 | }() 50 | 51 | return s, nil 52 | } 53 | -------------------------------------------------------------------------------- /static.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 David Crawshaw 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | //go:build !linksqlite3 16 | // +build !linksqlite3 17 | 18 | package sqlite 19 | 20 | import _ "github.com/go-llsqlite/crawshaw/c" 21 | -------------------------------------------------------------------------------- /wrappers.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 David Crawshaw 3 | 4 | Permission to use, copy, modify, and distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | void cfree(void *p) { 22 | free(p); 23 | }; 24 | 25 | extern int go_strm_w_tramp(uintptr_t, char*, int); 26 | int c_strm_w_tramp(void *pOut, const void *pData, int n) { 27 | return go_strm_w_tramp((uintptr_t)pOut, (char*)pData, n); 28 | } 29 | 30 | extern int go_strm_r_tramp(uintptr_t, char*, int*); 31 | int c_strm_r_tramp(void *pOut, const void *pData, int *pN) { 32 | return go_strm_r_tramp((uintptr_t)pOut, (char*)pData, pN); 33 | } 34 | 35 | extern int go_xapply_conflict_tramp(uintptr_t, int, sqlite3_changeset_iter*); 36 | int c_xapply_conflict_tramp(void* pCtx, int eConflict, sqlite3_changeset_iter* p) { 37 | return go_xapply_conflict_tramp((uintptr_t)pCtx, eConflict, p); 38 | } 39 | 40 | extern int go_xapply_filter_tramp(uintptr_t, char*); 41 | int c_xapply_filter_tramp(void* pCtx, const char* zTab) { 42 | return go_xapply_filter_tramp((uintptr_t)pCtx, (char*)zTab); 43 | } 44 | 45 | extern void go_func_tramp(sqlite3_context*, int, sqlite3_value**); 46 | void c_func_tramp(sqlite3_context* ctx, int n, sqlite3_value** valarray) { 47 | return go_func_tramp(ctx, n, valarray); 48 | } 49 | 50 | extern void go_step_tramp(sqlite3_context*, int, sqlite3_value**); 51 | void c_step_tramp(sqlite3_context* ctx, int n, sqlite3_value** valarray) { 52 | return go_step_tramp(ctx, n, valarray); 53 | } 54 | 55 | extern void go_final_tramp(sqlite3_context*); 56 | void c_final_tramp(sqlite3_context* ctx) { 57 | return go_final_tramp(ctx); 58 | } 59 | 60 | extern void go_destroy_tramp(uintptr_t); 61 | void c_destroy_tramp(void* ptr) { 62 | return go_destroy_tramp((uintptr_t)ptr); 63 | } 64 | 65 | extern int go_sqlite_auth_tramp(uintptr_t, int, char*, char*, char*, char*); 66 | int c_auth_tramp(void *userData, int action, const char* arg1, const char* arg2, const char* db, const char* trigger) { 67 | return go_sqlite_auth_tramp((uintptr_t)userData, action, (char*)arg1, (char*)arg2, (char*)db, (char*)trigger); 68 | } 69 | 70 | extern void go_log_fn(void*, int, char*); 71 | void c_log_fn(void* pArg, int code, char* msg) { 72 | return go_log_fn(pArg, code, msg); 73 | } 74 | 75 | extern int goBusyHandlerCallback(void *, int); 76 | int c_goBusyHandlerCallback(void *pArg, int count) { 77 | return goBusyHandlerCallback(pArg, count); 78 | } -------------------------------------------------------------------------------- /wrappers.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 David Crawshaw 3 | 4 | Permission to use, copy, modify, and distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef WRAPPERS_H 18 | #define WRAPPERS_H 19 | 20 | /* cfree wraps free to fix https://github.com/crawshaw/sqlite/issues/60 */ 21 | void cfree(void *p); 22 | 23 | int c_strm_w_tramp(void*, const void*, int); 24 | int c_strm_r_tramp(void*, const void*, int*); 25 | 26 | int c_xapply_conflict_tramp(void*, int, sqlite3_changeset_iter*); 27 | int c_xapply_filter_tramp(void*, const char*); 28 | 29 | void c_log_fn(void*, int, char*); 30 | int c_auth_tramp(void*, int, const char*, const char*, const char*, const char*); 31 | 32 | void c_func_tramp(sqlite3_context*, int, sqlite3_value**); 33 | void c_step_tramp(sqlite3_context*, int, sqlite3_value**); 34 | void c_final_tramp(sqlite3_context*); 35 | void c_destroy_tramp(void*); 36 | 37 | int c_goBusyHandlerCallback(void*, int); 38 | 39 | #endif // WRAPPERS_H 40 | -------------------------------------------------------------------------------- /zombiezen-compat.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | const ( 4 | ResultRange = SQLITE_RANGE 5 | ResultError = SQLITE_ERROR 6 | ResultOK = SQLITE_OK 7 | ) 8 | --------------------------------------------------------------------------------