limbo/bindings/go/rows.go

121 lines
2 KiB
Go

package limbo
import (
"database/sql/driver"
"errors"
"fmt"
"io"
"sync"
)
type limboRows struct {
mu sync.Mutex
ctx uintptr
columns []string
err error
closed bool
}
func newRows(ctx uintptr) *limboRows {
return &limboRows{
mu: sync.Mutex{},
ctx: ctx,
columns: nil,
err: nil,
closed: false,
}
}
func (r *limboRows) isClosed() bool {
if r.ctx == 0 || r.closed {
return true
}
return false
}
func (r *limboRows) Columns() []string {
if r.isClosed() {
return nil
}
if r.columns == nil {
r.mu.Lock()
count := rowsGetColumns(r.ctx)
if count > 0 {
columns := make([]string, 0, count)
for i := 0; i < int(count); i++ {
cstr := rowsGetColumnName(r.ctx, int32(i))
columns = append(columns, fmt.Sprintf("%s", GoString(cstr)))
freeCString(cstr)
}
r.mu.Unlock()
r.columns = columns
}
}
return r.columns
}
func (r *limboRows) Close() error {
r.err = errors.New(RowsClosedErr)
if r.isClosed() {
return r.err
}
r.mu.Lock()
r.closed = true
closeRows(r.ctx)
r.ctx = 0
r.mu.Unlock()
return nil
}
func (r *limboRows) Err() error {
if r.err == nil {
r.mu.Lock()
defer r.mu.Unlock()
r.getError()
}
return r.err
}
func (r *limboRows) Next(dest []driver.Value) error {
r.mu.Lock()
defer r.mu.Unlock()
if r.isClosed() {
return r.err
}
for {
status := rowsNext(r.ctx)
switch ResultCode(status) {
case Row:
for i := range dest {
valPtr := rowsGetValue(r.ctx, int32(i))
val := toGoValue(valPtr)
if val == nil {
r.getError()
}
dest[i] = val
}
return nil
case Io:
continue
case Done:
return io.EOF
default:
return r.getError()
}
}
}
// mutex will already be locked. this is always called after FFI
func (r *limboRows) getError() error {
if r.isClosed() {
return r.err
}
err := rowsGetError(r.ctx)
if err == 0 {
return nil
}
defer freeCString(err)
cpy := fmt.Sprintf("%s", GoString(err))
r.err = errors.New(cpy)
return r.err
}