blob.go

  1package sqlite3
  2
  3import (
  4	"io"
  5
  6	"github.com/ncruces/go-sqlite3/internal/util"
  7)
  8
  9// ZeroBlob represents a zero-filled, length n BLOB
 10// that can be used as an argument to
 11// [database/sql.DB.Exec] and similar methods.
 12type ZeroBlob int64
 13
 14// Blob is an handle to an open BLOB.
 15//
 16// It implements [io.ReadWriteSeeker] for incremental BLOB I/O.
 17//
 18// https://sqlite.org/c3ref/blob.html
 19type Blob struct {
 20	c      *Conn
 21	bytes  int64
 22	offset int64
 23	handle ptr_t
 24	bufptr ptr_t
 25	buflen int64
 26}
 27
 28var _ io.ReadWriteSeeker = &Blob{}
 29
 30// OpenBlob opens a BLOB for incremental I/O.
 31//
 32// https://sqlite.org/c3ref/blob_open.html
 33func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob, error) {
 34	if c.interrupt.Err() != nil {
 35		return nil, INTERRUPT
 36	}
 37
 38	defer c.arena.mark()()
 39	blobPtr := c.arena.new(ptrlen)
 40	dbPtr := c.arena.string(db)
 41	tablePtr := c.arena.string(table)
 42	columnPtr := c.arena.string(column)
 43
 44	var flags int32
 45	if write {
 46		flags = 1
 47	}
 48
 49	rc := res_t(c.call("sqlite3_blob_open", stk_t(c.handle),
 50		stk_t(dbPtr), stk_t(tablePtr), stk_t(columnPtr),
 51		stk_t(row), stk_t(flags), stk_t(blobPtr)))
 52
 53	if err := c.error(rc); err != nil {
 54		return nil, err
 55	}
 56
 57	blob := Blob{c: c}
 58	blob.handle = util.Read32[ptr_t](c.mod, blobPtr)
 59	blob.bytes = int64(int32(c.call("sqlite3_blob_bytes", stk_t(blob.handle))))
 60	return &blob, nil
 61}
 62
 63// Close closes a BLOB handle.
 64//
 65// It is safe to close a nil, zero or closed Blob.
 66//
 67// https://sqlite.org/c3ref/blob_close.html
 68func (b *Blob) Close() error {
 69	if b == nil || b.handle == 0 {
 70		return nil
 71	}
 72
 73	rc := res_t(b.c.call("sqlite3_blob_close", stk_t(b.handle)))
 74	b.c.free(b.bufptr)
 75	b.handle = 0
 76	return b.c.error(rc)
 77}
 78
 79// Size returns the size of the BLOB in bytes.
 80//
 81// https://sqlite.org/c3ref/blob_bytes.html
 82func (b *Blob) Size() int64 {
 83	return b.bytes
 84}
 85
 86// Read implements the [io.Reader] interface.
 87//
 88// https://sqlite.org/c3ref/blob_read.html
 89func (b *Blob) Read(p []byte) (n int, err error) {
 90	if b.offset >= b.bytes {
 91		return 0, io.EOF
 92	}
 93
 94	want := int64(len(p))
 95	avail := b.bytes - b.offset
 96	if want > avail {
 97		want = avail
 98	}
 99	if want > b.buflen {
100		b.bufptr = b.c.realloc(b.bufptr, want)
101		b.buflen = want
102	}
103
104	rc := res_t(b.c.call("sqlite3_blob_read", stk_t(b.handle),
105		stk_t(b.bufptr), stk_t(want), stk_t(b.offset)))
106	err = b.c.error(rc)
107	if err != nil {
108		return 0, err
109	}
110	b.offset += want
111	if b.offset >= b.bytes {
112		err = io.EOF
113	}
114
115	copy(p, util.View(b.c.mod, b.bufptr, want))
116	return int(want), err
117}
118
119// WriteTo implements the [io.WriterTo] interface.
120//
121// https://sqlite.org/c3ref/blob_read.html
122func (b *Blob) WriteTo(w io.Writer) (n int64, err error) {
123	if b.offset >= b.bytes {
124		return 0, nil
125	}
126
127	want := int64(1024 * 1024)
128	avail := b.bytes - b.offset
129	if want > avail {
130		want = avail
131	}
132	if want > b.buflen {
133		b.bufptr = b.c.realloc(b.bufptr, want)
134		b.buflen = want
135	}
136
137	for want > 0 {
138		rc := res_t(b.c.call("sqlite3_blob_read", stk_t(b.handle),
139			stk_t(b.bufptr), stk_t(want), stk_t(b.offset)))
140		err = b.c.error(rc)
141		if err != nil {
142			return n, err
143		}
144
145		mem := util.View(b.c.mod, b.bufptr, want)
146		m, err := w.Write(mem[:want])
147		b.offset += int64(m)
148		n += int64(m)
149		if err != nil {
150			return n, err
151		}
152		if int64(m) != want {
153			// notest // Write misbehaving
154			return n, io.ErrShortWrite
155		}
156
157		avail = b.bytes - b.offset
158		if want > avail {
159			want = avail
160		}
161	}
162	return n, nil
163}
164
165// Write implements the [io.Writer] interface.
166//
167// https://sqlite.org/c3ref/blob_write.html
168func (b *Blob) Write(p []byte) (n int, err error) {
169	want := int64(len(p))
170	if want > b.buflen {
171		b.bufptr = b.c.realloc(b.bufptr, want)
172		b.buflen = want
173	}
174	util.WriteBytes(b.c.mod, b.bufptr, p)
175
176	rc := res_t(b.c.call("sqlite3_blob_write", stk_t(b.handle),
177		stk_t(b.bufptr), stk_t(want), stk_t(b.offset)))
178	err = b.c.error(rc)
179	if err != nil {
180		return 0, err
181	}
182	b.offset += int64(len(p))
183	return len(p), nil
184}
185
186// ReadFrom implements the [io.ReaderFrom] interface.
187//
188// https://sqlite.org/c3ref/blob_write.html
189func (b *Blob) ReadFrom(r io.Reader) (n int64, err error) {
190	want := int64(1024 * 1024)
191	avail := b.bytes - b.offset
192	if l, ok := r.(*io.LimitedReader); ok && want > l.N {
193		want = l.N
194	}
195	if want > avail {
196		want = avail
197	}
198	if want < 1 {
199		want = 1
200	}
201	if want > b.buflen {
202		b.bufptr = b.c.realloc(b.bufptr, want)
203		b.buflen = want
204	}
205
206	for {
207		mem := util.View(b.c.mod, b.bufptr, want)
208		m, err := r.Read(mem[:want])
209		if m > 0 {
210			rc := res_t(b.c.call("sqlite3_blob_write", stk_t(b.handle),
211				stk_t(b.bufptr), stk_t(m), stk_t(b.offset)))
212			err := b.c.error(rc)
213			if err != nil {
214				return n, err
215			}
216			b.offset += int64(m)
217			n += int64(m)
218		}
219		if err == io.EOF {
220			return n, nil
221		}
222		if err != nil {
223			return n, err
224		}
225
226		avail = b.bytes - b.offset
227		if want > avail {
228			want = avail
229		}
230		if want < 1 {
231			want = 1
232		}
233	}
234}
235
236// Seek implements the [io.Seeker] interface.
237func (b *Blob) Seek(offset int64, whence int) (int64, error) {
238	switch whence {
239	default:
240		return 0, util.WhenceErr
241	case io.SeekStart:
242		break
243	case io.SeekCurrent:
244		offset += b.offset
245	case io.SeekEnd:
246		offset += b.bytes
247	}
248	if offset < 0 {
249		return 0, util.OffsetErr
250	}
251	b.offset = offset
252	return offset, nil
253}
254
255// Reopen moves a BLOB handle to a new row of the same database table.
256//
257// https://sqlite.org/c3ref/blob_reopen.html
258func (b *Blob) Reopen(row int64) error {
259	if b.c.interrupt.Err() != nil {
260		return INTERRUPT
261	}
262	err := b.c.error(res_t(b.c.call("sqlite3_blob_reopen", stk_t(b.handle), stk_t(row))))
263	b.bytes = int64(int32(b.c.call("sqlite3_blob_bytes", stk_t(b.handle))))
264	b.offset = 0
265	return err
266}