mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-19 09:04:16 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b631ff1add | ||
|
|
fdfaaa8cec | ||
|
|
6a2827f989 | ||
|
|
9d77322d50 | ||
|
|
c1915feb2e | ||
|
|
52f9af3ca0 | ||
|
|
2f90277165 | ||
|
|
356dd56e5f |
6
.github/workflows/repro.sh
vendored
6
.github/workflows/repro.sh
vendored
@@ -3,13 +3,13 @@ set -euo pipefail
|
||||
|
||||
if [[ "$OSTYPE" == "linux"* ]]; then
|
||||
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-24/wasi-sdk-24.0-x86_64-linux.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_118/binaryen-version_118-x86_64-linux.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_119/binaryen-version_119-x86_64-linux.tar.gz"
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-24/wasi-sdk-24.0-x86_64-macos.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_118/binaryen-version_118-x86_64-macos.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_119/binaryen-version_119-x86_64-macos.tar.gz"
|
||||
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
|
||||
WASI_SDK="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-24/wasi-sdk-24.0-x86_64-windows.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_118/binaryen-version_118-x86_64-windows.tar.gz"
|
||||
BINARYEN="https://github.com/WebAssembly/binaryen/releases/download/version_119/binaryen-version_119-x86_64-windows.tar.gz"
|
||||
fi
|
||||
|
||||
# Download tools
|
||||
|
||||
49
blob.go
49
blob.go
@@ -21,6 +21,8 @@ type Blob struct {
|
||||
bytes int64
|
||||
offset int64
|
||||
handle uint32
|
||||
bufptr uint32
|
||||
buflen int64
|
||||
}
|
||||
|
||||
var _ io.ReadWriteSeeker = &Blob{}
|
||||
@@ -66,7 +68,7 @@ func (b *Blob) Close() error {
|
||||
}
|
||||
|
||||
r := b.c.call("sqlite3_blob_close", uint64(b.handle))
|
||||
|
||||
b.c.free(b.bufptr)
|
||||
b.handle = 0
|
||||
return b.c.error(r)
|
||||
}
|
||||
@@ -86,17 +88,18 @@ func (b *Blob) Read(p []byte) (n int, err error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
avail := b.bytes - b.offset
|
||||
want := int64(len(p))
|
||||
avail := b.bytes - b.offset
|
||||
if want > avail {
|
||||
want = avail
|
||||
}
|
||||
|
||||
defer b.c.arena.mark()()
|
||||
ptr := b.c.arena.new(uint64(want))
|
||||
if want > b.buflen {
|
||||
b.bufptr = b.c.realloc(b.bufptr, uint64(want))
|
||||
b.buflen = want
|
||||
}
|
||||
|
||||
r := b.c.call("sqlite3_blob_read", uint64(b.handle),
|
||||
uint64(ptr), uint64(want), uint64(b.offset))
|
||||
uint64(b.bufptr), uint64(want), uint64(b.offset))
|
||||
err = b.c.error(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -106,7 +109,7 @@ func (b *Blob) Read(p []byte) (n int, err error) {
|
||||
err = io.EOF
|
||||
}
|
||||
|
||||
copy(p, util.View(b.c.mod, ptr, uint64(want)))
|
||||
copy(p, util.View(b.c.mod, b.bufptr, uint64(want)))
|
||||
return int(want), err
|
||||
}
|
||||
|
||||
@@ -123,19 +126,20 @@ func (b *Blob) WriteTo(w io.Writer) (n int64, err error) {
|
||||
if want > avail {
|
||||
want = avail
|
||||
}
|
||||
|
||||
defer b.c.arena.mark()()
|
||||
ptr := b.c.arena.new(uint64(want))
|
||||
if want > b.buflen {
|
||||
b.bufptr = b.c.realloc(b.bufptr, uint64(want))
|
||||
b.buflen = want
|
||||
}
|
||||
|
||||
for want > 0 {
|
||||
r := b.c.call("sqlite3_blob_read", uint64(b.handle),
|
||||
uint64(ptr), uint64(want), uint64(b.offset))
|
||||
uint64(b.bufptr), uint64(want), uint64(b.offset))
|
||||
err = b.c.error(r)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
mem := util.View(b.c.mod, ptr, uint64(want))
|
||||
mem := util.View(b.c.mod, b.bufptr, uint64(want))
|
||||
m, err := w.Write(mem[:want])
|
||||
b.offset += int64(m)
|
||||
n += int64(m)
|
||||
@@ -159,11 +163,15 @@ func (b *Blob) WriteTo(w io.Writer) (n int64, err error) {
|
||||
//
|
||||
// https://sqlite.org/c3ref/blob_write.html
|
||||
func (b *Blob) Write(p []byte) (n int, err error) {
|
||||
defer b.c.arena.mark()()
|
||||
ptr := b.c.arena.bytes(p)
|
||||
want := int64(len(p))
|
||||
if want > b.buflen {
|
||||
b.bufptr = b.c.realloc(b.bufptr, uint64(want))
|
||||
b.buflen = want
|
||||
}
|
||||
util.WriteBytes(b.c.mod, b.bufptr, p)
|
||||
|
||||
r := b.c.call("sqlite3_blob_write", uint64(b.handle),
|
||||
uint64(ptr), uint64(len(p)), uint64(b.offset))
|
||||
uint64(b.bufptr), uint64(want), uint64(b.offset))
|
||||
err = b.c.error(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -187,16 +195,17 @@ func (b *Blob) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
if want < 1 {
|
||||
want = 1
|
||||
}
|
||||
|
||||
defer b.c.arena.mark()()
|
||||
ptr := b.c.arena.new(uint64(want))
|
||||
if want > b.buflen {
|
||||
b.bufptr = b.c.realloc(b.bufptr, uint64(want))
|
||||
b.buflen = want
|
||||
}
|
||||
|
||||
for {
|
||||
mem := util.View(b.c.mod, ptr, uint64(want))
|
||||
mem := util.View(b.c.mod, b.bufptr, uint64(want))
|
||||
m, err := r.Read(mem[:want])
|
||||
if m > 0 {
|
||||
r := b.c.call("sqlite3_blob_write", uint64(b.handle),
|
||||
uint64(ptr), uint64(m), uint64(b.offset))
|
||||
uint64(b.bufptr), uint64(m), uint64(b.offset))
|
||||
err := b.c.error(r)
|
||||
if err != nil {
|
||||
return n, err
|
||||
|
||||
14
config.go
14
config.go
@@ -294,3 +294,17 @@ func autoVacuumCallback(ctx context.Context, mod api.Module, pApp, zSchema, nDbP
|
||||
schema := util.ReadString(mod, zSchema, _MAX_NAME)
|
||||
return uint32(fn(schema, uint(nDbPage), uint(nFreePage), uint(nBytePerPage)))
|
||||
}
|
||||
|
||||
// SoftHeapLimit imposes a soft limit on heap size.
|
||||
//
|
||||
// https://sqlite.org/c3ref/hard_heap_limit64.html
|
||||
func (c *Conn) SoftHeapLimit(n int64) int64 {
|
||||
return int64(c.call("sqlite3_soft_heap_limit64", uint64(n)))
|
||||
}
|
||||
|
||||
// SoftHeapLimit imposes a hard limit on heap size.
|
||||
//
|
||||
// https://sqlite.org/c3ref/hard_heap_limit64.html
|
||||
func (c *Conn) HardHeapLimit(n int64) int64 {
|
||||
return int64(c.call("sqlite3_hard_heap_limit64", uint64(n)))
|
||||
}
|
||||
|
||||
11
const.go
11
const.go
@@ -7,13 +7,10 @@ const (
|
||||
_ROW = 100 /* sqlite3_step() has another row ready */
|
||||
_DONE = 101 /* sqlite3_step() has finished executing */
|
||||
|
||||
_UTF8 = 1
|
||||
|
||||
_MAX_NAME = 1e6 // Self-imposed limit for most NUL terminated strings.
|
||||
_MAX_LENGTH = 1e9
|
||||
_MAX_SQL_LENGTH = 1e9
|
||||
_MAX_ALLOCATION_SIZE = 0x7ffffeff
|
||||
_MAX_FUNCTION_ARG = 100
|
||||
_MAX_NAME = 1e6 // Self-imposed limit for most NUL terminated strings.
|
||||
_MAX_LENGTH = 1e9
|
||||
_MAX_SQL_LENGTH = 1e9
|
||||
_MAX_FUNCTION_ARG = 100
|
||||
|
||||
ptrlen = 4
|
||||
)
|
||||
|
||||
20
context.go
20
context.go
@@ -84,9 +84,8 @@ func (ctx Context) ResultFloat(value float64) {
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultText(value string) {
|
||||
ptr := ctx.c.newString(value)
|
||||
ctx.c.call("sqlite3_result_text64",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(value)),
|
||||
uint64(ctx.c.freer), _UTF8)
|
||||
ctx.c.call("sqlite3_result_text_go",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(value)))
|
||||
}
|
||||
|
||||
// ResultRawText sets the text result of the function to a []byte.
|
||||
@@ -94,9 +93,8 @@ func (ctx Context) ResultText(value string) {
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultRawText(value []byte) {
|
||||
ptr := ctx.c.newBytes(value)
|
||||
ctx.c.call("sqlite3_result_text64",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(value)),
|
||||
uint64(ctx.c.freer), _UTF8)
|
||||
ctx.c.call("sqlite3_result_text_go",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(value)))
|
||||
}
|
||||
|
||||
// ResultBlob sets the result of the function to a []byte.
|
||||
@@ -105,9 +103,8 @@ func (ctx Context) ResultRawText(value []byte) {
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultBlob(value []byte) {
|
||||
ptr := ctx.c.newBytes(value)
|
||||
ctx.c.call("sqlite3_result_blob64",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(value)),
|
||||
uint64(ctx.c.freer))
|
||||
ctx.c.call("sqlite3_result_blob_go",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(value)))
|
||||
}
|
||||
|
||||
// ResultZeroBlob sets the result of the function to a zero-filled, length n BLOB.
|
||||
@@ -154,9 +151,8 @@ func (ctx Context) resultRFC3339Nano(value time.Time) {
|
||||
buf := util.View(ctx.c.mod, ptr, maxlen)
|
||||
buf = value.AppendFormat(buf[:0], time.RFC3339Nano)
|
||||
|
||||
ctx.c.call("sqlite3_result_text64",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(buf)),
|
||||
uint64(ctx.c.freer), _UTF8)
|
||||
ctx.c.call("sqlite3_result_text_go",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(buf)))
|
||||
}
|
||||
|
||||
// ResultPointer sets the result of the function to NULL, just like [Context.ResultNull],
|
||||
|
||||
2
embed/bcw2/.gitignore
vendored
Normal file
2
embed/bcw2/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
build/
|
||||
sqlite/
|
||||
Binary file not shown.
@@ -1,7 +1,4 @@
|
||||
aligned_alloc
|
||||
free
|
||||
malloc
|
||||
malloc_destructor
|
||||
sqlite3_anycollseq_init
|
||||
sqlite3_autovacuum_pages_go
|
||||
sqlite3_backup_finish
|
||||
@@ -9,7 +6,7 @@ sqlite3_backup_init
|
||||
sqlite3_backup_pagecount
|
||||
sqlite3_backup_remaining
|
||||
sqlite3_backup_step
|
||||
sqlite3_bind_blob64
|
||||
sqlite3_bind_blob_go
|
||||
sqlite3_bind_double
|
||||
sqlite3_bind_int64
|
||||
sqlite3_bind_null
|
||||
@@ -17,7 +14,7 @@ sqlite3_bind_parameter_count
|
||||
sqlite3_bind_parameter_index
|
||||
sqlite3_bind_parameter_name
|
||||
sqlite3_bind_pointer_go
|
||||
sqlite3_bind_text64
|
||||
sqlite3_bind_text_go
|
||||
sqlite3_bind_value
|
||||
sqlite3_bind_zeroblob64
|
||||
sqlite3_blob_bytes
|
||||
@@ -74,17 +71,21 @@ sqlite3_filename_database
|
||||
sqlite3_filename_journal
|
||||
sqlite3_filename_wal
|
||||
sqlite3_finalize
|
||||
sqlite3_free
|
||||
sqlite3_get_autocommit
|
||||
sqlite3_get_auxdata
|
||||
sqlite3_hard_heap_limit64
|
||||
sqlite3_interrupt
|
||||
sqlite3_last_insert_rowid
|
||||
sqlite3_limit
|
||||
sqlite3_malloc64
|
||||
sqlite3_open_v2
|
||||
sqlite3_overload_function
|
||||
sqlite3_prepare_v3
|
||||
sqlite3_progress_handler_go
|
||||
sqlite3_realloc64
|
||||
sqlite3_reset
|
||||
sqlite3_result_blob64
|
||||
sqlite3_result_blob_go
|
||||
sqlite3_result_double
|
||||
sqlite3_result_error
|
||||
sqlite3_result_error_code
|
||||
@@ -93,13 +94,14 @@ sqlite3_result_error_toobig
|
||||
sqlite3_result_int64
|
||||
sqlite3_result_null
|
||||
sqlite3_result_pointer_go
|
||||
sqlite3_result_text64
|
||||
sqlite3_result_text_go
|
||||
sqlite3_result_value
|
||||
sqlite3_result_zeroblob64
|
||||
sqlite3_rollback_hook_go
|
||||
sqlite3_set_authorizer_go
|
||||
sqlite3_set_auxdata_go
|
||||
sqlite3_set_last_insert_rowid
|
||||
sqlite3_soft_heap_limit64
|
||||
sqlite3_step
|
||||
sqlite3_stmt_busy
|
||||
sqlite3_stmt_readonly
|
||||
|
||||
Binary file not shown.
@@ -11,11 +11,11 @@ import (
|
||||
|
||||
// Register registers the SQL functions:
|
||||
//
|
||||
// readblob(schema, table, column, rowid, offset, n)
|
||||
// readblob(schema, table, column, rowid, offset, n/writer)
|
||||
//
|
||||
// Reads n bytes of a blob, starting at offset.
|
||||
//
|
||||
// writeblob(schema, table, column, rowid, offset, data)
|
||||
// writeblob(schema, table, column, rowid, offset, data/reader)
|
||||
//
|
||||
// Writes data into a blob, at the given offset.
|
||||
//
|
||||
@@ -27,6 +27,10 @@ import (
|
||||
// using [sqlite3.BindPointer] or [sqlite3.Pointer].
|
||||
// The optional args will be passed to the callback,
|
||||
// along with the [sqlite3.Blob] handle.
|
||||
// The [sqlite3.Blob] handle is only valid during
|
||||
// the execution of the callback. Callers cannot
|
||||
// read or write to the handle after the callback
|
||||
// exits.
|
||||
//
|
||||
// https://sqlite.org/c3ref/blob.html
|
||||
func Register(db *sqlite3.Conn) error {
|
||||
@@ -52,19 +56,24 @@ func readblob(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
return // notest
|
||||
}
|
||||
|
||||
n := arg[5].Int64()
|
||||
if n <= 0 {
|
||||
return
|
||||
if p, ok := arg[5].Pointer().(io.Writer); ok {
|
||||
var n int64
|
||||
n, err = blob.WriteTo(p)
|
||||
ctx.ResultInt64(n)
|
||||
} else {
|
||||
n := arg[5].Int64()
|
||||
if n <= 0 {
|
||||
return
|
||||
}
|
||||
buf := make([]byte, n)
|
||||
_, err = io.ReadFull(blob, buf)
|
||||
ctx.ResultBlob(buf)
|
||||
}
|
||||
buf := make([]byte, n)
|
||||
|
||||
_, err = io.ReadFull(blob, buf)
|
||||
if err != nil {
|
||||
ctx.ResultError(err)
|
||||
return // notest
|
||||
}
|
||||
|
||||
ctx.ResultBlob(buf)
|
||||
setAuxBlob(ctx, blob, false)
|
||||
}
|
||||
|
||||
@@ -82,7 +91,9 @@ func writeblob(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
}
|
||||
|
||||
if p, ok := arg[5].Pointer().(io.Reader); ok {
|
||||
_, err = blob.ReadFrom(p)
|
||||
var n int64
|
||||
n, err = blob.ReadFrom(p)
|
||||
ctx.ResultInt64(n)
|
||||
} else {
|
||||
_, err = blob.Write(arg[5].RawBlob())
|
||||
}
|
||||
|
||||
@@ -34,23 +34,26 @@ func Example() {
|
||||
const message = "Hello BLOB!"
|
||||
|
||||
// Create the BLOB.
|
||||
_, err = db.Exec(`INSERT INTO test VALUES (?)`, sqlite3.ZeroBlob(len(message)))
|
||||
r, err := db.Exec(`INSERT INTO test VALUES (?)`, sqlite3.ZeroBlob(len(message)))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
id, err := r.LastInsertId()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Write the BLOB.
|
||||
_, err = db.Exec(`SELECT writeblob('main', 'test', 'col', last_insert_rowid(), 0, ?)`, message)
|
||||
_, err = db.Exec(`SELECT writeblob('main', 'test', 'col', ?, 0, ?)`,
|
||||
id, message)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Read the BLOB.
|
||||
_, err = db.Exec(`SELECT openblob('main', 'test', 'col', rowid, false, ?) FROM test`,
|
||||
sqlite3.Pointer[blobio.OpenCallback](func(blob *sqlite3.Blob, _ ...sqlite3.Value) error {
|
||||
_, err = blob.WriteTo(os.Stdout)
|
||||
return err
|
||||
}))
|
||||
_, err = db.Exec(`SELECT readblob('main', 'test', 'col', ?, 0, ?)`,
|
||||
id, sqlite3.Pointer(os.Stdout))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@@ -10,7 +10,7 @@ require (
|
||||
github.com/ncruces/sort v0.1.2
|
||||
github.com/psanford/httpreadat v0.1.0
|
||||
github.com/tetratelabs/wazero v1.8.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/crypto v0.27.0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/sys v0.25.0
|
||||
golang.org/x/text v0.18.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -10,8 +10,8 @@ github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIw
|
||||
github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE=
|
||||
github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g=
|
||||
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
|
||||
@@ -5,5 +5,6 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
|
||||
@@ -39,7 +39,7 @@ type mmappedMemory struct {
|
||||
func (m *mmappedMemory) Reallocate(size uint64) []byte {
|
||||
com := uint64(len(m.buf))
|
||||
res := uint64(cap(m.buf))
|
||||
if com < size && size < res {
|
||||
if com < size && size <= res {
|
||||
// Round up to the page size.
|
||||
rnd := uint64(unix.Getpagesize() - 1)
|
||||
new := (size + rnd) &^ rnd
|
||||
|
||||
@@ -48,7 +48,7 @@ type virtualMemory struct {
|
||||
func (m *virtualMemory) Reallocate(size uint64) []byte {
|
||||
com := uint64(len(m.buf))
|
||||
res := uint64(cap(m.buf))
|
||||
if com < size && size < res {
|
||||
if com < size && size <= res {
|
||||
// Round up to the page size.
|
||||
rnd := uint64(windows.Getpagesize() - 1)
|
||||
new := (size + rnd) &^ rnd
|
||||
|
||||
27
sqlite.go
27
sqlite.go
@@ -86,7 +86,6 @@ type sqlite struct {
|
||||
mask uint32
|
||||
}
|
||||
stack [9]uint64
|
||||
freer uint32
|
||||
}
|
||||
|
||||
func instantiateSQLite() (sqlt *sqlite, err error) {
|
||||
@@ -102,14 +101,7 @@ func instantiateSQLite() (sqlt *sqlite, err error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
global := sqlt.mod.ExportedGlobal("malloc_destructor")
|
||||
if global == nil {
|
||||
return nil, util.BadBinaryErr
|
||||
}
|
||||
|
||||
sqlt.freer = util.ReadUint32(sqlt.mod, uint32(global.Get()))
|
||||
if sqlt.freer == 0 {
|
||||
if sqlt.getfn("sqlite3_progress_handler_go") == nil {
|
||||
return nil, util.BadBinaryErr
|
||||
}
|
||||
return sqlt, nil
|
||||
@@ -196,14 +188,19 @@ func (sqlt *sqlite) free(ptr uint32) {
|
||||
if ptr == 0 {
|
||||
return
|
||||
}
|
||||
sqlt.call("free", uint64(ptr))
|
||||
sqlt.call("sqlite3_free", uint64(ptr))
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) new(size uint64) uint32 {
|
||||
if size > _MAX_ALLOCATION_SIZE {
|
||||
ptr := uint32(sqlt.call("sqlite3_malloc64", size))
|
||||
if ptr == 0 && size != 0 {
|
||||
panic(util.OOMErr)
|
||||
}
|
||||
ptr := uint32(sqlt.call("malloc", size))
|
||||
return ptr
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) realloc(ptr uint32, size uint64) uint32 {
|
||||
ptr = uint32(sqlt.call("sqlite3_realloc64", uint64(ptr), size))
|
||||
if ptr == 0 && size != 0 {
|
||||
panic(util.OOMErr)
|
||||
}
|
||||
@@ -214,7 +211,11 @@ func (sqlt *sqlite) newBytes(b []byte) uint32 {
|
||||
if (*[0]byte)(b) == nil {
|
||||
return 0
|
||||
}
|
||||
ptr := sqlt.new(uint64(len(b)))
|
||||
size := len(b)
|
||||
if size == 0 {
|
||||
size = 1
|
||||
}
|
||||
ptr := sqlt.new(uint64(size))
|
||||
util.WriteBytes(sqlt.mod, ptr, b)
|
||||
return ptr
|
||||
}
|
||||
|
||||
13
sqlite3/bind.c
Normal file
13
sqlite3/bind.c
Normal file
@@ -0,0 +1,13 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
||||
int sqlite3_bind_text_go(sqlite3_stmt* stmt, int i, const char* zData,
|
||||
sqlite3_uint64 nData) {
|
||||
return sqlite3_bind_text64(stmt, i, zData, nData, &sqlite3_free, SQLITE_UTF8);
|
||||
}
|
||||
|
||||
int sqlite3_bind_blob_go(sqlite3_stmt* stmt, int i, const char* zData,
|
||||
sqlite3_uint64 nData) {
|
||||
return sqlite3_bind_blob64(stmt, i, zData, nData, &sqlite3_free);
|
||||
}
|
||||
@@ -9,16 +9,16 @@
|
||||
#include "ext/series.c"
|
||||
#include "ext/uint.c"
|
||||
// Bindings
|
||||
#include "bind.c"
|
||||
#include "column.c"
|
||||
#include "func.c"
|
||||
#include "hooks.c"
|
||||
#include "pointer.c"
|
||||
#include "result.c"
|
||||
#include "time.c"
|
||||
#include "vfs.c"
|
||||
#include "vtab.c"
|
||||
|
||||
sqlite3_destructor_type malloc_destructor = &free;
|
||||
|
||||
__attribute__((constructor)) void init() {
|
||||
sqlite3_initialize();
|
||||
sqlite3_auto_extension((void (*)(void))sqlite3_base_init);
|
||||
|
||||
13
sqlite3/result.c
Normal file
13
sqlite3/result.c
Normal file
@@ -0,0 +1,13 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
||||
void sqlite3_result_text_go(sqlite3_context* ctx, const char* zData,
|
||||
sqlite3_uint64 nData) {
|
||||
sqlite3_result_text64(ctx, zData, nData, &sqlite3_free, SQLITE_UTF8);
|
||||
}
|
||||
|
||||
void sqlite3_result_blob_go(sqlite3_context* ctx, const void* zData,
|
||||
sqlite3_uint64 nData) {
|
||||
sqlite3_result_blob64(ctx, zData, nData, &sqlite3_free);
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#define SQLITE_DQS 0
|
||||
#define SQLITE_THREADSAFE 0
|
||||
#define SQLITE_DEFAULT_MEMSTATUS 0
|
||||
#define SQLITE_DEFAULT_WAL_SYNCHRONOUS 1
|
||||
#define SQLITE_LIKE_DOESNT_MATCH_BLOBS
|
||||
#define SQLITE_MAX_EXPR_DEPTH 0
|
||||
@@ -13,6 +12,7 @@
|
||||
#define SQLITE_OMIT_AUTOINIT
|
||||
|
||||
// We need these:
|
||||
// #define SQLITE_DEFAULT_MEMSTATUS 0
|
||||
// #define SQLITE_OMIT_DECLTYPE
|
||||
// #define SQLITE_OMIT_PROGRESS_CALLBACK
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package sqlite3
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
@@ -39,7 +38,7 @@ func Test_sqlite_call_closed(t *testing.T) {
|
||||
sqlite.close()
|
||||
|
||||
defer func() { _ = recover() }()
|
||||
sqlite.call("free")
|
||||
sqlite.call("sqlite3_free")
|
||||
t.Error("want panic")
|
||||
}
|
||||
|
||||
@@ -57,19 +56,6 @@ func Test_sqlite_new(t *testing.T) {
|
||||
sqlite.new(math.MaxUint32)
|
||||
t.Error("want panic")
|
||||
})
|
||||
|
||||
t.Run("_MAX_ALLOCATION_SIZE", func(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
if os.Getenv("CI") != "" {
|
||||
t.Skip("skipping in CI")
|
||||
}
|
||||
defer func() { _ = recover() }()
|
||||
sqlite.new(_MAX_ALLOCATION_SIZE)
|
||||
sqlite.new(_MAX_ALLOCATION_SIZE)
|
||||
t.Error("want panic")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_sqlite_newArena(t *testing.T) {
|
||||
|
||||
20
stmt.go
20
stmt.go
@@ -246,10 +246,9 @@ func (s *Stmt) BindText(param int, value string) error {
|
||||
return TOOBIG
|
||||
}
|
||||
ptr := s.c.newString(value)
|
||||
r := s.c.call("sqlite3_bind_text64",
|
||||
r := s.c.call("sqlite3_bind_text_go",
|
||||
uint64(s.handle), uint64(param),
|
||||
uint64(ptr), uint64(len(value)),
|
||||
uint64(s.c.freer), _UTF8)
|
||||
uint64(ptr), uint64(len(value)))
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
@@ -262,10 +261,9 @@ func (s *Stmt) BindRawText(param int, value []byte) error {
|
||||
return TOOBIG
|
||||
}
|
||||
ptr := s.c.newBytes(value)
|
||||
r := s.c.call("sqlite3_bind_text64",
|
||||
r := s.c.call("sqlite3_bind_text_go",
|
||||
uint64(s.handle), uint64(param),
|
||||
uint64(ptr), uint64(len(value)),
|
||||
uint64(s.c.freer), _UTF8)
|
||||
uint64(ptr), uint64(len(value)))
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
@@ -279,10 +277,9 @@ func (s *Stmt) BindBlob(param int, value []byte) error {
|
||||
return TOOBIG
|
||||
}
|
||||
ptr := s.c.newBytes(value)
|
||||
r := s.c.call("sqlite3_bind_blob64",
|
||||
r := s.c.call("sqlite3_bind_blob_go",
|
||||
uint64(s.handle), uint64(param),
|
||||
uint64(ptr), uint64(len(value)),
|
||||
uint64(s.c.freer))
|
||||
uint64(ptr), uint64(len(value)))
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
@@ -335,10 +332,9 @@ func (s *Stmt) bindRFC3339Nano(param int, value time.Time) error {
|
||||
buf := util.View(s.c.mod, ptr, maxlen)
|
||||
buf = value.AppendFormat(buf[:0], time.RFC3339Nano)
|
||||
|
||||
r := s.c.call("sqlite3_bind_text64",
|
||||
r := s.c.call("sqlite3_bind_text_go",
|
||||
uint64(s.handle), uint64(param),
|
||||
uint64(ptr), uint64(len(buf)),
|
||||
uint64(s.c.freer), _UTF8)
|
||||
uint64(ptr), uint64(len(buf)))
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
|
||||
427
tests/config_test.go
Normal file
427
tests/config_test.go
Normal file
@@ -0,0 +1,427 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
"github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
func TestConn_Config(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
o, err := db.Config(sqlite3.DBCONFIG_DEFENSIVE)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != false {
|
||||
t.Error("want false")
|
||||
}
|
||||
|
||||
o, err = db.Config(sqlite3.DBCONFIG_DEFENSIVE, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != true {
|
||||
t.Error("want true")
|
||||
}
|
||||
|
||||
o, err = db.Config(sqlite3.DBCONFIG_DEFENSIVE)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != true {
|
||||
t.Error("want true")
|
||||
}
|
||||
|
||||
o, err = db.Config(sqlite3.DBCONFIG_DEFENSIVE, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != false {
|
||||
t.Error("want false")
|
||||
}
|
||||
|
||||
o, err = db.Config(sqlite3.DBCONFIG_DEFENSIVE)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != false {
|
||||
t.Error("want false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_ConfigLog(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var code sqlite3.ExtendedErrorCode
|
||||
err = db.ConfigLog(func(c sqlite3.ExtendedErrorCode, msg string) {
|
||||
t.Log(msg)
|
||||
code = c
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
db.Prepare(`SELECT * FRM sqlite_schema`)
|
||||
|
||||
if code != sqlite3.ExtendedErrorCode(sqlite3.ERROR) {
|
||||
t.Error("want sqlite3.ERROR")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_FileControl(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
file := filepath.Join(t.TempDir(), "test.db")
|
||||
db, err := sqlite3.Open(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
t.Run("MISUSE", func(t *testing.T) {
|
||||
_, err := db.FileControl("main", 0)
|
||||
if !errors.Is(err, sqlite3.MISUSE) {
|
||||
t.Errorf("got %v, want MISUSE", err)
|
||||
}
|
||||
})
|
||||
t.Run("FCNTL_RESET_CACHE", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_RESET_CACHE)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != nil {
|
||||
t.Errorf("got %v, want nil", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_PERSIST_WAL", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_PERSIST_WAL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != false {
|
||||
t.Errorf("got %v, want false", o)
|
||||
}
|
||||
|
||||
o, err = db.FileControl("", sqlite3.FCNTL_PERSIST_WAL, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != true {
|
||||
t.Errorf("got %v, want true", o)
|
||||
}
|
||||
|
||||
o, err = db.FileControl("", sqlite3.FCNTL_PERSIST_WAL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != true {
|
||||
t.Errorf("got %v, want true", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_CHUNK_SIZE", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_CHUNK_SIZE, 1024*1024)
|
||||
if !errors.Is(err, sqlite3.NOTFOUND) {
|
||||
t.Errorf("got %v, want NOTFOUND", err)
|
||||
}
|
||||
if o != nil {
|
||||
t.Errorf("got %v, want nil", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_RESERVE_BYTES", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_RESERVE_BYTES, 4)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != 0 {
|
||||
t.Errorf("got %v, want 0", o)
|
||||
}
|
||||
|
||||
o, err = db.FileControl("", sqlite3.FCNTL_RESERVE_BYTES)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != 4 {
|
||||
t.Errorf("got %v, want 4", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_DATA_VERSION", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_DATA_VERSION)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != uint32(2) {
|
||||
t.Errorf("got %v, want 2", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_VFS_POINTER", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_VFS_POINTER)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != vfs.Find("os") {
|
||||
t.Errorf("got %v, want os", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_FILE_POINTER", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_FILE_POINTER)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := o.(vfs.File); !ok {
|
||||
t.Errorf("got %v, want File", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_JOURNAL_POINTER", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_JOURNAL_POINTER)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != nil {
|
||||
t.Errorf("got %v, want nil", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_LOCKSTATE", func(t *testing.T) {
|
||||
if !vfs.SupportsFileLocking {
|
||||
t.Skip("skipping without locks")
|
||||
}
|
||||
|
||||
txn, err := db.BeginExclusive()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.End(&err)
|
||||
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_LOCKSTATE)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != vfs.LOCK_EXCLUSIVE {
|
||||
t.Errorf("got %v, want LOCK_EXCLUSIVE", o)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestConn_Limit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
l := db.Limit(sqlite3.LIMIT_COLUMN, -1)
|
||||
if l != 2000 {
|
||||
t.Errorf("got %d, want 2000", l)
|
||||
}
|
||||
|
||||
l = db.Limit(sqlite3.LIMIT_COLUMN, 100)
|
||||
if l != 2000 {
|
||||
t.Errorf("got %d, want 2000", l)
|
||||
}
|
||||
|
||||
l = db.Limit(sqlite3.LIMIT_COLUMN, -1)
|
||||
if l != 100 {
|
||||
t.Errorf("got %d, want 100", l)
|
||||
}
|
||||
|
||||
l = db.Limit(math.MaxUint32, -1)
|
||||
if l != -1 {
|
||||
t.Errorf("got %d, want -1", l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_SetAuthorizer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.SetAuthorizer(func(action sqlite3.AuthorizerActionCode, name3rd, name4th, schema, nameInner string) sqlite3.AuthorizerReturnCode {
|
||||
if action != sqlite3.AUTH_PRAGMA {
|
||||
t.Errorf("got %v, want PRAGMA", action)
|
||||
}
|
||||
if name3rd != "busy_timeout" {
|
||||
t.Errorf("got %q, want busy_timeout", name3rd)
|
||||
}
|
||||
if name4th != "5000" {
|
||||
t.Errorf("got %q, want 5000", name4th)
|
||||
}
|
||||
if schema != "main" {
|
||||
t.Errorf("got %q, want main", schema)
|
||||
}
|
||||
return sqlite3.AUTH_DENY
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`PRAGMA main.busy_timeout=5000`)
|
||||
if !errors.Is(err, sqlite3.AUTH) {
|
||||
t.Errorf("got %v, want sqlite3.AUTH", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_Trace(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows := 0
|
||||
closed := false
|
||||
err = db.Trace(math.MaxUint32, func(evt sqlite3.TraceEvent, a1 any, a2 any) error {
|
||||
switch evt {
|
||||
case sqlite3.TRACE_CLOSE:
|
||||
closed = true
|
||||
_ = a1.(*sqlite3.Conn)
|
||||
return db.Exec(`PRAGMA optimize`)
|
||||
case sqlite3.TRACE_STMT:
|
||||
stmt := a1.(*sqlite3.Stmt)
|
||||
if sql := a2.(string); sql != stmt.SQL() {
|
||||
t.Errorf("got %q, want %q", sql, stmt.SQL())
|
||||
}
|
||||
if sql := stmt.ExpandedSQL(); sql != `SELECT 1` {
|
||||
t.Errorf("got %q", sql)
|
||||
}
|
||||
case sqlite3.TRACE_PROFILE:
|
||||
_ = a1.(*sqlite3.Stmt)
|
||||
if ns := a2.(int64); ns < 0 {
|
||||
t.Errorf("got %d", ns)
|
||||
}
|
||||
case sqlite3.TRACE_ROW:
|
||||
_ = a1.(*sqlite3.Stmt)
|
||||
if a2 != nil {
|
||||
t.Errorf("got %v", a2)
|
||||
}
|
||||
rows++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, _, err := db.Prepare(`SELECT ?`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = stmt.BindInt(1, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = stmt.Exec()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rows != 1 {
|
||||
t.Error("want 1")
|
||||
}
|
||||
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !closed {
|
||||
t.Error("want closed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_AutoVacuumPages(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t, url.Values{
|
||||
"_pragma": {"auto_vacuum(full)"},
|
||||
})
|
||||
|
||||
db, err := sqlite3.Open(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.AutoVacuumPages(func(schema string, dbPages, freePages, bytesPerPage uint) uint {
|
||||
return freePages
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE TABLE test (col)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`INSERT INTO test VALUES (zeroblob(1024*1024))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`DROP TABLE test`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_memoryLimit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
n := db.HardHeapLimit(-1)
|
||||
if n != 0 {
|
||||
t.Fatal("want zero")
|
||||
}
|
||||
|
||||
const limit = 64 * 1024 * 1024
|
||||
|
||||
n = db.SoftHeapLimit(limit)
|
||||
if n != 0 {
|
||||
t.Fatal("want zero")
|
||||
}
|
||||
|
||||
n = db.SoftHeapLimit(-1)
|
||||
if n != limit {
|
||||
t.Fatal("want", limit)
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,6 @@ package tests
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -13,8 +11,6 @@ import (
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
"github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
@@ -265,358 +261,6 @@ func TestConn_Prepare_invalid(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_Config(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
o, err := db.Config(sqlite3.DBCONFIG_DEFENSIVE)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != false {
|
||||
t.Error("want false")
|
||||
}
|
||||
|
||||
o, err = db.Config(sqlite3.DBCONFIG_DEFENSIVE, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != true {
|
||||
t.Error("want true")
|
||||
}
|
||||
|
||||
o, err = db.Config(sqlite3.DBCONFIG_DEFENSIVE)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != true {
|
||||
t.Error("want true")
|
||||
}
|
||||
|
||||
o, err = db.Config(sqlite3.DBCONFIG_DEFENSIVE, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != false {
|
||||
t.Error("want false")
|
||||
}
|
||||
|
||||
o, err = db.Config(sqlite3.DBCONFIG_DEFENSIVE)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != false {
|
||||
t.Error("want false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_ConfigLog(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var code sqlite3.ExtendedErrorCode
|
||||
err = db.ConfigLog(func(c sqlite3.ExtendedErrorCode, msg string) {
|
||||
t.Log(msg)
|
||||
code = c
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
db.Prepare(`SELECT * FRM sqlite_schema`)
|
||||
|
||||
if code != sqlite3.ExtendedErrorCode(sqlite3.ERROR) {
|
||||
t.Error("want sqlite3.ERROR")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_FileControl(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
file := filepath.Join(t.TempDir(), "test.db")
|
||||
db, err := sqlite3.Open(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
t.Run("MISUSE", func(t *testing.T) {
|
||||
_, err := db.FileControl("main", 0)
|
||||
if !errors.Is(err, sqlite3.MISUSE) {
|
||||
t.Errorf("got %v, want MISUSE", err)
|
||||
}
|
||||
})
|
||||
t.Run("FCNTL_RESET_CACHE", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_RESET_CACHE)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != nil {
|
||||
t.Errorf("got %v, want nil", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_PERSIST_WAL", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_PERSIST_WAL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != false {
|
||||
t.Errorf("got %v, want false", o)
|
||||
}
|
||||
|
||||
o, err = db.FileControl("", sqlite3.FCNTL_PERSIST_WAL, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != true {
|
||||
t.Errorf("got %v, want true", o)
|
||||
}
|
||||
|
||||
o, err = db.FileControl("", sqlite3.FCNTL_PERSIST_WAL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != true {
|
||||
t.Errorf("got %v, want true", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_CHUNK_SIZE", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_CHUNK_SIZE, 1024*1024)
|
||||
if !errors.Is(err, sqlite3.NOTFOUND) {
|
||||
t.Errorf("got %v, want NOTFOUND", err)
|
||||
}
|
||||
if o != nil {
|
||||
t.Errorf("got %v, want nil", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_RESERVE_BYTES", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_RESERVE_BYTES, 4)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != 0 {
|
||||
t.Errorf("got %v, want 0", o)
|
||||
}
|
||||
|
||||
o, err = db.FileControl("", sqlite3.FCNTL_RESERVE_BYTES)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != 4 {
|
||||
t.Errorf("got %v, want 4", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_DATA_VERSION", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_DATA_VERSION)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != uint32(2) {
|
||||
t.Errorf("got %v, want 2", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_VFS_POINTER", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_VFS_POINTER)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != vfs.Find("os") {
|
||||
t.Errorf("got %v, want os", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_FILE_POINTER", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_FILE_POINTER)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := o.(vfs.File); !ok {
|
||||
t.Errorf("got %v, want File", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_JOURNAL_POINTER", func(t *testing.T) {
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_JOURNAL_POINTER)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != nil {
|
||||
t.Errorf("got %v, want nil", o)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FCNTL_LOCKSTATE", func(t *testing.T) {
|
||||
if !vfs.SupportsFileLocking {
|
||||
t.Skip("skipping without locks")
|
||||
}
|
||||
|
||||
txn, err := db.BeginExclusive()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer txn.End(&err)
|
||||
|
||||
o, err := db.FileControl("", sqlite3.FCNTL_LOCKSTATE)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if o != vfs.LOCK_EXCLUSIVE {
|
||||
t.Errorf("got %v, want LOCK_EXCLUSIVE", o)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestConn_Limit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
l := db.Limit(sqlite3.LIMIT_COLUMN, -1)
|
||||
if l != 2000 {
|
||||
t.Errorf("got %d, want 2000", l)
|
||||
}
|
||||
|
||||
l = db.Limit(sqlite3.LIMIT_COLUMN, 100)
|
||||
if l != 2000 {
|
||||
t.Errorf("got %d, want 2000", l)
|
||||
}
|
||||
|
||||
l = db.Limit(sqlite3.LIMIT_COLUMN, -1)
|
||||
if l != 100 {
|
||||
t.Errorf("got %d, want 100", l)
|
||||
}
|
||||
|
||||
l = db.Limit(math.MaxUint32, -1)
|
||||
if l != -1 {
|
||||
t.Errorf("got %d, want -1", l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_SetAuthorizer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.SetAuthorizer(func(action sqlite3.AuthorizerActionCode, name3rd, name4th, schema, nameInner string) sqlite3.AuthorizerReturnCode {
|
||||
if action != sqlite3.AUTH_PRAGMA {
|
||||
t.Errorf("got %v, want PRAGMA", action)
|
||||
}
|
||||
if name3rd != "busy_timeout" {
|
||||
t.Errorf("got %q, want busy_timeout", name3rd)
|
||||
}
|
||||
if name4th != "5000" {
|
||||
t.Errorf("got %q, want 5000", name4th)
|
||||
}
|
||||
if schema != "main" {
|
||||
t.Errorf("got %q, want main", schema)
|
||||
}
|
||||
return sqlite3.AUTH_DENY
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`PRAGMA main.busy_timeout=5000`)
|
||||
if !errors.Is(err, sqlite3.AUTH) {
|
||||
t.Errorf("got %v, want sqlite3.AUTH", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_Trace(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows := 0
|
||||
closed := false
|
||||
err = db.Trace(math.MaxUint32, func(evt sqlite3.TraceEvent, a1 any, a2 any) error {
|
||||
switch evt {
|
||||
case sqlite3.TRACE_CLOSE:
|
||||
closed = true
|
||||
_ = a1.(*sqlite3.Conn)
|
||||
return db.Exec(`PRAGMA optimize`)
|
||||
case sqlite3.TRACE_STMT:
|
||||
stmt := a1.(*sqlite3.Stmt)
|
||||
if sql := a2.(string); sql != stmt.SQL() {
|
||||
t.Errorf("got %q, want %q", sql, stmt.SQL())
|
||||
}
|
||||
if sql := stmt.ExpandedSQL(); sql != `SELECT 1` {
|
||||
t.Errorf("got %q", sql)
|
||||
}
|
||||
case sqlite3.TRACE_PROFILE:
|
||||
_ = a1.(*sqlite3.Stmt)
|
||||
if ns := a2.(int64); ns < 0 {
|
||||
t.Errorf("got %d", ns)
|
||||
}
|
||||
case sqlite3.TRACE_ROW:
|
||||
_ = a1.(*sqlite3.Stmt)
|
||||
if a2 != nil {
|
||||
t.Errorf("got %v", a2)
|
||||
}
|
||||
rows++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stmt, _, err := db.Prepare(`SELECT ?`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = stmt.BindInt(1, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = stmt.Exec()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = stmt.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rows != 1 {
|
||||
t.Error("want 1")
|
||||
}
|
||||
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !closed {
|
||||
t.Error("want closed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_ReleaseMemory(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -727,41 +371,6 @@ func TestConn_DBName(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_AutoVacuumPages(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmp := memdb.TestDB(t, url.Values{
|
||||
"_pragma": {"auto_vacuum(full)"},
|
||||
})
|
||||
|
||||
db, err := sqlite3.Open(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.AutoVacuumPages(func(schema string, dbPages, freePages, bytesPerPage uint) uint {
|
||||
return freePages
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`CREATE TABLE test (col)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`INSERT INTO test VALUES (zeroblob(1024*1024))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`DROP TABLE test`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConn_Status(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
4
vfs/tests/mptest/testdata/mptest.wasm.bz2
vendored
4
vfs/tests/mptest/testdata/mptest.wasm.bz2
vendored
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:42858f7755a42e64913c5119dcd5ecb3fbceac71e13952d4e8b62a97c05311b5
|
||||
size 476253
|
||||
oid sha256:4b2ca8d3b914990e5c3486e039a6a4160b36c8d6db6c95e2c6c6619d69d26e1d
|
||||
size 476258
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cf4fb9d930647e29f0da71981b6b1e204e4949c816e5f7d6aeded6a90ad9e8ab
|
||||
size 489516
|
||||
oid sha256:259a3d0311302a0ea1d577a8209425f0dbf1ec3a77e214dbafa0e2c1a30723d3
|
||||
size 489479
|
||||
|
||||
@@ -356,7 +356,7 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl
|
||||
out = err.Error()
|
||||
}
|
||||
if out != "" {
|
||||
fn := mod.ExportedFunction("malloc")
|
||||
fn := mod.ExportedFunction("sqlite3_malloc64")
|
||||
stack := [...]uint64{uint64(len(out) + 1)}
|
||||
if err := fn.CallWithStack(ctx, stack[:]); err != nil {
|
||||
panic(err)
|
||||
|
||||
Reference in New Issue
Block a user