/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 1996, 1997, 1998
* Sleepycat Software. All rights reserved.
*/
#include "config.h"
#ifndef lint
#endif /* not lint */
#ifndef NO_SYSTEM_INCLUDES
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#endif
#include "db_int.h"
#include "db_page.h"
#include "btree.h"
#include "shqueue.h"
#include "db_shash.h"
#include "lock.h"
#include "lock_ext.h"
} \
} \
}
/* If the cursor references a deleted record. */
/* If the cursor and index combination references a deleted record. */
/*
* 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.
*/
/*
* __bam_c_reset --
* Initialize internal cursor structure.
*/
static void
{
}
/*
* __bam_c_init --
* Initialize the access private portion of a cursor
*
* PUBLIC: int __bam_c_init __P((DBC *));
*/
int
{
int ret;
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.
*/
return (ret);
}
}
/* Initialize methods. */
} else {
}
/* Initialize dynamic information. */
return (0);
}
/*
* __bam_c_close --
* Close down the cursor from a single use.
*/
static int
{
int ret;
ret = 0;
/*
* If a cursor deleted a btree key, perform the actual deletion.
* (Recno keys are either deleted immediately or never deleted.)
*/
/* Discard any locks not acquired inside of a transaction. */
}
/* Sanity checks. */
#ifdef DIAGNOSTIC
#endif
/* Initialize dynamic information. */
return (ret);
}
/*
* __bam_c_destroy --
* Close a single cursor -- internal version.
*/
static int
{
/* Discard the structures. */
return (0);
}
/*
* __bam_c_del --
* Delete using a cursor.
*/
static int
{
PAGE *h;
int ret;
h = NULL;
/* Check for invalid flags. */
return (ret);
/*
* If we are running CDB, this had better be either a write
* cursor or an immediate writer.
*/
return (EINVAL);
/* If already deleted, return failure. */
return (DB_KEYEMPTY);
/*
* 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 long-lived read lock. Note, we have to have a read lock
* to even get here, so we simply discard it.
*/
goto err;
}
/*
* Acquire the underlying page (which may be different from the above
* page because it may be a duplicate page), and set the on-page and
* in-cursor delete flags. We don't need to lock it as we've already
* write-locked the page leading to it.
*/
} else {
}
goto err;
/* Log the change. */
if (DB_LOGGING(dbc) &&
goto err;
}
/*
* Set the intent-to-delete flag on the page and update all cursors. */
else
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.
*/
goto err;
goto err;
(void)__bam_stkrel(dbc, 0);
}
return (ret);
}
/*
* __bam_c_get --
* Get using a cursor (btree).
*/
static int
{
PAGE *h;
/* Check for invalid flags. */
return (ret);
/* Clear OR'd in additional bits so we can check for flag equality. */
tmp_rmw = 0;
tmp_rmw = 1;
}
}
/*
* 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) {
if (tmp_rmw)
return (ret);
}
/*
* Initialize the cursor for a new retrieval. Clear the cursor's
* page pointer, it was set before this operation, and no longer
* has any meaning.
*/
switch (flags) {
case DB_CURRENT:
/* It's not possible to return a deleted record. */
ret = DB_KEYEMPTY;
goto err;
}
/* Acquire the current page. */
if (ret != 0)
goto err;
break;
case DB_NEXT_DUP:
goto err;
}
goto err;
/* Make sure we didn't go past the end of the duplicates. */
ret = DB_NOTFOUND;
goto err;
}
break;
case DB_NEXT:
goto err;
break;
}
/* FALLTHROUGH */
case DB_FIRST:
goto err;
break;
case DB_PREV:
goto err;
break;
}
/* FALLTHROUGH */
case DB_LAST:
goto err;
break;
case DB_SET:
goto err;
/*
* We cannot currently be referencing a deleted record, but 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.
*/
goto err;
goto err;
ret = DB_NOTFOUND;
goto err;
}
}
break;
case DB_SET_RECNO:
goto err;
break;
case DB_GET_BOTH:
/* Acquire the current page. */
goto err;
/* If DBC_CONTINUE, move to the next item. */
goto err;
} else {
if ((ret =
goto err;
/*
* We may be referencing a duplicates page. Move to
* the first duplicate.
*/
goto err;
}
/* Search for a matching entry. */
goto err;
/* Ignore deleted entries. */
if (IS_CUR_DELETED(cp)) {
ret = DB_NOTFOUND;
goto err;
}
break;
case DB_SET_RANGE:
goto err;
/*
* As we didn't require an exact match, the search function
* may have returned an entry past the end of the page. If
* so, move to the next entry.
*/
goto err;
/*
* We may be referencing off-page duplicates, if so, move
* off-page.
*/
goto err;
/*
* We may be referencing a deleted record, if so, move to
* the next non-deleted record.
*/
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.
*/
goto err;
} else
if (ret)
goto err;
}
/* Return the data. */
goto err;
/*
* If the previous cursor record has been deleted, physically delete
* the entry from the page. We clear the deleted flag before we call
* the underlying delete routine so that, if an error occurs, and we
* restore the cursor, the deleted flag is cleared. This is because,
* if we manage to physically modify the page, and then restore the
* cursor, we might try to repeat the page modification when closing
* the cursor.
*/
goto err;
}
/* Release the previous lock, if any; the current lock is retained. */
/* Release the current page. */
goto err;
if (0) {
}
/* Release temporary lock upgrade. */
if (tmp_rmw)
return (ret);
}
/*
* __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
{
/*
* If iflagp is non-NULL, we're doing an insert.
*
* If the duplicates are off-page, use the duplicate search routine.
*/
return (ret);
if (cmp != 0)
return (DB_NOTFOUND);
return (0);
}
return (0);
}
/* Otherwise, do the search ourselves. */
for (;;) {
/* Save the last interesting cursor position. */
/* See if the data item matches the one we're looking for. */
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.
*/
return (DB_NOTFOUND);
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.
*/
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.
*/
return (DB_NOTFOUND);
return (0);
}
}
/* NOTREACHED */
}
/*
* __bam_c_rget --
* Return the record number for a cursor.
*/
static int
{
/* Get the page with the current item on it. */
return (ret);
/* Get a copy of the key. */
goto err;
exact = 1;
goto err;
/* Release the stack. */
__bam_stkrel(dbc, 0);
return (ret);
}
/*
* __bam_c_put --
* Put using a cursor.
*/
static int
{
void *arg;
return (ret);
/*
* 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.
*/
return (EINVAL);
return (EAGAIN);
}
if (0) {
split: /*
* To split, we need a valid key for the page. Since it's a
* cursor, we have to build one.
*
* Acquire a copy of a key from the page.
*/
if (needkey) {
goto err;
} else
/*
* 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) {
stack = 0;
} else
/*
* Restore the cursor to its original value. This is necessary
* for two reasons. First, we are about to copy it in case of
* error, again. Second, we adjust cursors during the split,
* and we have to ensure this cursor is adjusted appropriately,
* along with all the other cursors.
*/
goto err;
}
/*
* Initialize the cursor for a new retrieval. Clear the cursor's
* page pointer, it was set before this operation, and no longer
* has any meaning.
*/
switch (flags) {
case DB_AFTER:
case DB_BEFORE:
case DB_CURRENT:
needkey = 1;
} else {
}
/*
* !!!
* 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.
*/
/* Acquire a complete stack. */
goto err;
stack = 1;
} else {
/* Acquire the current page. */
if (ret != 0)
goto err;
iiflags = 0;
}
/*
* If the user has specified a duplicate comparison function,
* we return an error if DB_CURRENT was specified and the
* replacement data doesn't compare equal to the current data.
* This stops apps from screwing up the duplicate sort order.
*/
goto err;
}
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 __bam_dsearch
* to search for the right slot. Otherwise, we position to
* value.
*/
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 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.
*/
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.
*/
else
goto err;
} else
iiop = DB_CURRENT;
iiflags = 0;
} else {
}
} else {
}
break;
}
if (ret == DB_NEEDSPLIT)
goto split;
if (ret != 0)
goto err;
/*
* Reset any cursors referencing this item that might have the item
* marked for deletion.
*/
if (iiop == DB_CURRENT) {
/*
* It's also possible that we are the cursor that had the
* item marked for deletion, in which case we want to make
* sure that we don't delete it because we had the delete
* flag set already.
*/
}
/*
* Update the cursor to point to the new entry. The new entry was
* stored on the current page, because we split pages until it was
* possible.
*/
else
/*
* If the previous cursor record has been deleted, physically delete
* the entry from the page. We clear the deleted flag before we call
* the underlying delete routine so that, if an error occurs, and we
* restore the cursor, the deleted flag is cleared. This is because,
* if we manage to physically modify the page, and then restore the
* cursor, we might try to repeat the page modification when closing
* the cursor.
*/
goto err;
}
/* Release the previous lock, if any; the current lock is retained. */
/*
* 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.
*/
(void)__bam_stkrel(dbc, 0);
/* Release the current page. */
goto err;
if (0) {
err: /* Discard any pinned pages. */
if (stack)
(void)__bam_stkrel(dbc, 0);
else
}
DB_LOCK_IWRITE, 0);
return (ret);
}
/*
* __bam_c_first --
* Return the first record.
*/
static int
{
int ret;
/* Walk down the left-hand side of the tree. */
if ((ret =
return (ret);
return (ret);
/* If we find a leaf page, we're done. */
break;
}
/* Check for duplicates. */
return (ret);
/* If on an empty page or a deleted record, move to the next one. */
return (ret);
return (0);
}
/*
* __bam_c_last --
* Return the last record.
*/
static int
{
int ret;
/* Walk down the right-hand side of the tree. */
if ((ret =
return (ret);
return (ret);
/* If we find a leaf page, we're done. */
break;
pgno =
}
/* Check for duplicates. */
return (ret);
/* If on an empty page or a deleted record, move to the next one. */
return (ret);
return (0);
}
/*
* __bam_c_next --
* Move to the next record.
*/
static int
int initial_move;
{
int ret;
/*
* We're either moving through a page of duplicates or a btree leaf
* page.
*/
} else {
}
if ((ret =
return (ret);
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)
for (;;) {
/*
* 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.
*/
if (pgno == PGNO_INVALID) {
/* If in a btree leaf page, it's EOF. */
return (DB_NOTFOUND);
/* Continue from the last btree leaf page. */
} else
indx = 0;
return (ret);
if ((ret =
return (ret);
continue;
}
/* Ignore deleted records. */
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.
*/
return (ret);
continue;
}
} else {
}
break;
}
return (0);
}
/*
* __bam_c_prev --
* Move to the previous record.
*/
static int
{
/*
* We're either moving through a page of duplicates or a btree leaf
* page.
*/
} else {
}
if ((ret =
return (ret);
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.
*/
if (pgno == PGNO_INVALID) {
/* If in a btree leaf page, it's SOF. */
return (DB_NOTFOUND);
/* Continue from the last btree leaf page. */
set_indx = 0;
} else
set_indx = 1;
return (ret);
if ((ret =
return (ret);
if (set_indx)
if (indx == 0)
continue;
}
/* Ignore deleted records. */
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.
*/
return (ret);
continue;
}
} else {
}
break;
}
return (0);
}
/*
* __bam_c_search --
* Move to a specified record.
*/
static int
int *exactp;
{
BTREE *t;
PAGE *h;
/* Find an entry in the database. */
switch (flags) {
case DB_SET_RECNO:
return (ret);
break;
case DB_SET:
case DB_GET_BOTH:
goto search;
case DB_SET_RANGE:
goto search;
case DB_KEYFIRST:
sflags = S_KEYFIRST;
goto fast_search;
case DB_KEYLAST:
/*
* 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 = LOCK_INVALID;
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.
*/
goto fast_miss;
goto fast_miss;
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
* 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) {
if ((cmp =
goto try_begin;
if (cmp > 0) {
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 (;
;
goto fast_hit;
}
indx = 0;
if ((cmp =
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 (;
;
goto fast_hit;
}
goto fast_miss;
fast_hit: /* Set the exact match flag, we may have found a duplicate. */
/* Enter the entry in the stack. */
BT_STK_CLR(cp);
break;
if (lock != LOCK_INVALID)
break;
default: /* XXX: Impossible. */
abort();
/* NOTREACHED */
}
if (ret != 0)
return (ret);
/*
* Initialize the cursor to reference it. This has to be done
* before we return (even with DB_NOTFOUND) because we have to
* free the page(s) we locked in __bam_search.
*/
/*
* 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.
*/
t->bt_lpgno =
/* If we need an exact match and didn't find one, we're done. */
return (DB_NOTFOUND);
return (0);
}
/*
* __bam_dup --
* Check for an off-page duplicates entry, and if found, move to the
* first or last entry.
*
* PUBLIC: int __bam_dup __P((DBC *, CURSOR *, u_int32_t, int));
*/
int
int last_dup;
{
int ret;
/*
* 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.
*/
return (0);
return (ret);
if (last_dup) {
return (ret);
} else {
return (ret);
indx = 0;
}
/* Update the cursor's duplicate information. */
return (0);
}
/*
* __bam_c_physdel --
* Actually do the cursor deletion.
*/
static int
PAGE *h;
{
delete_page = ret = 0;
/* Figure out what we're deleting. */
} else {
}
/*
* If the item is referenced by another cursor, set that cursor's
* delete flag and leave it up to it to do the delete.
*
* !!!
* This test for > 0 is a tricky. There are two ways that we can
* be called here. Either we are closing the cursor or we've moved
* off the page with the deleted entry. In the first case, we've
* already removed the cursor from the active queue, so we won't see
* it in __bam_ca_delete. In the second case, it will be on a different
* item, so we won't bother with it in __bam_ca_delete.
*/
return (0);
/*
* If this is concurrent DB, upgrade the lock if necessary.
*/
return (EAGAIN);
/*
* If we don't already have the page locked, get it and delete the
* items.
*/
return (ret);
return (ret);
local_page = 1;
} else
local_page = 0;
/*
* 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) {
if (NUM_ENT(h) == 1 &&
cmd = DELETE_PAGE;
else {
cmd = DELETE_ITEM;
/* Delete the duplicate. */
goto err;
/*
* 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.
*/
}
/*
* Release any page we're holding and its lock.
*
* !!!
* If there is no subsequent page in the duplicate chain, then
* __db_drem will have put page "h" and set it to NULL.
*/
if (local_page) {
if (h != NULL)
local_page = 0;
}
if (cmd == NOTHING_FURTHER)
goto done;
/* Acquire the parent page and switch the index to its entry. */
if ((ret =
goto err;
goto err;
}
local_page = 1;
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
*/
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.
*
* 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
* 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.
*/
goto err;
delete_page = 1;
}
/*
* Do a normal btree delete.
*
* !!!
* Delete the key item first, otherwise the duplicate checks in
* __bam_ditem() won't work!
*/
goto err;
goto err;
if (local_page) {
local_page = 0;
}
/* Delete the page if it was emptied. */
if (delete_page)
err:
done: if (delete_page)
if (local_page) {
/*
* It's possible for h to be NULL, as __db_drem may have
* been relinking pages by the time that it deadlocked.
*/
if (h != NULL)
}
DB_LOCK_IWRITE, 0);
return (ret);
}
/*
* __bam_c_getstack --
* Acquire a full stack for a cursor.
*/
static int
{
PAGE *h;
h = NULL;
ret = 0;
/* Get the page with the current item on it. */
return (ret);
/* Get a copy of a key from the page. */
goto err;
/* Get a write-locked stack for that page. */
exact = 0;
/* We no longer need the key or the page. */
return (ret);
}