-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add Blob I/O #1083
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add Blob I/O #1083
Changes from all commits
c357e0c
6188ff5
88d6cfd
c6a9868
2866235
867ec21
5583431
606d2d2
79df247
dff49f9
01dbf4b
893e77d
7df73e0
e519482
67dc006
7f0a6fb
5b53687
ef6fa5a
fe0ed2e
cb853d0
7d10860
3f8fe99
81fbf42
a01c70a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
// Copyright (C) 2022 Yasuhiro Matsumoto <[email protected]>. | ||
// | ||
// Use of this source code is governed by an MIT-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package sqlite3 | ||
|
||
/* | ||
#ifndef USE_LIBSQLITE3 | ||
#include "sqlite3-binding.h" | ||
#else | ||
#include <sqlite3.h> | ||
#endif | ||
#include <stdlib.h> | ||
*/ | ||
import "C" | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io" | ||
"math" | ||
"runtime" | ||
"unsafe" | ||
) | ||
|
||
// SQLiteBlob implements the SQLite Blob I/O interface. | ||
type SQLiteBlob struct { | ||
conn *SQLiteConn | ||
blob *C.sqlite3_blob | ||
size int | ||
offset int | ||
} | ||
|
||
// Blob opens a blob. | ||
// | ||
// See https://www.sqlite.org/c3ref/blob_open.html for usage. | ||
// | ||
// Should only be used with conn.Raw. | ||
func (conn *SQLiteConn) Blob(database, table, column string, rowid int64, flags int) (*SQLiteBlob, error) { | ||
databaseptr := C.CString(database) | ||
defer C.free(unsafe.Pointer(databaseptr)) | ||
|
||
tableptr := C.CString(table) | ||
defer C.free(unsafe.Pointer(tableptr)) | ||
|
||
columnptr := C.CString(column) | ||
defer C.free(unsafe.Pointer(columnptr)) | ||
|
||
var blob *C.sqlite3_blob | ||
ret := C.sqlite3_blob_open(conn.db, databaseptr, tableptr, columnptr, C.longlong(rowid), C.int(flags), &blob) | ||
|
||
if ret != C.SQLITE_OK { | ||
return nil, conn.lastError() | ||
} | ||
|
||
size := int(C.sqlite3_blob_bytes(blob)) | ||
bb := &SQLiteBlob{conn: conn, blob: blob, size: size, offset: 0} | ||
|
||
runtime.SetFinalizer(bb, (*SQLiteBlob).Close) | ||
|
||
return bb, nil | ||
} | ||
|
||
// Read implements the io.Reader interface. | ||
func (s *SQLiteBlob) Read(b []byte) (n int, err error) { | ||
if s.offset >= s.size { | ||
return 0, io.EOF | ||
} | ||
|
||
if len(b) == 0 { | ||
return 0, nil | ||
} | ||
|
||
n = s.size - s.offset | ||
if len(b) < n { | ||
n = len(b) | ||
} | ||
|
||
p := &b[0] | ||
rittneje marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ret := C.sqlite3_blob_read(s.blob, unsafe.Pointer(p), C.int(n), C.int(s.offset)) | ||
if ret != C.SQLITE_OK { | ||
return 0, s.conn.lastError() | ||
} | ||
|
||
s.offset += n | ||
|
||
return n, nil | ||
} | ||
|
||
// Write implements the io.Writer interface. | ||
func (s *SQLiteBlob) Write(b []byte) (n int, err error) { | ||
if len(b) == 0 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I recommend you create a copy of Something like: tmp := make([]byte, len(b))
copy(tmp, b) Then use tmp instead of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't making a copy defeat the purpose of streaming blobs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really, as you'd only be copying the buffer (which is small - usually around 1024 bytes). But generally speaking you'd only have to copy if you need to guard against caller reuse of the buffer. This is usually done in logging writers that may want to return control back to the caller while the write operation against the logging backend is still ongoing. I'm not sure that would apply here though (given that we want to wait for the write to happen and only then return), so I just added a comment as a "recommendation". Feel free to ignore me. I haven't mentioned this, but thanks for reopening this PR! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no need to make a copy of the slice, as |
||
return 0, nil | ||
} | ||
|
||
if s.offset >= s.size { | ||
return 0, fmt.Errorf("sqlite3.SQLiteBlob.Write: insufficient space in %d-byte blob", s.size) | ||
} | ||
|
||
n = s.size - s.offset | ||
joriszwart marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if len(b) < n { | ||
n = len(b) | ||
} | ||
|
||
if n != len(b) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trying to remember - is there a reason not to do this check after the call to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @joriszwart following up on this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know what to do. Sorry. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rittneje can you help me out? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function implements
The interesting bit of this is:
So if At least that's what I understand from the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The only thing that would be nice to have is maybe an error that denotes not enough space. Something like I think sqlite has There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is a subjective statement.
If the error is because the blob is full, retrying is never going to work. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right on both counts. |
||
return 0, fmt.Errorf("sqlite3.SQLiteBlob.Write: insufficient space in %d-byte blob", s.size) | ||
} | ||
|
||
p := &b[0] | ||
rittneje marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ret := C.sqlite3_blob_write(s.blob, unsafe.Pointer(p), C.int(n), C.int(s.offset)) | ||
if ret != C.SQLITE_OK { | ||
return 0, s.conn.lastError() | ||
} | ||
|
||
s.offset += n | ||
|
||
return n, nil | ||
} | ||
|
||
// Seek implements the io.Seeker interface. | ||
func (s *SQLiteBlob) Seek(offset int64, whence int) (int64, error) { | ||
if offset > math.MaxInt32 { | ||
return 0, fmt.Errorf("sqlite3.SQLiteBlob.Seek: invalid offset %d", offset) | ||
} | ||
|
||
var abs int64 | ||
switch whence { | ||
case io.SeekStart: | ||
abs = offset | ||
case io.SeekCurrent: | ||
abs = int64(s.offset) + offset | ||
case io.SeekEnd: | ||
abs = int64(s.size) + offset | ||
default: | ||
return 0, fmt.Errorf("sqlite3.SQLiteBlob.Seek: invalid whence %d", whence) | ||
} | ||
|
||
if abs < 0 { | ||
return 0, errors.New("sqlite.SQLiteBlob.Seek: negative position") | ||
} | ||
|
||
if abs > math.MaxInt32 || abs > int64(s.size) { | ||
return 0, errors.New("sqlite3.SQLiteBlob.Seek: overflow position") | ||
} | ||
|
||
s.offset = int(abs) | ||
joriszwart marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return abs, nil | ||
} | ||
|
||
// Size returns the size of the blob. | ||
func (s *SQLiteBlob) Size() int { | ||
return s.size | ||
} | ||
|
||
// Close implements the io.Closer interface. | ||
func (s *SQLiteBlob) Close() error { | ||
ret := C.sqlite3_blob_close(s.blob) | ||
|
||
s.blob = nil | ||
runtime.SetFinalizer(s, nil) | ||
|
||
if ret != C.SQLITE_OK { | ||
return s.conn.lastError() | ||
} | ||
|
||
return nil | ||
} |
Uh oh!
There was an error while loading. Please reload this page.