github.com/ncruces/go-sqlite3@v0.15.1-0.20240520133447-53eef1510ff0/ext/blobio/blob.go (about) 1 // Package blobio provides an SQL interface to incremental BLOB I/O. 2 package blobio 3 4 import ( 5 "errors" 6 "io" 7 8 "github.com/ncruces/go-sqlite3" 9 "github.com/ncruces/go-sqlite3/internal/util" 10 ) 11 12 // Register registers the SQL functions: 13 // 14 // readblob(schema, table, column, rowid, offset, n) 15 // 16 // Reads n bytes of a blob, starting at offset. 17 // 18 // writeblob(schema, table, column, rowid, offset, data) 19 // 20 // Writes data into a blob, at the given offset. 21 // 22 // openblob(schema, table, column, rowid, write, callback, args...) 23 // 24 // Opens blobs for reading or writing. 25 // The callback is invoked for each open blob, 26 // and must be bound to an [OpenCallback], 27 // using [sqlite3.BindPointer] or [sqlite3.Pointer]. 28 // The optional args will be passed to the callback, 29 // along with the [sqlite3.Blob] handle. 30 // 31 // https://sqlite.org/c3ref/blob.html 32 func Register(db *sqlite3.Conn) { 33 db.CreateFunction("readblob", 6, 0, readblob) 34 db.CreateFunction("writeblob", 6, 0, writeblob) 35 db.CreateFunction("openblob", -1, 0, openblob) 36 } 37 38 // OpenCallback is the type for the openblob callback. 39 type OpenCallback func(*sqlite3.Blob, ...sqlite3.Value) error 40 41 func readblob(ctx sqlite3.Context, arg ...sqlite3.Value) { 42 blob, err := getAuxBlob(ctx, arg, false) 43 if err != nil { 44 ctx.ResultError(err) 45 return 46 } 47 48 _, err = blob.Seek(arg[4].Int64(), io.SeekStart) 49 if err != nil { 50 ctx.ResultError(err) 51 return 52 } 53 54 n := arg[5].Int64() 55 if n <= 0 { 56 return 57 } 58 buf := make([]byte, n) 59 60 _, err = io.ReadFull(blob, buf) 61 if err != nil { 62 ctx.ResultError(err) 63 return 64 } 65 66 ctx.ResultBlob(buf) 67 setAuxBlob(ctx, blob, false) 68 } 69 70 func writeblob(ctx sqlite3.Context, arg ...sqlite3.Value) { 71 blob, err := getAuxBlob(ctx, arg, true) 72 if err != nil { 73 ctx.ResultError(err) 74 return 75 } 76 77 _, err = blob.Seek(arg[4].Int64(), io.SeekStart) 78 if err != nil { 79 ctx.ResultError(err) 80 return 81 } 82 83 _, err = blob.Write(arg[5].RawBlob()) 84 if err != nil { 85 ctx.ResultError(err) 86 return 87 } 88 89 setAuxBlob(ctx, blob, false) 90 } 91 92 func openblob(ctx sqlite3.Context, arg ...sqlite3.Value) { 93 if len(arg) < 6 { 94 ctx.ResultError(util.ErrorString("openblob: wrong number of arguments")) 95 return 96 } 97 98 blob, err := getAuxBlob(ctx, arg, arg[4].Bool()) 99 if err != nil { 100 ctx.ResultError(err) 101 return 102 } 103 104 fn := arg[5].Pointer().(OpenCallback) 105 err = fn(blob, arg[6:]...) 106 if err != nil { 107 ctx.ResultError(err) 108 return 109 } 110 111 setAuxBlob(ctx, blob, true) 112 } 113 114 func getAuxBlob(ctx sqlite3.Context, arg []sqlite3.Value, write bool) (*sqlite3.Blob, error) { 115 row := arg[3].Int64() 116 117 if blob, ok := ctx.GetAuxData(0).(*sqlite3.Blob); ok { 118 if err := blob.Reopen(row); errors.Is(err, sqlite3.MISUSE) { 119 // Blob was closed (db, table, column or write changed). 120 } else { 121 return blob, err 122 } 123 } 124 125 db := arg[0].Text() 126 table := arg[1].Text() 127 column := arg[2].Text() 128 return ctx.Conn().OpenBlob(db, table, column, row, write) 129 } 130 131 func setAuxBlob(ctx sqlite3.Context, blob *sqlite3.Blob, open bool) { 132 // This ensures the blob is closed if db, table, column or write change. 133 ctx.SetAuxData(0, blob) // db 134 ctx.SetAuxData(1, blob) // table 135 ctx.SetAuxData(2, blob) // column 136 if open { 137 ctx.SetAuxData(4, blob) // write 138 } 139 }