mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 17:22:26 -05:00 
			
		
		
		
	This allows for building GoToSocial with [SQLite transpiled to WASM](https://github.com/ncruces/go-sqlite3) and accessed through [Wazero](https://wazero.io/).
		
			
				
	
	
		
			250 lines
		
	
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			250 lines
		
	
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package sqlite3
 | |
| 
 | |
| import (
 | |
| 	"io"
 | |
| 
 | |
| 	"github.com/ncruces/go-sqlite3/internal/util"
 | |
| )
 | |
| 
 | |
| // ZeroBlob represents a zero-filled, length n BLOB
 | |
| // that can be used as an argument to
 | |
| // [database/sql.DB.Exec] and similar methods.
 | |
| type ZeroBlob int64
 | |
| 
 | |
| // Blob is an handle to an open BLOB.
 | |
| //
 | |
| // It implements [io.ReadWriteSeeker] for incremental BLOB I/O.
 | |
| //
 | |
| // https://sqlite.org/c3ref/blob.html
 | |
| type Blob struct {
 | |
| 	c      *Conn
 | |
| 	bytes  int64
 | |
| 	offset int64
 | |
| 	handle uint32
 | |
| }
 | |
| 
 | |
| var _ io.ReadWriteSeeker = &Blob{}
 | |
| 
 | |
| // OpenBlob opens a BLOB for incremental I/O.
 | |
| //
 | |
| // https://sqlite.org/c3ref/blob_open.html
 | |
| func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob, error) {
 | |
| 	c.checkInterrupt()
 | |
| 	defer c.arena.mark()()
 | |
| 	blobPtr := c.arena.new(ptrlen)
 | |
| 	dbPtr := c.arena.string(db)
 | |
| 	tablePtr := c.arena.string(table)
 | |
| 	columnPtr := c.arena.string(column)
 | |
| 
 | |
| 	var flags uint64
 | |
| 	if write {
 | |
| 		flags = 1
 | |
| 	}
 | |
| 
 | |
| 	r := c.call("sqlite3_blob_open", uint64(c.handle),
 | |
| 		uint64(dbPtr), uint64(tablePtr), uint64(columnPtr),
 | |
| 		uint64(row), flags, uint64(blobPtr))
 | |
| 
 | |
| 	if err := c.error(r); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	blob := Blob{c: c}
 | |
| 	blob.handle = util.ReadUint32(c.mod, blobPtr)
 | |
| 	blob.bytes = int64(c.call("sqlite3_blob_bytes", uint64(blob.handle)))
 | |
| 	return &blob, nil
 | |
| }
 | |
| 
 | |
| // Close closes a BLOB handle.
 | |
| //
 | |
| // It is safe to close a nil, zero or closed Blob.
 | |
| //
 | |
| // https://sqlite.org/c3ref/blob_close.html
 | |
| func (b *Blob) Close() error {
 | |
| 	if b == nil || b.handle == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	r := b.c.call("sqlite3_blob_close", uint64(b.handle))
 | |
| 
 | |
| 	b.handle = 0
 | |
| 	return b.c.error(r)
 | |
| }
 | |
| 
 | |
| // Size returns the size of the BLOB in bytes.
 | |
| //
 | |
| // https://sqlite.org/c3ref/blob_bytes.html
 | |
| func (b *Blob) Size() int64 {
 | |
| 	return b.bytes
 | |
| }
 | |
| 
 | |
| // Read implements the [io.Reader] interface.
 | |
| //
 | |
| // https://sqlite.org/c3ref/blob_read.html
 | |
| func (b *Blob) Read(p []byte) (n int, err error) {
 | |
| 	if b.offset >= b.bytes {
 | |
| 		return 0, io.EOF
 | |
| 	}
 | |
| 
 | |
| 	avail := b.bytes - b.offset
 | |
| 	want := int64(len(p))
 | |
| 	if want > avail {
 | |
| 		want = avail
 | |
| 	}
 | |
| 
 | |
| 	defer b.c.arena.mark()()
 | |
| 	ptr := b.c.arena.new(uint64(want))
 | |
| 
 | |
| 	r := b.c.call("sqlite3_blob_read", uint64(b.handle),
 | |
| 		uint64(ptr), uint64(want), uint64(b.offset))
 | |
| 	err = b.c.error(r)
 | |
| 	if err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 	b.offset += want
 | |
| 	if b.offset >= b.bytes {
 | |
| 		err = io.EOF
 | |
| 	}
 | |
| 
 | |
| 	copy(p, util.View(b.c.mod, ptr, uint64(want)))
 | |
| 	return int(want), err
 | |
| }
 | |
| 
 | |
| // WriteTo implements the [io.WriterTo] interface.
 | |
| //
 | |
| // https://sqlite.org/c3ref/blob_read.html
 | |
| func (b *Blob) WriteTo(w io.Writer) (n int64, err error) {
 | |
| 	if b.offset >= b.bytes {
 | |
| 		return 0, nil
 | |
| 	}
 | |
| 
 | |
| 	want := int64(1024 * 1024)
 | |
| 	avail := b.bytes - b.offset
 | |
| 	if want > avail {
 | |
| 		want = avail
 | |
| 	}
 | |
| 
 | |
| 	defer b.c.arena.mark()()
 | |
| 	ptr := b.c.arena.new(uint64(want))
 | |
| 
 | |
| 	for want > 0 {
 | |
| 		r := b.c.call("sqlite3_blob_read", uint64(b.handle),
 | |
| 			uint64(ptr), uint64(want), uint64(b.offset))
 | |
| 		err = b.c.error(r)
 | |
| 		if err != nil {
 | |
| 			return n, err
 | |
| 		}
 | |
| 
 | |
| 		mem := util.View(b.c.mod, ptr, uint64(want))
 | |
| 		m, err := w.Write(mem[:want])
 | |
| 		b.offset += int64(m)
 | |
| 		n += int64(m)
 | |
| 		if err != nil {
 | |
| 			return n, err
 | |
| 		}
 | |
| 		if int64(m) != want {
 | |
| 			return n, io.ErrShortWrite
 | |
| 		}
 | |
| 
 | |
| 		avail = b.bytes - b.offset
 | |
| 		if want > avail {
 | |
| 			want = avail
 | |
| 		}
 | |
| 	}
 | |
| 	return n, nil
 | |
| }
 | |
| 
 | |
| // Write implements the [io.Writer] interface.
 | |
| //
 | |
| // https://sqlite.org/c3ref/blob_write.html
 | |
| func (b *Blob) Write(p []byte) (n int, err error) {
 | |
| 	defer b.c.arena.mark()()
 | |
| 	ptr := b.c.arena.bytes(p)
 | |
| 
 | |
| 	r := b.c.call("sqlite3_blob_write", uint64(b.handle),
 | |
| 		uint64(ptr), uint64(len(p)), uint64(b.offset))
 | |
| 	err = b.c.error(r)
 | |
| 	if err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 	b.offset += int64(len(p))
 | |
| 	return len(p), nil
 | |
| }
 | |
| 
 | |
| // ReadFrom implements the [io.ReaderFrom] interface.
 | |
| //
 | |
| // https://sqlite.org/c3ref/blob_write.html
 | |
| func (b *Blob) ReadFrom(r io.Reader) (n int64, err error) {
 | |
| 	want := int64(1024 * 1024)
 | |
| 	avail := b.bytes - b.offset
 | |
| 	if l, ok := r.(*io.LimitedReader); ok && want > l.N {
 | |
| 		want = l.N
 | |
| 	}
 | |
| 	if want > avail {
 | |
| 		want = avail
 | |
| 	}
 | |
| 	if want < 1 {
 | |
| 		want = 1
 | |
| 	}
 | |
| 
 | |
| 	defer b.c.arena.mark()()
 | |
| 	ptr := b.c.arena.new(uint64(want))
 | |
| 
 | |
| 	for {
 | |
| 		mem := util.View(b.c.mod, ptr, uint64(want))
 | |
| 		m, err := r.Read(mem[:want])
 | |
| 		if m > 0 {
 | |
| 			r := b.c.call("sqlite3_blob_write", uint64(b.handle),
 | |
| 				uint64(ptr), uint64(m), uint64(b.offset))
 | |
| 			err := b.c.error(r)
 | |
| 			if err != nil {
 | |
| 				return n, err
 | |
| 			}
 | |
| 			b.offset += int64(m)
 | |
| 			n += int64(m)
 | |
| 		}
 | |
| 		if err == io.EOF {
 | |
| 			return n, nil
 | |
| 		}
 | |
| 		if err != nil {
 | |
| 			return n, err
 | |
| 		}
 | |
| 
 | |
| 		avail = b.bytes - b.offset
 | |
| 		if want > avail {
 | |
| 			want = avail
 | |
| 		}
 | |
| 		if want < 1 {
 | |
| 			want = 1
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Seek implements the [io.Seeker] interface.
 | |
| func (b *Blob) Seek(offset int64, whence int) (int64, error) {
 | |
| 	switch whence {
 | |
| 	default:
 | |
| 		return 0, util.WhenceErr
 | |
| 	case io.SeekStart:
 | |
| 		break
 | |
| 	case io.SeekCurrent:
 | |
| 		offset += b.offset
 | |
| 	case io.SeekEnd:
 | |
| 		offset += b.bytes
 | |
| 	}
 | |
| 	if offset < 0 {
 | |
| 		return 0, util.OffsetErr
 | |
| 	}
 | |
| 	b.offset = offset
 | |
| 	return offset, nil
 | |
| }
 | |
| 
 | |
| // Reopen moves a BLOB handle to a new row of the same database table.
 | |
| //
 | |
| // https://sqlite.org/c3ref/blob_reopen.html
 | |
| func (b *Blob) Reopen(row int64) error {
 | |
| 	err := b.c.error(b.c.call("sqlite3_blob_reopen", uint64(b.handle), uint64(row)))
 | |
| 	b.bytes = int64(b.c.call("sqlite3_blob_bytes", uint64(b.handle)))
 | |
| 	b.offset = 0
 | |
| 	return err
 | |
| }
 |