Skip to content

Commit c357e0c

Browse files
committed
Add Blob I/O
1 parent c8a1143 commit c357e0c

File tree

2 files changed

+188
-0
lines changed

2 files changed

+188
-0
lines changed

blob_io.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright (C) 2022 Yasuhiro Matsumoto <[email protected]>.
2+
//
3+
// Use of this source code is governed by an MIT-style
4+
// license that can be found in the LICENSE file.
5+
6+
package sqlite3
7+
8+
/*
9+
#ifndef USE_LIBSQLITE3
10+
#include "sqlite3-binding.h"
11+
#else
12+
#include <sqlite3.h>
13+
#endif
14+
#include <stdlib.h>
15+
*/
16+
import "C"
17+
18+
import (
19+
"io"
20+
"runtime"
21+
"unsafe"
22+
)
23+
24+
// SQLiteBlob implements the SQLite Blob I/O interface.
25+
type SQLiteBlob struct {
26+
conn *SQLiteConn
27+
blob *C.sqlite3_blob
28+
size int
29+
offs int
30+
}
31+
32+
// Blob opens a blob.
33+
//
34+
// The flag parameter is ignored.
35+
func (conn *SQLiteConn) Blob(database, table, column string, rowid int64, flags int) (*SQLiteBlob, error) {
36+
databaseptr := C.CString(database)
37+
defer C.free(unsafe.Pointer(databaseptr))
38+
39+
tableptr := C.CString(table)
40+
defer C.free(unsafe.Pointer(tableptr))
41+
42+
columnptr := C.CString(column)
43+
defer C.free(unsafe.Pointer(columnptr))
44+
45+
var blob *C.sqlite3_blob
46+
ret := C.sqlite3_blob_open(conn.db, databaseptr, tableptr, columnptr, C.longlong(rowid), C.int(flags), &blob)
47+
48+
if ret == C.SQLITE_OK {
49+
size := int(C.sqlite3_blob_bytes(blob))
50+
bb := &SQLiteBlob{conn, blob, size, 0}
51+
52+
runtime.SetFinalizer(bb, (*SQLiteBlob).Close)
53+
54+
return bb, nil
55+
}
56+
57+
return nil, conn.lastError()
58+
}
59+
60+
// Read implements the io.Reader interface.
61+
func (s *SQLiteBlob) Read(b []byte) (n int, err error) {
62+
if s.offs >= s.size {
63+
return 0, io.EOF
64+
}
65+
66+
n = s.size - s.offs
67+
if len(b) < n {
68+
n = len(b)
69+
}
70+
71+
p := &b[0]
72+
ret := C.sqlite3_blob_read(s.blob, unsafe.Pointer(p), C.int(n), C.int(s.offs))
73+
if ret != C.SQLITE_OK {
74+
return 0, s.conn.lastError()
75+
}
76+
77+
s.offs += n
78+
79+
return n, nil
80+
}
81+
82+
// Close implements the io.Closer interface.
83+
func (s *SQLiteBlob) Close() error {
84+
ret := C.sqlite3_blob_close(s.blob)
85+
86+
s.blob = nil
87+
runtime.SetFinalizer(s, nil)
88+
89+
if ret != C.SQLITE_OK {
90+
return s.conn.lastError()
91+
}
92+
93+
return nil
94+
}

blob_io_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright (C) 2022 Yasuhiro Matsumoto <[email protected]>.
2+
//
3+
// Use of this source code is governed by an MIT-style
4+
// license that can be found in the LICENSE file.
5+
6+
//go:build cgo
7+
// +build cgo
8+
9+
package sqlite3
10+
11+
import (
12+
"bytes"
13+
"database/sql"
14+
"io"
15+
"testing"
16+
)
17+
18+
func TestBlobIO(t *testing.T) {
19+
driverName := "sqlite3_TestBlobIO2"
20+
var driverConn *SQLiteConn
21+
sql.Register(driverName, &SQLiteDriver{
22+
ConnectHook: func(conn *SQLiteConn) error {
23+
driverConn = conn
24+
return nil
25+
}})
26+
27+
db, err := sql.Open(driverName, ":memory:")
28+
if err != nil {
29+
t.Fatal("Fail to open:", err)
30+
}
31+
defer db.Close()
32+
33+
// Test data
34+
expected := []byte("I ❤️ SQLite in \x00\x01\x02…")
35+
rowid := int64(6581)
36+
37+
query := `
38+
CREATE TABLE data (
39+
value BLOB
40+
);
41+
42+
INSERT INTO data (_rowid_, value)
43+
VALUES (:rowid, :value);
44+
`
45+
46+
_, err = db.Exec(query, sql.Named("rowid", rowid), sql.Named("value", expected))
47+
if err != nil {
48+
t.Fatal("Failed to execute", err)
49+
}
50+
51+
// Open blob
52+
blob, err := driverConn.Blob("main", "data", "value", rowid, 0)
53+
if err != nil {
54+
t.Error("failed", err)
55+
}
56+
defer blob.Close()
57+
58+
// Read blob incrementally
59+
middle := len(expected) / 2
60+
first := expected[:middle]
61+
second := expected[middle:]
62+
63+
// Read part Ⅰ
64+
b1 := make([]byte, len(first))
65+
n1, err := blob.Read(b1)
66+
67+
if err != nil || n1 != len(b1) {
68+
t.Errorf("Failed to read %d bytes", n1)
69+
}
70+
71+
if bytes.Compare(first, b1) != 0 {
72+
t.Error("Expected\n", first, "got\n", b1)
73+
}
74+
75+
// Read part Ⅱ
76+
b2 := make([]byte, len(second))
77+
n2, err := blob.Read(b2)
78+
79+
if err != nil || n2 != len(b2) {
80+
t.Errorf("Failed to read %d bytes", n2)
81+
}
82+
83+
if bytes.Compare(second, b2) != 0 {
84+
t.Error("Expected\n", second, "got\n", b2)
85+
}
86+
87+
// EOF
88+
b3 := make([]byte, 10)
89+
n3, err := blob.Read(b3)
90+
91+
if err != io.EOF || n3 != 0 {
92+
t.Error("Expected EOF", err)
93+
}
94+
}

0 commit comments

Comments
 (0)