mirror of
https://github.com/tursodatabase/limbo.git
synced 2025-07-24 21:03:44 +00:00
230 lines
5.5 KiB
Go
230 lines
5.5 KiB
Go
package limbo
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"database/sql/driver"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/ebitengine/purego"
|
|
)
|
|
|
|
func init() {
|
|
err := ensureLibLoaded()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
sql.Register(driverName, &limboDriver{})
|
|
}
|
|
|
|
type limboDriver struct {
|
|
sync.Mutex
|
|
}
|
|
|
|
var (
|
|
libOnce sync.Once
|
|
limboLib uintptr
|
|
loadErr error
|
|
dbOpen func(string) uintptr
|
|
dbClose func(uintptr) uintptr
|
|
connPrepare func(uintptr, string) uintptr
|
|
connGetError func(uintptr) uintptr
|
|
freeBlobFunc func(uintptr)
|
|
freeStringFunc func(uintptr)
|
|
rowsGetColumns func(uintptr) int32
|
|
rowsGetColumnName func(uintptr, int32) uintptr
|
|
rowsGetValue func(uintptr, int32) uintptr
|
|
rowsGetError func(uintptr) uintptr
|
|
closeRows func(uintptr) uintptr
|
|
rowsNext func(uintptr) uintptr
|
|
stmtQuery func(stmtPtr uintptr, argsPtr uintptr, argCount uint64) uintptr
|
|
stmtExec func(stmtPtr uintptr, argsPtr uintptr, argCount uint64, changes uintptr) int32
|
|
stmtParamCount func(uintptr) int32
|
|
stmtGetError func(uintptr) uintptr
|
|
stmtClose func(uintptr) int32
|
|
)
|
|
|
|
// Register all the symbols on library load
|
|
func ensureLibLoaded() error {
|
|
libOnce.Do(func() {
|
|
limboLib, loadErr = loadLibrary()
|
|
if loadErr != nil {
|
|
return
|
|
}
|
|
purego.RegisterLibFunc(&dbOpen, limboLib, FfiDbOpen)
|
|
purego.RegisterLibFunc(&dbClose, limboLib, FfiDbClose)
|
|
purego.RegisterLibFunc(&connPrepare, limboLib, FfiDbPrepare)
|
|
purego.RegisterLibFunc(&connGetError, limboLib, FfiDbGetError)
|
|
purego.RegisterLibFunc(&freeBlobFunc, limboLib, FfiFreeBlob)
|
|
purego.RegisterLibFunc(&freeStringFunc, limboLib, FfiFreeCString)
|
|
purego.RegisterLibFunc(&rowsGetColumns, limboLib, FfiRowsGetColumns)
|
|
purego.RegisterLibFunc(&rowsGetColumnName, limboLib, FfiRowsGetColumnName)
|
|
purego.RegisterLibFunc(&rowsGetValue, limboLib, FfiRowsGetValue)
|
|
purego.RegisterLibFunc(&closeRows, limboLib, FfiRowsClose)
|
|
purego.RegisterLibFunc(&rowsNext, limboLib, FfiRowsNext)
|
|
purego.RegisterLibFunc(&rowsGetError, limboLib, FfiRowsGetError)
|
|
purego.RegisterLibFunc(&stmtQuery, limboLib, FfiStmtQuery)
|
|
purego.RegisterLibFunc(&stmtExec, limboLib, FfiStmtExec)
|
|
purego.RegisterLibFunc(&stmtParamCount, limboLib, FfiStmtParameterCount)
|
|
purego.RegisterLibFunc(&stmtGetError, limboLib, FfiStmtGetError)
|
|
purego.RegisterLibFunc(&stmtClose, limboLib, FfiStmtClose)
|
|
})
|
|
return loadErr
|
|
}
|
|
|
|
func (d *limboDriver) Open(name string) (driver.Conn, error) {
|
|
d.Lock()
|
|
conn, err := openConn(name)
|
|
d.Unlock()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return conn, nil
|
|
}
|
|
|
|
type limboConn struct {
|
|
sync.Mutex
|
|
ctx uintptr
|
|
}
|
|
|
|
func openConn(dsn string) (*limboConn, error) {
|
|
ctx := dbOpen(dsn)
|
|
if ctx == 0 {
|
|
return nil, fmt.Errorf("failed to open database for dsn=%q", dsn)
|
|
}
|
|
return &limboConn{
|
|
sync.Mutex{},
|
|
ctx,
|
|
}, loadErr
|
|
}
|
|
|
|
func (c *limboConn) Close() error {
|
|
if c.ctx == 0 {
|
|
return nil
|
|
}
|
|
c.Lock()
|
|
dbClose(c.ctx)
|
|
c.Unlock()
|
|
c.ctx = 0
|
|
return nil
|
|
}
|
|
|
|
func (c *limboConn) getError() error {
|
|
if c.ctx == 0 {
|
|
return errors.New("connection closed")
|
|
}
|
|
err := connGetError(c.ctx)
|
|
if err == 0 {
|
|
return nil
|
|
}
|
|
defer freeStringFunc(err)
|
|
cpy := fmt.Sprintf("%s", GoString(err))
|
|
return errors.New(cpy)
|
|
}
|
|
|
|
func (c *limboConn) Prepare(query string) (driver.Stmt, error) {
|
|
if c.ctx == 0 {
|
|
return nil, errors.New("connection closed")
|
|
}
|
|
c.Lock()
|
|
defer c.Unlock()
|
|
stmtPtr := connPrepare(c.ctx, query)
|
|
if stmtPtr == 0 {
|
|
return nil, c.getError()
|
|
}
|
|
return newStmt(stmtPtr, query), nil
|
|
}
|
|
|
|
// limboTx implements driver.Tx
|
|
type limboTx struct {
|
|
conn *limboConn
|
|
}
|
|
|
|
// Begin starts a new transaction with default isolation level
|
|
func (c *limboConn) Begin() (driver.Tx, error) {
|
|
c.Lock()
|
|
defer c.Unlock()
|
|
|
|
if c.ctx == 0 {
|
|
return nil, errors.New("connection closed")
|
|
}
|
|
|
|
// Execute BEGIN statement
|
|
stmtPtr := connPrepare(c.ctx, "BEGIN")
|
|
if stmtPtr == 0 {
|
|
return nil, c.getError()
|
|
}
|
|
|
|
stmt := newStmt(stmtPtr, "BEGIN")
|
|
defer stmt.Close()
|
|
|
|
_, err := stmt.Exec(nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &limboTx{conn: c}, nil
|
|
}
|
|
|
|
// BeginTx starts a transaction with the specified options.
|
|
// Currently only supports default isolation level and non-read-only transactions.
|
|
func (c *limboConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
|
// Skip handling non-default isolation levels and read-only mode
|
|
// for now, letting database/sql package handle these cases
|
|
if opts.Isolation != driver.IsolationLevel(sql.LevelDefault) || opts.ReadOnly {
|
|
return nil, driver.ErrSkip
|
|
}
|
|
|
|
// Check for context cancellation
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
default:
|
|
return c.Begin()
|
|
}
|
|
}
|
|
|
|
// Commit commits the transaction
|
|
func (tx *limboTx) Commit() error {
|
|
tx.conn.Lock()
|
|
defer tx.conn.Unlock()
|
|
|
|
if tx.conn.ctx == 0 {
|
|
return errors.New("connection closed")
|
|
}
|
|
|
|
stmtPtr := connPrepare(tx.conn.ctx, "COMMIT")
|
|
if stmtPtr == 0 {
|
|
return tx.conn.getError()
|
|
}
|
|
|
|
stmt := newStmt(stmtPtr, "COMMIT")
|
|
defer stmt.Close()
|
|
|
|
_, err := stmt.Exec(nil)
|
|
return err
|
|
}
|
|
|
|
// Rollback aborts the transaction.
|
|
// Note: This operation is not currently fully supported by Limbo and will return an error.
|
|
func (tx *limboTx) Rollback() error {
|
|
tx.conn.Lock()
|
|
defer tx.conn.Unlock()
|
|
|
|
if tx.conn.ctx == 0 {
|
|
return errors.New("connection closed")
|
|
}
|
|
|
|
stmtPtr := connPrepare(tx.conn.ctx, "ROLLBACK")
|
|
if stmtPtr == 0 {
|
|
return tx.conn.getError()
|
|
}
|
|
|
|
stmt := newStmt(stmtPtr, "ROLLBACK")
|
|
defer stmt.Close()
|
|
|
|
_, err := stmt.Exec(nil)
|
|
return err
|
|
}
|