2020-10-28 03:18:10 +00:00
package ethdb
import (
"bytes"
"context"
2020-12-16 14:35:14 +00:00
"encoding/binary"
2020-10-28 03:18:10 +00:00
"fmt"
"os"
"path"
"runtime"
2021-06-16 10:57:58 +00:00
"sort"
"strings"
2020-10-28 03:18:10 +00:00
"sync"
"time"
2020-11-28 15:08:02 +00:00
"unsafe"
2020-10-28 03:18:10 +00:00
"github.com/c2h5oh/datasize"
2021-05-20 18:25:53 +00:00
"github.com/ledgerwatch/erigon/common/dbutils"
"github.com/ledgerwatch/erigon/common/debug"
"github.com/ledgerwatch/erigon/ethdb/mdbx"
"github.com/ledgerwatch/erigon/log"
"github.com/ledgerwatch/erigon/metrics"
2020-10-28 03:18:10 +00:00
)
2020-10-28 12:17:18 +00:00
var _ DbCopier = & MdbxKV { }
2021-05-22 04:18:04 +00:00
const expectMdbxVersionMajor = 0
const expectMdbxVersionMinor = 10
2021-06-16 10:57:58 +00:00
const NonExistingDBI dbutils . DBI = 999_999_999
type BucketConfigsFunc func ( defaultBuckets dbutils . BucketsCfg ) dbutils . BucketsCfg
func DefaultBucketConfigs ( defaultBuckets dbutils . BucketsCfg ) dbutils . BucketsCfg {
return defaultBuckets
}
2020-10-28 03:18:10 +00:00
type MdbxOpts struct {
2021-06-16 04:00:35 +00:00
bucketsCfg BucketConfigsFunc
path string
2021-05-06 03:51:49 +00:00
inMem bool
2021-06-04 14:56:49 +00:00
label Label // marker to distinct db instances - one process may open many databases. for example to collect metrics of only 1 database
2021-05-06 03:51:49 +00:00
verbosity DBVerbosityLvl
2021-06-16 04:00:35 +00:00
mapSize datasize . ByteSize
flags uint
2020-10-28 03:18:10 +00:00
}
2020-11-28 14:26:28 +00:00
func NewMDBX ( ) MdbxOpts {
return MdbxOpts {
2021-05-06 03:51:49 +00:00
bucketsCfg : DefaultBucketConfigs ,
2021-05-22 04:18:04 +00:00
flags : mdbx . NoReadahead | mdbx . Coalesce | mdbx . Durable ,
2020-11-28 14:26:28 +00:00
}
}
2021-06-04 14:56:49 +00:00
func ( opts MdbxOpts ) Label ( label Label ) MdbxOpts {
opts . label = label
return opts
}
2020-10-28 03:18:10 +00:00
func ( opts MdbxOpts ) Path ( path string ) MdbxOpts {
opts . path = path
return opts
}
func ( opts MdbxOpts ) Set ( opt MdbxOpts ) MdbxOpts {
return opt
}
func ( opts MdbxOpts ) InMem ( ) MdbxOpts {
opts . inMem = true
return opts
}
2020-10-28 09:52:15 +00:00
func ( opts MdbxOpts ) Exclusive ( ) MdbxOpts {
2021-06-14 06:35:22 +00:00
opts . flags = opts . flags | mdbx . Exclusive
2020-10-28 09:52:15 +00:00
return opts
}
2020-11-28 14:26:28 +00:00
func ( opts MdbxOpts ) Flags ( f func ( uint ) uint ) MdbxOpts {
opts . flags = f ( opts . flags )
return opts
}
2021-04-19 05:44:14 +00:00
func ( opts MdbxOpts ) Readonly ( ) MdbxOpts {
opts . flags = opts . flags | mdbx . Readonly
return opts
}
2021-04-27 12:31:00 +00:00
func ( opts MdbxOpts ) DBVerbosity ( v DBVerbosityLvl ) MdbxOpts {
opts . verbosity = v
return opts
}
2020-10-28 03:18:10 +00:00
func ( opts MdbxOpts ) MapSize ( sz datasize . ByteSize ) MdbxOpts {
opts . mapSize = sz
return opts
}
func ( opts MdbxOpts ) WithBucketsConfig ( f BucketConfigsFunc ) MdbxOpts {
opts . bucketsCfg = f
return opts
}
2021-03-30 09:53:54 +00:00
func ( opts MdbxOpts ) Open ( ) ( RwKV , error ) {
2021-05-22 04:18:04 +00:00
if expectMdbxVersionMajor != mdbx . Major || expectMdbxVersionMinor != mdbx . Minor {
return nil , fmt . Errorf ( "unexpected mdbx version: %d.%d, expected %d %d. Please run 'make mdbx'" , mdbx . Major , mdbx . Minor , expectMdbxVersionMajor , expectMdbxVersionMinor )
}
2021-03-02 02:52:05 +00:00
var logger log . Logger
var err error
if opts . inMem {
logger = log . New ( "mdbx" , "inMem" )
2021-05-18 12:13:16 +00:00
opts . path = testKVPath ( )
2021-03-02 02:52:05 +00:00
} else {
logger = log . New ( "mdbx" , path . Base ( opts . path ) )
2020-10-28 03:18:10 +00:00
}
2021-03-02 02:52:05 +00:00
env , err := mdbx . NewEnv ( )
2021-02-05 07:11:42 +00:00
if err != nil {
return nil , err
}
2021-04-27 12:31:00 +00:00
if opts . verbosity != - 1 {
err = env . SetDebug ( mdbx . LogLvl ( opts . verbosity ) , mdbx . DbgDoNotChange , mdbx . LoggerDoNotChange ) // temporary disable error, because it works if call it 1 time, but returns error if call it twice in same process (what often happening in tests)
if err != nil {
return nil , fmt . Errorf ( "db verbosity set: %w" , err )
}
}
2021-03-23 09:00:07 +00:00
if err = env . SetOption ( mdbx . OptMaxDB , 100 ) ; err != nil {
2021-02-25 02:41:57 +00:00
return nil , err
}
2021-03-23 07:28:04 +00:00
if err = env . SetOption ( mdbx . OptMaxReaders , ReadersLimit ) ; err != nil {
2020-10-28 03:18:10 +00:00
return nil , err
}
2021-03-20 19:33:40 +00:00
if opts . mapSize == 0 {
if opts . inMem {
opts . mapSize = 64 * datasize . MB
} else {
2021-06-16 10:57:58 +00:00
opts . mapSize = 2 * datasize . TB
2020-10-28 03:18:10 +00:00
}
2021-03-20 19:33:40 +00:00
}
2021-05-06 03:51:49 +00:00
const pageSize = 4 * 1024
2021-03-20 19:33:40 +00:00
if opts . flags & mdbx . Accede == 0 {
2021-03-28 09:27:06 +00:00
if opts . inMem {
2021-04-21 13:03:08 +00:00
if err = env . SetGeometry ( - 1 , - 1 , int ( opts . mapSize ) , int ( 2 * datasize . MB ) , 0 , 4 * 1024 ) ; err != nil {
2021-03-28 09:27:06 +00:00
return nil , err
}
} else {
2021-05-06 03:51:49 +00:00
if err = env . SetGeometry ( - 1 , - 1 , int ( opts . mapSize ) , int ( 2 * datasize . GB ) , - 1 , pageSize ) ; err != nil {
2021-03-28 09:27:06 +00:00
return nil , err
}
2021-03-02 02:52:05 +00:00
}
2021-03-13 02:30:54 +00:00
if err = env . SetOption ( mdbx . OptRpAugmentLimit , 32 * 1024 * 1024 ) ; err != nil {
return nil , err
}
2021-03-02 02:52:05 +00:00
if err = os . MkdirAll ( opts . path , 0744 ) ; err != nil {
return nil , fmt . Errorf ( "could not create dir: %s, %w" , opts . path , err )
}
2020-10-28 03:18:10 +00:00
}
2021-04-21 13:03:08 +00:00
err = env . Open ( opts . path , opts . flags , 0664 )
2020-10-28 03:18:10 +00:00
if err != nil {
return nil , fmt . Errorf ( "%w, path: %s" , err , opts . path )
}
2021-05-28 07:08:24 +00:00
defaultDirtyPagesLimit , err := env . GetOption ( mdbx . OptTxnDpLimit )
if err != nil {
return nil , err
}
2021-04-26 05:00:20 +00:00
if opts . flags & mdbx . Accede == 0 && opts . flags & mdbx . Readonly == 0 {
2021-04-12 14:30:19 +00:00
// 1/8 is good for transactions with a lot of modifications - to reduce invalidation size.
2021-05-26 10:35:39 +00:00
// But Erigon app now using Batch and etl.Collectors to avoid writing to DB frequently changing data.
2021-04-12 14:30:19 +00:00
// It means most of our writes are: APPEND or "single UPSERT per key during transaction"
2021-05-06 03:51:49 +00:00
//if err = env.SetOption(mdbx.OptSpillMinDenominator, 8); err != nil {
// return nil, err
//}
if err = env . SetOption ( mdbx . OptTxnDpInitial , 16 * 1024 ) ; err != nil {
2021-04-12 14:30:19 +00:00
return nil , err
}
2021-05-06 03:51:49 +00:00
if err = env . SetOption ( mdbx . OptDpReverseLimit , 16 * 1024 ) ; err != nil {
2021-04-12 14:30:19 +00:00
return nil , err
}
2021-05-28 07:08:24 +00:00
if err = env . SetOption ( mdbx . OptTxnDpLimit , defaultDirtyPagesLimit * 2 ) ; err != nil { // default is RAM/42
2021-04-12 14:30:19 +00:00
return nil , err
}
2021-05-06 03:51:49 +00:00
// must be in the range from 12.5% (almost empty) to 50% (half empty)
// which corresponds to the range from 8192 and to 32768 in units respectively
if err = env . SetOption ( mdbx . OptMergeThreshold16dot16Percent , 32768 ) ; err != nil {
2021-04-12 14:30:19 +00:00
return nil , err
}
}
2021-02-25 02:41:57 +00:00
2021-05-06 03:51:49 +00:00
dirtyPagesLimit , err := env . GetOption ( mdbx . OptTxnDpLimit )
if err != nil {
return nil , err
}
2020-10-28 03:18:10 +00:00
db := & MdbxKV {
2021-05-06 03:51:49 +00:00
opts : opts ,
env : env ,
log : logger ,
wg : & sync . WaitGroup { } ,
buckets : dbutils . BucketsCfg { } ,
pageSize : pageSize ,
txSize : dirtyPagesLimit * pageSize ,
2020-10-28 03:18:10 +00:00
}
customBuckets := opts . bucketsCfg ( dbutils . BucketsConfigs )
for name , cfg := range customBuckets { // copy map to avoid changing global variable
db . buckets [ name ] = cfg
}
2021-05-08 08:45:40 +00:00
buckets := bucketSlice ( db . buckets )
2020-10-28 03:18:10 +00:00
// Open or create buckets
2020-11-28 14:26:28 +00:00
if opts . flags & mdbx . Readonly != 0 {
2021-04-03 06:26:00 +00:00
tx , innerErr := db . BeginRo ( context . Background ( ) )
2020-10-28 03:18:10 +00:00
if innerErr != nil {
return nil , innerErr
}
2021-05-08 08:45:40 +00:00
for _ , name := range buckets {
if db . buckets [ name ] . IsDeprecated {
2020-10-28 03:18:10 +00:00
continue
}
if err = tx . ( BucketMigrator ) . CreateBucket ( name ) ; err != nil {
return nil , err
}
}
2021-04-03 06:26:00 +00:00
err = tx . Commit ( )
2020-10-28 03:18:10 +00:00
if err != nil {
return nil , err
}
} else {
2021-03-21 13:15:25 +00:00
if err := db . Update ( context . Background ( ) , func ( tx RwTx ) error {
2021-05-08 08:45:40 +00:00
for _ , name := range buckets {
if db . buckets [ name ] . IsDeprecated {
2020-10-28 03:18:10 +00:00
continue
}
if err := tx . ( BucketMigrator ) . CreateBucket ( name ) ; err != nil {
return err
}
}
return nil
} ) ; err != nil {
return nil , err
}
}
// Configure buckets and open deprecated buckets
if err := env . View ( func ( tx * mdbx . Txn ) error {
2021-05-08 08:45:40 +00:00
for _ , name := range buckets {
2020-10-28 03:18:10 +00:00
// Open deprecated buckets if they exist, don't create
2021-05-08 08:45:40 +00:00
if ! db . buckets [ name ] . IsDeprecated {
2020-10-28 03:18:10 +00:00
continue
}
cnfCopy := db . buckets [ name ]
var dcmp mdbx . CmpFunc
switch cnfCopy . CustomDupComparator {
case dbutils . DupCmpSuffix32 :
dcmp = tx . GetCmpExcludeSuffix32 ( )
}
2021-02-01 13:57:41 +00:00
dbi , createErr := tx . OpenDBI ( name , mdbx . DBAccede , nil , dcmp )
2020-10-28 03:18:10 +00:00
if createErr != nil {
if mdbx . IsNotFound ( createErr ) {
cnfCopy . DBI = NonExistingDBI
db . buckets [ name ] = cnfCopy
continue // if deprecated bucket couldn't be open - then it's deleted and it's fine
} else {
2021-02-01 13:57:41 +00:00
return fmt . Errorf ( "bucket: %s, %w" , name , createErr )
2020-10-28 03:18:10 +00:00
}
}
cnfCopy . DBI = dbutils . DBI ( dbi )
db . buckets [ name ] = cnfCopy
}
return nil
} ) ; err != nil {
return nil , err
}
if ! opts . inMem {
if staleReaders , err := db . env . ReaderCheck ( ) ; err != nil {
db . log . Error ( "failed ReaderCheck" , "err" , err )
} else if staleReaders > 0 {
db . log . Debug ( "cleared reader slots from dead processes" , "amount" , staleReaders )
}
}
return db , nil
}
2021-03-30 09:53:54 +00:00
func ( opts MdbxOpts ) MustOpen ( ) RwKV {
2020-10-28 03:18:10 +00:00
db , err := opts . Open ( )
if err != nil {
panic ( fmt . Errorf ( "fail to open mdbx: %w" , err ) )
}
return db
}
type MdbxKV struct {
2021-05-06 03:51:49 +00:00
env * mdbx . Env
log log . Logger
wg * sync . WaitGroup
2021-06-16 04:00:35 +00:00
buckets dbutils . BucketsCfg
opts MdbxOpts
txSize uint64
pageSize uint64
2020-10-28 03:18:10 +00:00
}
2021-02-14 04:38:28 +00:00
func ( db * MdbxKV ) NewDbWithTheSameParameters ( ) * ObjectDatabase {
opts := db . opts
return NewObjectDatabase ( NewMDBX ( ) . Set ( opts ) . MustOpen ( ) )
}
2020-10-28 03:18:10 +00:00
// Close closes db
// All transactions must be closed before closing the database.
func ( db * MdbxKV ) Close ( ) {
2021-05-18 12:13:16 +00:00
if db . env == nil {
return
2020-10-28 03:18:10 +00:00
}
2021-05-18 12:13:16 +00:00
db . wg . Wait ( )
db . env . Close ( )
db . env = nil
2020-10-28 03:18:10 +00:00
if db . opts . inMem {
if err := os . RemoveAll ( db . opts . path ) ; err != nil {
db . log . Warn ( "failed to remove in-mem db file" , "err" , err )
}
2021-05-28 12:55:48 +00:00
} else {
db . log . Info ( "database closed (MDBX)" )
2020-10-28 03:18:10 +00:00
}
}
2021-03-09 06:34:13 +00:00
func ( db * MdbxKV ) CollectMetrics ( ) {
2021-05-06 03:51:49 +00:00
if ! metrics . Enabled {
return
}
2021-06-04 14:56:49 +00:00
if db . opts . label != Chain {
return
}
2021-05-06 03:51:49 +00:00
info , err := db . env . Info ( )
if err != nil {
return // ignore error for metrics collection
}
2021-04-27 08:32:41 +00:00
dbSize . Update ( int64 ( info . Geo . Current ) )
2021-05-06 03:51:49 +00:00
dbPgopsNewly . Update ( int64 ( info . PageOps . Newly ) )
dbPgopsCow . Update ( int64 ( info . PageOps . Cow ) )
dbPgopsClone . Update ( int64 ( info . PageOps . Clone ) )
dbPgopsSplit . Update ( int64 ( info . PageOps . Split ) )
dbPgopsMerge . Update ( int64 ( info . PageOps . Merge ) )
dbPgopsSpill . Update ( int64 ( info . PageOps . Spill ) )
dbPgopsUnspill . Update ( int64 ( info . PageOps . Unspill ) )
dbPgopsWops . Update ( int64 ( info . PageOps . Wops ) )
2021-03-09 06:34:13 +00:00
}
2021-04-03 06:26:00 +00:00
func ( db * MdbxKV ) BeginRo ( _ context . Context ) ( txn Tx , err error ) {
2020-10-28 03:18:10 +00:00
if db . env == nil {
return nil , fmt . Errorf ( "db closed" )
}
2021-02-10 17:04:22 +00:00
defer func ( ) {
if err == nil {
db . wg . Add ( 1 )
}
} ( )
2020-10-28 03:18:10 +00:00
2021-03-21 13:15:25 +00:00
tx , err := db . env . BeginTxn ( nil , mdbx . Readonly )
if err != nil {
2021-05-04 12:36:03 +00:00
return nil , fmt . Errorf ( "%w, trace: %s" , err , debug . Callers ( 10 ) )
2020-10-28 03:18:10 +00:00
}
2021-03-21 13:15:25 +00:00
tx . RawRead = true
return & MdbxTx {
db : db ,
tx : tx ,
readOnly : true ,
} , nil
}
func ( db * MdbxKV ) BeginRw ( _ context . Context ) ( txn RwTx , err error ) {
if db . env == nil {
return nil , fmt . Errorf ( "db closed" )
2020-10-30 08:43:11 +00:00
}
2021-03-21 13:15:25 +00:00
runtime . LockOSThread ( )
defer func ( ) {
if err == nil {
db . wg . Add ( 1 )
}
} ( )
2020-10-30 08:43:11 +00:00
2021-05-04 06:21:51 +00:00
tx , err := db . env . BeginTxn ( nil , 0 )
2020-10-28 03:18:10 +00:00
if err != nil {
2021-02-10 17:04:22 +00:00
runtime . UnlockOSThread ( ) // unlock only in case of error. normal flow is "defer .Rollback()"
2021-05-04 12:36:03 +00:00
return nil , fmt . Errorf ( "%w, trace: %s" , err , debug . Callers ( 10 ) )
2020-10-28 03:18:10 +00:00
}
tx . RawRead = true
2020-11-28 14:26:28 +00:00
return & MdbxTx {
2021-03-21 13:15:25 +00:00
db : db ,
tx : tx ,
2020-10-28 03:18:10 +00:00
} , nil
}
2020-11-28 14:26:28 +00:00
type MdbxTx struct {
2021-05-11 08:23:17 +00:00
tx * mdbx . Txn
db * MdbxKV
2021-06-04 13:32:48 +00:00
cursors map [ uint64 ] * mdbx . Cursor
2021-05-11 08:23:17 +00:00
statelessCursors map [ string ] Cursor
2021-06-16 04:00:35 +00:00
readOnly bool
cursorID uint64
2020-10-28 03:18:10 +00:00
}
type MdbxCursor struct {
bucketName string
2021-06-16 04:00:35 +00:00
tx * MdbxTx
c * mdbx . Cursor
2020-10-28 03:18:10 +00:00
bucketCfg dbutils . BucketConfigItem
2021-06-16 04:00:35 +00:00
dbi mdbx . DBI
id uint64
2020-10-28 03:18:10 +00:00
}
func ( db * MdbxKV ) Env ( ) * mdbx . Env {
return db . env
}
func ( db * MdbxKV ) AllDBI ( ) map [ string ] dbutils . DBI {
res := map [ string ] dbutils . DBI { }
for name , cfg := range db . buckets {
res [ name ] = cfg . DBI
}
return res
}
func ( db * MdbxKV ) AllBuckets ( ) dbutils . BucketsCfg {
return db . buckets
}
2021-05-20 11:49:33 +00:00
func ( tx * MdbxTx ) ForEach ( bucket string , fromPrefix [ ] byte , walker func ( k , v [ ] byte ) error ) error {
c , err := tx . Cursor ( bucket )
if err != nil {
return err
}
defer c . Close ( )
for k , v , err := c . Seek ( fromPrefix ) ; k != nil ; k , v , err = c . Next ( ) {
if err != nil {
return err
}
if err := walker ( k , v ) ; err != nil {
return err
}
}
return nil
}
func ( tx * MdbxTx ) ForPrefix ( bucket string , prefix [ ] byte , walker func ( k , v [ ] byte ) error ) error {
c , err := tx . Cursor ( bucket )
if err != nil {
return err
}
defer c . Close ( )
for k , v , err := c . Seek ( prefix ) ; k != nil ; k , v , err = c . Next ( ) {
if err != nil {
return err
}
if ! bytes . HasPrefix ( k , prefix ) {
break
}
if err := walker ( k , v ) ; err != nil {
return err
}
}
return nil
}
2021-06-04 12:28:18 +00:00
func ( tx * MdbxTx ) ForAmount ( bucket string , fromPrefix [ ] byte , amount uint32 , walker func ( k , v [ ] byte ) error ) error {
c , err := tx . Cursor ( bucket )
if err != nil {
return err
}
defer c . Close ( )
for k , v , err := c . Seek ( fromPrefix ) ; k != nil && amount > 0 ; k , v , err = c . Next ( ) {
if err != nil {
return err
}
if err := walker ( k , v ) ; err != nil {
return err
}
amount --
}
return nil
}
2021-05-20 11:49:33 +00:00
2021-04-27 08:32:41 +00:00
func ( tx * MdbxTx ) CollectMetrics ( ) {
2021-05-06 03:51:49 +00:00
if ! metrics . Enabled {
return
}
2021-06-04 14:56:49 +00:00
if tx . db . opts . label != Chain {
return
}
2021-04-27 08:32:41 +00:00
txInfo , err := tx . tx . Info ( true )
if err != nil {
2021-05-06 03:51:49 +00:00
return
2021-04-27 08:32:41 +00:00
}
txDirty . Update ( int64 ( txInfo . SpaceDirty ) )
2021-05-06 10:02:50 +00:00
txLimit . Update ( int64 ( tx . db . txSize ) )
2021-04-27 08:32:41 +00:00
txSpill . Update ( int64 ( txInfo . Spill ) )
txUnspill . Update ( int64 ( txInfo . Unspill ) )
2021-05-06 03:51:49 +00:00
gc , err := tx . BucketStat ( "gc" )
if err != nil {
return
}
gcLeafMetric . Update ( int64 ( gc . LeafPages ) )
gcOverflowMetric . Update ( int64 ( gc . OverflowPages ) )
gcPagesMetric . Update ( int64 ( ( gc . LeafPages + gc . OverflowPages ) * tx . db . pageSize / 8 ) )
2021-06-01 09:53:28 +00:00
state , err := tx . BucketStat ( dbutils . PlainStateBucket )
if err != nil {
return
}
stateLeafMetric . Update ( int64 ( state . LeafPages ) )
stateBranchesMetric . Update ( int64 ( state . BranchPages ) )
2021-04-27 08:32:41 +00:00
}
2020-11-28 14:26:28 +00:00
func ( tx * MdbxTx ) Comparator ( bucket string ) dbutils . CmpFunc {
2020-10-28 03:18:10 +00:00
b := tx . db . buckets [ bucket ]
return chooseComparator2 ( tx . tx , mdbx . DBI ( b . DBI ) , b )
}
2021-06-04 12:26:15 +00:00
// ExistingBuckets - all buckets stored as keys of un-named bucket
2020-11-28 14:26:28 +00:00
func ( tx * MdbxTx ) ExistingBuckets ( ) ( [ ] string , error ) {
2020-10-28 03:18:10 +00:00
var res [ ] string
rawTx := tx . tx
root , err := rawTx . OpenRoot ( 0 )
if err != nil {
return nil , err
}
c , err := rawTx . OpenCursor ( root )
if err != nil {
return nil , err
}
2021-05-19 04:21:17 +00:00
for k , _ , err := c . Get ( nil , nil , mdbx . First ) ; k != nil ; k , _ , err = c . Get ( nil , nil , mdbx . Next ) {
if err != nil {
return nil , err
}
2020-10-28 03:18:10 +00:00
res = append ( res , string ( k ) )
}
return res , nil
}
func ( db * MdbxKV ) View ( ctx context . Context , f func ( tx Tx ) error ) ( err error ) {
if db . env == nil {
return fmt . Errorf ( "db closed" )
}
db . wg . Add ( 1 )
defer db . wg . Done ( )
// can't use db.evn.View method - because it calls commit for read transactions - it conflicts with write transactions.
2021-04-03 06:26:00 +00:00
tx , err := db . BeginRo ( ctx )
2020-10-28 03:18:10 +00:00
if err != nil {
return err
}
defer tx . Rollback ( )
return f ( tx )
}
2021-03-21 13:15:25 +00:00
func ( db * MdbxKV ) Update ( ctx context . Context , f func ( tx RwTx ) error ) ( err error ) {
2020-10-28 03:18:10 +00:00
if db . env == nil {
return fmt . Errorf ( "db closed" )
}
db . wg . Add ( 1 )
defer db . wg . Done ( )
2021-03-21 13:15:25 +00:00
tx , err := db . BeginRw ( ctx )
2020-10-28 03:18:10 +00:00
if err != nil {
return err
}
defer tx . Rollback ( )
err = f ( tx )
if err != nil {
return err
}
2021-04-03 06:26:00 +00:00
err = tx . Commit ( )
2020-10-28 03:18:10 +00:00
if err != nil {
return err
}
return nil
}
2020-11-28 14:26:28 +00:00
func ( tx * MdbxTx ) CreateBucket ( name string ) error {
2020-10-28 03:18:10 +00:00
cnfCopy := tx . db . buckets [ name ]
var dcmp mdbx . CmpFunc
switch cnfCopy . CustomDupComparator {
case dbutils . DupCmpSuffix32 :
dcmp = tx . tx . GetCmpExcludeSuffix32 ( )
}
2020-12-16 14:35:14 +00:00
dbi , err := tx . tx . OpenDBI ( name , mdbx . DBAccede , nil , dcmp )
if err != nil && ! mdbx . IsNotFound ( err ) {
2021-02-01 13:57:41 +00:00
return fmt . Errorf ( "create bucket: %s, %w" , name , err )
2020-12-16 14:35:14 +00:00
}
if err == nil {
cnfCopy . DBI = dbutils . DBI ( dbi )
var flags uint
flags , err = tx . tx . Flags ( dbi )
if err != nil {
return err
}
cnfCopy . Flags = dbutils . BucketFlags ( flags )
tx . db . buckets [ name ] = cnfCopy
return nil
}
2021-05-22 03:03:02 +00:00
// if bucket with this name not found - check renamed one
rename := dbutils . Rename [ name ]
dbi , err = tx . tx . OpenDBI ( rename , mdbx . DBAccede , nil , dcmp )
if err != nil && ! mdbx . IsNotFound ( err ) {
return fmt . Errorf ( "create bucket: %s, %w" , name , err )
}
if err == nil {
cnfCopy . DBI = dbutils . DBI ( dbi )
var flags uint
flags , err = tx . tx . Flags ( dbi )
if err != nil {
return err
}
cnfCopy . Flags = dbutils . BucketFlags ( flags )
tx . db . buckets [ name ] = cnfCopy
return nil
}
2020-12-16 14:35:14 +00:00
// if bucket doesn't exists - create it
var flags = tx . db . buckets [ name ] . Flags
var nativeFlags uint
if tx . db . opts . flags & mdbx . Readonly == 0 {
nativeFlags |= mdbx . Create
}
2020-12-04 21:16:51 +00:00
if flags & dbutils . DupSort != 0 {
2020-10-28 03:18:10 +00:00
nativeFlags |= mdbx . DupSort
2020-12-04 21:16:51 +00:00
flags ^ = dbutils . DupSort
2020-10-28 03:18:10 +00:00
}
2020-12-04 21:16:51 +00:00
if flags != 0 {
return fmt . Errorf ( "some not supported flag provided for bucket" )
}
2021-05-22 03:03:02 +00:00
if rename != "" {
dbi , err = tx . tx . OpenDBI ( rename , nativeFlags , nil , dcmp )
} else {
dbi , err = tx . tx . OpenDBI ( name , nativeFlags , nil , dcmp )
}
2020-10-28 03:18:10 +00:00
if err != nil {
2021-02-01 13:57:41 +00:00
return fmt . Errorf ( "create bucket: %s, %w" , name , err )
2020-10-28 03:18:10 +00:00
}
cnfCopy . DBI = dbutils . DBI ( dbi )
tx . db . buckets [ name ] = cnfCopy
return nil
}
2021-02-14 04:38:28 +00:00
func chooseComparator2 ( tx * mdbx . Txn , dbi mdbx . DBI , cnfCopy dbutils . BucketConfigItem ) dbutils . CmpFunc {
if cnfCopy . CustomComparator == dbutils . DefaultCmp && cnfCopy . CustomDupComparator == dbutils . DefaultCmp {
if cnfCopy . Flags & mdbx . DupSort == 0 {
return dbutils . DefaultCmpFunc
}
return dbutils . DefaultDupCmpFunc
}
if cnfCopy . Flags & mdbx . DupSort == 0 {
return CustomCmpFunc2 ( tx , dbi )
}
return CustomDupCmpFunc2 ( tx , dbi )
}
func CustomCmpFunc2 ( tx * mdbx . Txn , dbi mdbx . DBI ) dbutils . CmpFunc {
return func ( k1 , k2 , v1 , v2 [ ] byte ) int {
return tx . Cmp ( dbi , k1 , k2 )
}
}
func CustomDupCmpFunc2 ( tx * mdbx . Txn , dbi mdbx . DBI ) dbutils . CmpFunc {
return func ( k1 , k2 , v1 , v2 [ ] byte ) int {
cmp := tx . Cmp ( dbi , k1 , k2 )
if cmp == 0 {
cmp = tx . DCmp ( dbi , v1 , v2 )
}
return cmp
}
}
2020-11-28 14:26:28 +00:00
func ( tx * MdbxTx ) dropEvenIfBucketIsNotDeprecated ( name string ) error {
2020-10-28 03:18:10 +00:00
dbi := tx . db . buckets [ name ] . DBI
// if bucket was not open on db start, then it's may be deprecated
// try to open it now without `Create` flag, and if fail then nothing to drop
if dbi == NonExistingDBI {
nativeDBI , err := tx . tx . OpenDBI ( name , 0 , nil , nil )
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil // DBI doesn't exists means no drop needed
}
2021-02-01 13:57:41 +00:00
return fmt . Errorf ( "bucket: %s, %w" , name , err )
2020-10-28 03:18:10 +00:00
}
dbi = dbutils . DBI ( nativeDBI )
}
if err := tx . tx . Drop ( mdbx . DBI ( dbi ) , true ) ; err != nil {
return err
}
cnfCopy := tx . db . buckets [ name ]
cnfCopy . DBI = NonExistingDBI
tx . db . buckets [ name ] = cnfCopy
return nil
}
2020-11-28 14:26:28 +00:00
func ( tx * MdbxTx ) ClearBucket ( bucket string ) error {
2021-02-21 18:41:59 +00:00
dbi := tx . db . buckets [ bucket ] . DBI
if dbi == NonExistingDBI {
return nil
}
2021-03-01 04:15:59 +00:00
return tx . tx . Drop ( mdbx . DBI ( dbi ) , false )
2020-10-28 03:18:10 +00:00
}
2020-11-28 14:26:28 +00:00
func ( tx * MdbxTx ) DropBucket ( bucket string ) error {
2020-10-28 03:18:10 +00:00
if cfg , ok := tx . db . buckets [ bucket ] ; ! ( ok && cfg . IsDeprecated ) {
return fmt . Errorf ( "%w, bucket: %s" , ErrAttemptToDeleteNonDeprecatedBucket , bucket )
}
return tx . dropEvenIfBucketIsNotDeprecated ( bucket )
}
2020-11-28 14:26:28 +00:00
func ( tx * MdbxTx ) ExistsBucket ( bucket string ) bool {
2020-10-28 03:18:10 +00:00
if cfg , ok := tx . db . buckets [ bucket ] ; ok {
return cfg . DBI != NonExistingDBI
}
return false
}
2021-04-03 06:26:00 +00:00
func ( tx * MdbxTx ) Commit ( ) error {
2020-10-28 03:18:10 +00:00
if tx . db . env == nil {
return fmt . Errorf ( "db closed" )
}
if tx . tx == nil {
return nil
}
defer func ( ) {
tx . tx = nil
2021-02-10 17:04:22 +00:00
tx . db . wg . Done ( )
2021-03-23 07:28:04 +00:00
if ! tx . readOnly {
runtime . UnlockOSThread ( )
}
2020-10-28 03:18:10 +00:00
} ( )
tx . closeCursors ( )
2021-02-09 02:31:37 +00:00
slowTx := 10 * time . Second
if debug . SlowCommit ( ) > 0 {
slowTx = debug . SlowCommit ( )
}
2021-05-06 03:51:49 +00:00
if debug . BigRoTxKb ( ) > 0 || debug . BigRwTxKb ( ) > 0 {
tx . PrintDebugInfo ( )
}
2021-02-09 02:31:37 +00:00
2020-10-28 03:18:10 +00:00
latency , err := tx . tx . Commit ( )
if err != nil {
return err
}
2021-02-09 02:31:37 +00:00
2021-06-04 14:56:49 +00:00
if tx . db . opts . label == Chain {
dbCommitPreparation . Update ( latency . Preparation )
dbCommitGc . Update ( latency . GC )
dbCommitAudit . Update ( latency . Audit )
dbCommitWrite . Update ( latency . Write )
dbCommitSync . Update ( latency . Sync )
dbCommitEnding . Update ( latency . Ending )
dbCommitBigBatchTimer . Update ( latency . Whole )
}
2021-05-06 03:51:49 +00:00
2021-02-09 02:31:37 +00:00
if latency . Whole > slowTx {
log . Info ( "Commit" ,
"preparation" , latency . Preparation ,
"gc" , latency . GC ,
"audit" , latency . Audit ,
"write" , latency . Write ,
"fsync" , latency . Sync ,
"ending" , latency . Ending ,
"whole" , latency . Whole ,
)
2020-11-28 14:26:28 +00:00
}
2020-10-28 03:18:10 +00:00
return nil
}
2020-11-28 14:26:28 +00:00
func ( tx * MdbxTx ) Rollback ( ) {
2020-10-28 03:18:10 +00:00
if tx . db . env == nil {
return
}
if tx . tx == nil {
return
}
defer func ( ) {
tx . tx = nil
2021-02-10 17:04:22 +00:00
tx . db . wg . Done ( )
2021-03-23 07:28:04 +00:00
if ! tx . readOnly {
runtime . UnlockOSThread ( )
}
2020-10-28 03:18:10 +00:00
} ( )
tx . closeCursors ( )
2021-04-03 03:30:28 +00:00
//tx.printDebugInfo()
2020-10-28 03:18:10 +00:00
tx . tx . Abort ( )
}
2021-04-03 03:30:28 +00:00
//nolint
2021-05-06 03:51:49 +00:00
func ( tx * MdbxTx ) SpaceDirty ( ) ( uint64 , uint64 , error ) {
txInfo , err := tx . tx . Info ( true )
if err != nil {
return 0 , 0 , err
}
2021-02-09 02:31:37 +00:00
2021-05-06 03:51:49 +00:00
return txInfo . SpaceDirty , tx . db . txSize , nil
}
//nolint
func ( tx * MdbxTx ) PrintDebugInfo ( ) {
txInfo , err := tx . tx . Info ( true )
if err != nil {
panic ( err )
}
txSize := uint ( txInfo . SpaceDirty / 1024 )
doPrint := debug . BigRoTxKb ( ) == 0 && debug . BigRwTxKb ( ) == 0 ||
tx . readOnly && debug . BigRoTxKb ( ) > 0 && txSize > debug . BigRoTxKb ( ) ||
( ! tx . readOnly && debug . BigRwTxKb ( ) > 0 && txSize > debug . BigRwTxKb ( ) )
if doPrint {
2021-05-22 04:18:04 +00:00
tx . db . log . Info ( "Tx info" ,
2021-05-06 03:51:49 +00:00
"id" , txInfo . Id ,
"read_lag" , txInfo . ReadLag ,
"ro" , tx . readOnly ,
//"space_retired_mb", txInfo.SpaceRetired/1024/1024,
"space_dirty_mb" , txInfo . SpaceDirty / 1024 / 1024 ,
//"callers", debug.Callers(7),
)
2021-02-09 02:31:37 +00:00
}
}
2020-11-28 14:26:28 +00:00
func ( tx * MdbxTx ) closeCursors ( ) {
2020-10-28 03:18:10 +00:00
for _ , c := range tx . cursors {
if c != nil {
c . Close ( )
}
}
2021-06-04 13:32:48 +00:00
tx . cursors = nil
tx . statelessCursors = nil
2020-10-28 03:18:10 +00:00
}
2021-05-11 08:23:17 +00:00
func ( tx * MdbxTx ) statelessCursor ( bucket string ) ( RwCursor , error ) {
if tx . statelessCursors == nil {
tx . statelessCursors = make ( map [ string ] Cursor )
2021-04-02 06:36:49 +00:00
}
2021-05-11 08:23:17 +00:00
c , ok := tx . statelessCursors [ bucket ]
if ! ok {
var err error
c , err = tx . Cursor ( bucket )
2021-04-02 06:36:49 +00:00
if err != nil {
2021-05-11 08:23:17 +00:00
return nil , err
2021-04-02 06:36:49 +00:00
}
2021-05-11 08:23:17 +00:00
tx . statelessCursors [ bucket ] = c
2021-04-02 06:36:49 +00:00
}
2021-05-11 08:23:17 +00:00
return c . ( RwCursor ) , nil
}
2021-04-02 06:36:49 +00:00
2021-05-11 08:23:17 +00:00
func ( tx * MdbxTx ) Put ( bucket string , k , v [ ] byte ) error {
c , err := tx . statelessCursor ( bucket )
2021-04-02 06:36:49 +00:00
if err != nil {
return err
}
2021-05-11 08:23:17 +00:00
return c . Put ( k , v )
2021-04-02 06:36:49 +00:00
}
2021-05-11 08:23:17 +00:00
func ( tx * MdbxTx ) Delete ( bucket string , k , v [ ] byte ) error {
c , err := tx . statelessCursor ( bucket )
if err != nil {
return err
2020-10-28 03:18:10 +00:00
}
2021-05-11 08:23:17 +00:00
return c . Delete ( k , v )
}
2020-10-28 03:18:10 +00:00
2021-05-11 08:23:17 +00:00
func ( tx * MdbxTx ) GetOne ( bucket string , k [ ] byte ) ( [ ] byte , error ) {
c , err := tx . statelessCursor ( bucket )
2020-10-28 03:18:10 +00:00
if err != nil {
return nil , err
}
2021-05-11 08:23:17 +00:00
_ , v , err := c . SeekExact ( k )
return v , err
2020-10-28 03:18:10 +00:00
}
2021-04-03 12:04:58 +00:00
func ( tx * MdbxTx ) Has ( bucket string , key [ ] byte ) ( bool , error ) {
2021-05-11 08:23:17 +00:00
c , err := tx . statelessCursor ( bucket )
if err != nil {
return false , err
2020-10-28 03:18:10 +00:00
}
2021-05-11 08:23:17 +00:00
k , _ , err := c . Seek ( key )
if err != nil {
2020-10-28 03:18:10 +00:00
return false , err
}
2021-05-11 08:23:17 +00:00
return bytes . Equal ( key , k ) , nil
2020-10-28 03:18:10 +00:00
}
2021-06-04 13:32:48 +00:00
func ( tx * MdbxTx ) Append ( bucket string , k , v [ ] byte ) error {
c , err := tx . statelessCursor ( bucket )
if err != nil {
return err
}
return c . Append ( k , v )
}
func ( tx * MdbxTx ) AppendDup ( bucket string , k , v [ ] byte ) error {
c , err := tx . statelessCursor ( bucket )
if err != nil {
return err
}
return c . ( * MdbxDupSortCursor ) . AppendDup ( k , v )
}
2021-03-20 14:12:54 +00:00
func ( tx * MdbxTx ) IncrementSequence ( bucket string , amount uint64 ) ( uint64 , error ) {
2021-05-11 08:23:17 +00:00
c , err := tx . statelessCursor ( dbutils . Sequence )
if err != nil {
return 0 , err
}
2021-02-14 04:38:28 +00:00
_ , v , err := c . SeekExact ( [ ] byte ( bucket ) )
2021-05-11 08:23:17 +00:00
if err != nil {
2021-02-14 04:38:28 +00:00
return 0 , err
}
var currentV uint64 = 0
if len ( v ) > 0 {
currentV = binary . BigEndian . Uint64 ( v )
}
newVBytes := make ( [ ] byte , 8 )
binary . BigEndian . PutUint64 ( newVBytes , currentV + amount )
err = c . Put ( [ ] byte ( bucket ) , newVBytes )
if err != nil {
return 0 , err
}
return currentV , nil
}
2021-03-20 14:12:54 +00:00
func ( tx * MdbxTx ) ReadSequence ( bucket string ) ( uint64 , error ) {
2021-05-11 08:23:17 +00:00
c , err := tx . statelessCursor ( dbutils . Sequence )
if err != nil {
return 0 , err
}
2021-03-20 14:12:54 +00:00
_ , v , err := c . SeekExact ( [ ] byte ( bucket ) )
if err != nil && ! mdbx . IsNotFound ( err ) {
return 0 , err
}
var currentV uint64 = 0
if len ( v ) > 0 {
currentV = binary . BigEndian . Uint64 ( v )
}
return currentV , nil
}
2020-11-28 14:26:28 +00:00
func ( tx * MdbxTx ) BucketSize ( name string ) ( uint64 , error ) {
2021-02-09 04:42:10 +00:00
st , err := tx . BucketStat ( name )
2020-10-28 03:18:10 +00:00
if err != nil {
return 0 , err
}
return ( st . LeafPages + st . BranchPages + st . OverflowPages ) * uint64 ( os . Getpagesize ( ) ) , nil
}
2020-11-28 14:26:28 +00:00
func ( tx * MdbxTx ) BucketStat ( name string ) ( * mdbx . Stat , error ) {
2020-10-28 03:18:10 +00:00
if name == "freelist" || name == "gc" || name == "free_list" {
return tx . tx . StatDBI ( mdbx . DBI ( 0 ) )
}
if name == "root" {
return tx . tx . StatDBI ( mdbx . DBI ( 1 ) )
}
2021-02-21 18:41:59 +00:00
st , err := tx . tx . StatDBI ( mdbx . DBI ( tx . db . buckets [ name ] . DBI ) )
if err != nil {
return nil , fmt . Errorf ( "bucket: %s, %w" , name , err )
}
return st , nil
2020-10-28 03:18:10 +00:00
}
2021-04-02 06:36:49 +00:00
func ( tx * MdbxTx ) RwCursor ( bucket string ) ( RwCursor , error ) {
2020-10-28 03:18:10 +00:00
b := tx . db . buckets [ bucket ]
if b . AutoDupSortKeysConversion {
2021-04-02 09:15:41 +00:00
return tx . stdCursor ( bucket )
2020-10-28 03:18:10 +00:00
}
if b . Flags & dbutils . DupSort != 0 {
2021-03-21 13:15:25 +00:00
return tx . RwCursorDupSort ( bucket )
2020-10-28 03:18:10 +00:00
}
2021-04-02 09:15:41 +00:00
return tx . stdCursor ( bucket )
2020-10-28 03:18:10 +00:00
}
2021-04-02 06:36:49 +00:00
func ( tx * MdbxTx ) Cursor ( bucket string ) ( Cursor , error ) {
2021-05-19 04:21:17 +00:00
return tx . RwCursor ( bucket )
2021-03-21 13:15:25 +00:00
}
2021-04-02 09:15:41 +00:00
func ( tx * MdbxTx ) stdCursor ( bucket string ) ( RwCursor , error ) {
2020-10-28 03:18:10 +00:00
b := tx . db . buckets [ bucket ]
2021-06-04 13:32:48 +00:00
c := & MdbxCursor { bucketName : bucket , tx : tx , bucketCfg : b , dbi : mdbx . DBI ( tx . db . buckets [ bucket ] . DBI ) , id : tx . cursorID }
tx . cursorID ++
2021-04-02 09:15:41 +00:00
var err error
c . c , err = tx . tx . OpenCursor ( c . dbi )
if err != nil {
return nil , fmt . Errorf ( "table: %s, %w" , c . bucketName , err )
}
// add to auto-cleanup on end of transactions
if tx . cursors == nil {
2021-06-04 13:32:48 +00:00
tx . cursors = map [ uint64 ] * mdbx . Cursor { }
2021-04-02 09:15:41 +00:00
}
2021-06-04 13:32:48 +00:00
tx . cursors [ c . id ] = c . c
2021-04-02 09:15:41 +00:00
return c , nil
2020-10-28 03:18:10 +00:00
}
2021-04-02 06:36:49 +00:00
func ( tx * MdbxTx ) RwCursorDupSort ( bucket string ) ( RwCursorDupSort , error ) {
2021-04-02 09:15:41 +00:00
basicCursor , err := tx . stdCursor ( bucket )
if err != nil {
return nil , err
}
return & MdbxDupSortCursor { MdbxCursor : basicCursor . ( * MdbxCursor ) } , nil
2020-10-28 03:18:10 +00:00
}
2021-04-02 06:36:49 +00:00
func ( tx * MdbxTx ) CursorDupSort ( bucket string ) ( CursorDupSort , error ) {
2021-03-21 13:15:25 +00:00
return tx . RwCursorDupSort ( bucket )
}
2020-11-28 15:08:02 +00:00
func ( tx * MdbxTx ) CHandle ( ) unsafe . Pointer {
panic ( "not implemented yet" )
}
2020-10-28 03:18:10 +00:00
// methods here help to see better pprof picture
2021-03-20 09:35:02 +00:00
func ( c * MdbxCursor ) set ( k [ ] byte ) ( [ ] byte , [ ] byte , error ) { return c . c . Get ( k , nil , mdbx . Set ) }
func ( c * MdbxCursor ) getCurrent ( ) ( [ ] byte , [ ] byte , error ) { return c . c . Get ( nil , nil , mdbx . GetCurrent ) }
func ( c * MdbxCursor ) first ( ) ( [ ] byte , [ ] byte , error ) { return c . c . Get ( nil , nil , mdbx . First ) }
func ( c * MdbxCursor ) next ( ) ( [ ] byte , [ ] byte , error ) { return c . c . Get ( nil , nil , mdbx . Next ) }
func ( c * MdbxCursor ) nextDup ( ) ( [ ] byte , [ ] byte , error ) { return c . c . Get ( nil , nil , mdbx . NextDup ) }
func ( c * MdbxCursor ) nextNoDup ( ) ( [ ] byte , [ ] byte , error ) { return c . c . Get ( nil , nil , mdbx . NextNoDup ) }
func ( c * MdbxCursor ) prev ( ) ( [ ] byte , [ ] byte , error ) { return c . c . Get ( nil , nil , mdbx . Prev ) }
func ( c * MdbxCursor ) prevDup ( ) ( [ ] byte , [ ] byte , error ) { return c . c . Get ( nil , nil , mdbx . PrevDup ) }
func ( c * MdbxCursor ) prevNoDup ( ) ( [ ] byte , [ ] byte , error ) { return c . c . Get ( nil , nil , mdbx . PrevNoDup ) }
func ( c * MdbxCursor ) last ( ) ( [ ] byte , [ ] byte , error ) { return c . c . Get ( nil , nil , mdbx . Last ) }
func ( c * MdbxCursor ) delCurrent ( ) error { return c . c . Del ( mdbx . Current ) }
func ( c * MdbxCursor ) delNoDupData ( ) error { return c . c . Del ( mdbx . NoDupData ) }
func ( c * MdbxCursor ) put ( k , v [ ] byte ) error { return c . c . Put ( k , v , 0 ) }
func ( c * MdbxCursor ) putCurrent ( k , v [ ] byte ) error { return c . c . Put ( k , v , mdbx . Current ) }
func ( c * MdbxCursor ) putNoOverwrite ( k , v [ ] byte ) error { return c . c . Put ( k , v , mdbx . NoOverwrite ) }
func ( c * MdbxCursor ) putNoDupData ( k , v [ ] byte ) error { return c . c . Put ( k , v , mdbx . NoDupData ) }
func ( c * MdbxCursor ) append ( k , v [ ] byte ) error { return c . c . Put ( k , v , mdbx . Append ) }
func ( c * MdbxCursor ) appendDup ( k , v [ ] byte ) error { return c . c . Put ( k , v , mdbx . AppendDup ) }
2021-03-19 07:45:01 +00:00
func ( c * MdbxCursor ) getBoth ( k , v [ ] byte ) ( [ ] byte , error ) {
_ , v , err := c . c . Get ( k , v , mdbx . GetBoth )
return v , err
2020-10-28 03:18:10 +00:00
}
func ( c * MdbxCursor ) setRange ( k [ ] byte ) ( [ ] byte , [ ] byte , error ) {
return c . c . Get ( k , nil , mdbx . SetRange )
}
2021-03-19 07:45:01 +00:00
func ( c * MdbxCursor ) getBothRange ( k , v [ ] byte ) ( [ ] byte , error ) {
_ , v , err := c . c . Get ( k , v , mdbx . GetBothRange )
return v , err
2020-10-28 03:18:10 +00:00
}
func ( c * MdbxCursor ) firstDup ( ) ( [ ] byte , error ) {
_ , v , err := c . c . Get ( nil , nil , mdbx . FirstDup )
return v , err
}
2021-03-19 07:45:01 +00:00
func ( c * MdbxCursor ) lastDup ( ) ( [ ] byte , error ) {
_ , v , err := c . c . Get ( nil , nil , mdbx . LastDup )
2020-10-28 03:18:10 +00:00
return v , err
}
func ( c * MdbxCursor ) Count ( ) ( uint64 , error ) {
st , err := c . tx . tx . StatDBI ( c . dbi )
if err != nil {
return 0 , err
}
return st . Entries , nil
}
func ( c * MdbxCursor ) First ( ) ( [ ] byte , [ ] byte , error ) {
2021-05-02 03:49:00 +00:00
return c . Seek ( nil )
2020-10-28 03:18:10 +00:00
}
func ( c * MdbxCursor ) Last ( ) ( [ ] byte , [ ] byte , error ) {
k , v , err := c . last ( )
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil , nil , nil
}
err = fmt . Errorf ( "failed MdbxKV cursor.Last(): %w, bucket: %s" , err , c . bucketName )
return [ ] byte { } , nil , err
}
b := c . bucketCfg
if b . AutoDupSortKeysConversion && len ( k ) == b . DupToLen {
keyPart := b . DupFromLen - b . DupToLen
k = append ( k , v [ : keyPart ] ... )
v = v [ keyPart : ]
}
return k , v , nil
}
func ( c * MdbxCursor ) Seek ( seek [ ] byte ) ( k , v [ ] byte , err error ) {
if c . bucketCfg . AutoDupSortKeysConversion {
return c . seekDupSort ( seek )
}
if len ( seek ) == 0 {
k , v , err = c . first ( )
} else {
k , v , err = c . setRange ( seek )
}
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil , nil , nil
}
err = fmt . Errorf ( "failed MdbxKV cursor.Seek(): %w, bucket: %s, key: %x" , err , c . bucketName , seek )
return [ ] byte { } , nil , err
}
return k , v , nil
}
func ( c * MdbxCursor ) seekDupSort ( seek [ ] byte ) ( k , v [ ] byte , err error ) {
b := c . bucketCfg
from , to := b . DupFromLen , b . DupToLen
if len ( seek ) == 0 {
k , v , err = c . first ( )
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil , nil , nil
}
return [ ] byte { } , nil , err
}
2021-02-14 04:38:28 +00:00
if len ( k ) == to {
k2 := make ( [ ] byte , 0 , len ( k ) + from - to )
k2 = append ( append ( k2 , k ... ) , v [ : from - to ] ... )
v = v [ from - to : ]
k = k2
}
2020-10-28 03:18:10 +00:00
return k , v , nil
}
var seek1 , seek2 [ ] byte
if len ( seek ) > to {
seek1 , seek2 = seek [ : to ] , seek [ to : ]
} else {
seek1 = seek
}
k , v , err = c . setRange ( seek1 )
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil , nil , nil
}
return [ ] byte { } , nil , err
}
if seek2 != nil && bytes . Equal ( seek1 , k ) {
2021-03-19 07:45:01 +00:00
v , err = c . getBothRange ( seek1 , seek2 )
2020-10-28 03:18:10 +00:00
if err != nil && mdbx . IsNotFound ( err ) {
k , v , err = c . next ( )
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil , nil , nil
}
return [ ] byte { } , nil , err
}
} else if err != nil {
return [ ] byte { } , nil , err
}
}
if len ( k ) == to {
k2 := make ( [ ] byte , 0 , len ( k ) + from - to )
k2 = append ( append ( k2 , k ... ) , v [ : from - to ] ... )
v = v [ from - to : ]
k = k2
}
return k , v , nil
}
func ( c * MdbxCursor ) Next ( ) ( k , v [ ] byte , err error ) {
k , v , err = c . next ( )
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil , nil , nil
}
return [ ] byte { } , nil , fmt . Errorf ( "failed MdbxKV cursor.Next(): %w" , err )
}
b := c . bucketCfg
if b . AutoDupSortKeysConversion && len ( k ) == b . DupToLen {
keyPart := b . DupFromLen - b . DupToLen
k = append ( k , v [ : keyPart ] ... )
v = v [ keyPart : ]
}
return k , v , nil
}
func ( c * MdbxCursor ) Prev ( ) ( k , v [ ] byte , err error ) {
k , v , err = c . prev ( )
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil , nil , nil
}
return [ ] byte { } , nil , fmt . Errorf ( "failed MdbxKV cursor.Prev(): %w" , err )
}
b := c . bucketCfg
if b . AutoDupSortKeysConversion && len ( k ) == b . DupToLen {
keyPart := b . DupFromLen - b . DupToLen
k = append ( k , v [ : keyPart ] ... )
v = v [ keyPart : ]
}
return k , v , nil
}
// Current - return key/data at current cursor position
func ( c * MdbxCursor ) Current ( ) ( [ ] byte , [ ] byte , error ) {
k , v , err := c . getCurrent ( )
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil , nil , nil
}
return [ ] byte { } , nil , err
}
b := c . bucketCfg
if b . AutoDupSortKeysConversion && len ( k ) == b . DupToLen {
keyPart := b . DupFromLen - b . DupToLen
k = append ( k , v [ : keyPart ] ... )
v = v [ keyPart : ]
}
return k , v , nil
}
2020-10-29 13:19:31 +00:00
func ( c * MdbxCursor ) Delete ( k , v [ ] byte ) error {
2020-10-28 03:18:10 +00:00
if c . bucketCfg . AutoDupSortKeysConversion {
2020-10-29 13:19:31 +00:00
return c . deleteDupSort ( k )
2020-10-28 03:18:10 +00:00
}
2020-10-30 08:38:25 +00:00
if c . bucketCfg . Flags & mdbx . DupSort != 0 {
2021-03-19 07:45:01 +00:00
_ , err := c . getBoth ( k , v )
2020-10-29 13:19:31 +00:00
if err != nil {
2020-10-30 08:38:25 +00:00
if mdbx . IsNotFound ( err ) {
2020-10-29 13:19:31 +00:00
return nil
}
return err
}
return c . delCurrent ( )
}
_ , _ , err := c . set ( k )
2020-10-28 03:18:10 +00:00
if err != nil {
2020-10-30 08:38:25 +00:00
if mdbx . IsNotFound ( err ) {
2020-10-28 03:18:10 +00:00
return nil
}
return err
}
return c . delCurrent ( )
}
// DeleteCurrent This function deletes the key/data pair to which the cursor refers.
// This does not invalidate the cursor, so operations such as MDB_NEXT
// can still be used on it.
// Both MDB_NEXT and MDB_GET_CURRENT will return the same record after
// this operation.
func ( c * MdbxCursor ) DeleteCurrent ( ) error {
return c . delCurrent ( )
}
func ( c * MdbxCursor ) deleteDupSort ( key [ ] byte ) error {
b := c . bucketCfg
from , to := b . DupFromLen , b . DupToLen
if len ( key ) != from && len ( key ) >= to {
2021-02-21 18:41:59 +00:00
return fmt . Errorf ( "delete from dupsort bucket: %s, can have keys of len==%d and len<%d. key: %x,%d" , c . bucketName , from , to , key , len ( key ) )
2020-10-28 03:18:10 +00:00
}
if len ( key ) == from {
2021-03-19 07:45:01 +00:00
v , err := c . getBothRange ( key [ : to ] , key [ to : ] )
2020-10-28 03:18:10 +00:00
if err != nil { // if key not found, or found another one - then nothing to delete
if mdbx . IsNotFound ( err ) {
return nil
}
return err
}
if ! bytes . Equal ( v [ : from - to ] , key [ to : ] ) {
return nil
}
return c . delCurrent ( )
}
_ , _ , err := c . set ( key )
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil
}
return err
}
return c . delCurrent ( )
}
func ( c * MdbxCursor ) PutNoOverwrite ( key [ ] byte , value [ ] byte ) error {
if len ( key ) == 0 {
return fmt . Errorf ( "mdbx doesn't support empty keys. bucket: %s" , c . bucketName )
}
if c . bucketCfg . AutoDupSortKeysConversion {
panic ( "not implemented" )
}
return c . putNoOverwrite ( key , value )
}
func ( c * MdbxCursor ) Put ( key [ ] byte , value [ ] byte ) error {
if len ( key ) == 0 {
return fmt . Errorf ( "mdbx doesn't support empty keys. bucket: %s" , c . bucketName )
}
2021-02-14 04:38:28 +00:00
2020-10-28 03:18:10 +00:00
b := c . bucketCfg
if b . AutoDupSortKeysConversion {
2020-12-16 14:35:14 +00:00
if err := c . putDupSort ( key , value ) ; err != nil {
return err
}
return nil
2020-10-28 03:18:10 +00:00
}
2020-12-16 14:35:14 +00:00
if err := c . put ( key , value ) ; err != nil {
return err
}
return nil
2020-10-28 03:18:10 +00:00
}
func ( c * MdbxCursor ) putDupSort ( key [ ] byte , value [ ] byte ) error {
b := c . bucketCfg
from , to := b . DupFromLen , b . DupToLen
if len ( key ) != from && len ( key ) >= to {
2021-02-21 18:41:59 +00:00
return fmt . Errorf ( "put dupsort bucket: %s, can have keys of len==%d and len<%d. key: %x,%d" , c . bucketName , from , to , key , len ( key ) )
2020-10-28 03:18:10 +00:00
}
if len ( key ) != from {
2020-11-02 21:07:58 +00:00
err := c . putNoOverwrite ( key , value )
2020-10-28 03:18:10 +00:00
if err != nil {
2020-11-02 21:07:58 +00:00
if mdbx . IsKeyExists ( err ) {
return c . putCurrent ( key , value )
2020-10-28 03:18:10 +00:00
}
2020-12-16 14:35:14 +00:00
return fmt . Errorf ( "putNoOverwrite, bucket: %s, key: %x, val: %x, err: %w" , c . bucketName , key , value , err )
2020-10-28 03:18:10 +00:00
}
2020-11-02 21:07:58 +00:00
return nil
2020-10-28 03:18:10 +00:00
}
value = append ( key [ to : ] , value ... )
key = key [ : to ]
2021-03-19 07:45:01 +00:00
v , err := c . getBothRange ( key , value [ : from - to ] )
2020-10-28 03:18:10 +00:00
if err != nil { // if key not found, or found another one - then just insert
if mdbx . IsNotFound ( err ) {
return c . put ( key , value )
}
return err
}
if bytes . Equal ( v [ : from - to ] , value [ : from - to ] ) {
if len ( v ) == len ( value ) { // in DupSort case mdbx.Current works only with values of same length
return c . putCurrent ( key , value )
}
err = c . delCurrent ( )
if err != nil {
return err
}
}
return c . put ( key , value )
}
2020-11-16 12:08:28 +00:00
func ( c * MdbxCursor ) SeekExact ( key [ ] byte ) ( [ ] byte , [ ] byte , error ) {
2020-10-28 03:18:10 +00:00
b := c . bucketCfg
if b . AutoDupSortKeysConversion && len ( key ) == b . DupFromLen {
from , to := b . DupFromLen , b . DupToLen
2021-03-19 07:45:01 +00:00
v , err := c . getBothRange ( key [ : to ] , key [ to : ] )
2020-10-28 03:18:10 +00:00
if err != nil {
if mdbx . IsNotFound ( err ) {
2020-11-16 12:08:28 +00:00
return nil , nil , nil
2020-10-28 03:18:10 +00:00
}
2020-11-16 12:08:28 +00:00
return [ ] byte { } , nil , err
2020-10-28 03:18:10 +00:00
}
if ! bytes . Equal ( key [ to : ] , v [ : from - to ] ) {
2020-11-16 12:08:28 +00:00
return nil , nil , nil
2020-10-28 03:18:10 +00:00
}
2021-03-19 07:45:01 +00:00
return key [ : to ] , v [ from - to : ] , nil
2020-10-28 03:18:10 +00:00
}
2021-02-09 02:31:37 +00:00
k , v , err := c . set ( key )
2020-10-28 03:18:10 +00:00
if err != nil {
if mdbx . IsNotFound ( err ) {
2020-11-16 12:08:28 +00:00
return nil , nil , nil
2020-10-28 03:18:10 +00:00
}
2020-11-16 12:08:28 +00:00
return [ ] byte { } , nil , err
2020-10-28 03:18:10 +00:00
}
2021-02-09 02:31:37 +00:00
return k , v , nil
2020-10-28 03:18:10 +00:00
}
// Append - speedy feature of mdbx which is not part of KV interface.
// Cast your cursor to *MdbxCursor to use this method.
// Return error - if provided data will not sorted (or bucket have old records which mess with new in sorting manner).
func ( c * MdbxCursor ) Append ( k [ ] byte , v [ ] byte ) error {
if len ( k ) == 0 {
return fmt . Errorf ( "mdbx doesn't support empty keys. bucket: %s" , c . bucketName )
}
b := c . bucketCfg
if b . AutoDupSortKeysConversion {
from , to := b . DupFromLen , b . DupToLen
if len ( k ) != from && len ( k ) >= to {
2021-02-21 18:41:59 +00:00
return fmt . Errorf ( "append dupsort bucket: %s, can have keys of len==%d and len<%d. key: %x,%d" , c . bucketName , from , to , k , len ( k ) )
2020-10-28 03:18:10 +00:00
}
if len ( k ) == from {
v = append ( k [ to : ] , v ... )
k = k [ : to ]
}
}
if b . Flags & mdbx . DupSort != 0 {
2021-02-01 13:57:41 +00:00
if err := c . appendDup ( k , v ) ; err != nil {
return fmt . Errorf ( "bucket: %s, %w" , c . bucketName , err )
}
return nil
2020-10-28 03:18:10 +00:00
}
2021-02-01 13:57:41 +00:00
if err := c . append ( k , v ) ; err != nil {
return fmt . Errorf ( "bucket: %s, %w" , c . bucketName , err )
}
return nil
2020-10-28 03:18:10 +00:00
}
func ( c * MdbxCursor ) Close ( ) {
if c . c != nil {
c . c . Close ( )
2021-06-04 13:32:48 +00:00
delete ( c . tx . cursors , c . id )
2020-10-28 03:18:10 +00:00
c . c = nil
}
}
type MdbxDupSortCursor struct {
* MdbxCursor
}
2020-11-28 14:26:28 +00:00
func ( c * MdbxDupSortCursor ) Internal ( ) * mdbx . Cursor {
return c . c
}
2020-10-28 03:18:10 +00:00
// DeleteExact - does delete
func ( c * MdbxDupSortCursor ) DeleteExact ( k1 , k2 [ ] byte ) error {
2021-03-19 07:45:01 +00:00
_ , err := c . getBoth ( k1 , k2 )
2020-10-28 03:18:10 +00:00
if err != nil { // if key not found, or found another one - then nothing to delete
if mdbx . IsNotFound ( err ) {
return nil
}
return err
}
return c . delCurrent ( )
}
func ( c * MdbxDupSortCursor ) SeekBothExact ( key , value [ ] byte ) ( [ ] byte , [ ] byte , error ) {
2021-03-19 07:45:01 +00:00
v , err := c . getBoth ( key , value )
2020-10-28 03:18:10 +00:00
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil , nil , nil
}
return [ ] byte { } , nil , fmt . Errorf ( "in SeekBothExact: %w" , err )
}
2021-03-19 07:45:01 +00:00
return key , v , nil
2020-10-28 03:18:10 +00:00
}
2021-03-19 07:45:01 +00:00
func ( c * MdbxDupSortCursor ) SeekBothRange ( key , value [ ] byte ) ( [ ] byte , error ) {
v , err := c . getBothRange ( key , value )
2020-10-28 03:18:10 +00:00
if err != nil {
if mdbx . IsNotFound ( err ) {
2021-03-19 07:45:01 +00:00
return nil , nil
2020-10-28 03:18:10 +00:00
}
2021-03-19 07:45:01 +00:00
return nil , fmt . Errorf ( "in SeekBothRange: %w" , err )
2020-10-28 03:18:10 +00:00
}
2021-03-19 07:45:01 +00:00
return v , nil
2020-10-28 03:18:10 +00:00
}
func ( c * MdbxDupSortCursor ) FirstDup ( ) ( [ ] byte , error ) {
v , err := c . firstDup ( )
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil , nil
}
return nil , fmt . Errorf ( "in FirstDup: %w" , err )
}
return v , nil
}
// NextDup - iterate only over duplicates of current key
func ( c * MdbxDupSortCursor ) NextDup ( ) ( [ ] byte , [ ] byte , error ) {
k , v , err := c . nextDup ( )
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil , nil , nil
}
return [ ] byte { } , nil , fmt . Errorf ( "in NextDup: %w" , err )
}
return k , v , nil
}
// NextNoDup - iterate with skipping all duplicates
func ( c * MdbxDupSortCursor ) NextNoDup ( ) ( [ ] byte , [ ] byte , error ) {
k , v , err := c . nextNoDup ( )
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil , nil , nil
}
return [ ] byte { } , nil , fmt . Errorf ( "in NextNoDup: %w" , err )
}
return k , v , nil
}
func ( c * MdbxDupSortCursor ) PrevDup ( ) ( [ ] byte , [ ] byte , error ) {
k , v , err := c . prevDup ( )
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil , nil , nil
}
return [ ] byte { } , nil , fmt . Errorf ( "in PrevDup: %w" , err )
}
return k , v , nil
}
func ( c * MdbxDupSortCursor ) PrevNoDup ( ) ( [ ] byte , [ ] byte , error ) {
k , v , err := c . prevNoDup ( )
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil , nil , nil
}
return [ ] byte { } , nil , fmt . Errorf ( "in PrevNoDup: %w" , err )
}
return k , v , nil
}
2021-03-19 07:45:01 +00:00
func ( c * MdbxDupSortCursor ) LastDup ( ) ( [ ] byte , error ) {
v , err := c . lastDup ( )
2020-10-28 03:18:10 +00:00
if err != nil {
if mdbx . IsNotFound ( err ) {
return nil , nil
}
return nil , fmt . Errorf ( "in LastDup: %w" , err )
}
return v , nil
}
2020-11-28 14:24:47 +00:00
func ( c * MdbxDupSortCursor ) Append ( k [ ] byte , v [ ] byte ) error {
if err := c . c . Put ( k , v , mdbx . Append | mdbx . AppendDup ) ; err != nil {
2021-02-01 13:57:41 +00:00
return fmt . Errorf ( "in Append: bucket=%s, %w" , c . bucketName , err )
2020-11-28 14:24:47 +00:00
}
return nil
}
2020-10-28 03:18:10 +00:00
func ( c * MdbxDupSortCursor ) AppendDup ( k [ ] byte , v [ ] byte ) error {
if err := c . appendDup ( k , v ) ; err != nil {
2021-02-01 13:57:41 +00:00
return fmt . Errorf ( "in AppendDup: bucket=%s, %w" , c . bucketName , err )
2020-10-28 03:18:10 +00:00
}
return nil
}
func ( c * MdbxDupSortCursor ) PutNoDupData ( key , value [ ] byte ) error {
if err := c . putNoDupData ( key , value ) ; err != nil {
return fmt . Errorf ( "in PutNoDupData: %w" , err )
}
return nil
}
// DeleteCurrentDuplicates - delete all of the data items for the current key.
func ( c * MdbxDupSortCursor ) DeleteCurrentDuplicates ( ) error {
if err := c . delNoDupData ( ) ; err != nil {
return fmt . Errorf ( "in DeleteCurrentDuplicates: %w" , err )
}
return nil
}
// Count returns the number of duplicates for the current key. See mdb_cursor_count
func ( c * MdbxDupSortCursor ) CountDuplicates ( ) ( uint64 , error ) {
res , err := c . c . Count ( )
if err != nil {
return 0 , fmt . Errorf ( "in CountDuplicates: %w" , err )
}
return res , nil
}
2021-06-16 10:57:58 +00:00
func bucketSlice ( b dbutils . BucketsCfg ) [ ] string {
buckets := make ( [ ] string , 0 , len ( b ) )
for name := range b {
buckets = append ( buckets , name )
}
sort . Slice ( buckets , func ( i , j int ) bool {
return strings . Compare ( buckets [ i ] , buckets [ j ] ) < 0
} )
return buckets
}