mdbx: rework min/max length checking for keys & values (squashed).

This commit is contained in:
Leonid Yuriev 2020-05-15 08:59:03 +03:00
parent 9736a36efb
commit d986d09b7b
2 changed files with 369 additions and 164 deletions

View File

@ -3144,10 +3144,11 @@ mdbx_audit(MDBX_txn *txn) {
return mdbx_audit_ex(txn, 0, (txn->mt_flags & MDBX_RDONLY) != 0);
}
static int __must_check_result mdbx_page_check(MDBX_env *env,
static int __must_check_result mdbx_page_check(MDBX_cursor *const mc,
const MDBX_page *const mp,
bool maybe_unfinished);
static int __must_check_result mdbx_cursor_check(MDBX_cursor *mc, bool pending);
unsigned options);
static int __must_check_result mdbx_cursor_check(MDBX_cursor *mc,
unsigned options);
static int __must_check_result mdbx_cursor_del0(MDBX_cursor *mc);
static int __must_check_result mdbx_del0(MDBX_txn *txn, MDBX_dbi dbi,
const MDBX_val *key,
@ -3180,6 +3181,9 @@ static void mdbx_cursor_copy(const MDBX_cursor *csrc, MDBX_cursor *cdst);
static int __must_check_result mdbx_drop0(MDBX_cursor *mc, int subs);
static int __must_check_result mdbx_fetch_sdb(MDBX_txn *txn, MDBX_dbi dbi);
static int __must_check_result mdbx_setup_dbx(MDBX_dbx *const dbx,
const MDBX_db *const db,
const unsigned pagesize);
static MDBX_cmp_func mdbx_cmp_memn, mdbx_cmp_memnr, mdbx_cmp_int_align4,
mdbx_cmp_int_align2, mdbx_cmp_int_unaligned, mdbx_cmp_lenfast;
@ -4054,9 +4058,19 @@ static int mdbx_page_retire(MDBX_cursor *mc, MDBX_page *mp) {
static __must_check_result __always_inline int
mdbx_retire_pgno(MDBX_cursor *mc, const pgno_t pgno) {
MDBX_page *mp;
int rc = mdbx_page_get(mc, pgno, &mp, NULL);
if (likely(rc == MDBX_SUCCESS))
rc = mdbx_page_retire(mc, mp);
int rc;
if (mdbx_audit_enabled()) {
const unsigned save_flags = mc->mc_flags;
mc->mc_flags |= C_RETIRING;
rc = mdbx_page_get(mc, pgno, &mp, NULL);
if (likely(rc == MDBX_SUCCESS))
rc = mdbx_page_retire(mc, mp);
mc->mc_flags = (mc->mc_flags & ~C_RETIRING) | (save_flags & C_RETIRING);
} else {
rc = mdbx_page_get(mc, pgno, &mp, NULL);
if (likely(rc == MDBX_SUCCESS))
rc = mdbx_page_retire(mc, mp);
}
return rc;
}
@ -10720,7 +10734,7 @@ dirty:
}
if (mdbx_audit_enabled()) {
int err = mdbx_page_check(env, p, true);
int err = mdbx_page_check(mc, p, C_UPDATING);
if (unlikely(err != MDBX_SUCCESS))
return err;
}
@ -10815,6 +10829,28 @@ __hot static int mdbx_page_search_root(MDBX_cursor *mc, MDBX_val *key,
return MDBX_SUCCESS;
}
static int mdbx_setup_dbx(MDBX_dbx *const dbx, const MDBX_db *const db,
const unsigned pagesize) {
dbx->md_klen_min =
(db->md_flags & MDBX_INTEGERKEY) ? 4 /* sizeof(uint32_t) */ : 0;
dbx->md_klen_max = mdbx_limits_keysize_max(pagesize, db->md_flags);
assert(dbx->md_klen_max != (unsigned)-1);
dbx->md_vlen_min = (db->md_flags & MDBX_INTEGERDUP)
? 4 /* sizeof(uint32_t) */
: ((db->md_flags & MDBX_DUPFIXED) ? 1 : 0);
dbx->md_vlen_max = mdbx_limits_valsize_max(pagesize, db->md_flags);
assert(dbx->md_vlen_max != (unsigned)-1);
if ((db->md_flags & (MDBX_DUPFIXED | MDBX_INTEGERDUP)) != 0 && db->md_xsize) {
if (unlikely(db->md_xsize < dbx->md_vlen_min ||
db->md_xsize > dbx->md_vlen_max))
return MDBX_CORRUPTED;
dbx->md_vlen_min = dbx->md_vlen_max = db->md_xsize;
}
return MDBX_SUCCESS;
}
static int mdbx_fetch_sdb(MDBX_txn *txn, MDBX_dbi dbi) {
MDBX_cursor_couple couple;
if (unlikely(TXN_DBI_CHANGED(txn, dbi)))
@ -10822,14 +10858,15 @@ static int mdbx_fetch_sdb(MDBX_txn *txn, MDBX_dbi dbi) {
int rc = mdbx_cursor_init(&couple.outer, txn, MAIN_DBI);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
rc = mdbx_page_search(&couple.outer, &txn->mt_dbxs[dbi].md_name, 0);
MDBX_dbx *const dbx = &txn->mt_dbxs[dbi];
rc = mdbx_page_search(&couple.outer, &dbx->md_name, 0);
if (unlikely(rc != MDBX_SUCCESS))
return (rc == MDBX_NOTFOUND) ? MDBX_BAD_DBI : rc;
MDBX_val data;
int exact = 0;
MDBX_node *node =
mdbx_node_search(&couple.outer, &txn->mt_dbxs[dbi].md_name, &exact);
MDBX_node *node = mdbx_node_search(&couple.outer, &dbx->md_name, &exact);
if (unlikely(!exact))
return MDBX_BAD_DBI;
if (unlikely((node_flags(node) & (F_DUPDATA | F_SUBDATA)) != F_SUBDATA))
@ -10844,10 +10881,15 @@ static int mdbx_fetch_sdb(MDBX_txn *txn, MDBX_dbi dbi) {
uint16_t md_flags = UNALIGNED_PEEK_16(data.iov_base, MDBX_db, md_flags);
/* The txn may not know this DBI, or another process may
* have dropped and recreated the DB with other flags. */
if (unlikely((txn->mt_dbs[dbi].md_flags & PERSISTENT_FLAGS) != md_flags))
MDBX_db *const db = &txn->mt_dbs[dbi];
if (unlikely((db->md_flags & PERSISTENT_FLAGS) != md_flags))
return MDBX_INCOMPATIBLE;
memcpy(&txn->mt_dbs[dbi], data.iov_base, sizeof(MDBX_db));
memcpy(db, data.iov_base, sizeof(MDBX_db));
rc = mdbx_setup_dbx(dbx, db, txn->mt_env->me_psize);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
txn->mt_dbstate[dbi] &= ~DB_STALE;
return MDBX_SUCCESS;
}
@ -11322,11 +11364,36 @@ static int mdbx_cursor_set(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data,
exactp = exactp ? exactp : &stub_exactp;
if ((mc->mc_db->md_flags & MDBX_INTEGERKEY) &&
unlikely(key->iov_len != sizeof(uint32_t) &&
key->iov_len != sizeof(uint64_t))) {
mdbx_cassert(mc, !"key-size is invalid for MDBX_INTEGERKEY");
return MDBX_BAD_VALSIZE;
static const unsigned keycheck_op_mask =
(1 << MDBX_GET_BOTH) | (1 << MDBX_GET_BOTH_RANGE) | (1 << MDBX_SET) |
(1 << MDBX_SET_KEY) | (1 << MDBX_SET_RANGE);
static const unsigned datacheck_op_mask =
(1 << MDBX_GET_BOTH) | (1 << MDBX_GET_BOTH_RANGE);
const unsigned op_mask = 1 << op;
if (op_mask & keycheck_op_mask) {
if (unlikely(key->iov_len < mc->mc_dbx->md_klen_min ||
key->iov_len > mc->mc_dbx->md_klen_max)) {
mdbx_cassert(mc, !"Invalid key-size");
return MDBX_BAD_VALSIZE;
}
if ((mc->mc_db->md_flags & MDBX_INTEGERKEY) != 0 &&
unlikely((key->iov_len & 3) | (1 & (uintptr_t)key->iov_base))) {
mdbx_cassert(mc, !"key-size/alignment is invalid for MDBX_INTEGERKEY");
return MDBX_BAD_VALSIZE;
}
if (op_mask & datacheck_op_mask) {
if (unlikely(data->iov_len < mc->mc_dbx->md_vlen_min ||
data->iov_len > mc->mc_dbx->md_vlen_max)) {
mdbx_cassert(mc, !"Invalid data-size");
return MDBX_BAD_VALSIZE;
}
if ((mc->mc_db->md_flags & MDBX_INTEGERDUP) != 0 &&
unlikely((data->iov_len & 3) | (1 & (uintptr_t)data->iov_base))) {
mdbx_cassert(mc, !"data-size/alignment is invalid for MDBX_INTEGERDUP");
return MDBX_BAD_VALSIZE;
}
}
}
if (mc->mc_xcursor)
@ -11845,38 +11912,27 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data,
if (unlikely(mc->mc_txn->mt_flags & (MDBX_RDONLY | MDBX_TXN_BLOCKED)))
return (mc->mc_txn->mt_flags & MDBX_RDONLY) ? MDBX_EACCESS : MDBX_BAD_TXN;
if ((mc->mc_flags & C_SUB) == 0) {
if (unlikely(key->iov_len > (size_t)((mc->mc_db->md_flags & MDBX_DUPSORT)
? env->me_maxkey_ds
: env->me_maxkey_nd) ||
data->iov_len > ((mc->mc_db->md_flags & MDBX_DUPSORT)
? env->me_maxval_ds
: env->me_maxval_nd))) {
if (likely((mc->mc_flags & C_SUB) == 0)) {
if (unlikely(key->iov_len < mc->mc_dbx->md_klen_min ||
key->iov_len > mc->mc_dbx->md_klen_max)) {
mdbx_cassert(mc, !"Invalid key-size");
return MDBX_BAD_VALSIZE;
}
if (unlikely(data->iov_len < mc->mc_dbx->md_vlen_min ||
data->iov_len > mc->mc_dbx->md_vlen_max)) {
mdbx_cassert(mc, !"Invalid data-size");
return MDBX_BAD_VALSIZE;
}
if ((mc->mc_db->md_flags & MDBX_INTEGERKEY)) {
if (unlikely(key->iov_len != sizeof(uint32_t) &&
key->iov_len != sizeof(uint64_t))) {
mdbx_cassert(mc, !"key-size is invalid for MDBX_INTEGERKEY");
return MDBX_BAD_VALSIZE;
}
if (unlikely(3 & (uintptr_t)key->iov_base)) {
mdbx_cassert(mc, !"key-alignment is invalid for MDBX_INTEGERKEY");
return MDBX_BAD_VALSIZE;
}
if ((mc->mc_db->md_flags & MDBX_INTEGERKEY) != 0 &&
unlikely((key->iov_len | (uintptr_t)key->iov_base) & 3)) {
mdbx_cassert(mc, !"key-size/alignment is invalid for MDBX_INTEGERKEY");
return MDBX_BAD_VALSIZE;
}
if ((mc->mc_db->md_flags & MDBX_INTEGERDUP)) {
if (unlikely(data->iov_len != sizeof(uint32_t) &&
data->iov_len != sizeof(uint64_t))) {
mdbx_cassert(mc, !"data-size is invalid for MDBX_INTEGERDUP");
return MDBX_BAD_VALSIZE;
}
if (unlikely(3 & (uintptr_t)data->iov_base)) {
mdbx_cassert(mc, !"data-alignment is invalid for MDBX_INTEGERDUP");
return MDBX_BAD_VALSIZE;
}
if ((mc->mc_db->md_flags & MDBX_INTEGERDUP) != 0 &&
unlikely((data->iov_len | (uintptr_t)data->iov_base) & 3)) {
mdbx_cassert(mc, !"data-size/alignment is invalid for MDBX_INTEGERDUP");
return MDBX_BAD_VALSIZE;
}
}
@ -11955,7 +12011,7 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data,
*data = olddata;
return MDBX_KEYEXIST;
}
if (rc == MDBX_SUCCESS) {
if (likely(rc == MDBX_SUCCESS)) {
if (exact) {
if (mc->mc_flags & C_SUB) {
mdbx_assert(env, data->iov_len == 0 && olddata.iov_len == 0);
@ -11982,7 +12038,7 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data,
return rc2;
}
if (rc == MDBX_NO_ROOT) {
if (unlikely(rc == MDBX_NO_ROOT)) {
MDBX_page *np;
/* new database, write a root leaf page */
mdbx_debug("%s", "allocating new root leaf page");
@ -11994,6 +12050,21 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data,
return rc2;
mc->mc_db->md_root = np->mp_pgno;
mc->mc_db->md_depth++;
if (mc->mc_db->md_flags & MDBX_INTEGERKEY) {
assert(key->iov_len >= mc->mc_dbx->md_klen_min &&
key->iov_len <= mc->mc_dbx->md_klen_max);
mc->mc_dbx->md_klen_min = mc->mc_dbx->md_klen_max = key->iov_len;
}
if (mc->mc_db->md_flags & (MDBX_INTEGERDUP | MDBX_DUPFIXED)) {
assert(data->iov_len >= mc->mc_dbx->md_vlen_min &&
data->iov_len <= mc->mc_dbx->md_vlen_max);
mc->mc_dbx->md_vlen_min = mc->mc_dbx->md_vlen_max = data->iov_len;
assert(mc->mc_xcursor != NULL);
mc->mc_db->md_xsize = mc->mc_xcursor->mx_db.md_xsize =
(unsigned)data->iov_len;
mc->mc_xcursor->mx_dbx.md_klen_min = mc->mc_xcursor->mx_dbx.md_klen_max =
data->iov_len;
}
*mc->mc_dbstate |= DB_DIRTY;
if ((mc->mc_db->md_flags & (MDBX_DUPSORT | MDBX_DUPFIXED)) == MDBX_DUPFIXED)
np->mp_flags |= P_LEAF2;
@ -12018,7 +12089,8 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data,
/* See note inside leaf_size() */ env->me_branch_nodemax) {
/* Too big for a node, insert in sub-DB. Set up an empty
* "old sub-page" for prep_subDB to expand to a full page. */
fp->mp_leaf2_ksize = (uint16_t)data->iov_len /* used if MDBX_DUPFIXED */;
fp->mp_leaf2_ksize =
(mc->mc_db->md_flags & MDBX_DUPFIXED) ? (uint16_t)data->iov_len : 0;
fp->mp_lower = fp->mp_upper = 0;
olddata.iov_len = PAGEHDRSZ;
goto prep_subDB;
@ -12054,7 +12126,7 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data,
}
if (mdbx_audit_enabled()) {
int err = mdbx_cursor_check(mc, false);
int err = mdbx_cursor_check(mc, 0);
if (unlikely(err != MDBX_SUCCESS))
return err;
}
@ -12063,7 +12135,7 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data,
more:;
if (mdbx_audit_enabled()) {
int err = mdbx_cursor_check(mc, false);
int err = mdbx_cursor_check(mc, 0);
if (unlikely(err != MDBX_SUCCESS))
return err;
}
@ -12135,7 +12207,7 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data,
memcpy(page_data(omp), data->iov_base, data->iov_len);
if (mdbx_audit_enabled()) {
int err = mdbx_cursor_check(mc, false);
int err = mdbx_cursor_check(mc, 0);
if (unlikely(err != MDBX_SUCCESS))
return err;
}
@ -12326,7 +12398,7 @@ int mdbx_cursor_put(MDBX_cursor *mc, const MDBX_val *key, MDBX_val *data,
}
if (mdbx_audit_enabled()) {
int err = mdbx_cursor_check(mc, false);
int err = mdbx_cursor_check(mc, 0);
if (unlikely(err != MDBX_SUCCESS))
return err;
}
@ -12349,7 +12421,7 @@ new_sub:
nflags |= MDBX_SPLIT_REPLACE;
rc = mdbx_page_split(mc, key, rdata, P_INVALID, nflags);
if (rc == MDBX_SUCCESS && mdbx_audit_enabled())
rc = mdbx_cursor_check(mc, false);
rc = mdbx_cursor_check(mc, 0);
} else {
/* There is room already in this leaf page. */
if (IS_LEAF2(mc->mc_pg[mc->mc_top])) {
@ -12472,7 +12544,7 @@ new_sub:
}
}
if (rc == MDBX_SUCCESS && mdbx_audit_enabled())
rc = mdbx_cursor_check(mc, false);
rc = mdbx_cursor_check(mc, 0);
return rc;
bad_sub:
if (unlikely(rc == MDBX_KEYEXIST))
@ -12943,6 +13015,8 @@ static int mdbx_xcursor_init0(MDBX_cursor *mc) {
mx->mx_dbx.md_name.iov_base = NULL;
mx->mx_dbx.md_cmp = mc->mc_dbx->md_dcmp;
mx->mx_dbx.md_dcmp = NULL;
mx->mx_dbx.md_klen_min = INT_MAX;
mx->mx_dbx.md_vlen_min = mx->mx_dbx.md_klen_max = mx->mx_dbx.md_vlen_max = 0;
return MDBX_SUCCESS;
}
@ -12968,7 +13042,6 @@ static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node) {
if (unlikely(node_ds(node) <= PAGEHDRSZ))
return MDBX_CORRUPTED;
MDBX_page *fp = node_data(node);
mx->mx_db.md_xsize = 0;
mx->mx_db.md_depth = 1;
mx->mx_db.md_branch_pages = 0;
mx->mx_db.md_leaf_pages = 1;
@ -12981,10 +13054,15 @@ static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node) {
mx->mx_cursor.mc_pg[0] = fp;
mx->mx_cursor.mc_ki[0] = 0;
mx->mx_db.md_flags = flags_db2sub(mc->mc_db->md_flags);
if (mc->mc_db->md_flags & MDBX_DUPFIXED)
mx->mx_db.md_xsize = fp->mp_leaf2_ksize;
mx->mx_db.md_xsize =
(mc->mc_db->md_flags & MDBX_DUPFIXED) ? fp->mp_leaf2_ksize : 0;
}
mx->mx_dbx.md_klen_min = mc->mc_dbx->md_vlen_min;
mx->mx_dbx.md_klen_max = mc->mc_dbx->md_vlen_max;
if (unlikely(mx->mx_db.md_xsize != mc->mc_db->md_xsize))
return MDBX_CORRUPTED;
mdbx_debug("Sub-db -%u root page %" PRIaPGNO, mx->mx_cursor.mc_dbi,
mx->mx_db.md_root);
mx->mx_dbstate = DB_VALID | DB_USRVALID | DB_DUPDATA;
@ -13010,51 +13088,66 @@ static int mdbx_xcursor_init2(MDBX_cursor *mc, MDBX_xcursor *src_mx,
mx->mx_cursor.mc_flags |= C_INITIALIZED;
mx->mx_cursor.mc_ki[0] = 0;
mx->mx_dbstate = DB_VALID | DB_USRVALID | DB_DUPDATA;
mx->mx_dbx.md_cmp = src_mx->mx_dbx.md_cmp;
} else if (!(mx->mx_cursor.mc_flags & C_INITIALIZED)) {
return MDBX_SUCCESS;
}
mx->mx_dbx.md_klen_min = src_mx->mx_dbx.md_klen_min;
mx->mx_dbx.md_klen_max = src_mx->mx_dbx.md_klen_max;
mx->mx_dbx.md_cmp = src_mx->mx_dbx.md_cmp;
mx->mx_db = src_mx->mx_db;
mx->mx_cursor.mc_pg[0] = src_mx->mx_cursor.mc_pg[0];
mdbx_debug("Sub-db -%u root page %" PRIaPGNO, mx->mx_cursor.mc_dbi,
mx->mx_db.md_root);
if (mx->mx_cursor.mc_flags & C_INITIALIZED) {
mdbx_debug("Sub-db -%u root page %" PRIaPGNO, mx->mx_cursor.mc_dbi,
mx->mx_db.md_root);
}
return MDBX_SUCCESS;
}
static __inline int mdbx_couple_init(MDBX_cursor_couple *couple,
const MDBX_dbi dbi, MDBX_txn *const txn,
MDBX_db *const db, MDBX_dbx *const dbx,
uint8_t *const dbstate) {
couple->outer.mc_signature = MDBX_MC_SIGNATURE;
couple->outer.mc_next = NULL;
couple->outer.mc_backup = NULL;
couple->outer.mc_dbi = dbi;
couple->outer.mc_txn = txn;
couple->outer.mc_db = db;
couple->outer.mc_dbx = dbx;
couple->outer.mc_dbstate = dbstate;
couple->outer.mc_snum = 0;
couple->outer.mc_top = 0;
couple->outer.mc_pg[0] = 0;
couple->outer.mc_flags = 0;
couple->outer.mc_ki[0] = 0;
couple->outer.mc_xcursor = NULL;
int rc = MDBX_SUCCESS;
if (unlikely(*couple->outer.mc_dbstate & DB_STALE)) {
rc = mdbx_page_search(&couple->outer, NULL, MDBX_PS_ROOTONLY);
rc = (rc != MDBX_NOTFOUND) ? rc : MDBX_SUCCESS;
} else if (unlikely(couple->outer.mc_dbx->md_klen_max == 0)) {
rc = mdbx_setup_dbx(couple->outer.mc_dbx, couple->outer.mc_db,
txn->mt_env->me_psize);
}
if (couple->outer.mc_db->md_flags & MDBX_DUPSORT) {
couple->inner.mx_cursor.mc_signature = MDBX_MC_SIGNATURE;
couple->outer.mc_xcursor = &couple->inner;
rc = mdbx_xcursor_init0(&couple->outer);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
couple->inner.mx_dbx.md_klen_min = couple->outer.mc_dbx->md_vlen_min;
couple->inner.mx_dbx.md_klen_max = couple->outer.mc_dbx->md_vlen_max;
}
return rc;
}
/* Initialize a cursor for a given transaction and database. */
static int mdbx_cursor_init(MDBX_cursor *mc, MDBX_txn *txn, MDBX_dbi dbi) {
mc->mc_signature = MDBX_MC_SIGNATURE;
mc->mc_next = NULL;
mc->mc_backup = NULL;
mc->mc_dbi = dbi;
mc->mc_txn = txn;
mc->mc_db = &txn->mt_dbs[dbi];
mc->mc_dbx = &txn->mt_dbxs[dbi];
mc->mc_dbstate = &txn->mt_dbstate[dbi];
mc->mc_snum = 0;
mc->mc_top = 0;
mc->mc_pg[0] = 0;
mc->mc_flags = 0;
mc->mc_ki[0] = 0;
mc->mc_xcursor = NULL;
if (txn->mt_dbs[dbi].md_flags & MDBX_DUPSORT) {
STATIC_ASSERT(offsetof(MDBX_cursor_couple, outer) == 0);
MDBX_xcursor *mx = &container_of(mc, MDBX_cursor_couple, outer)->inner;
mdbx_tassert(txn, mx != NULL);
mx->mx_cursor.mc_signature = MDBX_MC_SIGNATURE;
mc->mc_xcursor = mx;
int rc = mdbx_xcursor_init0(mc);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
}
int rc = MDBX_SUCCESS;
if (unlikely(*mc->mc_dbstate & DB_STALE)) {
rc = mdbx_page_search(mc, NULL, MDBX_PS_ROOTONLY);
rc = (rc != MDBX_NOTFOUND) ? rc : MDBX_SUCCESS;
}
return rc;
STATIC_ASSERT(offsetof(MDBX_cursor_couple, outer) == 0);
return mdbx_couple_init(container_of(mc, MDBX_cursor_couple, outer), dbi, txn,
&txn->mt_dbs[dbi], &txn->mt_dbxs[dbi],
&txn->mt_dbstate[dbi]);
}
int mdbx_cursor_open(MDBX_txn *txn, MDBX_dbi dbi, MDBX_cursor **ret) {
@ -13253,7 +13346,7 @@ static int mdbx_update_key(MDBX_cursor *mc, const MDBX_val *key) {
mdbx_node_del(mc, 0);
int rc = mdbx_page_split(mc, key, NULL, pgno, MDBX_SPLIT_REPLACE);
if (rc == MDBX_SUCCESS && mdbx_audit_enabled())
rc = mdbx_cursor_check(mc, true);
rc = mdbx_cursor_check(mc, C_UPDATING);
return rc;
}
@ -14108,8 +14201,10 @@ static int mdbx_rebalance(MDBX_cursor *mc) {
return MDBX_PROBLEM;
}
static __cold int mdbx_page_check(MDBX_env *env, const MDBX_page *const mp,
bool maybe_unfinished) {
static __cold int mdbx_page_check(MDBX_cursor *const mc,
const MDBX_page *const mp, unsigned options) {
options |= mc->mc_flags & (C_COPYING | C_UPDATING | C_RETIRING);
MDBX_env *const env = mc->mc_txn->mt_env;
const unsigned nkeys = page_numkeys(mp);
char *const end_of_page = (char *)mp + env->me_psize;
mdbx_assert(env, mp->mp_pgno >= MIN_PAGENO && mp->mp_pgno <= MAX_PAGENO);
@ -14124,7 +14219,7 @@ static __cold int mdbx_page_check(MDBX_env *env, const MDBX_page *const mp,
return MDBX_CORRUPTED;
return MDBX_SUCCESS;
}
if (!(IS_DIRTY(mp) && maybe_unfinished)) {
if ((options & C_UPDATING) == 0 || !IS_DIRTY(mp)) {
mdbx_assert(env, nkeys >= 2 || !IS_BRANCH(mp));
if (unlikely(nkeys < 2 && IS_BRANCH(mp)))
return MDBX_CORRUPTED;
@ -14133,6 +14228,15 @@ static __cold int mdbx_page_check(MDBX_env *env, const MDBX_page *const mp,
for (unsigned i = IS_LEAF(mp) ? 0 : 1; i < nkeys; ++i) {
if (IS_LEAF2(mp)) {
const size_t ksize = mp->mp_leaf2_ksize;
if ((options & C_COPYING) == 0 &&
unlikely(ksize != mc->mc_dbx->md_klen_min)) {
mdbx_assert(env, ksize >= mc->mc_dbx->md_klen_min);
mdbx_assert(env, ksize <= mc->mc_dbx->md_klen_max);
if (unlikely(ksize < mc->mc_dbx->md_klen_min ||
ksize > mc->mc_dbx->md_klen_max))
return MDBX_CORRUPTED;
mc->mc_dbx->md_klen_min = mc->mc_dbx->md_klen_max = ksize;
}
const char *const key = page_leaf2key(mp, i, ksize);
mdbx_assert(env, key + ksize <= end_of_page);
if (unlikely(end_of_page < key + ksize))
@ -14145,16 +14249,51 @@ static __cold int mdbx_page_check(MDBX_env *env, const MDBX_page *const mp,
return MDBX_CORRUPTED;
if (IS_LEAF(mp) || i > 0) {
size_t ksize = node_ks(node);
if ((options & C_COPYING) == 0) {
mdbx_assert(env, ksize >= mc->mc_dbx->md_klen_min);
mdbx_assert(env, ksize <= mc->mc_dbx->md_klen_max);
if (unlikely(ksize < mc->mc_dbx->md_klen_min ||
ksize > mc->mc_dbx->md_klen_max))
return MDBX_CORRUPTED;
}
char *key = node_key(node);
mdbx_assert(env, key + ksize <= end_of_page);
if (unlikely(end_of_page < key + ksize))
return MDBX_CORRUPTED;
}
if (IS_BRANCH(mp))
continue;
if (node_flags(node) == F_BIGDATA /* data on large-page */) {
if (IS_BRANCH(mp)) {
if ((options & C_RETIRING) == 0) {
const pgno_t ref = node_pgno(node);
mdbx_assert(env, ref >= MIN_PAGENO);
mdbx_assert(env, ref < mc->mc_txn->mt_next_pgno);
if (unlikely(ref < MIN_PAGENO || ref >= mc->mc_txn->mt_next_pgno))
return MDBX_CORRUPTED;
}
continue;
}
if (node_flags(node) == F_BIGDATA /* data on large-page */) {
const size_t dsize = node_ds(node);
if ((options & C_COPYING) == 0) {
mdbx_assert(env, dsize > mc->mc_dbx->md_vlen_min);
mdbx_assert(env, dsize <= mc->mc_dbx->md_vlen_max);
if (unlikely(dsize <= mc->mc_dbx->md_vlen_min ||
dsize > mc->mc_dbx->md_vlen_max))
return MDBX_CORRUPTED;
}
if ((options & C_RETIRING) == 0) {
MDBX_page *lp;
int err = mdbx_page_get(mc, node_largedata_pgno(node), &lp, NULL);
if (unlikely(err != MDBX_SUCCESS))
return err;
mdbx_assert(env, IS_OVERFLOW(lp));
mdbx_assert(env, number_of_ovpages(env, dsize) == lp->mp_pages);
if (unlikely(!IS_OVERFLOW(lp) ||
number_of_ovpages(env, dsize) != lp->mp_pages))
return MDBX_CORRUPTED;
}
continue;
}
const size_t dsize = node_ds(node);
const char *const data = node_data(node);
mdbx_assert(env, data + dsize <= end_of_page);
@ -14166,6 +14305,13 @@ static __cold int mdbx_page_check(MDBX_env *env, const MDBX_page *const mp,
mdbx_assert(env, false);
return MDBX_CORRUPTED;
case 0 /* usual */:
if ((options & C_COPYING) == 0) {
mdbx_assert(env, dsize >= mc->mc_dbx->md_vlen_min);
mdbx_assert(env, dsize <= mc->mc_dbx->md_vlen_max);
if (unlikely(dsize < mc->mc_dbx->md_vlen_min ||
dsize > mc->mc_dbx->md_vlen_max))
return MDBX_CORRUPTED;
}
break;
case F_SUBDATA /* sub-db */:
mdbx_assert(env, dsize >= sizeof(MDBX_db));
@ -14198,6 +14344,15 @@ static __cold int mdbx_page_check(MDBX_env *env, const MDBX_page *const mp,
if (IS_LEAF2(sp)) {
/* LEAF2 pages have no mp_ptrs[] or node headers */
size_t sub_ksize = sp->mp_leaf2_ksize;
if ((options & C_COPYING) == 0 &&
unlikely(sub_ksize != mc->mc_dbx->md_vlen_min)) {
mdbx_assert(env, sub_ksize >= mc->mc_dbx->md_vlen_min);
mdbx_assert(env, sub_ksize <= mc->mc_dbx->md_vlen_max);
if (unlikely(sub_ksize < mc->mc_dbx->md_vlen_min ||
sub_ksize > mc->mc_dbx->md_vlen_max))
return MDBX_CORRUPTED;
mc->mc_dbx->md_vlen_min = mc->mc_dbx->md_vlen_max = sub_ksize;
}
char *sub_key = page_leaf2key(sp, j, sub_ksize);
mdbx_assert(env, sub_key + sub_ksize <= end_of_subpage);
if (unlikely(end_of_subpage < sub_key + sub_ksize))
@ -14219,6 +14374,18 @@ static __cold int mdbx_page_check(MDBX_env *env, const MDBX_page *const mp,
char *sub_key = node_key(sub_node);
size_t sub_dsize = node_ds(sub_node);
char *sub_data = node_data(sub_node);
if ((options & C_COPYING) == 0) {
mdbx_assert(env, sub_ksize >= mc->mc_dbx->md_vlen_min);
mdbx_assert(env, sub_ksize <= mc->mc_dbx->md_vlen_max);
if (unlikely(sub_ksize < mc->mc_dbx->md_vlen_min ||
sub_ksize > mc->mc_dbx->md_vlen_max))
return MDBX_CORRUPTED;
}
mdbx_assert(env, sub_dsize == 0);
if (unlikely(sub_dsize != 0))
return MDBX_CORRUPTED;
mdbx_assert(env, sub_key + sub_ksize <= end_of_subpage);
if (unlikely(end_of_subpage < sub_key + sub_ksize))
return MDBX_CORRUPTED;
@ -14235,7 +14402,7 @@ static __cold int mdbx_page_check(MDBX_env *env, const MDBX_page *const mp,
return MDBX_SUCCESS;
}
static __cold int mdbx_cursor_check(MDBX_cursor *mc, bool pending) {
static __cold int mdbx_cursor_check(MDBX_cursor *mc, unsigned options) {
mdbx_tassert(mc->mc_txn, mc->mc_txn->mt_parent ||
mc->mc_txn->tw.dirtyroom +
mc->mc_txn->tw.dirtylist->length ==
@ -14243,10 +14410,10 @@ static __cold int mdbx_cursor_check(MDBX_cursor *mc, bool pending) {
mdbx_cassert(mc, mc->mc_top == mc->mc_snum - 1);
if (unlikely(mc->mc_top != mc->mc_snum - 1))
return MDBX_CURSOR_FULL;
mdbx_cassert(mc, pending ? mc->mc_snum <= mc->mc_db->md_depth
: mc->mc_snum == mc->mc_db->md_depth);
if (unlikely(pending ? mc->mc_snum > mc->mc_db->md_depth
: mc->mc_snum != mc->mc_db->md_depth))
mdbx_cassert(mc, (options & C_UPDATING) ? mc->mc_snum <= mc->mc_db->md_depth
: mc->mc_snum == mc->mc_db->md_depth);
if (unlikely((options & C_UPDATING) ? mc->mc_snum > mc->mc_db->md_depth
: mc->mc_snum != mc->mc_db->md_depth))
return MDBX_CURSOR_FULL;
for (int n = 0; n < (int)mc->mc_snum; ++n) {
@ -14259,7 +14426,7 @@ static __cold int mdbx_cursor_check(MDBX_cursor *mc, bool pending) {
mdbx_cassert(mc, branch == expect_branch);
if (unlikely(branch != expect_branch))
return MDBX_CURSOR_FULL;
if (!pending) {
if ((options & C_UPDATING) == 0) {
mdbx_cassert(mc,
nkeys > mc->mc_ki[n] || (!branch && nkeys == mc->mc_ki[n] &&
(mc->mc_flags & C_EOF) != 0));
@ -14273,7 +14440,7 @@ static __cold int mdbx_cursor_check(MDBX_cursor *mc, bool pending) {
return MDBX_CURSOR_FULL;
}
int err = mdbx_page_check(mc->mc_txn->mt_env, mp, pending);
int err = mdbx_page_check(mc, mp, options);
if (unlikely(err != MDBX_SUCCESS))
return err;
@ -14293,7 +14460,7 @@ static __cold int mdbx_cursor_check(MDBX_cursor *mc, bool pending) {
mdbx_cassert(mc, nested_leaf == expect_nested_leaf);
if (unlikely(nested_leaf != expect_nested_leaf))
return MDBX_CURSOR_FULL;
err = mdbx_page_check(mc->mc_txn->mt_env, np, pending);
err = mdbx_page_check(mc, np, options);
if (unlikely(err != MDBX_SUCCESS))
return err;
}
@ -14435,7 +14602,7 @@ static int mdbx_cursor_del0(MDBX_cursor *mc) {
if (unlikely(rc))
mc->mc_txn->mt_flags |= MDBX_TXN_ERROR;
else if (mdbx_audit_enabled())
rc = mdbx_cursor_check(mc, false);
rc = mdbx_cursor_check(mc, 0);
return rc;
}
@ -14526,7 +14693,7 @@ static int mdbx_page_split(MDBX_cursor *mc, const MDBX_val *newkey,
unsigned newindx = mc->mc_ki[mc->mc_top];
unsigned nkeys = page_numkeys(mp);
if (mdbx_audit_enabled()) {
rc = mdbx_cursor_check(mc, true);
rc = mdbx_cursor_check(mc, C_UPDATING);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
}
@ -14730,10 +14897,10 @@ static int mdbx_page_split(MDBX_cursor *mc, const MDBX_val *newkey,
mdbx_debug("separator is %d [%s]", split_indx, DKEY(&sepkey));
if (mdbx_audit_enabled()) {
rc = mdbx_cursor_check(mc, true);
rc = mdbx_cursor_check(mc, C_UPDATING);
if (unlikely(rc != MDBX_SUCCESS))
goto done;
rc = mdbx_cursor_check(&mn, true);
rc = mdbx_cursor_check(&mn, C_UPDATING);
if (unlikely(rc != MDBX_SUCCESS))
goto done;
}
@ -14752,7 +14919,7 @@ static int mdbx_page_split(MDBX_cursor *mc, const MDBX_val *newkey,
goto done;
mdbx_cassert(mc, (int)mc->mc_snum - snum == mc->mc_db->md_depth - depth);
if (mdbx_audit_enabled()) {
rc = mdbx_cursor_check(mc, true);
rc = mdbx_cursor_check(mc, C_UPDATING);
if (unlikely(rc != MDBX_SUCCESS))
goto done;
}
@ -15141,6 +15308,7 @@ static int __cold mdbx_env_cwalk(mdbx_copy *my, pgno_t *pg, int flags) {
memset(&couple, 0, sizeof(couple));
couple.outer.mc_snum = 1;
couple.outer.mc_txn = my->mc_txn;
couple.outer.mc_flags = couple.inner.mx_cursor.mc_flags = C_COPYING;
rc = mdbx_page_get(&couple.outer, *pg, &couple.outer.mc_pg[0], NULL);
if (unlikely(rc != MDBX_SUCCESS))
@ -16189,8 +16357,7 @@ int mdbx_dbi_open_ex(MDBX_txn *txn, const char *table_name, unsigned user_flags,
}
/* Got info, register DBI in this txn */
txn->mt_dbxs[slot].md_cmp = nullptr;
txn->mt_dbxs[slot].md_dcmp = nullptr;
memset(txn->mt_dbxs + slot, 0, sizeof(MDBX_dbx));
txn->mt_dbs[slot] = *(MDBX_db *)data.iov_base;
env->me_dbflags[slot] = 0;
rc = mdbx_dbi_bind(txn, slot, user_flags, keycmp, datacmp);
@ -16935,21 +17102,22 @@ int mdbx_txn_straggler(const MDBX_txn *txn, int *percent)
typedef struct mdbx_walk_ctx {
void *mw_user;
MDBX_pgvisitor_func *mw_visitor;
MDBX_cursor mw_cursor;
MDBX_txn *mw_txn;
MDBX_cursor *mw_cursor;
} mdbx_walk_ctx_t;
static int __cold mdbx_walk_sdb(mdbx_walk_ctx_t *ctx, MDBX_db *const db,
const char *name, int deep);
/* Depth-first tree traversal. */
static int __cold mdbx_env_walk(mdbx_walk_ctx_t *ctx, const char *dbi,
pgno_t pgno, int deep) {
if (unlikely(pgno == P_INVALID))
return MDBX_SUCCESS; /* empty db */
static int __cold mdbx_walk_tree(mdbx_walk_ctx_t *ctx, pgno_t pgno,
const char *name, int deep) {
assert(pgno != P_INVALID);
MDBX_page *mp;
int rc = mdbx_page_get(&ctx->mw_cursor, pgno, &mp, NULL);
int rc = mdbx_page_get(ctx->mw_cursor, pgno, &mp, NULL);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
rc = mdbx_page_check(ctx->mw_cursor.mc_txn->mt_env, mp, false);
rc = mdbx_page_check(ctx->mw_cursor, mp, 0);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
@ -17006,10 +17174,10 @@ static int __cold mdbx_env_walk(mdbx_walk_ctx_t *ctx, const char *dbi,
const pgno_t large_pgno = node_largedata_pgno(node);
MDBX_page *op;
rc = mdbx_page_get(&ctx->mw_cursor, large_pgno, &op, NULL);
rc = mdbx_page_get(ctx->mw_cursor, large_pgno, &op, NULL);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
rc = mdbx_page_check(ctx->mw_cursor.mc_txn->mt_env, op, false);
rc = mdbx_page_check(ctx->mw_cursor, op, 0);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
@ -17022,12 +17190,12 @@ static int __cold mdbx_env_walk(mdbx_walk_ctx_t *ctx, const char *dbi,
const size_t over_header = PAGEHDRSZ;
const size_t over_payload = node_ds(node);
const size_t over_unused =
pgno2bytes(ctx->mw_cursor.mc_txn->mt_env, op->mp_pages) -
pgno2bytes(ctx->mw_cursor->mc_txn->mt_env, op->mp_pages) -
over_payload - over_header;
rc = ctx->mw_visitor(
large_pgno, op->mp_pages, ctx->mw_user, deep, dbi,
pgno2bytes(ctx->mw_cursor.mc_txn->mt_env, op->mp_pages),
large_pgno, op->mp_pages, ctx->mw_user, deep, name,
pgno2bytes(ctx->mw_cursor->mc_txn->mt_env, op->mp_pages),
MDBX_page_large, 1, over_payload, over_header, over_unused);
} break;
@ -17083,7 +17251,7 @@ static int __cold mdbx_env_walk(mdbx_walk_ctx_t *ctx, const char *dbi,
}
}
rc = ctx->mw_visitor(pgno, 0, ctx->mw_user, deep + 1, dbi, node_ds(node),
rc = ctx->mw_visitor(pgno, 0, ctx->mw_user, deep + 1, name, node_ds(node),
subtype, nsubkeys, subpayload_size, subheader_size,
subunused_size + subalign_bytes);
header_size += subheader_size;
@ -17100,8 +17268,8 @@ static int __cold mdbx_env_walk(mdbx_walk_ctx_t *ctx, const char *dbi,
return rc;
}
rc = ctx->mw_visitor(mp->mp_pgno, 1, ctx->mw_user, deep, dbi,
ctx->mw_cursor.mc_txn->mt_env->me_psize, type, nkeys,
rc = ctx->mw_visitor(mp->mp_pgno, 1, ctx->mw_user, deep, name,
ctx->mw_cursor->mc_txn->mt_env->me_psize, type, nkeys,
payload_size, header_size, unused_size + align_bytes);
if (unlikely(rc != MDBX_SUCCESS))
@ -17113,7 +17281,7 @@ static int __cold mdbx_env_walk(mdbx_walk_ctx_t *ctx, const char *dbi,
MDBX_node *node = page_node(mp, i);
if (type == MDBX_page_branch) {
rc = mdbx_env_walk(ctx, dbi, node_pgno(node), deep + 1);
rc = mdbx_walk_tree(ctx, node_pgno(node), name, deep + 1);
if (unlikely(rc != MDBX_SUCCESS)) {
if (rc != MDBX_RESULT_TRUE)
return rc;
@ -17133,17 +17301,17 @@ static int __cold mdbx_env_walk(mdbx_walk_ctx_t *ctx, const char *dbi,
if (unlikely(namelen == 0 || node_ds(node) != sizeof(MDBX_db)))
return MDBX_CORRUPTED;
char namebuf_onstask[142];
char *const name = (namelen < sizeof(namebuf_onstask))
? namebuf_onstask
: mdbx_malloc(namelen + 1);
if (name) {
memcpy(name, node_key(node), namelen);
name[namelen] = 0;
char namebuf_onstask[64];
char *const sub_name = (namelen < sizeof(namebuf_onstask))
? namebuf_onstask
: mdbx_malloc(namelen + 1);
if (sub_name) {
memcpy(sub_name, node_key(node), namelen);
sub_name[namelen] = 0;
memcpy(&db, node_data(node), sizeof(db));
rc = mdbx_env_walk(ctx, name, db.md_root, deep + 1);
if (name != namebuf_onstask)
mdbx_free(name);
rc = mdbx_walk_sdb(ctx, &db, sub_name, deep + 1);
if (sub_name != namebuf_onstask)
mdbx_free(sub_name);
} else {
rc = MDBX_ENOMEM;
}
@ -17153,8 +17321,19 @@ static int __cold mdbx_env_walk(mdbx_walk_ctx_t *ctx, const char *dbi,
if (unlikely(node_ds(node) != sizeof(MDBX_db)))
return MDBX_CORRUPTED;
if (unlikely(ctx->mw_cursor->mc_xcursor == NULL))
return MDBX_CORRUPTED;
memcpy(&db, node_data(node), sizeof(db));
rc = mdbx_env_walk(ctx, dbi, db.md_root, deep + 1);
assert(ctx->mw_cursor->mc_xcursor ==
&container_of(ctx->mw_cursor, MDBX_cursor_couple, outer)->inner);
ctx->mw_cursor = &ctx->mw_cursor->mc_xcursor->mx_cursor;
rc = mdbx_walk_tree(ctx, db.md_root, name, deep + 1);
MDBX_xcursor *inner_xcursor =
container_of(ctx->mw_cursor, MDBX_xcursor, mx_cursor);
MDBX_cursor_couple *couple =
container_of(inner_xcursor, MDBX_cursor_couple, inner);
ctx->mw_cursor = &couple->outer;
break;
}
@ -17165,6 +17344,25 @@ static int __cold mdbx_env_walk(mdbx_walk_ctx_t *ctx, const char *dbi,
return MDBX_SUCCESS;
}
static int __cold mdbx_walk_sdb(mdbx_walk_ctx_t *ctx, MDBX_db *const db,
const char *name, int deep) {
if (unlikely(db->md_root == P_INVALID))
return MDBX_SUCCESS; /* empty db */
MDBX_cursor_couple couple;
MDBX_dbx dbx = {.md_klen_min = INT_MAX};
uint8_t dbstate = DB_VALID | DB_AUDITED;
int rc = mdbx_couple_init(&couple, ~0u, ctx->mw_txn, db, &dbx, &dbstate);
if (unlikely(rc != MDBX_SUCCESS))
return rc;
couple.outer.mc_next = ctx->mw_cursor;
ctx->mw_cursor = &couple.outer;
rc = mdbx_walk_tree(ctx, db->md_root, name, deep);
ctx->mw_cursor = couple.outer.mc_next;
return rc;
}
int __cold mdbx_env_pgwalk(MDBX_txn *txn, MDBX_pgvisitor_func *visitor,
void *user) {
int rc = check_txn(txn, MDBX_TXN_BLOCKED);
@ -17173,8 +17371,7 @@ int __cold mdbx_env_pgwalk(MDBX_txn *txn, MDBX_pgvisitor_func *visitor,
mdbx_walk_ctx_t ctx;
memset(&ctx, 0, sizeof(ctx));
ctx.mw_cursor.mc_snum = 1;
ctx.mw_cursor.mc_txn = txn;
ctx.mw_txn = txn;
ctx.mw_user = user;
ctx.mw_visitor = visitor;
@ -17184,10 +17381,9 @@ int __cold mdbx_env_pgwalk(MDBX_txn *txn, MDBX_pgvisitor_func *visitor,
(txn->mt_env->me_psize - sizeof(MDBX_meta) - PAGEHDRSZ) *
NUM_METAS);
if (!MDBX_IS_ERROR(rc))
rc = mdbx_env_walk(&ctx, MDBX_PGWALK_GC, txn->mt_dbs[FREE_DBI].md_root, 0);
rc = mdbx_walk_sdb(&ctx, &txn->mt_dbs[FREE_DBI], MDBX_PGWALK_GC, 0);
if (!MDBX_IS_ERROR(rc))
rc =
mdbx_env_walk(&ctx, MDBX_PGWALK_MAIN, txn->mt_dbs[MAIN_DBI].md_root, 0);
rc = mdbx_walk_sdb(&ctx, &txn->mt_dbs[MAIN_DBI], MDBX_PGWALK_MAIN, 0);
if (!MDBX_IS_ERROR(rc))
rc = visitor(P_INVALID, 0, user, INT_MIN, NULL, 0, MDBX_page_void, 0, 0, 0,
0);
@ -17763,8 +17959,7 @@ int mdbx_replace(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *key,
MDBX_page *page = cx.outer.mc_pg[cx.outer.mc_top];
if (txn->mt_dbs[dbi].md_flags & MDBX_DUPSORT) {
if (flags & MDBX_CURRENT) {
/* для не-уникальных ключей позволяем update/delete только если ключ
* один */
/* disallow update/delete for multi-values */
MDBX_node *node = page_node(page, cx.outer.mc_ki[cx.outer.mc_top]);
if (F_ISSET(node_flags(node), F_DUPDATA)) {
mdbx_tassert(txn, XCURSOR_INITED(&cx.outer) &&

View File

@ -681,9 +681,12 @@ typedef MDBX_DP *MDBX_DPL;
* The information here is mostly static/read-only. There is
* only a single copy of this record in the environment. */
typedef struct MDBX_dbx {
MDBX_val md_name; /* name of the database */
MDBX_cmp_func *md_cmp; /* function for comparing keys */
MDBX_cmp_func *md_dcmp; /* function for comparing data items */
MDBX_val md_name; /* name of the database */
MDBX_cmp_func *md_cmp; /* function for comparing keys */
MDBX_cmp_func *md_dcmp; /* function for comparing data items */
size_t md_klen_min, md_klen_max; /* min/max key length for the database */
size_t md_vlen_min,
md_vlen_max; /* min/max value/data length for the database */
} MDBX_dbx;
/* A database transaction.
@ -824,16 +827,23 @@ struct MDBX_cursor {
MDBX_dbx *mc_dbx;
/* The mt_dbstate for this database */
uint8_t *mc_dbstate;
unsigned mc_snum; /* number of pushed pages */
unsigned mc_top; /* index of top page, normally mc_snum-1 */
/* Cursor state flags. */
#define C_INITIALIZED 0x01 /* cursor has been initialized and is valid */
#define C_EOF 0x02 /* No more data */
#define C_SUB 0x04 /* Cursor is a sub-cursor */
#define C_DEL 0x08 /* last op was a cursor_del */
#define C_UNTRACK 0x10 /* Un-track cursor when closing */
#define C_RECLAIMING 0x20 /* GC lookup is prohibited */
#define C_GCFREEZE 0x40 /* reclaimed_pglist must not be updated */
unsigned mc_snum; /* number of pushed pages */
unsigned mc_top; /* index of top page, normally mc_snum-1 */
/* Cursor state flags. */
#define C_INITIALIZED 0x01 /* cursor has been initialized and is valid */
#define C_EOF 0x02 /* No more data */
#define C_SUB 0x04 /* Cursor is a sub-cursor */
#define C_DEL 0x08 /* last op was a cursor_del */
#define C_UNTRACK 0x10 /* Un-track cursor when closing */
#define C_RECLAIMING 0x20 /* GC lookup is prohibited */
#define C_GCFREEZE 0x40 /* reclaimed_pglist must not be updated */
/* Cursor checing flags. */
#define C_COPYING 0x100 /* skip key-value length check (copying simplify) */
#define C_UPDATING 0x200 /* update/rebalance pending */
#define C_RETIRING 0x400 /* refs to child pages may be invalid */
unsigned mc_flags; /* see mdbx_cursor */
MDBX_page *mc_pg[CURSOR_STACK]; /* stack of pushed pages */
indx_t mc_ki[CURSOR_STACK]; /* stack of page indices */