nimbus-eth1/nimbus/db/core_db/base.nim

781 lines
26 KiB
Nim

# Nimbus
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.
{.push raises: [].}
import
std/typetraits,
eth/common,
"../.."/[constants, errors],
".."/[kvt, aristo],
./backend/aristo_db,
./base/[api_tracking, base_config, base_desc, base_helpers]
export
CoreDbAccRef,
CoreDbAccount,
CoreDbApiError,
CoreDbCtxRef,
CoreDbErrorCode,
CoreDbError,
CoreDbKvtRef,
CoreDbMptRef,
CoreDbPersistentTypes,
CoreDbRef,
CoreDbTxRef,
CoreDbType
when CoreDbEnableApiTracking:
import
chronicles
logScope:
topics = "core_db"
const
logTxt = "API"
when CoreDbEnableProfiling:
export
CoreDbFnInx,
CoreDbProfListRef
when CoreDbEnableCaptJournal and false:
import
./backend/aristo_trace
type
CoreDbCaptRef* = distinct TraceLogInstRef
func `$`(p: CoreDbCaptRef): string =
if p.distinctBase.isNil: "<nil>" else: "<capt>"
else:
import
../aristo/[
aristo_delete, aristo_desc, aristo_fetch, aristo_merge, aristo_tx],
../kvt/[kvt_desc, kvt_utils, kvt_tx]
# ------------------------------------------------------------------------------
# Public context constructors and administration
# ------------------------------------------------------------------------------
proc ctx*(db: CoreDbRef): CoreDbCtxRef =
## Get the defauly context. This is a base descriptor which provides the
## KVT, MPT, the accounts descriptors as well as the transaction descriptor.
## They are kept all in sync, i.e. `persistent()` will store exactly this
## context.
##
db.defCtx
proc swapCtx*(db: CoreDbRef; ctx: CoreDbCtxRef): CoreDbCtxRef =
## Activate argument context `ctx` as default and return the previously
## active context. This function goes typically together with `forget()`. A
## valid scenario might look like
## ::
## proc doSomething(db: CoreDbRef; ctx: CoreDbCtxRef) =
## let saved = db.swapCtx ctx
## defer: db.swapCtx(saved).forget()
## ...
##
doAssert not ctx.isNil
db.setTrackNewApi BaseSwapCtxFn
result = db.defCtx
# Set read-write access and install
CoreDbAccRef(ctx).call(reCentre, db.ctx.mpt).isOkOr:
raiseAssert $api & " failed: " & $error
CoreDbKvtRef(ctx).call(reCentre, db.ctx.kvt).isOkOr:
raiseAssert $api & " failed: " & $error
db.defCtx = ctx
db.ifTrackNewApi: debug logTxt, api, elapsed
proc forget*(ctx: CoreDbCtxRef) =
## Dispose `ctx` argument context and related columns created with this
## context. This function fails if `ctx` is the default context.
##
ctx.setTrackNewApi CtxForgetFn
CoreDbAccRef(ctx).call(forget, ctx.mpt).isOkOr:
raiseAssert $api & ": " & $error
CoreDbKvtRef(ctx).call(forget, ctx.kvt).isOkOr:
raiseAssert $api & ": " & $error
ctx.ifTrackNewApi: debug logTxt, api, elapsed
# ------------------------------------------------------------------------------
# Public main descriptor methods
# ------------------------------------------------------------------------------
proc finish*(db: CoreDbRef; eradicate = false) =
## Database destructor. If the argument `eradicate` is set `false`, the
## database is left as-is and only the in-memory handlers are cleaned up.
##
## Otherwise the destructor is allowed to remove the database. This feature
## depends on the backend database. Currently, only the `AristoDbRocks` type
## backend removes the database on `true`.
##
db.setTrackNewApi BaseFinishFn
CoreDbKvtRef(db.ctx).call(finish, db.ctx.kvt, eradicate)
CoreDbAccRef(db.ctx).call(finish, db.ctx.mpt, eradicate)
db.ifTrackNewApi: debug logTxt, api, elapsed
proc `$$`*(e: CoreDbError): string =
## Pretty print error symbol, note that this directive may have side effects
## as it calls a backend function.
##
e.toStr()
proc persistent*(
db: CoreDbRef;
blockNumber: BlockNumber;
): CoreDbRc[void]
{.discardable.} =
## This function stored cached data from the default context (see `ctx()`
## below) to the persistent database.
##
## It also stores the argument block number `blockNumber` as a state record
## which can be retrieved via `stateBlockNumber()`.
##
db.setTrackNewApi BasePersistentFn
block body:
block:
let rc = CoreDbKvtRef(db.ctx).call(persist, db.ctx.kvt)
if rc.isOk or rc.error == TxPersistDelayed:
# The latter clause is OK: Piggybacking on `Aristo` backend
discard
elif CoreDbKvtRef(db.ctx).call(level, db.ctx.kvt) != 0:
result = err(rc.error.toError($api, TxPending))
break body
else:
result = err(rc.error.toError $api)
break body
block:
let rc = CoreDbAccRef(db.ctx).call(persist, db.ctx.mpt, blockNumber)
if rc.isOk:
discard
elif CoreDbAccRef(db.ctx).call(level, db.ctx.mpt) != 0:
result = err(rc.error.toError($api, TxPending))
break body
else:
result = err(rc.error.toError $api)
break body
result = ok()
db.ifTrackNewApi: debug logTxt, api, elapsed, blockNumber, result
proc stateBlockNumber*(db: CoreDbRef): BlockNumber =
## Rhis function returns the block number stored with the latest `persist()`
## directive.
##
db.setTrackNewApi BaseStateBlockNumberFn
result = block:
let rc = CoreDbAccRef(db.ctx).call(fetchLastSavedState, db.ctx.mpt)
if rc.isOk:
rc.value.serial.BlockNumber
else:
0u64
db.ifTrackNewApi: debug logTxt, api, elapsed, result
# ------------------------------------------------------------------------------
# Public key-value table methods
# ------------------------------------------------------------------------------
proc getKvt*(ctx: CoreDbCtxRef): CoreDbKvtRef =
## This function retrieves the common base object shared with other KVT
## descriptors. Any changes are immediately visible to subscribers.
## On destruction (when the constructed object gets out of scope), changes
## are not saved to the backend database but are still cached and available.
##
CoreDbKvtRef(ctx)
# ----------- KVT ---------------
proc get*(kvt: CoreDbKvtRef; key: openArray[byte]): CoreDbRc[Blob] =
## This function always returns a non-empty `Blob` or an error code.
kvt.setTrackNewApi KvtGetFn
result = block:
let rc = kvt.call(get, kvt.kvt, key)
if rc.isOk:
ok(rc.value)
elif rc.error == GetNotFound:
err(rc.error.toError($api, KvtNotFound))
else:
err(rc.error.toError $api)
kvt.ifTrackNewApi: debug logTxt, api, elapsed, key=key.toStr, result
proc getOrEmpty*(kvt: CoreDbKvtRef; key: openArray[byte]): CoreDbRc[Blob] =
## Variant of `get()` returning an empty `Blob` if the key is not found
## on the database.
##
kvt.setTrackNewApi KvtGetOrEmptyFn
result = block:
let rc = kvt.call(get, kvt.kvt, key)
if rc.isOk:
ok(rc.value)
elif rc.error == GetNotFound:
CoreDbRc[Blob].ok(EmptyBlob)
else:
err(rc.error.toError $api)
kvt.ifTrackNewApi: debug logTxt, api, elapsed, key=key.toStr, result
proc len*(kvt: CoreDbKvtRef; key: openArray[byte]): CoreDbRc[int] =
## This function returns the size of the value associated with `key`.
kvt.setTrackNewApi KvtLenFn
result = block:
let rc = kvt.call(len, kvt.kvt, key)
if rc.isOk:
ok(rc.value)
elif rc.error == GetNotFound:
err(rc.error.toError($api, KvtNotFound))
else:
err(rc.error.toError $api)
kvt.ifTrackNewApi: debug logTxt, api, elapsed, key=key.toStr, result
proc del*(kvt: CoreDbKvtRef; key: openArray[byte]): CoreDbRc[void] =
kvt.setTrackNewApi KvtDelFn
result = block:
let rc = kvt.call(del, kvt.kvt, key)
if rc.isOk:
ok()
else:
err(rc.error.toError $api)
kvt.ifTrackNewApi: debug logTxt, api, elapsed, key=key.toStr, result
proc put*(
kvt: CoreDbKvtRef;
key: openArray[byte];
val: openArray[byte];
): CoreDbRc[void] =
kvt.setTrackNewApi KvtPutFn
result = block:
let rc = kvt.call(put, kvt.kvt, key, val)
if rc.isOk:
ok()
else:
err(rc.error.toError $api)
kvt.ifTrackNewApi:
debug logTxt, api, elapsed, key=key.toStr, val=val.toLenStr, result
proc hasKey*(kvt: CoreDbKvtRef; key: openArray[byte]): CoreDbRc[bool] =
## Would be named `contains` if it returned `bool` rather than `Result[]`.
##
kvt.setTrackNewApi KvtHasKeyFn
result = block:
let rc = kvt.call(hasKey, kvt.kvt, key)
if rc.isOk:
ok(rc.value)
else:
err(rc.error.toError $api)
kvt.ifTrackNewApi: debug logTxt, api, elapsed, key=key.toStr, result
# ------------------------------------------------------------------------------
# Public functions for generic columns
# ------------------------------------------------------------------------------
proc getGeneric*(
ctx: CoreDbCtxRef;
clearData = false;
): CoreDbMptRef =
## Get a generic MPT, viewed as column
##
ctx.setTrackNewApi CtxGetGenericFn
result = CoreDbMptRef(ctx)
if clearData:
result.call(deleteGenericTree, ctx.mpt, CoreDbVidGeneric).isOkOr:
raiseAssert $api & ": " & $error
ctx.ifTrackNewApi: debug logTxt, api, clearData, elapsed
# ----------- generic MPT ---------------
proc fetch*(mpt: CoreDbMptRef; key: openArray[byte]): CoreDbRc[Blob] =
## Fetch data from the argument `mpt`. The function always returns a
## non-empty `Blob` or an error code.
##
mpt.setTrackNewApi MptFetchFn
result = block:
let rc = mpt.call(fetchGenericData, mpt.mpt, CoreDbVidGeneric, key)
if rc.isOk:
ok(rc.value)
elif rc.error == FetchPathNotFound:
err(rc.error.toError($api, MptNotFound))
else:
err(rc.error.toError $api)
mpt.ifTrackNewApi: debug logTxt, api, elapsed, key=key.toStr, result
proc fetchOrEmpty*(mpt: CoreDbMptRef; key: openArray[byte]): CoreDbRc[Blob] =
## This function returns an empty `Blob` if the argument `key` is not found
## on the database.
##
mpt.setTrackNewApi MptFetchOrEmptyFn
result = block:
let rc = mpt.call(fetchGenericData, mpt.mpt, CoreDbVidGeneric, key)
if rc.isOk:
ok(rc.value)
elif rc.error == FetchPathNotFound:
CoreDbRc[Blob].ok(EmptyBlob)
else:
err(rc.error.toError $api)
mpt.ifTrackNewApi: debug logTxt, api, elapsed, key=key.toStr, result
proc delete*(mpt: CoreDbMptRef; key: openArray[byte]): CoreDbRc[void] =
mpt.setTrackNewApi MptDeleteFn
result = block:
let rc = mpt.call(deleteGenericData, mpt.mpt,CoreDbVidGeneric, key)
if rc.isOk:
ok()
elif rc.error == DelPathNotFound:
err(rc.error.toError($api, MptNotFound))
else:
err(rc.error.toError $api)
mpt.ifTrackNewApi: debug logTxt, api, elapsed, key=key.toStr, result
proc merge*(
mpt: CoreDbMptRef;
key: openArray[byte];
val: openArray[byte];
): CoreDbRc[void] =
mpt.setTrackNewApi MptMergeFn
result = block:
let rc = mpt.call(mergeGenericData, mpt.mpt,CoreDbVidGeneric, key, val)
if rc.isOk:
ok()
else:
err(rc.error.toError $api)
mpt.ifTrackNewApi:
debug logTxt, api, elapsed, key=key.toStr, val=val.toLenStr, result
proc hasPath*(mpt: CoreDbMptRef; key: openArray[byte]): CoreDbRc[bool] =
## This function would be named `contains()` if it returned `bool` rather
## than a `Result[]`.
##
mpt.setTrackNewApi MptHasPathFn
result = block:
let rc = mpt.call(hasPathGeneric, mpt.mpt, CoreDbVidGeneric, key)
if rc.isOk:
ok(rc.value)
else:
err(rc.error.toError $api)
mpt.ifTrackNewApi: debug logTxt, api, elapsed, key=key.toStr, result
proc state*(mpt: CoreDbMptRef; updateOk = false): CoreDbRc[Hash256] =
## This function retrieves the Merkle state hash of the argument
## database column (if acvailable.)
##
## If the argument `updateOk` is set `true`, the Merkle hashes of the
## database will be updated first (if needed, at all).
##
mpt.setTrackNewApi MptStateFn
result = block:
let rc = mpt.call(fetchGenericState, mpt.mpt, CoreDbVidGeneric, updateOk)
if rc.isOk:
ok(rc.value)
else:
err(rc.error.toError $api)
mpt.ifTrackNewApi: debug logTxt, api, elapsed, updateOK, result
# ------------------------------------------------------------------------------
# Public methods for accounts
# ------------------------------------------------------------------------------
proc getAccounts*(ctx: CoreDbCtxRef): CoreDbAccRef =
## Accounts column constructor, will defect on failure.
##
ctx.setTrackNewApi CtxGetAccountsFn
result = CoreDbAccRef(ctx)
ctx.ifTrackNewApi: debug logTxt, api, elapsed
# ----------- accounts ---------------
proc fetch*(
acc: CoreDbAccRef;
accPath: Hash256;
): CoreDbRc[CoreDbAccount] =
## Fetch the account data record for the particular account indexed by
## the key `accPath`.
##
acc.setTrackNewApi AccFetchFn
result = block:
let rc = acc.call(fetchAccountRecord, acc.mpt, accPath)
if rc.isOk:
ok(rc.value)
elif rc.error == FetchPathNotFound:
err(rc.error.toError($api, AccNotFound))
else:
err(rc.error.toError $api)
acc.ifTrackNewApi: debug logTxt, api, elapsed, accPath=($$accPath), result
proc delete*(
acc: CoreDbAccRef;
accPath: Hash256;
): CoreDbRc[void] =
## Delete the particular account indexed by the key `accPath`. This
## will also destroy an associated storage area.
##
acc.setTrackNewApi AccDeleteFn
result = block:
let rc = acc.call(deleteAccountRecord, acc.mpt, accPath)
if rc.isOk:
ok()
elif rc.error == DelPathNotFound:
# TODO: Would it be conseqient to just return `ok()` here?
err(rc.error.toError($api, AccNotFound))
else:
err(rc.error.toError $api)
acc.ifTrackNewApi:
debug logTxt, api, elapsed, accPath=($$accPath), result
proc clearStorage*(
acc: CoreDbAccRef;
accPath: Hash256;
): CoreDbRc[void] =
## Delete all data slots from the storage area associated with the
## particular account indexed by the key `accPath`.
##
acc.setTrackNewApi AccClearStorageFn
result = block:
let rc = acc.call(deleteStorageTree, acc.mpt, accPath)
if rc.isOk or rc.error in {DelStoRootMissing,DelStoAccMissing}:
ok()
else:
err(rc.error.toError $api)
acc.ifTrackNewApi:
debug logTxt, api, elapsed, accPath=($$accPath), result
proc merge*(
acc: CoreDbAccRef;
accPath: Hash256;
accRec: CoreDbAccount;
): CoreDbRc[void] =
## Add or update the argument account data record `account`. Note that the
## `account` argument uniquely idendifies the particular account address.
##
acc.setTrackNewApi AccMergeFn
result = block:
let rc = acc.call(mergeAccountRecord, acc.mpt, accPath, accRec)
if rc.isOk:
ok()
else:
err(rc.error.toError $api)
acc.ifTrackNewApi:
debug logTxt, api, elapsed, accPath=($$accPath), result
proc hasPath*(
acc: CoreDbAccRef;
accPath: Hash256;
): CoreDbRc[bool] =
## Would be named `contains` if it returned `bool` rather than `Result[]`.
##
acc.setTrackNewApi AccHasPathFn
result = block:
let rc = acc.call(hasPathAccount, acc.mpt, accPath)
if rc.isOk:
ok(rc.value)
else:
err(rc.error.toError $api)
acc.ifTrackNewApi:
debug logTxt, api, elapsed, accPath=($$accPath), result
proc state*(acc: CoreDbAccRef; updateOk = false): CoreDbRc[Hash256] =
## This function retrieves the Merkle state hash of the accounts
## column (if available.)
##
## If the argument `updateOk` is set `true`, the Merkle hashes of the
## database will be updated first (if needed, at all).
##
acc.setTrackNewApi AccStateFn
result = block:
let rc = acc.call(fetchAccountState, acc.mpt, updateOk)
if rc.isOk:
ok(rc.value)
else:
err(rc.error.toError $api)
acc.ifTrackNewApi: debug logTxt, api, elapsed, updateOK, result
# ------------ storage ---------------
proc slotFetch*(
acc: CoreDbAccRef;
accPath: Hash256;
stoPath: Hash256;
): CoreDbRc[UInt256] =
## Like `fetch()` but with cascaded index `(accPath,slot)`.
acc.setTrackNewApi AccSlotFetchFn
result = block:
let rc = acc.call(fetchStorageData, acc.mpt, accPath, stoPath)
if rc.isOk:
ok(rc.value)
elif rc.error == FetchPathNotFound:
err(rc.error.toError($api, StoNotFound))
else:
err(rc.error.toError $api)
acc.ifTrackNewApi:
debug logTxt, api, elapsed, accPath=($$accPath),
stoPath=($$stoPath), result
proc slotDelete*(
acc: CoreDbAccRef;
accPath: Hash256;
stoPath: Hash256;
): CoreDbRc[void] =
## Like `delete()` but with cascaded index `(accPath,slot)`.
acc.setTrackNewApi AccSlotDeleteFn
result = block:
let rc = acc.call(deleteStorageData, acc.mpt, accPath, stoPath)
if rc.isOk or rc.error == DelStoRootMissing:
# The second `if` clause is insane but legit: A storage column was
# announced for an account but no data have been added, yet.
ok()
elif rc.error == DelPathNotFound:
err(rc.error.toError($api, StoNotFound))
else:
err(rc.error.toError $api)
acc.ifTrackNewApi:
debug logTxt, api, elapsed, accPath=($$accPath),
stoPath=($$stoPath), result
proc slotHasPath*(
acc: CoreDbAccRef;
accPath: Hash256;
stoPath: Hash256;
): CoreDbRc[bool] =
## Like `hasPath()` but with cascaded index `(accPath,slot)`.
acc.setTrackNewApi AccSlotHasPathFn
result = block:
let rc = acc.call(hasPathStorage, acc.mpt, accPath, stoPath)
if rc.isOk:
ok(rc.value)
else:
err(rc.error.toError $api)
acc.ifTrackNewApi:
debug logTxt, api, elapsed, accPath=($$accPath),
stoPath=($$stoPath), result
proc slotMerge*(
acc: CoreDbAccRef;
accPath: Hash256;
stoPath: Hash256;
stoData: UInt256;
): CoreDbRc[void] =
## Like `merge()` but with cascaded index `(accPath,slot)`.
acc.setTrackNewApi AccSlotMergeFn
result = block:
let rc = acc.call(mergeStorageData, acc.mpt, accPath, stoPath, stoData)
if rc.isOk:
ok()
else:
err(rc.error.toError $api)
acc.ifTrackNewApi:
debug logTxt, api, elapsed, accPath=($$accPath),
stoPath=($$stoPath), stoData, result
proc slotState*(
acc: CoreDbAccRef;
accPath: Hash256;
updateOk = false;
): CoreDbRc[Hash256] =
## This function retrieves the Merkle state hash of the storage data
## column (if available) related to the account indexed by the key
## `accPath`.`.
##
## If the argument `updateOk` is set `true`, the Merkle hashes of the
## database will be updated first (if needed, at all).
##
acc.setTrackNewApi AccSlotStateFn
result = block:
let rc = acc.call(fetchStorageState, acc.mpt, accPath, updateOk)
if rc.isOk:
ok(rc.value)
else:
err(rc.error.toError $api)
acc.ifTrackNewApi:
debug logTxt, api, elapsed, accPath=($$accPath), updateOk, result
proc slotStateEmpty*(
acc: CoreDbAccRef;
accPath: Hash256;
): CoreDbRc[bool] =
## This function returns `true` if the storage data column is empty or
## missing.
##
acc.setTrackNewApi AccSlotStateEmptyFn
result = block:
let rc = acc.call(hasStorageData, acc.mpt, accPath)
if rc.isOk:
ok(not rc.value)
else:
err(rc.error.toError $api)
acc.ifTrackNewApi:
debug logTxt, api, elapsed, accPath=($$accPath), result
proc slotStateEmptyOrVoid*(
acc: CoreDbAccRef;
accPath: Hash256;
): bool =
## Convenience wrapper, returns `true` where `slotStateEmpty()` would fail.
acc.setTrackNewApi AccSlotStateEmptyOrVoidFn
result = block:
let rc = acc.call(hasStorageData, acc.mpt, accPath)
if rc.isOk:
not rc.value
else:
true
acc.ifTrackNewApi:
debug logTxt, api, elapsed, accPath=($$accPath), result
# ------------- other ----------------
proc recast*(
acc: CoreDbAccRef;
accPath: Hash256;
accRec: CoreDbAccount;
updateOk = false;
): CoreDbRc[Account] =
## Complete the argument `accRec` to the portable Ethereum representation
## of an account statement. This conversion may fail if the storage colState
## hash (see `slotState()` above) is currently unavailable.
##
acc.setTrackNewApi AccRecastFn
let rc = acc.call(fetchStorageState, acc.mpt, accPath, updateOk)
result = block:
if rc.isOk:
ok Account(
nonce: accRec.nonce,
balance: accRec.balance,
codeHash: accRec.codeHash,
storageRoot: rc.value)
else:
err(rc.error.toError $api)
acc.ifTrackNewApi:
let slotState = if rc.isOk: $$(rc.value) else: "n/a"
debug logTxt, api, elapsed, accPath=($$accPath), slotState, result
# ------------------------------------------------------------------------------
# Public transaction related methods
# ------------------------------------------------------------------------------
proc level*(db: CoreDbRef): int =
## Retrieve transaction level (zero if there is no pending transaction).
##
db.setTrackNewApi BaseLevelFn
result = CoreDbAccRef(db.ctx).call(level, db.ctx.mpt)
db.ifTrackNewApi: debug logTxt, api, elapsed, result
proc newTransaction*(ctx: CoreDbCtxRef): CoreDbTxRef =
## Constructor
##
ctx.setTrackNewApi BaseNewTxFn
let
kTx = CoreDbKvtRef(ctx).call(txBegin, ctx.kvt).valueOr:
raiseAssert $api & ": " & $error
aTx = CoreDbAccRef(ctx).call(txBegin, ctx.mpt).valueOr:
raiseAssert $api & ": " & $error
result = ctx.bless CoreDbTxRef(kTx: kTx, aTx: aTx)
ctx.ifTrackNewApi:
let newLevel = CoreDbAccRef(ctx).call(level, ctx.mpt)
debug logTxt, api, elapsed, newLevel
proc level*(tx: CoreDbTxRef): int =
## Print positive transaction level for argument `tx`
##
tx.setTrackNewApi TxLevelFn
result = CoreDbAccRef(tx.ctx).call(txLevel, tx.aTx)
tx.ifTrackNewApi: debug logTxt, api, elapsed, result
proc commit*(tx: CoreDbTxRef) =
tx.setTrackNewApi TxCommitFn:
let prvLevel {.used.} = CoreDbAccRef(tx.ctx).call(txLevel, tx.aTx)
CoreDbAccRef(tx.ctx).call(commit, tx.aTx).isOkOr:
raiseAssert $api & ": " & $error
CoreDbKvtRef(tx.ctx).call(commit, tx.kTx).isOkOr:
raiseAssert $api & ": " & $error
tx.ifTrackNewApi: debug logTxt, api, elapsed, prvLevel
proc rollback*(tx: CoreDbTxRef) =
tx.setTrackNewApi TxRollbackFn:
let prvLevel {.used.} = CoreDbAccRef(tx.ctx).call(txLevel, tx.aTx)
CoreDbAccRef(tx.ctx).call(rollback, tx.aTx).isOkOr:
raiseAssert $api & ": " & $error
CoreDbKvtRef(tx.ctx).call(rollback, tx.kTx).isOkOr:
raiseAssert $api & ": " & $error
tx.ifTrackNewApi: debug logTxt, api, elapsed, prvLevel
proc dispose*(tx: CoreDbTxRef) =
tx.setTrackNewApi TxDisposeFn:
let prvLevel {.used.} = CoreDbAccRef(tx.ctx).call(txLevel, tx.aTx)
if CoreDbAccRef(tx.ctx).call(isTop, tx.aTx):
CoreDbAccRef(tx.ctx).call(rollback, tx.aTx).isOkOr:
raiseAssert $api & ": " & $error
if CoreDbKvtRef(tx.ctx).call(isTop, tx.kTx):
CoreDbKvtRef(tx.ctx).call(rollback, tx.kTx).isOkOr:
raiseAssert $api & ": " & $error
tx.ifTrackNewApi: debug logTxt, api, elapsed, prvLevel
# ------------------------------------------------------------------------------
# Public tracer methods
# ------------------------------------------------------------------------------
when CoreDbEnableCaptJournal and false: # currently disabled
proc newCapture*(
db: CoreDbRef;
): CoreDbRc[CoreDbCaptRef] =
## Trace constructor providing an overlay on top of the argument database
## `db`. This overlay provides a replacement database handle that can be
## retrieved via `db.recorder()` (which can in turn be ovelayed.) While
## running the overlay stores data in a log-table which can be retrieved
## via `db.logDb()`.
##
## Caveat:
## The original database argument `db` should not be used while the tracer
## is active (i.e. exists as overlay). The behaviour for this situation
## is undefined and depends on the backend implementation of the tracer.
##
db.setTrackNewApi BaseNewCaptureFn
result = db.methods.newCaptureFn flags
db.ifTrackNewApi: debug logTxt, api, elapsed, result
proc recorder*(cpt: CoreDbCaptRef): CoreDbRef =
## Getter, returns a tracer replacement handle to be used as new database.
## It records every action like fetch, store, hasKey, hasPath and delete.
## This descriptor can be superseded by a new overlay tracer (using
## `newCapture()`, again.)
##
## Caveat:
## Unless the desriptor `cpt` referes to the top level overlay tracer, the
## result is undefined and depends on the backend implementation of the
## tracer.
##
cpt.setTrackNewApi CptRecorderFn
result = cpt.methods.recorderFn()
cpt.ifTrackNewApi: debug logTxt, api, elapsed
proc logDb*(cp: CoreDbCaptRef): TableRef[Blob,Blob] =
## Getter, returns the logger table for the overlay tracer database.
##
## Caveat:
## Unless the desriptor `cpt` referes to the top level overlay tracer, the
## result is undefined and depends on the backend implementation of the
## tracer.
##
cp.setTrackNewApi CptLogDbFn
result = cp.methods.logDbFn()
cp.ifTrackNewApi: debug logTxt, api, elapsed
proc flags*(cp: CoreDbCaptRef):set[CoreDbCaptFlags] =
## Getter
##
cp.setTrackNewApi CptFlagsFn
result = cp.methods.getFlagsFn()
cp.ifTrackNewApi: debug logTxt, api, elapsed, result
proc forget*(cp: CoreDbCaptRef) =
## Explicitely stop recording the current tracer instance and reset to
## previous level.
##
cp.setTrackNewApi CptForgetFn
cp.methods.forgetFn()
cp.ifTrackNewApi: debug logTxt, api, elapsed
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------