package sqlite3 import ( "io" "github.com/ncruces/go-sqlite3/internal/util" ) // ZeroBlob represents a zero-filled, length n BLOB // that can be used as an argument to // [database/sql.DB.Exec] and similar methods. type ZeroBlob int64 // Blob is an handle to an open BLOB. // // It implements [io.ReadWriteSeeker] for incremental BLOB I/O. // // https://sqlite.org/c3ref/blob.html type Blob struct { c *Conn bytes int64 offset int64 handle uint32 } var _ io.ReadWriteSeeker = &Blob{} // OpenBlob opens a BLOB for incremental I/O. // // https://sqlite.org/c3ref/blob_open.html func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob, error) { c.checkInterrupt() defer c.arena.mark()() blobPtr := c.arena.new(ptrlen) dbPtr := c.arena.string(db) tablePtr := c.arena.string(table) columnPtr := c.arena.string(column) var flags uint64 if write { flags = 1 } r := c.call("sqlite3_blob_open", uint64(c.handle), uint64(dbPtr), uint64(tablePtr), uint64(columnPtr), uint64(row), flags, uint64(blobPtr)) if err := c.error(r); err != nil { return nil, err } blob := Blob{c: c} blob.handle = util.ReadUint32(c.mod, blobPtr) blob.bytes = int64(c.call("sqlite3_blob_bytes", uint64(blob.handle))) return &blob, nil } // Close closes a BLOB handle. // // It is safe to close a nil, zero or closed Blob. // // https://sqlite.org/c3ref/blob_close.html func (b *Blob) Close() error { if b == nil || b.handle == 0 { return nil } r := b.c.call("sqlite3_blob_close", uint64(b.handle)) b.handle = 0 return b.c.error(r) } // Size returns the size of the BLOB in bytes. // // https://sqlite.org/c3ref/blob_bytes.html func (b *Blob) Size() int64 { return b.bytes } // Read implements the [io.Reader] interface. // // https://sqlite.org/c3ref/blob_read.html func (b *Blob) Read(p []byte) (n int, err error) { if b.offset >= b.bytes { return 0, io.EOF } avail := b.bytes - b.offset want := int64(len(p)) if want > avail { want = avail } defer b.c.arena.mark()() ptr := b.c.arena.new(uint64(want)) r := b.c.call("sqlite3_blob_read", uint64(b.handle), uint64(ptr), uint64(want), uint64(b.offset)) err = b.c.error(r) if err != nil { return 0, err } 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://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 } want := int64(1024 * 1024) avail := b.bytes - b.offset if want > avail { want = avail } defer b.c.arena.mark()() ptr := b.c.arena.new(uint64(want)) for want > 0 { r := b.c.call("sqlite3_blob_read", uint64(b.handle), uint64(ptr), 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)) 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://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) r := b.c.call("sqlite3_blob_write", uint64(b.handle), uint64(ptr), uint64(len(p)), uint64(b.offset)) err = b.c.error(r) if err != nil { return 0, err } b.offset += int64(len(p)) return len(p), nil } // ReadFrom implements the [io.ReaderFrom] interface. // // https://sqlite.org/c3ref/blob_write.html func (b *Blob) ReadFrom(r io.Reader) (n int64, err error) { want := int64(1024 * 1024) avail := b.bytes - b.offset if l, ok := r.(*io.LimitedReader); ok && want > l.N { want = l.N } if want > avail { want = avail } if want < 1 { want = 1 } defer b.c.arena.mark()() ptr := b.c.arena.new(uint64(want)) for { mem := util.View(b.c.mod, ptr, 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)) err := b.c.error(r) 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 { default: return 0, util.WhenceErr case io.SeekStart: break case io.SeekCurrent: offset += b.offset case io.SeekEnd: offset += b.bytes } if offset < 0 { return 0, util.OffsetErr } b.offset = offset return offset, nil } // Reopen moves a BLOB handle to a new row of the same database table. // // https://sqlite.org/c3ref/blob_reopen.html func (b *Blob) Reopen(row int64) error { err := b.c.error(b.c.call("sqlite3_blob_reopen", uint64(b.handle), uint64(row))) b.bytes = int64(b.c.call("sqlite3_blob_bytes", uint64(b.handle))) b.offset = 0 return err }