mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-20 09:34:14 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6226c3b31 | ||
|
|
bed2ee7674 | ||
|
|
7e6d178122 | ||
|
|
f360c77a78 |
112
blob.go
112
blob.go
@@ -86,14 +86,14 @@ func (b *Blob) Read(p []byte) (n int, err error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
want := int64(len(p))
|
||||
avail := b.bytes - b.offset
|
||||
want := int64(len(p))
|
||||
if want > avail {
|
||||
want = avail
|
||||
}
|
||||
|
||||
ptr := b.c.new(uint64(want))
|
||||
defer b.c.free(ptr)
|
||||
defer b.c.arena.reset()
|
||||
ptr := b.c.arena.new(uint64(want))
|
||||
|
||||
r := b.c.call(b.c.api.blobRead, uint64(b.handle),
|
||||
uint64(ptr), uint64(want), uint64(b.offset))
|
||||
@@ -101,30 +101,68 @@ func (b *Blob) Read(p []byte) (n int, err error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
mem := util.View(b.c.mod, ptr, uint64(want))
|
||||
copy(p, mem)
|
||||
b.offset += want
|
||||
if b.offset >= b.bytes {
|
||||
err = io.EOF
|
||||
}
|
||||
|
||||
copy(p, util.View(b.c.mod, ptr, uint64(want)))
|
||||
return int(want), err
|
||||
}
|
||||
|
||||
// WriteTo implements the [io.WriterTo] interface.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_read.html
|
||||
func (b *Blob) WriteTo(w io.Writer) (n int64, err error) {
|
||||
if b.offset >= b.bytes {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
avail := b.bytes - b.offset
|
||||
want := int64(65536)
|
||||
if want > avail {
|
||||
want = avail
|
||||
}
|
||||
|
||||
ptr := b.c.new(uint64(want))
|
||||
defer b.c.free(ptr)
|
||||
|
||||
for want > 0 {
|
||||
r := b.c.call(b.c.api.blobRead, uint64(b.handle),
|
||||
uint64(ptr), uint64(want), uint64(b.offset))
|
||||
err = b.c.error(r[0])
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
mem := util.View(b.c.mod, ptr, uint64(want))
|
||||
m, err := w.Write(mem[:want])
|
||||
b.offset += int64(m)
|
||||
n += int64(m)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if int64(m) != want {
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
|
||||
avail = b.bytes - b.offset
|
||||
if want > avail {
|
||||
want = avail
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Write implements the [io.Writer] interface.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_write.html
|
||||
func (b *Blob) Write(p []byte) (n int, err error) {
|
||||
offset := b.offset
|
||||
if offset > b.bytes {
|
||||
offset = b.bytes
|
||||
}
|
||||
|
||||
ptr := b.c.newBytes(p)
|
||||
defer b.c.free(ptr)
|
||||
defer b.c.arena.reset()
|
||||
ptr := b.c.arena.bytes(p)
|
||||
|
||||
r := b.c.call(b.c.api.blobWrite, uint64(b.handle),
|
||||
uint64(ptr), uint64(len(p)), uint64(offset))
|
||||
uint64(ptr), uint64(len(p)), uint64(b.offset))
|
||||
err = b.c.error(r[0])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -133,6 +171,52 @@ func (b *Blob) Write(p []byte) (n int, err error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// ReadFrom implements the [io.ReaderFrom] interface.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_write.html
|
||||
func (b *Blob) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
avail := b.bytes - b.offset
|
||||
want := int64(65536)
|
||||
if want > avail {
|
||||
want = avail
|
||||
}
|
||||
if want < 1 {
|
||||
want = 1
|
||||
}
|
||||
|
||||
ptr := b.c.new(uint64(want))
|
||||
defer b.c.free(ptr)
|
||||
|
||||
for {
|
||||
mem := util.View(b.c.mod, ptr, uint64(want))
|
||||
m, err := r.Read(mem[:want])
|
||||
if m > 0 {
|
||||
r := b.c.call(b.c.api.blobWrite, uint64(b.handle),
|
||||
uint64(ptr), uint64(m), uint64(b.offset))
|
||||
err := b.c.error(r[0])
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
b.offset += int64(m)
|
||||
n += int64(m)
|
||||
}
|
||||
if err == io.EOF {
|
||||
return n, nil
|
||||
}
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
avail = b.bytes - b.offset
|
||||
if want > avail {
|
||||
want = avail
|
||||
}
|
||||
if want < 1 {
|
||||
want = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Seek implements the [io.Seeker] interface.
|
||||
func (b *Blob) Seek(offset int64, whence int) (int64, error) {
|
||||
switch whence {
|
||||
|
||||
2
go.mod
2
go.mod
@@ -4,7 +4,7 @@ go 1.19
|
||||
|
||||
require (
|
||||
github.com/ncruces/julianday v0.1.5
|
||||
github.com/tetratelabs/wazero v1.0.2
|
||||
github.com/tetratelabs/wazero v1.0.3
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/sys v0.7.0
|
||||
)
|
||||
|
||||
4
go.sum
4
go.sum
@@ -1,7 +1,7 @@
|
||||
github.com/ncruces/julianday v0.1.5 h1:hDJ9ejiMp3DHsoZ5KW4c1lwfMjbARS7u/gbYcd0FBZk=
|
||||
github.com/ncruces/julianday v0.1.5/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/tetratelabs/wazero v1.0.2 h1:lpwL5zczFHk2mxKur98035Gig+Z3vd9JURk6lUdZxXY=
|
||||
github.com/tetratelabs/wazero v1.0.2/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
|
||||
github.com/tetratelabs/wazero v1.0.3 h1:IWmaxc/5vKg71DE+c0SLjjLFAA3u3tD/Zegpgif2Wpo=
|
||||
github.com/tetratelabs/wazero v1.0.3/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package vfs
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -7,7 +7,10 @@ import (
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
func registerFunc1[T0, TR ~uint32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0) TR) {
|
||||
type i32 interface{ ~int32 | ~uint32 }
|
||||
type i64 interface{ ~int64 | ~uint64 }
|
||||
|
||||
func RegisterFuncII[TR, T0 i32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0) TR) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(api.GoModuleFunc(
|
||||
func(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
@@ -17,7 +20,7 @@ func registerFunc1[T0, TR ~uint32](mod wazero.HostModuleBuilder, name string, fn
|
||||
Export(name)
|
||||
}
|
||||
|
||||
func registerFunc2[T0, T1, TR ~uint32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1) TR) {
|
||||
func RegisterFuncIII[TR, T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1) TR) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(api.GoModuleFunc(
|
||||
func(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
@@ -27,7 +30,7 @@ func registerFunc2[T0, T1, TR ~uint32](mod wazero.HostModuleBuilder, name string
|
||||
Export(name)
|
||||
}
|
||||
|
||||
func registerFunc3[T0, T1, T2, TR ~uint32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1, _ T2) TR) {
|
||||
func RegisterFuncIIII[TR, T0, T1, T2 i32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1, _ T2) TR) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(api.GoModuleFunc(
|
||||
func(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
@@ -37,7 +40,7 @@ func registerFunc3[T0, T1, T2, TR ~uint32](mod wazero.HostModuleBuilder, name st
|
||||
Export(name)
|
||||
}
|
||||
|
||||
func registerFunc4[T0, T1, T2, T3, TR ~uint32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1, _ T2, _ T3) TR) {
|
||||
func RegisterFuncIIIII[TR, T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1, _ T2, _ T3) TR) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(api.GoModuleFunc(
|
||||
func(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
@@ -47,7 +50,7 @@ func registerFunc4[T0, T1, T2, T3, TR ~uint32](mod wazero.HostModuleBuilder, nam
|
||||
Export(name)
|
||||
}
|
||||
|
||||
func registerFunc5[T0, T1, T2, T3, T4, TR ~uint32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1, _ T2, _ T3, _ T4) TR) {
|
||||
func RegisterFuncIIIIII[TR, T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1, _ T2, _ T3, _ T4) TR) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(api.GoModuleFunc(
|
||||
func(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
@@ -57,21 +60,21 @@ func registerFunc5[T0, T1, T2, T3, T4, TR ~uint32](mod wazero.HostModuleBuilder,
|
||||
Export(name)
|
||||
}
|
||||
|
||||
func registerFuncRW(mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _, _, _ uint32, _ int64) _ErrorCode) {
|
||||
func RegisterFuncIIIIJ[TR, T0, T1, T2 i32, T3 i64](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1, _ T2, _ T3) TR) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(api.GoModuleFunc(
|
||||
func(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
stack[0] = uint64(fn(ctx, mod, uint32(stack[0]), uint32(stack[1]), uint32(stack[2]), int64(stack[3])))
|
||||
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3])))
|
||||
}),
|
||||
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}).
|
||||
Export(name)
|
||||
}
|
||||
|
||||
func registerFuncT(mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ uint32, _ int64) _ErrorCode) {
|
||||
func RegisterFuncIIJ[TR, T0 i32, T1 i64](mod wazero.HostModuleBuilder, name string, fn func(ctx context.Context, mod api.Module, _ T0, _ T1) TR) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(api.GoModuleFunc(
|
||||
func(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
stack[0] = uint64(fn(ctx, mod, uint32(stack[0]), int64(stack[1])))
|
||||
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1])))
|
||||
}),
|
||||
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}).
|
||||
Export(name)
|
||||
@@ -40,7 +40,8 @@ func init() {
|
||||
|
||||
rt = wazero.NewRuntime(ctx)
|
||||
wasi_snapshot_preview1.MustInstantiate(ctx, rt)
|
||||
env := vfs.NewEnvModuleBuilder(rt)
|
||||
|
||||
env := vfs.Export(rt.NewHostModuleBuilder("env"))
|
||||
env.NewFunctionBuilder().WithFunc(system).Export("system")
|
||||
_, err := env.Instantiate(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -35,7 +35,7 @@ func init() {
|
||||
|
||||
rt = wazero.NewRuntime(ctx)
|
||||
wasi_snapshot_preview1.MustInstantiate(ctx, rt)
|
||||
env := vfs.NewEnvModuleBuilder(rt)
|
||||
env := vfs.Export(rt.NewHostModuleBuilder("env"))
|
||||
_, err := env.Instantiate(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -17,37 +17,28 @@ import (
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
func Instantiate(ctx context.Context, r wazero.Runtime) {
|
||||
env := NewEnvModuleBuilder(r)
|
||||
_, err := env.Instantiate(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func NewEnvModuleBuilder(r wazero.Runtime) wazero.HostModuleBuilder {
|
||||
env := r.NewHostModuleBuilder("env")
|
||||
registerFuncT(env, "os_localtime", vfsLocaltime)
|
||||
registerFunc3(env, "os_randomness", vfsRandomness)
|
||||
registerFunc2(env, "os_sleep", vfsSleep)
|
||||
registerFunc2(env, "os_current_time", vfsCurrentTime)
|
||||
registerFunc2(env, "os_current_time_64", vfsCurrentTime64)
|
||||
registerFunc4(env, "os_full_pathname", vfsFullPathname)
|
||||
registerFunc3(env, "os_delete", vfsDelete)
|
||||
registerFunc4(env, "os_access", vfsAccess)
|
||||
registerFunc5(env, "os_open", vfsOpen)
|
||||
registerFunc1(env, "os_close", vfsClose)
|
||||
registerFuncRW(env, "os_read", vfsRead)
|
||||
registerFuncRW(env, "os_write", vfsWrite)
|
||||
registerFuncT(env, "os_truncate", vfsTruncate)
|
||||
registerFunc2(env, "os_sync", vfsSync)
|
||||
registerFunc2(env, "os_file_size", vfsFileSize)
|
||||
registerFunc3(env, "os_file_control", vfsFileControl)
|
||||
registerFunc1(env, "os_sector_size", vfsSectorSize)
|
||||
registerFunc1(env, "os_device_characteristics", vfsDeviceCharacteristics)
|
||||
registerFunc2(env, "os_lock", vfsLock)
|
||||
registerFunc2(env, "os_unlock", vfsUnlock)
|
||||
registerFunc2(env, "os_check_reserved_lock", vfsCheckReservedLock)
|
||||
func Export(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
|
||||
util.RegisterFuncIIJ(env, "os_localtime", vfsLocaltime)
|
||||
util.RegisterFuncIIII(env, "os_randomness", vfsRandomness)
|
||||
util.RegisterFuncIII(env, "os_sleep", vfsSleep)
|
||||
util.RegisterFuncIII(env, "os_current_time", vfsCurrentTime)
|
||||
util.RegisterFuncIII(env, "os_current_time_64", vfsCurrentTime64)
|
||||
util.RegisterFuncIIIII(env, "os_full_pathname", vfsFullPathname)
|
||||
util.RegisterFuncIIII(env, "os_delete", vfsDelete)
|
||||
util.RegisterFuncIIIII(env, "os_access", vfsAccess)
|
||||
util.RegisterFuncIIIIII(env, "os_open", vfsOpen)
|
||||
util.RegisterFuncII(env, "os_close", vfsClose)
|
||||
util.RegisterFuncIIIIJ(env, "os_read", vfsRead)
|
||||
util.RegisterFuncIIIIJ(env, "os_write", vfsWrite)
|
||||
util.RegisterFuncIIJ(env, "os_truncate", vfsTruncate)
|
||||
util.RegisterFuncIII(env, "os_sync", vfsSync)
|
||||
util.RegisterFuncIII(env, "os_file_size", vfsFileSize)
|
||||
util.RegisterFuncIIII(env, "os_file_control", vfsFileControl)
|
||||
util.RegisterFuncII(env, "os_sector_size", vfsSectorSize)
|
||||
util.RegisterFuncII(env, "os_device_characteristics", vfsDeviceCharacteristics)
|
||||
util.RegisterFuncIII(env, "os_lock", vfsLock)
|
||||
util.RegisterFuncIII(env, "os_unlock", vfsUnlock)
|
||||
util.RegisterFuncIII(env, "os_check_reserved_lock", vfsCheckReservedLock)
|
||||
return env
|
||||
}
|
||||
|
||||
|
||||
16
module.go
16
module.go
@@ -52,7 +52,12 @@ func instantiateModule() (*module, error) {
|
||||
func compileModule() {
|
||||
ctx := context.Background()
|
||||
sqlite3.runtime = wazero.NewRuntime(ctx)
|
||||
vfs.Instantiate(ctx, sqlite3.runtime)
|
||||
|
||||
env := vfs.Export(sqlite3.runtime.NewHostModuleBuilder("env"))
|
||||
_, sqlite3.err = env.Instantiate(ctx)
|
||||
if sqlite3.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
bin := Binary
|
||||
if bin == nil && Path != "" {
|
||||
@@ -288,6 +293,15 @@ func (a *arena) new(size uint64) uint32 {
|
||||
return ptr
|
||||
}
|
||||
|
||||
func (a *arena) bytes(b []byte) uint32 {
|
||||
if b == nil {
|
||||
return 0
|
||||
}
|
||||
ptr := a.new(uint64(len(b)))
|
||||
util.WriteBytes(a.m.mod, ptr, b)
|
||||
return ptr
|
||||
}
|
||||
|
||||
func (a *arena) string(s string) uint32 {
|
||||
ptr := a.new(uint64(len(s) + 1))
|
||||
util.WriteString(a.m.mod, ptr, s)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/adler32"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
@@ -48,17 +49,17 @@ func TestBlob(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = io.Copy(blob, bytes.NewReader(data[:size/2]))
|
||||
_, err = blob.Write(data[:size/2])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = io.Copy(blob, bytes.NewReader(data[:]))
|
||||
if !errors.Is(err, sqlite3.ERROR) {
|
||||
t.Fatal("want error")
|
||||
n, err := blob.Write(data[:])
|
||||
if n != 0 || !errors.Is(err, sqlite3.ERROR) {
|
||||
t.Fatalf("got (%d, %v), want (0, ERROR)", n, err)
|
||||
}
|
||||
|
||||
_, err = io.Copy(blob, bytes.NewReader(data[size/2:size]))
|
||||
_, err = blob.Write(data[size/2 : size])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -87,6 +88,126 @@ func TestBlob(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlob_large(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`INSERT INTO test VALUES (zeroblob(1000000))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
blob, err := db.OpenBlob("main", "test", "col", db.LastInsertRowID(), true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer blob.Close()
|
||||
|
||||
size := blob.Size()
|
||||
if size != 1000000 {
|
||||
t.Errorf("got %d, want 1000000", size)
|
||||
}
|
||||
|
||||
hash := adler32.New()
|
||||
_, err = io.CopyN(blob, io.TeeReader(rand.Reader, hash), 1000000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = blob.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want := hash.Sum32()
|
||||
hash.Reset()
|
||||
_, err = io.Copy(hash, blob)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if got := hash.Sum32(); got != want {
|
||||
t.Fatalf("got %d, want %d", got, want)
|
||||
}
|
||||
|
||||
if err := blob.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlob_overflow(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, err := sqlite3.Open(":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
err = db.Exec(`CREATE TABLE IF NOT EXISTS test (col)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Exec(`INSERT INTO test VALUES (zeroblob(1024))`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
blob, err := db.OpenBlob("main", "test", "col", db.LastInsertRowID(), true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer blob.Close()
|
||||
|
||||
n, err := blob.ReadFrom(rand.Reader)
|
||||
if n != 1024 || !errors.Is(err, sqlite3.ERROR) {
|
||||
t.Fatalf("got (%d, %v), want (0, ERROR)", n, err)
|
||||
}
|
||||
|
||||
n, err = blob.ReadFrom(rand.Reader)
|
||||
if n != 0 || !errors.Is(err, sqlite3.ERROR) {
|
||||
t.Fatalf("got (%d, %v), want (0, ERROR)", n, err)
|
||||
}
|
||||
|
||||
_, err = blob.Seek(-128, io.SeekEnd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n, err = blob.WriteTo(io.Discard)
|
||||
if n != 128 || err != nil {
|
||||
t.Fatalf("got (%d, %v), want (128, nil)", n, err)
|
||||
}
|
||||
|
||||
n, err = blob.WriteTo(io.Discard)
|
||||
if n != 0 || err != nil {
|
||||
t.Fatalf("got (%d, %v), want (0, nil)", n, err)
|
||||
}
|
||||
|
||||
if err := blob.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlob_invalid(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
4
tx.go
4
tx.go
@@ -69,7 +69,7 @@ func (tx Tx) End(errp *error) {
|
||||
defer panic(recovered)
|
||||
}
|
||||
|
||||
if (errp == nil || *errp == nil) && recovered == nil {
|
||||
if *errp == nil && recovered == nil {
|
||||
// Success path.
|
||||
if tx.c.GetAutocommit() { // There is nothing to commit.
|
||||
return
|
||||
@@ -155,7 +155,7 @@ func (s Savepoint) Release(errp *error) {
|
||||
defer panic(recovered)
|
||||
}
|
||||
|
||||
if (errp == nil || *errp == nil) && recovered == nil {
|
||||
if *errp == nil && recovered == nil {
|
||||
// Success path.
|
||||
if s.c.GetAutocommit() { // There is nothing to commit.
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user