package graphdblite
import "C"
import (
"errors"
"fmt"
"runtime"
"unsafe"
)
type Database struct {
ptr *C.GraphDB
}
type Result struct {
ptr *C.GraphResult
}
func lastError() error {
msg := C.graphdb_last_error()
if msg == nil {
return errors.New("unknown graphdblite error")
}
return errors.New(C.GoString(msg))
}
func Open(path string) (*Database, error) {
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
var ptr *C.GraphDB
rc := C.graphdb_open(cpath, &ptr)
if rc != 0 {
return nil, fmt.Errorf("graphdblite open: %w", lastError())
}
db := &Database{ptr: ptr}
runtime.SetFinalizer(db, (*Database).Close)
return db, nil
}
func OpenWithTimeout(path string, busyTimeoutMs uint32) (*Database, error) {
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
var ptr *C.GraphDB
rc := C.graphdb_open_with_timeout(cpath, C.uint32_t(busyTimeoutMs), &ptr)
if rc != 0 {
return nil, fmt.Errorf("graphdblite open: %w", lastError())
}
db := &Database{ptr: ptr}
runtime.SetFinalizer(db, (*Database).Close)
return db, nil
}
func OpenMemory() (*Database, error) {
var ptr *C.GraphDB
rc := C.graphdb_open_memory(&ptr)
if rc != 0 {
return nil, fmt.Errorf("graphdblite open memory: %w", lastError())
}
db := &Database{ptr: ptr}
runtime.SetFinalizer(db, (*Database).Close)
return db, nil
}
func (db *Database) SnapshotTo(path string) error {
if db.ptr == nil {
return errors.New("database is closed")
}
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
if rc := C.graphdb_snapshot_to(db.ptr, cpath); rc != 0 {
return fmt.Errorf("graphdblite snapshot_to: %w", lastError())
}
return nil
}
func (db *Database) Close() {
if db.ptr != nil {
C.graphdb_close(db.ptr)
db.ptr = nil
runtime.SetFinalizer(db, nil)
}
}
func (db *Database) Query(cypher string) (*Result, error) {
if db.ptr == nil {
return nil, errors.New("database is closed")
}
ccypher := C.CString(cypher)
defer C.free(unsafe.Pointer(ccypher))
var ptr *C.GraphResult
rc := C.graphdb_query(db.ptr, ccypher, &ptr)
if rc != 0 {
return nil, fmt.Errorf("graphdblite query: %w", lastError())
}
return &Result{ptr: ptr}, nil
}
func (db *Database) Execute(cypher string) (*Result, error) {
if db.ptr == nil {
return nil, errors.New("database is closed")
}
ccypher := C.CString(cypher)
defer C.free(unsafe.Pointer(ccypher))
var ptr *C.GraphResult
rc := C.graphdb_execute(db.ptr, ccypher, &ptr)
if rc != 0 {
return nil, fmt.Errorf("graphdblite execute: %w", lastError())
}
return &Result{ptr: ptr}, nil
}
type WriteTransaction struct {
db *Database
}
type ReadTransaction struct {
db *Database
}
func (db *Database) BeginWrite() (*WriteTransaction, error) {
if db.ptr == nil {
return nil, errors.New("database is closed")
}
rc := C.graphdb_tx_begin_write(db.ptr)
if rc != 0 {
return nil, fmt.Errorf("graphdblite begin write: %w", lastError())
}
return &WriteTransaction{db: db}, nil
}
func (db *Database) BeginRead() (*ReadTransaction, error) {
if db.ptr == nil {
return nil, errors.New("database is closed")
}
rc := C.graphdb_tx_begin_read(db.ptr)
if rc != 0 {
return nil, fmt.Errorf("graphdblite begin read: %w", lastError())
}
return &ReadTransaction{db: db}, nil
}
func (tx *WriteTransaction) Query(cypher string) (*Result, error) {
if tx.db == nil || tx.db.ptr == nil {
return nil, errors.New("transaction is finished")
}
ccypher := C.CString(cypher)
defer C.free(unsafe.Pointer(ccypher))
var ptr *C.GraphResult
rc := C.graphdb_tx_execute(tx.db.ptr, ccypher, &ptr)
if rc != 0 {
return nil, fmt.Errorf("graphdblite tx query: %w", lastError())
}
return &Result{ptr: ptr}, nil
}
func (tx *WriteTransaction) Execute(cypher string) (*Result, error) {
return tx.Query(cypher)
}
func (tx *WriteTransaction) CreateIndex(label, property string) error {
return tx.ddl("create_index", label, property, func(clabel, cprop *C.char) C.int32_t {
return C.graphdb_create_index(tx.db.ptr, clabel, cprop)
})
}
func (tx *WriteTransaction) DropIndex(label, property string) error {
return tx.ddl("drop_index", label, property, func(clabel, cprop *C.char) C.int32_t {
return C.graphdb_drop_index(tx.db.ptr, clabel, cprop)
})
}
func (tx *WriteTransaction) CreateFulltextIndex(label, property string) error {
return tx.ddl("create_fulltext_index", label, property, func(clabel, cprop *C.char) C.int32_t {
return C.graphdb_create_fulltext_index(tx.db.ptr, clabel, cprop)
})
}
func (tx *WriteTransaction) CreateFulltextIndexCI(label, property string) error {
return tx.ddl("create_fulltext_index_ci", label, property, func(clabel, cprop *C.char) C.int32_t {
return C.graphdb_create_fulltext_index_ci(tx.db.ptr, clabel, cprop)
})
}
func (tx *WriteTransaction) CreateFulltextIndexWord(label, property string) error {
return tx.ddl("create_fulltext_index_word", label, property, func(clabel, cprop *C.char) C.int32_t {
return C.graphdb_create_fulltext_index_word(tx.db.ptr, clabel, cprop)
})
}
func (tx *WriteTransaction) CreateFulltextIndexWordMulti(label string, properties []string) error {
if tx.db == nil || tx.db.ptr == nil {
return errors.New("transaction is finished")
}
if len(properties) == 0 {
return errors.New("graphdblite create_fulltext_index_word_multi: properties cannot be empty")
}
clabel := C.CString(label)
defer C.free(unsafe.Pointer(clabel))
cprops := make([]*C.char, len(properties))
for i, p := range properties {
cprops[i] = C.CString(p)
defer C.free(unsafe.Pointer(cprops[i]))
}
cpropsPtr := (**C.char)(unsafe.Pointer(&cprops[0]))
rc := C.graphdb_create_fulltext_index_word_multi(
tx.db.ptr,
clabel,
cpropsPtr,
C.size_t(len(properties)),
)
if rc != 0 {
return fmt.Errorf("graphdblite create_fulltext_index_word_multi: %w", lastError())
}
return nil
}
func (tx *WriteTransaction) DropFulltextIndex(label, property string) error {
return tx.ddl("drop_fulltext_index", label, property, func(clabel, cprop *C.char) C.int32_t {
return C.graphdb_drop_fulltext_index(tx.db.ptr, clabel, cprop)
})
}
func (tx *WriteTransaction) CreateCompositeIndex(label string, properties []string) error {
if tx.db == nil || tx.db.ptr == nil {
return errors.New("transaction is finished")
}
if len(properties) == 0 {
return errors.New("graphdblite create_composite_index: properties cannot be empty")
}
clabel := C.CString(label)
defer C.free(unsafe.Pointer(clabel))
cprops := make([]*C.char, len(properties))
for i, p := range properties {
cprops[i] = C.CString(p)
defer C.free(unsafe.Pointer(cprops[i]))
}
cpropsPtr := (**C.char)(unsafe.Pointer(&cprops[0]))
rc := C.graphdb_create_composite_index(
tx.db.ptr,
clabel,
cpropsPtr,
C.uintptr_t(len(properties)),
)
if rc != 0 {
return fmt.Errorf("graphdblite create_composite_index: %w", lastError())
}
return nil
}
func (tx *WriteTransaction) DropCompositeIndex(label string, properties []string) error {
if tx.db == nil || tx.db.ptr == nil {
return errors.New("transaction is finished")
}
if len(properties) == 0 {
return errors.New("graphdblite drop_composite_index: properties cannot be empty")
}
clabel := C.CString(label)
defer C.free(unsafe.Pointer(clabel))
cprops := make([]*C.char, len(properties))
for i, p := range properties {
cprops[i] = C.CString(p)
defer C.free(unsafe.Pointer(cprops[i]))
}
cpropsPtr := (**C.char)(unsafe.Pointer(&cprops[0]))
rc := C.graphdb_drop_composite_index(
tx.db.ptr,
clabel,
cpropsPtr,
C.uintptr_t(len(properties)),
)
if rc != 0 {
return fmt.Errorf("graphdblite drop_composite_index: %w", lastError())
}
return nil
}
func (tx *WriteTransaction) ddl(op, label, property string, call func(clabel, cprop *C.char) C.int32_t) error {
if tx.db == nil || tx.db.ptr == nil {
return errors.New("transaction is finished")
}
clabel := C.CString(label)
defer C.free(unsafe.Pointer(clabel))
cprop := C.CString(property)
defer C.free(unsafe.Pointer(cprop))
if rc := call(clabel, cprop); rc != 0 {
return fmt.Errorf("graphdblite %s: %w", op, lastError())
}
return nil
}
func (tx *WriteTransaction) Commit() error {
if tx.db == nil || tx.db.ptr == nil {
return errors.New("transaction is finished")
}
rc := C.graphdb_tx_commit(tx.db.ptr)
tx.db = nil
if rc != 0 {
return fmt.Errorf("graphdblite commit: %w", lastError())
}
return nil
}
func (tx *WriteTransaction) Rollback() error {
if tx.db == nil || tx.db.ptr == nil {
return errors.New("transaction is finished")
}
rc := C.graphdb_tx_rollback(tx.db.ptr)
tx.db = nil
if rc != 0 {
return fmt.Errorf("graphdblite rollback: %w", lastError())
}
return nil
}
func (tx *ReadTransaction) Query(cypher string) (*Result, error) {
if tx.db == nil || tx.db.ptr == nil {
return nil, errors.New("transaction is finished")
}
ccypher := C.CString(cypher)
defer C.free(unsafe.Pointer(ccypher))
var ptr *C.GraphResult
rc := C.graphdb_tx_execute(tx.db.ptr, ccypher, &ptr)
if rc != 0 {
return nil, fmt.Errorf("graphdblite tx query: %w", lastError())
}
return &Result{ptr: ptr}, nil
}
func (tx *ReadTransaction) Commit() error {
if tx.db == nil || tx.db.ptr == nil {
return errors.New("transaction is finished")
}
rc := C.graphdb_tx_commit(tx.db.ptr)
tx.db = nil
if rc != 0 {
return fmt.Errorf("graphdblite commit: %w", lastError())
}
return nil
}
func (db *Database) WithWriteTx(fn func(*WriteTransaction) error) (err error) {
tx, err := db.BeginWrite()
if err != nil {
return err
}
committed := false
defer func() {
if r := recover(); r != nil {
if !committed {
_ = tx.Rollback()
}
panic(r)
}
if !committed {
_ = tx.Rollback()
}
}()
if err = fn(tx); err != nil {
return err
}
if err = tx.Commit(); err != nil {
return err
}
committed = true
return nil
}
func (db *Database) WithReadTx(fn func(*ReadTransaction) error) (err error) {
tx, err := db.BeginRead()
if err != nil {
return err
}
finalized := false
defer func() {
if r := recover(); r != nil {
if !finalized {
_ = tx.Commit()
}
panic(r)
}
if !finalized {
_ = tx.Commit()
}
}()
if err = fn(tx); err != nil {
return err
}
if err = tx.Commit(); err != nil {
return err
}
finalized = true
return nil
}
func (r *Result) RowCount() int64 {
if r.ptr == nil {
return 0
}
return int64(C.graphdb_result_row_count(r.ptr))
}
func (r *Result) ColumnCount() int64 {
if r.ptr == nil {
return 0
}
return int64(C.graphdb_result_column_count(r.ptr))
}
func (r *Result) ColumnName(col int64) string {
if r.ptr == nil {
return ""
}
cname := C.graphdb_result_column_name(r.ptr, C.int64_t(col))
if cname == nil {
return ""
}
return C.GoString(cname)
}
const (
TypeNull = 0
TypeBool = 1
TypeI64 = 2
TypeF64 = 3
TypeString = 4
TypeList = 5
TypePath = 6
)
func (r *Result) ValueType(row, col int64) int32 {
if r.ptr == nil {
return TypeNull
}
return int32(C.graphdb_result_value_type(r.ptr, C.int64_t(row), C.int64_t(col)))
}
func (r *Result) ValueStr(row, col int64) string {
if r.ptr == nil {
return ""
}
cval := C.graphdb_result_value_str(r.ptr, C.int64_t(row), C.int64_t(col))
if cval == nil {
return ""
}
return C.GoString(cval)
}
func (r *Result) ValueI64(row, col int64) int64 {
if r.ptr == nil {
return 0
}
return int64(C.graphdb_result_value_i64(r.ptr, C.int64_t(row), C.int64_t(col)))
}
func (r *Result) ValueF64(row, col int64) float64 {
if r.ptr == nil {
return 0.0
}
return float64(C.graphdb_result_value_f64(r.ptr, C.int64_t(row), C.int64_t(col)))
}
func (r *Result) ValueBool(row, col int64) bool {
if r.ptr == nil {
return false
}
return C.graphdb_result_value_bool(r.ptr, C.int64_t(row), C.int64_t(col)) != 0
}
func (r *Result) JSON() string {
if r.ptr == nil {
return "[]"
}
cjson := C.graphdb_result_json(r.ptr)
if cjson == nil {
return "[]"
}
return C.GoString(cjson)
}
func (r *Result) Free() {
if r.ptr != nil {
C.graphdb_result_free(r.ptr)
r.ptr = nil
}
}