8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-27 20:03:03 +01:00
firebird-mirror/src/jrd/opt.cpp

6453 lines
182 KiB
C++

/*
* PROGRAM: JRD Access Method
* MODULE: opt.c
* DESCRIPTION: Optimizer / record selection expression compiler
*
* 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): ______________________________________.
* 2002.10.12: Nickolay Samofatov: Fixed problems with wrong results produced by
* outer joins
* 2001.07.28: John Bellardo: Added code to handle rse_skip nodes.
* 2001.07.17 Claudio Valderrama: Stop crash with indices and recursive calls
* of OPT_compile: indicator csb_indices set to zero after used memory is
* returned to the free pool.
* 2001.02.15: Claudio Valderrama: Don't obfuscate the plan output if a selectable
* stored procedure doesn't access tables, views or other procedures directly.
* 2002.10.29 Sean Leyne - Removed obsolete "Netware" port
* 2002.10.30: Arno Brinkman: Changes made to gen_retrieval, OPT_compile and make_inversion.
* Procedure sort_indices added. The changes in gen_retrieval are that now
* an index with high field-count has priority to build an index from.
* Procedure make_inversion is changed so that it not pick every index
* that comes away, this was slow performance with bad selectivity indices
* which most are foreign_keys with a reference to a few records.
* 2002.11.01: Arno Brinkman: Added match_indices for better support of OR handling
* in INNER JOIN (gen_join) statements.
* 2002.12.15: Arno Brinkman: Added find_used_streams, so that inside opt_compile all the
* streams are marked active. This causes that more indices can be used for
* an retrieval. With this change BUG SF #219525 is solved too.
*
*/
#include "firebird.h"
#include "../jrd/ib_stdio.h"
#include <string.h>
#include "../jrd/gds.h"
#include "../jrd/jrd.h"
#include "../jrd/align.h"
#include "../jrd/val.h"
#include "../jrd/req.h"
#include "../jrd/exe.h"
#include "../jrd/lls.h"
#include "../jrd/ods.h"
#include "../jrd/btr.h"
#include "../jrd/sort.h"
#include "../jrd/rse.h"
#include "../jrd/intl.h"
#include "../jrd/thd.h"
#include "../jrd/gdsassert.h"
#include "../jrd/all_proto.h"
#include "../jrd/btr_proto.h"
#include "../jrd/cch_proto.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/dpm_proto.h"
#include "../jrd/err_proto.h"
#include "../jrd/ext_proto.h"
#include "../jrd/intl_proto.h"
#include "../jrd/lck_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/mov_proto.h"
#include "../jrd/opt_proto.h"
#include "../jrd/par_proto.h"
#include "../jrd/sbm_proto.h"
#include "../jrd/thd_proto.h"
#include "../jrd/gds_proto.h"
#include "../jrd/dbg_proto.h"
#ifdef DEV_BUILD
#define OPT_DEBUG
#endif
static BOOLEAN augment_stack(JRD_NOD, LLS *);
static SLONG calculate_priority_level(OPT, IDX *);
static void check_indices(csb_repeat *);
static BOOLEAN check_relationship(OPT, USHORT, USHORT);
static void check_sorts(RSE);
static void class_mask(USHORT, JRD_NOD *, ULONG *);
static void clear_bounds(OPT, IDX *);
static JRD_NOD compose(JRD_NOD *, JRD_NOD, NOD_T);
static bool computable(CSB, JRD_NOD, SSHORT, bool);
static void compute_dependencies(JRD_NOD, ULONG *);
static void compute_dbkey_streams(CSB, JRD_NOD, UCHAR *);
static void compute_rse_streams(CSB, RSE, UCHAR *);
static SLONG decompose(TDBB, JRD_NOD, LLS *, CSB);
static USHORT distribute_equalities(LLS *, CSB);
static BOOLEAN dump_index(JRD_NOD, SCHAR **, SSHORT *);
static BOOLEAN dump_rsb(JRD_REQ, RSB, SCHAR **, SSHORT *);
static BOOLEAN estimate_cost(TDBB, OPT, USHORT, double *, double *);
#ifdef EXPRESSION_INDICES
static BOOLEAN expression_equal(TDBB, JRD_NOD, JRD_NOD);
#endif
static void find_best(TDBB, OPT, USHORT, USHORT, UCHAR *, JRD_NOD,
double, double);
static JRD_NOD find_dbkey(JRD_NOD, USHORT, SLONG *);
static USHORT find_order(TDBB, OPT, UCHAR *, JRD_NOD);
static void find_rsbs(RSB, LLS *, LLS *);
static void find_used_streams(RSB , UCHAR *);
static void form_rivers(TDBB, OPT, UCHAR *, LLS *, JRD_NOD *, JRD_NOD *, JRD_NOD);
static BOOLEAN form_river(TDBB, OPT, USHORT, UCHAR *, UCHAR *, LLS *, JRD_NOD *,
JRD_NOD *, JRD_NOD);
static RSB gen_aggregate(TDBB, OPT, JRD_NOD);
static RSB gen_boolean(TDBB, OPT, RSB, JRD_NOD);
static RSB gen_first(TDBB, OPT, RSB, JRD_NOD);
static void gen_join(TDBB, OPT, UCHAR *, LLS *, JRD_NOD *, JRD_NOD *, JRD_NOD);
static RSB gen_navigation(TDBB, OPT, USHORT, JRD_REL, STR, IDX *, JRD_NOD *);
#ifdef SCROLLABLE_CURSORS
static RSB gen_nav_rsb(TDBB, OPT, USHORT, JRD_REL, STR, IDX *, RSE_GET_MODE);
#else
static RSB gen_nav_rsb(TDBB, OPT, USHORT, JRD_REL, STR, IDX *);
#endif
static RSB gen_outer(TDBB, OPT, RSE, LLS, JRD_NOD *, JRD_NOD *);
static RSB gen_procedure(TDBB, OPT, JRD_NOD);
static RSB gen_residual_boolean(TDBB, OPT, RSB);
static RSB gen_retrieval(TDBB, OPT, SSHORT, JRD_NOD *, JRD_NOD *, bool, bool,
JRD_NOD *);
static RSB gen_rsb(TDBB, OPT, RSB, JRD_NOD, SSHORT, JRD_REL, STR, JRD_NOD, float);
static RSB gen_skip (TDBB, OPT, RSB, JRD_NOD);
static RSB gen_sort(TDBB, OPT, UCHAR *, UCHAR *, RSB, JRD_NOD, USHORT);
static BOOLEAN gen_sort_merge(TDBB, OPT, LLS *);
static RSB gen_union(TDBB, OPT, JRD_NOD, UCHAR *, USHORT);
static void get_inactivities(CSB, ULONG *);
static IRL indexed_relationship(TDBB, OPT, USHORT);
static STR make_alias(TDBB, CSB, csb_repeat *);
static JRD_NOD make_binary_node(NOD_T, JRD_NOD, JRD_NOD, USHORT);
static RSB make_cross(TDBB, OPT, LLS);
static JRD_NOD make_index_node(TDBB, JRD_REL, CSB, IDX *);
static JRD_NOD make_inference_node(CSB, JRD_NOD, JRD_NOD, JRD_NOD);
static JRD_NOD make_inversion(TDBB, OPT, JRD_NOD, USHORT);
static JRD_NOD make_missing(TDBB, OPT, JRD_REL, JRD_NOD, USHORT, IDX *);
static JRD_NOD make_starts(TDBB, OPT, JRD_REL, JRD_NOD, USHORT, IDX *);
static BOOLEAN map_equal(JRD_NOD, JRD_NOD, JRD_NOD);
static void mark_indices(csb_repeat *, SSHORT);
static SSHORT match_index(TDBB, OPT, SSHORT, JRD_NOD, IDX *);
static BOOLEAN match_indices(TDBB, OPT, SSHORT, JRD_NOD, IDX *);
static USHORT nav_rsb_size(RSB, USHORT, USHORT);
static BOOLEAN node_equality(JRD_NOD, JRD_NOD);
static JRD_NOD optimize_like(TDBB, JRD_NOD);
#ifdef OPT_DEBUG
static void print_order(OPT, USHORT, double, double);
#endif
static USHORT river_count(USHORT, JRD_NOD *);
static BOOLEAN river_reference(RIV, JRD_NOD);
static BOOLEAN search_stack(JRD_NOD, LLS);
static void set_active(OPT, RIV);
static void set_direction(JRD_NOD, JRD_NOD);
static void set_inactive(OPT, RIV);
static void set_made_river(OPT, RIV);
static void set_position(JRD_NOD, JRD_NOD, JRD_NOD);
static void set_rse_inactive(CSB, RSE);
static void sort_indices_by_selectivity(csb_repeat *);
static SSHORT sort_indices_by_priority(csb_repeat *, IDX **, SLONG *);
/* macro definitions */
#ifdef OPT_DEBUG
#define DEBUG_PUNT 5
#define DEBUG_RELATIONSHIPS 4
#define DEBUG_ALL 3
#define DEBUG_CANDIDATE 2
#define DEBUG_BEST 1
#define DEBUG_NONE 0
IB_FILE *opt_debug_file = 0;
static int opt_debug_flag = DEBUG_NONE;
#endif
#define SET_DEP_BIT(array, bit) array[(bit)/32] |= (1L << ((bit) % 32))
#define CLEAR_DEP_BIT(array, bit) array[(bit)/32] &= ~(1L << ((bit) % 32))
#define TEST_DEP_BIT(array, bit) (array[(bit)/32] & (1L << ((bit) % 32)))
#define TEST_DEP_ARRAYS(ar1, ar2) ((ar1[0] & ar2[0]) || (ar1[1] & ar2[1]) || \
(ar1[2] & ar2[2]) || (ar1[3] & ar2[3]) || \
(ar1[4] & ar2[4]) || (ar1[5] & ar2[5]) || \
(ar1[6] & ar2[6]) || (ar1[7] & ar2[7]))
#define STREAM_INDEX(node) (node->nod_type == nod_relation) ? e_rel_stream : \
(node->nod_type == nod_procedure) ? e_prc_stream : \
(node->nod_type == nod_union) ? e_uni_stream : e_agg_stream
/* some arbitrary fudge factors for calculating costs, etc.--
these could probably be better tuned someday */
#define ESTIMATED_SELECTIVITY 0.01
#define INVERSE_ESTIMATE 10
#define INDEX_COST 30.0
#define CACHE_PAGES_PER_STREAM 15
#define SELECTIVITY_THRESHOLD_FACTOR 10
#define OR_SELECTIVITY_THRESHOLD_FACTOR 2000
#define LOWEST_PRIORITY_LEVEL 0
#define SQL_MATCH_1_CHAR '_' /* Not translatable */
#define SQL_MATCH_ANY_CHARS '%' /* Not translatable */
/* enumeration of sort datatypes */
static const UCHAR sort_dtypes[] = {
0, /* dtype_null */
SKD_text, /* dtype_text */
SKD_cstring, /* dtype_cstring */
SKD_varying, /* dtype_varying */
0,
0,
0, /* dtype_packed */
0, /* dtype_byte */
SKD_short, /* dtype_short */
SKD_long, /* dtype_long */
SKD_quad, /* dtype_quad */
SKD_float, /* dtype_real */
SKD_double, /* dtype_double */
SKD_double, /* dtype_d_float */
SKD_sql_date, /* dtype_sql_date */
SKD_sql_time, /* dtype_sql_time */
SKD_timestamp2, /* dtype_timestamp */
0, /* dtype_blob */
0, /* dtype_array */
SKD_int64 /* dtype_int64 */
};
BOOLEAN OPT_access_path(JRD_REQ request,
SCHAR * buffer,
SSHORT buffer_length, USHORT * return_length)
{
/**************************************
*
* O P T _ a c c e s s _ p a t h
*
**************************************
*
* Functional description
* Returns a formatted access path for all
* rse's in the specified request.
*
**************************************/
VEC vector;
RSB rsb;
SLONG i;
SCHAR *begin;
DEV_BLKCHK(request, type_req);
begin = buffer;
/* loop through all RSEs in the request,
and describe the rsb tree for that rsb;
go backwards because items were popped
off the stack backwards */
vector = request->req_fors;
if (!vector)
return FALSE;
for (i = vector->count() - 1; i >= 0; i--) {
rsb = (RSB) (*vector)[i];
if (rsb && !dump_rsb(request, rsb, &buffer, &buffer_length))
break;
}
*return_length = buffer - begin;
if (i >= 0)
return FALSE;
else
return TRUE;
}
RSB OPT_compile(TDBB tdbb,
CSB csb, RSE rse, LLS parent_stack)
{
/**************************************
*
* O P T _ c o m p i l e
*
**************************************
*
* Functional description
* Compile and optimize a record selection expression into a
* set of record source blocks (rsb's).
*
**************************************/
DBB dbb;
OPT opt_;
IDX *idx;
RIV river;
JRD_NOD node, *ptr, *end, sort, project, aggregate;
LLS conjunct_stack, rivers_stack, *stack_end;
RSB rsb;
SLONG idx_size, conjunct_count;
SSHORT i, stream;
UCHAR *p, *q, streams[MAX_STREAMS+1], beds[MAX_STREAMS+1],
*k, *b_end, *k_end, key_streams[MAX_STREAMS+1],
local_streams[MAX_STREAMS+1], outer_streams[MAX_STREAMS+1],
sub_streams[MAX_STREAMS+1]
DEV_BLKCHK(csb, type_csb);
DEV_BLKCHK(rse, type_nod);
DEV_BLKCHK(parent_stack, type_lls);
SET_TDBB(tdbb);
dbb = tdbb->tdbb_database;
#ifdef OPT_DEBUG
if (opt_debug_flag != DEBUG_NONE && !opt_debug_file)
opt_debug_file = ib_fopen("opt_debug.out", "w");
#endif
/* If there is a boolean, there is some work to be done. First,
decompose the boolean into conjunctions. Then get descriptions
of all indices for all relations in the rse. This will give
us the info necessary to allocate a optimizer block big
enough to hold this crud. */
/* Do not allocate the IDX struct. Let BTR_all do the job. The allocated
memory will then be in csb->csb_rpt[stream].csb_idx_allocation, which
gets cleaned up before this function exits. */
idx = (IDX *) NULL;
idx_size = 0;
opt_ = FB_NEW(*dbb->dbb_permanent) Opt();
try {
opt_->opt_csb = csb;
if (rse->nod_flags & rse_stream)
opt_->opt_g_flags |= opt_g_stream;
beds[0] = streams[0] = key_streams[0] = outer_streams[0] = sub_streams[0] = 0;
conjunct_stack = rivers_stack = NULL;
conjunct_count = 0;
check_sorts(rse);
sort = rse->rse_sorted;
project = rse->rse_projection;
aggregate = rse->rse_aggregate;
// put any additional booleans on the conjunct stack, and see if we
// can generate additional booleans by associativity--this will help
// to utilize indices that we might not have noticed
if (rse->rse_boolean)
conjunct_count =
decompose(tdbb, rse->rse_boolean, &conjunct_stack, csb);
conjunct_count += distribute_equalities(&conjunct_stack, csb);
// find the end of the conjunct stack.
for (stack_end = &conjunct_stack; *stack_end;
stack_end = &(*stack_end)->lls_next);
// clear the csb_active flag of all streams in the rse
set_rse_inactive(csb, rse);
p = streams + 1;
// go through the record selection expression generating
// record source blocks for all streams
for (ptr = rse->rse_relation, end = ptr + rse->rse_count; ptr < end;
ptr++)
{
node = *ptr;
// find the stream number and place it at the end of the beds array
// (if this is really a stream and not another rse)
if (node->nod_type != nod_rse)
{
stream = (USHORT) node->nod_arg[STREAM_INDEX(node)];
assert(stream <= MAX_UCHAR);
assert(beds[0] < MAX_STREAMS && beds[0] < MAX_UCHAR);
beds[++beds[0]] = (UCHAR) stream;
}
// for nodes which are not relations, generate an rsb to
// represent that work has to be done to retrieve them;
// find all the substreams involved and compile them as well
rsb = NULL;
local_streams[0] = 0;
if (node->nod_type == nod_union) {
i = (SSHORT) key_streams[0];
compute_dbkey_streams(csb, node, key_streams);
rsb =
gen_union(tdbb, opt_, node, key_streams + i + 1,
(USHORT) (key_streams[0] - i));
assert(local_streams[0] < MAX_STREAMS && local_streams[0] < MAX_UCHAR);
local_streams[++local_streams[0]] =
(UCHAR) node->nod_arg[e_uni_stream];
}
else if (node->nod_type == nod_aggregate) {
assert((int)node->nod_arg[e_agg_stream] <= MAX_STREAMS);
assert((int)node->nod_arg[e_agg_stream] <= MAX_UCHAR);
rsb = gen_aggregate(tdbb, opt_, node);
assert(local_streams[0] < MAX_STREAMS && local_streams[0] < MAX_UCHAR);
local_streams[++local_streams[0]] =
(UCHAR) node->nod_arg[e_agg_stream];
}
else if (node->nod_type == nod_procedure) {
rsb = gen_procedure(tdbb, opt_, node);
assert(local_streams[0] < MAX_STREAMS && local_streams[0] < MAX_UCHAR);
local_streams[++local_streams[0]] =
(UCHAR) node->nod_arg[e_prc_stream];
}
else if (node->nod_type == nod_rse) {
compute_rse_streams(csb, (RSE) node, beds);
compute_rse_streams(csb, (RSE) node, local_streams);
compute_dbkey_streams(csb, node, key_streams);
// pass rse boolean only to inner substreams because join condition
// should never exclude records from outer substreams
if (rse->rse_jointype == blr_inner ||
(rse->rse_jointype == blr_left && (ptr - rse->rse_relation)==1) ||
(rse->rse_jointype == blr_right && (ptr - rse->rse_relation)==0) )
{
// AB: For an (X LEFT JOIN Y) mark the outer-streams (X) as
// active because the inner-streams (Y) are always "dependent"
// on the outer-streams. So that index retrieval nodes could be made.
// For an INNER JOIN mark previous generated RSB's as active.
if (rse->rse_jointype == blr_left) {
for (i = 1; i <= outer_streams[0]; i++) {
csb->csb_rpt[outer_streams[i]].csb_flags |= csb_active;
}
}
*stack_end = parent_stack;
rsb = OPT_compile(tdbb, csb, (RSE) node, conjunct_stack);
*stack_end = NULL;
if (rse->rse_jointype == blr_left) {
for (i = 1; i <= outer_streams[0]; i++) {
csb->csb_rpt[outer_streams[i]].csb_flags &= ~csb_active;
}
}
} else {
rsb = OPT_compile(tdbb, csb, (RSE) node, parent_stack);
find_used_streams(rsb, outer_streams);
}
}
// if an rsb has been generated, we have a non-relation;
// so it forms a river of its own since it is separately
// optimized from the streams in this rsb
if (rsb) {
i = local_streams[0];
river = FB_NEW_RPT(*tdbb->tdbb_default, i) riv();
river->riv_count = (UCHAR) i;
river->riv_rsb = rsb;
MOVE_FAST(local_streams + 1, river->riv_streams, i);
// AB: Save all inner-part streams
if (rse->rse_jointype == blr_inner ||
(rse->rse_jointype == blr_left && (ptr - rse->rse_relation)==0) ||
(rse->rse_jointype == blr_right && (ptr - rse->rse_relation)==1) )
{
find_used_streams(rsb, sub_streams);
}
set_made_river(opt_, river);
set_inactive(opt_, river);
LLS_PUSH(river, &rivers_stack);
continue;
}
// we have found a base relation; record its stream
// number in the streams array as a candidate for
// merging into a river
// TMN: Is the intention really to allow streams[0] to overflow?
// I must assume that is indeed not the intention (not to mention
// it would make code later on fail), so I added the following assert.
assert(streams[0] < MAX_STREAMS && streams[0] < MAX_UCHAR);
++streams[0];
*p++ = (UCHAR) stream;
assert(outer_streams[0] < MAX_STREAMS && outer_streams[0] < MAX_UCHAR);
outer_streams[++outer_streams[0]] = stream;
// if we have seen any booleans or sort fields, we may be able to
// use an index to optimize them; retrieve the current format of
// all indices at this time so we can determine if it's possible
// AB: if a parent_stack was available and conjunct_count was 0
// then no indices where retrieved. Added also OR check on
// parent_stack below. SF BUG # [ 508594 ]
csb->csb_rpt[stream].csb_idx_allocation = 0;
if (conjunct_count || sort || project || aggregate || parent_stack)
{
JRD_REL relation = (JRD_REL) node->nod_arg[e_rel_relation];
if (relation && !relation->rel_file)
{
csb->csb_rpt[stream].csb_indices =
BTR_all(tdbb, relation, &idx,
&csb->csb_rpt[stream].csb_idx,
&csb->csb_rpt[stream].csb_idx_allocation,
&idx_size);
sort_indices_by_selectivity(&csb->csb_rpt[stream]);
mark_indices(&csb->csb_rpt[stream], relation->rel_id);
}
else
csb->csb_rpt[stream].csb_indices = 0;
}
}
// this is an attempt to make sure we have a large enough cache to
// efficiently retrieve this query; make sure the cache has a minimum
// number of pages for each stream in the RSE (the number is just a guess)
if (streams[0] > 5) {
CCH_expand(tdbb, (ULONG) (streams[0] * CACHE_PAGES_PER_STREAM));
}
// At this point we are ready to start optimizing.
// We will use the opt block to hold information of
// a global nature, meaning that it needs to stick
// around for the rest of the optimization process.
// first fill out the conjuncts at the end of opt
opt_->opt_count = (SSHORT) conjunct_count;
// Check if size of optimizer block exceeded.
if (opt_->opt_count > MAX_CONJUNCTS) {
ERR_post(isc_optimizer_blk_exc, 0);
// Msg442: size of optimizer block exceeded
}
// AB: Because know we're going to use both parent & conjunct_stack
// try to again to make additional conjuncts for an inner join,
// but be sure that opt_->opt_count contains the real "base"
// find the end of the conjunct stack.
for (stack_end = &conjunct_stack; *stack_end;
stack_end = &(*stack_end)->lls_next);
SLONG saved_conjunct_count = conjunct_count;
*stack_end = parent_stack;
conjunct_count += distribute_equalities(&conjunct_stack, csb);
if (parent_stack) {
// Find parent_stack position and reset it to NULL
for (stack_end = &conjunct_stack; *stack_end && !(*stack_end == parent_stack);
stack_end = &(*stack_end)->lls_next) ;
*stack_end = NULL;
}
if (conjunct_count > MAX_CONJUNCTS) {
ERR_post(isc_optimizer_blk_exc, 0);
// Msg442: size of optimizer block exceeded
}
// AB: If equality nodes could be made get them first from the stack
for (i = saved_conjunct_count; i < conjunct_count; i++) {
opt_->opt_rpt[i].opt_conjunct = node = (JRD_NOD) LLS_POP(&conjunct_stack);
compute_dependencies(node, opt_->opt_rpt[i].opt_dependencies);
}
for (i = 0; i < saved_conjunct_count; i++) {
opt_->opt_rpt[i].opt_conjunct = node = (JRD_NOD) LLS_POP(&conjunct_stack);
compute_dependencies(node, opt_->opt_rpt[i].opt_dependencies);
}
// Store the conjuncts from the parent rse. But don't fiddle with
// the parent's stack itself.
for (; parent_stack && conjunct_count < MAX_CONJUNCTS;
parent_stack = parent_stack->lls_next, conjunct_count++) {
opt_->opt_rpt[conjunct_count].opt_conjunct = node =
(JRD_NOD) parent_stack->lls_object;
compute_dependencies(node,
opt_->opt_rpt[conjunct_count].opt_dependencies);
}
opt_->opt_parent_count = (SSHORT) conjunct_count;
// Check if size of optimizer block exceeded.
if (parent_stack) {
ERR_post(isc_optimizer_blk_exc, 0);
// Msg442: size of optimizer block exceeded
}
// attempt to optimize aggregates via an index, if possible
if (aggregate && !sort && !project) {
sort = aggregate;
}
else {
rse->rse_aggregate = aggregate = NULL;
}
// AB: Mark the previous used streams (sub-rse's) as active
for (i = 1; i <= sub_streams[0]; i++) {
csb->csb_rpt[sub_streams[i]].csb_flags |= csb_active;
}
// outer joins require some extra processing
if (rse->rse_jointype != blr_inner) {
rsb = gen_outer(tdbb, opt_, rse, rivers_stack, &sort, &project);
}
else {
BOOLEAN sort_present = (sort) ? TRUE : FALSE;
BOOLEAN outer_rivers = FALSE;
JRD_NOD saved_sort_node = sort;
// AB: If previous rsb's are already on the stack we can't use
// an navigational-retrieval for an ORDER BY cause the next
// streams are JOINed to the previous onces
if (rivers_stack) {
sort = NULL;
outer_rivers = TRUE;
// AB: We could already have multiple rivers at this
// point so try to do some sort/merging now.
while (rivers_stack->lls_next
&& gen_sort_merge(tdbb, opt_, &rivers_stack));
// AB: Mark the previous used streams (sub-rse's) again
// as active, because an SORT/MERGE could reset the flags
for (i = 1; i <= sub_streams[0]; i++) {
csb->csb_rpt[sub_streams[i]].csb_flags |= csb_active;
}
}
// attempt to form joins in decreasing order of desirability
assert(streams[0] != 1 || csb->csb_rpt[streams[1]].csb_relation != 0);
gen_join(tdbb, opt_, streams, &rivers_stack, &sort, &project,
rse->rse_plan);
// If there are multiple rivers, try some sort/merging
while (rivers_stack->lls_next
&& gen_sort_merge(tdbb, opt_, &rivers_stack));
rsb = make_cross(tdbb, opt_, rivers_stack);
// AB: When we have a merge then a previous made ordering with
// an index doesn't guarantee that the result will be in that
// order. So we assigned the sort node back.
// SF BUG # [ 221921 ] ORDER BY has no effect
RSB test_rsb = rsb;
if ((rsb) && (rsb->rsb_type == rsb_boolean) && (rsb->rsb_next)) {
test_rsb = rsb->rsb_next;
}
if ((sort_present && outer_rivers) ||
((test_rsb) && (test_rsb->rsb_type == rsb_merge) && !sort && sort_present)) {
sort = saved_sort_node;
}
// Pick up any residual boolean that may have fallen thru the cracks
rsb = gen_residual_boolean(tdbb, opt_, rsb);
}
// if the aggregate was not optimized via an index, get rid of the
// sort and flag the fact to the calling routine
if (aggregate && sort) {
rse->rse_aggregate = NULL;
sort = NULL;
}
// check index usage in all the base streams to ensure
// that any user-specified access plan is followed
for (i = 1; i <= streams[0]; i++) {
check_indices(&csb->csb_rpt[streams[i]]);
}
if (project || sort) {
// Eliminate any duplicate dbkey streams
b_end = beds + beds[0];
k_end = key_streams + key_streams[0];
for (p = k = &key_streams[1]; p <= k_end; p++) {
for (q = &beds[1]; q <= b_end && *q != *p; q++);
if (q > b_end)
*k++ = *p;
}
key_streams[0] = k - &key_streams[1];
// Handle project clause, if present.
if (project) {
rsb = gen_sort(tdbb, opt_, beds, key_streams, rsb, project, TRUE);
}
// Handle sort clause if present
if (sort) {
rsb = gen_sort(tdbb, opt_, beds, key_streams, rsb, sort, FALSE);
}
}
// Handle first and/or skip. The skip MUST (if present)
// appear in the rsb list AFTER the first. Since the gen_first and gen_skip
// functions add their nodes at the beginning of the rsb list we MUST call
// gen_skip before gen_first.
//
if (rse->rse_skip) {
rsb = gen_skip(tdbb, opt_, rsb, rse->rse_skip);
}
if (rse->rse_first) {
rsb = gen_first(tdbb, opt_, rsb, rse->rse_first);
}
// release memory allocated for index descriptions
for (i = 0; i < streams[0]; i++) {
stream = streams[i + 1];
if (csb->csb_rpt[stream].csb_idx_allocation)
delete csb->csb_rpt[stream].csb_idx_allocation;
csb->csb_rpt[stream].csb_idx_allocation = 0;
// CVC: The following line added because OPT_compile is recursive, both directly
// and through gen_union(), too. Otherwise, we happen to step on deallocated memory
// and this is the cause of the crashes with indices that have plagued IB since v4.
csb->csb_rpt [stream].csb_indices = 0;
}
DEBUG
// free up memory for optimizer structures
delete opt_;
#ifdef OPT_DEBUG
if (opt_debug_file) {
ib_fflush(opt_debug_file);
ib_fclose(opt_debug_file);
opt_debug_file = 0;
}
#endif
} // try
catch (const std::exception&) {
for (i = 0; i < streams[0]; i++) {
stream = streams[i + 1];
if (csb->csb_rpt[stream].csb_idx_allocation)
delete csb->csb_rpt[stream].csb_idx_allocation;
csb->csb_rpt[stream].csb_idx_allocation = 0;
}
delete opt_;
ERR_punt();
}
if (rse->rse_writelock)
rsb->rsb_flags |= rsb_writelock;
return rsb;
}
JRD_NOD OPT_make_dbkey(OPT opt_, JRD_NOD boolean, USHORT stream)
{
/**************************************
*
* O P T _ m a k e _ d b k e y
*
**************************************
*
* Functional description
* If boolean is an equality comparison on the proper dbkey,
* make a "bit_dbkey" operator (makes bitmap out of dbkey
* expression.
*
* This is a little hairy, since view dbkeys are expressed as
* concatenations of primitive dbkeys.
*
**************************************/
CSB csb;
JRD_NOD value, dbkey;
SLONG n;
TDBB tdbb;
tdbb = GET_THREAD_DATA;
DEV_BLKCHK(opt_, type_opt);
DEV_BLKCHK(boolean, type_nod);
/* If this isn't an equality, it isn't even interesting */
if (boolean->nod_type != nod_eql)
return NULL;
/* Find the side of the equality that is potentially a dbkey. If
neither, make the obvious deduction */
dbkey = boolean->nod_arg[0];
value = boolean->nod_arg[1];
n = 0;
if (dbkey->nod_type != nod_dbkey && dbkey->nod_type != nod_concatenate)
{
if (value->nod_type != nod_dbkey &&
value->nod_type != nod_concatenate)
{
return NULL;
}
dbkey = value;
value = boolean->nod_arg[0];
}
/* If the value isn't computable, this has been a waste of time */
csb = opt_->opt_csb;
if (!computable(csb, value, stream, false)) {
return NULL;
}
/* If this is a concatenation, find an appropriate dbkey */
if (dbkey->nod_type == nod_concatenate) {
dbkey = find_dbkey(dbkey, stream, &n);
if (!dbkey) {
return NULL;
}
}
/* Make sure we have the correct stream */
if ((USHORT) dbkey->nod_arg[0] != stream)
return NULL;
/* If this is a dbkey for the appropriate stream, it's invertable */
dbkey = PAR_make_node(tdbb, 2);
dbkey->nod_count = 1;
dbkey->nod_type = nod_bit_dbkey;
dbkey->nod_arg[0] = value;
dbkey->nod_arg[1] = (JRD_NOD) n;
dbkey->nod_impure = CMP_impure(csb, sizeof(struct inv));
return dbkey;
}
JRD_NOD OPT_make_index(TDBB tdbb, OPT opt_, JRD_REL relation, IDX * idx)
{
/**************************************
*
* O P T _ m a k e _ i n d e x
*
**************************************
*
* Functional description
* Build node for index scan.
*
**************************************/
IRB retrieval;
JRD_NOD node, *lower, *upper, *end_node;
Opt::opt_repeat * tail, *end;
SET_TDBB(tdbb);
DEV_BLKCHK(opt_, type_opt);
DEV_BLKCHK(relation, type_rel);
/* Allocate both a index retrieval node and block. */
node = make_index_node(tdbb, relation, opt_->opt_csb, idx);
retrieval = (IRB) node->nod_arg[e_idx_retrieval];
retrieval->irb_relation = relation;
/* Pick up lower bound segment values */
lower = retrieval->irb_value;
upper = retrieval->irb_value + idx->idx_count;
end = opt_->opt_rpt + idx->idx_count;
if (idx->idx_flags & idx_descending) {
for (tail = opt_->opt_rpt; tail->opt_lower && tail < end; tail++)
*upper++ = tail->opt_lower;
for (tail = opt_->opt_rpt; tail->opt_upper && tail < end; tail++)
*lower++ = tail->opt_upper;
retrieval->irb_generic |= irb_descending;
}
else {
for (tail = opt_->opt_rpt; tail->opt_lower && tail < end; tail++)
*lower++ = tail->opt_lower;
for (tail = opt_->opt_rpt; tail->opt_upper && tail < end; tail++)
*upper++ = tail->opt_upper;
}
retrieval->irb_lower_count = lower - retrieval->irb_value;
retrieval->irb_upper_count =
(upper - retrieval->irb_value) - idx->idx_count;
#ifdef IGNORE_NULL_IDX_KEY
/* when lower bound is given, and upper bound is not,
do not look at index keys which have <null> value in their first
segment. This is because these records will not satisfy the query
since <null> is a state and not a value, and thus cannot be compared
to a value.
*/
if (retrieval->irb_lower_count && retrieval->irb_upper_count == 0) {
retrieval->irb_generic |= irb_ignore_null_value_key;
}
#endif /* IGNORE_NULL_IDX_KEY */
/* Check to see if this is really an equality retrieval */
if (retrieval->irb_lower_count == retrieval->irb_upper_count) {
retrieval->irb_generic |= irb_equality;
lower = retrieval->irb_value;
upper = retrieval->irb_value + idx->idx_count;
for (end_node = lower + retrieval->irb_lower_count; lower < end_node;)
if (*upper++ != *lower++) {
retrieval->irb_generic &= ~irb_equality;
break;
}
}
/* If we are matching less than the full index, this is a partial match */
if (retrieval->irb_upper_count < idx->idx_count)
retrieval->irb_generic |= irb_partial;
/* mark the index as utilized for the purposes of this compile */
idx->idx_runtime_flags |= idx_used;
return node;
}
int OPT_match_index(OPT opt, USHORT stream, IDX * idx)
{
/**************************************
*
* O P T _ m a t c h _ i n d e x
*
**************************************
*
* Functional description
* Match any active (computable but not consumed) boolean
* conjunctions against a given index. This is used by
* the external relation modules to do index optimization.
* Return the number of matching items.
*
**************************************/
TDBB tdbb;
CSB csb;
JRD_NOD node;
USHORT n;
Opt::opt_repeat * tail, *opt_end;
tdbb = GET_THREAD_DATA;
DEV_BLKCHK(opt, type_opt);
/* If there are not conjunctions, don't waste our time */
if (!opt->opt_count) {
return 0;
}
csb = opt->opt_csb;
opt_end = opt->opt_rpt + opt->opt_count;
n = 0;
clear_bounds(opt, idx);
for (tail = opt->opt_rpt; tail < opt_end; tail++) {
node = tail->opt_conjunct;
if (!(tail->opt_flags & opt_used) && computable(csb, node, -1, true)) {
n += match_index(tdbb, opt, stream, node, idx);
}
}
return n;
}
void OPT_set_index(TDBB tdbb,
JRD_REQ request, RSB * rsb_ptr, JRD_REL relation, IDX * idx)
{
/**************************************
*
* O P T _ s e t _ i n d e x
*
**************************************
*
* Functional description
* Reset the navigational order of a stream.
* Given a sequential or navigational rsb,
* reset the rsb to be a navigational rsb
* on the specified index.
*
**************************************/
RSB old_rsb, new_rsb;
USHORT index_id, i;
OPT opt;
JRD_NOD inversion = NULL;
JRD_NOD index_node, new_index_node;
IRB retrieval;
IDL index;
VEC vector;
DBB dbb;
DEV_BLKCHK(request, type_req);
DEV_BLKCHK(*rsb_ptr, type_rsb);
DEV_BLKCHK(relation, type_rel);
SET_TDBB(tdbb);
dbb = tdbb->tdbb_database;
old_rsb = *rsb_ptr;
/* fix up a boolean rsb to point to the actual stream rsb */
if (old_rsb->rsb_type == rsb_boolean) {
rsb_ptr = &old_rsb->rsb_next;
old_rsb = old_rsb->rsb_next;
}
/* check if there is an existing inversion for
a boolean expression mapped to an index */
if (old_rsb->rsb_type == rsb_indexed) {
inversion = (JRD_NOD) old_rsb->rsb_arg[0];
}
else if (old_rsb->rsb_type == rsb_navigate) {
inversion = (JRD_NOD) old_rsb->rsb_arg[RSB_NAV_inversion];
}
/* set up a dummy optimizer block just for the purposes
of the set index, to pass information to subroutines */
opt = FB_NEW(*dbb->dbb_permanent) Opt();
opt->opt_g_flags |= opt_g_stream;
/* generate a new rsb for the retrieval, making sure to
preserve the inversion generated for the last rsb; note
that if the bitmap for the inversion has already been
generated, it will be reused since it is already part of
the impure area--I can't think of any reason not to reuse it--deej */
if (idx) {
new_rsb = gen_nav_rsb(tdbb, opt, old_rsb->rsb_stream, relation, 0, idx
#ifdef SCROLLABLE_CURSORS
, RSE_get_forward
#endif
);
new_rsb->rsb_arg[RSB_NAV_inversion] = (RSB) inversion;
new_rsb->rsb_cardinality = old_rsb->rsb_cardinality;
}
else {
new_rsb = gen_rsb(tdbb, opt, 0, inversion, old_rsb->rsb_stream,
relation, 0, 0, (float) old_rsb->rsb_cardinality);
}
/* point the impure area of the new rsb to the impure area of the
old; since impure area is pre-allocated it would be difficult
to change now, so just use the same area; NOTE: this implies
that we must take some pains to ensure that the impure area is
always large enough to handle a maximum-key index */
new_rsb->rsb_impure = old_rsb->rsb_impure;
/* find index node if the old rsb was navigational */
if (old_rsb->rsb_type == rsb_navigate) {
index_node = (JRD_NOD) old_rsb->rsb_arg[RSB_NAV_index];
}
else {
index_node = NULL;
}
/* if the new rsb is navigational, set up impure space in request
for new index node; to convert from non-navigational to navigational,
we need to adjust the impure area upwards to make room for an INV
structure, and vice versa to convert the other way */
if (idx) {
new_index_node = (JRD_NOD) new_rsb->rsb_arg[RSB_NAV_index];
if (old_rsb->rsb_type == rsb_navigate)
new_index_node->nod_impure = index_node->nod_impure;
else {
new_index_node->nod_impure = old_rsb->rsb_impure;
new_rsb->rsb_impure += sizeof(struct inv);
}
}
else if (old_rsb->rsb_type == rsb_navigate) {
new_rsb->rsb_impure -= sizeof(struct inv);
}
/* if there was a previous index, release its lock
and remove its resource from the request */
if (old_rsb->rsb_type == rsb_navigate) {
retrieval = (IRB) index_node->nod_arg[e_idx_retrieval];
index_id = retrieval->irb_index;
index = CMP_get_index_lock(tdbb, relation, index_id);
if (!--index->idl_count) {
LCK_release(tdbb, index->idl_lock);
}
CMP_release_resource(&request->req_resources, rsc_index, index_id);
}
/* get lock on new index */
if (idx) {
index = CMP_get_index_lock(tdbb, relation, idx->idx_id);
if (!index->idl_count) {
LCK_lock_non_blocking(tdbb, index->idl_lock, LCK_SR, TRUE);
}
++index->idl_count;
}
/* go out to the vector which stores all rsbs for the
request, and replace the old with the new */
vector = request->req_fors;
for (i = 0; i < vector->count(); i++) {
if ((*vector)[i] == (BLK) old_rsb) {
(*vector)[i] = (BLK) new_rsb;
break;
}
}
/* release unneeded blocks */
delete opt;
if (index_node) {
delete index_node;
}
delete old_rsb;
*rsb_ptr = new_rsb;
}
static BOOLEAN augment_stack(JRD_NOD node, LLS * stack)
{
/**************************************
*
* a u g m e n t _ s t a c k
*
**************************************
*
* Functional description
* Add node to stack unless node is already on stack.
*
**************************************/
LLS temp;
DEV_BLKCHK(node, type_nod);
DEV_BLKCHK(*stack, type_lls);
for (temp = *stack; temp; temp = temp->lls_next) {
if (node_equality(node, (JRD_NOD) temp->lls_object)) {
return FALSE;
}
}
LLS_PUSH(node, stack);
return TRUE;
}
static SLONG calculate_priority_level(OPT opt, IDX * idx)
{
/**************************************
*
* c a l c u l a t e _ p r i o r i t y _ l e v e l
*
**************************************
*
* Functional description
* Return an calculated value based on
* how nodes where matched on the index.
* Before calling this function the
* match_index function must be called first!
*
**************************************/
Opt::opt_repeat *idx_tail, *idx_end;
USHORT idx_field_count, idx_eql_count;
JRD_NOD node;
if (opt->opt_rpt[0].opt_lower || opt->opt_rpt[0].opt_upper) {
/* Count how many fields matches */
idx_field_count = 0;
idx_tail = opt->opt_rpt;
idx_end = idx_tail + idx->idx_count;
for (;idx_tail < idx_end &&
(idx_tail->opt_lower || idx_tail->opt_upper); idx_tail++) {
idx_field_count++;
}
/* Count the maximum equals that matches at the begin */
idx_eql_count = 0;
idx_tail = opt->opt_rpt;
idx_end = idx_tail + idx->idx_count;
for (;idx_tail < idx_end &&
(idx_tail->opt_lower || idx_tail->opt_upper); idx_tail++) {
node = idx_tail->opt_match;
if (node->nod_type == nod_eql) {
idx_eql_count++;
}
else {
break;
}
}
/* Calculate our priority level */
return ((idx_eql_count * MAX_IDX * MAX_IDX) +
(idx_field_count * MAX_IDX) + (idx->idx_count));
}
else {
return LOWEST_PRIORITY_LEVEL;
}
}
static void check_indices(csb_repeat * csb_tail)
{
/**************************************
*
* c h e c k _ i n d i c e s
*
**************************************
*
* Functional description
* Check to make sure that the user-specified
* indices were actually utilized by the optimizer.
*
**************************************/
TEXT index_name[32];
JRD_NOD plan, access_type;
JRD_REL relation;
IDX *idx;
USHORT i;
TDBB tdbb;
tdbb = GET_THREAD_DATA;
if (!(plan = csb_tail->csb_plan)) {
return;
}
if (plan->nod_type != nod_retrieve) {
return;
}
relation = csb_tail->csb_relation;
/* if there were no indices fetched at all but the
user specified some, error out using the first
index specified */
if (!csb_tail->csb_indices &&
(access_type = plan->nod_arg[e_retrieve_access_type]))
/* index %s cannot be used in the specified plan */
ERR_post(gds_index_unused, gds_arg_string, access_type->nod_arg[2],
0);
/* check to make sure that all indices are either used or marked not to be used,
and that there are no unused navigational indices */
idx = csb_tail->csb_idx;
for (i = 0; i < csb_tail->csb_indices; i++) {
if (!(idx->idx_runtime_flags & (idx_plan_dont_use | idx_used)) ||
((idx->idx_runtime_flags & idx_plan_navigate)
&& !(idx->idx_runtime_flags & idx_navigate)))
if (!
(idx->idx_runtime_flags &
(idx_plan_missing | idx_plan_starts))) {
if (relation) {
MET_lookup_index(tdbb, index_name, relation->rel_name,
(USHORT) (idx->idx_id + 1));
}
else {
index_name[0] = 0;
}
/* index %s cannot be used in the specified plan */
ERR_post(gds_index_unused, gds_arg_string,
ERR_cstring(index_name), 0);
}
idx = NEXT_IDX(idx->idx_rpt, idx->idx_count);
}
}
static BOOLEAN check_relationship(OPT opt, USHORT position, USHORT stream)
{
/**************************************
*
* c h e c k _ r e l a t i o n s h i p
*
**************************************
*
* Functional description
* Check for a potential indexed relationship.
*
**************************************/
Opt::opt_repeat * tail, *end;
DEV_BLKCHK(opt, type_opt);
for (tail = opt->opt_rpt, end = tail + position; tail < end; tail++)
{
const USHORT n = tail->opt_stream;
for (IRL relationship = opt->opt_rpt[n].opt_relationships;
relationship;
relationship = relationship->irl_next)
{
if (stream == relationship->irl_stream) {
return TRUE;
}
}
}
return FALSE;
}
static void check_sorts(RSE rse)
{
/**************************************
*
* c h e c k _ s o r t s
*
**************************************
*
* Functional description
* Try to optimize out unnecessary sorting.
*
**************************************/
JRD_NOD sort, project, group, sub_rse;
JRD_NOD *group_ptr, *project_ptr, *sort_ptr;
JRD_NOD *group_end, *project_end, *sort_end;
DEV_BLKCHK(rse, type_nod);
sort = rse->rse_sorted;
project = rse->rse_projection;
/* check if a GROUP BY exists using the same fields as the project or sort:
if so, the projection can be eliminated; if no projection exists, then
the sort can be eliminated */
if ((project || sort) &&
(rse->rse_count == 1) &&
(sub_rse = rse->rse_relation[0]) &&
(sub_rse->nod_type == nod_aggregate) &&
(group = sub_rse->nod_arg[e_agg_group])) {
/* if all the fields of the project are the same as all the fields
of the group by, get rid of the project */
if (project && (project->nod_count == group->nod_count)) {
for (project_ptr = project->nod_arg, project_end =
project_ptr + project->nod_count; project_ptr < project_end;
project_ptr++) {
for (group_ptr = group->nod_arg, group_end =
group_ptr + group->nod_count; group_ptr < group_end;
group_ptr++)
if (map_equal
(*group_ptr, *project_ptr,
sub_rse->nod_arg[e_agg_map])) break;
if (group_ptr == group_end)
break;
}
/* we can now ignore the project, but in case the project is being done
in descending order because of an order by, do the group by the same way */
if (project_ptr == project_end) {
set_direction(project, group);
project = rse->rse_projection = NULL;
}
}
/* if there is no projection, then we can make a similar optimization
for sort, except that sort may have fewer fields than group by */
if (!project && sort && (sort->nod_count <= group->nod_count)) {
for (sort_ptr = sort->nod_arg, sort_end =
sort_ptr + sort->nod_count; sort_ptr < sort_end; sort_ptr++) {
for (group_ptr = group->nod_arg, group_end =
group_ptr + sort->nod_count; group_ptr < group_end;
group_ptr++)
if (map_equal
(*group_ptr, *sort_ptr,
sub_rse->nod_arg[e_agg_map])) break;
if (group_ptr == group_end)
break;
}
/* if all the fields in the sort list match the first n fields in the
project list, we can ignore the sort, but update the sort order
(ascending/descending) to match that in the sort list */
if (sort_ptr == sort_end) {
set_direction(sort, group);
set_position(sort, group, sub_rse->nod_arg[e_agg_map]);
sort = rse->rse_sorted = NULL;
}
}
}
/* examine the ORDER BY and DISTINCT clauses; if all the fields in the
ORDER BY match the first n fields in the DISTINCT in any order, the
ORDER BY can be removed, changing the fields in the DISTINCT to match
the ordering of fields in the ORDER BY */
if (sort && project && (sort->nod_count <= project->nod_count)) {
for (sort_ptr = sort->nod_arg, sort_end = sort_ptr + sort->nod_count;
sort_ptr < sort_end; sort_ptr++) {
for (project_ptr = project->nod_arg, project_end =
project_ptr + sort->nod_count; project_ptr < project_end;
project_ptr++)
if ((*sort_ptr)->nod_type == nod_field
&& (*project_ptr)->nod_type == nod_field
&& (*sort_ptr)->nod_arg[e_fld_stream] ==
(*project_ptr)->nod_arg[e_fld_stream]
&& (*sort_ptr)->nod_arg[e_fld_id] ==
(*project_ptr)->nod_arg[e_fld_id])
break;
if (project_ptr == project_end)
break;
}
/* if all the fields in the sort list match the first n fields in the
project list, we can ignore the sort, but update the project to match the sort */
if (sort_ptr == sort_end) {
set_direction(sort, project);
set_position(sort, project, NULL);
sort = rse->rse_sorted = NULL;
}
}
}
static void class_mask(USHORT count, JRD_NOD * class_, ULONG * mask)
{
/**************************************
*
* c l a s s _ m a s k
*
**************************************
*
* Functional description
* Given an sort/merge join equivalence class (vector of node pointers
* of representative values for rivers), return a bit mask of rivers
* with values.
*
**************************************/
SLONG i;
#ifdef DEV_BUILD
if (*class_) {
DEV_BLKCHK(*class_, type_nod);
}
#endif
if (count > MAX_CONJUNCTS) {
ERR_post(isc_optimizer_blk_exc, 0);
/* Msg442: size of optimizer block exceeded */
}
for (i = 0; i < OPT_BITS; i++) {
mask[i] = 0;
}
for (i = 0; i < count; i++, class_++) {
if (*class_) {
SET_DEP_BIT(mask, i);
DEV_BLKCHK(*class_, type_nod);
}
}
}
static void clear_bounds(OPT opt, IDX * idx)
{
/**************************************
*
* c l e a r _ b o u n d s
*
**************************************
*
* Functional description
* Clear upper and lower value slots before matching booleans to
* indices.
*
**************************************/
Opt::opt_repeat * tail, *opt_end;
DEV_BLKCHK(opt, type_opt);
opt_end = &opt->opt_rpt[idx->idx_count];
for (tail = opt->opt_rpt; tail < opt_end; tail++) {
tail->opt_lower = NULL;
tail->opt_upper = NULL;
tail->opt_match = NULL;
}
}
static JRD_NOD compose(JRD_NOD * node1, JRD_NOD node2, NOD_T node_type)
{
/**************************************
*
* c o m p o s e
*
**************************************
*
* Functional description
* Build and AND out of two conjuncts.
*
**************************************/
DEV_BLKCHK(*node1, type_nod);
DEV_BLKCHK(node2, type_nod);
if (!node2) {
return *node1;
}
if (!*node1) {
return (*node1 = node2);
}
return *node1 = make_binary_node(node_type, *node1, node2, FALSE);
}
static bool computable(CSB csb,
JRD_NOD node,
SSHORT stream,
bool idx_use)
{
/**************************************
*
* c o m p u t a b l e
*
**************************************
*
* Functional description
* See if node is presently computable.
* Note that a field is not computable
* with respect to its own stream.
*
* There are two different uses of computable().
* (a) idx_use == false: when an unused conjunct is to be picked for making
* into a boolean and in making a db_key.
* In this case, a node is said to be computable, if all the streams
* involved in that node are csb_active. The csb_active flag
* defines all the streams available in the current scope of the
* query.
* (b) idx_use == true: to determine if we can use an
* index on the conjunct we have already chosen.
* In order to use an index on a conjunct, it is required that the
* all the streams involved in the conjunct are currently active
* or have been already processed before and made into rivers.
* Because, here we want to differentiate between streams we have
* not yet worked on and those that we have worked on or are currently
* working on.
*
**************************************/
RSE rse;
JRD_NOD *ptr, *end, sub, value;
USHORT n;
DEV_BLKCHK(csb, type_csb);
DEV_BLKCHK(node, type_nod);
/* Recurse thru interesting sub-nodes */
ptr = node->nod_arg;
if (node->nod_type == nod_procedure) {
return false;
}
if (node->nod_type == nod_union) {
JRD_NOD clauses;
clauses = node->nod_arg[e_uni_clauses];
for (ptr = clauses->nod_arg, end = ptr + clauses->nod_count; ptr < end; ptr += 2)
{
if (!computable(csb, *ptr, stream, idx_use)) {
return false;
}
}
}
else {
for (end = ptr + node->nod_count; ptr < end; ptr++) {
if (!computable(csb, *ptr, stream, idx_use)) {
return false;
}
}
}
switch (node->nod_type) {
case nod_field:
if ((n = (USHORT) node->nod_arg[e_fld_stream]) == stream) {
return false;
}
if (idx_use &&
!(csb->csb_rpt[n].csb_flags & (csb_made_river | csb_active)))
{
return false;
}
if (!idx_use && !(csb->csb_rpt[n].csb_flags & csb_active)) {
return false;
}
return true;
case nod_dbkey:
if ((n = (USHORT) node->nod_arg[0]) == stream) {
return false;
}
if (idx_use &&
!(csb->csb_rpt[n].
csb_flags & (csb_made_river | csb_active)))
{
return false;
}
if (!idx_use && !(csb->csb_rpt[n].csb_flags & csb_active)) {
return false;
}
return true;
case nod_min:
case nod_max:
case nod_average:
case nod_total:
case nod_count:
case nod_from:
if ((sub = node->nod_arg[e_stat_default]) &&
!computable(csb, sub, stream, idx_use))
{
return false;
}
rse = (RSE) node->nod_arg[e_stat_rse];
value = node->nod_arg[e_stat_value];
break;
case nod_rse:
rse = (RSE) node;
value = NULL;
break;
case nod_aggregate:
rse = (RSE) node->nod_arg[e_agg_rse];
rse->rse_sorted = node->nod_arg[e_agg_group];
value = NULL;
break;
default:
return true;
}
/* Node is a record selection expression. */
bool result = true;
if ((sub = rse->rse_first) && !computable(csb, sub, stream, idx_use)) {
return false;
}
if ((sub = rse->rse_skip) && !computable(csb, sub, stream, idx_use)) {
return false;
}
/* Set sub-streams of rse active */
for (ptr = rse->rse_relation, end = ptr + rse->rse_count; ptr < end; ptr++) {
if ((*ptr)->nod_type != nod_rse) {
n = (USHORT) (*ptr)->nod_arg[STREAM_INDEX((*ptr))];
csb->csb_rpt[n].csb_flags |= csb_active;
}
}
/* Check sub-stream */
if (((sub = rse->rse_boolean) && !computable(csb, sub, stream, idx_use)) ||
((sub = rse->rse_sorted) && !computable(csb, sub, stream, idx_use)) ||
((sub = rse->rse_projection) && !computable(csb, sub, stream, idx_use)))
{
result = false;
}
for (ptr = rse->rse_relation, end = ptr + rse->rse_count;
((ptr < end) && (result)); ptr++)
{
if ((*ptr)->nod_type != nod_rse) {
if (!computable(csb, (*ptr), stream, idx_use)) {
result = FALSE;
}
}
}
/* Check value expression, if any */
if (result && value && !computable(csb, value, stream, idx_use)) {
result = false;
}
/* Reset streams inactive */
for (ptr = rse->rse_relation, end = ptr + rse->rse_count; ptr < end;
ptr++)
{
if ((*ptr)->nod_type != nod_rse)
{
n = (USHORT) (*ptr)->nod_arg[STREAM_INDEX((*ptr))];
csb->csb_rpt[n].csb_flags &= ~csb_active;
}
}
return result;
}
static void compute_dependencies(JRD_NOD node, ULONG * dependencies)
{
/**************************************
*
* c o m p u t e _ d e p e n d e n c i e s
*
**************************************
*
* Functional description
* Compute stream dependencies for evaluation of an expression.
*
**************************************/
RSE rse;
JRD_NOD *ptr, *end, sub, value;
USHORT n;
DEV_BLKCHK(node, type_nod);
/* Recurse thru interesting sub-nodes */
ptr = node->nod_arg;
if (node->nod_type == nod_procedure) {
return;
}
for (end = ptr + node->nod_count; ptr < end; ptr++) {
compute_dependencies(*ptr, dependencies);
}
switch (node->nod_type) {
case nod_field:
n = (USHORT) node->nod_arg[e_fld_stream];
SET_DEP_BIT(dependencies, n);
return;
case nod_dbkey:
n = (USHORT) node->nod_arg[0];
SET_DEP_BIT(dependencies, n);
return;
case nod_min:
case nod_max:
case nod_average:
case nod_total:
case nod_count:
case nod_from:
if ( (sub = node->nod_arg[e_stat_default]) ) {
compute_dependencies(sub, dependencies);
}
rse = (RSE) node->nod_arg[e_stat_rse];
value = node->nod_arg[e_stat_value];
break;
case nod_rse:
rse = (RSE) node;
value = NULL;
break;
default:
return;
}
/* Node is a record selection expression. Groan. Ugh. Yuck. */
if ( (sub = rse->rse_first) ) {
compute_dependencies(sub, dependencies);
}
/* Check sub-expressions */
if ( (sub = rse->rse_boolean) ) {
compute_dependencies(sub, dependencies);
}
if ( (sub = rse->rse_sorted) ) {
compute_dependencies(sub, dependencies);
}
if ( (sub = rse->rse_projection) ) {
compute_dependencies(sub, dependencies);
}
/* Check value expression, if any */
if (value) {
compute_dependencies(value, dependencies);
}
/* Reset streams inactive */
for (ptr = rse->rse_relation, end = ptr + rse->rse_count; ptr < end; ptr++) {
if ((*ptr)->nod_type != nod_rse) {
n = (USHORT) (*ptr)->nod_arg[STREAM_INDEX((*ptr))];
CLEAR_DEP_BIT(dependencies, n);
}
}
}
static void compute_dbkey_streams(CSB csb, JRD_NOD node, UCHAR * streams)
{
/**************************************
*
* c o m p u t e _ d b k e y _ s t r e a m s
*
**************************************
*
* Functional description
* Identify all of the streams for which a
* dbkey may need to be carried through a sort.
*
**************************************/
JRD_NOD clauses, *ptr, *end;
RSE rse;
DEV_BLKCHK(csb, type_csb);
DEV_BLKCHK(node, type_nod);
if (node->nod_type == nod_relation) {
assert(streams[0] < MAX_STREAMS && streams[0] < MAX_UCHAR);
streams[++streams[0]] = (UCHAR) node->nod_arg[e_rel_stream];
}
else if (node->nod_type == nod_union) {
clauses = node->nod_arg[e_uni_clauses];
if (clauses->nod_type != nod_procedure) {
for (ptr = clauses->nod_arg, end = ptr + clauses->nod_count; ptr < end; ptr += 2) {
compute_dbkey_streams(csb, *ptr, streams);
}
}
}
else if (node->nod_type == nod_rse) {
rse = (RSE) node;
for (ptr = rse->rse_relation, end = ptr + rse->rse_count; ptr < end; ptr++) {
compute_dbkey_streams(csb, *ptr, streams);
}
}
}
static void compute_rse_streams(CSB csb, RSE rse, UCHAR * streams)
{
/**************************************
*
* c o m p u t e _ r s e _ s t r e a m s
*
**************************************
*
* Functional description
* Identify the streams that make up an rse.
*
**************************************/
JRD_NOD *ptr, *end, node;
DEV_BLKCHK(csb, type_csb);
DEV_BLKCHK(rse, type_nod);
for (ptr = rse->rse_relation, end = ptr + rse->rse_count; ptr < end; ptr++) {
node = *ptr;
if (node->nod_type != nod_rse) {
assert(streams[0] < MAX_STREAMS && streams[0] < MAX_UCHAR);
streams[++streams[0]] = (UCHAR) node->nod_arg[STREAM_INDEX(node)];
}
else {
compute_rse_streams(csb, (RSE) node, streams);
}
}
}
static SLONG decompose(TDBB tdbb,
JRD_NOD boolean_node, LLS * stack, CSB csb)
{
/**************************************
*
* d e c o m p o s e
*
**************************************
*
* Functional description
* Decompose a boolean into a stack of conjuctions.
*
**************************************/
JRD_NOD arg, node;
DEV_BLKCHK(boolean_node, type_nod);
DEV_BLKCHK(*stack, type_lls);
DEV_BLKCHK(csb, type_csb);
if (boolean_node->nod_type == nod_and) {
return decompose(tdbb, boolean_node->nod_arg[0], stack, csb) +
decompose(tdbb, boolean_node->nod_arg[1], stack, csb);
}
/* turn a between into (a greater than or equal) AND (a less than or equal) */
if (boolean_node->nod_type == nod_between) {
arg = boolean_node->nod_arg[0];
if (arg->nod_type == nod_from) {
/* Without this ERR_punt(), server was crashing with sub queries
* under "between" predicate, Bug No. 73766 */
ERR_post(isc_optimizer_between_err, 0);
/* Msg 493: Unsupported field type specified in BETWEEN predicate */
}
node = make_binary_node(nod_geq, arg, boolean_node->nod_arg[1], TRUE);
LLS_PUSH(node, stack);
arg = CMP_clone_node(tdbb, csb, arg);
node = make_binary_node(nod_leq, arg, boolean_node->nod_arg[2], TRUE);
LLS_PUSH(node, stack);
return 2;
}
/* turn a LIKE into a LIKE and a STARTING WITH, if it starts
with anything other than a pattern-matching character */
if ((boolean_node->nod_type == nod_like) &&
(arg = optimize_like(tdbb, boolean_node))) {
node =
make_binary_node(nod_starts, boolean_node->nod_arg[0], arg,
FALSE);
LLS_PUSH(node, stack);
LLS_PUSH(boolean_node, stack);
return 2;
}
LLS_PUSH(boolean_node, stack);
return 1;
}
static USHORT distribute_equalities(LLS * org_stack, CSB csb)
{
/**************************************
*
* d i s t r i b u t e _ e q u a l i t i e s
*
**************************************
*
* Functional description
* Given a stack of conjunctions, generate some simple
* inferences. In general, find classes of equalities,
* then find operations based on members of those classes.
* If we find any, generate additional conjunctions. In
* SSHORT:
*
* If (a == b) and (a $ c) --> (b $ c) for any
* operation '$'.
*
**************************************/
LLS classes[MAX_OPT_ITEMS], *class_, *class2, *end, stack, temp;
JRD_NOD boolean, node1, node2, new_node, arg1, arg2;
USHORT reverse, count, n;
DEV_BLKCHK(*org_stack, type_lls);
DEV_BLKCHK(csb, type_csb);
/* Zip thru stack of booleans looking for field equalities */
end = classes;
for (stack = *org_stack; stack; stack = stack->lls_next) {
boolean = (JRD_NOD) stack->lls_object;
if (boolean->nod_type != nod_eql)
continue;
node1 = boolean->nod_arg[0];
if (node1->nod_type != nod_field)
continue;
node2 = boolean->nod_arg[1];
if (node2->nod_type != nod_field)
continue;
for (class_ = classes; class_ < end; class_++)
if (search_stack(node1, *class_)) {
augment_stack(node2, class_);
break;
}
else if (search_stack(node2, *class_)) {
LLS_PUSH(node1, class_);
break;
}
if (class_ == end) {
*class_ = NULL;
++end;
LLS_PUSH(node1, class_);
LLS_PUSH(node2, class_);
}
}
if (end == classes)
return 0;
/* Make another pass looking for any equality relationships that may
have crept in between classes (this could result from the
sequence (A = B, C = D, B = C) */
for (class_ = classes; class_ < end; class_++)
for (stack = *class_; stack; stack = stack->lls_next)
for (class2 = class_ + 1; class2 < end; class2++)
if (search_stack((JRD_NOD) stack->lls_object, *class2)) {
DEBUG;
while (*class2)
augment_stack((JRD_NOD) LLS_POP(class2), class_);
}
count = 0;
/* Start by making a pass distributing field equalities */
for (class_ = classes; class_ < end; class_++) {
for (stack = *class_, n = 0; stack; stack = stack->lls_next)
n++;
if (n >= 3)
for (stack = *class_; stack; stack = stack->lls_next)
for (temp = stack->lls_next; temp; temp = temp->lls_next) {
boolean =
make_binary_node(nod_eql, (JRD_NOD) stack->lls_object,
(JRD_NOD) temp->lls_object, TRUE);
if (augment_stack(boolean, org_stack)) {
DEBUG;
count++;
}
else
delete boolean;
}
}
/* Now make a second pass looking for non-field equalities */
for (stack = *org_stack; stack; stack = stack->lls_next) {
boolean = (JRD_NOD) stack->lls_object;
if (boolean->nod_type != nod_eql &&
boolean->nod_type != nod_gtr &&
boolean->nod_type != nod_geq &&
boolean->nod_type != nod_leq &&
boolean->nod_type != nod_lss &&
boolean->nod_type != nod_matches &&
boolean->nod_type != nod_contains &&
boolean->nod_type != nod_like) continue;
node1 = boolean->nod_arg[0];
node2 = boolean->nod_arg[1];
reverse = FALSE;
if (node1->nod_type != nod_field) {
new_node = node1;
node1 = node2;
node2 = new_node;
reverse = TRUE;
}
if (node1->nod_type != nod_field)
continue;
if (node2->nod_type != nod_literal &&
node2->nod_type != nod_variable &&
node2->nod_type != nod_argument) continue;
for (class_ = classes; class_ < end; class_++)
if (search_stack(node1, *class_)) {
for (temp = *class_; temp; temp = temp->lls_next)
if (!node_equality(node1, (JRD_NOD) temp->lls_object)) {
if (reverse) {
arg1 = boolean->nod_arg[0];
arg2 = (JRD_NOD) temp->lls_object;
}
else {
arg1 = (JRD_NOD) temp->lls_object;
arg2 = boolean->nod_arg[1];
}
/* From the conjuncts X(A,B) and A=C, infer the
* conjunct X(C,B)
*/
new_node =
make_inference_node(csb, boolean, arg1, arg2);
if (augment_stack(new_node, org_stack))
count++;
}
break;
}
}
return count;
}
static BOOLEAN dump_index(JRD_NOD node,
SCHAR ** buffer_ptr, SSHORT * buffer_length)
{
/**************************************
*
* d u m p _ i n d e x
*
**************************************
*
* Functional description
* Dump an index inversion tree to
* an info buffer.
*
**************************************/
SCHAR *buffer;
IRB retrieval;
TEXT index_name[32], *i;
SSHORT length;
TDBB tdbb;
tdbb = GET_THREAD_DATA;
DEV_BLKCHK(node, type_nod);
buffer = *buffer_ptr;
if (--(*buffer_length) < 0)
return FALSE;
/* spit out the node type */
if (node->nod_type == nod_bit_and)
*buffer++ = gds_info_rsb_and;
else if (node->nod_type == nod_bit_or)
*buffer++ = gds_info_rsb_or;
else if (node->nod_type == nod_bit_dbkey)
*buffer++ = gds_info_rsb_dbkey;
else if (node->nod_type == nod_index)
*buffer++ = gds_info_rsb_index;
/* dump sub-nodes or the actual index info */
if ((node->nod_type == nod_bit_and) || (node->nod_type == nod_bit_or)) {
if (!dump_index(node->nod_arg[0], &buffer, buffer_length))
return FALSE;
if (!dump_index(node->nod_arg[1], &buffer, buffer_length))
return FALSE;
}
else if (node->nod_type == nod_index) {
retrieval = (IRB) node->nod_arg[e_idx_retrieval];
MET_lookup_index(tdbb, index_name, retrieval->irb_relation->rel_name,
(USHORT) (retrieval->irb_index + 1));
length = strlen(index_name);
if ((*buffer_length -= (length + 1)) >= 0) {
*buffer++ = (SCHAR) length;
i = index_name;
while (length--)
*buffer++ = *i++;
}
}
*buffer_ptr = buffer;
return TRUE;
}
static BOOLEAN dump_rsb(JRD_REQ request,
RSB rsb, SCHAR ** buffer_ptr, SSHORT * buffer_length)
{
/**************************************
*
* d u m p _ r s b
*
**************************************
*
* Functional description
* Returns a formatted access path for
* a particular rsb.
*
**************************************/
USHORT length;
JRD_REL relation;
STR alias;
SCHAR *name, *buffer;
RSB *ptr, *end;
SSHORT return_length;
JRD_PRC procedure;
DEV_BLKCHK(rsb, type_rsb);
buffer = *buffer_ptr;
/* leave room for the rsb begin, type, and end */
if ((*buffer_length -= 4) < 0)
return FALSE;
*buffer++ = gds_info_rsb_begin;
/* dump out the alias or relation name if it exists */
name = NULL;
relation = rsb->rsb_relation;
if ( (alias = rsb->rsb_alias) ) {
length = alias->str_length;
name = (SCHAR *) alias->str_data;
}
else if (relation) {
length = strlen(relation->rel_name);
name = relation->rel_name;
}
if (name) {
*buffer_length -= 2 + length;
if (*buffer_length < 0)
return FALSE;
*buffer++ = gds_info_rsb_relation;
*buffer++ = (SCHAR) length;
while (length--)
*buffer++ = *name++;
}
/* print out the type followed immediately by any
type-specific data */
*buffer++ = gds_info_rsb_type;
switch (rsb->rsb_type) {
case rsb_indexed:
*buffer++ = gds_info_rsb_indexed;
if (!dump_index((JRD_NOD) rsb->rsb_arg[0], &buffer, buffer_length))
return FALSE;
if (--(*buffer_length) < 0)
return FALSE;
break;
case rsb_navigate:
*buffer++ = gds_info_rsb_navigate;
if (!dump_index
((JRD_NOD) rsb->rsb_arg[RSB_NAV_index], &buffer,
buffer_length)) return FALSE;
if (--(*buffer_length) < 0)
return FALSE;
break;
case rsb_sequential:
*buffer++ = gds_info_rsb_sequential;
break;
case rsb_cross:
*buffer++ = gds_info_rsb_cross;
break;
case rsb_sort:
*buffer++ = gds_info_rsb_sort;
break;
case rsb_procedure:
*buffer++ = gds_info_rsb_procedure;
/* don't try to print out plans of procedures called by procedures, since
we could get into a recursive situation; if the customer wants to know
the plan produced by the sub-procedure, they can invoke it directly */
if (request->req_procedure)
break;
procedure = rsb->rsb_procedure;
if (!procedure || !procedure->prc_request)
return FALSE;
/* CVC: This is becoming trickier. There are procedures that don't have a plan
because they don't access tables. In this case, the engine gives up and swallows
the whole plan. Not acceptable. */
if (!procedure->prc_request->req_fors) {
STR n = procedure->prc_name;
length = (n && n->str_data) ? n->str_length : 0;
*buffer_length -= 5 + length;
if (*buffer_length < 0)
return FALSE;
*buffer++ = gds_info_rsb_begin;
*buffer++ = gds_info_rsb_relation;
*buffer++ = (SCHAR) length;
name = (SCHAR*) n->str_data;
while (length--)
*buffer++ = *name++;
*buffer++ = gds_info_rsb_type;
*buffer++ = gds_info_rsb_sequential;
/* *buffer++ = gds__info_rsb_unknown; */
*buffer++ = gds_info_rsb_end;
break;
}
if (!OPT_access_path
(procedure->prc_request, buffer, *buffer_length,
reinterpret_cast < USHORT * >(&return_length)))
return FALSE;
if ((*buffer_length -= return_length) < 0)
return FALSE;
buffer += return_length;
break;
case rsb_first:
*buffer++ = gds_info_rsb_first;
break;
case rsb_skip:
*buffer++ = gds_info_rsb_skip;
break;
case rsb_boolean:
*buffer++ = gds_info_rsb_boolean;
break;
case rsb_union:
*buffer++ = gds_info_rsb_union;
break;
case rsb_aggregate:
*buffer++ = gds_info_rsb_aggregate;
break;
case rsb_merge:
*buffer++ = gds_info_rsb_merge;
break;
case rsb_ext_sequential:
*buffer++ = gds_info_rsb_ext_sequential;
break;
case rsb_ext_indexed:
*buffer++ = gds_info_rsb_ext_indexed;
break;
case rsb_ext_dbkey:
*buffer++ = gds_info_rsb_ext_dbkey;
break;
case rsb_left_cross:
*buffer++ = gds_info_rsb_left_cross;
break;
case rsb_select:
*buffer++ = gds_info_rsb_select;
break;
case rsb_sql_join:
*buffer++ = gds_info_rsb_sql_join;
break;
case rsb_simulate:
*buffer++ = gds_info_rsb_simulate;
break;
case rsb_sim_cross:
*buffer++ = gds_info_rsb_sim_cross;
break;
case rsb_once:
*buffer++ = gds_info_rsb_once;
break;
default:
*buffer++ = gds_info_rsb_unknown;
break;
}
/* dump out any sub-rsbs; for join-type rses like cross
and merge, dump out the count of streams first, then
loop through the substreams and dump them out */
switch (rsb->rsb_type) {
case rsb_cross:
*buffer++ = (UCHAR) rsb->rsb_count;
for (ptr = rsb->rsb_arg, end = ptr + rsb->rsb_count; ptr < end; ptr++)
if (!dump_rsb(request, *ptr, &buffer, buffer_length))
return FALSE;
break;
case rsb_union:
*buffer++ = rsb->rsb_count / 2;
for (ptr = rsb->rsb_arg, end = ptr + rsb->rsb_count; ptr < end; ptr++) {
if (!dump_rsb(request, *ptr, &buffer, buffer_length))
return FALSE;
ptr++;
}
break;
case rsb_merge:
*buffer++ = (SCHAR) rsb->rsb_count;
for (ptr = rsb->rsb_arg, end = ptr + rsb->rsb_count * 2; ptr < end;
ptr += 2)
if (!dump_rsb(request, *ptr, &buffer, buffer_length))
return FALSE;
break;
case rsb_left_cross:
*buffer++ = 2;
if (!dump_rsb
(request, rsb->rsb_arg[RSB_LEFT_outer], &buffer,
buffer_length)) return FALSE;
if (!dump_rsb
(request, rsb->rsb_arg[RSB_LEFT_inner], &buffer,
buffer_length)) return FALSE;
break;
default: /* Shut up compiler warnings */
break;
}
/* dump out the next rsb */
if (rsb->rsb_next)
if (!dump_rsb(request, rsb->rsb_next, &buffer, buffer_length))
return FALSE;
*buffer++ = gds_info_rsb_end;
*buffer_ptr = buffer;
return TRUE;
}
static BOOLEAN estimate_cost(TDBB tdbb,
OPT opt,
USHORT stream,
double *cost, double *resulting_cardinality)
{
/**************************************
*
* e s t i m a t e _ c o s t
*
**************************************
*
* Functional description
* Make an estimate of the cost to fetch a stream. The cost
* is a function of estimated cardinality of the relation, index
* selectivity, and total boolean selectivity. Since none of
* this information is available, the estimates are likely to
* be a bit weak. Return TRUE if the relation is index
* retrievable.
*
**************************************/
CSB csb;
IDX *idx;
JRD_NOD node;
USHORT indexes, i, unique, equalities, inequalities, index_hits, count;
SSHORT n;
ULONG inactivities[OPT_BITS];
double s, selectivity, cardinality, index_selectivity;
Opt::opt_repeat * tail, *opt_end;
csb_repeat *csb_tail;
DEV_BLKCHK(opt, type_opt);
SET_TDBB(tdbb);
csb = opt->opt_csb;
csb_tail = &csb->csb_rpt[stream];
csb_tail->csb_flags |= csb_active;
cardinality = MAX(csb_tail->csb_cardinality, 10);
index_selectivity = 1.0;
indexes = equalities = inequalities = index_hits = 0;
unique = FALSE;
get_inactivities(csb, inactivities);
/* Compute index selectivity. This involves finding the indices
to be utilized and making a crude guess of selectivities. */
if (opt->opt_count) {
idx = csb_tail->csb_idx;
for (i = 0; i < csb_tail->csb_indices; i++) {
n = 0;
clear_bounds(opt, idx);
opt_end = &opt->opt_rpt[opt->opt_count];
for (tail = opt->opt_rpt; tail < opt_end; tail++) {
node = tail->opt_conjunct;
if (!(tail->opt_flags & opt_used) &&
!(TEST_DEP_ARRAYS(tail->opt_dependencies, inactivities)))
n += match_index(tdbb, opt, stream, node, idx);
}
tail = opt->opt_rpt;
if (tail->opt_lower || tail->opt_upper) {
indexes++;
opt_end = opt->opt_rpt + idx->idx_count;
for (count = 0; tail < opt_end; tail++, count++)
if (!tail->opt_lower
|| tail->opt_lower != tail->opt_upper) break;
s = idx->idx_selectivity;
if (s <= 0 || s >= 1)
s = ESTIMATED_SELECTIVITY;
if (count == idx->idx_count) {
if (idx->idx_flags & idx_unique) {
unique = TRUE;
s = 1 / cardinality;
}
}
else
s *= INVERSE_ESTIMATE;
index_selectivity *= s;
index_hits += MAX(count, n);
}
idx = NEXT_IDX(idx->idx_rpt, idx->idx_count);
}
}
/* We now known the relation cardinality, the combined index selectivity,
and the number of index lookups required. From this we can compute the
cost of executing the record selection expression (cost of index lookups
plus the number of records fetched). */
if (indexes)
*cost = cardinality * index_selectivity + indexes * INDEX_COST;
else
*cost = cardinality;
/* Next, we need to estimate the number of records coming out of the
record stream. This is based on conjunctions without regard to whether
or not they were the result of index operations. */
opt_end = opt->opt_rpt + opt->opt_count;
for (tail = opt->opt_rpt; tail < opt_end; tail++) {
node = tail->opt_conjunct;
if (!(tail->opt_flags & opt_used) &&
!(TEST_DEP_ARRAYS(tail->opt_dependencies, inactivities))) {
if (node->nod_type == nod_eql)
++equalities;
else
++inequalities;
tail->opt_flags |= opt_used;
}
}
if ((n = inequalities + 3 * (equalities - index_hits)) > 0) {
selectivity = 0.3 / n;
if (selectivity > index_selectivity)
selectivity = index_selectivity;
}
else
selectivity = index_selectivity;
cardinality *= selectivity;
if (unique)
*resulting_cardinality = cardinality;
else
*resulting_cardinality = MAX(cardinality, 1.0);
csb_tail->csb_flags |= csb_active;
return (indexes != 0);
}
#ifdef EXPRESSION_INDICES
static BOOLEAN expression_equal(TDBB tdbb, JRD_NOD node1, JRD_NOD node2)
{
/**************************************
*
* e x p r e s s i o n _ e q u a l
*
**************************************
*
* Functional description
* Determine if two expression trees are the same for
* the purposes of matching one half of a boolean expression
* to an index.
*
**************************************/
DSC *desc1, *desc2;
DEV_BLKCHK(node1, type_nod);
DEV_BLKCHK(node2, type_nod);
SET_TDBB(tdbb);
if (!node1 || !node2)
BUGCHECK(303); /* msg 303 Invalid expression for evaluation */
if (node1->nod_type != node2->nod_type)
return FALSE;
switch (node1->nod_type) {
case nod_add:
case nod_multiply:
case nod_add2:
case nod_multiply2:
/* A+B is equivilant to B+A, ditto A*B==B*A */
/* Note: If one expression is A+B+C, but the other is B+C+A we won't
necessarily match them */
if (expression_equal(tdbb, node1->nod_arg[0], node2->nod_arg[1]) &&
expression_equal(tdbb, node1->nod_arg[1], node2->nod_arg[0]))
return TRUE;
/* Fall into ... */
case nod_subtract:
case nod_divide:
case nod_subtract2:
case nod_divide2:
case nod_concatenate:
if (expression_equal(tdbb, node1->nod_arg[0], node2->nod_arg[0]) &&
expression_equal(tdbb, node1->nod_arg[1], node2->nod_arg[1]))
return TRUE;
break;
case nod_rec_version:
case nod_dbkey:
if (node1->nod_arg[0] == node2->nod_arg[1])
return TRUE;
break;
case nod_field:
/* don't compare stream id's because we will use computable() to make sure the
field is in the same stream as the index; the stream id of the index expression
is always 0 so it isn't usable for comparison */
if (node1->nod_arg[e_fld_id] == node2->nod_arg[e_fld_id])
return TRUE;
break;
case nod_function:
if ((node1->nod_arg[e_fun_function] == node2->nod_arg[e_fun_function])
&& expression_equal(tdbb, node1->nod_arg[e_fun_args],
node2->nod_arg[e_fun_args]))
return TRUE;
break;
case nod_literal:
desc1 = EVL_expr(tdbb, node1);
desc2 = EVL_expr(tdbb, node2);
if (!MOV_compare(desc1, desc2))
return TRUE;
break;
case nod_null:
case nod_user_name:
case nod_current_time:
case nod_current_date:
case nod_current_timestamp:
return TRUE;
case nod_value_if:
case nod_substr:
if (expression_equal(tdbb, node1->nod_arg[0], node2->nod_arg[0]) &&
expression_equal(tdbb, node1->nod_arg[1], node2->nod_arg[1]) &&
expression_equal(tdbb, node1->nod_arg[2], node2->nod_arg[2]))
return TRUE;
break;
case nod_gen_id:
case nod_gen_id2:
if (node1->nod_arg[e_gen_id] == node2->nod_arg[e_gen_id]) {
return TRUE;
}
break;
case nod_negate:
case nod_upcase:
case nod_internal_info:
if (expression_equal(tdbb, node1->nod_arg[0], node2->nod_arg[0])) {
return TRUE;
}
break;
case nod_cast:
desc1 = &((FMT) node1->nod_arg[e_cast_fmt])->fmt_desc[0];
desc2 = &((FMT) node2->nod_arg[e_cast_fmt])->fmt_desc[0];
if (DSC_EQUIV(desc1, desc2) &&
expression_equal(tdbb, node1->nod_arg[0], node2->nod_arg[0])) {
return TRUE;
}
break;
case nod_extract:
if (node1->nod_arg[e_extract_part] == node2->nod_arg[e_extract_part]
&& expression_equal(tdbb, node1->nod_arg[e_extract_value],
node2->nod_arg[e_extract_value])) {
return TRUE;
}
break;
default:
break;
}
return FALSE;
}
#endif
static void find_best(TDBB tdbb,
OPT opt,
USHORT stream,
USHORT position,
UCHAR * streams,
JRD_NOD plan_node,
double cost,
double cardinality)
{
/**************************************
*
* f i n d _ b e s t
*
**************************************
*
* Functional description
* Find the best join from the passed "stream" to
* the remaining "streams" in the rse. This routine
* uses recursion to successively consider all
* possible join orders which use indexed
* relationships to form joins.
*
**************************************/
CSB csb;
UCHAR *ptr, *stream_end;
double position_cost, position_cardinality, new_cost, new_cardinality;
USHORT flag_vector[MAX_STREAMS+1], *fv;
BOOLEAN done;
IRL relationship;
Opt::opt_repeat * tail, *opt_end, *order_end, *stream_data;
SET_TDBB(tdbb);
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(plan_node, type_nod);
#ifdef OPT_DEBUG
/* this is used only in development so is not in the message file */
if (opt_debug_flag >= DEBUG_PUNT) {
ERR_post(gds_random, gds_arg_string, "punt", 0);
}
#endif
/* if a plan was specified, check that this order matches the order
that the user provided; this may seem like an ass-backwards way to
enforce ordering, but I think it is important to follow the same
code path for SET PLAN as for a normal optimization--it reduces
chances for bugs to be introduced, and forces the person maintaining
the optimizer to think about SET PLAN when new features are added --deej */
if (plan_node && (streams[position + 1] != stream)) {
return;
}
/* do some initializations */
csb = opt->opt_csb;
csb->csb_rpt[stream].csb_flags |= csb_active;
stream_end = &streams[1] + streams[0];
opt_end = opt->opt_rpt + MAX(opt->opt_count, csb->csb_n_stream);
opt->opt_rpt[position].opt_stream = stream;
++position;
order_end = opt->opt_rpt + position;
stream_data = opt->opt_rpt + stream;
/* Save the various flag bits from the optimizer block to reset its
state after each test. */
for (tail = opt->opt_rpt, fv = flag_vector; tail < opt_end; tail++) {
*fv++ = tail->opt_flags & (opt_stream_used | opt_used);
}
/* Compute delta and total estimate cost to fetch this stream */
estimate_cost(tdbb, opt, stream, &position_cost,
&position_cardinality);
new_cost = cost + cardinality * position_cost;
new_cardinality = position_cardinality * cardinality;
++opt->opt_combinations;
/* If the partial order is either longer than any previous partial order,
or the same length and cheap, save order as "best" */
if (position > opt->opt_best_count ||
(position == opt->opt_best_count && new_cost < opt->opt_best_cost)) {
opt->opt_best_count = position;
opt->opt_best_cost = new_cost;
for (tail = opt->opt_rpt; tail < order_end; tail++) {
tail->opt_best_stream = tail->opt_stream;
}
#ifdef OPT_DEBUG
if (opt_debug_flag >= DEBUG_CANDIDATE) {
print_order(opt, position, new_cardinality, new_cost);
}
}
else {
if (opt_debug_flag >= DEBUG_ALL) {
print_order(opt, position, new_cardinality, new_cost);
}
#endif
}
/* mark this stream as "used" in the sense that it is already included
in this particular proposed stream ordering */
stream_data->opt_flags |= opt_stream_used;
done = FALSE;
/* We need to prune the combinations to avoid spending all of our time
recursing through find_best(). Based on experimentation, the cost of
recursion becomes significant at about a 7 table join. Therefore,
make a simplifying assumption that if we have already seen a join
ordering that is lower cost than this one, give up. */
if (position > 4) {
tail = opt->opt_rpt + position;
/* If we are the new low-cost join ordering, record that
fact. Otherwise, give up. */
if (tail->opt_best_stream_cost == 0 ||
new_cost < tail->opt_best_stream_cost) {
tail->opt_best_stream_cost = new_cost;
}
else {
if (!plan_node) {
done = TRUE;
}
}
}
/* if we've used up all the streams there's no reason to go any further */
if (position == streams[0]) {
done = TRUE;
}
/* First, handle any streams that have direct unique indexed relationships to this
stream. If there are any, we won't consider (now) indirect relationships */
if (!done) {
for (relationship = stream_data->opt_relationships; relationship;
relationship = relationship->irl_next) {
if (relationship->irl_unique &&
(!(opt->
opt_rpt[relationship->irl_stream].
opt_flags & opt_stream_used))) {
for (ptr = streams + 1; ptr < stream_end; ptr++) {
if (*ptr == relationship->irl_stream) {
if (!plan_node) {
done = TRUE;
}
find_best(tdbb, opt, relationship->irl_stream,
position, streams, plan_node, new_cost,
new_cardinality);
break;
}
}
}
}
}
/* Next, handle any streams that have direct indexed relationships to this
stream. If there are any, we won't consider (now) indirect relationships */
if (!done) {
for (relationship = stream_data->opt_relationships; relationship;
relationship = relationship->irl_next) {
if (!
(opt->opt_rpt[relationship->irl_stream].
opt_flags & opt_stream_used)) {
for (ptr = streams + 1; ptr < stream_end; ptr++) {
if (*ptr == relationship->irl_stream) {
if (!plan_node) {
done = TRUE;
}
find_best(tdbb, opt, relationship->irl_stream,
position, streams, plan_node, new_cost,
new_cardinality);
break;
}
}
}
}
}
/* If there were no direct relationships, look for indirect relationships */
if (!done)
for (ptr = streams + 1; ptr < stream_end; ptr++)
if (!(opt->opt_rpt[*ptr].opt_flags & opt_stream_used) &&
check_relationship(opt, position, *ptr))
find_best(tdbb, opt, *ptr, position,
streams, plan_node, new_cost, new_cardinality);
/* Clean up from any changes made for compute the cost for this stream */
csb->csb_rpt[stream].csb_flags &= ~csb_active;
for (tail = opt->opt_rpt, fv = flag_vector; tail < opt_end; tail++, fv++)
tail->opt_flags &= *fv;
}
static JRD_NOD find_dbkey(JRD_NOD dbkey, USHORT stream, SLONG * position)
{
/**************************************
*
* f i n d _ d b k e y
*
**************************************
*
* Functional description
* Search a dbkey (possibly a concatenated one) for
* a dbkey for specified stream.
*
**************************************/
JRD_NOD dbkey_temp, *ptr, *end;
DEV_BLKCHK(dbkey, type_nod);
if (dbkey->nod_type == nod_dbkey) {
if ((USHORT) dbkey->nod_arg[0] == stream)
return dbkey;
else {
*position = *position + 1;
return NULL;
}
}
else if (dbkey->nod_type == nod_concatenate)
for (ptr = dbkey->nod_arg, end = ptr + dbkey->nod_count;
ptr < end; ptr++)
if ( (dbkey_temp = find_dbkey(*ptr, stream, position)) )
return dbkey_temp;
return NULL;
}
static USHORT find_order(TDBB tdbb,
OPT opt, UCHAR * streams, JRD_NOD plan_node)
{
/**************************************
*
* f i n d _ o r d e r
*
**************************************
*
* Functional description
* Given a set of streams, select the "best order" to join them.
* The "best order" is defined as longest, cheapest join order
* (length, of course, takes precedence over cost). The best
* order is developed and returned in the optimization block.
*
**************************************/
UCHAR *stream, *stream_end;
SET_TDBB(tdbb);
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(plan_node, type_nod);
opt->opt_best_count = 0;
/* if a plan was specified, the order is already
present in the streams vector, so we only want
to try one order */
if (plan_node)
stream_end = &streams[1] + 1;
else
stream_end = &streams[1] + streams[0];
/* Consider each stream as the leftmost stream in the join order;
for each stream, the best order from that stream is considered,
and the one which is best is placed into the opt block. Thus
at the end of this loop the opt block holds the best order */
for (stream = streams + 1; stream < stream_end; stream++)
find_best(tdbb, opt, *stream, 0, streams, plan_node, (double) 0,
(double) 1);
#ifdef OPT_DEBUG
if (opt_debug_flag >= DEBUG_BEST) {
Opt::opt_repeat * order_end, *tail;
order_end = opt->opt_rpt + opt->opt_best_count;
ib_fprintf(opt_debug_file,
"find_order() -- best_count: %2.2d, best_streams: ",
opt->opt_best_count); for (tail = opt->opt_rpt;
tail < order_end; tail++)
ib_fprintf(opt_debug_file, "%2.2d ", tail->opt_best_stream);
ib_fprintf(opt_debug_file,
"\n\t\t\tbest_cost: %g\tcombinations: %ld\n",
opt->opt_best_cost, opt->opt_combinations);
}
#endif
return opt->opt_best_count;
}
static void find_rsbs(RSB rsb, LLS * stream_list, LLS * rsb_list)
{
/**************************************
*
* f i n d _ r s b s
*
**************************************
*
* Functional description
* Find all rsbs at or below the current one that map
* to a single stream. Save the stream numbers in a list.
* For unions and aggregates also save the rsb pointer.
*
**************************************/
RSB *ptr, *end;
#ifdef DEV_BUILD
DEV_BLKCHK(rsb, type_rsb);
DEV_BLKCHK(*stream_list, type_lls);
if (rsb_list) {
DEV_BLKCHK(*rsb_list, type_lls);
}
#endif
if (!rsb) {
return;
}
switch (rsb->rsb_type) {
case rsb_union:
case rsb_aggregate:
if (rsb_list)
LLS_PUSH(rsb, rsb_list);
case rsb_indexed:
case rsb_sequential:
case rsb_ext_sequential:
case rsb_ext_indexed:
case rsb_procedure:
/* No need to go any farther down with these */
LLS_PUSH((BLK) rsb->rsb_stream, stream_list);
return;
case rsb_cross:
/* Loop through the sub-streams */
for (ptr = rsb->rsb_arg, end = ptr + rsb->rsb_count; ptr < end; ptr++)
find_rsbs(*ptr, stream_list, rsb_list);
break;
case rsb_left_cross:
find_rsbs(rsb->rsb_arg[RSB_LEFT_outer], stream_list, rsb_list);
find_rsbs(rsb->rsb_arg[RSB_LEFT_inner], stream_list, rsb_list);
break;
case rsb_merge:
/* Loop through the sub-streams */
for (ptr = rsb->rsb_arg, end = ptr + rsb->rsb_count * 2;
ptr < end; ptr += 2)
find_rsbs(*ptr, stream_list, rsb_list);
break;
default: /* Shut up compiler warnings */
break;
}
find_rsbs(rsb->rsb_next, stream_list, rsb_list);
}
static void find_used_streams(RSB rsb, UCHAR * streams)
{
/**************************************
*
* f i n d _ u s e d _ s t r e a m s
*
**************************************
*
* Functional description
* Find all streams through the given rsb
* and add them to the stream list.
*
**************************************/
RSB *ptr, *end;
USHORT i, stream;
BOOLEAN found;
if (!(rsb)) {
return;
}
found = FALSE;
switch (rsb->rsb_type) {
case rsb_aggregate:
case rsb_ext_indexed:
case rsb_ext_sequential:
case rsb_indexed:
case rsb_navigate:
case rsb_procedure:
case rsb_sequential:
case rsb_union:
stream = rsb->rsb_stream;
found = TRUE;
break;
case rsb_cross:
for (ptr = rsb->rsb_arg, end = ptr + rsb->rsb_count; ptr < end; ptr++) {
find_used_streams(*ptr, streams);
}
break;
case rsb_merge:
/*for (ptr = rsb->rsb_arg, end = ptr + rsb->rsb_count * 2; ptr < end; ptr += 2) {
find_used_streams(*ptr, streams);
}*/
// Nothing
break;
case rsb_left_cross:
find_used_streams(rsb->rsb_arg[RSB_LEFT_inner], streams);
find_used_streams(rsb->rsb_arg[RSB_LEFT_outer], streams);
break;
default: /* Shut up compiler warnings */
break;
}
if (rsb->rsb_next) {
find_used_streams(rsb->rsb_next, streams);
}
if (found) {
found = FALSE;
for (i = 1; i <= streams[0]; i++) {
if (stream == streams[i]) {
found = TRUE;
break;
}
}
if (!found) {
streams[++streams[0]] = stream;
}
}
}
static void form_rivers(TDBB tdbb,
OPT opt,
UCHAR * streams,
LLS * river_stack,
JRD_NOD * sort_clause,
JRD_NOD * project_clause, JRD_NOD plan_clause)
{
/**************************************
*
* f o r m _ r i v e r s
*
**************************************
*
* Functional description
* Form streams into rivers according
* to the user-specified plan.
*
**************************************/
JRD_NOD *ptr, *end, plan_node, relation_node;
USHORT count;
UCHAR temp[MAX_STREAMS+1];
SET_TDBB(tdbb);
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(*river_stack, type_lls);
if (sort_clause) {
DEV_BLKCHK(*sort_clause, type_nod);
}
if (project_clause) {
DEV_BLKCHK(*project_clause, type_nod);
}
DEV_BLKCHK(plan_clause, type_nod);
temp[0] = 0;
count = plan_clause->nod_count;
/* this must be a join or a merge node, so go through
the substreams and place them into the temp vector
for formation into a river */
for (ptr = plan_clause->nod_arg, end = ptr + count; ptr < end; ptr++) {
plan_node = *ptr;
if (plan_node->nod_type == nod_merge
|| plan_node->nod_type == nod_join) {
form_rivers(tdbb, opt, streams, river_stack, sort_clause,
project_clause, plan_node);
continue;
}
/* at this point we must have a retrieval node, so put
the stream into the river */
temp[0]++;
relation_node = plan_node->nod_arg[e_retrieve_relation];
temp[temp[0]] = (UCHAR) relation_node->nod_arg[e_rel_stream];
}
/* just because the user specified a join does not mean that
we are able to form a river; thus form as many rivers out
of the join are as necessary to exhaust the streams */
do
count = find_order(tdbb, opt, temp, plan_node);
while (form_river
(tdbb, opt, count, streams, temp, river_stack, sort_clause,
project_clause, 0));
}
static BOOLEAN form_river(TDBB tdbb,
OPT opt,
USHORT count,
UCHAR * streams,
UCHAR * temp,
LLS * river_stack,
JRD_NOD * sort_clause,
JRD_NOD * project_clause, JRD_NOD plan_clause)
{
/**************************************
*
* f o r m _ r i v e r
*
**************************************
*
* Functional description
* Form streams into rivers (combinations of streams).
*
**************************************/
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(*river_stack, type_lls);
if (sort_clause) {
DEV_BLKCHK(*sort_clause, type_nod);
}
if (project_clause) {
DEV_BLKCHK(*project_clause, type_nod);
}
DEV_BLKCHK(plan_clause, type_nod);
SET_TDBB(tdbb);
CSB csb = opt->opt_csb;
/* Allocate a river block and move the best order into it */
RIV river = FB_NEW_RPT(*tdbb->tdbb_default, count) riv();
LLS_PUSH(river, river_stack);
river->riv_count = (UCHAR) count;
RSB rsb;
RSB* ptr;
if (count == 1) {
rsb = NULL;
ptr = &river->riv_rsb;
}
else {
river->riv_rsb = rsb = FB_NEW_RPT(*tdbb->tdbb_default, count) Rsb();
rsb->rsb_type = rsb_cross;
rsb->rsb_count = count;
rsb->rsb_impure = CMP_impure(csb, sizeof(struct irsb));
ptr = rsb->rsb_arg;
}
UCHAR* stream = river->riv_streams;
Opt::opt_repeat* opt_end = opt->opt_rpt + count;
if (count != streams[0]) {
sort_clause = project_clause = NULL;
}
Opt::opt_repeat* tail;
for (tail = opt->opt_rpt; tail < opt_end; tail++, stream++, ptr++) {
*stream = (UCHAR) tail->opt_best_stream;
*ptr =
gen_retrieval(tdbb, opt, *stream, sort_clause, project_clause,
false, false, NULL);
sort_clause = project_clause = NULL;
}
/* determine whether the rsb we just made should be marked as a projection */
if (rsb && rsb->rsb_arg[0]
&& ((RSB) rsb->rsb_arg[0])->rsb_flags & rsb_project)
{
rsb->rsb_flags |= rsb_project;
}
set_made_river(opt, river);
set_inactive(opt, river);
/* Reform "temp" from streams not consumed */
stream = temp + 1;
UCHAR* end_stream = stream + temp[0];
if (!(temp[0] -= count)) {
return FALSE;
}
for (UCHAR* t2 = stream; t2 < end_stream; t2++) {
for (tail = opt->opt_rpt; tail < opt_end; tail++) {
if (*t2 == tail->opt_best_stream) {
goto used;
}
}
*stream++ = *t2;
used:;
}
return TRUE;
}
static RSB gen_aggregate(TDBB tdbb, OPT opt, JRD_NOD node)
{
/**************************************
*
* g e n _ a g g r e g a t e
*
**************************************
*
* Functional description
* Generate an RSB (Record Source Block) for each aggregate operation.
* Generate an ASB (Aggregate Sort Block) for each DISTINCT aggregate.
*
**************************************/
JRD_NOD operator_;
JRD_NOD* ptr;
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(node, type_nod);
SET_TDBB(tdbb);
CSB csb = opt->opt_csb;
RSE rse = (RSE) node->nod_arg[e_agg_rse];
rse->rse_sorted = node->nod_arg[e_agg_group];
// try to optimize MAX and MIN to use an index; for now, optimize
// only the simplest case, although it is probably possible
// to use an index in more complex situations
JRD_NOD map = node->nod_arg[e_agg_map];
if ((map->nod_count == 1) &&
(ptr = map->nod_arg) &&
(operator_ = (*ptr)->nod_arg[e_asgn_from]) &&
(operator_->nod_type == nod_agg_min ||
operator_->nod_type == nod_agg_max))
{
/* generate a sort block which the optimizer will try to map to an index */
JRD_NOD aggregate = PAR_make_node(tdbb, 2);
aggregate->nod_type = nod_sort;
aggregate->nod_count = 1;
aggregate->nod_arg[0] = operator_->nod_arg[e_asgn_from];
/* in the max case, flag the sort as descending */
if (operator_->nod_type == nod_agg_max) {
aggregate->nod_arg[1] = (JRD_NOD) TRUE;
}
rse->rse_aggregate = aggregate;
}
// allocate and optimize the record source block
RSB rsb = FB_NEW_RPT(*tdbb->tdbb_default, 1) Rsb();
rsb->rsb_type = rsb_aggregate;
assert((int)node->nod_arg[e_agg_stream] <= MAX_STREAMS);
assert((int)node->nod_arg[e_agg_stream] <= MAX_UCHAR);
rsb->rsb_stream = (UCHAR) node->nod_arg[e_agg_stream];
rsb->rsb_format = csb->csb_rpt[rsb->rsb_stream].csb_format;
rsb->rsb_next = OPT_compile(tdbb, csb, rse, NULL);
rsb->rsb_arg[0] = (RSB) node;
rsb->rsb_impure = CMP_impure(csb, sizeof(struct irsb));
if (rse->rse_aggregate)
{
// The rse_aggregate is still set. That means the optimizer
// was able to match the field to an index, so flag that fact
// so that it can be handled in EVL_group
if (operator_->nod_type == nod_agg_min) {
operator_->nod_type = nod_agg_min_indexed;
} else if (operator_->nod_type == nod_agg_max) {
operator_->nod_type = nod_agg_max_indexed;
}
}
// Now generate a separate ASB (Aggregate Sort Block) for each
// distinct operation;
// note that this should be optimized to use indices if possible
DSC descriptor;
DSC* desc = &descriptor;
JRD_NOD* end;
for (ptr = map->nod_arg, end = ptr + map->nod_count; ptr < end; ptr++)
{
JRD_NOD from = (*ptr)->nod_arg[e_asgn_from];
if ((from->nod_type == nod_agg_count_distinct) ||
(from->nod_type == nod_agg_total_distinct) ||
(from->nod_type == nod_agg_total_distinct2) ||
(from->nod_type == nod_agg_average_distinct2) ||
(from->nod_type == nod_agg_average_distinct))
{
const USHORT count = asb_delta + 1 +
(sizeof(SKD) + sizeof(JRD_NOD *) - 1) / sizeof(JRD_NOD*);
ASB asb = (ASB) PAR_make_node(tdbb, count);
asb->nod_type = nod_asb;
asb->nod_count = 0;
/* build the sort key definition. Turn varying text and
cstrings into text */
CMP_get_desc(tdbb, csb, from->nod_arg[0], desc);
if (desc->dsc_dtype == dtype_varying) {
desc->dsc_dtype = dtype_text;
desc->dsc_length -= sizeof(USHORT);
}
else if (desc->dsc_dtype == dtype_cstring) {
desc->dsc_dtype = dtype_text;
desc->dsc_length--;
}
SKD* sort_key = asb->asb_key_desc = (SKD*) asb->asb_key_data;
sort_key->skd_offset = 0;
assert(desc->dsc_dtype >= 0 &&
desc->dsc_dtype < FB_NELEM(sort_dtypes));
sort_key->skd_dtype = sort_dtypes[desc->dsc_dtype];
/* as it is legal to have skd_dtype = 0
I have removed these asserts, to avoid
server restarts in debug mode.
FSG 18.Dec.2000
*/
/*assert (sort_key->skd_dtype != 0); */
sort_key->skd_length = desc->dsc_length;
sort_key->skd_flags = SKD_ascending;
asb->nod_impure = CMP_impure(csb, sizeof(struct iasb));
asb->asb_desc = *desc;
from->nod_arg[1] = (JRD_NOD) asb;
from->nod_count = 2;
}
}
return rsb;
}
static RSB gen_boolean(TDBB tdbb, OPT opt, RSB prior_rsb, JRD_NOD node)
{
/**************************************
*
* g e n _ b o o l e a n
*
**************************************
*
* Functional description
* Compile and optimize a record selection expression into a
* set of record source blocks (rsb's).
*
**************************************/
CSB csb;
RSB rsb;
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(node, type_nod);
DEV_BLKCHK(prior_rsb, type_rsb);
SET_TDBB(tdbb);
csb = opt->opt_csb;
rsb = FB_NEW_RPT(*tdbb->tdbb_default, 1) Rsb();
rsb->rsb_count = 1;
rsb->rsb_type = rsb_boolean;
rsb->rsb_next = prior_rsb;
rsb->rsb_arg[0] = (RSB) node;
rsb->rsb_impure = CMP_impure(csb, sizeof(struct irsb));
return rsb;
}
static RSB gen_first(TDBB tdbb, OPT opt, RSB prior_rsb, JRD_NOD node)
{
/**************************************
*
* g e n _ f i r s t
*
**************************************
*
* Functional description
* Compile and optimize a record selection expression into a
* set of record source blocks (rsb's).
*
*
* NOTE: The rsb_first node MUST appear in the rsb list before the
* rsb_skip node. The calling code MUST call gen_first after
* gen_skip.
*
**************************************/
CSB csb;
RSB rsb;
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(prior_rsb, type_rsb);
DEV_BLKCHK(node, type_nod);
SET_TDBB(tdbb);
csb = opt->opt_csb;
rsb = FB_NEW_RPT(*tdbb->tdbb_default, 1) Rsb();
rsb->rsb_count = 1;
rsb->rsb_type = rsb_first;
rsb->rsb_next = prior_rsb;
rsb->rsb_arg[0] = (RSB) node;
rsb->rsb_impure = CMP_impure(csb, sizeof(struct irsb_first_n));
return rsb;
}
static void gen_join(TDBB tdbb,
OPT opt,
UCHAR* streams,
LLS* river_stack,
JRD_NOD* sort_clause,
JRD_NOD* project_clause,
JRD_NOD plan_clause)
{
/**************************************
*
* g e n _ j o i n
*
**************************************
*
* Functional description
* Find all indexed relationships between streams,
* then form streams into rivers (combinations of
* streams).
*
**************************************/
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(*river_stack, type_lls);
DEV_BLKCHK(*sort_clause, type_nod);
DEV_BLKCHK(*project_clause, type_nod);
DEV_BLKCHK(plan_clause, type_nod);
SET_TDBB(tdbb);
DBB dbb = tdbb->tdbb_database;
CSB csb = opt->opt_csb;
if (!streams[0]) {
return;
}
/* If there is only a single stream, don't bother with a join */
if (streams[0] == 1)
{
/* if a nod_cardinality references this stream,
compute the cardinality even though we don't
need it to optimize retrieval */
csb_repeat* csb_tail = &csb->csb_rpt[streams[1]];
assert(csb_tail);
if (csb_tail->csb_flags & csb_compute)
{
JRD_REL relation = csb_tail->csb_relation;
assert(relation);
FMT format = CMP_format(tdbb, csb, streams[1]);
assert(format);
csb_tail->csb_cardinality =
(float) DPM_data_pages(tdbb, relation) *
dbb->dbb_page_size / format->fmt_length;
}
RIV river = FB_NEW_RPT(*tdbb->tdbb_default, 1) riv();
river->riv_count = 1;
assert(csb->csb_rpt[streams[1]].csb_relation);
river->riv_rsb =
gen_retrieval(tdbb, opt, streams[1], sort_clause, project_clause,
false, false, NULL);
river->riv_streams[0] = streams[1];
LLS_PUSH(river, river_stack);
return;
}
/* Compute cardinality and indexed relationships for all streams */
const UCHAR* end_stream = streams + 1 + streams[0];
for (UCHAR* stream = streams + 1; stream < end_stream; stream++)
{
csb_repeat* csb_tail = &csb->csb_rpt[*stream];
assert(csb_tail);
JRD_REL relation = csb_tail->csb_relation;
assert(relation);
FMT format = CMP_format(tdbb, csb, *stream);
/* if this is an external file, set an arbitrary cardinality;
if a plan was specified, don't bother computing cardinality;
otherwise give a rough estimate based on the number of data
pages times the estimated number of records per page -- note
this is an upper limit since all pages are probably not full
and many of the records on page may be back versions */
if (relation->rel_file)
csb_tail->csb_cardinality = (float) 10000;
else if (plan_clause)
csb_tail->csb_cardinality = (float) 0;
else
{
assert(format);
csb_tail->csb_cardinality =
(float) DPM_data_pages(tdbb, relation) *
dbb->dbb_page_size / format->fmt_length;
}
/* find indexed relationships from this stream to every other stream */
Opt::opt_repeat* tail = opt->opt_rpt + *stream;
csb_tail->csb_flags |= csb_active;
for (UCHAR* t2 = streams + 1; t2 < end_stream; t2++)
{
if (*t2 != *stream)
{
csb_repeat* csb_tail2 = &csb->csb_rpt[*t2];
csb_tail2->csb_flags |= csb_active;
IRL relationship = indexed_relationship(tdbb, opt, *t2);
if (relationship)
{
relationship->irl_next = tail->opt_relationships;
tail->opt_relationships = relationship;
relationship->irl_stream = *t2;
}
csb_tail2->csb_flags &= ~csb_active;
}
}
csb_tail->csb_flags &= ~csb_active;
#ifdef OPT_DEBUG
if (opt_debug_flag >= DEBUG_RELATIONSHIPS)
{
ib_fprintf(opt_debug_file,
"gen_join () -- relationships from stream %2.2d: ",
*stream);
for (IRL relationship = tail->opt_relationships;
relationship;
relationship = relationship->irl_next)
{
ib_fprintf(opt_debug_file, "%2.2d %s ",
relationship->irl_stream,
(relationship->irl_unique) ? "(unique)" : "");
}
ib_fprintf(opt_debug_file, "\n");
}
#endif
}
/* if the user specified a plan, force a join order;
otherwise try to find one */
if (plan_clause)
{
form_rivers(tdbb, opt, streams, river_stack, sort_clause,
project_clause, plan_clause);
}
else
{
/* copy the streams vector to a temporary space to be used
to form rivers out of streams */
USHORT count;
UCHAR temp[MAX_STREAMS+1];
MOVE_FAST(streams, temp, streams[0] + 1);
do
count = find_order(tdbb, opt, temp, 0);
while (form_river
(tdbb, opt, count, streams, temp, river_stack, sort_clause,
project_clause, 0));
}
}
static RSB gen_navigation(TDBB tdbb,
OPT opt,
USHORT stream,
JRD_REL relation, STR alias, IDX * idx, JRD_NOD * sort_ptr)
{
/**************************************
*
* g e n _ n a v i g a t i o n
*
**************************************
*
* Functional description
* See if a navigational walk of an index is in order. If so,
* generate the appropriate RSB and zap the sort pointer. If
* not, return NULL. Prior to ODS7, missing values sorted in
* the wrong place for ascending indices, so don't use them.
*
**************************************/
JRD_NOD node, *ptr, *end, sort;
idx::idx_repeat * idx_tail;
#ifdef SCROLLABLE_CURSORS
RSE_GET_MODE last_mode, mode;
#endif
SET_TDBB(tdbb);
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(relation, type_rel);
DEV_BLKCHK(alias, type_str);
DEV_BLKCHK(*sort_ptr, type_nod);
/* Check sort order against index. If they don't match, give up and
go home. Also don't bother if we have a non-unique index.
This is because null values aren't placed in a "good" spot in
the index in versions prior to V3.2. */
sort = *sort_ptr;
/* if the number of fields in the sort is greater than the number of
fields in the index, the index will not be used to optimize the
sort--note that in the case where the first field is unique, this
could be optimized, since the sort will be performed correctly by
navigating on a unique index on the first field--deej */
if (sort->nod_count > idx->idx_count)
return NULL;
/* not sure the significance of this magic number; if it's meant to
signify that we shouldn't navigate on a system table, our catalog
has grown beyond 16 tables--it doesn't seem like a problem
to allow navigating through system tables, so I won't bump the
number up, but I'll leave it at 16 for safety's sake--deej */
if (relation->rel_id <= 16)
return NULL;
/* if the user-specified access plan for this request didn't
mention this index, forget it */
if ((idx->idx_runtime_flags & idx_plan_dont_use) &&
!(idx->idx_runtime_flags & idx_plan_navigate))
return NULL;
/* check to see if the fields in the sort match the fields in the index
in the exact same order--we used to check for ascending/descending prior
to SCROLLABLE_CURSORS, but now descending sorts can use ascending indices
and vice versa */
#ifdef SCROLLABLE_CURSORS
last_mode = RSE_get_next;
#endif
for (ptr = sort->nod_arg, end = ptr + sort->nod_count, idx_tail =
idx->idx_rpt; ptr < end; ptr++, idx_tail++) {
node = *ptr;
if (node->nod_type != nod_field
|| (USHORT) node->nod_arg[e_fld_stream] != stream
|| (USHORT) node->nod_arg[e_fld_id] != idx_tail->idx_field
|| ptr[2*sort->nod_count] /* do not use index if NULLS FIRST is used */
#ifdef SCROLLABLE_CURSORS
)
#else
|| (ptr[sort->nod_count]
&& !(idx->idx_flags & idx_descending))
|| (!ptr[sort->nod_count]
&& (idx->idx_flags & idx_descending)) )
#endif
return NULL;
#ifdef SCROLLABLE_CURSORS
/* determine whether we ought to navigate backwards or forwards through
the index--we can't allow navigating one index in two different directions
on two different fields at the same time! */
mode = ((ptr[sort->nod_count] && !(idx->idx_flags & idx_descending))
|| (!ptr[sort->nod_count]
&& (idx->
idx_flags & idx_descending))) ? RSE_get_backward :
RSE_get_forward;
if (last_mode == RSE_get_next)
last_mode = mode;
else if (last_mode != mode)
return NULL;
#endif
}
/* Looks like we can do a navigational walk. Flag that
we have used this index for navigation, and allocate
a navigational rsb for it. */
*sort_ptr = NULL;
idx->idx_runtime_flags |= idx_navigate;
return gen_nav_rsb(tdbb, opt, stream, relation, alias, idx
#ifdef SCROLLABLE_CURSORS
, mode
#endif
);
}
static RSB gen_nav_rsb(TDBB tdbb,
OPT opt,
USHORT stream, JRD_REL relation, STR alias, IDX * idx
#ifdef SCROLLABLE_CURSORS
, RSE_GET_MODE mode
#endif
)
{
/**************************************
*
* g e n _ n a v _ r s b
*
**************************************
*
* Functional description
* Generate a navigational rsb, either
* for a compile or for a set index.
*
**************************************/
RSB rsb;
USHORT key_length, size;
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(relation, type_rel);
DEV_BLKCHK(alias, type_str);
SET_TDBB(tdbb);
key_length = ROUNDUP(BTR_key_length(relation, idx), sizeof(SLONG));
rsb = FB_NEW_RPT(*tdbb->tdbb_default, RSB_NAV_count) Rsb();
rsb->rsb_type = rsb_navigate;
rsb->rsb_relation = relation;
rsb->rsb_stream = (UCHAR) stream;
rsb->rsb_alias = alias;
rsb->rsb_arg[RSB_NAV_index] =
(RSB) OPT_make_index(tdbb, opt, relation, idx);
rsb->rsb_arg[RSB_NAV_key_length] = (RSB) (SLONG) key_length;
#ifdef SCROLLABLE_CURSORS
/* indicate that the index needs to be navigated in a mirror-image
fashion; that when the user wants to go backwards we actually go
forwards and vice versa */
if (mode == RSE_get_backward)
rsb->rsb_flags |= rsb_descending;
#endif
/* if this is a blr_stream, adjust the allocated impure area
to be based on the maximum key size so that the index may be
reset at any time to another index of larger key length
without adjusting the impure area offsets */
if (opt->opt_g_flags & opt_g_stream)
key_length = MAX_KEY;
size = nav_rsb_size(rsb, key_length, 0);
rsb->rsb_impure = CMP_impure(opt->opt_csb, size);
return rsb;
}
static RSB gen_outer(TDBB tdbb,
OPT opt,
RSE rse,
LLS river_stack, JRD_NOD * sort_clause, JRD_NOD * project_clause)
{
/**************************************
*
* g e n _ o u t e r
*
**************************************
*
* Functional description
* Generate a top level outer join. The "outer" and "inner"
* sub-streams must be handled differently from each other.
* The inner is like other streams. The outer stream isn't
* because conjuncts may not eliminate records from the
* stream. They only determine if a join with an inner
* stream record is to be attempted.
*
**************************************/
RSB rsb;
struct {
RSB stream_rsb;
USHORT stream_num;
} stream_o, stream_i, *stream_ptr[2];
RIV river;
JRD_NOD node, boolean, inner_boolean;
SSHORT i;
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(rse, type_nod);
DEV_BLKCHK(river_stack, type_lls);
DEV_BLKCHK(*sort_clause, type_nod);
SET_TDBB(tdbb);
/* Determine which stream should be outer and which is inner.
In the case of a left join, the syntactically left stream is the
outer, and the right stream is the inner. For all others, swap
the sense of inner and outer, though for a full join it doesn't
matter and we should probably try both orders to see which is
more efficient. */
if (rse->rse_jointype != blr_left) {
stream_ptr[1] = &stream_o;
stream_ptr[0] = &stream_i;
}
else {
stream_ptr[0] = &stream_o;
stream_ptr[1] = &stream_i;
}
/* Loop through the outer join sub-streams in
reverse order because rivers may have been PUSHed */
for (i = 1; i >= 0; i--)
{
node = rse->rse_relation[i];
if (node->nod_type == nod_union ||
node->nod_type == nod_aggregate ||
node->nod_type == nod_procedure || node->nod_type == nod_rse)
{
river = (RIV) LLS_POP(&river_stack);
stream_ptr[i]->stream_rsb = river->riv_rsb;
}
else
{
stream_ptr[i]->stream_rsb = NULL;
stream_ptr[i]->stream_num =
(USHORT) node->nod_arg[STREAM_INDEX(node)];
}
}
/* Generate rsbs for the sub-streams. For the left sub-stream
we also will get a boolean back */
boolean = inner_boolean = NULL;
if (!stream_o.stream_rsb) {
stream_o.stream_rsb =
gen_retrieval(tdbb, opt, stream_o.stream_num, sort_clause,
project_clause, true, false, &boolean);
}
/* in the case of a full join, we must make sure we don't exclude record from
the inner stream; otherwise just retrieve it as we would for an inner join */
if (!stream_i.stream_rsb)
{
const bool bFullJoin = rse->rse_jointype == blr_full;
const bool bOuter = bFullJoin;
JRD_NOD* ppNod = bFullJoin ? &inner_boolean : 0;
stream_i.stream_rsb =
gen_retrieval(tdbb,
opt,
stream_i.stream_num,
sort_clause,
project_clause,
bOuter,
true,
ppNod);
}
/* generate a parent boolean rsb for any remaining booleans that
were not satisfied via an index lookup */
stream_i.stream_rsb =
gen_residual_boolean(tdbb, opt, stream_i.stream_rsb);
/* Allocate and fill in the rsb */
rsb = FB_NEW_RPT(*tdbb->tdbb_default, RSB_LEFT_count) Rsb();
rsb->rsb_type = rsb_left_cross;
rsb->rsb_count = 2;
rsb->rsb_impure = CMP_impure(opt->opt_csb, sizeof(struct irsb));
rsb->rsb_arg[RSB_LEFT_outer] = stream_o.stream_rsb;
rsb->rsb_arg[RSB_LEFT_inner] = stream_i.stream_rsb;
rsb->rsb_arg[RSB_LEFT_boolean] = (RSB) boolean;
rsb->rsb_arg[RSB_LEFT_streams] = NULL;
rsb->rsb_arg[RSB_LEFT_rsbs] = NULL;
rsb->rsb_arg[RSB_LEFT_inner_boolean] = (RSB) inner_boolean;
rsb->rsb_arg[RSB_LEFT_inner_streams] = NULL;
/* find all the outer and inner substreams and push them on a stack. */
find_rsbs(stream_i.stream_rsb,
(LLS *) & rsb->rsb_arg[RSB_LEFT_streams],
(LLS *) & rsb->rsb_arg[RSB_LEFT_rsbs]);
if (rse->rse_jointype == blr_full)
find_rsbs(stream_o.stream_rsb,
(LLS *) & rsb->rsb_arg[RSB_LEFT_inner_streams], NULL);
return rsb;
}
static RSB gen_procedure(TDBB tdbb, OPT opt, JRD_NOD node)
{
/**************************************
*
* g e n _ p r o c e d u r e
*
**************************************
*
* Functional description
* Compile and optimize a record selection expression into a
* set of record source blocks (rsb's).
*
**************************************/
CSB csb;
JRD_PRC procedure;
RSB rsb;
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(node, type_nod);
SET_TDBB(tdbb);
csb = opt->opt_csb;
procedure = MET_lookup_procedure_id(tdbb,
(SSHORT)node->nod_arg[e_prc_procedure], FALSE, FALSE, 0);
rsb = FB_NEW_RPT(*tdbb->tdbb_default, RSB_PRC_count) Rsb();
rsb->rsb_type = rsb_procedure;
rsb->rsb_stream = (UCHAR) node->nod_arg[e_prc_stream];
rsb->rsb_procedure = procedure;
rsb->rsb_impure = CMP_impure(csb, sizeof(struct irsb_procedure));
rsb->rsb_arg[RSB_PRC_inputs] = (RSB) node->nod_arg[e_prc_inputs];
rsb->rsb_arg[RSB_PRC_in_msg] = (RSB) node->nod_arg[e_prc_in_msg];
return rsb;
}
static RSB gen_residual_boolean(TDBB tdbb, OPT opt, RSB prior_rsb)
{
/**************************************
*
* g e n _ r e s i d u a l _ b o o l e a n
*
**************************************
*
* Functional description
* Pick up any residual boolean remaining,
* meaning those that have not been used
* as part of some join. These booleans
* must still be applied to the result stream.
*
**************************************/
JRD_NOD node, boolean;
Opt::opt_repeat * tail, *opt_end;
SET_TDBB(tdbb);
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(prior_rsb, type_rsb);
boolean = NULL;
opt_end = opt->opt_rpt + opt->opt_count;
for (tail = opt->opt_rpt; tail < opt_end; tail++) {
node = tail->opt_conjunct;
if (!(tail->opt_flags & opt_used)) {
compose(&boolean, node, nod_and);
tail->opt_flags |= opt_used;
}
}
if (!boolean)
return prior_rsb;
return gen_boolean(tdbb, opt, prior_rsb, boolean);
}
static RSB gen_retrieval(TDBB tdbb,
OPT opt,
SSHORT stream,
JRD_NOD* sort_ptr,
JRD_NOD* project_ptr,
bool outer_flag,
bool inner_flag,
JRD_NOD* return_boolean)
{
/**************************************
*
* g e n _ r e t r i e v a l
*
**************************************
*
* Functional description
* Compile and optimize a record selection expression into a
* set of record source blocks (rsb's).
*
**************************************/
IDX *idx;
JRD_NOD node, opt_boolean;
SSHORT i, j, count, position;
Opt::opt_repeat *tail, *idx_tail, *idx_end;
BOOLEAN full = FALSE;
SET_TDBB(tdbb);
#ifdef DEV_BUILD
DEV_BLKCHK(opt, type_opt);
if (sort_ptr) {
DEV_BLKCHK(*sort_ptr, type_nod);
}
if (project_ptr) {
DEV_BLKCHK(*project_ptr, type_nod);
}
if (return_boolean) {
DEV_BLKCHK(*return_boolean, type_nod);
}
#endif
/* since a full outer join is a special case for us, as we have 2 outer
* streams, recoginze this condition and set the full flag, also reset the
* inner flag. This condition is only statisfied for the second stream in
* the full join. This condition is only set from the call in gen_outer() in
* case of a full join.
*/
if ((inner_flag == TRUE) && (outer_flag == TRUE)) {
/* the inner flag back to false and set the full flag */
inner_flag = FALSE;
full = TRUE;
}
CSB csb = opt->opt_csb;
csb_repeat* csb_tail = &csb->csb_rpt[stream];
JRD_REL relation = csb_tail->csb_relation;
assert(relation);
STR alias = make_alias(tdbb, csb, csb_tail);
csb_tail->csb_flags |= csb_active;
/* bug #8180 reported by Bill Karwin: when a DISTINCT and an ORDER BY
are done on different fields, and the ORDER BY can be mapped to an
index, then the records are returned in the wrong order because the
DISTINCT sort is performed after the navigational walk of the index;
for that reason, we need to de-optimize this case so that the ORDER
BY does not use an index; if desired, we could re-optimize by doing
the DISTINCT first, using a sparse bit map to store the DISTINCT
records, then perform the navigational walk for the ORDER BY and
filter the records out with the sparse bitmap. However, that is a
task for another day. --deej */
/* Bug #8958: comment out anything having to do with mapping a DISTINCT
to an index, for now. The fix for this bug was going to be so involved
that it made more sense to deoptimize this case for the time being until
we can determine whether it really makes sense to optimize a DISTINCT,
or for that matter, and ORDER BY, via an index--there is a case
to be made that it is a deoptimization and more testing needs to be done
to determine that; see more comments in the bug description
*/
if (sort_ptr && *sort_ptr && project_ptr && *project_ptr) {
sort_ptr = NULL;
}
/* Time to find inversions. For each index on the relation
match all unused booleans against the index looking for upper
and lower bounds that can be computed by the index. When
all unused conjunctions are exhausted, see if there is enough
information for an index retrieval. If so, build up an
inversion component of the boolean. */
JRD_NOD inversion = NULL;
Opt::opt_repeat* opt_end =
opt->opt_rpt + (inner_flag ? opt->opt_count : opt->opt_parent_count);
RSB rsb = NULL;
if (relation->rel_file)
{
rsb = EXT_optimize(opt, stream, sort_ptr ? sort_ptr : project_ptr);
}
else if (opt->opt_parent_count || (sort_ptr && *sort_ptr)
/***|| (project_ptr && *project_ptr)***/
)
{
/* we want to start with indices which have more index segments, attempting to match
all the conjuncts possible to these indices, on the theory that one index matched
to n booleans is more selective and uses less resources than using n indices;
therefore find out which index has the most segments and proceed backwards from there;
NOTE: it's possible that a boolean might be matched to an index and then later it could
have been paired with another boolean to match another index such that both booleans
could be calculated via the index; currently we won't detect that case
*/
Firebird::vector<IDX*> idx_walk_vector(MAX_INDICES);
IDX** idx_walk = &idx_walk_vector[0];
Firebird::vector<SLONG> idx_priority_level_vector(MAX_INDICES);
SLONG* idx_priority_level = &idx_priority_level_vector[0];
for (i = 0, idx = csb_tail->csb_idx; i < csb_tail->csb_indices;
i++, idx = NEXT_IDX(idx->idx_rpt, idx->idx_count))
{
idx_walk[i] = idx;
idx_priority_level[i] = LOWEST_PRIORITY_LEVEL;
/* skip this part if the index wasn't specified for indexed
retrieval (still need to look for navigational retrieval) */
if ((idx->idx_runtime_flags & idx_plan_dont_use) &&
!(idx->idx_runtime_flags & idx_plan_navigate))
{
continue;
}
/* go through all the unused conjuncts and see if
any of them are computable using this index */
clear_bounds(opt, idx);
tail = opt->opt_rpt;
if (outer_flag) {
tail += opt->opt_count;
}
for (; tail < opt_end; tail++)
{
if (tail->opt_flags & opt_matched) {
continue;
}
node = tail->opt_conjunct;
if (!(tail->opt_flags & opt_used) &&
computable(csb, node, -1, (inner_flag || outer_flag)))
{
if (count = match_index(tdbb, opt, stream, node, idx)) {
/* mark the index in the bitmap and if this conjunct
has a own index mark this also */
if (idx->idx_count == count) {
tail->opt_idx_full_match = TRUE;
}
}
}
if (node->nod_type == nod_starts)
compose(&inversion,
make_starts(tdbb, opt, relation, node, stream,
idx), nod_bit_and);
if (node->nod_type == nod_missing)
compose(&inversion,
make_missing(tdbb, opt, relation, node,
stream, idx), nod_bit_and);
}
/* look for a navigational retrieval (unless one was already found or
there is no sort block); if no navigational retrieval on this index,
add an indexed retrieval to the inversion tree */
if (!rsb)
{
if (sort_ptr && *sort_ptr)
{
rsb = gen_navigation(tdbb, opt, stream, relation, alias,
idx, sort_ptr);
if (rsb) {
continue;
}
}
/* for now, make sure that we only map a DISTINCT to an index if they contain
the same number of fields; it should be possible to map a DISTINCT to an
index which has extra fields to the right, but we need to add some code
in NAV_get_record() to check when the relevant fields change, rather than
the whole index key */
/***if (project_ptr && *project_ptr)
if ((idx->idx_count == (*project_ptr)->nod_count) &&
(rsb = gen_navigation (tdbb, opt, stream, relation, alias, idx, project_ptr)))
{
rsb->rsb_flags |= rsb_project;
continue;
}***/
}
if (opt->opt_rpt[0].opt_lower || opt->opt_rpt[0].opt_upper) {
/* Calculate the priority level for this index */
idx_priority_level[i] = calculate_priority_level(opt, idx);
}
}
/* Sort indices based on the priority level into idx_walk */
SSHORT idx_walk_count =
sort_indices_by_priority(csb_tail, idx_walk, idx_priority_level);
/* Walk through the indicies based on earlier calculated count and
when necessary build the index */
// TMN: Shouldn't this be allocated from the tdbb->tdbb_default pool?
Firebird::vector<SSHORT> conjunct_position_vector(MAX_INDICES);
SSHORT* conjunct_position = &conjunct_position_vector[0];
Firebird::vector<Opt::opt_repeat*> matching_nodes_vector(MAX_INDICES);
Opt::opt_repeat** matching_nodes = &matching_nodes_vector[0];
for (i = 0; i < idx_walk_count; i++)
{
idx = idx_walk[i];
if (idx->idx_runtime_flags & idx_plan_dont_use) {
continue;
}
j = 0;
clear_bounds(opt, idx);
tail = opt->opt_rpt;
if (outer_flag) {
tail += opt->opt_count;
}
for (; tail < opt_end; tail++)
{
/* Test if this conjunction is available for this index. */
if (!(tail->opt_flags & opt_matched))
{
/* Setting opt_lower and/or opt_upper values */
node = tail->opt_conjunct;
if (!(tail->opt_flags & opt_used) &&
computable(csb,
node,
-1,
(inner_flag || outer_flag)))
{
if (match_index(tdbb, opt, stream, node, idx))
{
position = 0;
idx_tail = opt->opt_rpt;
idx_end = idx_tail + idx->idx_count;
for (; idx_tail < idx_end; idx_tail++, position++) {
if (idx_tail->opt_match == node) {
conjunct_position[j] = position;
break;
}
}
matching_nodes[j++] = tail;
count = j;
}
}
}
}
if (opt->opt_rpt[0].opt_lower || opt->opt_rpt[0].opt_upper)
{
/* Use a different marking if a PLAN was specified, this is
for backwards compatibility. Juck... */
if (csb_tail->csb_plan)
{
/* Mark only used conjuncts in this index as used */
idx_tail = opt->opt_rpt;
idx_end = idx_tail + idx->idx_count;
for (; idx_tail < idx_end &&
(idx_tail->opt_lower || idx_tail->opt_upper); idx_tail++)
{
for (tail = opt->opt_rpt; tail < opt_end; tail++) {
if (idx_tail->opt_match == tail->opt_conjunct) {
tail->opt_flags |= opt_matched;
}
}
}
}
else
{
// No plan
/* Mark all conjuncts that could be calculated against the
index as used. For example if you have :
(node1 >= constant) and (node1 <= constant) be sure both
conjuncts will be marked as opt_matched */
position = 0;
idx_tail = opt->opt_rpt;
idx_end = idx_tail + idx->idx_count;
for (; idx_tail < idx_end &&
(idx_tail->opt_lower || idx_tail->opt_upper);
idx_tail++, position++)
{
for (j = 0; j < count; j++) {
if (conjunct_position[j] == position) {
matching_nodes[j]->opt_flags |= opt_matched;
}
}
}
}
compose(&inversion, OPT_make_index(tdbb, opt, relation, idx),
nod_bit_and);
idx->idx_runtime_flags |= idx_used_with_and;
}
}
}
if (outer_flag) {
/* Now make another pass thru the outer conjuncts only, finding unused,
computable booleans. When one is found, roll it into a final
boolean and mark it used. */
*return_boolean = NULL;
opt_end = opt->opt_rpt + opt->opt_count;
for (tail = opt->opt_rpt; tail < opt_end; tail++)
{
node = tail->opt_conjunct;
if (!(tail->opt_flags & opt_used)
&& computable(csb, node, -1, false))
{
compose(return_boolean, node, nod_and);
tail->opt_flags |= opt_used;
}
}
}
/* Now make another pass thru the conjuncts finding unused, computable
booleans. When one is found, roll it into a final boolean and mark
it used. If a computable boolean didn't match against an index then
mark the stream to denote unmatched booleans. */
opt_boolean = NULL;
opt_end = opt->opt_rpt + (inner_flag ? opt->opt_count : opt->opt_parent_count);
tail = opt->opt_rpt;
if (outer_flag) {
tail += opt->opt_count;
}
for (; tail < opt_end; tail++)
{
node = tail->opt_conjunct;
if (!relation->rel_file) {
compose(&inversion, OPT_make_dbkey(opt, node, stream), nod_bit_and);
}
if (!(tail->opt_flags & opt_used)
&& computable(csb, node, -1, false))
{
if (node->nod_type == nod_or) {
compose(&inversion, make_inversion(tdbb, opt, node, stream),
nod_bit_and);
}
compose(&opt_boolean, node, nod_and);
tail->opt_flags |= opt_used;
if (!outer_flag && !(tail->opt_flags & opt_matched)) {
csb_tail->csb_flags |= csb_unmatched;
}
}
}
if (full)
return gen_rsb(tdbb, opt, rsb, inversion, stream, relation, alias,
*return_boolean, csb_tail->csb_cardinality);
else
return gen_rsb(tdbb, opt, rsb, inversion, stream, relation, alias,
opt_boolean, csb_tail->csb_cardinality);
}
static RSB gen_rsb(TDBB tdbb,
OPT opt,
RSB rsb,
JRD_NOD inversion,
SSHORT stream,
JRD_REL relation, STR alias, JRD_NOD boolean, float cardinality)
{
/**************************************
*
* g e n _ r s b
*
**************************************
*
* Functional description
* Generate a record source block to handle either a sort or a project.
*
**************************************/
SSHORT size;
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(rsb, type_rsb);
DEV_BLKCHK(inversion, type_nod);
DEV_BLKCHK(relation, type_rel);
DEV_BLKCHK(alias, type_str);
DEV_BLKCHK(boolean, type_nod);
SET_TDBB(tdbb);
if (rsb) {
if (rsb->rsb_type == rsb_navigate && inversion)
rsb->rsb_arg[RSB_NAV_inversion] = (RSB) inversion;
}
else {
if (inversion) {
rsb = FB_NEW_RPT(*tdbb->tdbb_default, 1) Rsb();
rsb->rsb_type = rsb_indexed;
rsb->rsb_count = 1;
size = sizeof(struct irsb_index);
rsb->rsb_arg[0] = (RSB) inversion;
}
else {
rsb = FB_NEW_RPT(*tdbb->tdbb_default, 0) Rsb();
rsb->rsb_type = rsb_sequential;
size = sizeof(struct irsb);
if (boolean)
opt->opt_csb->csb_rpt[stream].csb_flags |= csb_unmatched;
}
rsb->rsb_stream = (UCHAR) stream;
rsb->rsb_relation = relation;
rsb->rsb_alias = alias;
/* if this is a blr_stream, we need to leave room
in the impure area for a navigational-type rsb;
even if this is not currently a navigational rsb,
OPT_set_index() could be used to convert it to one */
if (opt->opt_g_flags & opt_g_stream) {
size = sizeof(struct inv);
size = nav_rsb_size(rsb, MAX_KEY, size);
}
rsb->rsb_impure = CMP_impure(opt->opt_csb, size);
}
if (boolean)
rsb = gen_boolean(tdbb, opt, rsb, boolean);
/* retain the cardinality for use at runtime by blr_cardinality */
rsb->rsb_cardinality = (ULONG) cardinality;
return rsb;
}
static RSB gen_skip (TDBB tdbb, OPT opt, RSB prior_rsb, JRD_NOD node)
{
/**************************************
*
* g e n _ s k i p
*
**************************************
*
* Functional description
* Compile and optimize a record selection expression into a
* set of record source blocks (rsb's).
*
* NOTE: The rsb_skip node MUST appear in the rsb list after the
* rsb_first node. The calling code MUST call gen_skip before
* gen_first.
*
**************************************/
CSB csb;
RSB rsb;
DEV_BLKCHK (opt, type_opt);
DEV_BLKCHK (prior_rsb, type_rsb);
DEV_BLKCHK (node, type_nod);
SET_TDBB (tdbb);
csb = opt->opt_csb;
rsb = FB_NEW_RPT(*tdbb->tdbb_default, 0) Rsb(); // was : rsb = (RSB) ALLOCDV (type_rsb, 1);
rsb->rsb_count = 1;
rsb->rsb_type = rsb_skip;
rsb->rsb_next = prior_rsb;
rsb->rsb_arg [0] = (RSB) node;
rsb->rsb_impure = CMP_impure (csb, sizeof (struct irsb_skip_n));
return rsb;
}
static RSB gen_sort(TDBB tdbb,
OPT opt,
UCHAR * streams,
UCHAR * dbkey_streams,
RSB prior_rsb, JRD_NOD sort, USHORT project_flag)
{
/**************************************
*
* g e n _ s o r t
*
**************************************
*
* Functional description
* Generate a record source block to handle either a sort or a project.
* The two case are virtual identical -- the only difference is that
* project eliminates duplicates. However, since duplicates are
* recognized and handled by sort, the JRD processing is identical.
*
**************************************/
RSB rsb;
CSB csb;
SLONG id;
ULONG map_length = 0;
JRD_NOD node, *node_ptr, *end_node;
UCHAR *ptr, *end_ptr;
USHORT stream, items, count, flag_offset;
SMB map;
SKD *sort_key, *end_key;
DSC *desc, descriptor;
FMT format;
LLS id_stack, stream_stack;
smb_repeat * map_item;
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(prior_rsb, type_rsb);
DEV_BLKCHK(sort, type_nod);
SET_TDBB(tdbb);
/* We already know the number of keys, but we also need to compute the
total number of fields, keys and non-keys, to be pumped thru sort. Starting
with the number of keys, count the other field referenced. Since a field
is often a key, check for overlap to keep the length of the sort record
down. */
/* Along with the record number, the transaction id of the
* record will also be stored in the sort file. This will
* be used to detect update conflict in read committed
* transactions.
*/
csb = opt->opt_csb;
items =
sort->nod_count + (streams[0] * 2) +
(dbkey_streams ? dbkey_streams[0] : 0);
end_ptr = streams + streams[0];
end_node = sort->nod_arg + sort->nod_count;
id_stack = stream_stack = NULL;
for (ptr = &streams[1]; ptr <= end_ptr; ptr++) {
id = -1;
while (SBM_next(csb->csb_rpt[*ptr].csb_fields, &id, RSE_get_forward)) {
items++;
LLS_PUSH((BLK) id, &id_stack);
LLS_PUSH((BLK) * ptr, &stream_stack);
for (node_ptr = sort->nod_arg; node_ptr < end_node; node_ptr++) {
node = *node_ptr;
if (node->nod_type == nod_field
&& (USHORT) node->nod_arg[e_fld_stream] == *ptr
&& (USHORT) node->nod_arg[e_fld_id] == id) {
desc = &descriptor;
CMP_get_desc(tdbb, csb, node, desc);
/* International type text has a computed key */
if (IS_INTL_DATA(desc))
break;
--items;
LLS_POP(&id_stack);
LLS_POP(&stream_stack);
break;
}
}
}
}
/* Now that we know the number of items, allocate a sort map block. Allocate
it sufficiently large that there is room for a sort key descriptor on the
end. */
count = items +
(sizeof(SKD) * 2 * sort->nod_count + sizeof(smb_repeat) -
1) / sizeof(smb_repeat);
map = FB_NEW_RPT(*tdbb->tdbb_default, count) smb();
map->smb_keys = sort->nod_count * 2;
map->smb_count = items;
if (project_flag)
map->smb_flags |= SMB_project;
/* Loop thru sort keys building sort keys. Actually, to handle null values
correctly, two sort keys are made for each field, one for the null flag
and one for field itself. */
map_item = map->smb_rpt;
sort_key = (SKD *) & map->smb_rpt[items];
map->smb_key_desc = sort_key;
for (node_ptr = sort->nod_arg; node_ptr < end_node;
node_ptr++, map_item++) {
/* Pick up sort key expression. */
node = *node_ptr;
desc = &descriptor;
CMP_get_desc(tdbb, csb, node, desc);
/* Allow for "key" forms of International text to grow */
if (IS_INTL_DATA(desc)) {
/* Turn varying text and cstrings into text. */
if (desc->dsc_dtype == dtype_varying) {
desc->dsc_dtype = dtype_text;
desc->dsc_length -= sizeof(USHORT);
}
else if (desc->dsc_dtype == dtype_cstring) {
desc->dsc_dtype = dtype_text;
desc->dsc_length--;
}
desc->dsc_length =
INTL_key_length(tdbb, INTL_INDEX_TYPE(desc),
desc->dsc_length);}
/* Make key for null flag */
#ifndef WORDS_BIGENDIAN
map_length = ROUNDUP(map_length, sizeof(SLONG));
#endif
sort_key->skd_offset = map_item->smb_flag_offset =
(USHORT) map_length++; sort_key->skd_dtype = SKD_text;
sort_key->skd_length = 1;
sort_key->skd_flags = SKD_ascending;
if (*(node_ptr + sort->nod_count*2))
sort_key->skd_flags |= SKD_descending;
++sort_key;
/* Make key for sort key proper */
#ifndef WORDS_BIGENDIAN
map_length = ROUNDUP(map_length, sizeof(SLONG));
#else
if (desc->dsc_dtype >= dtype_aligned)
map_length =
FB_ALIGN(map_length, type_alignments[desc->dsc_dtype]);
#endif
sort_key->skd_offset = (USHORT) map_length;
sort_key->skd_flags = SKD_ascending;
if (*(node_ptr + sort->nod_count))
sort_key->skd_flags |= SKD_descending;
assert(desc->dsc_dtype >= 0 &&
desc->dsc_dtype < FB_NELEM(sort_dtypes));
sort_key->skd_dtype = sort_dtypes[desc->dsc_dtype];
/*assert (sort_key->skd_dtype != 0); */
if (sort_key->skd_dtype == SKD_varying ||
sort_key->skd_dtype == SKD_cstring) {
if (desc->dsc_ttype == ttype_binary)
sort_key->skd_flags |= SKD_binary;
}
sort_key->skd_length = desc->dsc_length;
++sort_key;
map_item->smb_node = node;
map_item->smb_desc = *desc;
map_item->smb_desc.dsc_address = (UCHAR *) map_length;
map_length += desc->dsc_length;
if (node->nod_type == nod_field) {
map_item->smb_stream = (USHORT) node->nod_arg[e_fld_stream];
map_item->smb_field_id = (USHORT) node->nod_arg[e_fld_id];
}
}
map_length = ROUNDUP(map_length, sizeof(SLONG));
map->smb_key_length = (USHORT) map_length >> SHIFTLONG;
flag_offset = (USHORT) map_length;
map_length += items - sort->nod_count;
/* Now go back and process all to fields involved with the sort. If the
field has already been mentioned as a sort key, don't bother to repeat
it. */
while (stream_stack) {
id = (USHORT) LLS_POP(&id_stack);
stream = (USHORT) LLS_POP(&stream_stack);
format = CMP_format(tdbb, csb, stream);
desc = &format->fmt_desc[id];
if (id >= format->fmt_count || desc->dsc_length == 0)
IBERROR(157); /* msg 157 cannot sort on a field that does not exist */
if (desc->dsc_dtype >= dtype_aligned)
map_length =
FB_ALIGN(map_length, type_alignments[desc->dsc_dtype]);
map_item->smb_field_id = (SSHORT) id;
map_item->smb_stream = stream;
map_item->smb_flag_offset = flag_offset++;
map_item->smb_desc = *desc;
map_item->smb_desc.dsc_address = (UCHAR *) map_length;
map_length += desc->dsc_length;
map_item++;
}
/* Make fields for record numbers record for all streams */
map_length = ROUNDUP(map_length, sizeof(SLONG));
for (ptr = &streams[1]; ptr <= end_ptr; ptr++, map_item++) {
map_item->smb_field_id = SMB_DBKEY;
map_item->smb_stream = *ptr;
desc = &map_item->smb_desc;
desc->dsc_dtype = dtype_long;
desc->dsc_length = sizeof(SLONG);
desc->dsc_address = (UCHAR *) map_length;
map_length += desc->dsc_length;
}
/* Make fields for transaction id of record for all streams */
for (ptr = &streams[1]; ptr <= end_ptr; ptr++, map_item++) {
map_item->smb_field_id = SMB_TRANS_ID;
map_item->smb_stream = *ptr;
desc = &map_item->smb_desc;
desc->dsc_dtype = dtype_long;
desc->dsc_length = sizeof(SLONG);
desc->dsc_address = (UCHAR *) map_length;
map_length += desc->dsc_length;
}
if (dbkey_streams)
for (ptr = &dbkey_streams[1], end_ptr =
dbkey_streams + dbkey_streams[0]; ptr <= end_ptr;
ptr++, map_item++) {
map_item->smb_field_id = SMB_DBKEY;
map_item->smb_stream = *ptr;
desc = &map_item->smb_desc;
desc->dsc_dtype = dtype_long;
desc->dsc_length = sizeof(SLONG);
desc->dsc_address = (UCHAR *) map_length;
map_length += desc->dsc_length;
}
/* Make fields to store varying and cstring length. */
end_key = sort_key;
for (sort_key = map->smb_key_desc; sort_key < end_key; sort_key++) {
/* assert (sort_key->skd_dtype != 0); */
if (sort_key->skd_dtype == SKD_varying ||
sort_key->skd_dtype == SKD_cstring) {
sort_key->skd_vary_offset = (USHORT) map_length;
map_length += sizeof(USHORT);
}
}
if (map_length > MAX_SORT_RECORD)
ERR_post(isc_sort_rec_size_err, isc_arg_number, map_length, 0);
/* Msg438: sort record size of %ld bytes is too big */
map->smb_length = (USHORT) map_length;
/* That was most unpleasant. Never the less, it's done (except for
the debugging). All that remains is to build the record source
block for the sort. */
rsb = FB_NEW_RPT(*tdbb->tdbb_default, 1) Rsb();
rsb->rsb_type = rsb_sort;
rsb->rsb_next = prior_rsb;
rsb->rsb_arg[0] = (RSB) map;
rsb->rsb_impure = CMP_impure(csb, sizeof(struct irsb_sort));
return rsb;
}
static BOOLEAN gen_sort_merge(TDBB tdbb, OPT opt, LLS * org_rivers)
{
/**************************************
*
* g e n _ s o r t _ m e r g e
*
**************************************
*
* Functional description
* We've got a set of rivers that may or may not be amenable to
* a sort/merge join, and it's time to find out. If there are,
* build a sort/merge RSB, push it on the rsb stack, and update
* rivers accordingly. If two or more rivers were successfully
* joined, return TRUE. If the whole things is a moby no-op,
* return FALSE.
*
**************************************/
DBB dbb;
RIV river1, river2;
LLS stack1, stack2;
USHORT i, cnt, class_cnt, river_cnt, stream_cnt;
ULONG selected_rivers[OPT_BITS], selected_rivers2[OPT_BITS];
UCHAR *stream;
VEC scratch;
JRD_NOD *classes, *class_, *selected_classes[MAX_OPT_ITEMS],
**selected_class, *last_class, node, node1, node2, sort, *ptr;
RSB rsb, merge_rsb;
RSB *rsb_tail;
Opt::opt_repeat * tail, *end;
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(*org_rivers, type_lls);
SET_TDBB(tdbb);
dbb = tdbb->tdbb_database;
/* Count the number of "rivers" involved in the operation, then allocate
a scratch block large enough to hold values to compute equality
classes */
for (cnt = 0, stack1 = *org_rivers; stack1; stack1 = stack1->lls_next) {
river1 = (RIV) stack1->lls_object;
river1->riv_number = cnt++;
}
scratch = vec::newVector(*dbb->dbb_permanent, opt->opt_count * cnt);
classes = (JRD_NOD *) &*(scratch->begin());
// classes = (JRD_NOD *) &(scratch->[0]);
/* Compute equivalence classes among streams. This involves finding groups
of streams joined by field equalities. */
last_class = classes;
for (tail = opt->opt_rpt, end = tail + opt->opt_count; tail < end; tail++) {
if (tail->opt_flags & opt_used)
continue;
node = tail->opt_conjunct;
if (node->nod_type != nod_eql)
continue;
node1 = node->nod_arg[0];
node2 = node->nod_arg[1];
for (stack1 = *org_rivers; stack1; stack1 = stack1->lls_next) {
river1 = (RIV) stack1->lls_object;
if (!river_reference(river1, node1))
if (river_reference(river1, node2)) {
node = node1;
node1 = node2;
node2 = node;
}
else
continue;
for (stack2 = stack1->lls_next; stack2; stack2 = stack2->lls_next) {
river2 = (RIV) stack2->lls_object;
if (river_reference(river2, node2)) {
for (class_ = classes; class_ < last_class; class_ += cnt)
if (node_equality(node1, classes[river1->riv_number])
|| node_equality(node2,
classes[river2->
riv_number])) break;
class_[river1->riv_number] = node1;
class_[river2->riv_number] = node2;
if (class_ == last_class)
last_class += cnt;
}
}
}
}
/* Pick both a set of classes and a set of rivers on which to join with
sort merge. Obviously, if the set of classes is empty, return FALSE
to indicate that nothing could be done. */
river_cnt = stream_cnt = 0;
for (class_ = classes; class_ < last_class; class_ += cnt) {
i = river_count(cnt, class_);
if (i > river_cnt) {
river_cnt = i;
selected_class = selected_classes;
*selected_class++ = class_;
class_mask(cnt, class_, selected_rivers);
}
else {
class_mask(cnt, class_, selected_rivers2);
for (i = 0; i < OPT_BITS; i++)
if ((selected_rivers[i] & selected_rivers2[i]) !=
selected_rivers[i])
break;
if (i == OPT_BITS)
*selected_class++ = class_;
}
}
if (!river_cnt) {
delete scratch;
return FALSE;
}
*selected_class = NULL;
class_cnt = selected_class - selected_classes;
/* Build a sort stream */
merge_rsb = FB_NEW_RPT(*tdbb->tdbb_default, river_cnt * 2) Rsb();
merge_rsb->rsb_count = river_cnt;
merge_rsb->rsb_type = rsb_merge;
merge_rsb->rsb_impure = CMP_impure(opt->opt_csb,
(USHORT) (sizeof
(struct irsb_mrg) +
river_cnt *
sizeof
(irsb_mrg::
irsb_mrg_repeat)));
rsb_tail = merge_rsb->rsb_arg;
stream_cnt = 0;
for (stack1 = *org_rivers; stack1; stack1 = stack1->lls_next) {
river1 = (RIV) stack1->lls_object;
if (!(TEST_DEP_BIT(selected_rivers, river1->riv_number)))
continue;
stream_cnt += river1->riv_count;
sort = FB_NEW_RPT(*tdbb->tdbb_default, class_cnt * 2) jrd_nod();
sort->nod_type = nod_sort;
sort->nod_count = class_cnt;
for (selected_class = selected_classes, ptr = sort->nod_arg;
*selected_class; selected_class++)
*ptr++ = (*selected_class)[river1->riv_number];
rsb =
gen_sort(tdbb, opt, &river1->riv_count, NULL, river1->riv_rsb,
sort, FALSE); *rsb_tail++ = rsb;
*rsb_tail++ = (RSB) sort;
}
/* Finally, merge selected rivers into a single river, and rebuild original
river stack */
river1 = FB_NEW_RPT(*tdbb->tdbb_default, stream_cnt) riv();
river1->riv_count = (UCHAR) stream_cnt;
river1->riv_rsb = merge_rsb;
stream = river1->riv_streams;
stack1 = NULL;
LLS_PUSH(river1, &stack1);
while (*org_rivers) {
river2 = (RIV) LLS_POP(org_rivers);
if (TEST_DEP_BIT(selected_rivers, river2->riv_number)) {
MOVE_FAST(river2->riv_streams, stream, river2->riv_count);
stream += river2->riv_count;
}
else
LLS_PUSH(river2, &stack1);
}
/* Pick up any boolean that may apply */
set_active(opt, river1);
node = NULL;
for (tail = opt->opt_rpt; tail < end; tail++)
{
node1 = tail->opt_conjunct;
if (!(tail->opt_flags & opt_used)
&& computable(opt->opt_csb, node1, -1, false))
{
compose(&node, node1, nod_and);
tail->opt_flags |= opt_used;
}
}
if (node)
river1->riv_rsb = gen_boolean(tdbb, opt, river1->riv_rsb, node);
set_inactive(opt, river1);
*org_rivers = stack1;
delete scratch;
return TRUE;
}
static RSB gen_union(TDBB tdbb,
OPT opt,
JRD_NOD union_node, UCHAR * streams, USHORT nstreams)
{
/**************************************
*
* g e n _ u n i o n
*
**************************************
*
* Functional description
* Generate a union complex.
*
**************************************/
RSB rsb, *rsb_ptr;
USHORT count;
JRD_NOD clauses, *ptr, *end;
CSB csb;
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(union_node, type_nod);
SET_TDBB(tdbb);
clauses = union_node->nod_arg[e_uni_clauses];
count = clauses->nod_count;
csb = opt->opt_csb;
rsb = FB_NEW_RPT(*tdbb->tdbb_default, count + nstreams + 1) Rsb();
rsb->rsb_type = rsb_union;
rsb->rsb_count = count;
rsb->rsb_stream = (UCHAR) union_node->nod_arg[e_uni_stream];
rsb->rsb_format = csb->csb_rpt[rsb->rsb_stream].csb_format;
rsb->rsb_impure = CMP_impure(csb, sizeof(struct irsb));
rsb_ptr = rsb->rsb_arg;
for (ptr = clauses->nod_arg, end = ptr + count; ptr < end;) {
*rsb_ptr++ = OPT_compile(tdbb, csb, (RSE) * ptr++, NULL);
*rsb_ptr++ = (RSB) * ptr++;
}
/* Save the count and numbers of the streams that make up the union */
*rsb_ptr++ = (RSB) nstreams;
while (nstreams--)
*rsb_ptr++ = (RSB) * streams++;
return rsb;
}
static void get_inactivities(CSB csb, ULONG * dependencies)
{
/**************************************
*
* g e t _ i n a c t i v i t i e s
*
**************************************
*
* Functional description
* Find any streams not explicitily active.
*
**************************************/
USHORT n;
Csb::rpt_itr tail, end;
DEV_BLKCHK(csb, type_csb);
for (n = 0; n < OPT_BITS; n++)
dependencies[n] = (ULONG) - 1;
for (tail = csb->csb_rpt.begin(), end = tail + csb->csb_n_stream, n = 0;
tail < end; n++, tail++)
if (tail->csb_flags & csb_active)
CLEAR_DEP_BIT(dependencies, n);
}
static IRL indexed_relationship(TDBB tdbb, OPT opt, USHORT stream)
{
/**************************************
*
* i n d e x e d _ r e l a t i o n s h i p
*
**************************************
*
* Functional description
* See if two streams are related by an index.
* An indexed relationship is a means of joining two
* streams via an index, which is possible when a field from
* each of the streams is compared with a field from the other,
* and there is an index on one stream to retrieve the value
* of the other field.
*
**************************************/
DEV_BLKCHK(opt, type_opt);
SET_TDBB(tdbb);
if (!opt->opt_count) {
return NULL;
}
CSB csb = opt->opt_csb;
csb_repeat* csb_tail = &csb->csb_rpt[stream];
Opt::opt_repeat* opt_end = &opt->opt_rpt[opt->opt_count];
IRL relationship = NULL;
/* Loop thru indexes looking for a match */
IDX* idx = csb_tail->csb_idx;
for (USHORT i = 0; i < csb_tail->csb_indices;
i++, idx = NEXT_IDX(idx->idx_rpt, idx->idx_count))
{
/* skip this part if the index wasn't specified for indexed retrieval */
if (idx->idx_runtime_flags & idx_plan_dont_use) {
continue;
}
clear_bounds(opt, idx);
Opt::opt_repeat* tail;
for (tail = opt->opt_rpt; tail < opt_end; tail++)
{
JRD_NOD node = tail->opt_conjunct;
if (!(tail->opt_flags & opt_used)
&& computable(csb, node, -1, false))
{
/* AB: Why only check for AND structures ?
Added match_indices for support of "OR" with INNER JOINs */
/* match_index(tdbb, opt, stream, node, idx); */
match_indices(tdbb, opt, stream, node, idx);
/* AB: Why should we look further ? */
if (opt->opt_rpt->opt_lower || opt->opt_rpt->opt_upper) {
break;
}
}
}
tail = opt->opt_rpt;
if (tail->opt_lower || tail->opt_upper) {
if (!relationship) {
relationship = FB_NEW(*tdbb->tdbb_default) irl();
}
if (idx->idx_flags & idx_unique) {
relationship->irl_unique = TRUE;
break;
}
}
}
return relationship;
}
static STR make_alias(TDBB tdbb, CSB csb, csb_repeat * base_tail)
{
/**************************************
*
* m a k e _ a l i a s
*
**************************************
*
* Functional description
* Make an alias string suitable for printing
* as part of the plan. For views, this means
* multiple aliases to distinguish the base
* table.
*
**************************************/
STR alias;
USHORT alias_length = 0;
TEXT *p, *q;
csb_repeat *csb_tail;
DEV_BLKCHK(csb, type_csb);
SET_TDBB(tdbb);
if (!base_tail->csb_view && !base_tail->csb_alias)
return NULL;
/* calculate the length of the alias by going up through
the view stack to find the lengths of all aliases;
adjust for spaces and a null terminator */
for (csb_tail = base_tail;;
csb_tail = &csb->csb_rpt[csb_tail->csb_view_stream]) {
if (csb_tail->csb_alias)
alias_length += csb_tail->csb_alias->str_length;
else
alias_length +=
(!(csb_tail->csb_relation)
|| !(csb_tail->csb_relation->
rel_name)) ? 0 : strlen(csb_tail->csb_relation->
rel_name);
alias_length++;
if (!csb_tail->csb_view)
break;
}
/* allocate a string block to hold the concatenated alias */
alias = FB_NEW_RPT(*tdbb->tdbb_default, alias_length) str();
alias->str_length = alias_length - 1;
/* now concatenate the individual aliases into the string block,
beginning at the end and copying back to the beginning */
p = (TEXT *) alias->str_data + alias->str_length;
*p-- = 0;
for (csb_tail = base_tail;;
csb_tail = &csb->csb_rpt[csb_tail->csb_view_stream]) {
if (csb_tail->csb_alias)
q = (TEXT *) csb_tail->csb_alias->str_data;
else
q = (!(csb_tail->csb_relation)
|| !(csb_tail->csb_relation->rel_name)) ? NULL : csb_tail->
csb_relation->rel_name;
/* go to the end of the alias and copy it backwards */
if (q) {
for (alias_length = 0; *q; alias_length++)
q++;
while (alias_length--)
*p-- = *--q;
}
if (!csb_tail->csb_view)
break;
*p-- = ' ';
}
return alias;
}
static JRD_NOD make_binary_node(NOD_T type, JRD_NOD arg1, JRD_NOD arg2, USHORT flag)
{
/**************************************
*
* m a k e _ b i n a r y _ n o d e
*
**************************************
*
* Functional description
* Make a binary node.
*
**************************************/
JRD_NOD node;
TDBB tdbb;
tdbb = GET_THREAD_DATA;
DEV_BLKCHK(arg1, type_nod);
DEV_BLKCHK(arg2, type_nod);
node = PAR_make_node(tdbb, 2);
node->nod_type = type;
node->nod_arg[0] = arg1;
node->nod_arg[1] = arg2;
if (flag)
node->nod_flags |= nod_comparison;
return node;
}
static RSB make_cross(TDBB tdbb, OPT opt, LLS stack)
{
/**************************************
*
* m a k e _ c r o s s
*
**************************************
*
* Functional description
* Generate a cross block.
*
**************************************/
RIV river;
RSB rsb, *ptr;
CSB csb;
LLS temp;
USHORT count;
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(stack, type_lls);
SET_TDBB(tdbb);
for (temp = stack, count = 0; temp; count++)
temp = temp->lls_next;
if (count == 1) {
river = (RIV) LLS_POP(&stack);
return river->riv_rsb;
}
csb = opt->opt_csb;
rsb = FB_NEW_RPT(*tdbb->tdbb_default, count) Rsb();
rsb->rsb_type = rsb_cross;
rsb->rsb_count = count;
rsb->rsb_impure = CMP_impure(csb, sizeof(struct irsb));
ptr = rsb->rsb_arg + count;
while (stack) {
river = (RIV) LLS_POP(&stack);
*--ptr = river->riv_rsb;
}
return rsb;
}
static JRD_NOD make_index_node(TDBB tdbb, JRD_REL relation, CSB csb, IDX * idx)
{
/**************************************
*
* m a k e _ i n d e x _ n o d e
*
**************************************
*
* Functional description
* Make an index node and an index retrieval block.
*
**************************************/
JRD_NOD node;
IRB retrieval;
DEV_BLKCHK(relation, type_rel);
DEV_BLKCHK(csb, type_csb);
SET_TDBB(tdbb);
/* check whether this is during a compile or during
a SET INDEX operation */
if (csb)
CMP_post_resource(tdbb, &csb->csb_resources,
reinterpret_cast < BLK > (relation), rsc_index,
idx->idx_id);
else
CMP_post_resource(tdbb, &tdbb->tdbb_request->req_resources,
reinterpret_cast < BLK > (relation), rsc_index,
idx->idx_id);
node = PAR_make_node(tdbb, e_idx_length);
node->nod_type = nod_index;
node->nod_count = 0;
retrieval = FB_NEW_RPT(*tdbb->tdbb_default, idx->idx_count * 2) irb();
node->nod_arg[e_idx_retrieval] = (JRD_NOD) retrieval;
retrieval->irb_index = idx->idx_id;
MOVE_FAST(idx, &retrieval->irb_desc, sizeof(retrieval->irb_desc));
if (csb)
node->nod_impure = CMP_impure(csb, sizeof(struct inv));
return node;
}
static JRD_NOD make_inference_node(CSB csb, JRD_NOD boolean, JRD_NOD arg1, JRD_NOD arg2)
{
/**************************************
*
* m a k e _ i n f e r e n c e _ n o d e
*
**************************************
*
* Defined
* 1996-Jan-15 David Schnepper
*
* Functional description
* From the predicate, boolean, and infer a new
* predicate using arg1 & arg2 as the first two
* parameters to the predicate.
*
* This is used when the engine knows A<B and A=C, and
* creates a new node to represent the infered knowledge C<B.
*
* Note that this may be sometimes incorrect with 3-value
* logic (per Chris Date's Object & Relations seminar).
* Later stages of query evaluation evaluate exactly
* the originally specified query, so 3-value issues are
* caught there. Making this inference might cause us to
* examine more records than needed, but would not result
* in incorrect results.
*
* Note that some nodes, specifically nod_like, have
* more than two parameters for a boolean operation.
* (nod_like has an optional 3rd parameter for the ESCAPE character
* option of SQL)
* Nod_sleuth also has an optional 3rd parameter (for the GDML
* matching ESCAPE character language). But nod_sleuth is
* (apparently) not considered during optimization.
*
*
**************************************/
JRD_NOD node;
USHORT n;
TDBB tdbb;
tdbb = GET_THREAD_DATA;
DEV_BLKCHK(csb, type_csb);
DEV_BLKCHK(boolean, type_nod);
DEV_BLKCHK(arg1, type_nod);
DEV_BLKCHK(arg2, type_nod);
assert(boolean->nod_count >= 2); /* must be a conjunction boolean */
/* Clone the input predicate */
node = PAR_make_node(tdbb, boolean->nod_count);
node->nod_type = boolean->nod_type;
node->nod_flags = boolean->nod_flags;
/* But substitute new values for some of the predicate arguments */
node->nod_arg[0] = CMP_clone_node(tdbb, csb, arg1);
node->nod_arg[1] = CMP_clone_node(tdbb, csb, arg2);
/* Arguments after the first two are just cloned (eg: LIKE ESCAPE clause) */
for (n = 2; n < boolean->nod_count; n++)
node->nod_arg[n] = CMP_clone_node(tdbb, csb, boolean->nod_arg[n]);
return node;
}
static JRD_NOD make_inversion(TDBB tdbb, OPT opt, JRD_NOD boolean, USHORT stream)
{
/**************************************
*
* m a k e _ i n v e r s i o n
*
**************************************
*
* Functional description
* Build an inversion for a boolean, if possible. Otherwise,
* return NULL. Make inversion is call initially from
* gen_retrieval to handle "or" nodes, but may be called
* recursively for almost anything.
*
**************************************/
IDX *idx;
JRD_NOD inversion, inversion2, node;
SSHORT i;
float compound_selectivity;
BOOLEAN accept, used_in_compound, accept_starts, accept_missing;
SET_TDBB(tdbb);
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(boolean, type_nod);
csb_repeat* csb_tail = &opt->opt_csb->csb_rpt[stream];
JRD_REL relation = csb_tail->csb_relation;
if ((!relation) || (relation->rel_file)) {
return NULL;
}
/* Handle the "OR" case up front */
if (boolean->nod_type == nod_or)
{
inversion = make_inversion(tdbb, opt, boolean->nod_arg[0], stream);
if (!inversion) {
return NULL;
}
inversion2 = make_inversion(tdbb, opt, boolean->nod_arg[1], stream);
if (inversion2) {
return compose(&inversion, inversion2, nod_bit_or);
}
if (inversion->nod_type == nod_index) {
delete inversion->nod_arg[e_idx_retrieval];
}
delete inversion;
return NULL;
}
/* Time to find inversions. For each index on the relation
match all unused booleans against the index looking for upper
and lower bounds that can be computed by the index. When
all unused conjunctions are exhausted, see if there is enough
information for an index retrieval. If so, build up and
inversion component of the boolean. */
/* AB: If the boolean is a part of an earlier created index
retrieval check with the compound_selectivity if it's
really interresting to use. */
inversion = NULL;
accept_starts = TRUE;
accept_missing = TRUE;
used_in_compound = FALSE;
compound_selectivity = 1; /* Real maximum selectivity possible is 1 */
// TMN: Shouldn't this be allocated from the tdbb->tdbb_default pool?
Firebird::vector<IDX*> idx_walk_vector(MAX_INDICES);
IDX** idx_walk = &idx_walk_vector[0];
Firebird::vector<SLONG> idx_priority_level_vector(MAX_INDICES);
SLONG* idx_priority_level = &idx_priority_level_vector[0];
idx = csb_tail->csb_idx;
if (opt->opt_count) {
for (i = 0; i < csb_tail->csb_indices; i++) {
idx_walk[i] = idx;
idx_priority_level[i] = LOWEST_PRIORITY_LEVEL;
clear_bounds(opt, idx);
if (match_index(tdbb, opt, stream, boolean, idx) &&
!(idx->idx_runtime_flags & idx_plan_dont_use)) {
/* Calculate the priority level of this index */
idx_priority_level[i] = calculate_priority_level(opt, idx);
}
/* If the index was already used in an AND node check
if this node is also present in this index */
if (idx->idx_runtime_flags & idx_used_with_and) {
if ((match_index(tdbb, opt, stream, boolean, idx)) &&
(idx->idx_selectivity < compound_selectivity)) {
compound_selectivity = idx->idx_selectivity;
used_in_compound = TRUE;
}
}
/* Because indices are already sort based on their selectivity
it's not needed to more then 1 index for a node */
if ((boolean->nod_type == nod_starts) && accept_starts) {
node = make_starts(tdbb, opt, relation, boolean, stream, idx);
if (node) {
compose(&inversion, node, nod_bit_and);
accept_starts = FALSE;
}
}
if ((boolean->nod_type == nod_missing) && accept_missing) {
node = make_missing(tdbb, opt, relation, boolean, stream, idx);
if (node) {
compose(&inversion, node, nod_bit_and);
accept_missing = FALSE;
}
}
idx = NEXT_IDX(idx->idx_rpt, idx->idx_count);
}
}
/* Sort indices based on the priority level into idx_walk */
SSHORT idx_walk_count =
sort_indices_by_priority(csb_tail, idx_walk, idx_priority_level);
accept = TRUE;
idx = csb_tail->csb_idx;
if (opt->opt_count) {
for (i = 0; i < idx_walk_count; i++) {
idx = idx_walk[i];
if (idx->idx_runtime_flags & idx_plan_dont_use) {
continue;
}
clear_bounds(opt, idx);
if (((accept || used_in_compound) &&
(idx->idx_selectivity < compound_selectivity * OR_SELECTIVITY_THRESHOLD_FACTOR)) ||
(csb_tail->csb_plan)) {
match_index(tdbb, opt, stream, boolean, idx);
if (opt->opt_rpt[0].opt_lower || opt->opt_rpt[0].opt_upper) {
compose(&inversion, OPT_make_index(tdbb, opt, relation, idx),
nod_bit_and);
accept = FALSE;
}
}
}
}
if (!inversion)
inversion = OPT_make_dbkey(opt, boolean, stream);
return inversion;
}
static JRD_NOD make_missing(TDBB tdbb,
OPT opt,
JRD_REL relation, JRD_NOD boolean, USHORT stream, IDX * idx)
{
/**************************************
*
* m a k e _ m i s s i n g
*
**************************************
*
* Functional description
* If the a given boolean is an index optimizable, build and
* return a inversion type node. Indexes built before minor
* version 3 (V3.2) have unreliable representations for missing
* character string fields, so they won't be used.
*
**************************************/
JRD_NOD field, node, value;
IRB retrieval;
SET_TDBB(tdbb);
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(relation, type_rel);
DEV_BLKCHK(boolean, type_nod);
field = boolean->nod_arg[0];
if (field->nod_type != nod_field)
return NULL;
if ((USHORT) field->nod_arg[e_fld_stream] != stream ||
(USHORT) field->nod_arg[e_fld_id] != idx->idx_rpt[0].idx_field)
return NULL;
node = make_index_node(tdbb, relation, opt->opt_csb, idx);
retrieval = (IRB) node->nod_arg[e_idx_retrieval];
retrieval->irb_relation = relation;
retrieval->irb_generic = irb_starting;
retrieval->irb_lower_count = retrieval->irb_upper_count = 1;
/* If we are matching less than the full index, this is a partial match */
if (retrieval->irb_upper_count < idx->idx_count)
retrieval->irb_generic |= irb_partial;
retrieval->irb_value[0] = retrieval->irb_value[idx->idx_count] =
value = PAR_make_node(tdbb, 0);
value->nod_type = nod_null;
idx->idx_runtime_flags |= idx_plan_missing;
return node;
}
static JRD_NOD make_starts(TDBB tdbb,
OPT opt,
JRD_REL relation, JRD_NOD boolean, USHORT stream, IDX * idx)
{
/**************************************
*
* m a k e _ s t a r t s
*
**************************************
*
* Functional description
* If the given boolean is an index optimizable, build and
* return a inversion type node.
*
**************************************/
JRD_NOD value, field, node;
IRB retrieval;
SET_TDBB(tdbb);
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(relation, type_rel);
DEV_BLKCHK(boolean, type_nod);
if (boolean->nod_type != nod_starts)
return NULL;
field = boolean->nod_arg[0];
value = boolean->nod_arg[1];
if (field->nod_type != nod_field) {
if (value->nod_type != nod_field)
return NULL;
field = value;
value = boolean->nod_arg[0];
}
/* Every string starts with an empty string so
don't bother using an index in that case. */
if (value->nod_type == nod_literal) {
DSC *literal_desc;
literal_desc = &((LIT) value)->lit_desc;
if ((literal_desc->dsc_dtype == dtype_text &&
literal_desc->dsc_length == 0) ||
(literal_desc->dsc_dtype == dtype_varying &&
literal_desc->dsc_length == sizeof(USHORT)))
return NULL;
}
if ((USHORT) field->nod_arg[e_fld_stream] != stream ||
(USHORT) field->nod_arg[e_fld_id] != idx->idx_rpt[0].idx_field
|| !(idx->idx_rpt[0].idx_itype == idx_string
|| idx->idx_rpt[0].idx_itype == idx_byte_array
|| idx->idx_rpt[0].idx_itype == idx_metadata
|| idx->idx_rpt[0].idx_itype >= idx_first_intl_string)
|| !computable(opt->opt_csb, value, stream, false))
{
return NULL;
}
node = make_index_node(tdbb, relation, opt->opt_csb, idx);
retrieval = (IRB) node->nod_arg[e_idx_retrieval];
retrieval->irb_relation = relation;
retrieval->irb_generic = irb_starting;
retrieval->irb_lower_count = retrieval->irb_upper_count = 1;
/* If we are matching less than the full index, this is a partial match */
if (retrieval->irb_upper_count < idx->idx_count)
retrieval->irb_generic |= irb_partial;
retrieval->irb_value[0] = retrieval->irb_value[idx->idx_count] = value;
idx->idx_runtime_flags |= idx_plan_starts;
return node;
}
static BOOLEAN map_equal(JRD_NOD field1, JRD_NOD field2, JRD_NOD map)
{
/**************************************
*
* m a p _ e q u a l
*
**************************************
*
* Functional description
* Test to see if two fields are equal, where the fields
* are in two different streams possibly mapped to each other.
* Order of the input fields is important.
*
**************************************/
JRD_NOD *map_ptr, *map_end, map_from, map_to;
DEV_BLKCHK(field1, type_nod);
DEV_BLKCHK(field2, type_nod);
DEV_BLKCHK(map, type_nod);
if (field1->nod_type != nod_field)
return FALSE;
if (field2->nod_type != nod_field)
return FALSE;
/* look through the mapping and see if we can find an equivalence */
for (map_ptr = map->nod_arg, map_end = map_ptr + map->nod_count;
map_ptr < map_end; map_ptr++) {
map_from = (*map_ptr)->nod_arg[e_asgn_from];
map_to = (*map_ptr)->nod_arg[e_asgn_to];
if (map_from->nod_type != nod_field || map_to->nod_type != nod_field)
continue;
if (field1->nod_arg[e_fld_stream] != map_from->nod_arg[e_fld_stream]
|| field1->nod_arg[e_fld_id] != map_from->nod_arg[e_fld_id])
continue;
if (field2->nod_arg[e_fld_stream] != map_to->nod_arg[e_fld_stream]
|| field2->nod_arg[e_fld_id] != map_to->nod_arg[e_fld_id])
continue;
return TRUE;
}
return FALSE;
}
static void mark_indices(csb_repeat * csb_tail, SSHORT relation_id)
{
/**************************************
*
* m a r k _ i n d i c e s
*
**************************************
*
* Functional description
* Mark indices that were not included
* in the user-specified access plan.
*
**************************************/
JRD_NOD access_type, plan, *arg, *end;
IDX *idx;
USHORT i, plan_count = 0;
if (!(plan = csb_tail->csb_plan))
return;
if (plan->nod_type != nod_retrieve)
return;
/* find out how many indices were specified; if
there were none, this is a sequential retrieval */
if ( (access_type = plan->nod_arg[e_retrieve_access_type]) )
plan_count = access_type->nod_count;
/* go through each of the indices and mark it unusable
for indexed retrieval unless it was specifically mentioned
in the plan; also mark indices for navigational access */
idx = csb_tail->csb_idx;
for (i = 0; i < csb_tail->csb_indices; i++) {
if (access_type) {
for (arg = access_type->nod_arg, end = arg + plan_count;
arg < end; arg += 3) {
if (relation_id != (SSHORT) * arg)
/* index %s cannot be used in the specified plan */
ERR_post(gds_index_unused, gds_arg_string, *(arg + 2), 0);
if (idx->idx_id == (USHORT) * (arg + 1))
if (access_type->nod_type == nod_navigational)
idx->idx_runtime_flags |= idx_plan_navigate;
else /* nod_indices */
break;
}
if (arg == end)
idx->idx_runtime_flags |= idx_plan_dont_use;
}
else
idx->idx_runtime_flags |= idx_plan_dont_use;
idx = NEXT_IDX(idx->idx_rpt, idx->idx_count);
}
}
static SSHORT match_index(TDBB tdbb,
OPT opt,
SSHORT stream, JRD_NOD boolean, IDX * idx)
{
/**************************************
*
* m a t c h _ i n d e x
*
**************************************
*
* Functional description
* Match a boolean against an index location lower and upper
* bounds. Return the number of relational nodes that were
* matched. In ODS versions prior to 7, descending indexes
* were not reliable and will not be used.
*
**************************************/
JRD_NOD match, value;
SSHORT i, forward, count;
Opt::opt_repeat * ptr;
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(boolean, type_nod);
SET_TDBB(tdbb);
if (boolean->nod_count < 2)
return 0;
if (boolean->nod_type == nod_and)
return match_index(tdbb, opt, stream, boolean->nod_arg[0], idx) +
match_index(tdbb, opt, stream, boolean->nod_arg[1], idx);
forward = TRUE;
count = 0;
match = boolean->nod_arg[0];
value = boolean->nod_arg[1];
#ifdef EXPRESSION_INDICES
if (idx->idx_expression) {
/* see if one side or the other is matchable to the index expression */
if (!expression_equal(tdbb, idx->idx_expression, match) ||
!computable(opt->opt_csb, value, stream, true))
{
if (expression_equal(tdbb, idx->idx_expression, value) &&
computable(opt->opt_csb, match, stream, true))
{
match = boolean->nod_arg[1];
value = boolean->nod_arg[0];
}
else
return 0;
}
}
else {
#endif
/* If left side is not a field, swap sides.
If left side is still not a field, give up */
if (match->nod_type != nod_field ||
(USHORT) match->nod_arg[e_fld_stream] != stream ||
!computable(opt->opt_csb, value, stream, true))
{
match = value;
value = boolean->nod_arg[0];
if (match->nod_type != nod_field ||
(USHORT) match->nod_arg[e_fld_stream] != stream ||
!computable(opt->opt_csb, value, stream, true))
{
return 0;
}
forward = FALSE;
}
#ifdef EXPRESSION_INDICES
}
#endif
/* match the field to an index, if possible, and save the value to be matched
as either the lower or upper bound for retrieval, or both */
for (i = 0, ptr = opt->opt_rpt; i < idx->idx_count; i++, ptr++) {
if
#ifdef EXPRESSION_INDICES
(idx->idx_expression ||
#endif
((USHORT) match->nod_arg[e_fld_id] == idx->idx_rpt[i].idx_field)
#ifdef EXPRESSION_INDICES
)
#endif
{
++count;
/* AB: If we have already an exact match don't
override it with worser matches, but increment the
count so that the node will be marked as matched! */
if (ptr->opt_match && ptr->opt_match->nod_type == nod_eql) {
break;
}
switch (boolean->nod_type) {
case nod_between:
if (!forward ||
!computable(opt->opt_csb,
boolean->nod_arg[2],
stream,
true))
{
return 0;
}
ptr->opt_lower = value;
ptr->opt_upper = boolean->nod_arg[2];
ptr->opt_match = boolean;
break;
case nod_eql:
ptr->opt_lower = ptr->opt_upper = value;
ptr->opt_match = boolean;
break;
case nod_gtr:
case nod_geq:
if (forward) {
ptr->opt_lower = value;
}
else {
ptr->opt_upper = value;
}
ptr->opt_match = boolean;
break;
case nod_lss:
case nod_leq:
if (forward) {
ptr->opt_upper = value;
}
else {
ptr->opt_lower = value;
}
ptr->opt_match = boolean;
break;
default: /* Shut up compiler warnings */
break;
}
}
}
return count;
}
static BOOLEAN match_indices(TDBB tdbb,
OPT opt,
SSHORT stream, JRD_NOD boolean, IDX * idx)
{
/**************************************
*
* m a t c h _ i n d i c e s
*
**************************************
*
* Functional description
* Match a boolean against an index location lower and upper
* bounds. Return the number of relational nodes that were
* matched. In ODS versions prior to 7, descending indexes
* were not reliable and will not be used.
*
**************************************/
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(boolean, type_nod);
SET_TDBB(tdbb);
if (boolean->nod_count < 2) {
return FALSE;
}
if (boolean->nod_type == nod_or) {
if (match_indices(tdbb, opt, stream, boolean->nod_arg[0], idx) &&
match_indices(tdbb, opt, stream, boolean->nod_arg[1], idx)) {
opt->opt_rpt->opt_match = NULL;
return TRUE;
}
}
else {
if (match_index(tdbb, opt, stream, boolean, idx)) {
opt->opt_rpt->opt_match = NULL;
return TRUE;
}
}
opt->opt_rpt->opt_match = NULL;
opt->opt_rpt->opt_upper = NULL;
opt->opt_rpt->opt_lower = NULL;
return FALSE;
}
static USHORT nav_rsb_size(RSB rsb, USHORT key_length, USHORT size)
{
/**************************************
*
* n a v _ r s b _ s i z e
*
**************************************
*
* Functional description
* Calculate the size of a navigational rsb.
*
**************************************/
DEV_BLKCHK(rsb, type_rsb);
#ifdef SCROLLABLE_CURSORS
/* allocate extra impure area to hold the current key,
plus an upper and lower bound key value, for a total
of three times the key length for the index */
size += sizeof(struct irsb_nav) + 3 * key_length;
#else
size += sizeof(struct irsb_nav) + 2 * key_length;
#endif
size = FB_ALIGN(size, ALIGNMENT);
/* make room for an idx structure to describe the index
that was used to generate this rsb */
if (rsb->rsb_type == rsb_navigate)
rsb->rsb_arg[RSB_NAV_idx_offset] = (RSB) (SLONG) size;
size += sizeof(struct idx);
return size;
}
static BOOLEAN node_equality(JRD_NOD node1, JRD_NOD node2)
{
/**************************************
*
* n o d e _ e q u a l i t y
*
**************************************
*
* Functional description
* Test two field node pointers for symbolic equality.
*
**************************************/
DEV_BLKCHK(node1, type_nod);
DEV_BLKCHK(node2, type_nod);
if (!node1 || !node2)
return FALSE;
if (node1->nod_type != node2->nod_type)
return FALSE;
if (node1 == node2)
return TRUE;
switch (node1->nod_type) {
case nod_field:
return (node1->nod_arg[e_fld_stream] == node2->nod_arg[e_fld_stream]
&& node1->nod_arg[e_fld_id] == node2->nod_arg[e_fld_id]);
case nod_eql:
if (node_equality(node1->nod_arg[0], node2->nod_arg[0])
&& node_equality(node1->nod_arg[1], node2->nod_arg[1]))
return TRUE;
if (node_equality(node1->nod_arg[0], node2->nod_arg[1])
&& node_equality(node1->nod_arg[1], node2->nod_arg[0]))
return TRUE;
return FALSE;
case nod_gtr:
case nod_geq:
case nod_leq:
case nod_lss:
case nod_matches:
case nod_contains:
case nod_like:
default:
break;
}
return FALSE;
}
static JRD_NOD optimize_like(TDBB tdbb, JRD_NOD like_node)
{
/**************************************
*
* o p t i m i z e _ l i k e
*
**************************************
*
* Functional description
* Optimize a LIKE expression, if possible,
* into a "starting with" AND a "like". This
* will allow us to use the index for the
* starting with, and the LIKE can just tag
* aSLONG for the ride.
* But on the ride it does useful work, consider
* match LIKE "ab%c". This is optimized by adding
* AND starting_with "ab", but the LIKE clause is
* still needed.
*
**************************************/
JRD_NOD search_node, escape_node, node;
DSC *search_desc, *escape_desc;
UCHAR *p, *end, *q, *p_start;
SSHORT count;
LIT literal;
USHORT ch, escape_ch;
USHORT p_count;
UCHAR tmp_buffer[32]; /* large enough to hold 1 ch of escape string */
TextType *text_obj;
SET_TDBB(tdbb);
DEV_BLKCHK(like_node, type_nod);
search_node = like_node->nod_arg[1];
escape_node = (like_node->nod_count > 2) ? like_node->nod_arg[2] : NULL;
/* if the matching string or the escape string can't
be evaluated at compile time, forget it */
if ((search_node->nod_type != nod_literal) ||
(escape_node && escape_node->nod_type != nod_literal))
return NULL;
search_desc = &((LIT) search_node)->lit_desc;
if (escape_node)
escape_desc = &((LIT) escape_node)->lit_desc;
/* if either is not a character expression, forget it */
if ((search_desc->dsc_dtype > dtype_any_text) ||
(escape_node && escape_desc->dsc_dtype > dtype_any_text))
return NULL;
/* Get the escape character, if any */
text_obj = NULL;
if (escape_node)
{
/* Ensure escape string is same character set as search string
* Error report on Overflow is OK, as sql only allows a single
* character escape string */
const char* p2;
p_count =
MOV_make_string(escape_desc, INTL_TTYPE(search_desc), &p2,
reinterpret_cast < vary * >(tmp_buffer),
sizeof(tmp_buffer));
p = reinterpret_cast<UCHAR*>(const_cast<char*>(p2));
/* Now get first character from escape string */
escape_ch =
INTL_getch(tdbb, &text_obj,
INTL_TTYPE(search_desc), &p, &p_count);
}
/* if the first character is a wildcard char, forget it */
p = search_desc->dsc_address;
p_count = search_desc->dsc_length;
ch =
INTL_getch(tdbb, &text_obj,
INTL_TTYPE(search_desc), &p, &p_count);
if ((!escape_node || ch != escape_ch)
&& (ch == SQL_MATCH_1_CHAR || ch == SQL_MATCH_ANY_CHARS))
return NULL;
/* allocate a literal node to store the starting with string;
assume it will be shorter than the search string */
count =
lit_delta + (search_desc->dsc_length + sizeof(int) -
1) / sizeof(int);
node = PAR_make_node(tdbb, count);
node->nod_type = nod_literal;
node->nod_count = 0;
literal = (LIT) node;
literal->lit_desc = *search_desc;
literal->lit_desc.dsc_address = q = literal->lit_data;
/* copy the string into the starting with literal, up to the first wildcard character */
p_count = search_desc->dsc_length;
for (p = search_desc->dsc_address, end =
p + search_desc->dsc_length; p < end;)
{
/* if there are escape characters, skip past them and
don't treat the next char as a wildcard */
p_start = p;
ch =
INTL_getch(tdbb, &text_obj,
INTL_TTYPE(search_desc), &p, &p_count);
if (escape_node && (ch == escape_ch))
{
/* Check for Escape character at end of string */
if (!(p < end))
break;
p_start = p;
ch =
INTL_getch(tdbb, &text_obj,
INTL_TTYPE(search_desc), &p, &p_count);
}
else if (ch == SQL_MATCH_1_CHAR || ch == SQL_MATCH_ANY_CHARS)
{
break;
}
/* copy the bytes of the character */
while (p_start < p) {
*q++ = *p_start++;
}
}
literal->lit_desc.dsc_length = q - literal->lit_desc.dsc_address;
return node;
}
#ifdef OPT_DEBUG
static void print_order(OPT opt,
USHORT position, double cardinality, double cost)
{
/**************************************
*
* p r i n t _ o r d e r
*
**************************************
*
* Functional description
*
**************************************/
Opt::opt_repeat * tail, *order_end;
DEV_BLKCHK(opt, type_opt);
order_end = opt->opt_rpt + position;
ib_fprintf(opt_debug_file, "print_order() -- position %2.2d: ", position);
for (tail = opt->opt_rpt; tail < order_end; tail++)
ib_fprintf(opt_debug_file, "stream %2.2d, ", tail->opt_stream);
ib_fprintf(opt_debug_file, "\n\t\t\tcardinality: %g\tcost: %g\n",
cardinality, cost);
}
#endif
static USHORT river_count(USHORT count, JRD_NOD * class_)
{
/**************************************
*
* r i v e r _ c o u n t
*
**************************************
*
* Functional description
* Given an sort/merge join equivalence class (vector of node pointers
* of representative values for rivers), return the count of rivers
* with values.
*
**************************************/
USHORT i, cnt;
#ifdef DEV_BUILD
if (*class_) {
DEV_BLKCHK(*class_, type_nod);
}
#endif
cnt = 0;
for (i = 0; i < count; i++, class_++)
if (*class_) {
cnt++;
DEV_BLKCHK(*class_, type_nod);
}
return cnt;
}
static BOOLEAN river_reference(RIV river, JRD_NOD node)
{
/**************************************
*
* r i v e r _ r e f e r e n c e
*
**************************************
*
* Functional description
* See if a value node is a reference to a given river. Return
* TRUE or FALSE.
*
**************************************/
UCHAR *streams, *end;
DEV_BLKCHK(river, type_riv);
DEV_BLKCHK(node, type_nod);
if (node->nod_type != nod_field)
return FALSE;
for (streams = river->riv_streams, end =
streams + river->riv_count; streams < end; streams++)
if ((USHORT) node->nod_arg[e_fld_stream] == *streams)
return TRUE;
return FALSE;
}
static BOOLEAN search_stack(JRD_NOD node, LLS stack)
{
/**************************************
*
* s e a r c h _ s t a c k
*
**************************************
*
* Functional description
* Search a stack for the presence of a particular value.
*
**************************************/
DEV_BLKCHK(node, type_nod);
DEV_BLKCHK(stack, type_lls);
for (; stack; stack = stack->lls_next)
if (node_equality(node, (JRD_NOD) stack->lls_object))
return TRUE;
return FALSE;
}
static void set_active(OPT opt, RIV river)
{
/**************************************
*
* s e t _ a c t i v e
*
**************************************
*
* Functional description
* Set a group of streams active.
*
**************************************/
CSB csb;
UCHAR *streams, *end;
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(river, type_riv);
csb = opt->opt_csb;
for (streams = river->riv_streams, end =
streams + river->riv_count; streams < end; streams++)
csb->csb_rpt[*streams].csb_flags |= csb_active;
}
static void set_direction(JRD_NOD from_clause, JRD_NOD to_clause)
{
/**************************************
*
* s e t _ d i r e c t i o n
*
**************************************
*
* Functional description
* Update the direction of a GROUP BY, DISTINCT, or ORDER BY
* clause to the same direction as another clause.
*
**************************************/
JRD_NOD *from_ptr, *to_ptr, *end;
DEV_BLKCHK(from_clause, type_nod);
DEV_BLKCHK(to_clause, type_nod);
/* all three clauses are allocated with twice the number of arguments to
leave room at the end for an ascending/descending flag, one for each field */
from_ptr = from_clause->nod_arg + from_clause->nod_count;
to_ptr = to_clause->nod_arg + to_clause->nod_count;
for (end = from_ptr + from_clause->nod_count; from_ptr < end; from_ptr++)
*to_ptr++ = *from_ptr;
}
static void set_inactive(OPT opt, RIV river)
{
/**************************************
*
* s e t _ i n a c t i v e
*
**************************************
*
* Functional description
* Set a group of streams inactive.
*
**************************************/
CSB csb;
UCHAR *streams, *end;
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(river, type_riv);
csb = opt->opt_csb;
for (streams = river->riv_streams, end =
streams + river->riv_count; streams < end; streams++)
csb->csb_rpt[*streams].csb_flags &= ~csb_active;
}
static void set_made_river(OPT opt, RIV river)
{
/**************************************
*
* s e t _ m a d e _ r i v e r
*
**************************************
*
* Functional description
* Mark all the streams in a river with the csb_made_river flag.
*
* A stream with this flag set, incicates that this stream has
* already been made into a river. Currently, this flag is used
* in computable() to decide if we can use the an index to
* optimise retrieving the streams involved in the conjunct.
*
* We can use an index in retrieving the streams involved in a
* conjunct if both of the streams are currently active or have
* been processed (and made into rivers) before.
*
**************************************/
CSB csb;
UCHAR *streams, *end;
DEV_BLKCHK(opt, type_opt);
DEV_BLKCHK(river, type_riv);
csb = opt->opt_csb;
for (streams = river->riv_streams, end =
streams + river->riv_count; streams < end; streams++)
csb->csb_rpt[*streams].csb_flags |= csb_made_river;
}
static void set_position(JRD_NOD from_clause, JRD_NOD to_clause, JRD_NOD map)
{
/**************************************
*
* s e t _ p o s i t i o n
*
**************************************
*
* Functional description
* Update the fields in a GROUP BY, DISTINCT, or ORDER BY
* clause to the same position as another clause, possibly
* using a mapping between the streams.
*
**************************************/
JRD_NOD *from_ptr, *to_ptr;
JRD_NOD *from_end, *to_end;
JRD_NOD *to_swap, swap;
DEV_BLKCHK(from_clause, type_nod);
DEV_BLKCHK(to_clause, type_nod);
DEV_BLKCHK(map, type_nod);
/* Track the position in the from list with "to_swap", and find the corresponding
field in the from list with "to_ptr", then swap the two fields. By the time
we get to the end of the from list, all fields in the to list will be reordered. */
to_swap = to_clause->nod_arg;
for (from_ptr = from_clause->nod_arg, from_end =
from_ptr + from_clause->nod_count; from_ptr < from_end; from_ptr++) {
for (to_ptr = to_clause->nod_arg, to_end =
to_ptr + from_clause->nod_count; to_ptr < to_end; to_ptr++) {
if ((map && map_equal(*to_ptr, *from_ptr, map))
|| (!map
&& (*from_ptr)->nod_arg[e_fld_stream] ==
(*to_ptr)->nod_arg[e_fld_stream]
&& (*from_ptr)->nod_arg[e_fld_id] ==
(*to_ptr)->nod_arg[e_fld_id])) {
swap = *to_swap;
*to_swap = *to_ptr;
*to_ptr = swap;
}
}
to_swap++;
}
}
static void set_rse_inactive(CSB csb, RSE rse)
{
/***************************************************
*
* s e t _ r s e _ i n a c t i v e
*
***************************************************
*
* Functional Description:
* Set all the streams involved in an rse as inactive. Do it recursively.
*
***************************************************/
JRD_NOD node, *ptr, *end;
SSHORT stream;
for (ptr = rse->rse_relation, end = ptr + rse->rse_count;
ptr < end; ptr++) {
node = *ptr;
if (node->nod_type != nod_rse) {
stream = (USHORT) node->nod_arg[STREAM_INDEX(node)];
csb->csb_rpt[stream].csb_flags &= ~csb_active;
}
else
set_rse_inactive(csb, (RSE) node);
}
}
static void sort_indices_by_selectivity(csb_repeat * csb_tail)
{
/***************************************************
*
* s o r t _ i n d i c e s _ b y _ s e l e c t i v i t y
*
***************************************************
*
* Functional Description:
* Sort indices based on there selectivity.
* Lowest selectivy as first, highest as last.
*
***************************************************/
IDX *idx, *selected_idx;
USHORT i, j;
IDX idx_sort[MAX_IDX];
float selectivity;
BOOLEAN same_selectivity;
if (csb_tail->csb_plan) {
return;
}
/* Walk through the indices and sort them into into idx_sort
where idx_sort[0] contains the lowest selectivity (best) and
idx_sort[csb_tail->csb_indices - 1] the highest (worst) */
if (csb_tail->csb_idx && (csb_tail->csb_indices > 1)) {
for (j = 0; j < csb_tail->csb_indices; j++) {
selectivity = 1; /* Maximum selectivity is 1 (when all keys are the same) */
idx = csb_tail->csb_idx;
for (i = 0; i < csb_tail->csb_indices; i++) {
/* Prefer ASC indices in the case of almost the same selectivities */
if (selectivity > idx->idx_selectivity) {
same_selectivity = ((selectivity - idx->idx_selectivity) <= 0.00001);
}
else {
same_selectivity = ((idx->idx_selectivity - selectivity) <= 0.00001);
}
if (!(idx->idx_runtime_flags & idx_marker) &&
(idx->idx_selectivity <= selectivity) &&
!((idx->idx_flags & idx_descending) && same_selectivity)) {
selectivity = idx->idx_selectivity;
selected_idx = idx;
}
idx = NEXT_IDX(idx->idx_rpt, idx->idx_count);
}
if (!selected_idx) {
idx = csb_tail->csb_idx;
for (i = 0; i < csb_tail->csb_indices; i++) {
if (!(idx->idx_runtime_flags & idx_marker)) {
selected_idx = idx;
break;
}
idx = NEXT_IDX(idx->idx_rpt, idx->idx_count);
}
}
selected_idx->idx_runtime_flags |= idx_marker;
MOVE_FAST(selected_idx, &idx_sort[j], sizeof(IDX));
}
/* Finally store the right order in cbs_tail->csb_idx */
idx = csb_tail->csb_idx;
for (j = 0; j < csb_tail->csb_indices; j++) {
idx->idx_runtime_flags &= ~idx_marker;
MOVE_FAST(&idx_sort[j], idx, sizeof(IDX));
idx = NEXT_IDX(idx->idx_rpt, idx->idx_count);
}
}
}
static SSHORT sort_indices_by_priority(csb_repeat * csb_tail,
IDX ** idx_walk,
SLONG * idx_priority_level)
{
/***************************************************
*
* s o r t _ i n d i c e s _ b y _ p r i o r i t y
*
***************************************************
*
* Functional Description:
* Sort indices based on the priority level.
*
***************************************************/
IDX * idx_csb[MAX_INDICES];
memcpy(idx_csb, idx_walk, sizeof(idx_csb));
SSHORT idx_walk_count = 0;
float selectivity = 1; /* Real maximum selectivity possible is 1 */
for (SSHORT i = 0; i < csb_tail->csb_indices; i++)
{
SSHORT last_idx = -1;
SLONG last_priority_level = 0;
for (SSHORT j = csb_tail->csb_indices - 1; j >= 0; j--)
{
if (!(idx_priority_level[j] == 0) &&
(idx_priority_level[j] >= last_priority_level))
{
last_priority_level = idx_priority_level[j];
last_idx = j;
}
}
if (last_idx >= 0) {
/* dimitr: Empirically, it's better to use less indices with very good selectivity
than using all available ones. Here we're deciding how many indices we
should use. Since all indices are already ordered by their selectivity,
it becomes a trivial task. But note that indices with zero (unknown)
selectivity are always used, because we don't have a clue how useful
they are in fact, so we should be optimistic in this case. Unique
indices are also always used, because they are good by definition,
regardless of their (probably old) selectivity values. */
IDX *idx = idx_csb[last_idx];
bool should_be_used = true;
if (!(idx->idx_flags & idx_unique) && idx->idx_selectivity && !(csb_tail->csb_plan)) {
if (selectivity * SELECTIVITY_THRESHOLD_FACTOR < idx->idx_selectivity) {
should_be_used = false;
}
selectivity = idx->idx_selectivity;
}
idx_priority_level[last_idx] = 0; /* Mark as used by setting priority_level to 0 */
if (should_be_used) {
idx_walk[idx_walk_count] = idx_csb[last_idx];
idx_walk_count++;
}
}
}
return idx_walk_count;
}