// Marshal a protobuf struct into a database thang. func marshalDBT(dbt *C.DBT, val proto.Message) (err error) { buf, err := proto.Marshal(val) if err != nil { return } if len(buf) > 0 { dbt.data = unsafe.Pointer(&buf[0]) dbt.size = C.u_int32_t(len(buf)) } else { dbt.data = nil dbt.size = 0 } return }
// Marshal the key of a record into a database thang. func (db Database) marshalKey(dbt *C.DBT, rec proto.Message) (err error) { key := recordKey(rec) dbtype, err := db.Type() if err != nil { return } switch dbtype { case Numbered, Queue: dbt.data = unsafe.Pointer(key.(*uint32)) dbt.size = 4 default: err = marshalDBT(dbt, key.(proto.Message)) } return }
// Retrieve the first record with matching key from the database. If // exact is false, the first record with a key greater than or equal // to the given one is fetched; this operation mode only makes sense // in combination with a B-tree database. func (cur Cursor) Set(rec proto.Message, exact bool) (err error) { var key, data C.DBT var flags C.u_int32_t = 0 if exact { key.flags |= C.DB_DBT_READONLY flags |= C.DB_SET } else { key.flags |= C.DB_DBT_MALLOC flags |= C.DB_SET_RANGE } data.flags |= C.DB_DBT_REALLOC defer C.free(data.data) err = cur.db.marshalKey(&key, rec) if err == nil { odata := key.data defer func() { if key.data != odata { C.free(data.data) } }() } else { return } err = check(C.db_cursor_get(cur.ptr, &key, &data, flags)) if err != nil { return } err = cur.db.unmarshalData(&data, rec) if err != nil { return } err = cur.db.unmarshalKey(&key, rec) return }
// Retrieve the previous record from the cursor. func (cur Cursor) Prev(rec proto.Message) (err error) { var key, data C.DBT key.flags |= C.DB_DBT_REALLOC defer C.free(key.data) data.flags |= C.DB_DBT_REALLOC defer C.free(data.data) err = check(C.db_cursor_get(cur.ptr, &key, &data, C.DB_PREV)) if err != nil { return } err = cur.db.unmarshalData(&data, rec) if err != nil { return } err = cur.db.unmarshalKey(&key, rec) return }
// Store records in the database. In combination with a queue or // numbered database the append flags causes the keys of the records // to be set to fresh record numbers, for any other database it // prevents an existing record with the same key from being // overwritten. func (db Database) Put(txn Transaction, append bool, recs ...proto.Message) (err error) { dbtype, err := db.Type() if err != nil { return } var key, data C.DBT var flags C.u_int32_t = 0 if append { key.flags |= C.DB_DBT_USERMEM switch dbtype { case Numbered, Queue: flags |= C.DB_APPEND default: flags |= C.DB_NOOVERWRITE } } else { key.flags |= C.DB_DBT_READONLY } data.flags |= C.DB_DBT_READONLY for _, rec := range recs { err = db.marshalData(&data, rec) if err != nil { return } err = db.marshalKey(&key, rec) if err == nil { key.ulen = key.size } else { return } err = check(C.db_put(db.ptr, txn.ptr, &key, &data, flags)) if err != nil { return } } return }
// Get records from the database. The consume flag makes sense only in // combination with a queue database and causes the operation to wait // for and obtain the next enqueued record. func (db Database) Get(txn Transaction, consume bool, recs ...proto.Message) (err error) { var key, data C.DBT var flags C.u_int32_t = 0 if consume { key.flags |= C.DB_DBT_USERMEM flags |= C.DB_CONSUME_WAIT } else { key.flags |= C.DB_DBT_READONLY } data.flags |= C.DB_DBT_REALLOC defer C.free(data.data) for _, rec := range recs { err = db.marshalKey(&key, rec) if err == nil { key.ulen = key.size } else { return } err = check(C.db_get(db.ptr, txn.ptr, &key, &data, flags)) if err != nil { return } err = db.unmarshalData(&data, rec) if err != nil { return } err = db.unmarshalKey(&key, rec) if err != nil { return } } return }
// Delete records from the database. func (db Database) Del(txn Transaction, recs ...proto.Message) (err error) { var key C.DBT key.flags |= C.DB_DBT_READONLY for _, rec := range recs { err = db.marshalKey(&key, rec) if err != nil { return } err = check(C.db_del(db.ptr, txn.ptr, &key, 0)) if err != nil { return } } return }