status-go/vendor/zombiezen.com/go/sqlite/blob.go

320 lines
8.6 KiB
Go

// Copyright (c) 2018 David Crawshaw <david@zentus.com>
// Copyright (c) 2021 Ross Light <ross@zombiezen.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// SPDX-License-Identifier: ISC
package sqlite
import (
"errors"
"fmt"
"io"
"unsafe"
"modernc.org/libc"
lib "modernc.org/sqlite/lib"
)
const blobBufSize = 4096
var (
mainCString = mustCString("main")
tempCString = mustCString("temp")
)
// OpenBlob opens a blob in a particular {database,table,column,row}.
//
// https://www.sqlite.org/c3ref/blob_open.html
func (c *Conn) OpenBlob(dbn, table, column string, row int64, write bool) (*Blob, error) {
if c == nil {
return nil, fmt.Errorf("sqlite: open blob %q.%q: nil connection", table, column)
}
return c.openBlob(dbn, table, column, row, write)
}
func (c *Conn) openBlob(dbn, table, column string, row int64, write bool) (_ *Blob, err error) {
var cdb uintptr
switch dbn {
case "", "main":
cdb = mainCString
case "temp":
cdb = tempCString
default:
var err error
cdb, err = libc.CString(dbn)
if err != nil {
return nil, fmt.Errorf("sqlite: open blob %q.%q: %w", table, column, err)
}
defer libc.Xfree(c.tls, cdb)
}
var writeFlag int32
if write {
writeFlag = 1
}
buf, err := malloc(c.tls, blobBufSize)
if err != nil {
return nil, fmt.Errorf("sqlite: open blob %q.%q: %w", table, column, err)
}
defer func() {
if err != nil {
libc.Xfree(c.tls, buf)
}
}()
ctable, err := libc.CString(table)
if err != nil {
return nil, fmt.Errorf("sqlite: open blob %q.%q: %w", table, column, err)
}
defer libc.Xfree(c.tls, ctable)
ccolumn, err := libc.CString(column)
if err != nil {
return nil, fmt.Errorf("sqlite: open blob %q.%q: %w", table, column, err)
}
defer libc.Xfree(c.tls, ccolumn)
blobPtrPtr, err := malloc(c.tls, ptrSize)
if err != nil {
return nil, fmt.Errorf("sqlite: open blob %q.%q: %w", table, column, err)
}
defer libc.Xfree(c.tls, blobPtrPtr)
for {
if err := c.interrupted(); err != nil {
return nil, fmt.Errorf("sqlite: open blob %q.%q: %w", table, column, err)
}
res := ResultCode(lib.Xsqlite3_blob_open(
c.tls,
c.conn,
cdb,
ctable,
ccolumn,
row,
writeFlag,
blobPtrPtr,
))
switch res {
case ResultLockedSharedCache:
if err := reserr(waitForUnlockNotify(c.tls, c.conn, c.unlockNote)); err != nil {
return nil, fmt.Errorf("sqlite: open blob %q.%q: %w", table, column, err)
}
// loop
case ResultOK:
blobPtr := *(*uintptr)(unsafe.Pointer(blobPtrPtr))
return &Blob{
conn: c,
blob: blobPtr,
buf: buf,
size: lib.Xsqlite3_blob_bytes(c.tls, blobPtr),
}, nil
default:
return nil, fmt.Errorf("sqlite: open blob %q.%q: %w", table, column, c.extreserr(res))
}
}
}
// Blob provides streaming access to SQLite blobs.
type Blob struct {
conn *Conn
blob uintptr
buf uintptr
off int32
size int32
}
func (blob *Blob) bufSlice() []byte {
return libc.GoBytes(blob.buf, blobBufSize)
}
// Read reads up to len(p) bytes from the blob into p.
// https://www.sqlite.org/c3ref/blob_read.html
func (blob *Blob) Read(p []byte) (int, error) {
if blob.blob == 0 {
return 0, fmt.Errorf("sqlite: read blob: %w", errInvalidBlob)
}
if blob.off >= blob.size {
return 0, io.EOF
}
if err := blob.conn.interrupted(); err != nil {
return 0, fmt.Errorf("sqlite: read blob: %w", err)
}
if rem := blob.size - blob.off; len(p) > int(rem) {
p = p[:rem]
}
fullLen := len(p)
for len(p) > 0 {
nn := int32(blobBufSize)
if int(nn) > len(p) {
nn = int32(len(p))
}
res := ResultCode(lib.Xsqlite3_blob_read(blob.conn.tls, blob.blob, blob.buf, nn, blob.off))
if err := reserr(res); err != nil {
return fullLen - len(p), fmt.Errorf("sqlite: read blob: %w", err)
}
copy(p, blob.bufSlice()[:int(nn)])
p = p[nn:]
blob.off += nn
}
return fullLen, nil
}
// WriteTo copies the blob to w until there's no more data to write or
// an error occurs.
func (blob *Blob) WriteTo(w io.Writer) (n int64, err error) {
if blob.blob == 0 {
return 0, fmt.Errorf("sqlite: read blob: %w", errInvalidBlob)
}
if blob.off >= blob.size {
return 0, nil
}
if err := blob.conn.interrupted(); err != nil {
return 0, fmt.Errorf("sqlite: read blob: %w", err)
}
for blob.off < blob.size {
buf := blob.bufSlice()
if remaining := int(blob.size - blob.off); len(buf) > remaining {
buf = buf[:remaining]
}
res := ResultCode(lib.Xsqlite3_blob_read(blob.conn.tls, blob.blob, blob.buf, int32(len(buf)), blob.off))
if err := reserr(res); err != nil {
return n, fmt.Errorf("sqlite: read blob: %w", err)
}
nn, err := w.Write(buf)
blob.off += int32(nn)
n += int64(nn)
if err != nil {
return n, err
}
}
return n, nil
}
// Write writes len(p) from p to the blob.
// https://www.sqlite.org/c3ref/blob_write.html
func (blob *Blob) Write(p []byte) (int, error) {
if blob.blob == 0 {
return 0, fmt.Errorf("sqlite: write blob: %w", errInvalidBlob)
}
if err := blob.conn.interrupted(); err != nil {
return 0, fmt.Errorf("sqlite: write blob: %w", err)
}
fullLen := len(p)
for len(p) > 0 {
nn := copy(blob.bufSlice(), p)
res := ResultCode(lib.Xsqlite3_blob_write(blob.conn.tls, blob.blob, blob.buf, int32(nn), blob.off))
if err := reserr(res); err != nil {
return fullLen - len(p), fmt.Errorf("sqlite: write blob: %w", err)
}
p = p[nn:]
blob.off += int32(nn)
}
return fullLen, nil
}
// WriteString writes s to the blob.
// https://www.sqlite.org/c3ref/blob_write.html
func (blob *Blob) WriteString(s string) (int, error) {
if blob.blob == 0 {
return 0, fmt.Errorf("sqlite: write blob: %w", errInvalidBlob)
}
if err := blob.conn.interrupted(); err != nil {
return 0, fmt.Errorf("sqlite: write blob: %w", err)
}
fullLen := len(s)
for len(s) > 0 {
nn := copy(blob.bufSlice(), s)
res := ResultCode(lib.Xsqlite3_blob_write(blob.conn.tls, blob.blob, blob.buf, int32(nn), blob.off))
if err := reserr(res); err != nil {
return fullLen - len(s), fmt.Errorf("sqlite: write blob: %w", err)
}
s = s[nn:]
blob.off += int32(nn)
}
return fullLen, nil
}
// ReadFrom copies data from r to the blob until EOF or error.
func (blob *Blob) ReadFrom(r io.Reader) (n int64, err error) {
if blob.blob == 0 {
return 0, fmt.Errorf("sqlite: write blob: %w", errInvalidBlob)
}
if err := blob.conn.interrupted(); err != nil {
return 0, fmt.Errorf("sqlite: write blob: %w", err)
}
for {
nn, err := r.Read(blob.bufSlice())
if nn > 0 {
res := ResultCode(lib.Xsqlite3_blob_write(blob.conn.tls, blob.blob, blob.buf, int32(nn), blob.off))
if err := reserr(res); err != nil {
return n, fmt.Errorf("sqlite: write blob: %w", err)
}
n += int64(nn)
blob.off += int32(nn)
}
if err != nil {
if err == io.EOF {
err = nil
}
return n, err
}
}
}
// Seek sets the offset for the next Read or Write and returns the offset.
// Seeking past the end of the blob returns an error.
func (blob *Blob) Seek(offset int64, whence int) (int64, error) {
switch whence {
case io.SeekStart:
// use offset directly
case io.SeekCurrent:
offset += int64(blob.off)
case io.SeekEnd:
offset += int64(blob.size)
default:
return int64(blob.off), fmt.Errorf("sqlite: seek blob: invalid whence %d", whence)
}
if offset < 0 {
return int64(blob.off), fmt.Errorf("sqlite: seek blob: negative offset %d", offset)
}
if offset > int64(blob.size) {
return int64(blob.off), fmt.Errorf("sqlite: seek blob: offset %d is past size %d", offset, blob.size)
}
blob.off = int32(offset)
return offset, nil
}
// Size returns the number of bytes in the blob.
func (blob *Blob) Size() int64 {
return int64(blob.size)
}
// Close releases any resources associated with the blob handle.
// https://www.sqlite.org/c3ref/blob_close.html
func (blob *Blob) Close() error {
if blob.blob == 0 {
return errInvalidBlob
}
libc.Xfree(blob.conn.tls, blob.buf)
blob.buf = 0
res := ResultCode(lib.Xsqlite3_blob_close(blob.conn.tls, blob.blob))
blob.blob = 0
if err := reserr(res); err != nil {
return fmt.Errorf("sqlite: close blob: %w", err)
}
return nil
}
var errInvalidBlob = errors.New("invalid blob")
// TODO: Blob Reopen