github.com/ledgerwatch/erigon-lib@v1.0.0/kv/kv_interface.go (about)

     1  /*
     2     Copyright 2022 Erigon contributors
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package kv
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  
    24  	"github.com/VictoriaMetrics/metrics"
    25  	"github.com/ledgerwatch/erigon-lib/kv/iter"
    26  	"github.com/ledgerwatch/erigon-lib/kv/order"
    27  )
    28  
    29  //Variables Naming:
    30  //  tx - Database Transaction
    31  //  txn - Ethereum Transaction (and TxNum - is also number of Etherum Transaction)
    32  //  blockNum - Ethereum block number - same across all nodes. blockID - auto-increment ID - which can be differrent across all nodes
    33  //  txNum/txID - same
    34  //  RoTx - Read-Only Database Transaction. RwTx - read-write
    35  //  k, v - key, value
    36  //  ts - TimeStamp. Usually it's Etherum's TransactionNumber (auto-increment ID). Or BlockNumber.
    37  //  Cursor - low-level mdbx-tide api to navigate over Table
    38  //  Iter - high-level iterator-like api over Table/InvertedIndex/History/Domain. Has less features than Cursor. See package `iter`.
    39  
    40  //Methods Naming:
    41  //  Prune: delete old data
    42  //  Unwind: delete recent data
    43  //  Get: exact match of criterias
    44  //  Range: [from, to). from=nil means StartOfTable, to=nil means EndOfTable, rangeLimit=-1 means Unlimited
    45  //      Range is analog of SQL's: SELECT * FROM Table WHERE k>=from AND k<to ORDER BY k ASC/DESC LIMIT n
    46  //  Prefix: `Range(Table, prefix, kv.NextSubtree(prefix))`
    47  
    48  //Abstraction Layers:
    49  // LowLevel:
    50  //    1. DB/Tx - low-level key-value database
    51  //    2. Snapshots/FrozenData - immutable files with historical data. May be downloaded at first App
    52  //         start or auto-generate by moving old data from DB to Snapshots.
    53  //         Most important difference between DB and Snapshots: creation of
    54  //         snapshot files (build/merge) doesn't mutate any existing files - only producing new one!
    55  //         It means we don't need concept of "RwTx" for Snapshots.
    56  //         Files can become useless/garbage (merged to bigger file) - last reader of this file will
    57  //         remove it from FileSystem on tx.Rollback().
    58  //         Invariant: existing readers can't see new files, new readers can't see garbage files
    59  //
    60  // MediumLevel:
    61  //    1. TemporalDB - abstracting DB+Snapshots. Target is:
    62  //         - provide 'time-travel' API for data: consistan snapshot of data as of given Timestamp.
    63  //         - auto-close iterators on Commit/Rollback
    64  //         - auto-open/close agg.MakeContext() on Begin/Commit/Rollback
    65  //         - to keep DB small - only for Hot/Recent data (can be update/delete by re-org).
    66  //         - And TemporalRoTx/TemporalRwTx actaully open Read-Only files view (MakeContext) - no concept of "Read-Write view of snapshot files".
    67  //         - using next entities:
    68  //               - InvertedIndex: supports range-scans
    69  //               - History: can return value of key K as of given TimeStamp. Doesn't know about latest/current
    70  //                   value of key K. Returns NIL if K not changed after TimeStamp.
    71  //               - Domain: as History but also aware about latest/current value of key K. Can move
    72  //                   cold (updated long time ago) parts of state from db to snapshots.
    73  
    74  // HighLevel:
    75  //      1. Application - rely on TemporalDB (Ex: ExecutionLayer) or just DB (Ex: TxPool, Sentry, Downloader).
    76  
    77  const ReadersLimit = 32000 // MDBX_READERS_LIMIT=32767
    78  
    79  // const Unbounded []byte = nil
    80  const Unlim int = -1
    81  
    82  var (
    83  	ErrAttemptToDeleteNonDeprecatedBucket = errors.New("only buckets from dbutils.ChaindataDeprecatedTables can be deleted")
    84  
    85  	DbSize    = metrics.GetOrCreateCounter(`db_size`)    //nolint
    86  	TxLimit   = metrics.GetOrCreateCounter(`tx_limit`)   //nolint
    87  	TxSpill   = metrics.GetOrCreateCounter(`tx_spill`)   //nolint
    88  	TxUnspill = metrics.GetOrCreateCounter(`tx_unspill`) //nolint
    89  	TxDirty   = metrics.GetOrCreateCounter(`tx_dirty`)   //nolint
    90  
    91  	DbCommitPreparation = metrics.GetOrCreateSummary(`db_commit_seconds{phase="preparation"}`) //nolint
    92  	//DbGCWallClock       = metrics.GetOrCreateSummary(`db_commit_seconds{phase="gc_wall_clock"}`) //nolint
    93  	//DbGCCpuTime         = metrics.GetOrCreateSummary(`db_commit_seconds{phase="gc_cpu_time"}`)   //nolint
    94  	//DbCommitAudit       = metrics.GetOrCreateSummary(`db_commit_seconds{phase="audit"}`)         //nolint
    95  	DbCommitWrite  = metrics.GetOrCreateSummary(`db_commit_seconds{phase="write"}`)  //nolint
    96  	DbCommitSync   = metrics.GetOrCreateSummary(`db_commit_seconds{phase="sync"}`)   //nolint
    97  	DbCommitEnding = metrics.GetOrCreateSummary(`db_commit_seconds{phase="ending"}`) //nolint
    98  	DbCommitTotal  = metrics.GetOrCreateSummary(`db_commit_seconds{phase="total"}`)  //nolint
    99  
   100  	DbPgopsNewly   = metrics.GetOrCreateCounter(`db_pgops{phase="newly"}`)   //nolint
   101  	DbPgopsCow     = metrics.GetOrCreateCounter(`db_pgops{phase="cow"}`)     //nolint
   102  	DbPgopsClone   = metrics.GetOrCreateCounter(`db_pgops{phase="clone"}`)   //nolint
   103  	DbPgopsSplit   = metrics.GetOrCreateCounter(`db_pgops{phase="split"}`)   //nolint
   104  	DbPgopsMerge   = metrics.GetOrCreateCounter(`db_pgops{phase="merge"}`)   //nolint
   105  	DbPgopsSpill   = metrics.GetOrCreateCounter(`db_pgops{phase="spill"}`)   //nolint
   106  	DbPgopsUnspill = metrics.GetOrCreateCounter(`db_pgops{phase="unspill"}`) //nolint
   107  	DbPgopsWops    = metrics.GetOrCreateCounter(`db_pgops{phase="wops"}`)    //nolint
   108  	/*
   109  		DbPgopsPrefault = metrics.NewCounter(`db_pgops{phase="prefault"}`) //nolint
   110  		DbPgopsMinicore = metrics.NewCounter(`db_pgops{phase="minicore"}`) //nolint
   111  		DbPgopsMsync    = metrics.NewCounter(`db_pgops{phase="msync"}`)    //nolint
   112  		DbPgopsFsync    = metrics.NewCounter(`db_pgops{phase="fsync"}`)    //nolint
   113  		DbMiLastPgNo    = metrics.NewCounter(`db_mi_last_pgno`)            //nolint
   114  
   115  		DbGcWorkRtime    = metrics.GetOrCreateSummary(`db_gc_seconds{phase="work_rtime"}`) //nolint
   116  		DbGcWorkRsteps   = metrics.NewCounter(`db_gc{phase="work_rsteps"}`)                //nolint
   117  		DbGcWorkRxpages  = metrics.NewCounter(`db_gc{phase="work_rxpages"}`)               //nolint
   118  		DbGcSelfRtime    = metrics.GetOrCreateSummary(`db_gc_seconds{phase="self_rtime"}`) //nolint
   119  		DbGcSelfXtime    = metrics.GetOrCreateSummary(`db_gc_seconds{phase="self_xtime"}`) //nolint
   120  		DbGcWorkXtime    = metrics.GetOrCreateSummary(`db_gc_seconds{phase="work_xtime"}`) //nolint
   121  		DbGcSelfRsteps   = metrics.NewCounter(`db_gc{phase="self_rsteps"}`)                //nolint
   122  		DbGcWloops       = metrics.NewCounter(`db_gc{phase="wloop"}`)                      //nolint
   123  		DbGcCoalescences = metrics.NewCounter(`db_gc{phase="coalescences"}`)               //nolint
   124  		DbGcWipes        = metrics.NewCounter(`db_gc{phase="wipes"}`)                      //nolint
   125  		DbGcFlushes      = metrics.NewCounter(`db_gc{phase="flushes"}`)                    //nolint
   126  		DbGcKicks        = metrics.NewCounter(`db_gc{phase="kicks"}`)                      //nolint
   127  		DbGcWorkMajflt   = metrics.NewCounter(`db_gc{phase="work_majflt"}`)                //nolint
   128  		DbGcSelfMajflt   = metrics.NewCounter(`db_gc{phase="self_majflt"}`)                //nolint
   129  		DbGcWorkCounter  = metrics.NewCounter(`db_gc{phase="work_counter"}`)               //nolint
   130  		DbGcSelfCounter  = metrics.NewCounter(`db_gc{phase="self_counter"}`)               //nolint
   131  		DbGcSelfXpages   = metrics.NewCounter(`db_gc{phase="self_xpages"}`)                //nolint
   132  	*/
   133  
   134  	//DbGcWorkPnlMergeTime   = metrics.GetOrCreateSummary(`db_gc_pnl_seconds{phase="work_merge_time"}`) //nolint
   135  	//DbGcWorkPnlMergeVolume = metrics.NewCounter(`db_gc_pnl{phase="work_merge_volume"}`)               //nolint
   136  	//DbGcWorkPnlMergeCalls  = metrics.NewCounter(`db_gc{phase="work_merge_calls"}`)                    //nolint
   137  	//DbGcSelfPnlMergeTime   = metrics.GetOrCreateSummary(`db_gc_pnl_seconds{phase="slef_merge_time"}`) //nolint
   138  	//DbGcSelfPnlMergeVolume = metrics.NewCounter(`db_gc_pnl{phase="self_merge_volume"}`)               //nolint
   139  	//DbGcSelfPnlMergeCalls  = metrics.NewCounter(`db_gc_pnl{phase="slef_merge_calls"}`)                //nolint
   140  
   141  	GcLeafMetric     = metrics.GetOrCreateCounter(`db_gc_leaf`)     //nolint
   142  	GcOverflowMetric = metrics.GetOrCreateCounter(`db_gc_overflow`) //nolint
   143  	GcPagesMetric    = metrics.GetOrCreateCounter(`db_gc_pages`)    //nolint
   144  
   145  )
   146  
   147  type DBVerbosityLvl int8
   148  type Label uint8
   149  
   150  const (
   151  	ChainDB      Label = 0
   152  	TxPoolDB     Label = 1
   153  	SentryDB     Label = 2
   154  	ConsensusDB  Label = 3
   155  	DownloaderDB Label = 4
   156  	InMem        Label = 5
   157  )
   158  
   159  func (l Label) String() string {
   160  	switch l {
   161  	case ChainDB:
   162  		return "chaindata"
   163  	case TxPoolDB:
   164  		return "txpool"
   165  	case SentryDB:
   166  		return "sentry"
   167  	case ConsensusDB:
   168  		return "consensus"
   169  	case DownloaderDB:
   170  		return "downloader"
   171  	case InMem:
   172  		return "inMem"
   173  	default:
   174  		return "unknown"
   175  	}
   176  }
   177  func UnmarshalLabel(s string) Label {
   178  	switch s {
   179  	case "chaindata":
   180  		return ChainDB
   181  	case "txpool":
   182  		return TxPoolDB
   183  	case "sentry":
   184  		return SentryDB
   185  	case "consensus":
   186  		return ConsensusDB
   187  	case "downloader":
   188  		return DownloaderDB
   189  	case "inMem":
   190  		return InMem
   191  	default:
   192  		panic(fmt.Sprintf("unexpected label: %s", s))
   193  	}
   194  }
   195  
   196  type Has interface {
   197  	// Has indicates whether a key exists in the database.
   198  	Has(table string, key []byte) (bool, error)
   199  }
   200  type GetPut interface {
   201  	Getter
   202  	Putter
   203  }
   204  type Getter interface {
   205  	Has
   206  
   207  	// GetOne references a readonly section of memory that must not be accessed after txn has terminated
   208  	GetOne(table string, key []byte) (val []byte, err error)
   209  
   210  	// ForEach iterates over entries with keys greater or equal to fromPrefix.
   211  	// walker is called for each eligible entry.
   212  	// If walker returns an error:
   213  	//   - implementations of local db - stop
   214  	//   - implementations of remote db - do not handle this error and may finish (send all entries to client) before error happen.
   215  	ForEach(table string, fromPrefix []byte, walker func(k, v []byte) error) error
   216  	ForPrefix(table string, prefix []byte, walker func(k, v []byte) error) error
   217  	ForAmount(table string, prefix []byte, amount uint32, walker func(k, v []byte) error) error
   218  }
   219  
   220  // Putter wraps the database write operations.
   221  type Putter interface {
   222  	// Put inserts or updates a single entry.
   223  	Put(table string, k, v []byte) error
   224  }
   225  
   226  // Deleter wraps the database delete operations.
   227  type Deleter interface {
   228  	// Delete removes a single entry.
   229  	Delete(table string, k []byte) error
   230  }
   231  
   232  type Closer interface {
   233  	Close()
   234  }
   235  
   236  // RoDB - Read-only version of KV.
   237  type RoDB interface {
   238  	Closer
   239  	ReadOnly() bool
   240  	View(ctx context.Context, f func(tx Tx) error) error
   241  
   242  	// BeginRo - creates transaction
   243  	// 	tx may be discarded by .Rollback() method
   244  	//
   245  	// A transaction and its cursors must only be used by a single
   246  	// 	thread (not goroutine), and a thread may only have a single transaction at a time.
   247  	//  It happen automatically by - because this method calls runtime.LockOSThread() inside (Rollback/Commit releases it)
   248  	//  By this reason application code can't call runtime.UnlockOSThread() - it leads to undefined behavior.
   249  	//
   250  	// If this `parent` is non-NULL, the new transaction
   251  	//	will be a nested transaction, with the transaction indicated by parent
   252  	//	as its parent. Transactions may be nested to any level. A parent
   253  	//	transaction and its cursors may not issue any other operations than
   254  	//	Commit and Rollback while it has active child transactions.
   255  	BeginRo(ctx context.Context) (Tx, error)
   256  	AllTables() TableCfg
   257  	PageSize() uint64
   258  }
   259  
   260  // RwDB low-level database interface - main target is - to provide common abstraction over top of MDBX and RemoteKV.
   261  //
   262  // Common pattern for short-living transactions:
   263  //
   264  //	 if err := db.View(ctx, func(tx ethdb.Tx) error {
   265  //	    ... code which uses database in transaction
   266  //	 }); err != nil {
   267  //			return err
   268  //	}
   269  //
   270  // Common pattern for long-living transactions:
   271  //
   272  //	tx, err := db.Begin()
   273  //	if err != nil {
   274  //		return err
   275  //	}
   276  //	defer tx.Rollback()
   277  //
   278  //	... code which uses database in transaction
   279  //
   280  //	err := tx.Commit()
   281  //	if err != nil {
   282  //		return err
   283  //	}
   284  type RwDB interface {
   285  	RoDB
   286  
   287  	Update(ctx context.Context, f func(tx RwTx) error) error
   288  	UpdateNosync(ctx context.Context, f func(tx RwTx) error) error
   289  
   290  	BeginRw(ctx context.Context) (RwTx, error)
   291  	BeginRwNosync(ctx context.Context) (RwTx, error)
   292  }
   293  
   294  type StatelessReadTx interface {
   295  	Getter
   296  
   297  	Commit() error // Commit all the operations of a transaction into the database.
   298  	Rollback()     // Rollback - abandon all the operations of the transaction instead of saving them.
   299  
   300  	// ReadSequence - allows to create a linear sequence of unique positive integers for each table.
   301  	// Can be called for a read transaction to retrieve the current sequence value, and the increment must be zero.
   302  	// Sequence changes become visible outside the current write transaction after it is committed, and discarded on abort.
   303  	// Starts from 0.
   304  	ReadSequence(table string) (uint64, error)
   305  
   306  	BucketSize(table string) (uint64, error)
   307  }
   308  
   309  type StatelessWriteTx interface {
   310  	Putter
   311  	Deleter
   312  
   313  	/*
   314  		// if need N id's:
   315  		baseId, err := tx.IncrementSequence(bucket, N)
   316  		if err != nil {
   317  		   return err
   318  		}
   319  		for i := 0; i < N; i++ {    // if N == 0, it will work as expected
   320  		    id := baseId + i
   321  		    // use id
   322  		}
   323  
   324  
   325  		// or if need only 1 id:
   326  		id, err := tx.IncrementSequence(bucket, 1)
   327  		if err != nil {
   328  		    return err
   329  		}
   330  		// use id
   331  	*/
   332  	IncrementSequence(table string, amount uint64) (uint64, error)
   333  	Append(table string, k, v []byte) error
   334  	AppendDup(table string, k, v []byte) error
   335  }
   336  
   337  type StatelessRwTx interface {
   338  	StatelessReadTx
   339  	StatelessWriteTx
   340  }
   341  
   342  // Tx
   343  // WARNING:
   344  //   - Tx is not threadsafe and may only be used in the goroutine that created it
   345  //   - ReadOnly transactions do not lock goroutine to thread, RwTx does
   346  type Tx interface {
   347  	StatelessReadTx
   348  	BucketMigratorRO
   349  
   350  	// ID returns the identifier associated with this transaction. For a
   351  	// read-only transaction, this corresponds to the snapshot being read;
   352  	// concurrent readers will frequently have the same transaction ID.
   353  	ViewID() uint64
   354  
   355  	// Cursor - creates cursor object on top of given bucket. Type of cursor - depends on bucket configuration.
   356  	// If bucket was created with mdbx.DupSort flag, then cursor with interface CursorDupSort created
   357  	// Otherwise - object of interface Cursor created
   358  	//
   359  	// Cursor, also provides a grain of magic - it can use a declarative configuration - and automatically break
   360  	// long keys into DupSort key/values. See docs for `bucket.go:TableCfgItem`
   361  	Cursor(table string) (Cursor, error)
   362  	CursorDupSort(table string) (CursorDupSort, error) // CursorDupSort - can be used if bucket has mdbx.DupSort flag
   363  
   364  	DBSize() (uint64, error)
   365  
   366  	// --- High-Level methods: 1request -> stream of server-side pushes ---
   367  
   368  	// Range [from, to)
   369  	// Range(from, nil) means [from, EndOfTable)
   370  	// Range(nil, to)   means [StartOfTable, to)
   371  	Range(table string, fromPrefix, toPrefix []byte) (iter.KV, error)
   372  	// Stream is like Range, but for requesting huge data (Example: full table scan). Client can't stop it.
   373  	//Stream(table string, fromPrefix, toPrefix []byte) (iter.KV, error)
   374  	// RangeAscend - like Range [from, to) but also allow pass Limit parameters
   375  	// Limit -1 means Unlimited
   376  	RangeAscend(table string, fromPrefix, toPrefix []byte, limit int) (iter.KV, error)
   377  	//StreamAscend(table string, fromPrefix, toPrefix []byte, limit int) (iter.KV, error)
   378  	// RangeDescend - is like Range [from, to), but expecing `from`<`to`
   379  	// example: RangeDescend("Table", "B", "A", -1)
   380  	RangeDescend(table string, fromPrefix, toPrefix []byte, limit int) (iter.KV, error)
   381  	//StreamDescend(table string, fromPrefix, toPrefix []byte, limit int) (iter.KV, error)
   382  	// Prefix - is exactly Range(Table, prefix, kv.NextSubtree(prefix))
   383  	Prefix(table string, prefix []byte) (iter.KV, error)
   384  
   385  	// RangeDupSort - like Range but for fixed single key and iterating over range of values
   386  	RangeDupSort(table string, key []byte, fromPrefix, toPrefix []byte, asc order.By, limit int) (iter.KV, error)
   387  
   388  	// --- High-Level methods: 1request -> 1page of values in response -> send next page request ---
   389  	// Paginate(table string, fromPrefix, toPrefix []byte) (PairsStream, error)
   390  
   391  	// --- High-Level deprecated methods ---
   392  
   393  	ForEach(table string, fromPrefix []byte, walker func(k, v []byte) error) error
   394  	ForPrefix(table string, prefix []byte, walker func(k, v []byte) error) error
   395  	ForAmount(table string, prefix []byte, amount uint32, walker func(k, v []byte) error) error
   396  }
   397  
   398  // RwTx
   399  //
   400  // WARNING:
   401  //   - RwTx is not threadsafe and may only be used in the goroutine that created it.
   402  //   - ReadOnly transactions do not lock goroutine to thread, RwTx does
   403  //   - User Can't call runtime.LockOSThread/runtime.UnlockOSThread in same goroutine until RwTx Commit/Rollback
   404  type RwTx interface {
   405  	Tx
   406  	StatelessWriteTx
   407  	BucketMigrator
   408  
   409  	RwCursor(table string) (RwCursor, error)
   410  	RwCursorDupSort(table string) (RwCursorDupSort, error)
   411  
   412  	// CollectMetrics - does collect all DB-related and Tx-related metrics
   413  	// this method exists only in RwTx to avoid concurrency
   414  	CollectMetrics()
   415  }
   416  
   417  type BucketMigratorRO interface {
   418  	ListBuckets() ([]string, error)
   419  }
   420  
   421  // BucketMigrator used for buckets migration, don't use it in usual app code
   422  type BucketMigrator interface {
   423  	BucketMigratorRO
   424  	DropBucket(string) error
   425  	CreateBucket(string) error
   426  	ExistsBucket(string) (bool, error)
   427  	ClearBucket(string) error
   428  }
   429  
   430  // Cursor - class for navigating through a database
   431  // CursorDupSort are inherit this class
   432  //
   433  // If methods (like First/Next/Seek) return error, then returned key SHOULD not be nil (can be []byte{} for example).
   434  // Then looping code will look as:
   435  // c := kv.Cursor(bucketName)
   436  //
   437  //	for k, v, err := c.First(); k != nil; k, v, err = c.Next() {
   438  //	   if err != nil {
   439  //	       return err
   440  //	   }
   441  //	   ... logic
   442  //	}
   443  type Cursor interface {
   444  	First() ([]byte, []byte, error)               // First - position at first key/data item
   445  	Seek(seek []byte) ([]byte, []byte, error)     // Seek - position at first key greater than or equal to specified key
   446  	SeekExact(key []byte) ([]byte, []byte, error) // SeekExact - position at exact matching key if exists
   447  	Next() ([]byte, []byte, error)                // Next - position at next key/value (can iterate over DupSort key/values automatically)
   448  	Prev() ([]byte, []byte, error)                // Prev - position at previous key
   449  	Last() ([]byte, []byte, error)                // Last - position at last key and last possible value
   450  	Current() ([]byte, []byte, error)             // Current - return key/data at current cursor position
   451  
   452  	Count() (uint64, error) // Count - fast way to calculate amount of keys in bucket. It counts all keys even if Prefix was set.
   453  
   454  	Close()
   455  }
   456  
   457  type RwCursor interface {
   458  	Cursor
   459  
   460  	Put(k, v []byte) error           // Put - based on order
   461  	Append(k []byte, v []byte) error // Append - append the given key/data pair to the end of the database. This option allows fast bulk loading when keys are already known to be in the correct order.
   462  	Delete(k []byte) error           // Delete - short version of SeekExact+DeleteCurrent or SeekBothExact+DeleteCurrent
   463  
   464  	// DeleteCurrent This function deletes the key/data pair to which the cursor refers.
   465  	// This does not invalidate the cursor, so operations such as MDB_NEXT
   466  	// can still be used on it.
   467  	// Both MDB_NEXT and MDB_GET_CURRENT will return the same record after
   468  	// this operation.
   469  	DeleteCurrent() error
   470  }
   471  
   472  // CursorDupSort
   473  //
   474  // Example:
   475  //
   476  //	for k, v, err = cursor.First(); k != nil; k, v, err = cursor.NextNoDup() {
   477  //		if err != nil {
   478  //			return err
   479  //		}
   480  //		for ; v != nil; _, v, err = cursor.NextDup() {
   481  //			if err != nil {
   482  //				return err
   483  //			}
   484  //
   485  //		}
   486  //	}
   487  type CursorDupSort interface {
   488  	Cursor
   489  
   490  	// SeekBothExact -
   491  	// second parameter can be nil only if searched key has no duplicates, or return error
   492  	SeekBothExact(key, value []byte) ([]byte, []byte, error)
   493  	SeekBothRange(key, value []byte) ([]byte, error) // SeekBothRange - exact match of the key, but range match of the value
   494  	FirstDup() ([]byte, error)                       // FirstDup - position at first data item of current key
   495  	NextDup() ([]byte, []byte, error)                // NextDup - position at next data item of current key
   496  	NextNoDup() ([]byte, []byte, error)              // NextNoDup - position at first data item of next key
   497  	PrevDup() ([]byte, []byte, error)
   498  	PrevNoDup() ([]byte, []byte, error)
   499  	LastDup() ([]byte, error) // LastDup - position at last data item of current key
   500  
   501  	CountDuplicates() (uint64, error) // CountDuplicates - number of duplicates for the current key
   502  }
   503  
   504  type RwCursorDupSort interface {
   505  	CursorDupSort
   506  	RwCursor
   507  
   508  	PutNoDupData(key, value []byte) error // PutNoDupData - inserts key without dupsort
   509  	DeleteCurrentDuplicates() error       // DeleteCurrentDuplicates - deletes all of the data items for the current key
   510  	DeleteExact(k1, k2 []byte) error      // DeleteExact - delete 1 value from given key
   511  	AppendDup(key, value []byte) error    // AppendDup - same as Append, but for sorted dup data
   512  }
   513  
   514  // ---- Temporal part
   515  
   516  type (
   517  	Domain      string
   518  	History     string
   519  	InvertedIdx string
   520  )
   521  
   522  type TemporalTx interface {
   523  	Tx
   524  	DomainGet(name Domain, k, k2 []byte) (v []byte, ok bool, err error)
   525  	DomainGetAsOf(name Domain, k, k2 []byte, ts uint64) (v []byte, ok bool, err error)
   526  	HistoryGet(name History, k []byte, ts uint64) (v []byte, ok bool, err error)
   527  
   528  	// IndexRange - return iterator over range of inverted index for given key `k`
   529  	// Asc semantic:  [from, to) AND from > to
   530  	// Desc semantic: [from, to) AND from < to
   531  	// Limit -1 means Unlimited
   532  	// from -1, to -1 means unbounded (StartOfTable, EndOfTable)
   533  	// Example: IndexRange("IndexName", 10, 5, order.Desc, -1)
   534  	// Example: IndexRange("IndexName", -1, -1, order.Asc, 10)
   535  	IndexRange(name InvertedIdx, k []byte, fromTs, toTs int, asc order.By, limit int) (timestamps iter.U64, err error)
   536  	HistoryRange(name History, fromTs, toTs int, asc order.By, limit int) (it iter.KV, err error)
   537  	DomainRange(name Domain, fromKey, toKey []byte, ts uint64, asc order.By, limit int) (it iter.KV, err error)
   538  }