mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-26 07:23:08 +01:00
1958 lines
54 KiB
C++
1958 lines
54 KiB
C++
/*
|
|
* PROGRAM: JRD Access Method
|
|
* MODULE: nav.c
|
|
* DESCRIPTION: Navigational index walking
|
|
*
|
|
* The contents of this file are subject to the Interbase Public
|
|
* License Version 1.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy
|
|
* of the License at http://www.Inprise.com/IPL.html
|
|
*
|
|
* Software distributed under the License is distributed on an
|
|
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
|
|
* or implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code was created by Inprise Corporation
|
|
* and its predecessors. Portions created by Inprise Corporation are
|
|
* Copyright (C) Inprise Corporation.
|
|
*
|
|
* All Rights Reserved.
|
|
* Contributor(s): ______________________________________.
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#include <string.h>
|
|
#include "../jrd/jrd.h"
|
|
#include "../jrd/ods.h"
|
|
#include "../jrd/val.h"
|
|
#include "../jrd/btr.h"
|
|
#include "../jrd/req.h"
|
|
#include "../jrd/exe.h"
|
|
#include "../jrd/sbm.h"
|
|
#include "../jrd/rse.h"
|
|
#include "gen/codes.h"
|
|
#include "../jrd/rng.h"
|
|
#include "../jrd/lck.h"
|
|
#include "../jrd/cch.h"
|
|
#include "../jrd/blr.h"
|
|
#include "../jrd/all_proto.h"
|
|
#include "../jrd/btr_proto.h"
|
|
#include "../jrd/cch_proto.h"
|
|
#include "../jrd/dbg_proto.h"
|
|
#include "../jrd/err_proto.h"
|
|
#include "../jrd/evl_proto.h"
|
|
#include "../jrd/gds_proto.h"
|
|
#include "../jrd/mov_proto.h"
|
|
#include "../jrd/nav_proto.h"
|
|
#include "../jrd/rng_proto.h"
|
|
#include "../jrd/rse_proto.h"
|
|
#include "../jrd/sbm_proto.h"
|
|
#include "../jrd/thd_proto.h"
|
|
#include "../jrd/vio_proto.h"
|
|
|
|
#define MOVE_BYTE(x_from, x_to) *x_to++ = *x_from++;
|
|
|
|
static SSHORT compare_keys(IDX *, UCHAR *, USHORT, KEY *, USHORT);
|
|
#ifdef SCROLLABLE_CURSORS
|
|
static void expand_index(WIN *);
|
|
#endif
|
|
#ifdef PC_ENGINE
|
|
static BOOLEAN find_dbkey(RSB, ULONG);
|
|
static BOOLEAN find_record(RSB, RSE_GET_MODE, KEY *, USHORT, USHORT);
|
|
#endif
|
|
static BTX find_current(EXP, BTR, BTN);
|
|
static BOOLEAN find_saved_node(RSB, IRSB_NAV, WIN *, BTN *);
|
|
static BTN get_position(TDBB, RSB, IRSB_NAV, WIN *, RSE_GET_MODE, BTX *);
|
|
static BOOLEAN get_record(RSB, IRSB_NAV, RPB *, KEY *, BOOLEAN);
|
|
static void init_fetch(IRSB_NAV);
|
|
static BTN nav_open(TDBB, RSB, IRSB_NAV, WIN *, RSE_GET_MODE, BTX *);
|
|
static void set_position(IRSB_NAV, RPB *, WIN *, BTN, BTX, UCHAR *, USHORT);
|
|
static void setup_bitmaps(RSB, IRSB_NAV);
|
|
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
EXP NAV_expand_index(WIN * window, IRSB_NAV impure)
|
|
{
|
|
/**************************************
|
|
*
|
|
* N A V _ e x p a n d _ i n d e x
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Given a window with a btree leaf page loaded into it,
|
|
* expand the index page into a form that facilitates walking
|
|
* backward through it. Each node's data is decompressed.
|
|
* Since the prefix field is not needed, it contains an offset to
|
|
* the prior node.
|
|
*
|
|
* If an impure pointer is passed, set the current node on the
|
|
* expanded page to match that on the corresponding btree page.
|
|
*
|
|
**************************************/
|
|
BTR page;
|
|
EXP expanded_page;
|
|
BTN node, end, current_node;
|
|
BTX expanded_node, prior_expanded_node;
|
|
KEY key;
|
|
UCHAR *p, *q;
|
|
USHORT l;
|
|
|
|
/* allocate the expanded page to allow room for all nodes fully expanded,
|
|
plus leave room for an extra node with zero-length tail
|
|
(note that the difference in size of a BTX node versus a BTN node could
|
|
be deducted, but there is no easy way to get the no. of nodes on page) */
|
|
|
|
/* if the right version of expanded page is available, there
|
|
* is no work to be done
|
|
*/
|
|
|
|
if ((expanded_page = window->win_expanded_buffer) &&
|
|
(expanded_page->exp_incarnation == CCH_get_incarnation(window)))
|
|
return expanded_page;
|
|
|
|
/* If different incarnation, reallocate the page */
|
|
|
|
if (expanded_page)
|
|
ALL_free(expanded_page);
|
|
|
|
page = (BTR) window->win_buffer;
|
|
|
|
expanded_page = (EXP) ALL_malloc(EXP_SIZE + page->btr_prefix_total +
|
|
(SLONG) page->btr_length + BTX_SIZE,
|
|
ERR_jmp);
|
|
window->win_expanded_buffer = expanded_page;
|
|
expanded_page->exp_incarnation = -1;
|
|
|
|
expand_index(window);
|
|
|
|
if (!impure)
|
|
return expanded_page;
|
|
|
|
/* go through the nodes on the original page and reposition */
|
|
|
|
node = (BTN) page->btr_nodes;
|
|
expanded_node = (BTX) expanded_page->exp_nodes;
|
|
|
|
current_node = (BTN) ((UCHAR *) page + impure->irsb_nav_offset);
|
|
impure->irsb_nav_expanded_offset = -1;
|
|
|
|
for (end = LAST_NODE(page); node < end;) {
|
|
if (node == current_node)
|
|
impure->irsb_nav_expanded_offset =
|
|
(UCHAR *) expanded_node - (UCHAR *) expanded_page;
|
|
|
|
node = BTR_next_node(node, &expanded_node);
|
|
}
|
|
|
|
return expanded_page;
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef PC_ENGINE
|
|
BOOLEAN NAV_find_record(RSB rsb,
|
|
USHORT operator, USHORT direction, JRD_NOD find_key)
|
|
{
|
|
/**************************************
|
|
*
|
|
* N A V _ f i n d _ r e c o r d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Find a record with the specified key value
|
|
* in a stream opened on a navigational index.
|
|
* This routine must set BOF, EOF, or CRACK.
|
|
*
|
|
**************************************/
|
|
TDBB tdbb;
|
|
JRD_REQ request;
|
|
IRSB_NAV impure;
|
|
IDX *idx;
|
|
KEY key_value;
|
|
WIN window;
|
|
BTN expanded_node;
|
|
BOOLEAN backwards;
|
|
USHORT search_flags;
|
|
|
|
backwards = (direction == blr_backward
|
|
|| direction == blr_backward_starting);
|
|
|
|
if (direction == blr_forward_starting
|
|
|| direction == blr_backward_starting) search_flags = irb_starting;
|
|
else
|
|
search_flags = 0;
|
|
|
|
|
|
tdbb = GET_THREAD_DATA;
|
|
request = tdbb->tdbb_request;
|
|
impure = (IRSB_NAV) ((UCHAR *) request + rsb->rsb_impure);
|
|
window.win_flags = 0;
|
|
|
|
init_fetch(impure);
|
|
if (!impure->irsb_nav_page) {
|
|
nav_open(tdbb, rsb, impure, &window, RSE_get_forward, &expanded_node);
|
|
CCH_RELEASE(tdbb, &window);
|
|
}
|
|
|
|
/* finding a record invalidates the visited records */
|
|
|
|
SBM_reset(&impure->irsb_nav_records_visited);
|
|
|
|
idx =
|
|
(IDX *) ((SCHAR *) impure + (SLONG) rsb->rsb_arg[RSB_NAV_idx_offset]);
|
|
|
|
if ((idx == NULL) ||
|
|
(find_key->nod_count == 0) ||
|
|
(find_key->nod_count > idx->idx_count)) ERR_post(gds__invalid_key, 0);
|
|
|
|
if (find_key->nod_count < idx->idx_count)
|
|
search_flags |= irb_partial;
|
|
|
|
BTR_make_key(tdbb, find_key->nod_count, &find_key->nod_arg[0], idx,
|
|
&key_value, search_flags & irb_starting);
|
|
|
|
/* save the key value */
|
|
|
|
impure->irsb_nav_length = key_value.key_length;
|
|
MOVE_FAST(key_value.key_data, impure->irsb_nav_data,
|
|
key_value.key_length);
|
|
|
|
/* for descending indices, reverse the sense of the comparison operator
|
|
to simplify the following switch statement */
|
|
|
|
if (idx->idx_flags & idx_descending) {
|
|
if (operator == blr_lss)
|
|
operator = blr_gtr;
|
|
else if (operator == blr_leq)
|
|
operator = blr_geq;
|
|
else if (operator == blr_gtr)
|
|
operator = blr_lss;
|
|
else if (operator == blr_geq)
|
|
operator = blr_leq;
|
|
}
|
|
|
|
/* find the proper record by comparing the passed key with the records in
|
|
the record stream, optimizing the fetch as best as possible by using the
|
|
index to get as close to the proper record as possible */
|
|
|
|
switch (operator) {
|
|
case blr_lss:
|
|
if (backwards)
|
|
/* find the first record before the given key value */
|
|
{
|
|
find_record(rsb, RSE_get_first, &key_value, find_key->nod_count,
|
|
search_flags);
|
|
return NAV_get_record(rsb, impure,
|
|
request->req_rpb + rsb->rsb_stream,
|
|
RSE_get_backward);
|
|
}
|
|
else
|
|
/* find the first record in the stream, assuming its key value is < the passed key */
|
|
{
|
|
nav_open(tdbb, rsb, impure, &window, RSE_get_forward,
|
|
&expanded_node);
|
|
CCH_RELEASE(tdbb, &window);
|
|
if (!NAV_get_record
|
|
(rsb, impure, request->req_rpb + rsb->rsb_stream,
|
|
RSE_get_forward)) return FALSE;
|
|
|
|
if (compare_keys
|
|
(idx, impure->irsb_nav_data, impure->irsb_nav_length,
|
|
&key_value, search_flags) < 0)
|
|
return TRUE;
|
|
else {
|
|
RSE_MARK_CRACK(tdbb, rsb, irsb_crack);
|
|
return FALSE;
|
|
}
|
|
}
|
|
/* break; unreachable */
|
|
|
|
case blr_leq:
|
|
if (backwards)
|
|
/* find the last record with the matching key; barring that, find the first less than */
|
|
{
|
|
if (find_record
|
|
(rsb, RSE_get_last, &key_value, find_key->nod_count,
|
|
search_flags)) return TRUE;
|
|
return NAV_get_record(rsb, impure,
|
|
request->req_rpb + rsb->rsb_stream,
|
|
RSE_get_backward);
|
|
}
|
|
else
|
|
/* find the first record in the stream, assuming its key value is <= the passed key */
|
|
{
|
|
nav_open(tdbb, rsb, impure, &window, RSE_get_forward,
|
|
&expanded_node);
|
|
CCH_RELEASE(tdbb, &window);
|
|
if (!NAV_get_record
|
|
(rsb, impure, request->req_rpb + rsb->rsb_stream,
|
|
RSE_get_forward)) return FALSE;
|
|
|
|
if (compare_keys
|
|
(idx, impure->irsb_nav_data, impure->irsb_nav_length,
|
|
&key_value, search_flags) <= 0)
|
|
return TRUE;
|
|
else {
|
|
RSE_MARK_CRACK(tdbb, rsb, irsb_crack);
|
|
return FALSE;
|
|
}
|
|
}
|
|
/* break; unreachable */
|
|
|
|
case blr_eql:
|
|
if (find_record(rsb, backwards ? RSE_get_last : RSE_get_first,
|
|
&key_value, find_key->nod_count, search_flags))
|
|
return TRUE;
|
|
else {
|
|
/* We need to override the crack semantics of find_record, because even
|
|
if we go to EOF or BOF as a result of the find, this is defined as a
|
|
crack. On the other hand, we do need to retain a forced crack if
|
|
one was set. bug #7024 */
|
|
|
|
if (impure->irsb_flags & irsb_forced_crack)
|
|
RSE_MARK_CRACK(tdbb, rsb, irsb_crack | irsb_forced_crack);
|
|
else
|
|
RSE_MARK_CRACK(tdbb, rsb, irsb_crack);
|
|
return FALSE;
|
|
}
|
|
/* break; unreachable */
|
|
|
|
case blr_gtr:
|
|
if (backwards)
|
|
/* find the last record in the stream, assuming its key value is > the passed key */
|
|
{
|
|
nav_open(tdbb, rsb, impure, &window, RSE_get_backward,
|
|
&expanded_node);
|
|
CCH_RELEASE(tdbb, &window);
|
|
if (!NAV_get_record
|
|
(rsb, impure, request->req_rpb + rsb->rsb_stream,
|
|
RSE_get_backward)) return FALSE;
|
|
|
|
if (compare_keys
|
|
(idx, impure->irsb_nav_data, impure->irsb_nav_length,
|
|
&key_value, search_flags) > 0)
|
|
return TRUE;
|
|
else {
|
|
RSE_MARK_CRACK(tdbb, rsb, irsb_crack);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
/* find the first record after the last matching record */
|
|
{
|
|
find_record(rsb, RSE_get_last, &key_value, find_key->nod_count,
|
|
search_flags);
|
|
|
|
/* special case when the key is greater than the last record in the file;
|
|
stream is defined to be on EOF; see bug #6151 */
|
|
|
|
if (impure->irsb_flags & irsb_eof)
|
|
return FALSE;
|
|
|
|
return NAV_get_record(rsb, impure,
|
|
request->req_rpb + rsb->rsb_stream,
|
|
RSE_get_forward);
|
|
}
|
|
/* break; unreachable */
|
|
|
|
case blr_geq:
|
|
if (backwards)
|
|
/* find the last record in the stream, assuming its key value is >= the passed key */
|
|
{
|
|
nav_open(tdbb, rsb, impure, &window, RSE_get_backward,
|
|
&expanded_node);
|
|
CCH_RELEASE(tdbb, &window);
|
|
if (!NAV_get_record
|
|
(rsb, impure, request->req_rpb + rsb->rsb_stream,
|
|
RSE_get_backward)) return FALSE;
|
|
|
|
if (compare_keys
|
|
(idx, impure->irsb_nav_data, impure->irsb_nav_length,
|
|
&key_value, search_flags) >= 0)
|
|
return TRUE;
|
|
else {
|
|
RSE_MARK_CRACK(tdbb, rsb, irsb_crack);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
/* find the first record of matching key; barring that, find the first greater */
|
|
{
|
|
if (find_record
|
|
(rsb, RSE_get_first, &key_value, find_key->nod_count,
|
|
search_flags)) return TRUE;
|
|
|
|
/* special case when the key is greater than the last record in the file;
|
|
stream is defined to be on EOF; see bug #6151 */
|
|
|
|
if (impure->irsb_flags & irsb_eof)
|
|
return FALSE;
|
|
|
|
return NAV_get_record(rsb, impure,
|
|
request->req_rpb + rsb->rsb_stream,
|
|
RSE_get_forward);
|
|
}
|
|
/* break; unreachable */
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef PC_ENGINE
|
|
void NAV_get_bookmark(RSB rsb, IRSB_NAV impure, BKM bookmark)
|
|
{
|
|
/**************************************
|
|
*
|
|
* N A V _ g e t _ b o o k m a r k
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Return a descriptor containing a pointer
|
|
* to a bookmark data structure which describes
|
|
* the current location of a navigational stream.
|
|
*
|
|
**************************************/
|
|
|
|
/* store the info necessary to return to this location in the index */
|
|
|
|
bookmark->bkm_number = impure->irsb_nav_number;
|
|
bookmark->bkm_page = impure->irsb_nav_page;
|
|
bookmark->bkm_incarnation = impure->irsb_nav_incarnation;
|
|
bookmark->bkm_offset = impure->irsb_nav_offset;
|
|
bookmark->bkm_expanded_offset = impure->irsb_nav_expanded_offset;
|
|
|
|
/* store the current key value, setting up the key descriptor to point at it */
|
|
|
|
bookmark->bkm_key_desc.dsc_dtype = dtype_text;
|
|
bookmark->bkm_key_desc.dsc_length = impure->irsb_nav_length;
|
|
bookmark->bkm_key_desc.dsc_address = bookmark->bkm_key_data;
|
|
MOVE_FAST(impure->irsb_nav_data, bookmark->bkm_key_data,
|
|
impure->irsb_nav_length);
|
|
}
|
|
#endif
|
|
|
|
|
|
BOOLEAN NAV_get_record(
|
|
RSB rsb,
|
|
IRSB_NAV impure, RPB * rpb, RSE_GET_MODE direction)
|
|
{
|
|
/**************************************
|
|
*
|
|
* N A V _ g e t _ r e c o r d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Get a record from a stream, either in
|
|
* the forward or backward direction, or the
|
|
* current record. This routine must set
|
|
* BOF, EOF, or CRACK properly.
|
|
*
|
|
**************************************/
|
|
TDBB tdbb;
|
|
IDX *idx;
|
|
IRB retrieval;
|
|
JRD_NOD retrieval_node;
|
|
BTR page;
|
|
BTN node, next;
|
|
EXP expanded_page;
|
|
BTX expanded_node, expanded_next = NULL;
|
|
WIN window;
|
|
SLONG number;
|
|
USHORT l;
|
|
KEY key, upper, lower;
|
|
UCHAR *p, *q;
|
|
BOOLEAN page_changed = FALSE;
|
|
|
|
DEBUG;
|
|
|
|
tdbb = GET_THREAD_DATA;
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
/* before we do anything, check for the case where an ascending index
|
|
was used to optimize a descending sort, or a descending index was
|
|
used to optimize an ascending sort--in either case, we need to flip
|
|
the nominal direction of navigation */
|
|
|
|
if (rsb->rsb_flags & rsb_descending)
|
|
if (direction == RSE_get_forward)
|
|
direction = RSE_get_backward;
|
|
else if (direction == RSE_get_backward)
|
|
direction = RSE_get_forward;
|
|
#endif
|
|
|
|
init_fetch(impure);
|
|
idx =
|
|
(IDX *) ((SCHAR *) impure + (SLONG) rsb->rsb_arg[RSB_NAV_idx_offset]);
|
|
|
|
/* The bitmap is only valid when we are continuing on in one
|
|
direction. It is of no help when we change direction,
|
|
and so we need to reset in that case. */
|
|
|
|
if (((impure->irsb_flags & irsb_backwards)
|
|
&& direction != RSE_get_backward)
|
|
|| (!(impure->irsb_flags & irsb_backwards)
|
|
&& direction != RSE_get_forward))
|
|
SBM_reset(&impure->irsb_nav_records_visited);
|
|
|
|
if (direction == RSE_get_forward)
|
|
impure->irsb_flags &= ~irsb_backwards;
|
|
else if (direction == RSE_get_backward)
|
|
impure->irsb_flags |= irsb_backwards;
|
|
|
|
/* find the last fetched position from the index */
|
|
|
|
window.win_page = impure->irsb_nav_page;
|
|
window.win_flags = 0;
|
|
next =
|
|
get_position(tdbb, rsb, impure, &window, direction, &expanded_next);
|
|
MOVE_FAST(impure->irsb_nav_data, key.key_data, impure->irsb_nav_length);
|
|
retrieval_node = (JRD_NOD) rsb->rsb_arg[RSB_NAV_index];
|
|
retrieval = (IRB) retrieval_node->nod_arg[e_idx_retrieval];
|
|
|
|
/* set the upper (or lower) limit for navigational retrieval */
|
|
|
|
if ((direction == RSE_get_forward) && retrieval->irb_upper_count) {
|
|
upper.key_length = impure->irsb_nav_upper_length;
|
|
#ifdef SCROLLABLE_CURSORS
|
|
MOVE_FAST(
|
|
(impure->irsb_nav_data +
|
|
(2 * (SLONG) rsb->rsb_arg[RSB_NAV_key_length])),
|
|
upper.key_data, upper.key_length);
|
|
#else
|
|
MOVE_FAST(
|
|
(impure->irsb_nav_data +
|
|
(SLONG) rsb->rsb_arg[RSB_NAV_key_length]), upper.key_data,
|
|
upper.key_length);
|
|
#endif
|
|
}
|
|
else if ((direction == RSE_get_backward) && retrieval->irb_lower_count) {
|
|
lower.key_length = impure->irsb_nav_lower_length;
|
|
MOVE_FAST(
|
|
(impure->irsb_nav_data +
|
|
(SLONG) rsb->rsb_arg[RSB_NAV_key_length]), lower.key_data,
|
|
lower.key_length);
|
|
}
|
|
|
|
/* In the case of a DISTINCT, we must detect whether the key changed since the last
|
|
time a record was returned from the rsb. It is not good enough to know whether the
|
|
key changed since the last node in the index, because the record represented
|
|
by the last index node may not have satisfied some boolean expression on the rsb.
|
|
So to find out whether the key has changed since the last time a record was actually
|
|
returned from the rsb, we save the record count each time we return a candidate record,
|
|
and see if the count is updated the next time we get here.
|
|
|
|
If the count has been updated, we know the record has been returned from the rsb, and
|
|
the index key must change before we can return another DISTINCT record candidate. If
|
|
the last candidate did not qualify, we know we should return another record no matter
|
|
what. */
|
|
|
|
if (rsb->rsb_record_count != impure->irsb_nav_count) {
|
|
impure->irsb_flags &= ~irsb_key_changed;
|
|
impure->irsb_nav_count = rsb->rsb_record_count;
|
|
}
|
|
else
|
|
impure->irsb_flags |= irsb_key_changed;
|
|
|
|
/* Find the next interesting node. If necessary, skip to the next page */
|
|
|
|
for (;;) {
|
|
node = next;
|
|
expanded_node = expanded_next;
|
|
if (node)
|
|
number =
|
|
BTR_get_quad(reinterpret_cast < char *>(BTN_NUMBER(node)));
|
|
page = (BTR) window.win_buffer;
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
/* in the backwards case, check to make sure we haven't hit the
|
|
beginning of a page, and if so fetch the left sibling page. */
|
|
|
|
if (direction == RSE_get_backward) {
|
|
if (node < (BTN) page->btr_nodes) {
|
|
expanded_page = window.win_expanded_buffer;
|
|
|
|
if (!page->btr_left_sibling) {
|
|
impure->irsb_flags |= irsb_bof;
|
|
break;
|
|
}
|
|
|
|
page = BTR_left_handoff(tdbb, &window, page, LCK_read);
|
|
|
|
#ifdef PC_ENGINE
|
|
RNG_add_page(window.win_page);
|
|
#endif
|
|
expanded_page = NAV_expand_index(&window, 0);
|
|
next = BTR_last_node(page, expanded_page, &expanded_next);
|
|
|
|
page_changed = TRUE;
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
/* In the forwards case, check for end of page. If we find
|
|
it, do a simple handoff to the right sibling page. */
|
|
#endif
|
|
{
|
|
if (number == END_LEVEL) {
|
|
impure->irsb_flags |= irsb_eof;
|
|
break;
|
|
}
|
|
if (number == END_BUCKET) {
|
|
page = (BTR) window.win_buffer;
|
|
page =
|
|
(BTR) CCH_HANDOFF(tdbb, &window, page->btr_sibling,
|
|
LCK_read, pag_index);
|
|
#ifdef PC_ENGINE
|
|
RNG_add_page(window.win_page);
|
|
#endif
|
|
next = (BTN) page->btr_nodes;
|
|
if ( (expanded_page = window.win_expanded_buffer) )
|
|
expanded_next = (BTX) expanded_page->exp_nodes;
|
|
|
|
page_changed = TRUE;
|
|
continue;
|
|
}
|
|
|
|
#ifdef IGNORE_NULL_IDX_KEY
|
|
if (number == END_NON_NULL) {
|
|
if (retrieval->irb_generic & irb_ignore_null_value_key) {
|
|
/* we have reached the END_NON_NULL marker. Return since we are
|
|
* not interested in the remaining nodes.
|
|
*/
|
|
RSE_MARK_CRACK(tdbb, rdb, irsb_crack);
|
|
break;
|
|
}
|
|
else {
|
|
/* ignore this node, and go to the next one */
|
|
next = BTR_next_node(node, &expanded_node);
|
|
expanded_next = expanded_node;
|
|
continue;
|
|
}
|
|
}
|
|
#endif /* IGNORE_NULL_IDX_KEY */
|
|
|
|
}
|
|
|
|
/* In the project (DISTINCT) case, we need to determine if the key has changed
|
|
since the last record that was returned from the rsb. That means that we must
|
|
know what the key was for that record
|
|
|
|
|
|
in the case where we are on the same page,
|
|
we simply check to see if there is any data in the node; if we are on a new
|
|
page, we must compare this node with the last key we saw
|
|
NOTE: the optimizer only uses indices which have the same number of fields as
|
|
those in the DISTINCT clause; there's no reason we couldn't use an index which
|
|
has extra fields to the right, we just need to add some code to check
|
|
when only the first n segment(s) of the key has changed, rather than the whole key */
|
|
|
|
if (rsb->rsb_flags & rsb_project) {
|
|
if (page_changed) {
|
|
if (BTN_LENGTH(node) != key.key_length)
|
|
impure->irsb_flags |= irsb_key_changed;
|
|
else {
|
|
p = key.key_data;
|
|
q = BTN_DATA(node);
|
|
for (l = key.key_length; l; l--)
|
|
if (*p++ != *q++)
|
|
break;
|
|
if (l)
|
|
impure->irsb_flags |= irsb_key_changed;
|
|
}
|
|
}
|
|
else if (BTN_LENGTH(node))
|
|
impure->irsb_flags |= irsb_key_changed;
|
|
}
|
|
page_changed = FALSE;
|
|
|
|
/* Build the current key value from the prefix and current node data. */
|
|
|
|
if (expanded_node) {
|
|
if ( (l = BTN_LENGTH(node) + BTN_PREFIX(node)) ) {
|
|
p = key.key_data;
|
|
q = expanded_node->btx_data;
|
|
do
|
|
*p++ = *q++;
|
|
while (--l);
|
|
}
|
|
|
|
}
|
|
else {
|
|
if ( (l = BTN_LENGTH(node)) ){
|
|
p = key.key_data + BTN_PREFIX(node);
|
|
q = BTN_DATA(node);
|
|
do
|
|
*p++ = *q++;
|
|
while (--l);
|
|
}
|
|
}
|
|
|
|
key.key_length = BTN_LENGTH(node) + BTN_PREFIX(node);
|
|
|
|
/* Make sure we haven't hit the upper (or lower) limit. */
|
|
|
|
if ((direction == RSE_get_forward) && retrieval->irb_upper_count &&
|
|
compare_keys(idx, key.key_data, key.key_length, &upper,
|
|
retrieval->irb_generic & (irb_descending |
|
|
irb_partial |
|
|
irb_starting)) > 0) {
|
|
RSE_MARK_CRACK(tdbb, rsb, irsb_crack);
|
|
break;
|
|
}
|
|
|
|
if ((direction == RSE_get_backward) && retrieval->irb_lower_count &&
|
|
compare_keys(idx, key.key_data, key.key_length, &lower,
|
|
retrieval->irb_generic & (irb_descending |
|
|
irb_partial |
|
|
irb_starting)) < 0) {
|
|
RSE_MARK_CRACK(tdbb, rsb, irsb_crack);
|
|
break;
|
|
}
|
|
|
|
/* skip this record if:
|
|
1) there is an inversion tree for this index and this record
|
|
is not in the bitmap for the inversion, or
|
|
2) the record has already been visited, or
|
|
3) this is a projection and the new node is the same as the last */
|
|
|
|
if ((rsb->rsb_arg[RSB_NAV_inversion] &&
|
|
(!impure->irsb_nav_bitmap
|
|
|| !SBM_test(*impure->irsb_nav_bitmap, number)))
|
|
|| SBM_test(impure->irsb_nav_records_visited, number)
|
|
|| ((rsb->rsb_flags & rsb_project)
|
|
&& !(impure->irsb_flags & irsb_key_changed))) {
|
|
if (direction == RSE_get_backward) {
|
|
next = BTR_previous_node(node, &expanded_node);
|
|
expanded_next = expanded_node;
|
|
continue;
|
|
}
|
|
else if (direction == RSE_get_forward
|
|
&& !(impure->irsb_flags & irsb_forced_crack)) {
|
|
next = BTR_next_node(node, &expanded_node);
|
|
expanded_next = expanded_node;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* reset the current navigational position in the index */
|
|
|
|
rpb->rpb_number = number;
|
|
#ifdef PC_ENGINE
|
|
if (direction != RSE_get_current)
|
|
#endif
|
|
set_position(impure, rpb, &window, node, expanded_node,
|
|
key.key_data, key.key_length);
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
|
|
if (get_record(rsb, impure, rpb, &key, FALSE)) {
|
|
#ifdef PC_ENGINE
|
|
if (impure->irsb_flags & irsb_refresh)
|
|
RNG_add_record(rpb);
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
#ifdef PC_ENGINE
|
|
/* if we're here, we didn't find the record at the current position,
|
|
so it must have been deleted out from under us; by definition we
|
|
are on a crack (already marked by get_record()) */
|
|
|
|
if (direction == RSE_get_current)
|
|
return FALSE;
|
|
#endif
|
|
|
|
next =
|
|
get_position(tdbb, rsb, impure, &window, direction,
|
|
&expanded_next);
|
|
}
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
|
|
/* crack, bof, or eof must have been set at this point */
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
#ifdef PC_ENGINE
|
|
BOOLEAN NAV_reset_position(RSB rsb, RPB * new_rpb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* N A V _ r e s e t _ p o s i t i o n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Reset the stream represented by the passed
|
|
* rsb to the record indicated by the passed rpb.
|
|
*
|
|
**************************************/
|
|
TDBB tdbb;
|
|
JRD_REQ request;
|
|
KEY key_value;
|
|
IRSB_NAV impure;
|
|
IDX *idx;
|
|
WIN window;
|
|
BTN expanded_node;
|
|
ULONG record_number;
|
|
|
|
tdbb = GET_THREAD_DATA;
|
|
request = tdbb->tdbb_request;
|
|
impure = (IRSB_NAV) ((UCHAR *) request + rsb->rsb_impure);
|
|
window.win_flags = 0;
|
|
|
|
init_fetch(impure);
|
|
if (!impure->irsb_nav_page) {
|
|
nav_open(tdbb, rsb, impure, &window, RSE_get_current, &expanded_node);
|
|
CCH_RELEASE(tdbb, &window);
|
|
}
|
|
|
|
/* resetting the stream invalidates the visited records */
|
|
|
|
SBM_reset(&impure->irsb_nav_records_visited);
|
|
|
|
idx =
|
|
(IDX *) ((SCHAR *) impure + (SLONG) rsb->rsb_arg[RSB_NAV_idx_offset]);
|
|
|
|
/* save the record number, in case the passed new_rpb is
|
|
the same as the one on the rpb, in which case it will
|
|
be updated by find_record()--bug #7426 */
|
|
|
|
record_number = new_rpb->rpb_number;
|
|
|
|
/* find the key value of the new position, and set the stream to it */
|
|
|
|
BTR_key(tdbb, new_rpb->rpb_relation, new_rpb->rpb_record, idx,
|
|
&key_value);
|
|
if (!find_record(rsb, RSE_get_first, &key_value, idx->idx_count, /* XXX */
|
|
0))
|
|
return FALSE;
|
|
|
|
/* now find the dbkey of the new record within the
|
|
duplicates of this key value */
|
|
|
|
return find_dbkey(rsb, record_number);
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef PC_ENGINE
|
|
BOOLEAN NAV_set_bookmark(RSB rsb, IRSB_NAV impure, RPB * rpb, BKM bookmark)
|
|
{
|
|
/**************************************
|
|
*
|
|
* N A V _ s e t _ b o o k m a r k
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Set up the impure area so that the current
|
|
* position of the stream is that of the
|
|
* stored bookmark.
|
|
*
|
|
**************************************/
|
|
|
|
/* setting to bookmark invalidates the visited records */
|
|
|
|
SBM_reset(&impure->irsb_nav_records_visited);
|
|
|
|
/* save the bookmark state in the impure area for the stream */
|
|
|
|
impure->irsb_nav_number = bookmark->bkm_number;
|
|
impure->irsb_nav_incarnation = bookmark->bkm_incarnation;
|
|
impure->irsb_nav_offset = bookmark->bkm_offset;
|
|
impure->irsb_nav_expanded_offset = bookmark->bkm_expanded_offset;
|
|
|
|
/* assuming we had already fetched a page when
|
|
we set the bookmark, make sure the "first" flag
|
|
is not set so that we don't reopen the stream--bug 5709 */
|
|
|
|
if (impure->irsb_nav_page = bookmark->bkm_page)
|
|
impure->irsb_flags &= ~irsb_first;
|
|
|
|
/* store the current key value, setting up the key descriptor to point at it */
|
|
|
|
impure->irsb_nav_length = bookmark->bkm_key_desc.dsc_length;
|
|
MOVE_FAST(bookmark->bkm_key_data, impure->irsb_nav_data,
|
|
bookmark->bkm_key_desc.dsc_length);
|
|
|
|
if (impure->irsb_flags & (irsb_bof | irsb_eof | irsb_crack))
|
|
return FALSE;
|
|
|
|
/* if we didn't find the record, it must have been deleted
|
|
from under us; the desired semantics is to be on a crack
|
|
at the previous position of the record */
|
|
|
|
return NAV_get_record(rsb, impure, rpb, RSE_get_current);
|
|
}
|
|
#endif
|
|
|
|
|
|
static SSHORT compare_keys(
|
|
IDX * idx,
|
|
UCHAR * key_string1,
|
|
USHORT length1, KEY * key2, USHORT flags)
|
|
{
|
|
/**************************************
|
|
*
|
|
* c o m p a r e _ k e y s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Compare two index keys.
|
|
* If a partial key comparison is specified,
|
|
* ensure that the shorter key (the second
|
|
* one) matches segment-by-segment with the
|
|
* index key.
|
|
*
|
|
**************************************/
|
|
UCHAR *string1, *string2, *segment;
|
|
USHORT length2, l, remainder;
|
|
idx::idx_repeat * tail;
|
|
|
|
string1 = key_string1;
|
|
string2 = key2->key_data;
|
|
length2 = key2->key_length;
|
|
|
|
if ( (l = MIN(length1, length2)) )
|
|
do
|
|
if (*string1++ != *string2++)
|
|
return (string1[-1] < string2[-1]) ? -1 : 1;
|
|
while (--l);
|
|
|
|
/* if the keys are identical, return 0 */
|
|
|
|
if (length1 == length2)
|
|
return 0;
|
|
|
|
/* do a partial key search; if the index is a compound key,
|
|
check to see if the segments are matching--for character
|
|
fields, do partial matches within a segment if irb_starting
|
|
is specified, for other types do only matches on the entire segment */
|
|
|
|
if ((flags & (irb_partial | irb_starting)) && (length1 > length2)) {
|
|
/* figure out what segment we're on; if it's a
|
|
character segment we've matched the partial string */
|
|
|
|
if (idx->idx_count > 1) {
|
|
segment =
|
|
key_string1 +
|
|
((length2 - 1) / (STUFF_COUNT + 1)) * (STUFF_COUNT + 1);
|
|
tail = idx->idx_rpt + (idx->idx_count - *segment);
|
|
}
|
|
else
|
|
tail = &idx->idx_rpt[0];
|
|
|
|
/* If it's a string type key, and we're allowing "starting with" fuzzy
|
|
* type matching, we're done
|
|
*/
|
|
if ((flags & irb_starting) &&
|
|
(tail->idx_itype == idx_string ||
|
|
tail->idx_itype == idx_byte_array ||
|
|
tail->idx_itype == idx_metadata ||
|
|
tail->idx_itype >= idx_first_intl_string)) return 0;
|
|
|
|
|
|
if (idx->idx_count > 1) {
|
|
/* if we've exhausted the segment, we've found a match */
|
|
|
|
if (!(remainder = length2 % (STUFF_COUNT + 1)) &&
|
|
(*string1 != *segment)) return 0;
|
|
|
|
/* if the rest of the key segment is 0's, we've found a match */
|
|
|
|
if (remainder) {
|
|
for (remainder = STUFF_COUNT + 1 - remainder; remainder;
|
|
remainder--)
|
|
if (*string1++)
|
|
break;
|
|
|
|
if (!remainder)
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (flags & irb_descending)
|
|
return (length1 < length2) ? 1 : -1;
|
|
else
|
|
return (length1 < length2) ? -1 : 1;
|
|
}
|
|
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
static void expand_index(WIN * window)
|
|
{
|
|
/**************************************
|
|
*
|
|
* e x p a n d _ i n d e x
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Given a window with a btree leaf page loaded into it,
|
|
* expand the index page into a form that facilitates walking
|
|
* backward through it. Each node's data is decompressed.
|
|
* Since the prefix field is not needed, it contains an offset to
|
|
* the prior node.
|
|
*
|
|
**************************************/
|
|
BTR page;
|
|
EXP expanded_page;
|
|
BTN node, end, prior_node, current_node;
|
|
BTX expanded_node, prior_expanded_node;
|
|
KEY key;
|
|
UCHAR *p, *q;
|
|
USHORT l;
|
|
|
|
|
|
page = (BTR) window->win_buffer;
|
|
expanded_page = window->win_expanded_buffer;
|
|
expanded_page->exp_incarnation = CCH_get_incarnation(window);
|
|
|
|
/* go through the nodes on the original page and expand them */
|
|
|
|
node = (BTN) page->btr_nodes;
|
|
expanded_node = (BTX) expanded_page->exp_nodes;
|
|
prior_node = NULL;
|
|
|
|
for (end = LAST_NODE(page); node < end;) {
|
|
for (p = key.key_data + BTN_PREFIX(node), q = BTN_DATA(node), l =
|
|
BTN_LENGTH(node); l; l--)
|
|
*p++ = *q++;
|
|
l = BTN_LENGTH(node) + BTN_PREFIX(node);
|
|
for (p = expanded_node->btx_data, q = key.key_data; l; l--)
|
|
*p++ = *q++;
|
|
|
|
/* point back to the prior nodes on the expanded page and the btree page */
|
|
|
|
expanded_node->btx_btr_previous_length =
|
|
prior_node ? BTN_LENGTH(prior_node) : 0;
|
|
expanded_node->btx_previous_length =
|
|
prior_node ? BTN_LENGTH(prior_node) + BTN_PREFIX(prior_node) : 0;
|
|
|
|
prior_node = node;
|
|
|
|
node = BTR_next_node(node, &expanded_node);
|
|
}
|
|
|
|
/* insert one additional expanded node to keep track of the length of the
|
|
last node on page--this is necessary because the end of bucket marker
|
|
contains the length and key of the first node on the next page */
|
|
|
|
expanded_node->btx_btr_previous_length =
|
|
prior_node ? BTN_LENGTH(prior_node) : 0;
|
|
expanded_node->btx_previous_length =
|
|
prior_node ? BTN_LENGTH(prior_node) + BTN_PREFIX(prior_node) : 0;
|
|
|
|
expanded_page->exp_length =
|
|
(UCHAR *) expanded_node - (UCHAR *) expanded_page + BTX_SIZE;
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef PC_ENGINE
|
|
static BOOLEAN find_dbkey(RSB rsb, ULONG record_number)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f i n d _ d b k e y
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Look for the record with the passed dbkey in the
|
|
* set of records with key value equal to the current
|
|
* record's key. This is used to find a particular
|
|
* record within a set of equivalent keys.
|
|
*
|
|
**************************************/
|
|
TDBB tdbb;
|
|
JRD_REQ request;
|
|
IRSB_NAV impure;
|
|
RPB *rpb;
|
|
WIN window;
|
|
BTN node;
|
|
BTX expanded_node = NULL;
|
|
KEY key;
|
|
|
|
tdbb = GET_THREAD_DATA;
|
|
request = tdbb->tdbb_request;
|
|
impure = (IRSB_NAV) ((UCHAR *) request + rsb->rsb_impure);
|
|
rpb = request->req_rpb + rsb->rsb_stream;
|
|
|
|
init_fetch(impure);
|
|
|
|
/* if we're on a crack, this isn't going very far */
|
|
|
|
if (impure->irsb_flags & (irsb_bof | irsb_eof | irsb_crack))
|
|
return FALSE;
|
|
|
|
/* find the last fetched position from the index,
|
|
and get the current key value */
|
|
|
|
window.win_page = impure->irsb_nav_page;
|
|
window.win_flags = 0;
|
|
node =
|
|
get_position(tdbb, rsb, impure, &window, RSE_get_current,
|
|
&expanded_node);
|
|
MOVE_FAST(impure->irsb_nav_data, key.key_data, impure->irsb_nav_length);
|
|
key.key_length = impure->irsb_nav_length;
|
|
|
|
/* In case of an error, we still need to release the window we hold
|
|
* during the parse. See HACKs for bug 7041
|
|
*/
|
|
|
|
try {
|
|
|
|
/* loop through the equivalent values of the given key, looking for a
|
|
record which matches the passed dbkey */
|
|
|
|
for (;;) {
|
|
rpb->rpb_number = BTR_get_quad(BTN_NUMBER(node));
|
|
|
|
/* if we find an index entry with the proper dbkey, try to fetch the record */
|
|
|
|
if (rpb->rpb_number == record_number) {
|
|
if (get_record(rsb, impure, rpb, &key, TRUE)) {
|
|
set_position(impure, rpb, &window, node, expanded_node,
|
|
key.key_data, key.key_length);
|
|
CCH_RELEASE(tdbb, &window);
|
|
return TRUE;
|
|
}
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
return FALSE;
|
|
}
|
|
|
|
/* go to the next node; if we find a non-equivalent node, give up */
|
|
|
|
node = BTR_next_node(node, &expanded_node);
|
|
if (BTN_LENGTH(node)) {
|
|
CCH_RELEASE(tdbb, &window);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
} // try
|
|
catch (const std::exception&) {
|
|
CCH_RELEASE(tdbb, &window);
|
|
Firebird::status_exception::raise(-1);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef PC_ENGINE
|
|
static BOOLEAN find_record(
|
|
RSB rsb,
|
|
RSE_GET_MODE mode,
|
|
KEY * find_key,
|
|
USHORT find_count, USHORT search_flags)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f i n d _ r e c o r d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Find a record with the specified key value
|
|
* in a stream opened on a navigational index.
|
|
* Depending on the mode, get the first or
|
|
* last value with the given key. This routine
|
|
* must handle CRACK semantics.
|
|
*
|
|
**************************************/
|
|
TDBB tdbb;
|
|
JRD_REQ request;
|
|
IRSB_NAV impure;
|
|
RPB *rpb;
|
|
JRD_NOD retrieval_node;
|
|
IRB retrieval;
|
|
IDX *idx;
|
|
BTR page;
|
|
WIN window;
|
|
BTN node;
|
|
EXP expanded_page;
|
|
BTX expanded_node;
|
|
KEY lower, upper, *tmp, value;
|
|
USHORT upper_count, lower_count;
|
|
BOOLEAN result = FALSE, position_set = FALSE;
|
|
UCHAR *p, *q;
|
|
USHORT l;
|
|
|
|
tdbb = GET_THREAD_DATA;
|
|
request = tdbb->tdbb_request;
|
|
impure = (IRSB_NAV) ((UCHAR *) request + rsb->rsb_impure);
|
|
rpb = request->req_rpb + rsb->rsb_stream;
|
|
window.win_flags = 0;
|
|
|
|
retrieval_node = (JRD_NOD) rsb->rsb_arg[RSB_NAV_index];
|
|
retrieval = (IRB) retrieval_node->nod_arg[e_idx_retrieval];
|
|
|
|
/* save the current equality retrieval key */
|
|
|
|
tmp = retrieval->irb_key;
|
|
lower_count = retrieval->irb_lower_count;
|
|
upper_count = retrieval->irb_upper_count;
|
|
|
|
/* find the page that the key value should be on */
|
|
|
|
retrieval->irb_key = find_key;
|
|
retrieval->irb_upper_count = retrieval->irb_lower_count = find_count;
|
|
|
|
idx =
|
|
(IDX *) ((SCHAR *) impure + (SLONG) rsb->rsb_arg[RSB_NAV_idx_offset]);
|
|
page =
|
|
BTR_find_page(tdbb, retrieval, &window, idx, &lower, &upper, FALSE);
|
|
|
|
/* restore the saved equality retrieval key */
|
|
|
|
retrieval->irb_key = tmp;
|
|
retrieval->irb_lower_count = lower_count;
|
|
retrieval->irb_upper_count = upper_count;
|
|
|
|
/* find the appropriate leaf node */
|
|
|
|
while (!(node = BTR_find_leaf(page, find_key, impure->irsb_nav_data,
|
|
0, idx->idx_flags & idx_descending, TRUE)))
|
|
page =
|
|
(BTR) CCH_HANDOFF(tdbb, &window, page->btr_sibling, LCK_read,
|
|
pag_index);
|
|
|
|
if (expanded_page = window.win_expanded_buffer)
|
|
expanded_node = find_current(expanded_page, page, node);
|
|
else
|
|
expanded_node = NULL;
|
|
|
|
/* seed the key value with the prefix seen up to the current key */
|
|
|
|
MOVE_FAST(impure->irsb_nav_data, value.key_data, BTN_PREFIX(node));
|
|
|
|
/* In case of an error, we still need to release the window we hold
|
|
* during the parse. See HACKs for bug 7041
|
|
*/
|
|
|
|
try {
|
|
|
|
/* loop through the equivalent values of the given key, finding
|
|
a valid record if possible; for RSE_get_last, save the last
|
|
valid record and keep looking for more
|
|
|
|
NOTE: this whole process could be made more efficient if we
|
|
implemented a means of finding the last index key via
|
|
BTR_find_page() and BTR_find_leaf(), then proceeding backwards
|
|
to find a valid record -deej */
|
|
|
|
for (;;)
|
|
{
|
|
rpb->rpb_number = BTR_get_quad(BTN_NUMBER(node));
|
|
|
|
/* if we have gone past the search key value, stop looking */
|
|
|
|
if (rpb->rpb_number == END_LEVEL) {
|
|
CCH_RELEASE(tdbb, &window);
|
|
RSE_MARK_CRACK(tdbb, rsb, irsb_eof);
|
|
return FALSE;
|
|
}
|
|
|
|
if (rpb->rpb_number == END_BUCKET) {
|
|
page =
|
|
(BTR) CCH_HANDOFF(tdbb, &window, page->btr_sibling, LCK_read,
|
|
pag_index);
|
|
node = (BTN) page->btr_nodes;
|
|
|
|
if (expanded_page = window.win_expanded_buffer)
|
|
expanded_node = (BTX) expanded_page->exp_nodes;
|
|
continue;
|
|
}
|
|
|
|
#ifdef IGNORE_NULL_IDX_KEY
|
|
if (rpb->rpb_number == END_NON_NULL) {
|
|
/* ignore this node, and go to the next one. */
|
|
node = BTR_next_node(node, &expanded_node);
|
|
continue;
|
|
}
|
|
#endif /* IGNORE_NULL_IDX_KEY */
|
|
|
|
/* update the current stored key value */
|
|
|
|
value.key_length = BTN_LENGTH(node) + BTN_PREFIX(node);
|
|
p = value.key_data + BTN_PREFIX(node);
|
|
q = BTN_DATA(node);
|
|
for (l = BTN_LENGTH(node); l--;)
|
|
*p++ = *q++;
|
|
|
|
/* if the index key is greater than the search key, we didn't find the key */
|
|
|
|
if (compare_keys
|
|
(idx, value.key_data, value.key_length, find_key,
|
|
search_flags) > 0) {
|
|
/* if we never saw a valid record, mark as a forced crack so that
|
|
we will be at a position before the current record */
|
|
|
|
if (!position_set) {
|
|
set_position(impure, rpb, &window, node, expanded_node,
|
|
value.key_data, value.key_length);
|
|
RSE_MARK_CRACK(tdbb, rsb, irsb_crack | irsb_forced_crack);
|
|
}
|
|
|
|
CCH_RELEASE(tdbb, &window);
|
|
return result;
|
|
}
|
|
|
|
/* anytime we successfully retrieve a record, set position
|
|
to it in case we go past a viable record in looking for
|
|
the last record of equivalent key */
|
|
|
|
if (result = get_record(rsb, impure, rpb, &value, TRUE)) {
|
|
set_position(impure, rpb, &window, node, expanded_node,
|
|
value.key_data, value.key_length);
|
|
position_set = TRUE;
|
|
}
|
|
|
|
/* if we're looking for the first record, we're done */
|
|
|
|
if (result && mode == RSE_get_first) {
|
|
CCH_RELEASE(tdbb, &window);
|
|
return result;
|
|
}
|
|
|
|
node = BTR_next_node(node, &expanded_node);
|
|
}
|
|
|
|
} // try
|
|
catch (const std::exception&) {
|
|
CCH_RELEASE(tdbb, &window);
|
|
Firebird::status_exception::raise(-1);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
static BTX find_current(EXP expanded_page, BTR page, BTN current_node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f i n d _ c u r r e n t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Find the current location in the expanded
|
|
* index buffer according to its corresponding
|
|
* btree page.
|
|
*
|
|
**************************************/
|
|
BTN node, end;
|
|
BTX expanded_node;
|
|
|
|
if (!expanded_page)
|
|
return NULL;
|
|
|
|
expanded_node = expanded_page->exp_nodes;
|
|
|
|
for (node = page->btr_nodes, end = LAST_NODE(page); node < end;
|
|
node = NEXT_NODE(node)) {
|
|
if (node == current_node)
|
|
return expanded_node;
|
|
|
|
expanded_node = NEXT_EXPANDED(expanded_node, node);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static BOOLEAN find_saved_node(
|
|
RSB rsb,
|
|
IRSB_NAV impure,
|
|
WIN * window, BTN * return_node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f i n d _ s a v e d _ n o d e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* A page has changed. Find the new position of the
|
|
* previously stored node. The node could even be on
|
|
* a sibling page if the page has split. If we find
|
|
* the actual node, return TRUE.
|
|
*
|
|
**************************************/
|
|
IDX *idx;
|
|
BTR page;
|
|
BTN node, end;
|
|
SLONG number;
|
|
KEY key;
|
|
UCHAR *p, *q;
|
|
USHORT l;
|
|
int result;
|
|
TDBB tdbb;
|
|
|
|
tdbb = GET_THREAD_DATA;
|
|
|
|
idx =
|
|
(IDX *) ((SCHAR *) impure + (SLONG) rsb->rsb_arg[RSB_NAV_idx_offset]);
|
|
page = (BTR) CCH_FETCH(tdbb, window, LCK_read, pag_index);
|
|
|
|
/* the outer loop goes through all the sibling pages
|
|
looking for the node (in case the page has split);
|
|
the inner loop goes through the nodes on each page */
|
|
|
|
while (TRUE)
|
|
for (node = page->btr_nodes, end = LAST_NODE(page); node < end;
|
|
node = NEXT_NODE(node)) {
|
|
number =
|
|
BTR_get_quad(reinterpret_cast < char *>(BTN_NUMBER(node)));
|
|
if (number == END_LEVEL) {
|
|
*return_node = node;
|
|
return FALSE;
|
|
}
|
|
if (number == END_BUCKET) {
|
|
page =
|
|
(BTR) CCH_HANDOFF(tdbb, window, page->btr_sibling,
|
|
LCK_read, pag_index);
|
|
break;
|
|
}
|
|
#ifdef IGNORE_NULL_IDX_KEY
|
|
if (number == END_NON_NULL) {
|
|
/* ignore this node, and go to the next one. */
|
|
continue;
|
|
}
|
|
#endif /* IGNORE_NULL_IDX_KEY */
|
|
|
|
/* maintain the running key value and compare it with the stored value */
|
|
|
|
if ( (l = BTN_LENGTH(node)) ) {
|
|
p = key.key_data + BTN_PREFIX(node);
|
|
q = BTN_DATA(node);
|
|
do
|
|
*p++ = *q++;
|
|
while (--l);
|
|
}
|
|
key.key_length = BTN_LENGTH(node) + BTN_PREFIX(node);
|
|
result =
|
|
compare_keys(idx, impure->irsb_nav_data,
|
|
impure->irsb_nav_length, &key, FALSE);
|
|
|
|
/* if the keys are equal, return this node even if it is just a duplicate node;
|
|
duplicate nodes that have already been returned will be filtered out at a
|
|
higher level by checking the sparse bit map of records already returned, and
|
|
duplicate nodes that were inserted before the stored node can be
|
|
safely returned since their position in the index is arbitrary */
|
|
|
|
if (!result) {
|
|
*return_node = node;
|
|
if (number == impure->irsb_nav_number)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/* if the stored key is less than the current key, then the stored key
|
|
has been deleted from the index and we should just return the next
|
|
key after it */
|
|
|
|
if (result < 0) {
|
|
*return_node = node;
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static BTN get_position(
|
|
TDBB tdbb,
|
|
RSB rsb,
|
|
IRSB_NAV impure,
|
|
WIN * window,
|
|
RSE_GET_MODE direction, BTX * expanded_node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* g e t _ p o s i t i o n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Re-fetch B-tree page and reposition the node pointer to where
|
|
* we left off. If the page has been sitting in cache unchanged
|
|
* since we were there last, this is easy and cheap. If there's
|
|
* any chance the page has changed, we need to resynch.
|
|
*
|
|
**************************************/
|
|
SLONG incarnation;
|
|
BTR page;
|
|
BTN node;
|
|
EXP expanded_page = NULL;
|
|
BOOLEAN found;
|
|
|
|
SET_TDBB(tdbb);
|
|
|
|
/* If this is the first time, start at the beginning (or the end) */
|
|
|
|
if (!window->win_page || impure->irsb_flags & (irsb_bof | irsb_eof))
|
|
return nav_open(tdbb, rsb, impure, window, direction, expanded_node);
|
|
|
|
#ifdef PC_ENGINE
|
|
/* if we are on a forced crack, don't really get the next node in
|
|
the forward direction, since we are really supposed to be on a
|
|
position before the current node */
|
|
|
|
if ((impure->irsb_flags & irsb_forced_crack)
|
|
&& (direction == RSE_get_forward))
|
|
direction = RSE_get_current;
|
|
#endif
|
|
|
|
/* Re-fetch page and get incarnation counter */
|
|
|
|
page = (BTR) CCH_FETCH(tdbb, window, LCK_read, pag_index);
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
/* we must ensure that if we are going backwards, we always
|
|
have an expanded buffer (and a valid position on that page) */
|
|
|
|
if (direction == RSE_get_backward)
|
|
NAV_expand_index(window, impure);
|
|
expanded_page = window->win_expanded_buffer;
|
|
#endif
|
|
|
|
incarnation = CCH_get_incarnation(window);
|
|
if (incarnation == impure->irsb_nav_incarnation) {
|
|
node = (BTN) ((UCHAR *) page + impure->irsb_nav_offset);
|
|
if (!expanded_page) /* theoretically impossible */
|
|
*expanded_node = NULL;
|
|
else if (impure->irsb_nav_expanded_offset < 0) /* position unknown or invalid */
|
|
*expanded_node = find_current(expanded_page, page, node);
|
|
else
|
|
*expanded_node =
|
|
(BTX) ((UCHAR *) expanded_page +
|
|
impure->irsb_nav_expanded_offset);
|
|
|
|
/* The new way of doing things is to have the current
|
|
nav_offset be the last node fetched. Go forward or
|
|
backward from that point accordingly. */
|
|
|
|
if (direction == RSE_get_backward)
|
|
node = BTR_previous_node(node, expanded_node);
|
|
else if (direction == RSE_get_forward)
|
|
node = BTR_next_node(node, expanded_node);
|
|
return node;
|
|
}
|
|
|
|
/* Page is presumably changed. If there is a previous
|
|
stored position in the page, go back to it if possible. */
|
|
|
|
CCH_RELEASE(tdbb, window);
|
|
if (!impure->irsb_nav_page)
|
|
return nav_open(tdbb, rsb, impure, window, direction, expanded_node);
|
|
|
|
found = find_saved_node(rsb, impure, window, &node);
|
|
page = (BTR) window->win_buffer;
|
|
if (node) {
|
|
*expanded_node =
|
|
find_current(window->win_expanded_buffer, page, node);
|
|
if (direction == RSE_get_backward)
|
|
node = BTR_previous_node(node, expanded_node);
|
|
|
|
/* in the forward case, seek to the next node only if we found
|
|
the actual node on page; if we did not find it, we are already
|
|
at the next node (such as when the node is garbage collected) */
|
|
|
|
else if (direction == RSE_get_forward && found)
|
|
node = BTR_next_node(node, expanded_node);
|
|
return node;
|
|
}
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
/* As a last resort, return the first (or last if backwards)
|
|
node on the page. The sparse bitmap will prevent us from
|
|
seeing records we have already visited before. */
|
|
|
|
if (direction == RSE_get_backward) {
|
|
expanded_page = NAV_expand_index(window, 0);
|
|
return BTR_last_node(page, expanded_page, expanded_node);
|
|
}
|
|
|
|
if (expanded_page = window->win_expanded_buffer)
|
|
*expanded_node = (BTX) expanded_page->exp_nodes;
|
|
#endif
|
|
|
|
return (BTN) page->btr_nodes;
|
|
}
|
|
|
|
|
|
static BOOLEAN get_record(
|
|
RSB rsb,
|
|
IRSB_NAV impure,
|
|
RPB * rpb, KEY * key, BOOLEAN inhibit_cleanup)
|
|
{
|
|
/**************************************
|
|
*
|
|
* g e t _ r e c o r d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Get the record indicated by the passed rpb.
|
|
* This routine must set or clear the CRACK flag.
|
|
*
|
|
**************************************/
|
|
TDBB tdbb;
|
|
JRD_REQ request;
|
|
IDX *idx;
|
|
KEY value;
|
|
USHORT old_att_flags;
|
|
|
|
tdbb = GET_THREAD_DATA;
|
|
request = tdbb->tdbb_request;
|
|
idx =
|
|
(IDX *) ((SCHAR *) impure + (SLONG) rsb->rsb_arg[RSB_NAV_idx_offset]);
|
|
|
|
BOOLEAN result;
|
|
|
|
try {
|
|
|
|
if (inhibit_cleanup) {
|
|
/* Inhibit garbage collection & other housekeeping -
|
|
* to prevent a deadlock when we visit the record.
|
|
* There are cases when we are called here and have a window locked
|
|
* HACK for bug 7041
|
|
*/
|
|
|
|
old_att_flags =
|
|
static_cast<USHORT>(tdbb->tdbb_attachment->att_flags & ATT_no_cleanup);
|
|
tdbb->tdbb_attachment->att_flags |= ATT_no_cleanup; /* HACK */
|
|
}
|
|
|
|
/* the attempt to get a record, whether successful or not, takes
|
|
us off bof or eof (otherwise we will keep trying to retrieve
|
|
the first record) */
|
|
|
|
impure->irsb_flags &= ~(irsb_bof | irsb_eof);
|
|
|
|
result =
|
|
VIO_get(tdbb,
|
|
rpb,
|
|
rsb,
|
|
request->req_transaction,
|
|
reinterpret_cast<blk*>(request->req_pool));
|
|
if (result)
|
|
{
|
|
BTR_key(tdbb,
|
|
rpb->rpb_relation,
|
|
rpb->rpb_record,
|
|
reinterpret_cast <
|
|
struct idx *>((SCHAR *) impure +
|
|
(SLONG) rsb->rsb_arg[RSB_NAV_idx_offset]),
|
|
&value);
|
|
if (compare_keys(idx, key->key_data, key->key_length, &value, FALSE)) {
|
|
result = FALSE;
|
|
} else {
|
|
/* the successful retrieval of a record
|
|
means we are no longer on a crack */
|
|
|
|
RSE_MARK_CRACK(tdbb, rsb, 0);
|
|
|
|
/* mark in the navigational bitmap that we have visited this record */
|
|
|
|
SBM_set(tdbb, &impure->irsb_nav_records_visited, rpb->rpb_number);
|
|
}
|
|
}
|
|
|
|
if (!result) {
|
|
RSE_MARK_CRACK(tdbb, rsb, irsb_crack);
|
|
}
|
|
|
|
if (inhibit_cleanup) {
|
|
tdbb->tdbb_attachment->att_flags &= ~ATT_no_cleanup;
|
|
tdbb->tdbb_attachment->att_flags |= (old_att_flags & ATT_no_cleanup);
|
|
}
|
|
|
|
} // try
|
|
catch (const std::exception&) {
|
|
/* Cleanup the HACK should there be an error */
|
|
tdbb->tdbb_attachment->att_flags &= ~ATT_no_cleanup;
|
|
tdbb->tdbb_attachment->att_flags |=
|
|
(old_att_flags & ATT_no_cleanup);
|
|
Firebird::status_exception::raise(-1);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
static void init_fetch(IRSB_NAV impure)
|
|
{
|
|
/**************************************
|
|
*
|
|
* i n i t _ f e t c h
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Initialize for a fetch operation.
|
|
*
|
|
**************************************/
|
|
|
|
if (impure->irsb_flags & irsb_first) {
|
|
impure->irsb_flags &= ~irsb_first;
|
|
impure->irsb_nav_page = 0;
|
|
}
|
|
}
|
|
|
|
|
|
static BTN nav_open(
|
|
TDBB tdbb,
|
|
RSB rsb,
|
|
IRSB_NAV impure,
|
|
WIN * window, RSE_GET_MODE direction, BTX * expanded_node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* n a v _ o p e n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Open a stream to walk an index.
|
|
*
|
|
**************************************/
|
|
IRB retrieval;
|
|
KEY lower, upper, *limit_ptr;
|
|
BTR page;
|
|
//EXP expanded_page;
|
|
BTN node;
|
|
JRD_NOD retrieval_node;
|
|
IDX *idx;
|
|
|
|
SET_TDBB(tdbb);
|
|
|
|
/* initialize for a retrieval */
|
|
|
|
setup_bitmaps(rsb, impure);
|
|
impure->irsb_nav_page = 0;
|
|
impure->irsb_nav_length = 0;
|
|
|
|
if (direction == RSE_get_forward)
|
|
impure->irsb_flags |= irsb_bof;
|
|
else if (direction == RSE_get_backward)
|
|
impure->irsb_flags |= irsb_eof;
|
|
|
|
/* Find the starting leaf page */
|
|
|
|
retrieval_node = (JRD_NOD) rsb->rsb_arg[RSB_NAV_index];
|
|
retrieval = (IRB) retrieval_node->nod_arg[e_idx_retrieval];
|
|
idx =
|
|
(IDX *) ((SCHAR *) impure + (SLONG) rsb->rsb_arg[RSB_NAV_idx_offset]);
|
|
page =
|
|
BTR_find_page(tdbb, retrieval, window, idx, &lower, &upper,
|
|
(direction == RSE_get_backward));
|
|
impure->irsb_nav_page = window->win_page;
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
/* store the upper and lower bounds for the search in the impure area for the rsb */
|
|
|
|
if (retrieval->irb_lower_count) {
|
|
impure->irsb_nav_lower_length = lower.key_length;
|
|
MOVE_FAST(lower.key_data,
|
|
(impure->irsb_nav_data +
|
|
(SLONG) rsb->rsb_arg[RSB_NAV_key_length]),
|
|
lower.key_length);
|
|
}
|
|
|
|
if (retrieval->irb_upper_count) {
|
|
impure->irsb_nav_upper_length = upper.key_length;
|
|
MOVE_FAST(upper.key_data,
|
|
(impure->irsb_nav_data +
|
|
(2 * (SLONG) rsb->rsb_arg[RSB_NAV_key_length])),
|
|
upper.key_length);
|
|
}
|
|
|
|
/* find the limit the search needs to begin with, if any */
|
|
|
|
limit_ptr = NULL;
|
|
if (direction == RSE_get_forward) {
|
|
if (retrieval->irb_lower_count)
|
|
limit_ptr = &lower;
|
|
}
|
|
else {
|
|
if (retrieval->irb_upper_count)
|
|
limit_ptr = &upper;
|
|
}
|
|
#else
|
|
/* find the upper limit for the search (or lower for backwards) */
|
|
|
|
limit_ptr = NULL;
|
|
if (direction == RSE_get_forward) {
|
|
if (retrieval->irb_upper_count) {
|
|
impure->irsb_nav_upper_length = upper.key_length;
|
|
MOVE_FAST(upper.key_data,
|
|
(impure->irsb_nav_data +
|
|
(SLONG) rsb->rsb_arg[RSB_NAV_key_length]),
|
|
upper.key_length);
|
|
}
|
|
if (retrieval->irb_lower_count)
|
|
limit_ptr = &lower;
|
|
}
|
|
else {
|
|
if (retrieval->irb_lower_count) {
|
|
impure->irsb_nav_lower_length = lower.key_length;
|
|
MOVE_FAST(lower.key_data,
|
|
(impure->irsb_nav_data +
|
|
(SLONG) rsb->rsb_arg[RSB_NAV_key_length]),
|
|
lower.key_length);
|
|
}
|
|
if (retrieval->irb_upper_count)
|
|
limit_ptr = &upper;
|
|
}
|
|
#endif
|
|
|
|
/* If there is a starting descriptor, search down index to starting position.
|
|
This may involve sibling buckets if splits are in progress. If there
|
|
isn't a starting descriptor, walk down the left side of the index (or the
|
|
right side if backwards). */
|
|
|
|
if (limit_ptr) {
|
|
while (!(node = BTR_find_leaf(page, limit_ptr, impure->irsb_nav_data,
|
|
0, idx->idx_flags & idx_descending,
|
|
TRUE))
|
|
|| (BTR_get_quad(reinterpret_cast < char *>(BTN_NUMBER(node)))
|
|
== END_BUCKET))
|
|
page =
|
|
(BTR) CCH_HANDOFF(tdbb, window, page->btr_sibling, LCK_read,
|
|
pag_index);
|
|
|
|
impure->irsb_nav_length = BTN_PREFIX(node) + BTN_LENGTH(node);
|
|
|
|
#ifdef SCROLLABLE_CURSORS
|
|
/* ensure that if we are going backward, there is an expanded page */
|
|
|
|
if (direction == RSE_get_backward && !window->win_expanded_buffer)
|
|
NAV_expand_index(window, 0);
|
|
|
|
/* now find our current location on the expanded page */
|
|
|
|
if (expanded_node && (expanded_page = window->win_expanded_buffer))
|
|
*expanded_node = find_current(expanded_page, page, node);
|
|
#endif
|
|
}
|
|
#ifdef SCROLLABLE_CURSORS
|
|
else if (direction == RSE_get_backward) {
|
|
expanded_page = NAV_expand_index(window, 0);
|
|
node = BTR_last_node(page, expanded_page, expanded_node);
|
|
}
|
|
#endif
|
|
else {
|
|
node = (BTN) page->btr_nodes;
|
|
#ifdef SCROLLABLE_CURSORS
|
|
if (expanded_node && (expanded_page = window->win_expanded_buffer))
|
|
*expanded_node = (BTX) expanded_page->exp_nodes;
|
|
else
|
|
*expanded_node = NULL;
|
|
#endif
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
static void set_position(
|
|
IRSB_NAV impure,
|
|
RPB * rpb,
|
|
WIN * window,
|
|
BTN node,
|
|
BTX expanded_node, UCHAR * key_data, USHORT length)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e t _ p o s i t i o n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Setup the information about the current position
|
|
* in the navigational index. Store all required data in the
|
|
* impure area for the request.
|
|
*
|
|
**************************************/
|
|
|
|
/* We can actually set position without having a data page
|
|
fetched; if not, just set the incarnation to the lowest possible */
|
|
|
|
impure->irsb_nav_incarnation = CCH_get_incarnation(window);
|
|
impure->irsb_nav_page = window->win_page;
|
|
impure->irsb_nav_number = rpb->rpb_number;
|
|
|
|
/* save the current key value if it hasn't already been saved */
|
|
|
|
if (key_data) {
|
|
impure->irsb_nav_length = length;
|
|
MOVE_FAST(key_data, impure->irsb_nav_data, length);
|
|
}
|
|
|
|
/* setup the offsets into the current index page and expanded index buffer */
|
|
|
|
impure->irsb_nav_offset = (UCHAR *) node - (UCHAR *) window->win_buffer;
|
|
if (window->win_expanded_buffer)
|
|
impure->irsb_nav_expanded_offset =
|
|
(UCHAR *) expanded_node - (UCHAR *) window->win_expanded_buffer;
|
|
else
|
|
impure->irsb_nav_expanded_offset = -1;
|
|
}
|
|
|
|
|
|
static void setup_bitmaps(RSB rsb, IRSB_NAV impure)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s e t u p _ b i t m a p s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Setup the various bitmaps associated
|
|
* with a stream.
|
|
*
|
|
**************************************/
|
|
TDBB tdbb;
|
|
|
|
tdbb = GET_THREAD_DATA;
|
|
|
|
/* Start a bitmap which tells us we have already visited
|
|
this record; this is to handle the case where there is more
|
|
than one leaf node reference to the same record number; the
|
|
bitmap allows us to filter out the multiple references. */
|
|
|
|
SBM_reset(&impure->irsb_nav_records_visited);
|
|
|
|
/* the first time we open the stream, compute a bitmap
|
|
for the inversion tree--this may cause problems for
|
|
read-committed transactions since they will get one
|
|
view of the database when the stream is opened */
|
|
|
|
if (rsb->rsb_arg[RSB_NAV_inversion]) {
|
|
/* There is no need to reset or release the bitmap, it is
|
|
* done in EVL_bitmap ().
|
|
*/
|
|
|
|
impure->irsb_nav_bitmap = EVL_bitmap(tdbb,
|
|
reinterpret_cast <
|
|
jrd_nod *
|
|
>(rsb->rsb_arg
|
|
[RSB_NAV_inversion]));
|
|
}
|
|
}
|