From 089a0c0670a3c7720d291e2db1e239b3806e5904 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Wed, 6 Dec 2023 15:39:26 +0000 Subject: [PATCH] Pivot virtual table. --- README.md | 4 + conn.go | 8 +- embed/exports.txt | 2 + embed/sqlite3.wasm | Bin 1470019 -> 1470092 bytes error.go | 3 +- ext/array/array.go | 2 +- ext/csv/schema.go | 8 +- ext/pivot/pivot.go | 267 +++++++++++++++++++++++++++++++++++++ ext/pivot/pivot_test.go | 219 ++++++++++++++++++++++++++++++ ext/statement/stmt.go | 102 +++++++------- ext/statement/stmt_test.go | 1 - internal/util/error.go | 9 -- stmt.go | 1 + value.go | 25 ++++ 14 files changed, 577 insertions(+), 74 deletions(-) create mode 100644 ext/pivot/pivot.go create mode 100644 ext/pivot/pivot_test.go diff --git a/README.md b/README.md index dc874d2..1da5d16 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,10 @@ and uses [wazero](https://wazero.io/) to provide `cgo`-free SQLite bindings. reads [comma-separated values](https://sqlite.org/csv.html). - [`github.com/ncruces/go-sqlite3/ext/lines`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/lines) reads files [line-by-line](https://github.com/asg017/sqlite-lines). +- [`github.com/ncruces/go-sqlite3/ext/pivot`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/pivot) + creates [pivot tables](https://github.com/jakethaw/pivot_vtab). +- [`github.com/ncruces/go-sqlite3/ext/statement`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/statement) + creates [table-valued functions with SQL](https://github.com/0x09/sqlite-statement-vtab). - [`github.com/ncruces/go-sqlite3/ext/stats`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/stats) provides [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) diff --git a/conn.go b/conn.go index d637033..2e339ac 100644 --- a/conn.go +++ b/conn.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net/url" - "runtime" "strings" "github.com/ncruces/go-sqlite3/internal/util" @@ -56,8 +55,6 @@ func newConn(filename string, flags OpenFlag) (conn *Conn, err error) { defer func() { if conn == nil { sqlite.close() - } else { - runtime.SetFinalizer(conn, util.Finalizer[Conn](3)) } }() @@ -92,7 +89,7 @@ func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) { for _, p := range query["_pragma"] { pragmas.WriteString(`PRAGMA `) pragmas.WriteString(p) - pragmas.WriteByte(';') + pragmas.WriteString(`;`) } } @@ -140,7 +137,6 @@ func (c *Conn) Close() error { } c.handle = 0 - runtime.SetFinalizer(c, nil) return c.close() } @@ -194,7 +190,7 @@ func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail str if stmt.handle == 0 { return nil, "", nil } - return + return stmt, tail, nil } // GetAutocommit tests the connection for auto-commit mode. diff --git a/embed/exports.txt b/embed/exports.txt index ddd284d..0fefb37 100644 --- a/embed/exports.txt +++ b/embed/exports.txt @@ -83,6 +83,8 @@ sqlite3_user_data sqlite3_value_blob sqlite3_value_bytes sqlite3_value_double +sqlite3_value_dup +sqlite3_value_free sqlite3_value_int64 sqlite3_value_nochange sqlite3_value_pointer_go diff --git a/embed/sqlite3.wasm b/embed/sqlite3.wasm index c900bdabb2edad56de1f4e1e82bab08a737865d0..b65fcd172e7e8cd9659429359f194553fd36d329 100755 GIT binary patch delta 26752 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�=N&`FRl+?mW7$C3 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 OCqIJK=bh|RL;fFpDkXye diff --git a/error.go b/error.go index ca86b66..838f1aa 100644 --- a/error.go +++ b/error.go @@ -44,8 +44,7 @@ func (e *Error) Error() string { } if e.msg != "" { - b.WriteByte(':') - b.WriteByte(' ') + b.WriteString(": ") b.WriteString(e.msg) } diff --git a/ext/array/array.go b/ext/array/array.go index 928aa6b..41eced8 100644 --- a/ext/array/array.go +++ b/ext/array/array.go @@ -119,7 +119,7 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error { return nil } -func indexable(v reflect.Value) (_ reflect.Value, err error) { +func indexable(v reflect.Value) (reflect.Value, error) { if v.Kind() == reflect.Slice { return v, nil } diff --git a/ext/csv/schema.go b/ext/csv/schema.go index 2ca807c..e3243e1 100644 --- a/ext/csv/schema.go +++ b/ext/csv/schema.go @@ -8,9 +8,9 @@ import ( ) func getSchema(header bool, columns int, row []string) string { - var sep = "" + var sep string var str strings.Builder - str.WriteString(`CREATE TABLE x(`) + str.WriteString("CREATE TABLE x(") if 0 <= columns && columns < len(row) { row = row[:columns] @@ -20,7 +20,7 @@ func getSchema(header bool, columns int, row []string) string { if header && f != "" { str.WriteString(sqlite3.QuoteIdentifier(f)) } else { - str.WriteByte('c') + str.WriteString("c") str.WriteString(strconv.Itoa(i + 1)) } str.WriteString(" TEXT") @@ -28,7 +28,7 @@ func getSchema(header bool, columns int, row []string) string { } for i := len(row); i < columns; i++ { str.WriteString(sep) - str.WriteByte('c') + str.WriteString("c") str.WriteString(strconv.Itoa(i + 1)) str.WriteString(" TEXT") sep = "," diff --git a/ext/pivot/pivot.go b/ext/pivot/pivot.go new file mode 100644 index 0000000..f9b20b6 --- /dev/null +++ b/ext/pivot/pivot.go @@ -0,0 +1,267 @@ +// Package pivot implements a pivot virtual table. +// +// https://github.com/jakethaw/pivot_vtab +package pivot + +import ( + "errors" + "fmt" + "strings" + + "github.com/ncruces/go-sqlite3" +) + +// Register registers the pivot virtual table. +func Register(db *sqlite3.Conn) { + sqlite3.CreateModule(db, "pivot", declare, declare) +} + +type table struct { + db *sqlite3.Conn + scan string + cell string + keys []string + cols []*sqlite3.Value +} + +func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (_ *table, err error) { + if len(arg) != 3 { + return nil, fmt.Errorf("pivot: wrong number of arguments") + } + + table := &table{db: db} + defer func() { + if err != nil { + table.Close() + } + }() + + var sep string + var create strings.Builder + create.WriteString("CREATE TABLE x(") + + // Row key query. + table.scan = "SELECT * FROM\n" + arg[0] + stmt, _, err := db.Prepare(table.scan) + if err != nil { + return nil, err + } + defer stmt.Close() + + table.keys = make([]string, stmt.ColumnCount()) + for i := range table.keys { + name := sqlite3.QuoteIdentifier(stmt.ColumnName(i)) + table.keys[i] = name + create.WriteString(sep) + create.WriteString(name) + sep = "," + } + stmt.Close() + + // Column definition query. + stmt, _, err = db.Prepare("SELECT * FROM\n" + arg[1]) + if err != nil { + return nil, err + } + + if stmt.ColumnCount() != 2 { + return nil, fmt.Errorf("pivot: column definition query expects 2 result columns") + } + for stmt.Step() { + name := sqlite3.QuoteIdentifier(stmt.ColumnText(1)) + table.cols = append(table.cols, stmt.ColumnValue(0).Dup()) + create.WriteString(",") + create.WriteString(name) + } + stmt.Close() + + // Pivot cell query. + table.cell = "SELECT * FROM\n" + arg[2] + stmt, _, err = db.Prepare(table.cell) + if err != nil { + return nil, err + } + + if stmt.ColumnCount() != 1 { + return nil, fmt.Errorf("pivot: cell query expects 1 result columns") + } + if stmt.BindCount() != len(table.keys)+1 { + return nil, fmt.Errorf("pivot: cell query expects %d bound parameters", len(table.keys)+1) + } + + create.WriteByte(')') + err = db.DeclareVtab(create.String()) + if err != nil { + return nil, err + } + return table, nil +} + +func (t *table) Close() error { + for i := range t.cols { + t.cols[i].Close() + } + return nil +} + +func (t *table) BestIndex(idx *sqlite3.IndexInfo) error { + var idxStr strings.Builder + idxStr.WriteString(t.scan) + + argvIndex := 1 + sep := " WHERE " + for i, cst := range idx.Constraint { + if !cst.Usable || !(0 <= cst.Column && cst.Column < len(t.keys)) { + continue + } + + var op string + switch cst.Op { + case sqlite3.INDEX_CONSTRAINT_EQ: + op = "=" + case sqlite3.INDEX_CONSTRAINT_LT: + op = "<" + case sqlite3.INDEX_CONSTRAINT_GT: + op = ">" + case sqlite3.INDEX_CONSTRAINT_LE: + op = "<=" + case sqlite3.INDEX_CONSTRAINT_GE: + op = ">=" + case sqlite3.INDEX_CONSTRAINT_NE: + op = "<>" + case sqlite3.INDEX_CONSTRAINT_MATCH: + op = "MATCH" + case sqlite3.INDEX_CONSTRAINT_LIKE: + op = "LIKE" + case sqlite3.INDEX_CONSTRAINT_GLOB: + op = "GLOB" + case sqlite3.INDEX_CONSTRAINT_REGEXP: + op = "REGEXP" + case sqlite3.INDEX_CONSTRAINT_IS, sqlite3.INDEX_CONSTRAINT_ISNULL: + op = "IS" + case sqlite3.INDEX_CONSTRAINT_ISNOT, sqlite3.INDEX_CONSTRAINT_ISNOTNULL: + op = "IS NOT" + default: + continue + } + + idxStr.WriteString(sep) + idxStr.WriteString(t.keys[cst.Column]) + idxStr.WriteString(" ") + idxStr.WriteString(op) + idxStr.WriteString(" ?") + + idx.ConstraintUsage[i] = sqlite3.IndexConstraintUsage{ + ArgvIndex: argvIndex, + Omit: true, + } + sep = " AND " + argvIndex++ + } + + sep = " ORDER BY " + idx.OrderByConsumed = true + for _, ord := range idx.OrderBy { + if !(0 <= ord.Column && ord.Column < len(t.keys)) { + idx.OrderByConsumed = false + continue + } + idxStr.WriteString(sep) + idxStr.WriteString(t.keys[ord.Column]) + if ord.Desc { + idxStr.WriteString(" DESC") + } + sep = "," + } + + idx.EstimatedCost = 1e9 / float64(argvIndex) + idx.IdxStr = idxStr.String() + return nil +} + +func (t *table) Open() (sqlite3.VTabCursor, error) { + return &cursor{table: t}, nil +} + +func (t *table) Rename(new string) error { + return nil +} + +type cursor struct { + table *table + scan *sqlite3.Stmt + cell *sqlite3.Stmt + rowID int64 +} + +func (c *cursor) Close() error { + return errors.Join(c.scan.Close(), c.cell.Close()) +} + +func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error { + err := c.scan.Close() + if err != nil { + return err + } + + c.scan, _, err = c.table.db.Prepare(idxStr) + if err != nil { + return err + } + for i, arg := range arg { + err := c.scan.BindValue(i+1, arg) + if err != nil { + return err + } + } + + if c.cell == nil { + c.cell, _, err = c.table.db.Prepare(c.table.cell) + if err != nil { + return err + } + } + + c.rowID = 0 + return c.Next() +} + +func (c *cursor) Next() error { + if c.scan.Step() { + count := c.scan.ColumnCount() + for i := 0; i < count; i++ { + err := c.cell.BindValue(i+1, c.scan.ColumnValue(i)) + if err != nil { + return err + } + } + c.rowID++ + } + return c.scan.Err() +} + +func (c *cursor) EOF() bool { + return !c.scan.Busy() +} + +func (c *cursor) RowID() (int64, error) { + return c.rowID, nil +} + +func (c *cursor) Column(ctx *sqlite3.Context, col int) error { + count := c.scan.ColumnCount() + if col < count { + ctx.ResultValue(c.scan.ColumnValue(col)) + return nil + } + + err := c.cell.BindValue(count+1, *c.table.cols[col-count]) + if err != nil { + return err + } + + if c.cell.Step() { + ctx.ResultValue(c.cell.ColumnValue(0)) + } + return c.cell.Reset() +} diff --git a/ext/pivot/pivot_test.go b/ext/pivot/pivot_test.go new file mode 100644 index 0000000..97970eb --- /dev/null +++ b/ext/pivot/pivot_test.go @@ -0,0 +1,219 @@ +package pivot_test + +import ( + "fmt" + "log" + "strings" + "testing" + + "github.com/ncruces/go-sqlite3" + _ "github.com/ncruces/go-sqlite3/embed" + "github.com/ncruces/go-sqlite3/ext/pivot" +) + +// https://antonz.org/sqlite-pivot-table/ +func Example() { + db, err := sqlite3.Open(":memory:") + if err != nil { + log.Fatal(err) + } + defer db.Close() + + pivot.Register(db) + + err = db.Exec(` + CREATE TABLE sales(product TEXT, year INT, income DECIMAL); + INSERT INTO sales(product, year, income) VALUES + ('alpha', 2020, 100), + ('alpha', 2021, 120), + ('alpha', 2022, 130), + ('alpha', 2023, 140), + ('beta', 2020, 10), + ('beta', 2021, 20), + ('beta', 2022, 40), + ('beta', 2023, 80), + ('gamma', 2020, 80), + ('gamma', 2021, 75), + ('gamma', 2022, 78), + ('gamma', 2023, 80); + `) + if err != nil { + log.Fatal(err) + } + + err = db.Exec(` + CREATE VIRTUAL TABLE v_sales USING pivot( + -- rows + (SELECT DISTINCT product FROM sales), + -- columns + (SELECT DISTINCT year, year FROM sales), + -- cells + (SELECT sum(income) FROM sales WHERE product = ? AND year = ?) + )`) + if err != nil { + log.Fatal(err) + } + + stmt, _, err := db.Prepare(`SELECT * FROM v_sales`) + if err != nil { + log.Fatal(err) + } + defer stmt.Close() + + cols := make([]string, stmt.ColumnCount()) + for i := range cols { + cols[i] = stmt.ColumnName(i) + } + fmt.Println(pretty(cols)) + for stmt.Step() { + for i := range cols { + cols[i] = stmt.ColumnText(i) + } + fmt.Println(pretty(cols)) + } + if err := stmt.Reset(); err != nil { + log.Fatal(err) + } + + // Output: + // product 2020 2021 2022 2023 + // alpha 100 120 130 140 + // beta 10 20 40 80 + // gamma 80 75 78 80 +} + +func TestRegister(t *testing.T) { + t.Parallel() + + db, err := sqlite3.Open(":memory:") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + pivot.Register(db) + + err = db.Exec(` + CREATE TABLE r AS + SELECT 1 id UNION SELECT 2 UNION SELECT 3; + + CREATE TABLE c( + id INTEGER PRIMARY KEY, + name TEXT + ); + INSERT INTO c (name) VALUES + ('a'),('b'),('c'),('d'); + + CREATE TABLE x( + r_id INT, + c_id INT, + val TEXT + ); + INSERT INTO x (r_id, c_id, val) + SELECT r.id, c.id, c.name || r.id + FROM c, r; + `) + if err != nil { + t.Fatal(err) + } + + err = db.Exec(` + CREATE VIRTUAL TABLE v_x USING pivot( + -- rows + (SELECT id r_id FROM r), + -- columns + (SELECT id c_id, name FROM c), + -- cells + (SELECT val FROM x WHERE r_id = ?1 AND c_id = ?2) + )`) + if err != nil { + t.Fatal(err) + } + + stmt, _, err := db.Prepare(`SELECT * FROM v_x WHERE rowid <> 0 AND r_id <> 1 ORDER BY rowid, r_id DESC LIMIT 1`) + if err != nil { + t.Fatal(err) + } + defer stmt.Close() + + if stmt.Step() { + if got := stmt.ColumnInt(0); got != 3 { + t.Errorf("got %d, want 3", got) + } + } +} + +func TestRegister_errors(t *testing.T) { + t.Parallel() + + db, err := sqlite3.Open(":memory:") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + pivot.Register(db) + + err = db.Exec(`CREATE VIRTUAL TABLE pivot USING pivot()`) + if err == nil { + t.Fatal("want error") + } else { + t.Log(err) + } + + err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot(SELECT 1, SELECT 2, SELECT 3)`) + if err == nil { + t.Fatal("want error") + } else { + t.Log(err) + } + + err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot((SELECT 1), SELECT 2, SELECT 3)`) + if err == nil { + t.Fatal("want error") + } else { + t.Log(err) + } + + err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot((SELECT 1), (SELECT 2), SELECT 3)`) + if err == nil { + t.Fatal("want error") + } else { + t.Log(err) + } + + err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot((SELECT 1), (SELECT 1, 2), SELECT 3)`) + if err == nil { + t.Fatal("want error") + } else { + t.Log(err) + } + + err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot((SELECT 1), (SELECT 1, 2), (SELECT 3, 4))`) + if err == nil { + t.Fatal("want error") + } else { + t.Log(err) + } + + err = db.Exec(`CREATE VIRTUAL TABLE split_date USING pivot((SELECT 1), (SELECT 1, 2), (SELECT 3))`) + if err == nil { + t.Fatal("want error") + } else { + t.Log(err) + } +} + +func pretty(cols []string) string { + var buf strings.Builder + for i, s := range cols { + if i != 0 { + buf.WriteByte(' ') + } + for buf.Len()%8 != 0 { + buf.WriteByte(' ') + } + buf.WriteString(s) + } + return buf.String() +} diff --git a/ext/statement/stmt.go b/ext/statement/stmt.go index 951280a..77289c0 100644 --- a/ext/statement/stmt.go +++ b/ext/statement/stmt.go @@ -15,55 +15,6 @@ import ( // Register registers the statement virtual table. func Register(db *sqlite3.Conn) { - declare := func(db *sqlite3.Conn, _, _, _ string, arg ...string) (*table, error) { - if len(arg) != 1 { - return nil, fmt.Errorf("statement: wrong number of arguments") - } - - sql := "SELECT * FROM\n" + arg[0] - - stmt, _, err := db.Prepare(sql) - if err != nil { - return nil, err - } - - var sep = "" - var str strings.Builder - str.WriteString(`CREATE TABLE x(`) - outputs := stmt.ColumnCount() - for i := 0; i < outputs; i++ { - name := sqlite3.QuoteIdentifier(stmt.ColumnName(i)) - str.WriteString(sep) - str.WriteString(name) - str.WriteByte(' ') - str.WriteString(stmt.ColumnDeclType(i)) - sep = "," - } - inputs := stmt.BindCount() - for i := 1; i <= inputs; i++ { - str.WriteString(sep) - name := stmt.BindName(i) - if name == "" { - str.WriteString("[") - str.WriteString(strconv.Itoa(i)) - str.WriteString("] HIDDEN") - } else { - str.WriteString(sqlite3.QuoteIdentifier(name[1:])) - str.WriteString(" HIDDEN") - } - sep = "," - } - str.WriteByte(')') - - err = db.DeclareVtab(str.String()) - if err != nil { - stmt.Close() - return nil, err - } - - return &table{sql: sql, stmt: stmt}, nil - } - sqlite3.CreateModule(db, "statement", declare, declare) } @@ -73,6 +24,55 @@ type table struct { inuse bool } +func declare(db *sqlite3.Conn, _, _, _ string, arg ...string) (*table, error) { + if len(arg) != 1 { + return nil, fmt.Errorf("statement: wrong number of arguments") + } + + sql := "SELECT * FROM\n" + arg[0] + + stmt, _, err := db.Prepare(sql) + if err != nil { + return nil, err + } + + var sep string + var str strings.Builder + str.WriteString("CREATE TABLE x(") + outputs := stmt.ColumnCount() + for i := 0; i < outputs; i++ { + name := sqlite3.QuoteIdentifier(stmt.ColumnName(i)) + str.WriteString(sep) + str.WriteString(name) + str.WriteString(" ") + str.WriteString(stmt.ColumnDeclType(i)) + sep = "," + } + inputs := stmt.BindCount() + for i := 1; i <= inputs; i++ { + str.WriteString(sep) + name := stmt.BindName(i) + if name == "" { + str.WriteString("[") + str.WriteString(strconv.Itoa(i)) + str.WriteString("] HIDDEN") + } else { + str.WriteString(sqlite3.QuoteIdentifier(name[1:])) + str.WriteString(" HIDDEN") + } + sep = "," + } + str.WriteByte(')') + + err = db.DeclareVtab(str.String()) + if err != nil { + stmt.Close() + return nil, err + } + + return &table{sql: sql, stmt: stmt}, nil +} + func (t *table) Close() error { return t.stmt.Close() } @@ -120,11 +120,12 @@ func (t *table) BestIndex(idx *sqlite3.IndexInfo) error { return nil } -func (t *table) Open() (_ sqlite3.VTabCursor, err error) { +func (t *table) Open() (sqlite3.VTabCursor, error) { stmt := t.stmt if !t.inuse { t.inuse = true } else { + var err error stmt, _, err = t.stmt.Conn().Prepare(t.sql) if err != nil { return nil, err @@ -186,7 +187,6 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error { func (c *cursor) Next() error { if c.stmt.Step() { c.rowID++ - return nil } return c.stmt.Err() } diff --git a/ext/statement/stmt_test.go b/ext/statement/stmt_test.go index 208edcf..9e9d6a9 100644 --- a/ext/statement/stmt_test.go +++ b/ext/statement/stmt_test.go @@ -95,7 +95,6 @@ func TestRegister(t *testing.T) { t.Errorf("hypot(%d, %d) = %d", x, y, hypot) } } - } func TestRegister_errors(t *testing.T) { diff --git a/internal/util/error.go b/internal/util/error.go index 20f80ac..1f5555f 100644 --- a/internal/util/error.go +++ b/internal/util/error.go @@ -1,7 +1,6 @@ package util import ( - "fmt" "runtime" "strconv" ) @@ -34,14 +33,6 @@ func AssertErr() ErrorString { return ErrorString(msg) } -func Finalizer[T any](skip int) func(*T) { - msg := fmt.Sprintf("sqlite3: %T not closed", new(T)) - if _, file, line, ok := runtime.Caller(skip + 1); ok && skip >= 0 { - msg += " (" + file + ":" + strconv.Itoa(line) + ")" - } - return func(*T) { panic(ErrorString(msg)) } -} - func ErrorCodeString(rc uint32) string { switch rc { case ABORT_ROLLBACK: diff --git a/stmt.go b/stmt.go index 85ac447..e9b80ae 100644 --- a/stmt.go +++ b/stmt.go @@ -81,6 +81,7 @@ func (s *Stmt) Step() bool { r := s.c.call("sqlite3_step", uint64(s.handle)) switch r { case _ROW: + s.err = nil return true case _DONE: s.err = nil diff --git a/value.go b/value.go index 0426aad..e180eb4 100644 --- a/value.go +++ b/value.go @@ -16,6 +16,7 @@ type Value struct { *sqlite handle uint32 unprot bool + copied bool } func (v Value) protected() uint64 { @@ -25,6 +26,30 @@ func (v Value) protected() uint64 { return uint64(v.handle) } +// Dup makes a copy of the SQL value and returns a pointer to that copy. +// +// https://sqlite.org/c3ref/value_dup.html +func (v Value) Dup() *Value { + r := v.call("sqlite3_value_dup", uint64(v.handle)) + return &Value{ + copied: true, + sqlite: v.sqlite, + handle: uint32(r), + } +} + +// Close frees an SQL value previously obtained by [Value.Dup]. +// +// https://sqlite.org/c3ref/value_dup.html +func (dup *Value) Close() error { + if !dup.copied { + panic(util.ValueErr) + } + dup.call("sqlite3_value_free", uint64(dup.handle)) + dup.handle = 0 + return nil +} + // Type returns the initial [Datatype] of the value. // // https://sqlite.org/c3ref/value_blob.html