From 828788912e809911efdeda0c1be56bbb1fa0c933 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Wed, 8 Nov 2023 14:41:11 +0000 Subject: [PATCH] JSON example. --- README.md | 11 +++--- conn.go | 5 +-- const.go | 1 + driver/json_test.go | 65 ++++++++++++++++++++++++++++++++++ driver/savepoint_test.go | 3 +- driver_test.go | 75 ---------------------------------------- ext/blob/blob_test.go | 3 +- internal/util/const.go | 1 + 8 files changed, 79 insertions(+), 85 deletions(-) create mode 100644 driver/json_test.go delete mode 100644 driver_test.go diff --git a/README.md b/README.md index 39b2e69..1ac3b99 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,18 @@ and uses [wazero](https://wazero.io/) to provide `cgo`-free SQLite bindings. ([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package)). - [`github.com/ncruces/go-sqlite3/embed`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/embed) embeds a build of SQLite into your application. +- [`github.com/ncruces/go-sqlite3/ext/blob`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/blob) + simplifies incremental BLOB I/O. +- [`github.com/ncruces/go-sqlite3/ext/stats`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/stats) + registers [statistics functions](https://www.oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html). +- [`github.com/ncruces/go-sqlite3/ext/unicode`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/unicode) + registers Unicode aware functions. - [`github.com/ncruces/go-sqlite3/vfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs) wraps the [C SQLite VFS API](https://www.sqlite.org/vfs.html) and provides a pure Go implementation. - [`github.com/ncruces/go-sqlite3/vfs/memdb`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/memdb) implements an in-memory VFS. - [`github.com/ncruces/go-sqlite3/vfs/readervfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/readervfs) implements a VFS for immutable databases. -- [`github.com/ncruces/go-sqlite3/ext/unicode`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/unicode) - registers Unicode aware functions. -- [`github.com/ncruces/go-sqlite3/ext/stats`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/stats) - registers [statistics functions](https://www.oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html). - [`github.com/ncruces/go-sqlite3/gormlite`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/gormlite) provides a [GORM](https://gorm.io) driver. @@ -87,6 +89,7 @@ Performance is tested by running - [x] incremental BLOB I/O - [x] online backup - [x] JSON support + - [ ] virtual tables - [ ] session extension - [ ] custom VFSes - [x] custom VFS API diff --git a/conn.go b/conn.go index 4ffa27c..c2f24bb 100644 --- a/conn.go +++ b/conn.go @@ -303,12 +303,9 @@ func (c *Conn) error(rc uint64, sql ...string) error { // DriverConn is implemented by the SQLite [database/sql] driver connection. // -// It can be used to access advanced SQLite features like -// [savepoints], [online backup] and [incremental BLOB I/O]. +// It can be used to access SQLite features like [online backup]. // -// [savepoints]: https://www.sqlite.org/lang_savepoint.html // [online backup]: https://www.sqlite.org/backup.html -// [incremental BLOB I/O]: https://www.sqlite.org/c3ref/blob_open.html type DriverConn interface { Raw() *Conn } diff --git a/const.go b/const.go index 9d0cd30..14757e2 100644 --- a/const.go +++ b/const.go @@ -97,6 +97,7 @@ const ( IOERR_ROLLBACK_ATOMIC ExtendedErrorCode = xErrorCode(IOERR) | (31 << 8) IOERR_DATA ExtendedErrorCode = xErrorCode(IOERR) | (32 << 8) IOERR_CORRUPTFS ExtendedErrorCode = xErrorCode(IOERR) | (33 << 8) + IOERR_IN_PAGE ExtendedErrorCode = xErrorCode(IOERR) | (34 << 8) LOCKED_SHAREDCACHE ExtendedErrorCode = xErrorCode(LOCKED) | (1 << 8) LOCKED_VTAB ExtendedErrorCode = xErrorCode(LOCKED) | (2 << 8) BUSY_RECOVERY ExtendedErrorCode = xErrorCode(BUSY) | (1 << 8) diff --git a/driver/json_test.go b/driver/json_test.go new file mode 100644 index 0000000..e1cce5f --- /dev/null +++ b/driver/json_test.go @@ -0,0 +1,65 @@ +package driver_test + +import ( + "fmt" + "log" + + "github.com/ncruces/go-sqlite3" + "github.com/ncruces/go-sqlite3/driver" + _ "github.com/ncruces/go-sqlite3/embed" + _ "github.com/ncruces/go-sqlite3/vfs/memdb" +) + +func Example_json() { + db, err := driver.Open("file:/test.db?vfs=memdb", nil) + if err != nil { + log.Fatal(err) + } + defer db.Close() + + _, err = db.Exec(` + CREATE TABLE orders ( + cart_id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + cart TEXT + ); + `) + if err != nil { + log.Fatal(err) + } + + type CartItem struct { + ItemID string `json:"id"` + Name string `json:"name"` + Quantity int `json:"quantity,omitempty"` + Price int `json:"price,omitempty"` + } + + type Cart struct { + Items []CartItem `json:"items"` + } + + _, err = db.Exec(`INSERT INTO orders (user_id, cart) VALUES (?, ?)`, 123, sqlite3.JSON(Cart{ + []CartItem{ + {ItemID: "111", Name: "T-shirt", Quantity: 1, Price: 250}, + {ItemID: "222", Name: "Trousers", Quantity: 1, Price: 600}, + }, + })) + if err != nil { + log.Fatal(err) + } + + var total string + err = db.QueryRow(` + SELECT total(json_each.value -> 'price') + FROM orders, json_each(cart -> 'items') + WHERE cart_id = last_insert_rowid() + `).Scan(&total) + if err != nil { + log.Fatal(err) + } + + fmt.Println("total:", total) + // Output: + // total: 850 +} diff --git a/driver/savepoint_test.go b/driver/savepoint_test.go index a4e72bc..8f8c966 100644 --- a/driver/savepoint_test.go +++ b/driver/savepoint_test.go @@ -5,10 +5,11 @@ import ( "log" "github.com/ncruces/go-sqlite3/driver" + _ "github.com/ncruces/go-sqlite3/vfs/memdb" ) func ExampleSavepoint() { - db, err := driver.Open(":memory:", nil) + db, err := driver.Open("file:/test.db?vfs=memdb", nil) if err != nil { log.Fatal(err) } diff --git a/driver_test.go b/driver_test.go deleted file mode 100644 index 63b0c75..0000000 --- a/driver_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package sqlite3_test - -import ( - "context" - "database/sql" - "fmt" - "log" - "os" - - "github.com/ncruces/go-sqlite3" - _ "github.com/ncruces/go-sqlite3/driver" - _ "github.com/ncruces/go-sqlite3/embed" -) - -var db *sql.DB - -func ExampleDriverConn() { - var err error - db, err = sql.Open("sqlite3", "demo.db") - if err != nil { - log.Fatal(err) - } - defer os.Remove("demo.db") - defer db.Close() - - ctx := context.Background() - - conn, err := db.Conn(ctx) - if err != nil { - log.Fatal(err) - } - defer conn.Close() - - _, err = conn.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS test (col)`) - if err != nil { - log.Fatal(err) - } - - res, err := conn.ExecContext(ctx, `INSERT INTO test VALUES (?)`, sqlite3.ZeroBlob(11)) - if err != nil { - log.Fatal(err) - } - - id, err := res.LastInsertId() - if err != nil { - log.Fatal(err) - } - - err = conn.Raw(func(driverConn any) error { - conn := driverConn.(sqlite3.DriverConn).Raw() - savept := conn.Savepoint() - defer savept.Release(&err) - - blob, err := conn.OpenBlob("main", "test", "col", id, true) - if err != nil { - return err - } - defer blob.Close() - - _, err = fmt.Fprint(blob, "Hello BLOB!") - return err - }) - if err != nil { - log.Fatal(err) - } - - var msg string - err = conn.QueryRowContext(ctx, `SELECT col FROM test`).Scan(&msg) - if err != nil { - log.Fatal(err) - } - fmt.Println(msg) - // Output: - // Hello BLOB! -} diff --git a/ext/blob/blob_test.go b/ext/blob/blob_test.go index e8e8206..58c6589 100644 --- a/ext/blob/blob_test.go +++ b/ext/blob/blob_test.go @@ -9,11 +9,12 @@ import ( "github.com/ncruces/go-sqlite3/driver" _ "github.com/ncruces/go-sqlite3/embed" "github.com/ncruces/go-sqlite3/ext/blob" + _ "github.com/ncruces/go-sqlite3/vfs/memdb" ) func Example() { // Open the database, registering the extension. - db, err := driver.Open(":memory:", func(conn *sqlite3.Conn) error { + db, err := driver.Open("file:/test.db?vfs=memdb", func(conn *sqlite3.Conn) error { blob.Register(conn) return nil }) diff --git a/internal/util/const.go b/internal/util/const.go index 6398bc1..86bb974 100644 --- a/internal/util/const.go +++ b/internal/util/const.go @@ -72,6 +72,7 @@ const ( IOERR_ROLLBACK_ATOMIC = IOERR | (31 << 8) IOERR_DATA = IOERR | (32 << 8) IOERR_CORRUPTFS = IOERR | (33 << 8) + IOERR_IN_PAGE = IOERR | (34 << 8) LOCKED_SHAREDCACHE = LOCKED | (1 << 8) LOCKED_VTAB = LOCKED | (2 << 8) BUSY_RECOVERY = BUSY | (1 << 8)