/*- * See the file LICENSE for redistribution information. * * Copyright (c) 1996, 1997, 1998, 1999 * Sleepycat Software. All rights reserved. */ #include "db_config.h" #ifndef lint static const char sccsid[] = "@(#)bt_cursor.c 11.21 (Sleepycat) 11/10/99"; #endif /* not lint */ #ifndef NO_SYSTEM_INCLUDES #include #include #include #include #endif #include "db_int.h" #include "db_page.h" #include "db_shash.h" #include "btree.h" #include "lock.h" #include "qam.h" static int CDB___bam_c_close __P((DBC *)); static int CDB___bam_c_del __P((DBC *, u_int32_t)); static int CDB___bam_c_destroy __P((DBC *)); static int CDB___bam_c_first __P((DBC *)); static int CDB___bam_c_get __P((DBC *, DBT *, DBT *, u_int32_t)); static int CDB___bam_c_getstack __P((DBC *)); static int CDB___bam_c_last __P((DBC *)); static int CDB___bam_c_next __P((DBC *, int)); static int CDB___bam_c_physdel __P((DBC *)); static int CDB___bam_c_prev __P((DBC *)); static int CDB___bam_c_put __P((DBC *, DBT *, DBT *, u_int32_t)); static void CDB___bam_c_reset __P((BTREE_CURSOR *)); static int CDB___bam_c_rget __P((DBC *, DBT *, u_int32_t)); static int CDB___bam_c_search __P((DBC *, const DBT *, u_int32_t, int *)); static int CDB___bam_dsearch __P((DBC *, DBT *, u_int32_t *)); static int CDB___bam_dup __P((DBC *, u_int32_t, int)); /* * Acquire a new page/lock for the cursor. If we hold a page/lock, discard * the page, and lock-couple the lock. * * !!! * We have to handle both where we have a lock to lock-couple and where we * don't -- we don't duplicate locks when we duplicate cursors if we are * running in a transaction environment as there's no point if locks are * never discarded. This means that the cursor may or may no hold a lock. */ #undef ACQUIRE #define ACQUIRE(dbc, pgno, mode, ret) { \ BTREE_CURSOR *__cp = (dbc)->internal; \ if (__cp->page != NULL) { \ ret = CDB_memp_fput((dbc)->dbp->mpf, __cp->page, 0); \ __cp->page = NULL; \ } else \ ret = 0; \ if (ret == 0 && mode != DB_LOCK_NG && \ (ret = CDB___db_lget(dbc, \ __cp->lock.off == LOCK_INVALID ? 0 : 1, \ pgno, mode, 0, &__cp->lock)) != 0) \ __cp->lock_mode = mode; \ if (ret == 0) \ ret = CDB_memp_fget( \ (dbc)->dbp->mpf, &(pgno), 0, &__cp->page); \ } /* * Acquire a write lock if we don't already have one. * * !!! * See ACQUIRE macro on why we handle cursors that don't have locks. */ #undef ACQUIRE_WRITE_LOCK #define ACQUIRE_WRITE_LOCK(dbc, ret) { \ BTREE_CURSOR *__cp = (dbc)->internal; \ if (F_ISSET((dbc)->dbp->dbenv, DB_ENV_LOCKING) && \ __cp->lock_mode != DB_LOCK_WRITE && \ ((ret) = CDB___db_lget(dbc, \ __cp->lock.off == LOCK_INVALID ? 0 : 1, \ __cp->pgno, DB_LOCK_WRITE, 0, &__cp->lock)) == 0) \ __cp->lock_mode = DB_LOCK_WRITE; \ } /* Discard the current page/lock held by a cursor. */ #undef DISCARD #define DISCARD(dbc, ret) { \ BTREE_CURSOR *__cp = (dbc)->internal; \ int __t_ret; \ if (__cp->page != NULL) { \ ret = CDB_memp_fput((dbc)->dbp->mpf, __cp->page, 0); \ __cp->page = NULL; \ } else \ ret = 0; \ if (__cp->lock.off != LOCK_INVALID) { \ if ((__t_ret = \ __TLPUT((dbc), __cp->lock)) != 0 && (ret) == 0) \ ret = __t_ret; \ __cp->lock.off = LOCK_INVALID; \ __cp->lock_mode = DB_LOCK_NG; \ } \ } /* If the cursor references a deleted record. */ #undef IS_CUR_DELETED #define IS_CUR_DELETED(cp) \ (((cp)->dpgno == PGNO_INVALID && \ B_DISSET(GET_BKEYDATA((cp)->page, \ (cp)->indx + O_INDX)->type)) || \ ((cp)->dpgno != PGNO_INVALID && \ B_DISSET(GET_BKEYDATA((cp)->page, (cp)->dindx)->type))) /* If the cursor and index combination references a deleted record. */ #undef IS_DELETED #define IS_DELETED(cp, indx) \ (((cp)->dpgno == PGNO_INVALID && \ B_DISSET(GET_BKEYDATA((cp)->page, (indx) + O_INDX)->type)) || \ ((cp)->dpgno != PGNO_INVALID && \ B_DISSET(GET_BKEYDATA((cp)->page, (indx))->type))) /* * Test to see if two cursors could point to duplicates of the same key, * whether on-page or off-page. The leaf page numbers must be the same * in both cases. In the case of off-page duplicates, the key indices * on the leaf page will be the same. In the case of on-page duplicates, * the duplicate page number must not be set, and the key index offsets * must be the same. For the last test, as the saved copy of the cursor * will not have a valid page pointer, we use the cursor's. */ #undef POSSIBLE_DUPLICATE #define POSSIBLE_DUPLICATE(cursor, copy) \ ((cursor)->pgno == (copy)->pgno && \ ((cursor)->indx == (copy)->indx || \ ((cursor)->dpgno == PGNO_INVALID && \ (copy)->dpgno == PGNO_INVALID && \ (cursor)->page->inp[(cursor)->indx] == \ (cursor)->page->inp[(copy)->indx]))) /* * CDB___bam_c_reset -- * Initialize internal cursor structure. */ static void CDB___bam_c_reset(cp) BTREE_CURSOR *cp; { cp->sp = cp->csp = cp->stack; cp->esp = cp->stack + sizeof(cp->stack) / sizeof(cp->stack[0]); cp->page = NULL; cp->pgno = PGNO_INVALID; cp->indx = 0; cp->dpgno = PGNO_INVALID; cp->dindx = 0; cp->lock.off = LOCK_INVALID; cp->lock_mode = DB_LOCK_NG; cp->recno = RECNO_OOB; cp->flags = 0; } /* * CDB___bam_c_init -- * Initialize the access private portion of a cursor * * PUBLIC: int CDB___bam_c_init __P((DBC *)); */ int CDB___bam_c_init(dbc) DBC *dbc; { BTREE_CURSOR *cp; DB *dbp; int ret; dbp = dbc->dbp; /* Allocate the internal structure. */ if ((ret = CDB___os_calloc(1, sizeof(BTREE_CURSOR), &cp)) != 0) return (ret); /* * Logical record numbers are always the same size, and we don't want * to have to check for space every time we return one. Allocate it * in advance. */ if (dbp->type == DB_RECNO || F_ISSET(dbp, DB_BT_RECNUM)) { if ((ret = CDB___os_malloc(sizeof(db_recno_t), NULL, &dbc->rkey.data)) != 0) { CDB___os_free(cp, sizeof(BTREE_CURSOR)); return (ret); } dbc->rkey.ulen = sizeof(db_recno_t); } /* Initialize methods. */ dbc->internal = cp; if (dbp->type == DB_BTREE) { dbc->c_am_close = CDB___bam_c_close; dbc->c_am_destroy = CDB___bam_c_destroy; dbc->c_del = CDB___bam_c_del; dbc->c_get = CDB___bam_c_get; dbc->c_put = CDB___bam_c_put; } else { dbc->c_am_close = CDB___bam_c_close; dbc->c_am_destroy = CDB___bam_c_destroy; dbc->c_del = CDB___ram_c_del; dbc->c_get = CDB___ram_c_get; dbc->c_put = CDB___ram_c_put; } /* Initialize dynamic information. */ CDB___bam_c_reset(cp); return (0); } /* * CDB___bam_c_dup -- * Duplicate a btree cursor, such that the new one holds appropriate * locks for the position of the original. * * PUBLIC: int CDB___bam_c_dup __P((DBC *, DBC *)); */ int CDB___bam_c_dup(orig_dbc, new_dbc) DBC *orig_dbc, *new_dbc; { BTREE_CURSOR *orig, *new; orig = orig_dbc->internal; new = new_dbc->internal; CDB___bam_c_reset(new); new->pgno = orig->pgno; new->indx = orig->indx; new->dpgno = orig->dpgno; new->dindx = orig->dindx; new->recno = orig->recno; new->lock_mode = orig->lock_mode; if (orig->lock.off == LOCK_INVALID) return (0); /* * If we are in a transaction, then we do not need to reacquire * a lock, because we automatically hold all locks until transaction * completion. */ if (orig_dbc->txn == NULL) return (CDB___db_lget(new_dbc, 0, new->pgno, new->lock_mode, 0, &new->lock)); return (0); } /* * CDB___bam_c_close -- * Close down the cursor from a single use. */ static int CDB___bam_c_close(dbc) DBC *dbc; { BTREE_CURSOR *cp; DB *dbp; int ret, t_ret; dbp = dbc->dbp; cp = dbc->internal; ret = 0; /* * If a cursor deleted a btree key, perform the actual deletion. * (Recno keys are either deleted immediately or never deleted.) */ if (dbp->type == DB_BTREE && F_ISSET(cp, C_DELETED)) ret = CDB___bam_c_physdel(dbc); /* Discard any locks not acquired inside of a transaction. */ if (cp->lock.off != LOCK_INVALID) { if ((t_ret = __TLPUT(dbc, cp->lock)) != 0 && ret == 0) ret = t_ret; cp->lock.off = LOCK_INVALID; } /* Confirm that the stack has been emptied. */ DB_ASSERT(cp->csp == cp->stack); /* Initialize dynamic information. */ CDB___bam_c_reset(cp); return (ret); } /* * CDB___bam_c_destroy -- * Close a single cursor -- internal version. */ static int CDB___bam_c_destroy(dbc) DBC *dbc; { /* Discard the structures. */ CDB___os_free(dbc->internal, sizeof(BTREE_CURSOR)); return (0); } /* * CDB___bam_c_del -- * Delete using a cursor. */ static int CDB___bam_c_del(dbc, flags) DBC *dbc; u_int32_t flags; { BTREE_CURSOR *cp; DB *dbp; PAGE *h; db_pgno_t pgno; db_indx_t indx; int ret; dbp = dbc->dbp; cp = dbc->internal; h = NULL; PANIC_CHECK(dbp->dbenv); /* Check for invalid flags. */ if ((ret = CDB___db_cdelchk(dbp, flags, F_ISSET(dbp, DB_AM_RDONLY), cp->pgno != PGNO_INVALID)) != 0) return (ret); DEBUG_LWRITE(dbc, dbc->txn, "bam_c_del", NULL, NULL, flags); /* If already deleted, return failure. */ if (F_ISSET(cp, C_DELETED)) return (DB_KEYEMPTY); /* * If we are running CDB, this had better be either a write * cursor or an immediate writer. If it's a regular writer, * that means we have an IWRITE lock and we need to upgrade * it to a write lock. */ if (F_ISSET(dbp->dbenv, DB_ENV_CDB)) { if (!F_ISSET(dbc, DBC_WRITECURSOR | DBC_WRITER)) return (EPERM); if (F_ISSET(dbc, DBC_WRITECURSOR) && (ret = CDB_lock_get(dbp->dbenv, dbc->locker, DB_LOCK_UPGRADE, &dbc->lock_dbt, DB_LOCK_WRITE, &dbc->mylock)) != 0) return (ret); } /* * We don't physically delete the record until the cursor moves, so * we have to have a long-lived write lock on the page instead of a * a long-lived read lock. * * We have to have a read lock to even get here. We lock-couple so * that we don't lose our read lock on failure. That's probably not * necessary, our only failure mode is deadlock and once we deadlock * the cursor shouldn't have to support further operations. */ ACQUIRE_WRITE_LOCK(dbc, ret); if (ret != 0) goto err; /* * Acquire the underlying page and set the on-page and in-cursor * delete flags. */ if (cp->dpgno == PGNO_INVALID) { pgno = cp->pgno; indx = cp->indx; } else { pgno = cp->dpgno; indx = cp->dindx; } if ((ret = CDB_memp_fget(dbp->mpf, &pgno, 0, &h)) != 0) goto err; /* Log the change. */ if (DB_LOGGING(dbc) && (ret = CDB___bam_cdel_log(dbp->dbenv, dbc->txn, &LSN(h), 0, dbp->log_fileid, PGNO(h), &LSN(h), indx)) != 0) goto err; /* Set the intent-to-delete flag on the page and update all cursors. */ if (cp->dpgno == PGNO_INVALID) B_DSET(GET_BKEYDATA(h, indx + O_INDX)->type); else B_DSET(GET_BKEYDATA(h, indx)->type); (void)CDB___bam_ca_delete(dbp, pgno, indx, 1); if ((ret = CDB_memp_fput(dbp->mpf, h, DB_MPOOL_DIRTY)) != 0) goto err; h = NULL; /* * If the tree has record numbers, we have to adjust the counts. * * !!! * This test is right -- we don't yet support duplicates and record * numbers in the same tree, so ignore duplicates if DB_BT_RECNUM * set. */ if (F_ISSET(dbp, DB_BT_RECNUM)) { if ((ret = CDB___bam_c_getstack(dbc)) != 0) goto err; if ((ret = CDB___bam_adjust(dbc, -1)) != 0) goto err; (void)CDB___bam_stkrel(dbc, 0); } err: if (h != NULL) (void)CDB_memp_fput(dbp->mpf, h, 0); /* Release the upgraded lock. */ if (F_ISSET(dbc, DBC_WRITECURSOR)) (void)CDB___lock_downgrade(dbp->dbenv, &dbc->mylock, DB_LOCK_IWRITE, 0); return (ret); } /* * CDB___bam_c_get -- * Get using a cursor (btree). */ static int CDB___bam_c_get(dbc_orig, key, data, flags) DBC *dbc_orig; DBT *key, *data; u_int32_t flags; { BTREE_CURSOR *cp, *orig, start; DB *dbp; DBC *dbc; PAGE *h; u_int32_t tmp_rmw; int exact, ret; dbp = dbc_orig->dbp; orig = dbc_orig->internal; PANIC_CHECK(dbp->dbenv); /* Check for invalid flags. */ if ((ret = CDB___db_cgetchk(dbp, key, data, flags, orig->pgno != PGNO_INVALID)) != 0) return (ret); /* Clear OR'd in additional bits so we can check for flag equality. */ tmp_rmw = LF_ISSET(DB_RMW); LF_CLR(DB_RMW); DEBUG_LREAD(dbc_orig, dbc_orig->txn, "bam_c_get", flags == DB_SET || flags == DB_SET_RANGE ? key : NULL, NULL, flags); /* * Return a cursor's record number. It has nothing to do with the * cursor get code except that it's been rammed into the interface. */ if (flags == DB_GET_RECNO) return (CDB___bam_c_rget(dbc_orig, data, flags | tmp_rmw)); /* Get a copy of the original cursor, including position. */ if ((ret = dbc_orig->c_dup(dbc_orig, &dbc, DB_POSITIONI)) != 0) return (ret); if (tmp_rmw) F_SET(dbc, DBC_RMW); cp = dbc->internal; switch (flags) { case DB_CURRENT: /* It's not possible to return a deleted record. */ if (F_ISSET(orig, C_DELETED)) { ret = DB_KEYEMPTY; goto err; } /* * Acquire the current page. We have at least a read-lock * already. The caller may have set DB_RMW asking for a * write lock, but any upgrade to a write lock has no better * chance of succeeding now instead of later, so we don't try. */ if ((ret = CDB_memp_fget(dbp->mpf, cp->dpgno == PGNO_INVALID ? &cp->pgno : &cp->dpgno, 0, &cp->page)) != 0) goto err; break; case DB_NEXT_DUP: if (cp->pgno == PGNO_INVALID) { ret = EINVAL; goto err; } if ((ret = CDB___bam_c_next(dbc, 1)) != 0) goto err; /* Make sure we didn't go past the end of the duplicates. */ if (!POSSIBLE_DUPLICATE(cp, orig)) { ret = DB_NOTFOUND; goto err; } break; case DB_NEXT: if (cp->pgno != PGNO_INVALID) { if ((ret = CDB___bam_c_next(dbc, 1)) != 0) goto err; break; } /* FALLTHROUGH */ case DB_FIRST: if ((ret = CDB___bam_c_first(dbc)) != 0) goto err; break; case DB_PREV: if (cp->pgno != PGNO_INVALID) { if ((ret = CDB___bam_c_prev(dbc)) != 0) goto err; break; } /* FALLTHROUGH */ case DB_LAST: if ((ret = CDB___bam_c_last(dbc)) != 0) goto err; break; case DB_SET: if ((ret = CDB___bam_c_search(dbc, key, flags, &exact)) != 0) goto err; /* * We cannot be referencing a non-existent or deleted record * because we specified an exact match. We may be referencing * off-page duplicates. * * If we're referencing off-page duplicates, move off-page. * If we moved off-page, move to the next non-deleted record. * If we moved to the next non-deleted record, check to make * sure we didn't switch records because our current record * had no non-deleted data items. */ start = *cp; if ((ret = CDB___bam_dup(dbc, cp->indx, 0)) != 0) goto err; if (cp->dpgno != PGNO_INVALID && IS_CUR_DELETED(cp)) { if ((ret = CDB___bam_c_next(dbc, 0)) != 0) goto err; if (!POSSIBLE_DUPLICATE(cp, &start)) { ret = DB_NOTFOUND; goto err; } } break; case DB_SET_RECNO: if ((ret = CDB___bam_c_search(dbc, key, flags, &exact)) != 0) goto err; break; case DB_GET_BOTH: if (F_ISSET(dbc, DBC_CONTINUE)) { /* Acquire the current page. */ if ((ret = CDB_memp_fget(dbp->mpf, cp->dpgno == PGNO_INVALID ? &cp->pgno : &cp->dpgno, 0, &cp->page)) != 0) goto err; /* Move to the next item. */ start = *cp; if ((ret = CDB___bam_c_next(dbc, 1)) != 0) goto err; /* Verify that we haven't moved to a new key. */ if (!POSSIBLE_DUPLICATE(cp, &start)) { ret = DB_NOTFOUND; goto err; } } else { if ((ret = CDB___bam_c_search(dbc, key, flags, &exact)) != 0) goto err; /* * We cannot be referencing a non-existent or deleted * record because we specified an exact match. We may * be referencing off-page duplicates. */ if ((ret = CDB___bam_dup(dbc, cp->indx, 0)) != 0) goto err; } /* Search for a matching entry. */ if ((ret = CDB___bam_dsearch(dbc, data, NULL)) != 0) goto err; /* Ignore deleted entries. */ if (IS_CUR_DELETED(cp)) { ret = DB_NOTFOUND; goto err; } break; case DB_SET_RANGE: if ((ret = CDB___bam_c_search(dbc, key, flags, &exact)) != 0) goto err; /* * As we didn't require an exact match, the search function * may have returned an entry past the end of the page. Or, * we may be referencing a deleted record. If so, move to * the next entry. */ if (cp->indx == NUM_ENT(cp->page) || IS_CUR_DELETED(cp)) if ((ret = CDB___bam_c_next(dbc, 0)) != 0) goto err; /* * If we're referencing off-page duplicates, move off-page. * If we moved off-page, move to the next non-deleted record. */ if ((ret = CDB___bam_dup(dbc, cp->indx, 0)) != 0) goto err; if (cp->dpgno != PGNO_INVALID && IS_CUR_DELETED(cp)) if ((ret = CDB___bam_c_next(dbc, 0)) != 0) goto err; break; } /* * Return the key if the user didn't give us one. If we've moved to * a duplicate page, we may no longer have a pointer to the main page, * so we have to go get it. We know that it's already read-locked, * however, so we don't have to acquire a new lock. */ if (flags != DB_SET) { if (cp->dpgno != PGNO_INVALID) { if ((ret = CDB_memp_fget(dbp->mpf, &cp->pgno, 0, &h)) != 0) goto err; } else h = cp->page; ret = CDB___db_ret(dbp, h, cp->indx, key, &dbc->rkey.data, &dbc->rkey.ulen); if (cp->dpgno != PGNO_INVALID) (void)CDB_memp_fput(dbp->mpf, h, 0); if (ret) goto err; } /* Return the data. */ if ((ret = CDB___db_ret(dbp, cp->page, cp->dpgno == PGNO_INVALID ? cp->indx + O_INDX : cp->dindx, data, &dbc->rdata.data, &dbc->rdata.ulen)) != 0) goto err; /* Release the current page. */ if ((ret = CDB_memp_fput(dbp->mpf, cp->page, 0)) != 0) goto err; cp->page = NULL; /* Release the temporary lock upgrade. */ if (tmp_rmw) F_CLR(dbc, DBC_RMW); /* * Swap the cursors so we are left with the new position inside of * the original DBCs structure, and close the dup'd cursor once it * references the old position. * * The close can fail, but we only expect DB_LOCK_DEADLOCK failures. * This violates our "the cursor is unchanged on error" semantics, * but since all you can do with a DB_LOCK_DEADLOCK failure is close * the cursor, I believe that's OK. */ orig = dbc_orig->internal; dbc_orig->internal = dbc->internal; dbc->internal = orig; ret = dbc->c_close(dbc); if (0) { err: /* Discard any page we acquired. */ if (cp->page != NULL) (void)CDB_memp_fput(dbp->mpf, cp->page, 0); /* Close the newly dup'd cursor. */ (void)dbc->c_close(dbc); } return (ret); } /* * CDB___bam_dsearch -- * Search for a matching data item (or the first data item that's * equal to or greater than the one we're searching for). */ static int CDB___bam_dsearch(dbc, data, iflagp) DBC *dbc; DBT *data; u_int32_t *iflagp; /* Non-NULL if we're doing an insert. */ { BTREE_CURSOR *cp, copy, last; DB *dbp; int cmp, ret; dbp = dbc->dbp; cp = dbc->internal; /* If the duplicates are off-page, use the duplicate search routine. */ if (cp->dpgno != PGNO_INVALID) { if ((ret = CDB___db_dsearch(dbc, iflagp != NULL, data, cp->dpgno, &cp->dindx, &cp->page, &cmp)) != 0) return (ret); cp->dpgno = cp->page->pgno; if (iflagp == NULL) { if (cmp != 0) return (DB_NOTFOUND); return (0); } *iflagp = DB_BEFORE; return (0); } /* Otherwise, do the search ourselves. */ copy = *cp; for (;;) { /* Save the last interesting cursor position. */ last = *cp; /* See if the data item matches the one we're looking for. */ if ((cmp = CDB___bam_cmp(dbp, data, cp->page, cp->indx + O_INDX, dbp->dup_compare == NULL ? CDB___bam_defcmp : dbp->dup_compare)) == 0) { if (iflagp != NULL) *iflagp = DB_AFTER; return (0); } /* * If duplicate entries are sorted, we're done if we find a * page entry that sorts greater than the application item. * If doing an insert, return success, otherwise DB_NOTFOUND. */ if (dbp->dup_compare != NULL && cmp < 0) { if (iflagp == NULL) return (DB_NOTFOUND); *iflagp = DB_BEFORE; return (0); } /* * Move to the next item. If we reach the end of the page and * we're doing an insert, set the cursor to the last item and * set the referenced memory location so callers know to insert * after the item, instead of before it. If not inserting, we * return DB_NOTFOUND. */ if ((cp->indx += P_INDX) >= NUM_ENT(cp->page)) { if (iflagp == NULL) return (DB_NOTFOUND); goto use_last; } /* * Make sure we didn't go past the end of the duplicates. The * error conditions are the same as above. */ if (!POSSIBLE_DUPLICATE(cp, ©)) { if (iflagp == NULL) return (DB_NOTFOUND); use_last: *cp = last; *iflagp = DB_AFTER; return (0); } } /* NOTREACHED */ } /* * CDB___bam_c_rget -- * Return the record number for a cursor. */ static int CDB___bam_c_rget(dbc, data, flags) DBC *dbc; DBT *data; u_int32_t flags; { BTREE_CURSOR *cp; DB *dbp; DBT dbt; db_recno_t recno; int exact, ret; COMPQUIET(flags, 0); dbp = dbc->dbp; cp = dbc->internal; /* Get the page with the current item on it. */ if ((ret = CDB_memp_fget(dbp->mpf, &cp->pgno, 0, &cp->page)) != 0) return (ret); /* Get a copy of the key. */ memset(&dbt, 0, sizeof(DBT)); dbt.flags = DB_DBT_MALLOC | DB_DBT_INTERNAL; if ((ret = CDB___db_ret(dbp, cp->page, cp->indx, &dbt, NULL, NULL)) != 0) goto err; exact = 1; if ((ret = CDB___bam_search(dbc, &dbt, F_ISSET(dbc, DBC_RMW) ? S_FIND_WR : S_FIND, 1, &recno, &exact)) != 0) goto err; ret = CDB___db_retcopy(dbp, data, &recno, sizeof(recno), &dbc->rdata.data, &dbc->rdata.ulen); /* Release the stack. */ CDB___bam_stkrel(dbc, 0); err: (void)CDB_memp_fput(dbp->mpf, cp->page, 0); CDB___os_free(dbt.data, dbt.size); return (ret); } /* * CDB___bam_c_put -- * Put using a cursor. */ static int CDB___bam_c_put(dbc_orig, key, data, flags) DBC *dbc_orig; DBT *key, *data; u_int32_t flags; { BTREE_CURSOR *cp, *orig; DB *dbp; DBC *dbc; DBT dbt; db_indx_t indx; db_pgno_t pgno; u_int32_t iiop; int exact, needkey, ret, ret_ignore, stack; void *arg; dbp = dbc_orig->dbp; orig = dbc_orig->internal; PANIC_CHECK(dbp->dbenv); /* Check for invalid flags. */ if ((ret = CDB___db_cputchk(dbp, key, data, flags, F_ISSET(dbp, DB_AM_RDONLY), orig->pgno != PGNO_INVALID)) != 0) return (ret); DEBUG_LWRITE(dbc_orig, dbc_orig->txn, "bam_c_put", flags == DB_KEYFIRST || flags == DB_KEYLAST ? key : NULL, data, flags); /* * If we are running CDB, this had better be either a write * cursor or an immediate writer. If it's a regular writer, * that means we have an IWRITE lock and we need to upgrade * it to a write lock. */ if (F_ISSET(dbp->dbenv, DB_ENV_CDB)) { if (!F_ISSET(dbc_orig, DBC_WRITECURSOR | DBC_WRITER)) return (EPERM); if (F_ISSET(dbc_orig, DBC_WRITECURSOR) && (ret = CDB_lock_get(dbp->dbenv, dbc_orig->locker, DB_LOCK_UPGRADE, &dbc_orig->lock_dbt, DB_LOCK_WRITE, &dbc_orig->mylock)) != 0) return (ret); } if (0) { split: /* * To split, we need a valid key for the page, and since it's * a cursor, we may have to build one. Get a copy of a key * from the page. */ if (needkey) { memset(&dbt, 0, sizeof(DBT)); if ((ret = CDB___db_ret(dbp, cp->page, indx, &dbt, &dbc->rkey.data, &dbc->rkey.ulen)) != 0) goto err; arg = &dbt; } else arg = key; /* * Discard any locks and pinned pages (the locks are discarded * even if we're running with transactions, as they lock pages * that we're sorry we ever acquired). If stack is set and the * cursor entries are valid, they point to the same entries as * the stack, don't free them twice. */ if (stack) { (void)CDB___bam_stkrel(dbc, 1); stack = 0; } else { DISCARD(dbc, ret); if (ret != 0) goto err; } /* Close the newly dup'd cursor. */ (void)dbc->c_close(dbc); /* Split the tree. */ if ((ret = CDB___bam_split(dbc_orig, arg)) != 0) return (ret); } /* Get a copy of the original cursor, including position. */ if ((ret = dbc_orig->c_dup(dbc_orig, &dbc, DB_POSITIONI)) != 0) return (ret); cp = dbc->internal; needkey = ret = stack = 0; switch (flags) { case DB_AFTER: case DB_BEFORE: case DB_CURRENT: needkey = 1; if (cp->dpgno == PGNO_INVALID) { pgno = cp->pgno; indx = cp->indx; } else { pgno = cp->dpgno; indx = cp->dindx; } /* * !!! * This test is right -- we don't yet support duplicates and * record numbers in the same tree, so ignore duplicates if * DB_BT_RECNUM set. */ if (F_ISSET(dbp, DB_BT_RECNUM) && (flags != DB_CURRENT || F_ISSET(orig, C_DELETED))) { /* Acquire a complete stack. */ if ((ret = CDB___bam_c_getstack(dbc)) != 0) goto err; cp->page = cp->csp->page; stack = 1; } else { /* Acquire the current page with a write lock. */ ACQUIRE_WRITE_LOCK(dbc, ret); if (ret != 0) goto err; if ((ret = CDB_memp_fget(dbp->mpf, &pgno, 0, &cp->page)) != 0) goto err; } iiop = flags; break; case DB_KEYFIRST: case DB_KEYLAST: /* * If we have a duplicate comparison function, we position to * the first of any on-page duplicates, and use CDB___bam_dsearch * to search for the right slot. Otherwise, we position to * the first/last of any on-page duplicates based on the flag * value. */ if ((ret = CDB___bam_c_search(dbc, key, flags == DB_KEYFIRST || dbp->dup_compare != NULL ? DB_KEYFIRST : DB_KEYLAST, &exact)) != 0) goto err; stack = 1; /* * If an exact match: * If duplicates aren't supported, replace the current * item. (When implementing the DB->put function, our * caller has already checked the DB_NOOVERWRITE flag.) * * If there's a duplicate comparison function, find the * correct slot for this duplicate item. * * If there's no duplicate comparison function, set the * insert flag based on the argument flags. * * If there's no match, the search function returned the * smallest slot greater than the key, use it. */ if (exact) { if (F_ISSET(dbp, DB_AM_DUP)) { /* * If at off-page duplicate page, move to the * first or last entry -- if a comparison * function was specified, start searching at * the first entry. Otherwise, move based on * the DB_KEYFIRST/DB_KEYLAST flags. */ if ((ret = CDB___bam_dup(dbc, cp->indx, dbp->dup_compare == NULL && flags != DB_KEYFIRST)) != 0) goto err; /* * If there's a comparison function, search for * the correct slot. Otherwise, set the insert * flag based on the argment flag. */ if (dbp->dup_compare == NULL) iiop = flags == DB_KEYFIRST ? DB_BEFORE : DB_AFTER; else if ((ret = CDB___bam_dsearch( dbc, data, &iiop)) != 0) goto err; } else iiop = DB_CURRENT; } else iiop = DB_KEYFIRST; if (cp->dpgno == PGNO_INVALID) { pgno = cp->pgno; indx = cp->indx; } else { pgno = cp->dpgno; indx = cp->dindx; } break; } ret = CDB___bam_iitem(dbc, &cp->page, &indx, key, data, iiop, 0); if (ret == DB_NEEDSPLIT) goto split; if (ret != 0) goto err; /* * Discard any pages pinned in the tree and their locks, except for * the leaf page, for which we only discard the pin, not the lock. * * Note, the leaf page participated in the stack we acquired, and so * we have to adjust the stack as necessary. If there was only a * single page on the stack, we don't have to free further stack pages. */ if (stack && BT_STK_POP(cp) != NULL) (void)CDB___bam_stkrel(dbc, 0); /* Release the current page. */ if ((ret = CDB_memp_fput(dbp->mpf, cp->page, 0)) != 0) goto err; /* * Swap the cursors so we are left with the new position inside of * the original DBCs structure, and close the dup'd cursor once it * references the old position. * * The close can fail, but we only expect DB_LOCK_DEADLOCK failures. * This violates our "the cursor is unchanged on error" semantics, * but since all you can do with a DB_LOCK_DEADLOCK failure is close * the cursor, I believe that's OK. */ orig = dbc_orig->internal; dbc_orig->internal = dbc->internal; dbc->internal = orig; ret = dbc->c_close(dbc); if (0) { err: /* Discard any page(s) we acquired. */ if (stack) (void)CDB___bam_stkrel(dbc, 0); else DISCARD(dbc, ret_ignore); /* Close the newly dup'd cursor. */ (void)dbc->c_close(dbc); } /* Release the upgraded lock. */ if (F_ISSET(dbc_orig, DBC_WRITECURSOR)) (void)CDB___lock_downgrade(dbp->dbenv, &dbc_orig->mylock, DB_LOCK_IWRITE, 0); return (ret); } /* * CDB___bam_c_first -- * Return the first record. */ static int CDB___bam_c_first(dbc) DBC *dbc; { BTREE_CURSOR *cp; DB *dbp; db_pgno_t pgno; int ret; dbp = dbc->dbp; cp = dbc->internal; ret = 0; /* Walk down the left-hand side of the tree. */ for (pgno = ((BTREE *)dbp->bt_internal)->bt_root;;) { ACQUIRE(dbc, pgno, DB_LOCK_READ, ret); if (ret != 0) return (ret); /* If we find a leaf page, we're done. */ if (ISLEAF(cp->page)) break; pgno = GET_BINTERNAL(cp->page, 0)->pgno; } /* If we want a write lock instead of a read lock, get it now. */ if (F_ISSET(dbc, DBC_RMW)) { ACQUIRE_WRITE_LOCK(dbc, ret); if (ret != 0) return (ret); } cp->pgno = cp->page->pgno; cp->indx = 0; cp->dpgno = PGNO_INVALID; /* * If we're referencing off-page duplicates, move off-page. * If on an empty page or a deleted record, move to the next one. */ if (NUM_ENT(cp->page) > 0) if ((ret = CDB___bam_dup(dbc, cp->indx, 0)) != 0) return (ret); if (NUM_ENT(cp->page) == 0 || IS_CUR_DELETED(cp)) if ((ret = CDB___bam_c_next(dbc, 0)) != 0) return (ret); return (0); } /* * CDB___bam_c_last -- * Return the last record. */ static int CDB___bam_c_last(dbc) DBC *dbc; { BTREE_CURSOR *cp; DB *dbp; db_pgno_t pgno; int ret; dbp = dbc->dbp; cp = dbc->internal; ret = 0; /* Walk down the right-hand side of the tree. */ for (pgno = ((BTREE *)dbp->bt_internal)->bt_root;;) { ACQUIRE(dbc, pgno, DB_LOCK_READ, ret); if (ret != 0) return (ret); /* If we find a leaf page, we're done. */ if (ISLEAF(cp->page)) break; pgno = GET_BINTERNAL(cp->page, NUM_ENT(cp->page) - O_INDX)->pgno; } /* If we want a write lock instead of a read lock, get it now. */ if (F_ISSET(dbc, DBC_RMW)) { ACQUIRE_WRITE_LOCK(dbc, ret); if (ret != 0) return (ret); } cp->pgno = cp->page->pgno; cp->indx = NUM_ENT(cp->page) == 0 ? 0 : NUM_ENT(cp->page) - P_INDX; cp->dpgno = PGNO_INVALID; /* * If we're referencing off-page duplicates, move off-page. * If on an empty page or a deleted record, move to the previous one. */ if (NUM_ENT(cp->page) > 0) if ((ret = CDB___bam_dup(dbc, cp->indx, 1)) != 0) return (ret); if (NUM_ENT(cp->page) == 0 || IS_CUR_DELETED(cp)) if ((ret = CDB___bam_c_prev(dbc)) != 0) return (ret); return (0); } /* * CDB___bam_c_next -- * Move to the next record. */ static int CDB___bam_c_next(dbc, initial_move) DBC *dbc; int initial_move; { BTREE_CURSOR *cp; DB *dbp; db_indx_t adjust, indx; db_lockmode_t lock_mode; db_pgno_t pgno; int ret; dbp = dbc->dbp; cp = dbc->internal; ret = 0; /* * We're either moving through a page of duplicates or a btree leaf * page. */ if (cp->dpgno == PGNO_INVALID) { adjust = dbp->type == DB_BTREE ? P_INDX : O_INDX; pgno = cp->pgno; indx = cp->indx; lock_mode = F_ISSET(dbc, DBC_RMW) ? DB_LOCK_WRITE : DB_LOCK_READ; } else { adjust = O_INDX; pgno = cp->dpgno; indx = cp->dindx; lock_mode = DB_LOCK_NG; } if (cp->page == NULL) { ACQUIRE(dbc, pgno, lock_mode, ret); if (ret != 0) return (ret); } /* * If at the end of the page, move to a subsequent page. * * !!! * Check for >= NUM_ENT. If we're here as the result of a search that * landed us on NUM_ENT, we'll increment indx before we test. * * !!! * This code handles empty pages and pages with only deleted entries. */ if (initial_move) indx += adjust; for (;;) { if (indx >= NUM_ENT(cp->page)) { /* * If we're in a btree leaf page, we've reached the end * of the tree. If we've reached the end of a page of * duplicates, continue from the btree leaf page where * we found this page of duplicates. */ pgno = cp->page->next_pgno; if (pgno == PGNO_INVALID) { /* If in a btree leaf page, it's EOF. */ if (cp->dpgno == PGNO_INVALID) return (DB_NOTFOUND); /* Continue from the last btree leaf page. */ cp->dpgno = PGNO_INVALID; adjust = P_INDX; pgno = cp->pgno; indx = cp->indx + P_INDX; lock_mode = F_ISSET(dbc, DBC_RMW) ? DB_LOCK_WRITE : DB_LOCK_READ; } else indx = 0; ACQUIRE(dbc, pgno, lock_mode, ret); if (ret != 0) return (ret); continue; } /* Ignore deleted records. */ if (IS_DELETED(cp, indx)) { indx += adjust; continue; } /* * If we're not in a duplicates page, check to see if we've * found a page of duplicates, in which case we move to the * first entry. */ if (cp->dpgno == PGNO_INVALID) { cp->pgno = cp->page->pgno; cp->indx = indx; if ((ret = CDB___bam_dup(dbc, indx, 0)) != 0) return (ret); if (cp->dpgno != PGNO_INVALID) { indx = cp->dindx; adjust = O_INDX; continue; } } else { cp->dpgno = cp->page->pgno; cp->dindx = indx; } break; } return (0); } /* * CDB___bam_c_prev -- * Move to the previous record. */ static int CDB___bam_c_prev(dbc) DBC *dbc; { BTREE_CURSOR *cp; DB *dbp; db_indx_t indx, adjust; db_lockmode_t lock_mode; db_pgno_t pgno; int ret, set_indx; dbp = dbc->dbp; cp = dbc->internal; ret = 0; /* * We're either moving through a page of duplicates or a btree leaf * page. */ if (cp->dpgno == PGNO_INVALID) { adjust = dbp->type == DB_BTREE ? P_INDX : O_INDX; pgno = cp->pgno; indx = cp->indx; lock_mode = F_ISSET(dbc, DBC_RMW) ? DB_LOCK_WRITE : DB_LOCK_READ; } else { adjust = O_INDX; pgno = cp->dpgno; indx = cp->dindx; lock_mode = DB_LOCK_NG; } if (cp->page == NULL) { ACQUIRE(dbc, pgno, lock_mode, ret); if (ret != 0) return (ret); } /* * If at the beginning of the page, move to any previous one. * * !!! * This code handles empty pages and pages with only deleted entries. */ for (;;) { if (indx == 0) { /* * If we're in a btree leaf page, we've reached the * beginning of the tree. If we've reached the first * of a page of duplicates, continue from the btree * leaf page where we found this page of duplicates. */ pgno = cp->page->prev_pgno; if (pgno == PGNO_INVALID) { /* If in a btree leaf page, it's SOF. */ if (cp->dpgno == PGNO_INVALID) return (DB_NOTFOUND); /* Continue from the last btree leaf page. */ cp->dpgno = PGNO_INVALID; adjust = P_INDX; pgno = cp->pgno; indx = cp->indx; set_indx = 0; lock_mode = F_ISSET(dbc, DBC_RMW) ? DB_LOCK_WRITE : DB_LOCK_READ; } else set_indx = 1; ACQUIRE(dbc, pgno, lock_mode, ret); if (ret != 0) return (ret); if (set_indx) indx = NUM_ENT(cp->page); if (indx == 0) continue; } /* Ignore deleted records. */ indx -= adjust; if (IS_DELETED(cp, indx)) continue; /* * If we're not in a duplicates page, check to see if we've * found a page of duplicates, in which case we move to the * last entry. */ if (cp->dpgno == PGNO_INVALID) { cp->pgno = cp->page->pgno; cp->indx = indx; if ((ret = CDB___bam_dup(dbc, indx, 1)) != 0) return (ret); if (cp->dpgno != PGNO_INVALID) { indx = cp->dindx + O_INDX; adjust = O_INDX; continue; } } else { cp->dpgno = cp->page->pgno; cp->dindx = indx; } break; } return (0); } /* * CDB___bam_c_search -- * Move to a specified record. */ static int CDB___bam_c_search(dbc, key, flags, exactp) DBC *dbc; const DBT *key; u_int32_t flags; int *exactp; { BTREE *t; BTREE_CURSOR *cp; DB *dbp; DB_LOCK lock; PAGE *h; db_recno_t recno; db_indx_t indx; u_int32_t sflags; int cmp, ret; dbp = dbc->dbp; cp = dbc->internal; t = dbp->bt_internal; ret = 0; /* Discard any previously held position. */ DISCARD(dbc, ret); if (ret != 0) return (ret); /* Find an entry in the database. */ switch (flags) { case DB_SET_RECNO: if ((ret = CDB___ram_getno(dbc, key, &recno, 0)) != 0) return (ret); sflags = (F_ISSET(dbc, DBC_RMW) ? S_FIND_WR : S_FIND) | S_EXACT; ret = CDB___bam_rsearch(dbc, &recno, sflags, 1, exactp); break; case DB_SET: case DB_GET_BOTH: sflags = (F_ISSET(dbc, DBC_RMW) ? S_FIND_WR : S_FIND) | S_EXACT; goto search; case DB_SET_RANGE: sflags = (F_ISSET(dbc, DBC_RMW) ? S_WRITE : S_READ) | S_DUPFIRST; goto search; case DB_KEYFIRST: sflags = S_KEYFIRST; goto fast_search; case DB_KEYLAST: sflags = S_KEYLAST; fast_search: /* * If the application has a history of inserting into the first * or last pages of the database, we check those pages first to * avoid doing a full search. * * Record numbers can't be fast-tracked, the entire tree has to * be locked. */ h = NULL; lock.off = LOCK_INVALID; if (F_ISSET(dbp, DB_BT_RECNUM)) goto search; /* Check if the application has a history of sorted input. */ if (t->bt_lpgno == PGNO_INVALID) goto search; /* * Lock and retrieve the page on which we did the last insert. * It's okay if it doesn't exist, or if it's not the page type * we expected, it just means that the world changed. */ cp->lock_mode = DB_LOCK_WRITE; if (CDB___db_lget(dbc, 0, t->bt_lpgno, cp->lock_mode, 0, &lock)) goto fast_miss; if (CDB_memp_fget(dbp->mpf, &t->bt_lpgno, 0, &h)) goto fast_miss; if (TYPE(h) != P_LBTREE) goto fast_miss; if (NUM_ENT(h) == 0) goto fast_miss; /* * What we do here is test to see if we're at the beginning or * end of the tree and if the new item sorts before/after the * first/last page entry. We don't try and catch inserts into * the middle of the tree (although we could, as long as there * were two keys on the page and we saved both the index and * the page number of the last insert). */ if (h->next_pgno == PGNO_INVALID) { indx = NUM_ENT(h) - P_INDX; if ((cmp = CDB___bam_cmp(dbp, key, h, indx, t->bt_compare)) < 0) goto try_begin; if (cmp > 0) { indx += P_INDX; goto fast_hit; } /* * Found a duplicate. If doing DB_KEYLAST, we're at * the correct position, otherwise, move to the first * of the duplicates. */ if (flags == DB_KEYLAST) goto fast_hit; for (; indx > 0 && h->inp[indx - P_INDX] == h->inp[indx]; indx -= P_INDX) ; goto fast_hit; } try_begin: if (h->prev_pgno == PGNO_INVALID) { indx = 0; if ((cmp = CDB___bam_cmp(dbp, key, h, indx, t->bt_compare)) > 0) goto fast_miss; if (cmp < 0) goto fast_hit; /* * Found a duplicate. If doing DB_KEYFIRST, we're at * the correct position, otherwise, move to the last * of the duplicates. */ if (flags == DB_KEYFIRST) goto fast_hit; for (; indx < (db_indx_t)(NUM_ENT(h) - P_INDX) && h->inp[indx] == h->inp[indx + P_INDX]; indx += P_INDX) ; goto fast_hit; } goto fast_miss; fast_hit: /* Set the exact match flag, we may have found a duplicate. */ *exactp = cmp == 0; /* Enter the entry in the stack. */ BT_STK_CLR(cp); BT_STK_ENTER(cp, h, indx, lock, cp->lock_mode, ret); break; fast_miss: if (h != NULL) (void)CDB_memp_fput(dbp->mpf, h, 0); /* * This is not the right page, so logically we do not need to * retain the lock. */ if (lock.off != LOCK_INVALID) (void)__LPUT(dbc, lock); search: ret = CDB___bam_search(dbc, key, sflags, 1, NULL, exactp); break; default: /* XXX: Impossible. */ abort(); /* NOTREACHED */ } if (ret != 0) return (ret); /* Initialize the cursor to reference the returned page. */ cp->page = cp->csp->page; cp->pgno = cp->csp->page->pgno; cp->indx = cp->csp->indx; cp->dpgno = PGNO_INVALID; cp->lock = cp->csp->lock; cp->lock_mode = cp->csp->lock_mode; /* * If we inserted a key into the first or last slot of the tree, * remember where it was so we can do it more quickly next time. */ if (flags == DB_KEYFIRST || flags == DB_KEYLAST) t->bt_lpgno = ((cp->page->next_pgno == PGNO_INVALID && cp->indx >= NUM_ENT(cp->page)) || (cp->page->prev_pgno == PGNO_INVALID && cp->indx == 0)) ? cp->pgno : PGNO_INVALID; return (0); } /* * CDB___bam_dup -- * Check for an off-page duplicates entry, and if found, move to the * first or last entry. */ static int CDB___bam_dup(dbc, indx, last_dup) DBC *dbc; u_int32_t indx; int last_dup; { BOVERFLOW *bo; BTREE_CURSOR *cp; DB *dbp; db_pgno_t pgno; int ret; dbp = dbc->dbp; cp = dbc->internal; /* We should be referencing a valid entry on the page. */ DB_ASSERT(NUM_ENT(cp->page) > 0); /* * It's possible that the entry is deleted, in which case it doesn't * have duplicates. */ if (IS_CUR_DELETED(cp)) return (0); /* * Check for an overflow entry. If we find one, move to the * duplicates page, and optionally move to the last record on * that page. * * !!! * We don't lock duplicates pages, we've already got the correct * lock on the main page. */ bo = GET_BOVERFLOW(cp->page, indx + O_INDX); if (B_TYPE(bo->type) != B_DUPLICATE) return (0); pgno = bo->pgno; if ((ret = CDB_memp_fput(dbp->mpf, cp->page, 0)) != 0) return (ret); cp->page = NULL; if (last_dup) { if ((ret = CDB___db_dend(dbc, pgno, &cp->page)) != 0) return (ret); indx = NUM_ENT(cp->page) - O_INDX; } else { if ((ret = CDB_memp_fget(dbp->mpf, &pgno, 0, &cp->page)) != 0) return (ret); indx = 0; } /* Update the cursor's duplicate information. */ cp->dpgno = cp->page->pgno; cp->dindx = indx; return (0); } /* * CDB___bam_c_physdel -- * Actually do the cursor deletion. */ static int CDB___bam_c_physdel(dbc) DBC *dbc; { enum { DELETE_ITEM, DELETE_PAGE, NOTHING_FURTHER } cmd; BOVERFLOW bo; BTREE_CURSOR *cp; DB *dbp; DBT dbt; DB_LOCK lock; PAGE *h; db_indx_t indx; db_pgno_t pgno, next_pgno, prev_pgno, root_pgno; int delete_page, local_page, ret; dbp = dbc->dbp; cp = dbc->internal; delete_page = ret = 0; /* Figure out what we're deleting. */ if (cp->dpgno == PGNO_INVALID) { pgno = cp->pgno; indx = cp->indx; } else { pgno = cp->dpgno; indx = cp->dindx; } /* * If the item is referenced by another cursor, make sure that * cursor's delete flag is set and leave it up to it to do the * delete. * * !!! * This test for > 0 is tricky. This code is called when we close * a cursor. In this case, we've already removed the cursor from * the active queue, so we won't see it in CDB___bam_ca_delete. */ if (CDB___bam_ca_delete(dbp, pgno, indx, 1) > 0) return (0); /* * If this is concurrent DB, upgrade the lock if necessary. */ if (F_ISSET(dbc, DBC_WRITECURSOR) && (ret = CDB_lock_get(dbp->dbenv, dbc->locker, DB_LOCK_UPGRADE, &dbc->lock_dbt, DB_LOCK_WRITE, &dbc->mylock)) != 0) return (ret); /* * Lock and retrieve the current page. We potentially have to acquire * a new lock here if the cursor that did the original logical deletion * and which already has a write lock is not the cursor that is doing * the physical deletion and which may only have a read lock. */ if ((ret = CDB___db_lget(dbc, 0, pgno, DB_LOCK_WRITE, 0, &lock)) != 0) return (ret); if ((ret = CDB_memp_fget(dbp->mpf, &pgno, 0, &h)) != 0) return (ret); local_page = 1; /* * If we're deleting a duplicate entry and there are other duplicate * entries remaining, call the common code to do the work and fix up * the parent page as necessary. Otherwise, do a normal btree delete. * * There are 5 possible cases: * * 1. It's not a duplicate item: do a normal btree delete. * 2. It's a duplicate item: * 2a: We delete an item from a page of duplicates, but there are * more items on the page. * 2b: We delete the last item from a page of duplicates, deleting * the last duplicate. * 2c: We delete the last item from a page of duplicates, but there * is a previous page of duplicates. * 2d: We delete the last item from a page of duplicates, but there * is a following page of duplicates. * * In the case of: * * 1: There's nothing further to do. * 2a: There's nothing further to do. * 2b: Do the normal btree delete instead of a duplicate delete, as * that deletes both the duplicate chain and the parent page's * entry. * 2c: There's nothing further to do. * 2d: Delete the duplicate, and update the parent page's entry. */ if (TYPE(h) == P_DUPLICATE) { pgno = PGNO(h); prev_pgno = PREV_PGNO(h); next_pgno = NEXT_PGNO(h); if (NUM_ENT(h) == 1 && prev_pgno == PGNO_INVALID && next_pgno == PGNO_INVALID) cmd = DELETE_PAGE; else { cmd = DELETE_ITEM; /* Delete the duplicate. */ if ((ret = CDB___db_drem(dbc, &h, indx)) != 0) goto err; /* * Update the cursors. * * !!! * The page referenced by h may have been modified, * don't use its page number. */ CDB___bam_ca_di(dbp, pgno, indx, -1); /* * 2a: h != NULL, h->pgno == pgno * 2b: We don't reach this clause, as the above test * was true. * 2c: h == NULL, prev_pgno != PGNO_INVALID * 2d: h != NULL, next_pgno != PGNO_INVALID * * Test for 2a and 2c: if we didn't empty the current * page or there was a previous page of duplicates, we * don't need to touch the parent page. */ if ((h != NULL && pgno == h->pgno) || prev_pgno != PGNO_INVALID) cmd = NOTHING_FURTHER; } /* * Release any page we're holding and its lock. * * !!! * If there is no subsequent page in the duplicate chain, then * CDB___db_drem will have put page "h" and set it to NULL. */ if (local_page) { if (h != NULL) (void)CDB_memp_fput(dbp->mpf, h, 0); (void)__TLPUT(dbc, lock); local_page = 0; } if (cmd == NOTHING_FURTHER) goto done; /* Acquire the parent page and switch the index to its entry. */ if ((ret = CDB___db_lget(dbc, 0, cp->pgno, DB_LOCK_WRITE, 0, &lock)) != 0) goto err; if ((ret = CDB_memp_fget(dbp->mpf, &cp->pgno, 0, &h)) != 0) { (void)__TLPUT(dbc, lock); goto err; } local_page = 1; indx = cp->indx; if (cmd == DELETE_PAGE) goto btd; /* * Copy, delete, update, add-back the parent page's data entry. * * XXX * This may be a performance/logging problem. We should add a * log message which simply logs/updates a random set of bytes * on a page, and use it instead of doing a delete/add pair. */ indx += O_INDX; bo = *GET_BOVERFLOW(h, indx); if ((ret = CDB___db_ditem(dbc, h, indx, BOVERFLOW_SIZE)) != 0) goto err; bo.pgno = next_pgno; memset(&dbt, 0, sizeof(dbt)); dbt.data = &bo; dbt.size = BOVERFLOW_SIZE; if ((ret = CDB___db_pitem(dbc, h, indx, BOVERFLOW_SIZE, &dbt, NULL)) != 0) goto err; if ((ret = CDB_memp_fset(dbp->mpf, h, DB_MPOOL_DIRTY)) != 0) goto err; goto done; } btd: /* * If the page is going to be emptied, delete it. To delete a leaf * page we need a copy of a key from the page. We use the 0th page * index since it's the last key that the page held. * * We malloc the page information instead of using the return key/data * memory because we've already set them -- the reason we've already * set them is because we're (potentially) about to do a reverse split, * which would make our saved page information useless. * * !!! * The following operations to delete a page might deadlock. I think * that's OK. The problem is if we're deleting an item because we're * closing cursors because we've already deadlocked and want to call * CDB_txn_abort(). If we fail due to deadlock, we leave a locked empty * page in the tree, which won't be empty long because we're going to * undo the delete. */ root_pgno = ((BTREE *)dbp->bt_internal)->bt_root; if (!F_ISSET(dbp, DB_BT_REVSPLIT) && NUM_ENT(h) == 2 && h->pgno != root_pgno) { memset(&dbt, 0, sizeof(DBT)); dbt.flags = DB_DBT_MALLOC | DB_DBT_INTERNAL; if ((ret = CDB___db_ret(dbp, h, 0, &dbt, NULL, NULL)) != 0) goto err; delete_page = 1; } /* * Do a normal btree delete. * * !!! * Delete the key item first, otherwise the duplicate checks in * CDB___bam_ditem() won't work! */ if ((ret = CDB___bam_ditem(dbc, h, indx)) != 0) goto err; if ((ret = CDB___bam_ditem(dbc, h, indx)) != 0) goto err; /* Discard any remaining locks/pages. */ if (local_page) { (void)CDB_memp_fput(dbp->mpf, h, 0); (void)__TLPUT(dbc, lock); local_page = 0; } /* Delete the page if it was emptied. */ if (delete_page) ret = CDB___bam_dpage(dbc, &dbt); err: done: if (delete_page) CDB___os_free(dbt.data, dbt.size); if (local_page) { /* * It's possible for h to be NULL, as CDB___db_drem may have * been relinking pages by the time that it deadlocked. */ if (h != NULL) (void)CDB_memp_fput(dbp->mpf, h, 0); (void)__TLPUT(dbc, lock); } if (F_ISSET(dbc, DBC_WRITECURSOR)) (void)CDB___lock_downgrade(dbp->dbenv, &dbc->mylock, DB_LOCK_IWRITE, 0); return (ret); } /* * CDB___bam_c_getstack -- * Acquire a full stack for a cursor. */ static int CDB___bam_c_getstack(dbc) DBC *dbc; { BTREE_CURSOR *cp; DB *dbp; DBT dbt; PAGE *h; db_pgno_t pgno; int exact, ret; dbp = dbc->dbp; cp = dbc->internal; memset(&dbt, 0, sizeof(DBT)); h = NULL; ret = 0; /* Get the page with the current item on it. */ pgno = cp->pgno; if ((ret = CDB_memp_fget(dbp->mpf, &pgno, 0, &h)) != 0) return (ret); /* Get a copy of a key from the page. */ dbt.flags = DB_DBT_MALLOC | DB_DBT_INTERNAL; if ((ret = CDB___db_ret(dbp, h, 0, &dbt, NULL, NULL)) != 0) goto err; /* Get a write-locked stack for that page. */ exact = 0; ret = CDB___bam_search(dbc, &dbt, S_KEYFIRST, 1, NULL, &exact); /* We no longer need the key or the page. */ err: if (h != NULL) (void)CDB_memp_fput(dbp->mpf, h, 0); if (dbt.data != NULL) CDB___os_free(dbt.data, dbt.size); return (ret); }