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}