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  }