Skip to content

File query_planner.c

File List > cubrid > src > optimizer > query_planner.c

Go to the documentation of this file

/*
 * Copyright 2008 Search Solution Corporation
 * Copyright 2016 CUBRID Corporation
 *
 *  Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

/*
 * query_planner.c - Plan descriptors
 */

#ident "$Id$"

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#if !defined(WINDOWS)
#include <values.h>
#endif /* !WINDOWS */
#include "jansson.h"

#include "parser.h"
#include "object_primitive.h"
#include "optimizer.h"
#include "query_planner.h"
#include "query_graph.h"
#include "environment_variable.h"
#include "misc_string.h"
#include "system_parameter.h"
#include "parser.h"
#include "parser_message.h"
#include "intl_support.h"
#include "storage_common.h"
#include "xasl_analytic.hpp"
#include "xasl_generation.h"
#include "schema_manager.h"
#include "network_interface_cl.h"
#include "dbtype.h"
#include "regu_var.hpp"

#define TEST_DUMP_PLAN_SCAN_COST 0
#define TEST_DUMP_PLAN_SORT_COST 0
#define TEST_DUMP_PLAN_JOIN_COST 0
#define TEST_DUMP_PLAN_FOLLOW_COST 0

#define TEST_HASH_JOIN_ENABLE 0
#define TEST_HASH_JOIN_FORCE_ENABLE 0

#define INDENT_INCR     4
#define INDENT_FMT      "%*c"
#define TITLE_WIDTH     7
#define TITLE_FMT       "%-" __STR(TITLE_WIDTH) "s"
#define INDENTED_TITLE_FMT  INDENT_FMT TITLE_FMT
#define __STR(n)        __VAL(n)
#define __VAL(n)        #n
#define SORT_SPEC_FMT(spec) \
  "%d %s %s", (spec)->pos_descr.pos_no + 1, \
  ((spec)->s_order == S_ASC ? "asc" : "desc"), \
  ((spec)->s_nulls == S_NULLS_FIRST ? "nulls first" : "nulls last")

#define VALID_INNER(plan)   (plan->well_rooted || \
                 (plan->plan_type == QO_PLANTYPE_SORT))

#define TEMP_SETUP_COST 5.0
#define QO_CPU_WEIGHT 0.0025
#define ISCAN_OID_ACCESS_OVERHEAD 20    /* need to be adjusted */
#define MJ_CPU_OVERHEAD_FACTOR 20
#define HJ_BUILD_CPU_OVERHEAD_FACTOR 30
#define HJ_PROBE_CPU_OVERHEAD_FACTOR 20
#define HJ_FILE_IO_WEIGHT 0.5   /* Unused */
#define ISCAN_IO_HIT_RATIO 0.5
#define SSCAN_DEFAULT_CARD 100
#define GUESSED_BIND_LIMIT_CARD 2000    /* When limit is a bind variable, assume that fewer rows will be assigned. */

#define RBO_CHECK_COST 50
#define RBO_CHECK_RATIO 1.2
#define RBO_CHECK_LIMIT_RATIO 10

#define qo_scan_walk    qo_generic_walk
#define qo_worst_walk   qo_generic_walk

#define qo_generic_free NULL
#define qo_sort_free    qo_generic_free
#define qo_follow_free  qo_generic_free
#define qo_worst_free   qo_generic_free

#define QO_INFO_INDEX(_M_offset, _bitset)  \
    (_M_offset + (unsigned int)(BITPATTERN(_bitset) & planner->node_mask))

#define QO_IS_LIMIT_NODE(env, node) \
  (BITSET_MEMBER (QO_ENV_SORT_LIMIT_NODES ((env)), QO_NODE_IDX ((node))))

typedef enum
{ JOIN_RIGHT_ORDER, JOIN_OPPOSITE_ORDER } JOIN_ORDER_TRY;

struct ndv_info
{
  QO_ENV *env;
  int total_ndv;
  BITSET seg_bitset;
};
typedef struct ndv_info NDV_INFO;

typedef int (*QO_WALK_FUNCTION) (QO_PLAN *, void *);

static int infos_allocated = 0;
static int infos_deallocated = 0;

static int qo_plans_allocated;
static int qo_plans_deallocated;
static int qo_plans_malloced;
static int qo_plans_demalloced;
static int qo_accumulating_plans;
static int qo_next_tmpfile;

static QO_PLAN *qo_plan_free_list;

static QO_PLAN *qo_scan_new (QO_INFO *, QO_NODE *, QO_SCANMETHOD);
static void qo_scan_free (QO_PLAN *);
static void qo_join_free (QO_PLAN *);

static void qo_scan_fprint (QO_PLAN *, FILE *, int);
static void qo_sort_fprint (QO_PLAN *, FILE *, int);
static void qo_join_fprint (QO_PLAN *, FILE *, int);
static void qo_hjoin_fprint (QO_PLAN *, FILE *, int);
static void qo_follow_fprint (QO_PLAN *, FILE *, int);
static void qo_worst_fprint (QO_PLAN *, FILE *, int);

static void qo_scan_info (QO_PLAN *, FILE *, int);
static void qo_sort_info (QO_PLAN *, FILE *, int);
static void qo_join_info (QO_PLAN *, FILE *, int);
static void qo_follow_info (QO_PLAN *, FILE *, int);
static void qo_worst_info (QO_PLAN *, FILE *, int);

void qo_plan_lite_print (QO_PLAN *, FILE *, int);
static void qo_plan_del_ref_func (QO_PLAN * plan, void *ignore);

static void qo_generic_walk (QO_PLAN *, void (*)(QO_PLAN *, void *), void *, void (*)(QO_PLAN *, void *), void *);
static void qo_plan_print_sort_spec_helper (PT_NODE *, bool, FILE *, int);
static void qo_plan_print_sort_spec (QO_PLAN *, FILE *, int);
static void qo_plan_print_costs (QO_PLAN *, FILE *, int);
static void qo_plan_print_projected_segs (QO_PLAN *, FILE *, int);
static void qo_plan_print_sarged_terms (QO_PLAN *, FILE *, int);
static void qo_plan_print_outer_join_terms (QO_PLAN *, FILE *, int);
static void qo_plan_print_subqueries (QO_PLAN *, FILE *, int);
static void qo_plan_print_analytic_eval (QO_PLAN *, FILE *, int);
static void qo_sort_walk (QO_PLAN *, void (*)(QO_PLAN *, void *), void *, void (*)(QO_PLAN *, void *), void *);
static void qo_join_walk (QO_PLAN *, void (*)(QO_PLAN *, void *), void *, void (*)(QO_PLAN *, void *), void *);
static void qo_follow_walk (QO_PLAN *, void (*)(QO_PLAN *, void *), void *, void (*)(QO_PLAN *, void *), void *);

static void qo_plan_compute_cost (QO_PLAN *);
static void qo_plan_compute_subquery_cost (PT_NODE *, double *, double *);
static void qo_sscan_cost (QO_PLAN *);
static void qo_iscan_cost (QO_PLAN *);
static void qo_sort_cost (QO_PLAN *);
static void qo_mjoin_cost (QO_PLAN *);
static void qo_nljoin_cost (QO_PLAN *);
static void qo_hjoin_cost (QO_PLAN *);
static void qo_follow_cost (QO_PLAN *);
static void qo_worst_cost (QO_PLAN *);
static void qo_zero_cost (QO_PLAN *);

static void qo_estimate_ngroups (QO_PLAN *, SORT_TYPE);
static int qo_get_group_ndv (QO_PLAN *, SORT_TYPE);
static double qo_estimate_ndv (double N, double p, double n);

static QO_PLAN *qo_top_plan_new (QO_PLAN *);

static double log3 (double);

static void qo_init_planvec (QO_PLANVEC *);
static void qo_uninit_planvec (QO_PLANVEC *);
static QO_PLAN_COMPARE_RESULT qo_check_planvec (QO_PLANVEC *, QO_PLAN *);
static QO_PLAN_COMPARE_RESULT qo_cmp_planvec (QO_PLANVEC *, QO_PLAN *);
static QO_PLAN *qo_find_best_plan_on_planvec (QO_PLANVEC *, double);

static void qo_info_nodes_init (QO_ENV *);
static QO_INFO *qo_alloc_info (QO_PLANNER *, BITSET *, BITSET *, BITSET *, double, double);
static void qo_free_info (QO_INFO *);
static void qo_detach_info (QO_INFO *);
static void qo_dump_planvec (QO_PLANVEC *, FILE *, int);
static void qo_dump_info (QO_INFO *, FILE *);
static void qo_dump_planner_info (QO_PLANNER *, QO_PARTITION *, FILE *);

static void qo_get_term_hit_prob (QO_TERM * term, QO_INFO * head_info, QO_INFO * tail_info, QO_ENV * env,
                  double *out_head_factor, double *out_tail_factor);
static void planner_visit_node (QO_PLANNER *, QO_PARTITION *, PT_HINT_ENUM, QO_NODE *, QO_NODE *, BITSET *, BITSET *,
                BITSET *, BITSET *, BITSET *, BITSET *, BITSET *, int);
static double planner_nodeset_join_cost (QO_PLANNER *, BITSET *);
static void planner_permutate (QO_PLANNER *, QO_PARTITION *, PT_HINT_ENUM, QO_NODE *, BITSET *, BITSET *, BITSET *,
                   BITSET *, BITSET *, BITSET *, BITSET *, int, int *);

static QO_PLAN *qo_find_best_nljoin_inner_plan_on_info (QO_PLAN *, QO_INFO *, JOIN_TYPE, int);
static QO_PLAN *qo_find_best_plan_on_info (QO_INFO *, QO_EQCLASS *, double);
static bool qo_check_new_best_plan_on_info (QO_INFO *, QO_PLAN *);
static int qo_check_plan_on_info (QO_INFO *, QO_PLAN *);
static int qo_examine_idx_join (QO_INFO *, JOIN_TYPE, QO_INFO *, QO_INFO *, BITSET *, BITSET *, BITSET *);
static int qo_examine_nl_join (QO_INFO *, JOIN_TYPE, QO_INFO *, QO_INFO *, BITSET *, BITSET *, BITSET *, BITSET *,
                   BITSET *, int, BITSET *);
static int qo_examine_merge_join (QO_INFO *, JOIN_TYPE, QO_INFO *, QO_INFO *, BITSET *, BITSET *, BITSET *, BITSET *,
                  BITSET *);
static int qo_examine_hash_join (QO_INFO *, JOIN_TYPE, QO_INFO *, QO_INFO *, BITSET *, BITSET *, BITSET *, BITSET *,
                 BITSET *);
static int qo_examine_correlated_index (QO_INFO *, JOIN_TYPE, QO_INFO *, QO_INFO *, BITSET *, BITSET *, BITSET *);
static int qo_examine_follow (QO_INFO *, QO_TERM *, QO_INFO *, BITSET *, BITSET *);
static void qo_compute_projected_segs (QO_PLANNER *, BITSET *, BITSET *, BITSET *);
static int qo_compute_projected_size (QO_PLANNER *, BITSET *);


static QO_PLANNER *qo_alloc_planner (QO_ENV *);
static void qo_clean_planner (QO_PLANNER *);
static QO_INFO *qo_search_partition_join (QO_PLANNER *, QO_PARTITION *, BITSET *);
static QO_PLAN *qo_search_partition (QO_PLANNER *, QO_PARTITION *, QO_EQCLASS *, BITSET *);
static QO_PLAN *qo_search_planner (QO_PLANNER *);
static void sort_partitions (QO_PLANNER *);
static QO_PLAN *qo_combine_partitions (QO_PLANNER *, BITSET *);
static int qo_generate_join_index_scan (QO_INFO *, JOIN_TYPE, QO_PLAN *, QO_INFO *, QO_NODE *, QO_NODE_INDEX_ENTRY *,
                    BITSET *, BITSET *, BITSET *, BITSET *);
static void qo_generate_seq_scan (QO_INFO *, QO_NODE *);
static int qo_generate_index_scan (QO_INFO *, QO_NODE *, QO_NODE_INDEX_ENTRY *, int);
static int qo_generate_loose_index_scan (QO_INFO *, QO_NODE *, QO_NODE_INDEX_ENTRY *);
static int qo_generate_sort_limit_plan (QO_ENV *, QO_INFO *, QO_PLAN *);
static void qo_plan_add_to_free_list (QO_PLAN *, void *ignore);
static void qo_plans_teardown (QO_ENV * env);
static void qo_plans_init (QO_ENV * env);
static void qo_plan_walk (QO_PLAN *, void (*)(QO_PLAN *, void *), void *, void (*)(QO_PLAN *, void *), void *);
static QO_PLAN *qo_plan_finalize (QO_PLAN *);
static QO_PLAN *qo_plan_order_by (QO_PLAN *, QO_EQCLASS *);
static QO_PLAN_COMPARE_RESULT qo_plan_cmp_prefer_covering_index (QO_PLAN *, QO_PLAN *);
static void qo_plan_fprint (QO_PLAN *, FILE *, int, const char *);
static QO_PLAN_COMPARE_RESULT qo_plan_cmp (QO_PLAN *, QO_PLAN *);
static QO_PLAN_COMPARE_RESULT qo_plan_iscan_terms_cmp (QO_PLAN * a, QO_PLAN * b);
static QO_PLAN_COMPARE_RESULT qo_index_covering_plans_cmp (QO_PLAN *, QO_PLAN *);
static QO_PLAN_COMPARE_RESULT qo_order_by_skip_plans_cmp (QO_PLAN *, QO_PLAN *);
static QO_PLAN_COMPARE_RESULT qo_group_by_skip_plans_cmp (QO_PLAN *, QO_PLAN *);
static QO_PLAN_COMPARE_RESULT qo_multi_range_opt_plans_cmp (QO_PLAN *, QO_PLAN *);
static void qo_plan_free (QO_PLAN *);
static QO_PLAN *qo_plan_malloc (QO_ENV *);
static const char *qo_term_string (QO_TERM *, char *buf);
static QO_PLAN *qo_worst_new (QO_ENV *);
static QO_PLAN *qo_cp_new (QO_INFO *, QO_PLAN *, QO_PLAN *, BITSET *, BITSET *);
static QO_PLAN *qo_follow_new (QO_INFO *, QO_PLAN *, QO_TERM *, BITSET *, BITSET *);
static QO_PLAN *qo_join_new (QO_INFO *, JOIN_TYPE, QO_JOINMETHOD, QO_PLAN *, QO_PLAN *, BITSET *, BITSET *, BITSET *,
                 BITSET *, BITSET *, BITSET *);
static QO_PLAN *qo_sort_new (QO_PLAN *, QO_EQCLASS *, SORT_TYPE);
static QO_PLAN *qo_seq_scan_new (QO_INFO *, QO_NODE *);
static QO_PLAN *qo_index_scan_new (QO_INFO *, QO_NODE *, QO_NODE_INDEX_ENTRY *, QO_SCANMETHOD, BITSET *, BITSET *);
static int qo_has_is_not_null_term (QO_NODE * node);

static bool qo_validate_index_term_notnull (QO_ENV * env, QO_INDEX_ENTRY * index_entryp);
static bool qo_validate_index_attr_notnull (QO_ENV * env, QO_INDEX_ENTRY * index_entryp, PT_NODE * col);
static int qo_validate_index_for_orderby (QO_ENV * env, QO_NODE_INDEX_ENTRY * ni_entryp);
static int qo_validate_index_for_groupby (QO_ENV * env, QO_NODE_INDEX_ENTRY * ni_entryp);
static PT_NODE *qo_search_isnull_key_expr (PARSER_CONTEXT * parser, PT_NODE * tree, void *arg, int *continue_walk);
static PT_NODE *qo_get_col_product_ndv (PARSER_CONTEXT * parser, PT_NODE * tree, void *arg, int *continue_walk);
static bool qo_check_orderby_skip_descending (QO_PLAN * plan);
static bool qo_check_skip_term (QO_ENV * env, BITSET visited_segs, QO_TERM * term, BITSET * visited_terms,
                BITSET * cur_visited_terms);
static bool qo_check_groupby_skip_descending (QO_PLAN * plan, PT_NODE * list);

static int qo_walk_plan_tree (QO_PLAN * plan, QO_WALK_FUNCTION f, void *arg);
static void qo_set_use_desc (QO_PLAN * plan);
static int qo_set_orderby_skip (QO_PLAN * plan, void *arg);
static int qo_unset_hint_use_desc_idx (QO_PLAN * plan, void *arg);
static int qo_validate_indexes_for_orderby (QO_PLAN * plan, void *arg);
static int qo_unset_multi_range_optimization (QO_PLAN * plan, void *arg);
static bool qo_plan_is_orderby_skip_candidate (QO_PLAN * plan);
static bool qo_is_sort_limit (QO_PLAN * plan);
static int qo_check_like_recompile_candidate (QO_PLAN * plan, void *arg);

static json_t *qo_plan_scan_print_json (QO_PLAN * plan);
static json_t *qo_plan_sort_print_json (QO_PLAN * plan);
static json_t *qo_plan_join_print_json (QO_PLAN * plan);
static json_t *qo_plan_follow_print_json (QO_PLAN * plan);
static json_t *qo_plan_print_json (QO_PLAN * plan);

static void qo_plan_scan_print_text (FILE * fp, QO_PLAN * plan, int indent);
static void qo_plan_sort_print_text (FILE * fp, QO_PLAN * plan, int indent);
static void qo_plan_join_print_text (FILE * fp, QO_PLAN * plan, int indent);
static void qo_plan_follow_print_text (FILE * fp, QO_PLAN * plan, int indent);
static void qo_plan_print_text (FILE * fp, QO_PLAN * plan, int indent);

static bool qo_index_has_bit_attr (QO_INDEX_ENTRY * index_entryp);

static QO_PLAN_VTBL qo_seq_scan_plan_vtbl = {
  "sscan",
  qo_scan_fprint,
  qo_scan_walk,
  qo_scan_free,
  qo_sscan_cost,
  qo_sscan_cost,
  qo_scan_info,
  "Sequential scan"
};

static QO_PLAN_VTBL qo_index_scan_plan_vtbl = {
  "iscan",
  qo_scan_fprint,
  qo_scan_walk,
  qo_scan_free,
  qo_iscan_cost,
  qo_iscan_cost,
  qo_scan_info,
  "Index scan"
};

static QO_PLAN_VTBL qo_sort_plan_vtbl = {
  "temp",
  qo_sort_fprint,
  qo_sort_walk,
  qo_sort_free,
  qo_sort_cost,
  qo_sort_cost,
  qo_sort_info,
  "Sort"
};

static QO_PLAN_VTBL qo_nl_join_plan_vtbl = {
  "nl-join",
  qo_join_fprint,
  qo_join_walk,
  qo_join_free,
  qo_nljoin_cost,
  qo_nljoin_cost,
  qo_join_info,
  "Nested-loop join"
};

static QO_PLAN_VTBL qo_idx_join_plan_vtbl = {
  "idx-join",
  qo_join_fprint,
  qo_join_walk,
  qo_join_free,
  qo_nljoin_cost,
  qo_nljoin_cost,
  qo_join_info,
  "Correlated-index join"
};

static QO_PLAN_VTBL qo_merge_join_plan_vtbl = {
  "m-join",
  qo_join_fprint,
  qo_join_walk,
  qo_join_free,
  qo_mjoin_cost,
  qo_mjoin_cost,
  qo_join_info,
  "Merge join"
};

static QO_PLAN_VTBL qo_hash_join_plan_vtbl = {
  "hash-join",
  qo_hjoin_fprint,
  qo_join_walk,
  qo_join_free,
#if TEST_HASH_JOIN_FORCE_ENABLE
  qo_zero_cost,
  qo_zero_cost,
#else /* TEST_HASH_JOIN_FORCE_ENABLE */
  qo_hjoin_cost,
  qo_hjoin_cost,
#endif /* TEST_HASH_JOIN_FORCE_ENABLE */
  qo_join_info,
  "Hash join"
};

static QO_PLAN_VTBL qo_follow_plan_vtbl = {
  "follow",
  qo_follow_fprint,
  qo_follow_walk,
  qo_follow_free,
  qo_follow_cost,
  qo_follow_cost,
  qo_follow_info,
  "Object fetch"
};

static QO_PLAN_VTBL qo_set_follow_plan_vtbl = {
  "set_follow",
  qo_follow_fprint,
  qo_follow_walk,
  qo_follow_free,
  qo_follow_cost,
  qo_follow_cost,
  qo_follow_info,
  "Set fetch"
};

static QO_PLAN_VTBL qo_worst_plan_vtbl = {
  "worst",
  qo_worst_fprint,
  qo_worst_walk,
  qo_worst_free,
  qo_worst_cost,
  qo_worst_cost,
  qo_worst_info,
  "Bogus"
};

QO_PLAN_VTBL *all_vtbls[] = {
  &qo_seq_scan_plan_vtbl,
  &qo_index_scan_plan_vtbl,
  &qo_sort_plan_vtbl,
  &qo_nl_join_plan_vtbl,
  &qo_idx_join_plan_vtbl,
  &qo_merge_join_plan_vtbl,
  &qo_hash_join_plan_vtbl,
  &qo_follow_plan_vtbl,
  &qo_set_follow_plan_vtbl,
  &qo_worst_plan_vtbl
};

#define DEFAULT_NULL_SELECTIVITY (double) 0.01
#define DEFAULT_EXISTS_SELECTIVITY (double) 0.1
#define DEFAULT_SELECTIVITY (double) 0.1
#define DEFAULT_EQUAL_SELECTIVITY (double) 0.001
#define DEFAULT_EQUIJOIN_SELECTIVITY (double) 0.001
#define DEFAULT_COMP_SELECTIVITY (double) 0.1
#define DEFAULT_BETWEEN_SELECTIVITY (double) 0.01
#define DEFAULT_IN_SELECTIVITY (double) 0.01
#define DEFAULT_RANGE_SELECTIVITY (double) 0.1

/* Structural equivalence classes for expressions */

typedef enum PRED_CLASS
{
  PC_ATTR,
  PC_CONST,
  PC_HOST_VAR,
  PC_SUBQUERY,
  PC_SET,
  PC_OTHER,
  PC_MULTI_ATTR
} PRED_CLASS;

static double qo_or_selectivity (QO_ENV * env, double lhs_sel, double rhs_sel);

static double qo_and_selectivity (QO_ENV * env, double lhs_sel, double rhs_sel);

static double qo_not_selectivity (QO_ENV * env, double sel);

static double qo_equal_selectivity (QO_ENV * env, PT_NODE * pt_expr);

static double qo_comp_selectivity (QO_ENV * env, PT_NODE * pt_expr);

static double qo_between_selectivity (QO_ENV * env, PT_NODE * pt_expr);

static double qo_range_selectivity (QO_ENV * env, PT_NODE * pt_expr);

static double qo_all_some_in_selectivity (QO_ENV * env, PT_NODE * pt_expr);

static PRED_CLASS qo_classify (PT_NODE * attr);

static int qo_index_cardinality (QO_ENV * env, PT_NODE * attr);
static int qo_index_cardinality_with_dedup (QO_ENV * env, PT_NODE * attr, BITSET * seg_bitset);

/*
 * log3 () -
 *   return:
 *   n(in):
 */
static double
log3 (double n)
{
  // C++11 and later: Thread-safe initialization for local static variables is guaranteed by the compiler, called "Magic Statics"
  static double ln3_value = log (3.0);
  return log (n) / ln3_value;
}

/*
 * qo_plan_malloc () -
 *   return:
 *   env(in):
 */
static QO_PLAN *
qo_plan_malloc (QO_ENV * env)
{
  QO_PLAN *plan;

  ++qo_plans_allocated;
  if (qo_plan_free_list)
    {
      plan = qo_plan_free_list;
      qo_plan_free_list = plan->plan_un.free.link;
    }
  else
    {
      ++qo_plans_malloced;
      plan = (QO_PLAN *) malloc (sizeof (QO_PLAN));
      if (plan == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, sizeof (QO_PLAN));
      return NULL;
    }

#if !defined(NDEBUG)
      /* Prevent faults when qo_plan_fprint or qo_plan_lite_print is called. */
      plan->vtbl = NULL;
#endif
    }

  bitset_init (&(plan->sarged_terms), env);
  bitset_init (&(plan->subqueries), env);

  plan->parallel_opt_use = PLAN_PARALLEL_OPT_NO;
  plan->skip_orderby_opt = QO_PLAN_SKIP_ORDERBY_NO;

  plan->has_sort_limit = false;
  plan->use_iscan_descending = false;
  plan->need_final_sort = false;
  plan->limit_nljoin_guessed_card = 0.0;

  return plan;
}


/*
 * qo_term_string () -
 *   return:
 *   term(in):
 */
static const char *
qo_term_string (QO_TERM * term, char *buf)
{
  char *p;
  BITSET_ITERATOR bi;
  int i;
  QO_ENV *env;
  const char *separator;
  PT_NODE *conj, *saved_next = NULL;

  env = QO_TERM_ENV (term);

  conj = QO_TERM_PT_EXPR (term);
  if (conj)
    {
      saved_next = conj->next;
      conj->next = NULL;
    }

  switch (QO_TERM_CLASS (term))
    {
    case QO_TC_DEP_LINK:
      sprintf (buf, "table(");
      p = buf + strlen (buf);
      separator = "";
      for (i = bitset_iterate (&(QO_NODE_DEP_SET (QO_TERM_TAIL (term))), &bi); i != -1; i = bitset_next_member (&bi))
    {
      sprintf (p, "%s%s", separator, QO_NODE_NAME (QO_ENV_NODE (env, i)));
      p = buf + strlen (buf);
      separator = ",";
    }
      sprintf (p, ") -> %s", QO_NODE_NAME (QO_TERM_TAIL (term)));
      p = buf;
      break;

    case QO_TC_DEP_JOIN:
      p = buf;
      sprintf (p, "dep-join(%s,%s)", QO_NODE_NAME (QO_TERM_HEAD (term)), QO_NODE_NAME (QO_TERM_TAIL (term)));
      break;

    case QO_TC_DUMMY_JOIN:
      p = buf;
      sprintf (p, "dummy(%s,%s)", QO_NODE_NAME (QO_TERM_HEAD (term)), QO_NODE_NAME (QO_TERM_TAIL (term)));
      break;

    default:
      assert_release (conj != NULL);
      if (conj)
    {
      PARSER_CONTEXT *parser = QO_ENV_PARSER (QO_TERM_ENV (term));
      PT_PRINT_VALUE_FUNC saved_func = parser->print_db_value;
      unsigned int save_custom = parser->custom_print;

      parser->custom_print |= PT_CONVERT_RANGE;
      parser->print_db_value = NULL;

      p = parser_print_tree (parser, conj);

      parser->custom_print = save_custom;
      parser->print_db_value = saved_func;
    }
      else
    {
      p = buf;
      buf[0] = '\0';
    }
    }

  /* restore link */
  if (conj)
    {
      conj->next = saved_next;
    }

  return p;
}

/*
 * qo_estimate_ngroups () -
 *   return:
 *   plan(in):
 *
 * GROUP BY cardinality estimation:
 *
 * - Single table without filters:
 *   The number of groups is estimated as the NDV of the GROUP BY column.
 *   e.g. GROUP BY col2 -> Ngroups = NDV(col2)
 *
 * - Multiple GROUP BY columns:
 *   Columns are assumed to be independent.
 *   Ngroups = NDV(col1) * NDV(col2)
 *   This may overestimate the actual number of groups.
 *
 * - With filters:
 *   NDV after filtering is estimated using:
 *     n * (1 - ((N - p) / N)^(N / n))
 *   where n = NDV, N = total rows, p = filtered rows.
 *
 * - With joins:
 *   The same formula is applied.
 *   N is the estimated row count after join conditions,
 *   and p is the estimated row count after applying all predicates.
 */
static void
qo_estimate_ngroups (QO_PLAN * plan, SORT_TYPE sort_type)
{
  int group_ndv, estimate_ndv;
  double expected_nrows = plan->info->cardinality;
  double total_nrows = plan->info->total_rows;

  /* get NDV of GROUP BY */
  group_ndv = MIN (qo_get_group_ndv (plan, sort_type), expected_nrows);
  if (group_ndv == -1)
    {
      plan->info->group_rows = expected_nrows;
      return;
    }

  if (expected_nrows == total_nrows)
    {
      estimate_ndv = group_ndv;
    }
  else
    {
      /* estimate number of groups */
      estimate_ndv = MAX (qo_estimate_ndv (total_nrows, expected_nrows, group_ndv), 1);
    }

  estimate_ndv = MIN (expected_nrows, estimate_ndv);

  if (plan->info->group_rows > estimate_ndv)
    {
      plan->info->group_rows = estimate_ndv;
    }
}

/*
 * qo_estimate_ndv () - NDV estimation formula derived from extracted data volume
 *   return:  estimated ndv
 *
 * formula:
 *   n * (1 - ((N - p) / N)^(N / n))
 *
 * N: total_nrows
 * p: expected_nrows
 * n: NDV of group columns
 */
double
qo_estimate_ndv (double N, double p, double n)
{
  if (N <= 0.0 || n <= 0.0)
    {
      return 0.0;
    }

  double ratio = (N - p) / N;
  double exponent = N / n;

  return n * (1.0 - pow (ratio, exponent));
}

/*
 * qo_get_group_ndv () -
 *   return:
 *   plan(in):
 */
static int
qo_get_group_ndv (QO_PLAN * plan, SORT_TYPE sort_type)
{
  PT_NODE *nodes;
  QO_ENV *env = NULL;
  PARSER_CONTEXT *parser = NULL;
  NDV_INFO ndv_info;


  env = (plan->info)->env;
  parser = QO_ENV_PARSER (env);

  if ((QO_ENV_PT_TREE (env))->node_type == PT_SELECT)
    {
      if (sort_type == SORT_GROUPBY)
    {
      nodes = (QO_ENV_PT_TREE (env))->info.query.q.select.group_by;
    }
      else
    {
      nodes = (QO_ENV_PT_TREE (env))->info.query.q.select.list;
    }
    }
  else
    {
      return -1;
    }

  ndv_info.env = env;
  ndv_info.total_ndv = 1;
  bitset_init (&ndv_info.seg_bitset, env);
  /* The NDV is simply extracted from column without considering the function, etc. and product of NDV of each column */
  parser_walk_tree (parser, nodes, qo_get_col_product_ndv, &ndv_info, NULL, NULL);
  if (ndv_info.total_ndv == 1)
    {
      ndv_info.total_ndv = -1;
    }
  bitset_delset (&ndv_info.seg_bitset);

  return ndv_info.total_ndv;
}

/*
 * qo_plan_compute_cost () -
 *   return:
 *   plan(in):
 */
static void
qo_plan_compute_cost (QO_PLAN * plan)
{
  QO_ENV *env;
  QO_SUBQUERY *subq;
  PT_NODE *query;
  double temp_cpu_cost, temp_io_cost;
  double subq_cpu_cost, subq_io_cost;
  int i;
  BITSET_ITERATOR iter;

  /* When computing the cost for a WORST_PLAN, we'll get in here without a backing info node; just work around it. */
  env = plan->info ? (plan->info)->env : NULL;
  subq_cpu_cost = subq_io_cost = 0.0;

  /* Compute the costs for all of the subqueries. Each of the pinned subqueries is intended to be evaluated once for
   * each row produced by this plan; the cost of each such evaluation in the fixed cost of the subquery plus one trip
   * through the result, i.e.,
   *
   * QO_PLAN_FIXED_COST(subplan) + QO_PLAN_ACCESS_COST(subplan)
   *
   * The cost info for the subplan has (probably) been squirreled away in a QO_SUMMARY structure reachable from the
   * original select node.
   */

  for (i = bitset_iterate (&(plan->subqueries), &iter); i != -1; i = bitset_next_member (&iter))
    {
      subq = env ? &env->subqueries[i] : NULL;
      query = subq ? subq->node : NULL;
      qo_plan_compute_subquery_cost (query, &temp_cpu_cost, &temp_io_cost);
      subq_cpu_cost += temp_cpu_cost;
      subq_io_cost += temp_io_cost;
    }

  /* This computes the specific cost characteristics for each plan. */
  (*(plan->vtbl)->cost_fn) (plan);

  /* Now add in the subquery costs; this cost is incurred for each row produced by this plan, so multiply it by the
   * estimated scan_rows and add it to the access cost.
   */
  if (plan->info)
    {
      plan->variable_cpu_cost += (plan->info)->scan_rows * subq_cpu_cost;
      plan->variable_io_cost += (plan->info)->scan_rows * subq_io_cost;
    }
}

/*
 * qo_plan_compute_subquery_cost () -
 *   return:
 *   subquery(in):
 *   subq_cpu_cost(in):
 *   subq_io_cost(in):
 */
static void
qo_plan_compute_subquery_cost (PT_NODE * subquery, double *subq_cpu_cost, double *subq_io_cost)
{
  QO_SUMMARY *summary;
  double arg1_cpu_cost, arg1_io_cost, arg2_cpu_cost, arg2_io_cost;

  *subq_cpu_cost = *subq_io_cost = 0.0; /* init */

  if (subquery == NULL)
    {
      /* This case is selected when a subquery wasn't optimized for some reason.
       * just take a guess. ---> NEED MORE CONSIDERATION
       */
      *subq_cpu_cost = 5.0;
      *subq_io_cost = 5.0;
      return;
    }

  switch (subquery->node_type)
    {
    case PT_SELECT:
      summary = (QO_SUMMARY *) subquery->info.query.q.select.qo_summary;
      if (summary)
    {
      *subq_cpu_cost += summary->fixed_cpu_cost + summary->variable_cpu_cost;
      *subq_io_cost += summary->fixed_io_cost + summary->variable_io_cost;
    }
      else
    {
      /* it may be unknown error. just take a guess. ---> NEED MORE CONSIDERATION */
      *subq_cpu_cost = 5.0;
      *subq_io_cost = 5.0;
    }

      /* Here, GROUP BY and ORDER BY cost must be considered. ---> NEED MORE CONSIDERATION */
      /* ---> under construction <--- */
      break;

    case PT_UNION:
    case PT_INTERSECTION:
    case PT_DIFFERENCE:
      qo_plan_compute_subquery_cost (subquery->info.query.q.union_.arg1, &arg1_cpu_cost, &arg1_io_cost);
      qo_plan_compute_subquery_cost (subquery->info.query.q.union_.arg2, &arg2_cpu_cost, &arg2_io_cost);

      *subq_cpu_cost = arg1_cpu_cost + arg2_cpu_cost;
      *subq_io_cost = arg1_io_cost + arg2_io_cost;

      /* later, sort cost and result-set scan cost must be considered. ---> NEED MORE CONSIDERATION */
      /* ---> under construction <--- */
      break;

    default:
      /* it is unknown case. just take a guess. ---> NEED MORE CONSIDERATION */
      *subq_cpu_cost = 5.0;
      *subq_io_cost = 5.0;
      break;
    }

  return;
}

/*
 * qo_walk_plan_tree () - applies a callback to every plan in a plan tree
 *                        and stops on errors
 *   return:  the error of the first callback function that returns something
 *            other than NO_ERROR, or NO_ERROR.
 *   plan(in): the root of the plan tree to walk
 *   f(in): functor (callback) to apply to each non-null plan
 *   arg(in/out): argument to be used liberally by the callback
 */
static int
qo_walk_plan_tree (QO_PLAN * plan, QO_WALK_FUNCTION f, void *arg)
{
  int ret = NO_ERROR;

  if (!plan)
    {
      return NO_ERROR;
    }

  switch (plan->plan_type)
    {
    case QO_PLANTYPE_SCAN:
      return f (plan, arg);

    case QO_PLANTYPE_FOLLOW:
      return qo_walk_plan_tree (plan->plan_un.follow.head, f, arg);

    case QO_PLANTYPE_SORT:
      return qo_walk_plan_tree (plan->plan_un.sort.subplan, f, arg);

    case QO_PLANTYPE_JOIN:
      ret = qo_walk_plan_tree (plan->plan_un.join.outer, f, arg);
      if (ret != NO_ERROR)
    {
      return ret;
    }

      return qo_walk_plan_tree (plan->plan_un.join.inner, f, arg);

    default:
      return ER_FAILED;
    }
}

/*
 * qo_set_use_desc () - sets certain plans' use_descending index flag
 *
 * return: NO_ERROR
 * plan(in):
 *
 * note: the function only cares about index scans and skips other plan types
 */
static void
qo_set_use_desc (QO_PLAN * plan)
{
  switch (plan->plan_type)
    {
    case QO_PLANTYPE_SCAN:
      if (((qo_is_iscan (plan) || qo_is_iscan_from_groupby (plan))
       && plan->plan_un.scan.index->head->groupby_skip == true)
      || ((qo_is_iscan (plan) || qo_is_iscan_from_orderby (plan))
          && plan->plan_un.scan.index->head->orderby_skip == true))
    {
      plan->plan_un.scan.index->head->use_descending = true;
    }
      break;

    case QO_PLANTYPE_SORT:
      qo_set_use_desc (plan->plan_un.sort.subplan);
      break;

    case QO_PLANTYPE_JOIN:
      qo_set_use_desc (plan->plan_un.join.outer);
      break;

    case QO_PLANTYPE_FOLLOW:
      qo_set_use_desc (plan->plan_un.follow.head);
      break;

    default:
      break;
    }
}

/*
 * qo_unset_multi_range_optimization () - set all multi_range_opt flags
 *                    on all indexes to false
 *
 * return    : NO_ERROR
 * plan (in) : current plan
 * arg (in)  : not used
 */
static int
qo_unset_multi_range_optimization (QO_PLAN * plan, void *arg)
{
  if (plan->multi_range_opt_use == PLAN_MULTI_RANGE_OPT_NO)
    {
      /* nothing to do */
      return NO_ERROR;
    }

  /* set multi_range_opt to false */
  plan->multi_range_opt_use = PLAN_MULTI_RANGE_OPT_NO;

  if (qo_is_index_mro_scan (plan))
    {
      QO_INDEX_ENTRY *index_entryp;

      index_entryp = plan->plan_un.scan.index->head;

      /* multi_range_opt may have set the descending order on index */
      if (index_entryp->use_descending)
    {
      /* if descending order is hinted or if skip order by / skip group by are true, leave the index as descending */
      if (((plan->info->env->pt_tree->info.query.q.select.hint & PT_HINT_USE_IDX_DESC) == 0)
          && !index_entryp->groupby_skip && !index_entryp->orderby_skip)
        {
          /* set use_descending to false */
          index_entryp->use_descending = false;
        }
    }
    }

  return NO_ERROR;
}

/*
 * qo_set_orderby_skip () - sets certain plans' orderby_skip index flag to the
 *                          boolean value given in *arg.
 *
 * return: NO_ERROR
 * plan(in):
 * arg(in): will be cast to bool* and its values used to set
 *          the orderby_skip property
 * note: the function only cares about index scans and skips other plan types
 */
static int
qo_set_orderby_skip (QO_PLAN * plan, void *arg)
{
  if (qo_is_iscan (plan) || qo_is_iscan_from_orderby (plan))
    {
      bool yn = *((bool *) arg);
      plan->plan_un.scan.index->head->orderby_skip = yn;
      plan->skip_orderby_opt = (yn) ? QO_PLAN_SKIP_ORDERBY_USE : plan->skip_orderby_opt;
    }

  return NO_ERROR;
}

static int
qo_unset_hint_use_desc_idx (QO_PLAN * plan, void *arg)
{
  if (qo_is_interesting_order_scan (plan))
    {
      if (plan->plan_un.scan.index && plan->plan_un.scan.index->head)
    {
      if (plan->plan_un.scan.index->head->use_descending)
        {
          /* We no longer need to set the USE_DESC_IDX hint if the planner wants a descending index, because the
           * requirement is copied to each scan_ptr's index info at XASL generation.
           * plan->info->env->pt_tree->info.query.q.select.hint |= PT_HINT_USE_IDX_DESC;
           */
        }
      else if (plan->plan_un.scan.index->head->orderby_skip || qo_is_index_mro_scan (plan))
        {
          if (plan->info->env != NULL)
        {
          plan->info->env->pt_tree->info.query.q.select.hint &= ~PT_HINT_USE_IDX_DESC;
        }
        }
    }
    }

  return NO_ERROR;
}

/*
 * qo_validate_indexes_for_orderby () - wrapper function for
 *                  qo_validate_index_for_orderby
 *                                      used with qo_walk_plan_tree.
 * return: NO_ERROR or ER_FAILED if the wrapped function returns false
 * plan(in):
 * arg(in): not used, must be NULL
 */
static int
qo_validate_indexes_for_orderby (QO_PLAN * plan, void *arg)
{
  if (qo_is_iscan_from_orderby (plan))
    {
      if (!qo_validate_index_for_orderby (plan->info->env, plan->plan_un.scan.index))
    {
      return ER_FAILED;
    }
    }

  return NO_ERROR;
}

/*
 * qo_top_plan_new () -
 *   return:
 *   plan(in):
 */
static QO_PLAN *
qo_top_plan_new (QO_PLAN * plan)
{
  QO_ENV *env;
  PT_NODE *tree, *group_by, *order_by, *orderby_for;
  bool ordbynum_flag = false;
  PT_MISC_TYPE all_distinct;
  PARSER_CONTEXT *parser;

  if (plan == NULL || plan->top_rooted)
    {
      return plan;      /* is already top-level plan - OK */
    }

  if (plan->info == NULL    /* worst plan */
      || (env = (plan->info)->env) == NULL || bitset_cardinality (&((plan->info)->nodes)) < env->Nnodes
      || /* sub-plan */ (tree = QO_ENV_PT_TREE (env)) == NULL
      || (parser = QO_ENV_PARSER (env)) == NULL)
    {
      return plan;      /* do nothing */
    }

  QO_ASSERT (env, tree->node_type == PT_SELECT);

  plan->top_rooted = true;  /* mark as top-level plan */

  if (pt_is_single_tuple (QO_ENV_PARSER (env), tree))
    {               /* one tuple plan */
      return plan;      /* do nothing */
    }

  if (qo_plan_multi_range_opt (plan))
    {
      /* already found out that multi range optimization can be applied on current plan, skip any other checks */
      return plan;
    }

  all_distinct = tree->info.query.all_distinct;
  group_by = tree->info.query.q.select.group_by;
  order_by = tree->info.query.order_by;
  orderby_for = tree->info.query.orderby_for;

  if (order_by)
    {
      (void) parser_walk_leaves (parser, tree, pt_check_orderbynum_pre, NULL, pt_check_orderbynum_post, &ordbynum_flag);
    }

  if (group_by || (all_distinct == PT_DISTINCT || order_by))
    {

      bool groupby_skip, orderby_skip, is_index_w_prefix;
      bool found_instnum;
      int t;

      groupby_skip = orderby_skip = false;  /* init */

      found_instnum = false;
      for (t = 0; t < (signed) plan->info->planner->T; t++)
    {
      if (QO_TERM_CLASS (&plan->info->planner->term[t]) == QO_TC_TOTALLY_AFTER_JOIN)
        {
          found_instnum = true;
          break;
        }
    }

      plan->iscan_sort_list = qo_plan_compute_iscan_sort_list (plan, NULL, &is_index_w_prefix, false);

      /* GROUP BY */
      /* if we have rollup, we do not skip the group by */
      if (group_by && !group_by->flag.with_rollup)
    {
      PT_NODE *group_sort_list = NULL;

      group_sort_list = qo_plan_compute_iscan_sort_list (plan, group_by, &is_index_w_prefix, false);

      if (group_sort_list)
        {
          if (found_instnum /* && found_grpynum */ )
        {
          ;     /* give up */
        }
          else
        {
          groupby_skip = pt_sort_spec_cover_groupby (parser, group_sort_list, group_by, tree);

          /* if index plan and can't skip group by, we search that maybe a descending scan can be used. */
          if (qo_is_interesting_order_scan (plan) && !groupby_skip)
            {
              groupby_skip = qo_check_groupby_skip_descending (plan, group_sort_list);

              if (groupby_skip)
            {
              plan->use_iscan_descending = true;
            }
            }
        }

          parser_free_node (parser, group_sort_list);
        }

      if (groupby_skip)
        {
          /* if the plan is index_groupby, we validate the plan */
          if (qo_is_iscan_from_groupby (plan) || qo_is_iscan_from_orderby (plan))
        {
          if (!qo_validate_index_for_groupby (plan->info->env, plan->plan_un.scan.index))
            {
              /* drop the plan if it wasn't validated */
              qo_worst_cost (plan);
              return plan;
            }
        }
          /* if all goes well, we have an indexed plan with group by skip! */
          if (plan->plan_type == QO_PLANTYPE_SCAN && plan->plan_un.scan.index)
        {
          plan->plan_un.scan.index->head->groupby_skip = true;
        }
        }
      else
        {
          if (plan->iscan_sort_list)
        {
          parser_free_tree (parser, plan->iscan_sort_list);
          plan->iscan_sort_list = NULL;
        }

          /* if the order by is not skipped we drop the plan because it didn't helped us */
          if (qo_is_iscan_from_groupby (plan) || qo_is_iscan_from_orderby (plan))
        {
          qo_worst_cost (plan);
          return plan;
        }

          plan = qo_sort_new (plan, QO_UNORDERED, SORT_GROUPBY);
          assert (plan->iscan_sort_list == NULL);
        }
    }

      /* DISTINCT, ORDER BY */
      if (all_distinct == PT_DISTINCT || order_by)
    {
      if (plan->iscan_sort_list)
        {           /* need to check */
          if (all_distinct == PT_DISTINCT)
        {
          ;     /* give up */
        }
          else
        {       /* non distinct */
          if (group_by)
            {
              /* we already removed covered ORDER BY in reduce_order_by(). so is not covered ordering */
              ;     /* give up; DO NOT DELETE ME - need future work */
            }
          else
            {       /* non group_by */
              if (found_instnum && (orderby_for || ordbynum_flag))
            {
              /* at here, we can not merge orderby_num pred with inst_num pred */
              ; /* give up; DO NOT DELETE ME - need future work */
            }
              else if (!is_index_w_prefix && !tree->info.query.q.select.connect_by
                   && !pt_has_analytic (parser, tree))
            {
              orderby_skip = pt_sort_spec_cover (plan->iscan_sort_list, order_by);

              /* try using a reverse scan */
              if (!orderby_skip)
                {
                  orderby_skip = qo_check_orderby_skip_descending (plan);

                  if (orderby_skip)
                {
                  plan->use_iscan_descending = true;
                }
                }
            }
            }
        }
        }

      if (orderby_skip)
        {
          if (qo_is_iscan_from_groupby (plan))
        {
          /* group by skipping plan and we have order by skip -> drop */
          qo_worst_cost (plan);
          return plan;
        }

          if (orderby_for)
        {       /* apply inst_num filter */
          ;     /* DO NOT DELETE ME - need future work */
        }

          /* validate the index orderby plan or subplans */
          if (qo_walk_plan_tree (plan, qo_validate_indexes_for_orderby, NULL) != NO_ERROR)
        {
          /* drop the plan if it wasn't validated */
          qo_worst_cost (plan);
          return plan;
        }

          /* if all goes well, we have an indexed plan with order by skip: set the flag to all suitable subplans */
          {
        bool yn = true;
        qo_walk_plan_tree (plan, qo_set_orderby_skip, &yn);
          }
        }
      else
        {
          if (!groupby_skip && plan->iscan_sort_list)
        {
          parser_free_tree (parser, plan->iscan_sort_list);
          plan->iscan_sort_list = NULL;
        }

          /* if the order by is not skipped we drop the plan because it didn't helped us */
          if (qo_is_iscan_from_orderby (plan))
        {
          qo_worst_cost (plan);
          return plan;
        }

          plan = qo_sort_new (plan, QO_UNORDERED, all_distinct == PT_DISTINCT ? SORT_DISTINCT : SORT_ORDERBY);
        }
    }
    }

  return plan;
}

/*
 * qo_generic_walk () -
 *   return:
 *   plan(in):
 *   child_fn(in):
 *   child_data(in):
 *   parent_fn(in):
 *   parent_data(in):
 */
static void
qo_generic_walk (QO_PLAN * plan, void (*child_fn) (QO_PLAN *, void *), void *child_data,
         void (*parent_fn) (QO_PLAN *, void *), void *parent_data)
{
  if (parent_fn)
    {
      (*parent_fn) (plan, parent_data);
    }
}

/*
 * qo_plan_print_sort_spec_helper () -
 *   return:
 *   list(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_plan_print_sort_spec_helper (PT_NODE * list, bool is_iscan_asc, FILE * f, int howfar)
{
  const char *prefix;
  bool is_sort_spec_asc = true;

  if (list == NULL)
    {
      return;
    }

  fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', "sort:  ");

  prefix = "";
  for (; list; list = list->next)
    {
      if (list->info.sort_spec.pos_descr.pos_no < 1)
    {           /* useless from here */
      break;
    }
      fputs (prefix, f);

      if (list->info.sort_spec.asc_or_desc == PT_ASC)
    {
      is_sort_spec_asc = true;
    }
      else
    {
      is_sort_spec_asc = false;
    }

      fprintf (f, "%d %s", list->info.sort_spec.pos_descr.pos_no, (is_sort_spec_asc == is_iscan_asc) ? "asc" : "desc");

      if (TP_TYPE_HAS_COLLATION (TP_DOMAIN_TYPE (list->info.sort_spec.pos_descr.dom))
      && TP_DOMAIN_COLLATION (list->info.sort_spec.pos_descr.dom) != LANG_SYS_COLLATION
      && TP_DOMAIN_COLLATION_FLAG (list->info.sort_spec.pos_descr.dom) != TP_DOMAIN_COLL_LEAVE)
    {
      fprintf (f, " collate %s",
           lang_get_collation_name (TP_DOMAIN_COLLATION (list->info.sort_spec.pos_descr.dom)));
    }
      prefix = ", ";
    }
}

/*
 * qo_plan_print_sort_spec () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_plan_print_sort_spec (QO_PLAN * plan, FILE * f, int howfar)
{
  bool is_iscan_asc = true;

  if (plan->top_rooted != true)
    {               /* check for top level plan */
      return;
    }

  is_iscan_asc = plan->use_iscan_descending ? false : true;

  qo_plan_print_sort_spec_helper (plan->iscan_sort_list, is_iscan_asc, f, howfar);

  if (plan->plan_type == QO_PLANTYPE_SORT)
    {
      QO_ENV *env;
      PT_NODE *tree;

      env = (plan->info)->env;
      if (env == NULL)
    {
      assert (false);
      return;       /* give up */
    }
      tree = QO_ENV_PT_TREE (env);
      if (tree == NULL)
    {
      assert (false);
      return;       /* give up */
    }

      if (plan->plan_un.sort.sort_type == SORT_GROUPBY && tree->node_type == PT_SELECT)
    {
      qo_plan_print_sort_spec_helper (tree->info.query.q.select.group_by, true, f, howfar);
    }

      if ((plan->plan_un.sort.sort_type == SORT_DISTINCT || plan->plan_un.sort.sort_type == SORT_ORDERBY)
      && PT_IS_QUERY (tree))
    {
      qo_plan_print_sort_spec_helper (tree->info.query.order_by, true, f, howfar);
    }
    }
}

/*
 * qo_plan_print_costs () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_plan_print_costs (QO_PLAN * plan, FILE * f, int howfar)
{
  double fixed = plan->fixed_cpu_cost + plan->fixed_io_cost;
  double variable = plan->variable_cpu_cost + plan->variable_io_cost;
  double card = (plan->plan_type == QO_PLANTYPE_JOIN && QO_IS_NL_JOIN (plan) && plan->limit_nljoin_guessed_card > 0)
    ? plan->limit_nljoin_guessed_card : (plan->info)->cardinality;

  fprintf (f, "\n" INDENTED_TITLE_FMT "%.0f card %.0f", (int) howfar, ' ', "cost:", fixed + variable, card);

#if TEST_DUMP_PLAN_SCAN_COST
  fprintf (f, "\n" INDENTED_TITLE_FMT "%.0f expected %.0f scan %.0f total %.0f group %.0f hit_prob %.5f", (int) howfar,
       ' ', "cost:", fixed + variable, (plan->info)->cardinality, (plan->info)->scan_rows, (plan->info)->total_rows,
       (plan->info)->group_rows, (plan->info)->hit_prob);
#endif /* TEST_DUMP_PLAN_SCAN_COST */
}


/*
 * qo_plan_print_projected_segs () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_plan_print_projected_segs (QO_PLAN * plan, FILE * f, int howfar)
{
  int sx;
  const char *prefix = "";
  BITSET_ITERATOR si;

  if (!((plan->info)->env->dump_enable))
    {
      return;
    }

  fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', "segs:");
  for (sx = bitset_iterate (&((plan->info)->projected_segs), &si); sx != -1; sx = bitset_next_member (&si))
    {
      fputs (prefix, f);
      qo_seg_fprint (&(plan->info)->env->segs[sx], f);
      prefix = ", ";
    }
}


/*
 * qo_plan_print_sarged_terms () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_plan_print_sarged_terms (QO_PLAN * plan, FILE * f, int howfar)
{
  if (!bitset_is_empty (&(plan->sarged_terms)))
    {
      fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', "sargs:");
      qo_termset_fprint ((plan->info)->env, &plan->sarged_terms, f);
    }
}

/*
 * qo_plan_print_outer_join_terms () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_plan_print_outer_join_terms (QO_PLAN * plan, FILE * f, int howfar)
{
  if (!bitset_is_empty (&(plan->plan_un.join.during_join_terms)))
    {
      fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', "during:");
      qo_termset_fprint ((plan->info)->env, &(plan->plan_un.join.during_join_terms), f);
    }
  if (!bitset_is_empty (&(plan->plan_un.join.after_join_terms)))
    {
      fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', "after:");
      qo_termset_fprint ((plan->info)->env, &(plan->plan_un.join.after_join_terms), f);
    }
}


/*
 * qo_plan_print_subqueries () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_plan_print_subqueries (QO_PLAN * plan, FILE * f, int howfar)
{
  if (!bitset_is_empty (&(plan->subqueries)))
    {
      fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', "subqs: ");
      bitset_print (&(plan->subqueries), f);
    }
}

/*
 * qo_plan_print_analytic_eval () - print evaluation order of analytic
 *                  functions
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_plan_print_analytic_eval (QO_PLAN * plan, FILE * f, int howfar)
{
  ANALYTIC_EVAL_TYPE *eval;
  ANALYTIC_TYPE *func;
  SORT_LIST *sort;
  int i, j, k;
  char buf[32];

  if (plan->analytic_eval_list != NULL)
    {
      fprintf (f, "\n\nAnalytic functions:");

      /* list functions */
      for (i = 0, k = 0, eval = plan->analytic_eval_list; eval != NULL; eval = eval->next, k++)
    {
      /* run info */
      sprintf (buf, "run[%d]: ", k);
      fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', buf);
      fprintf (f, "sort with key (");

      /* eval sort list */
      for (sort = eval->sort_list; sort != NULL; sort = sort->next)
        {
          fprintf (f, SORT_SPEC_FMT (sort));
          if (sort->next != NULL)
        {
          fputs (", ", f);
        }
        }
      fputs (")", f);

      for (func = eval->head; func != NULL; func = func->next, i++)
        {
          /* func info */
          fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', "");
          fprintf (f, "func[%d]: ", i);
          fputs (fcode_get_lowercase_name (func->function), f);

          /* func partition by */
          fputs (" partition by (", f);
          for (sort = eval->sort_list, j = func->sort_prefix_size; sort != NULL && j > 0; sort = sort->next, j--)
        {
          fprintf (f, SORT_SPEC_FMT (sort));
          if (sort->next != NULL && j != 1)
            {
              fputs (", ", f);
            }
        }

          /* func order by */
          fputs (") order by (", f);
          for (j = func->sort_list_size - func->sort_prefix_size; sort != NULL && j > 0; sort = sort->next, j--)
        {
          fprintf (f, SORT_SPEC_FMT (sort));
          if (sort->next != NULL && j != 1)
            {
              fputs (", ", f);
            }
        }
          fputs (")", f);
        }
    }
    }
}

/*
 * qo_scan_new () -
 *   return:
 *   info(in):
 *   node(in):
 *   scan_method(in):
 */
static QO_PLAN *
qo_scan_new (QO_INFO * info, QO_NODE * node, QO_SCANMETHOD scan_method)
{
  QO_PLAN *plan;

  plan = qo_plan_malloc (info->env);
  if (plan == NULL)
    {
      return NULL;
    }

  plan->info = info;
  plan->refcount = 0;
  plan->top_rooted = false;
  plan->well_rooted = true;
  plan->iscan_sort_list = NULL;
  plan->analytic_eval_list = NULL;
  plan->plan_type = QO_PLANTYPE_SCAN;
  plan->order = QO_UNORDERED;

  plan->plan_un.scan.scan_method = scan_method;
  plan->plan_un.scan.node = node;

  bitset_assign (&(plan->sarged_terms), &(QO_NODE_SARGS (node)));

  bitset_assign (&(plan->subqueries), &(QO_NODE_SUBQUERIES (node)));
  bitset_init (&(plan->plan_un.scan.terms), info->env);
  bitset_init (&(plan->plan_un.scan.kf_terms), info->env);
  bitset_init (&(plan->plan_un.scan.hash_terms), info->env);
  plan->plan_un.scan.index_equi = false;
  plan->plan_un.scan.index_cover = false;
  plan->plan_un.scan.index_iss = false;
  plan->plan_un.scan.index_loose = false;
  plan->plan_un.scan.index = NULL;

  plan->multi_range_opt_use = PLAN_MULTI_RANGE_OPT_NO;
  bitset_init (&(plan->plan_un.scan.multi_col_range_segs), info->env);

  return plan;
}


/*
 * qo_scan_free () -
 *   return:
*   plan(in):
 */
static void
qo_scan_free (QO_PLAN * plan)
{
  bitset_delset (&(plan->plan_un.scan.terms));
  bitset_delset (&(plan->plan_un.scan.kf_terms));
  bitset_delset (&(plan->plan_un.scan.hash_terms));
  bitset_delset (&(plan->plan_un.scan.multi_col_range_segs));
}


/*
 * qo_seq_scan_new () -
 *   return:
 *   info(in):
 *   node(in):
 */
static QO_PLAN *
qo_seq_scan_new (QO_INFO * info, QO_NODE * node)
{
  QO_PLAN *plan;

  plan = qo_scan_new (info, node, QO_SCANMETHOD_SEQ_SCAN);
  if (plan == NULL)
    {
      return NULL;
    }

  plan->vtbl = &qo_seq_scan_plan_vtbl;

  assert (bitset_is_empty (&(plan->plan_un.scan.terms)));
  assert (bitset_is_empty (&(plan->plan_un.scan.kf_terms)));
  assert (plan->plan_un.scan.index_equi == false);
  assert (plan->plan_un.scan.index_cover == false);
  assert (plan->plan_un.scan.index_iss == false);
  assert (plan->plan_un.scan.index_loose == false);
  assert (plan->plan_un.scan.index == NULL);

  qo_plan_compute_cost (plan);

  plan = qo_top_plan_new (plan);

  return plan;
}


/*
 * qo_sscan_cost () -
 *   return:
 *   planp(in):
 */
static void
qo_sscan_cost (QO_PLAN * planp)
{
  QO_NODE *nodep;

  nodep = planp->plan_un.scan.node;
  planp->fixed_cpu_cost = 0.0;
  planp->fixed_io_cost = 0.0;
  if (QO_NODE_NCARD (nodep) == 0)
    {
      planp->variable_cpu_cost = 1.0 * (double) QO_CPU_WEIGHT;
    }
  else
    {
      planp->variable_cpu_cost = (double) QO_NODE_NCARD (nodep) * (double) QO_CPU_WEIGHT;
    }
  planp->variable_io_cost = (double) QO_NODE_TCARD (nodep);
  planp->info->scan_rows = MAX (1, QO_NODE_NCARD (nodep));

#if TEST_DUMP_PLAN_SCAN_COST
  fprintf (stdout, "\nSequential Scan Cost: \n");
  fprintf (stdout, "  -    Fixed CPU Cost: %lf\n", planp->fixed_cpu_cost);
  fprintf (stdout, "  -    Fixed I/O Cost: %lf\n", planp->fixed_io_cost);
  fprintf (stdout, "  - Variable CPU Cost: %lf\n", planp->variable_cpu_cost);
  fprintf (stdout, "  - Variable I/O Cost: %lf\n", planp->variable_io_cost);
  fprintf (stdout, "  -    Total     Cost: %lf\n",
       planp->fixed_cpu_cost + planp->fixed_io_cost + planp->variable_cpu_cost + planp->variable_io_cost);
  if (planp->vtbl != NULL)
    {
      // qo_plan_lite_print (planp, stdout, 0);
      qo_plan_fprint (planp, stdout, 0, NULL);
      fprintf (stdout, "\n");
    }
  fprintf (stdout, "\n");
#endif /* TEST_DUMP_PLAN_SCAN_COST */
}

/*
 * qo_index_has_bit_attr () - temporary function
 *    determines if index has any bit/varbit attributes
 *    return: true/false
 *    index_entyp(in):
 */
static bool
qo_index_has_bit_attr (QO_INDEX_ENTRY * index_entryp)
{
  TP_DOMAIN *domain;
  int col_num = index_entryp->col_num;
  int j;

  for (j = 0; j < col_num; j++)
    {
      domain = index_entryp->constraints->attributes[j]->domain;
      if (TP_DOMAIN_TYPE (domain) == DB_TYPE_BIT || TP_DOMAIN_TYPE (domain) == DB_TYPE_VARBIT)
    {
      return true;
    }
    }

  return false;
}

/*
 * qo_index_scan_new () -
 *   return:
 *   info(in):
 *   node(in):
 *   ni_entry(in):
 *   scan_method(in):
 *   range_terms(in):
 *   indexable_terms(in):
 */
static QO_PLAN *
qo_index_scan_new (QO_INFO * info, QO_NODE * node, QO_NODE_INDEX_ENTRY * ni_entry, QO_SCANMETHOD scan_method,
           BITSET * range_terms, BITSET * indexable_terms)
{
  QO_PLAN *plan = NULL;
  BITSET_ITERATOR iter;
  int t = -1;
  QO_ENV *env = info->env;
  QO_INDEX_ENTRY *index_entryp = NULL;
  QO_TERM *term = NULL;
  BITSET index_segs;
  BITSET term_segs;
  BITSET remaining_terms;
  int first_seg;
  bool first_col_present = false;

  assert (ni_entry != NULL);
  assert (ni_entry->head != NULL);

  assert (scan_method == QO_SCANMETHOD_INDEX_SCAN || scan_method == QO_SCANMETHOD_INDEX_ORDERBY_SCAN
      || scan_method == QO_SCANMETHOD_INDEX_GROUPBY_SCAN || scan_method == QO_SCANMETHOD_INDEX_SCAN_INSPECT);

  assert (scan_method != QO_SCANMETHOD_INDEX_SCAN || !(ni_entry->head->force < 0));
  assert (scan_method == QO_SCANMETHOD_INDEX_SCAN_INSPECT || range_terms != NULL);

  plan = qo_scan_new (info, node, scan_method);
  if (plan == NULL)
    {
      return NULL;
    }

  bitset_init (&index_segs, env);
  bitset_init (&term_segs, env);
  bitset_init (&remaining_terms, env);

  if (range_terms != NULL)
    {
      /* remove key-range terms from sarged terms */
      bitset_difference (&(plan->sarged_terms), range_terms);
    }

  /* remove key-range terms from remaining terms */
  if (indexable_terms != NULL)
    {
      bitset_assign (&remaining_terms, indexable_terms);
      bitset_difference (&remaining_terms, range_terms);
    }
  bitset_union (&remaining_terms, &(plan->sarged_terms));

  /*
   * This is, in essence, the selectivity of the index.  We
   * really need to do a better job of figuring out the cost of
   * an indexed scan.
   */
  plan->vtbl = &qo_index_scan_plan_vtbl;
  plan->plan_un.scan.index = ni_entry;

  index_entryp = (plan->plan_un.scan.index)->head;
  first_seg = index_entryp->seg_idxs[0];

  if (range_terms != NULL)
    {
      /* set key-range terms */
      bitset_assign (&(plan->plan_un.scan.terms), range_terms);
      bitset_assign (&(plan->plan_un.scan.multi_col_range_segs), &(index_entryp->multi_col_range_segs));
      for (t = bitset_iterate (range_terms, &iter); t != -1; t = bitset_next_member (&iter))
    {
      term = QO_ENV_TERM (env, t);

      if (first_seg != -1 && BITSET_MEMBER (QO_TERM_SEGS (term), first_seg))
        {
          first_col_present = true;
        }

      if (!QO_TERM_IS_FLAGED (term, QO_TERM_EQUAL_OP))
        {
          break;
        }
    }
    }

  if (!bitset_is_empty (&(plan->plan_un.scan.terms)) && t == -1)
    {
      /* is all equi-cond key-range terms */
      plan->plan_un.scan.index_equi = true;
    }
  else
    {
      plan->plan_un.scan.index_equi = false;
    }

  if (index_entryp->constraints->func_index_info && index_entryp->cover_segments == false)
    {
      /* do not permit key-filter */
      assert (bitset_is_empty (&(plan->plan_un.scan.kf_terms)));
    }
  else
    {
      /* all segments consisting in key columns */
      for (t = 0; t < index_entryp->nsegs; t++)
    {
      if ((index_entryp->seg_idxs[t]) != -1)
        {
          bitset_add (&index_segs, (index_entryp->seg_idxs[t]));
        }
    }

      for (t = bitset_iterate (&remaining_terms, &iter); t != -1; t = bitset_next_member (&iter))
    {
      term = QO_ENV_TERM (env, t);

      if (QO_TERM_IS_FLAGED (term, QO_TERM_NON_IDX_SARG_COLL))
        {
          /* term contains a collation that prevents us from using this term as a key range/filter */
          continue;
        }

      if (!bitset_is_empty (&(QO_TERM_SUBQUERIES (term))))
        {
          continue;     /* term contains correlated subquery */
        }

      /* check for no key-range index scan */
      if (bitset_is_empty (&(plan->plan_un.scan.terms)))
        {
          if (qo_is_filter_index (index_entryp) || qo_is_iscan_from_orderby (plan)
          || qo_is_iscan_from_groupby (plan))
        {
          /* filter index has a pre-defined key-range. ordery/groupby scan already checked nullable terms */
          ;     /* go ahead */
        }
          else
        {
          /* do not permit non-indexable term as key-filter */
          if (!term->can_use_index)
            {
              continue;
            }
        }
        }

      bitset_assign (&term_segs, &(QO_TERM_SEGS (term)));
      bitset_intersect (&term_segs, &(QO_NODE_SEGS (node)));

      /* if the term is consisted by only the node's segments which appear in scan terms, it will be key-filter.
       * otherwise will be data filter
       */
      if (!bitset_is_empty (&term_segs))
        {
          if (bitset_subset (&index_segs, &term_segs))
        {
          bitset_add (&(plan->plan_un.scan.kf_terms), t);
        }
        }
    }

      /* exclude key filter terms from sargs terms */
      bitset_difference (&(plan->sarged_terms), &(plan->plan_un.scan.kf_terms));
      bitset_difference (&remaining_terms, &(plan->plan_un.scan.kf_terms));
    }

  /* check for index cover scan */
  plan->plan_un.scan.index_cover = false;   /* init */
  if (index_entryp->cover_segments)
    {
      /* do not consider prefix index */
      if (qo_is_prefix_index (index_entryp) == false)
    {
      for (t = bitset_iterate (&remaining_terms, &iter); t != -1; t = bitset_next_member (&iter))
        {
          term = QO_ENV_TERM (env, t);

          if (!bitset_is_empty (&(QO_TERM_SUBQUERIES (term))))
        {
          /* term contains correlated subquery */
          continue;
        }

          break;        /* found data-filter */
        }

      if (t == -1)
        {
          /* not found data-filter; mark as covering index scan */
          plan->plan_un.scan.index_cover = true;
        }
    }
    }

  assert (!bitset_intersects (&(plan->plan_un.scan.terms), &(plan->plan_un.scan.kf_terms)));

  assert (!bitset_intersects (&(plan->plan_un.scan.terms), &(plan->sarged_terms)));
  assert (!bitset_intersects (&(plan->plan_un.scan.kf_terms), &(plan->sarged_terms)));

  assert (!bitset_intersects (&(plan->plan_un.scan.terms), &remaining_terms));
  assert (!bitset_intersects (&(plan->plan_un.scan.kf_terms), &remaining_terms));

  bitset_delset (&remaining_terms);
  bitset_delset (&term_segs);
  bitset_delset (&index_segs);

  /* check for index skip scan */
  plan->plan_un.scan.index_iss = false; /* init */
  if (index_entryp->is_iss_candidate)
    {
      assert (!bitset_is_empty (&(plan->plan_un.scan.terms)));
      assert (index_entryp->ils_prefix_len == 0);
      assert (!qo_is_filter_index (index_entryp));

      if (first_col_present == false)
    {
      plan->plan_un.scan.index_iss = true;
    }
    }

  /* check for loose index scan */
  plan->plan_un.scan.index_loose = false;   /* init */
  if (index_entryp->ils_prefix_len > 0)
    {
      assert (plan->plan_un.scan.index_iss == false);

      /* do not consider prefix index */
      if (qo_is_prefix_index (index_entryp) == false)
    {
      if (scan_method == QO_SCANMETHOD_INDEX_SCAN && qo_is_index_covering_scan (plan)
          && !qo_index_has_bit_attr (index_entryp))
        {
          /* covering index, no key-range, no data-filter; mark as loose index scan */
          plan->plan_un.scan.index_loose = true;
        }

      /* keep out not good index scan */
      if (!qo_is_index_loose_scan (plan))
        {
          /* check for no key-range, no key-filter index scan */
          if (qo_is_iscan (plan) && bitset_is_empty (&(plan->plan_un.scan.terms))
          && bitset_is_empty (&(plan->plan_un.scan.kf_terms)))
        {
          assert (!qo_is_iscan_from_groupby (plan));
          assert (!qo_is_iscan_from_orderby (plan));

          /* is not good index scan */
          qo_plan_release (plan);
          return NULL;
        }
        }
    }
    }

  /* check for no key-range, no key-filter index scan */
  if (qo_is_iscan (plan) && bitset_is_empty (&(plan->plan_un.scan.terms))
      && bitset_is_empty (&(plan->plan_un.scan.kf_terms)) && scan_method != QO_SCANMETHOD_INDEX_SCAN_INSPECT)
    {
      assert (!qo_is_iscan_from_groupby (plan));
      assert (!qo_is_iscan_from_orderby (plan));

      /* check for filter-index, loose index scan */
      if (qo_is_filter_index (index_entryp) || qo_is_index_loose_scan (plan))
    {
      /* filter index has a pre-defined key-range. */
      assert (bitset_is_empty (&(plan->plan_un.scan.terms)));

      ;         /* go ahead */
    }
      else
    {
      assert (false);
      qo_plan_release (plan);
      return NULL;
    }
    }

  if (qo_check_iscan_for_multi_range_opt (plan))
    {
      bool dummy;

      plan->multi_range_opt_use = PLAN_MULTI_RANGE_OPT_USE;
      plan->iscan_sort_list = qo_plan_compute_iscan_sort_list (plan, NULL, &dummy, false);
    }

  assert (plan->plan_un.scan.index != NULL);

  qo_plan_compute_cost (plan);

  plan = qo_top_plan_new (plan);

  return plan;
}

/*
 * qo_iscan_cost () -
 *   return:
 *   planp(in):
 */
static void
qo_iscan_cost (QO_PLAN * planp)
{
  QO_NODE *nodep;
  QO_NODE_INDEX_ENTRY *ni_entryp;
  QO_ATTR_CUM_STATS *cum_statsp;
  QO_INDEX_ENTRY *index_entryp;
  double sel, sel_limit, height, leaves, opages, filter_sel, leaf_access, heap_access;
  double object_IO, index_IO;
  QO_TERM *termp;
  BITSET_ITERATOR iter;
  int i, t, n, pkeys_num, index;

  nodep = planp->plan_un.scan.node;
  ni_entryp = planp->plan_un.scan.index;
  index_entryp = (ni_entryp)->head;
  cum_statsp = &(ni_entryp)->cum_stats;

  if (index_entryp->force < 0)
    {
      assert (false);
      qo_worst_cost (planp);
      return;
    }
  else if (index_entryp->force > 0)
    {
      qo_zero_cost (planp);
      return;
    }

  n = index_entryp->col_num;
  if (SM_IS_CONSTRAINT_UNIQUE_FAMILY (index_entryp->constraints->type) && n == index_entryp->nsegs)
    {
      assert (n > 0);

      for (i = 0; i < n; i++)
    {
      if (bitset_is_empty (&index_entryp->seg_equal_terms[i]))
        {
          break;
        }
    }

      if (i == n)
    {
      /* When the index is a unique family and all of index columns are specified in the equal conditions, the
       * cardinality of the scan will 0 or 1. In this case we will make the scan cost to zero, thus to force the
       * optimizer to select this scan.
       */
      index_entryp->all_unique_index_columns_are_equi_terms = true;
    }
    }

  /* selectivity of the index terms */
  sel = 1.0;

  pkeys_num = MIN (n, cum_statsp->pkeys_size);
  assert (pkeys_num <= BTREE_STATS_PKEYS_NUM);

  if (bitset_is_empty (&(planp->plan_un.scan.terms)))
    {
      assert (!qo_is_index_iss_scan (planp));
    }

  for (i = 0, t = bitset_iterate (&(planp->plan_un.scan.terms), &iter); t != -1; t = bitset_next_member (&iter))
    {
      termp = QO_ENV_TERM (QO_NODE_ENV (nodep), t);
      sel *= QO_TERM_SELECTIVITY (termp);

      /* each term can have multi index column. e.g.) (a,b) in .. */
      for (int j = 0; j < index_entryp->col_num; j++)
    {
      if (BITSET_MEMBER (QO_TERM_SEGS (termp), index_entryp->seg_idxs[j]))
        {
          i++;
        }
    }
    }

  /* check upper bound */
  sel = MIN (sel, 1.0);

  sel_limit = 0.0;      /* init */

  /* set selectivity limit */
  if (qo_is_index_iss_scan (planp))
    {
      index = i;
    }
  else
    {
      index = (i == 0) ? 0 : i - 1;
    }

  if (i <= pkeys_num && cum_statsp->pkeys[index] >= 1)
    {
      sel_limit = 1.0 / (double) cum_statsp->pkeys[index];
    }
  else
    {               /* can not use btree partial-key statistics */
      if (cum_statsp->keys >= 1)
    {
      sel_limit = 1.0 / (double) cum_statsp->keys;
    }
      else
    {
      if (QO_NODE_NCARD (nodep) == 0)
        {           /* empty class */
          sel_limit = sel = 0.0;
        }
      else if (QO_NODE_NCARD (nodep) >= 1)
        {
          sel_limit = 1.0 / (double) QO_NODE_NCARD (nodep);
        }
    }
    }
  assert (sel_limit <= 1.0);

  /* check lower bound */
  sel = MAX (sel, sel_limit);

  /* selectivity of the index key filter terms */
  filter_sel = 1.0;
  for (t = bitset_iterate (&(planp->plan_un.scan.kf_terms), &iter); t != -1; t = bitset_next_member (&iter))
    {
      termp = QO_ENV_TERM (QO_NODE_ENV (nodep), t);
      filter_sel *= QO_TERM_SELECTIVITY (termp);
    }

  /* number of leaf to be selected */
  leaf_access = sel * (double) QO_NODE_NCARD (nodep);
  /* height of the B+tree */
  height = (double) cum_statsp->height - 1;
  if (height < 0)
    {
      height = 0;
    }
  /* number of leaf pages to be accessed */
  leaves = ceil (sel * (double) cum_statsp->leafs);
  /* total number of pages occupied by objects */
  opages = (double) QO_NODE_TCARD (nodep);
  /* I/O cost to access B+tree index */
  index_IO = ((ni_entryp)->n * height) + leaves;

  /* Index Skip Scan adds to the index IO cost the K extra BTREE searches it does to fetch the next value for the
   * following BTRangeScan
   */
  if (qo_is_index_iss_scan (planp))
    {
      if (pkeys_num > 0)
    {
      assert (cum_statsp->pkeys != NULL);
      assert (cum_statsp->pkeys_size != 0);

      /* K leaves are additionally read */
      index_IO += cum_statsp->pkeys[0];
    }
    }

  /* IO cost to fetch objects */
  if (qo_is_index_covering_scan (planp))
    {
      object_IO = 1.0;
      heap_access = 0;
    }
  else
    {
      object_IO = opages * sel * filter_sel;
      heap_access = (double) QO_NODE_NCARD (nodep) * sel * filter_sel * (double) ISCAN_OID_ACCESS_OVERHEAD;
    }
  object_IO = MAX (1.0, object_IO);

  /* index scan requires more CPU cost than sequential scan */
  planp->fixed_cpu_cost = 0.0;
  planp->fixed_io_cost = index_IO;
  planp->variable_cpu_cost = (leaf_access + heap_access) * (double) QO_CPU_WEIGHT;
  planp->variable_io_cost = object_IO;
  planp->info->scan_rows = MAX (1, (double) QO_NODE_NCARD (nodep) * sel * filter_sel);

#if TEST_DUMP_PLAN_SCAN_COST
  fprintf (stdout, "\nIndex Scan Cost: \n");
  fprintf (stdout, "  -    Fixed CPU Cost: %lf\n", planp->fixed_cpu_cost);
  fprintf (stdout, "  -    Fixed I/O Cost: %lf\n", planp->fixed_io_cost);
  fprintf (stdout, "  - Variable CPU Cost: %lf\n", planp->variable_cpu_cost);
  fprintf (stdout, "  - Variable I/O Cost: %lf\n", planp->variable_io_cost);
  fprintf (stdout, "  -    Total     Cost: %lf\n",
       planp->fixed_cpu_cost + planp->fixed_io_cost + planp->variable_cpu_cost + planp->variable_io_cost);
  if (planp->vtbl != NULL)
    {
      // qo_plan_lite_print (planp, stdout, 0);
      qo_plan_fprint (planp, stdout, 0, NULL);
      fprintf (stdout, "\n");
    }
  fprintf (stdout, "\n");
#endif /* TEST_DUMP_PLAN_SCAN_COST */
}


static void
qo_scan_fprint (QO_PLAN * plan, FILE * f, int howfar)
{
  bool natural_desc_index = false;

  if (plan->plan_un.scan.node->entity_spec->info.spec.cte_pointer)
    {
      PT_NODE *spec = plan->plan_un.scan.node->entity_spec;
      if (spec->info.spec.cte_pointer->info.pointer.node->info.cte.recursive_part)
    {
      fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', "recursive CTE: ");
    }
      else
    {
      fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', "simple CTE:");
    }
    }
  else
    {
      fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', "class:");
    }

  qo_node_fprint (plan->plan_un.scan.node, f);

  if (qo_is_interesting_order_scan (plan))
    {
      fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', "index: ");
      fprintf (f, "%s ", plan->plan_un.scan.index->head->constraints->name);

      /* print key limit */
      if (plan->plan_un.scan.index->head->key_limit)
    {
      PT_NODE *key_limit = plan->plan_un.scan.index->head->key_limit;
      PT_NODE *saved_next = key_limit->next;
      PARSER_CONTEXT *parser = QO_ENV_PARSER (plan->info->env);
      PT_PRINT_VALUE_FUNC saved_func = parser->print_db_value;

      parser->print_db_value = pt_print_node_value;
      if (saved_next)
        {
          saved_next->next = key_limit;
          key_limit->next = NULL;
        }
      fprintf (f, "keylimit %s ", parser_print_tree_list (parser, saved_next ? saved_next : key_limit));
      parser->print_db_value = saved_func;
      if (saved_next)
        {
          key_limit->next = saved_next;
          saved_next->next = NULL;
        }
    }

      qo_termset_fprint ((plan->info)->env, &plan->plan_un.scan.terms, f);

      /* print index covering */
      if (qo_is_index_covering_scan (plan))
    {
      if (bitset_cardinality (&(plan->plan_un.scan.terms)) > 0)
        {
          fprintf (f, " ");
        }
      fprintf (f, "(covers)");
    }

      if (qo_is_index_iss_scan (plan))
    {
      fprintf (f, " (index skip scan)");
    }

      if (qo_is_index_loose_scan (plan))
    {
      fprintf (f, " (loose index scan on prefix %d)", plan->plan_un.scan.index->head->ils_prefix_len);
    }

      if (qo_plan_multi_range_opt (plan))
    {
      fprintf (f, " (multi_range_opt)");
    }

      if (plan->plan_un.scan.index && plan->plan_un.scan.index->head->use_descending)
    {
      fprintf (f, " (desc_index)");
      natural_desc_index = true;
    }

      if (!natural_desc_index && (QO_ENV_PT_TREE (plan->info->env)->info.query.q.select.hint & PT_HINT_USE_IDX_DESC))
    {
      fprintf (f, " (desc_index forced)");
    }

      if (!bitset_is_empty (&(plan->plan_un.scan.kf_terms)))
    {
      fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', "filtr: ");
      qo_termset_fprint ((plan->info)->env, &(plan->plan_un.scan.kf_terms), f);
    }
    }
}

/*
 * qo_scan_info () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_scan_info (QO_PLAN * plan, FILE * f, int howfar)
{
  QO_NODE *node = plan->plan_un.scan.node;
  int i, n = 1;
  const char *name;
  char buf[257] = { '\0', };

  fprintf (f, "\n%*c%s(", (int) howfar, ' ', (plan->vtbl)->info_string);
  if (QO_NODE_INFO (node))
    {
      for (i = 0, n = QO_NODE_INFO_N (node); i < n; i++)
    {
      name = QO_NODE_INFO (node)->info[i].name;
      fprintf (f, "%s ", (name ? name : "(anon)"));
    }
    }
  name = QO_NODE_NAME (node);
  if (n == 1)
    {
      fprintf (f, "%s", (name ? name : "(unknown)"));
    }
  else
    {
      fprintf (f, "as %s", (name ? name : "(unknown)"));
    }

  if (qo_is_iscan (plan) || qo_is_iscan_from_orderby (plan))
    {
      BITSET_ITERATOR bi;
      QO_ENV *env;
      int i;
      const char *separator;
      bool natural_desc_index = false;

      env = (plan->info)->env;
      separator = ", ";

      fprintf (f, "%s%s", separator, plan->plan_un.scan.index->head->constraints->name);

      /* print key limit */
      if (plan->plan_un.scan.index->head->key_limit)
    {
      PT_NODE *key_limit = plan->plan_un.scan.index->head->key_limit;
      PT_NODE *saved_next = key_limit->next;
      PARSER_CONTEXT *parser = QO_ENV_PARSER (plan->info->env);
      PT_PRINT_VALUE_FUNC saved_func = parser->print_db_value;
      parser->print_db_value = pt_print_node_value;
      if (saved_next)
        {
          saved_next->next = key_limit;
          key_limit->next = NULL;
        }
      fprintf (f, "(keylimit %s) ", parser_print_tree_list (parser, saved_next ? saved_next : key_limit));
      parser->print_db_value = saved_func;
      if (saved_next)
        {
          key_limit->next = saved_next;
          saved_next->next = NULL;
        }
    }

      for (i = bitset_iterate (&(plan->plan_un.scan.terms), &bi); i != -1; i = bitset_next_member (&bi))
    {
      fprintf (f, "%s%s", separator, qo_term_string (QO_ENV_TERM (env, i), buf));
      separator = " and ";
    }
      if (bitset_cardinality (&(plan->plan_un.scan.kf_terms)) > 0)
    {
      separator = ", [";
      for (i = bitset_iterate (&(plan->plan_un.scan.kf_terms), &bi); i != -1; i = bitset_next_member (&bi))
        {
          fprintf (f, "%s%s", separator, qo_term_string (QO_ENV_TERM (env, i), buf));
          separator = " and ";
        }
      fprintf (f, "]");
    }

      /* print index covering */
      if (qo_is_index_covering_scan (plan))
    {
      fprintf (f, " (covers)");
    }

      if (qo_is_index_iss_scan (plan))
    {
      fprintf (f, " (index skip scan)");
    }

      if (qo_is_index_loose_scan (plan))
    {
      fprintf (f, " (loose index scan on prefix %d)", plan->plan_un.scan.index->head->ils_prefix_len);
    }

      if (qo_plan_multi_range_opt (plan))
    {
      fprintf (f, " (multi_range_opt)");
    }

      if (plan->plan_un.scan.index && plan->plan_un.scan.index->head->use_descending)
    {
      fprintf (f, " (desc_index)");
      natural_desc_index = true;
    }

      if (!natural_desc_index && (QO_ENV_PT_TREE (plan->info->env)->info.query.q.select.hint & PT_HINT_USE_IDX_DESC))
    {
      fprintf (f, " (desc_index forced)");
    }
    }

  fprintf (f, ")");
}


/*
 * qo_sort_new () -
 *   return:
 *   root(in):
 *   order(in):
 *   sort_type(in):
 */
static QO_PLAN *
qo_sort_new (QO_PLAN * root, QO_EQCLASS * order, SORT_TYPE sort_type)
{
  QO_PLAN *subplan, *plan;

  subplan = root;

  if (sort_type == SORT_TEMP)
    {               /* is not top-level plan */
      /* skip out top-level sort plan */
      for (; subplan && subplan->plan_type == QO_PLANTYPE_SORT && subplan->plan_un.sort.sort_type != SORT_LIMIT;
       subplan = subplan->plan_un.sort.subplan)
    {
      if (subplan->top_rooted && subplan->plan_un.sort.sort_type != SORT_TEMP)
        {
          ;         /* skip and go ahead */
        }
      else
        {
          break;        /* is not top-level sort plan */
        }
    }

      /* check for dummy sort plan */
      if (order == QO_UNORDERED && subplan != NULL && subplan->plan_type == QO_PLANTYPE_SORT)
    {
      return qo_plan_add_ref (root);
    }

      /* skip out empty sort plan */
      for (; subplan && subplan->plan_type == QO_PLANTYPE_SORT && subplan->plan_un.sort.sort_type != SORT_LIMIT;
       subplan = subplan->plan_un.sort.subplan)
    {
      if (!bitset_is_empty (&(subplan->sarged_terms)))
        {
          break;
        }
    }
    }

  if (subplan == NULL)
    {
      return NULL;
    }

  plan = qo_plan_malloc ((subplan->info)->env);
  if (plan == NULL)
    {
      return NULL;
    }

  plan->info = subplan->info;
  plan->refcount = 0;
  plan->top_rooted = subplan->top_rooted;
  plan->well_rooted = false;
  plan->iscan_sort_list = NULL;
  plan->analytic_eval_list = NULL;
  plan->order = order;
  plan->plan_type = QO_PLANTYPE_SORT;
  plan->vtbl = &qo_sort_plan_vtbl;

  plan->plan_un.sort.sort_type = sort_type;
  plan->plan_un.sort.subplan = qo_plan_add_ref (subplan);
  plan->plan_un.sort.xasl = NULL;   /* To be determined later */

  plan->multi_range_opt_use = PLAN_MULTI_RANGE_OPT_NO;
  plan->has_sort_limit = (sort_type == SORT_LIMIT || subplan->has_sort_limit);
  plan->need_final_sort = subplan->need_final_sort;

  qo_plan_compute_cost (plan);
  if (sort_type == SORT_GROUPBY || sort_type == SORT_DISTINCT)
    {
      qo_estimate_ngroups (plan, sort_type);
    }

  plan = qo_top_plan_new (plan);

  return plan;
}

/*
 * qo_sort_walk () -
 *   return:
 *   plan(in):
 *   child_fn(in):
 *   child_data(in):
 *   parent_fn(in):
 *   parent_data(in):
 */
static void
qo_sort_walk (QO_PLAN * plan, void (*child_fn) (QO_PLAN *, void *), void *child_data,
          void (*parent_fn) (QO_PLAN *, void *), void *parent_data)
{
  if (child_fn)
    {
      (*child_fn) (plan->plan_un.sort.subplan, child_data);
    }
  if (parent_fn)
    {
      (*parent_fn) (plan, parent_data);
    }
}

static void
qo_sort_fprint (QO_PLAN * plan, FILE * f, int howfar)
{
  switch (plan->plan_un.sort.sort_type)
    {
    case SORT_TEMP:
      fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', "order:");
      qo_eqclass_fprint_wrt (plan->order, &(plan->info->nodes), f);
      break;

    case SORT_LIMIT:
      fprintf (f, "(sort limit)");
      break;

    case SORT_GROUPBY:
      fprintf (f, "(group by)");
      break;

    case SORT_ORDERBY:
      fprintf (f, "(order by)");
      break;

    case SORT_DISTINCT:
      fprintf (f, "(distinct)");
      break;

    default:
      break;
    }

  qo_plan_fprint (plan->plan_un.sort.subplan, f, howfar, "subplan: ");
}

/*
 * qo_sort_info () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_sort_info (QO_PLAN * plan, FILE * f, int howfar)
{

  switch (plan->plan_un.sort.sort_type)
    {
    case SORT_TEMP:
      if (plan->order != QO_UNORDERED)
    {
#if 0
      /*
       * Don't bother printing these out; they're almost always
       * superfluous from the standpoint of a naive user trying to
       * figure out what's going on.
       */
      fprintf (f, "\n%*c%s(", (int) howfar, ' ', (plan->vtbl)->info_string);
      qo_eqclass_fprint_wrt (plan->order, &(plan->info->nodes), f);
      fprintf (f, ")");
#endif
    }
      break;
    case SORT_LIMIT:
      fprintf (f, "\n%*c%s(%s)", (int) howfar, ' ', (plan->vtbl)->info_string, "sort limit");
      howfar += INDENT_INCR;
      break;
    case SORT_GROUPBY:
      fprintf (f, "\n%*c%s(%s)", (int) howfar, ' ', (plan->vtbl)->info_string, "group by");
      howfar += INDENT_INCR;
      break;

    case SORT_ORDERBY:
      fprintf (f, "\n%*c%s(%s)", (int) howfar, ' ', (plan->vtbl)->info_string, "order by");
      howfar += INDENT_INCR;
      break;

    case SORT_DISTINCT:
      fprintf (f, "\n%*c%s(%s)", (int) howfar, ' ', (plan->vtbl)->info_string, "distinct");
      howfar += INDENT_INCR;
      break;

    default:
      break;
    }

  qo_plan_lite_print (plan->plan_un.sort.subplan, f, howfar);
}

/*
 * qo_sort_cost () -
 *   return:
 *   planp(in):
 */
static void
qo_sort_cost (QO_PLAN * planp)
{
  QO_PLAN *subplanp;

  subplanp = planp->plan_un.sort.subplan;

  /* for worst cost */
  if (subplanp->fixed_cpu_cost == QO_INFINITY || subplanp->fixed_io_cost == QO_INFINITY
      || subplanp->variable_cpu_cost == QO_INFINITY || subplanp->variable_io_cost == QO_INFINITY)
    {
      qo_worst_cost (planp);
      return;
    }

  if (subplanp->plan_type == QO_PLANTYPE_SORT && planp->plan_un.sort.sort_type == SORT_TEMP)
    {
      /* This plan won't actually incur any runtime cost because it won't actually exist (its sort spec will supersede
       * the sort spec of the subplan).  We can't just clobber the sort spec on the lower plan because it might be
       * shared by others.
       */
      planp->fixed_cpu_cost = subplanp->fixed_cpu_cost;
      planp->fixed_io_cost = subplanp->fixed_io_cost;
      planp->variable_cpu_cost = subplanp->variable_cpu_cost;
      planp->variable_io_cost = subplanp->variable_io_cost;
    }
  else if (planp->plan_un.sort.sort_type == SORT_LIMIT)
    {
      if (subplanp->plan_type == QO_PLANTYPE_SORT)
    {
      /* No sense in having a STOP plan above a SORT plan */
      qo_worst_cost (planp);
    }

      if (qo_is_iscan_from_orderby (subplanp))
    {
      double save_ncard = QO_NODE_NCARD (subplanp->plan_un.scan.node);
      QO_NODE_NCARD (subplanp->plan_un.scan.node) = (double) db_get_bigint (&QO_ENV_LIMIT_VALUE (planp->info->env));
      (*(subplanp->vtbl)->cost_fn) (subplanp);
      QO_NODE_NCARD (subplanp->plan_un.scan.node) = save_ncard;
    }

      /* SORT-LIMIT plan has the same cost as the subplan (since actually sorting items in memory is not a big
       * drawback. Costs improvements will be applied when we consider joining this plan with other plans
       */
      planp->fixed_cpu_cost = subplanp->fixed_cpu_cost;
      planp->fixed_io_cost = subplanp->fixed_io_cost;
      planp->variable_cpu_cost = subplanp->variable_cpu_cost;
      planp->variable_io_cost = subplanp->variable_io_cost;
    }
  else
    {
      QO_EQCLASS *order;
      double objects, pages, result_size;

      order = planp->order;
      objects = (subplanp->info)->cardinality;
      result_size = objects * (double) (subplanp->info)->projected_size;
      pages = result_size / (double) IO_PAGESIZE;
      if (pages < 1.0)
    {
      pages = 1.0;
    }

      /* The cost (in io's) of just setting up a list file.  This is mostly to discourage the optimizer from choosing
       * merge join for joins of little classes.
       */
      planp->fixed_cpu_cost = subplanp->fixed_cpu_cost + subplanp->variable_cpu_cost + TEMP_SETUP_COST;
      planp->fixed_io_cost = subplanp->fixed_io_cost + subplanp->variable_io_cost;
      planp->variable_cpu_cost = objects * (double) QO_CPU_WEIGHT;
      planp->variable_io_cost = pages;

      if (order != QO_UNORDERED && order != subplanp->order)
    {
      double sort_io, tcard;

      sort_io = 0.0;    /* init */

      if (objects > 1.0)
        {
          if (pages < (double) prm_get_integer_value (PRM_ID_SR_NBUFFERS))
        {
          /* We can sort the result in memory without any additional io costs. Assume cpu costs are n*log(n) in
           * number of recors.
           */
          sort_io = (double) QO_CPU_WEIGHT *objects * log2 (objects);
        }
          else
        {
          /* There are too many records to permit an in-memory sort, so io costs will be increased.  Assume
           * that the io costs increase by the number of pages required to hold the intermediate result.  CPU
           * costs increase as above. Model courtesy of Ender.
           */
          sort_io = pages * log3 (pages / 4.0);

          /* guess: apply IO caching for big size sort list. Disk IO cost cannot be greater than the 10% number
           * of the requested IO pages
           */
          if (subplanp->plan_type == QO_PLANTYPE_SCAN)
            {
              tcard = (double) QO_NODE_TCARD (subplanp->plan_un.scan.node);
              tcard *= 0.1;
              if (pages >= tcard)
            {   /* big size sort list */
              sort_io *= 0.1;
            }
            }
        }
        }

      planp->fixed_io_cost += sort_io;
    }
    }

#if TEST_DUMP_PLAN_SORT_COST
  fprintf (stdout, "\nSort Cost: \n");
  fprintf (stdout, "  -    Fixed CPU Cost: %lf\n", planp->fixed_cpu_cost);
  fprintf (stdout, "  -    Fixed I/O Cost: %lf\n", planp->fixed_io_cost);
  fprintf (stdout, "  - Variable CPU Cost: %lf\n", planp->variable_cpu_cost);
  fprintf (stdout, "  - Variable I/O Cost: %lf\n", planp->variable_io_cost);
  fprintf (stdout, "  -    Total     Cost: %lf\n",
       planp->fixed_cpu_cost + planp->fixed_io_cost + planp->variable_cpu_cost + planp->variable_io_cost);
  if (planp->vtbl != NULL)
    {
      // qo_plan_lite_print (planp, stdout, 0);
      qo_plan_fprint (planp, stdout, 0, NULL);
      fprintf (stdout, "\n");
    }
  fprintf (stdout, "\n");
#endif /* TEST_DUMP_PLAN_SORT_COST */
}

/*
 * qo_join_new () -
 *   return:
 *   info(in):
 *   join_type(in):
 *   join_method(in):
 *   outer(in):
 *   inner(in):
 *   join_terms(in):
 *   duj_terms(in):
 *   afj_terms(in):
 *   sarged_terms(in):
 *   pinned_subqueries(in):
 */
static QO_PLAN *
qo_join_new (QO_INFO * info, JOIN_TYPE join_type, QO_JOINMETHOD join_method, QO_PLAN * outer, QO_PLAN * inner,
         BITSET * join_terms, BITSET * duj_terms, BITSET * afj_terms, BITSET * sarged_terms,
         BITSET * pinned_subqueries, BITSET * hash_terms)
{
  QO_PLAN *plan = NULL;
  QO_NODE *node = NULL;
  PT_NODE *spec = NULL;
  BITSET sarg_out_terms;

  bitset_init (&sarg_out_terms, info->env);

  if (inner->has_sort_limit && join_method != QO_JOINMETHOD_MERGE_JOIN)
    {
      /* SORT-LIMIT plans are allowed on inner nodes only for merge joins */
      return NULL;
    }

  plan = qo_plan_malloc (info->env);
  if (plan == NULL)
    {
      return NULL;
    }

  QO_ASSERT (info->env, outer != NULL);
  QO_ASSERT (info->env, inner != NULL);

  plan->info = info;
  plan->refcount = 0;
  plan->top_rooted = false;
  plan->well_rooted = false;
  plan->iscan_sort_list = NULL;
  plan->analytic_eval_list = NULL;
  plan->plan_type = QO_PLANTYPE_JOIN;
  plan->multi_range_opt_use = PLAN_MULTI_RANGE_OPT_NO;
  plan->has_sort_limit = (outer->has_sort_limit || inner->has_sort_limit);

  switch (join_method)
    {

    case QO_JOINMETHOD_NL_JOIN:
    case QO_JOINMETHOD_IDX_JOIN:
      if (join_method == QO_JOINMETHOD_NL_JOIN)
    {
      plan->vtbl = &qo_nl_join_plan_vtbl;
    }
      else
    {
      plan->vtbl = &qo_idx_join_plan_vtbl;
    }
      plan->order = QO_UNORDERED;

      /* These checks are necessary because of restrictions in the current XASL implementation of nested loop joins.
       * Never put anything on the inner plan that isn't file-based (i.e., a scan of either a heap file or a list
       * file).
       */
      if (!VALID_INNER (inner))
    {
      inner = qo_sort_new (inner, inner->order, SORT_TEMP);
    }
      else if (IS_OUTER_JOIN_TYPE (join_type))
    {
      /* for outer join, if inner plan is a scan of classes in hierarchy */
      if (inner->plan_type == QO_PLANTYPE_SCAN && QO_NODE_IS_CLASS_HIERARCHY (inner->plan_un.scan.node))
        {
          inner = qo_sort_new (inner, inner->order, SORT_TEMP);
        }
    }

      break;

    case QO_JOINMETHOD_MERGE_JOIN:

      plan->vtbl = &qo_merge_join_plan_vtbl;
#if 0
      /* Don't do this anymore; it relies on symmetry, which definitely doesn't apply anymore with the advent of outer
       * joins.
       */

      /* Arrange to always put the smallest cardinality on the outer term; this may lead to some savings given the
       * current merge join implementation.
       */
      if ((inner->info)->cardinality < (outer->info)->cardinality)
    {
      QO_PLAN *tmp;
      tmp = inner;
      inner = outer;
      outer = tmp;
    }
#endif

      /* The merge join result has the same nominal order as the two subjoins that feed it.  However, if it happens
       * that none of the segments in that order are to be projected from the result, the result is effectively
       * *unordered*.  Check for that condition here.
       */
      plan->order =
    bitset_intersects (&(QO_EQCLASS_SEGS (outer->order)),
               &((plan->info)->projected_segs)) ? outer->order : QO_UNORDERED;

      /* The current implementation of merge joins always produces a list file These two checks are necessary because
       * of restrictions in the current XASL implementation of merge joins.
       */
      if (outer->plan_type != QO_PLANTYPE_SORT)
    {
      outer = qo_sort_new (outer, outer->order, SORT_TEMP);
    }
      if (inner->plan_type != QO_PLANTYPE_SORT)
    {
      inner = qo_sort_new (inner, inner->order, SORT_TEMP);
    }

      break;

    case QO_JOINMETHOD_HASH_JOIN:
      plan->vtbl = &qo_hash_join_plan_vtbl;
      plan->order = QO_UNORDERED;

      break;
    }

  assert (inner != NULL && outer != NULL);
  if (inner == NULL || outer == NULL)
    {
      return NULL;
    }

  node = QO_ENV_NODE (info->env, bitset_first_member (&((inner->info)->nodes)));

  assert (node != NULL);
  if (node == NULL)
    {
      return NULL;
    }

  /* check for cselect of method */
  spec = QO_NODE_ENTITY_SPEC (node);
  if (spec && spec->info.spec.flat_entity_list == NULL && spec->info.spec.derived_table_type == PT_IS_CSELECT)
    {
      /* mark as cselect join */
      plan->plan_un.join.join_type = JOIN_CSELECT;
    }
  else
    {
      plan->plan_un.join.join_type = join_type;
    }

  plan->plan_un.join.join_method = join_method;
  plan->plan_un.join.outer = qo_plan_add_ref (outer);
  plan->plan_un.join.inner = qo_plan_add_ref (inner);

  bitset_init (&(plan->plan_un.join.join_terms), info->env);
  bitset_init (&(plan->plan_un.join.during_join_terms), info->env);
  bitset_init (&(plan->plan_un.join.after_join_terms), info->env);
  bitset_init (&(plan->plan_un.join.hash_terms), info->env);

  /* set join terms */
  bitset_assign (&(plan->plan_un.join.join_terms), join_terms);
  /* set hash terms */
  bitset_assign (&(plan->plan_un.join.hash_terms), hash_terms);
  /* add to out terms */
  bitset_union (&sarg_out_terms, &(plan->plan_un.join.join_terms));

  if (IS_OUTER_JOIN_TYPE (join_type))
    {
      /* set during join terms */
      bitset_assign (&(plan->plan_un.join.during_join_terms), duj_terms);
      bitset_difference (&(plan->plan_un.join.during_join_terms), &sarg_out_terms);
      /* add to out terms */
      bitset_union (&sarg_out_terms, &(plan->plan_un.join.during_join_terms));

      /* set after join terms */
      bitset_assign (&(plan->plan_un.join.after_join_terms), afj_terms);
      bitset_difference (&(plan->plan_un.join.after_join_terms), &sarg_out_terms);
      /* add to out terms */
      bitset_union (&sarg_out_terms, &(plan->plan_un.join.after_join_terms));
    }

  /* set plan's sarged terms */
  bitset_assign (&(plan->sarged_terms), sarged_terms);
  bitset_difference (&(plan->sarged_terms), &sarg_out_terms);

  /* Make sure that the pinned subqueries and the sargs are placed on the same node: by now the pinned subqueries are
   * very likely pinned here precisely because they're used by these sargs. Separating them (so that they get evaluated
   * in some different order) will yield incorrect results.
   */
  bitset_assign (&(plan->subqueries), pinned_subqueries);

  plan->parallel_opt_use = qo_check_hjoin_for_parallel_opt (plan);

  if (qo_check_join_for_multi_range_opt (plan))
    {
      bool dummy;

      plan->multi_range_opt_use = PLAN_MULTI_RANGE_OPT_USE;
      plan->iscan_sort_list = qo_plan_compute_iscan_sort_list (plan, NULL, &dummy, false);
    }

  qo_plan_compute_cost (plan);

  if (QO_ENV_USE_SORT_LIMIT (info->env) && !plan->has_sort_limit
      && bitset_is_equivalent (&info->env->sort_limit_nodes, &info->nodes))
    {
      /* Consider creating a SORT_LIMIT plan over this plan only if it cannot skip order by. Since we know that we
       * already have all ORDER BY nodes in this plan, we can verify orderby_skip at this point
       */
      plan = qo_sort_new (plan, QO_UNORDERED, SORT_LIMIT);
      if (plan == NULL)
    {
      return NULL;
    }
    }

#if 1               /* MERGE_ALWAYS_MAKES_LISTFILE */
  /* This is necessary to get the proper cost model for merge joins, which always build their result into a listfile
   * right now.  At the moment the cost model for a merge plan just models the cost of producing the result tuples, but
   * not storing them into a listfile. We could push the cost into the merge plan itself, I suppose, but a rational
   * implementation wouldn't impose this cost, and so I have hope that one day we'll be able to eliminate it.
   */
  if (join_method == QO_JOINMETHOD_MERGE_JOIN)
    {
      /* As noted in the comment on plan->order in qo_join_new,
       * the sort-merge join preserves the outer plan’s sort order but does not consider the sort direction.
       * It always sorts the join columns in S_ASC order (gen_outer).
       * Therefore, even if the ORDER BY sort column matches the sort column used for the sort-merge join,
       * a final sort may be required if the sort directions differ.
       */
      plan->need_final_sort = true;

      plan = qo_sort_new (plan, plan->order, SORT_TEMP);
    }
#endif /* MERGE_ALWAYS_MAKES_LISTFILE */

  if (join_method == QO_JOINMETHOD_HASH_JOIN)
    {
      plan->need_final_sort = true;
    }

  bitset_delset (&sarg_out_terms);

  plan = qo_top_plan_new (plan);

  return plan;
}

/*
 * qo_join_free () -
 *   return:
 *   plan(in):
 */
static void
qo_join_free (QO_PLAN * plan)
{
  bitset_delset (&(plan->plan_un.join.join_terms));
  bitset_delset (&(plan->plan_un.join.during_join_terms));
  bitset_delset (&(plan->plan_un.join.after_join_terms));
}

/*
 * qo_join_walk () -
 *   return:
 *   plan(in):
 *   child_fn(in):
 *   child_data(in):
 *   parent_fn(in):
 *   parent_data(in):
 */
static void
qo_join_walk (QO_PLAN * plan, void (*child_fn) (QO_PLAN *, void *), void *child_data,
          void (*parent_fn) (QO_PLAN *, void *), void *parent_data)
{
  if (child_fn)
    {
      (*child_fn) (plan->plan_un.join.outer, child_data);
      (*child_fn) (plan->plan_un.join.inner, child_data);
    }
  if (parent_fn)
    {
      (*parent_fn) (plan, parent_data);
    }
}

/*
 * qo_join_fprint () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_join_fprint (QO_PLAN * plan, FILE * f, int howfar)
{
  switch (plan->plan_un.join.join_type)
    {
    case JOIN_INNER:
      if (!bitset_is_empty (&(plan->plan_un.join.join_terms)))
    {
      fputs (" (inner join)", f);
    }
      else
    {
      if (plan->plan_un.join.join_method == QO_JOINMETHOD_IDX_JOIN)
        {
          fputs (" (inner join)", f);
        }
      else
        {
          fputs (" (cross join)", f);
        }
    }
      break;
    case JOIN_LEFT:
      fputs (" (left outer join)", f);
      break;
    case JOIN_RIGHT:
      fputs (" (right outer join)", f);
      break;
    case JOIN_OUTER:        /* not used */
      fputs (" (full outer join)", f);
      break;
    case JOIN_CSELECT:
      fputs (" (cselect join)", f);
      break;
    case NO_JOIN:
    default:
      fputs (" (unknown join type)", f);
      break;
    }
  if (!bitset_is_empty (&(plan->plan_un.join.join_terms)))
    {
      fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', "edge:");
      qo_termset_fprint ((plan->info)->env, &(plan->plan_un.join.join_terms), f);
    }
  qo_plan_fprint (plan->plan_un.join.outer, f, howfar, "outer: ");
  qo_plan_fprint (plan->plan_un.join.inner, f, howfar, "inner: ");
  qo_plan_print_outer_join_terms (plan, f, howfar);
}

/*
 * qo_join_info () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_join_info (QO_PLAN * plan, FILE * f, int howfar)
{
  if (!bitset_is_empty (&(plan->plan_un.join.join_terms)))
    {
      QO_ENV *env;
      const char *separator;
      int i;
      BITSET_ITERATOR bi;
      char buf[257] = { '\0', };

      env = (plan->info)->env;
      separator = "";

      fprintf (f, "\n%*c%s(", (int) howfar, ' ', (plan->vtbl)->info_string);
      for (i = bitset_iterate (&(plan->plan_un.join.join_terms), &bi); i != -1; i = bitset_next_member (&bi))
    {
      fprintf (f, "%s%s", separator, qo_term_string (QO_ENV_TERM (env, i), buf));
      separator = " and ";
    }
      fprintf (f, ")");
    }
  else
    {
      fprintf (f, "\n%*cNested loops", (int) howfar, ' ');
    }

  if (plan->plan_un.join.join_type == JOIN_LEFT)
    {
      fprintf (f, ": left outer");
    }
  else if (plan->plan_un.join.join_type == JOIN_RIGHT)
    {
      fprintf (f, ": right outer");
    }

  qo_plan_lite_print (plan->plan_un.join.outer, f, howfar + INDENT_INCR);
  qo_plan_lite_print (plan->plan_un.join.inner, f, howfar + INDENT_INCR);
}


/*
 * qo_can_apply_limit_card () -
 *   return: true if limit-based cardinality can be applied for guessed_result_cardinality, false otherwise
 *   env(in):
 *
 * Limit card should NOT be applied when the query has:
 * 1. ORDER BY (sort needed, cannot stop early)
 * 2. Analytic (window functions need full partition)
 * 3. DISTINCT (deduplication needs full result)
 * 4. GROUP BY (aggregation needs full group)
 * 5. Aggregate functions (scalar or with GROUP BY)
 * 6. Window / Recursive CTE / Hierarchical Query (CONNECT BY)
 */
static bool
qo_can_apply_limit_card (QO_ENV * env)
{
  PARSER_CONTEXT *parser;
  PT_NODE *tree;

  if (env == NULL || (tree = QO_ENV_PT_TREE (env)) == NULL)
    {
      return false;
    }

  parser = QO_ENV_PARSER (env);
  if (parser == NULL)
    {
      return false;
    }

  /* Only PT_SELECT has group_by, connect_by in q.select */
  if (tree->node_type != PT_SELECT)
    {
      return false;
    }

  /* 1. ORDER BY */
  if (tree->info.query.order_by != NULL)
    {
      return false;
    }

  /* 2. Analytic (Window functions) */
  if (pt_has_analytic (parser, tree))
    {
      return false;
    }

  /* 3. DISTINCT */
  if (tree->info.query.all_distinct == PT_DISTINCT)
    {
      return false;
    }

  /* 4. GROUP BY */
  if (tree->info.query.q.select.group_by != NULL)
    {
      return false;
    }

  /* 5. Aggregate functions */
  if (pt_has_aggregate (parser, tree))
    {
      return false;
    }

  /* 6. Hierarchical Query (CONNECT BY) */
  if (tree->info.query.q.select.connect_by != NULL)
    {
      return false;
    }

  /* 7. Recursive CTE */
  if (tree->info.query.with != NULL && tree->info.query.with->info.with_clause.recursive != 0)
    {
      return false;
    }

  return true;
}

/*
 * qo_nljoin_cost () -
 *   return:
 *   planp(in):
 */
static void
qo_nljoin_cost (QO_PLAN * planp)
{
  QO_PLAN *inner, *outer;
  double inner_io_cost, inner_cpu_cost, outer_io_cost, outer_cpu_cost;
  double guessed_result_cardinality, limit_val, outer_card;

  inner = planp->plan_un.join.inner;

  /* for worst cost */
  if (inner->fixed_cpu_cost == QO_INFINITY || inner->fixed_io_cost == QO_INFINITY
      || inner->variable_cpu_cost == QO_INFINITY || inner->variable_io_cost == QO_INFINITY)
    {
      qo_worst_cost (planp);
      return;
    }

  outer = planp->plan_un.join.outer;

  /* for worst cost */
  if (outer->fixed_cpu_cost == QO_INFINITY || outer->fixed_io_cost == QO_INFINITY
      || outer->variable_cpu_cost == QO_INFINITY || outer->variable_io_cost == QO_INFINITY)
    {
      qo_worst_cost (planp);
      return;
    }

  /* CPU and IO costs which are fixed againt join */
  planp->fixed_cpu_cost = outer->fixed_cpu_cost + inner->fixed_cpu_cost;
  planp->fixed_io_cost = outer->fixed_io_cost + inner->fixed_io_cost;

  /* inner side CPU cost of nested-loop block join */
  if (outer->plan_type == QO_PLANTYPE_SORT && outer->plan_un.sort.sort_type == SORT_LIMIT)
    {
      /* cardinality of a SORT_LIMIT plan is given by the value of the query limit */
      guessed_result_cardinality = (double) db_get_bigint (&QO_ENV_LIMIT_VALUE (outer->info->env));
    }
  else if (QO_PLAN_HAS_LIMIT (planp)
       && (planp->info->planner->can_apply_limit_card || qo_plan_is_orderby_skip_candidate (planp)))
    {
      limit_val = QO_PLAN_HAS_CONSTANT_LIMIT (planp)
    ? (double) db_get_bigint (&QO_ENV_LIMIT_VALUE (planp->info->env)) : GUESSED_BIND_LIMIT_CARD;

      if (outer->plan_type == QO_PLANTYPE_SCAN)
    {
      planp->limit_nljoin_guessed_card = MAX (limit_val / (outer->info)->hit_prob, 1.0);
      guessed_result_cardinality = MIN (planp->limit_nljoin_guessed_card, (outer->info)->cardinality);
    }
      else if (outer->plan_type == QO_PLANTYPE_JOIN)
    {
      guessed_result_cardinality = outer->limit_nljoin_guessed_card;
      outer_card = ((outer->info)->cardinality == 0) ? 1 : (outer->info)->cardinality;
      /* result = outer_guessed * (inner_card * selectivity) = outer_guessed * (plan_card/outer_card). */
      planp->limit_nljoin_guessed_card =
        MAX (1.0, guessed_result_cardinality * ((planp->info)->cardinality / outer_card));
    }
      else
    {
      /* won't come here */
      guessed_result_cardinality = (outer->info)->cardinality;
    }
      guessed_result_cardinality = MAX (1.0, guessed_result_cardinality);
    }
  else
    {
      guessed_result_cardinality = (outer->info)->cardinality;
    }
  inner_cpu_cost = guessed_result_cardinality * inner->variable_cpu_cost;

  /* inner side IO cost of nested-loop block join */
  if (qo_is_iscan (inner))
    {
      inner_io_cost = guessed_result_cardinality * inner->variable_io_cost * (1 - ISCAN_IO_HIT_RATIO);
    }
  else
    {
      /* if inner is seq scan, it is calculated by default card. */
      /* This prevents the worst plan if the cardinality is calculated to be less than the actual value. */
      inner_io_cost = (guessed_result_cardinality + SSCAN_DEFAULT_CARD) * inner->variable_io_cost;
    }

  /* outer side CPU cost of nested-loop block join */
  outer_cpu_cost = outer->variable_cpu_cost;
  /* outer side IO cost of nested-loop block join */
  outer_io_cost = outer->variable_io_cost;

  /* CPU and IO costs which are variable according to the join plan */
  planp->variable_cpu_cost = inner_cpu_cost + outer_cpu_cost;
  planp->variable_io_cost = inner_io_cost + outer_io_cost;

  {
    QO_ENV *env;
    int i;
    QO_SUBQUERY *subq;
    PT_NODE *query;
    double temp_cpu_cost, temp_io_cost;
    double subq_cpu_cost, subq_io_cost;
    BITSET_ITERATOR iter;

    /* Compute the costs for all of the subqueries. Each of the pinned subqueries is intended to be evaluated once for
     * each row produced by this plan; the cost of each such evaluation in the fixed cost of the subquery plus one trip
     * through the result, i.e.,
     *
     * QO_PLAN_FIXED_COST(subplan) + QO_PLAN_ACCESS_COST(subplan)
     *
     * The cost info for the subplan has (probably) been squirreled away in a QO_SUMMARY structure reachable from the
     * original select node.
     */

    /* When computing the cost for a WORST_PLAN, we'll get in here without a backing info node; just work around it. */
    env = inner->info ? (inner->info)->env : NULL;
    subq_cpu_cost = subq_io_cost = 0.0; /* init */

    for (i = bitset_iterate (&(inner->subqueries), &iter); i != -1; i = bitset_next_member (&iter))
      {
    subq = env ? &env->subqueries[i] : NULL;
    query = subq ? subq->node : NULL;
    qo_plan_compute_subquery_cost (query, &temp_cpu_cost, &temp_io_cost);
    subq_cpu_cost += temp_cpu_cost;
    subq_io_cost += temp_io_cost;
      }

    /* subq cost is already included in the inner. so add it for the cardinality excluded due to ISCAN_IO_HIT_RATIO. */
    planp->variable_cpu_cost += guessed_result_cardinality * ISCAN_IO_HIT_RATIO * subq_cpu_cost;
    planp->variable_io_cost += guessed_result_cardinality * ISCAN_IO_HIT_RATIO * subq_io_cost;  /* assume IO as # blocks */
  }

#if TEST_DUMP_PLAN_JOIN_COST
  fprintf (stdout, "\nNested Loop Cost: \n");
  fprintf (stdout, "  -    Fixed CPU Cost: %lf\n", planp->fixed_cpu_cost);
  fprintf (stdout, "  -    Fixed I/O Cost: %lf\n", planp->fixed_io_cost);
  fprintf (stdout, "  - Variable CPU Cost: %lf\n", planp->variable_cpu_cost);
  fprintf (stdout, "  - Variable I/O Cost: %lf\n", planp->variable_io_cost);
  fprintf (stdout, "  -    Total     Cost: %lf\n",
       planp->fixed_cpu_cost + planp->fixed_io_cost + planp->variable_cpu_cost + planp->variable_io_cost);
  if (planp->vtbl != NULL)
    {
      // qo_plan_lite_print (planp, stdout, 0);
      qo_plan_fprint (planp, stdout, 0, NULL);
      fprintf (stdout, "\n");
    }
  fprintf (stdout, "\n");
#endif /* TEST_DUMP_PLAN_JOIN_COST */
}

/*
 * qo_mjoin_cost () -
 *   return:
 *   planp(in):
 */
static void
qo_mjoin_cost (QO_PLAN * planp)
{
  QO_PLAN *inner;
  QO_PLAN *outer;
  QO_ENV *env;
  double outer_cardinality = 0.0, inner_cardinality = 0.0;

  inner = planp->plan_un.join.inner;

  /* for worst cost */
  if (inner->fixed_cpu_cost == QO_INFINITY || inner->fixed_io_cost == QO_INFINITY
      || inner->variable_cpu_cost == QO_INFINITY || inner->variable_io_cost == QO_INFINITY)
    {
      qo_worst_cost (planp);
      return;
    }

  outer = planp->plan_un.join.outer;

  /* for worst cost */
  if (outer->fixed_cpu_cost == QO_INFINITY || outer->fixed_io_cost == QO_INFINITY
      || outer->variable_cpu_cost == QO_INFINITY || outer->variable_io_cost == QO_INFINITY)
    {
      qo_worst_cost (planp);
      return;
    }

  env = outer->info->env;
  if (outer->has_sort_limit)
    {
      outer_cardinality = (double) db_get_bigint (&QO_ENV_LIMIT_VALUE (env));
    }
  else
    {
      outer_cardinality = outer->info->cardinality;
    }

  if (inner->has_sort_limit)
    {
      inner_cardinality = (double) db_get_bigint (&QO_ENV_LIMIT_VALUE (env));
    }
  else
    {
      inner_cardinality = inner->info->cardinality;
    }

  /* CPU and IO costs which are fixed against join */
  planp->fixed_cpu_cost = outer->fixed_cpu_cost + inner->fixed_cpu_cost;
  planp->fixed_io_cost = outer->fixed_io_cost + inner->fixed_io_cost;
  /* CPU and IO costs which are variable according to the join plan */
  planp->variable_cpu_cost = outer->variable_cpu_cost + inner->variable_cpu_cost;
  planp->variable_cpu_cost += (outer_cardinality + inner_cardinality) * QO_CPU_WEIGHT * MJ_CPU_OVERHEAD_FACTOR;
  /* merge cost */
  planp->variable_io_cost = outer->variable_io_cost + inner->variable_io_cost;

#if TEST_DUMP_PLAN_JOIN_COST
  fprintf (stdout, "\nSort Merge Cost: \n");
  fprintf (stdout, "  -    Fixed CPU Cost: %lf\n", planp->fixed_cpu_cost);
  fprintf (stdout, "  -    Fixed I/O Cost: %lf\n", planp->fixed_io_cost);
  fprintf (stdout, "  - Variable CPU Cost: %lf\n", planp->variable_cpu_cost);
  fprintf (stdout, "  - Variable I/O Cost: %lf\n", planp->variable_io_cost);
  fprintf (stdout, "  -    Total     Cost: %lf\n",
       planp->fixed_cpu_cost + planp->fixed_io_cost + planp->variable_cpu_cost + planp->variable_io_cost);
  if (planp->vtbl != NULL)
    {
      // qo_plan_lite_print (planp, stdout, 0);
      qo_plan_fprint (planp, stdout, 0, NULL);
      fprintf (stdout, "\n");
    }
  fprintf (stdout, "\n");
#endif /* TEST_DUMP_PLAN_JOIN_COST */
}

/*
 * qo_hjoin_cost () -
 *   return:
 *   planp(in):
 */
static void
qo_hjoin_cost (QO_PLAN * plan_p)
{
  QO_PLAN *inner_plan_p, *outer_plan_p;
  double inner_cardinality, outer_cardinality;
  double inner_build_cpu_cost, outer_build_cpu_cost;
  double inner_build_io_cost, outer_build_io_cost;

  inner_plan_p = plan_p->plan_un.join.inner;

  /* for worst cost */
  if ((inner_plan_p->fixed_cpu_cost == QO_INFINITY) || (inner_plan_p->fixed_io_cost == QO_INFINITY)
      || (inner_plan_p->variable_cpu_cost == QO_INFINITY) || (inner_plan_p->variable_io_cost == QO_INFINITY))
    {
      qo_worst_cost (plan_p);
      return;
    }

  outer_plan_p = plan_p->plan_un.join.outer;

  /* for worst cost */
  if ((outer_plan_p->fixed_cpu_cost == QO_INFINITY) || (outer_plan_p->fixed_io_cost == QO_INFINITY)
      || (outer_plan_p->variable_cpu_cost == QO_INFINITY) || (outer_plan_p->variable_io_cost == QO_INFINITY))
    {
      qo_worst_cost (plan_p);
      return;
    }

  inner_cardinality = inner_plan_p->info->cardinality;
  outer_cardinality = outer_plan_p->info->cardinality;

  plan_p->fixed_cpu_cost = outer_plan_p->fixed_cpu_cost + inner_plan_p->fixed_cpu_cost;
  plan_p->fixed_io_cost = outer_plan_p->fixed_io_cost + inner_plan_p->fixed_io_cost;

  plan_p->variable_cpu_cost = outer_plan_p->variable_cpu_cost + inner_plan_p->variable_cpu_cost;
  plan_p->variable_io_cost = outer_plan_p->variable_io_cost + inner_plan_p->variable_io_cost;

  inner_build_cpu_cost = (inner_cardinality * QO_CPU_WEIGHT * HJ_BUILD_CPU_OVERHEAD_FACTOR);
  inner_build_cpu_cost += (outer_cardinality * QO_CPU_WEIGHT * HJ_PROBE_CPU_OVERHEAD_FACTOR);
  inner_build_io_cost = 0.0;

  outer_build_cpu_cost = (inner_cardinality * QO_CPU_WEIGHT * HJ_PROBE_CPU_OVERHEAD_FACTOR);
  outer_build_cpu_cost += (outer_cardinality * QO_CPU_WEIGHT * HJ_BUILD_CPU_OVERHEAD_FACTOR);
  outer_build_io_cost = 0.0;

#if 0
  /* No need to increase weight since partitioned hash join is used even when mem_limit is exceeded. */

  UINT64 mem_limit = prm_get_bigint_value (PRM_ID_MAX_HASH_LIST_SCAN_SIZE);

  if ((inner_cardinality * (sizeof (HENTRY_HLS) + 16 /* sizeof (QFILE_TUPLE_SIMPLE_POS) */ )) > mem_limit)
    {
      inner_build_io_cost += (inner_cardinality * HJ_FILE_IO_WEIGHT);
      inner_build_io_cost += (outer_cardinality * HJ_FILE_IO_WEIGHT);
    }

  if ((outer_cardinality * (sizeof (HENTRY_HLS) + 16 /* sizeof (QFILE_TUPLE_SIMPLE_POS) */ )) > mem_limit)
    {
      outer_build_io_cost += (inner_cardinality * HJ_FILE_IO_WEIGHT);
      outer_build_io_cost += (outer_cardinality * HJ_FILE_IO_WEIGHT);
    }
#endif

  switch (plan_p->plan_un.join.join_type)
    {
    case JOIN_LEFT:
      plan_p->variable_cpu_cost += inner_build_cpu_cost;
      plan_p->variable_io_cost += inner_build_io_cost;
      break;

    case JOIN_RIGHT:
      plan_p->variable_cpu_cost += outer_build_cpu_cost;
      plan_p->variable_io_cost += outer_build_io_cost;
      break;

    case JOIN_INNER:
      if ((inner_build_cpu_cost + inner_build_io_cost) <= (outer_build_cpu_cost + outer_build_io_cost))
    {
      plan_p->variable_cpu_cost += inner_build_cpu_cost;
      plan_p->variable_io_cost += inner_build_io_cost;
    }
      else
    {
      plan_p->variable_cpu_cost += outer_build_cpu_cost;
      plan_p->variable_io_cost += outer_build_io_cost;
    }
      break;

    default:
      qo_worst_cost (plan_p);
      assert (false);
    }

#if TEST_DUMP_PLAN_JOIN_COST
  fprintf (stdout, "\nHash Join Cost: \n");
  fprintf (stdout, "  -    Fixed CPU Cost: %lf\n", plan_p->fixed_cpu_cost);
  fprintf (stdout, "  -    Fixed I/O Cost: %lf\n", plan_p->fixed_io_cost);
  fprintf (stdout, "  - Variable CPU Cost: %lf\n", plan_p->variable_cpu_cost);
  fprintf (stdout, "  - Variable I/O Cost: %lf\n", plan_p->variable_io_cost);
  fprintf (stdout, "  -    Total     Cost: %lf\n",
       plan_p->fixed_cpu_cost + plan_p->fixed_io_cost + plan_p->variable_cpu_cost + plan_p->variable_io_cost);
  if (plan_p->vtbl != NULL)
    {
      // qo_plan_lite_print (plan_p, stdout, 0);
      qo_plan_fprint (plan_p, stdout, 0, NULL);
      fprintf (stdout, "\n");
    }
  fprintf (stdout, "\n");
#endif /* TEST_DUMP_PLAN_JOIN_COST */
}

/*
 * qo_hjoin_fprint () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_hjoin_fprint (QO_PLAN * plan, FILE * f, int howfar)
{
  switch (plan->plan_un.join.join_type)
    {
    case JOIN_INNER:
      fputs (" (inner join)", f);
      break;

    case JOIN_LEFT:
      fputs (" (left outer join)", f);
      break;

    case JOIN_RIGHT:
      fputs (" (right outer join)", f);
      break;

    case JOIN_OUTER:
      /* Unsupported. */
      assert (false);
      fputs (" (full outer join)", f);
      break;

    case JOIN_CSELECT:
      /* Unsupported. */
      assert (false);
      fputs (" (cselect join)", f);
      break;

    case NO_JOIN:
    default:
      /* Impossible. */
      assert (false);
      fputs (" (unknown join type)", f);
      break;
    }

  if (!bitset_is_empty (&(plan->plan_un.join.join_terms)))
    {
      fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', "edge:");
      qo_termset_fprint ((plan->info)->env, &(plan->plan_un.join.join_terms), f);
    }

  qo_plan_fprint (plan->plan_un.join.outer, f, howfar, "outer: ");
  qo_plan_fprint (plan->plan_un.join.inner, f, howfar, "inner: ");
  qo_plan_print_outer_join_terms (plan, f, howfar);
}

/*
 * qo_follow_new () -
 *   return:
 *   info(in):
 *   head_plan(in):
 *   path_term(in):
 *   sarged_terms(in):
 *   pinned_subqueries(in):
 */
static QO_PLAN *
qo_follow_new (QO_INFO * info, QO_PLAN * head_plan, QO_TERM * path_term, BITSET * sarged_terms,
           BITSET * pinned_subqueries)
{
  QO_PLAN *plan;

  plan = qo_plan_malloc (info->env);
  if (plan == NULL)
    {
      return NULL;
    }

  QO_ASSERT (info->env, head_plan != NULL);

  plan->info = info;
  plan->refcount = 0;
  plan->top_rooted = false;
  plan->well_rooted = head_plan->well_rooted;
  plan->iscan_sort_list = NULL;
  plan->analytic_eval_list = NULL;
  plan->plan_type = QO_PLANTYPE_FOLLOW;
  plan->vtbl = &qo_follow_plan_vtbl;
  plan->order = QO_UNORDERED;

  plan->plan_un.follow.head = qo_plan_add_ref (head_plan);
  plan->plan_un.follow.path = path_term;

  plan->multi_range_opt_use = PLAN_MULTI_RANGE_OPT_NO;

  bitset_assign (&(plan->sarged_terms), sarged_terms);
  bitset_remove (&(plan->sarged_terms), QO_TERM_IDX (path_term));

  bitset_assign (&(plan->subqueries), pinned_subqueries);

  bitset_union (&(plan->sarged_terms), &(QO_NODE_SARGS (QO_TERM_TAIL (path_term))));
  bitset_union (&(plan->subqueries), &(QO_NODE_SUBQUERIES (QO_TERM_TAIL (path_term))));

  qo_plan_compute_cost (plan);

  plan = qo_top_plan_new (plan);

  return plan;
}

/*
 * qo_follow_walk () -
 *   return:
 *   plan(in):
 *   child_fn(in):
 *   child_data(in):
 *   parent_fn(in):
 *   parent_data(in):
 */
static void
qo_follow_walk (QO_PLAN * plan, void (*child_fn) (QO_PLAN *, void *), void *child_data,
        void (*parent_fn) (QO_PLAN *, void *), void *parent_data)
{
  if (child_fn)
    {
      (*child_fn) (plan->plan_un.follow.head, child_data);
    }
  if (parent_fn)
    {
      (*parent_fn) (plan, parent_data);
    }
}

/*
 * qo_follow_fprint () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_follow_fprint (QO_PLAN * plan, FILE * f, int howfar)
{
  fprintf (f, "\n" INDENTED_TITLE_FMT, (int) howfar, ' ', "edge:");
  qo_term_fprint (plan->plan_un.follow.path, f);
  qo_plan_fprint (plan->plan_un.follow.head, f, howfar, "head: ");
}

/*
 * qo_follow_info () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_follow_info (QO_PLAN * plan, FILE * f, int howfar)
{
  char buf[257] = { '\0', };
  fprintf (f, "\n%*c%s(%s)", (int) howfar, ' ', (plan->vtbl)->info_string,
       qo_term_string (plan->plan_un.follow.path, buf));
  qo_plan_lite_print (plan->plan_un.follow.head, f, howfar + INDENT_INCR);
}

/*
 * qo_follow_cost () -
 *   return:
 *   planp(in):
 */
static void
qo_follow_cost (QO_PLAN * planp)
{
  QO_PLAN *head;
  QO_NODE *tail;
  double cardinality, target_pages, fetch_ios;

  head = planp->plan_un.follow.head;

  /* for worst cost */
  if (head->fixed_cpu_cost == QO_INFINITY || head->fixed_io_cost == QO_INFINITY
      || head->variable_cpu_cost == QO_INFINITY || head->variable_io_cost == QO_INFINITY)
    {
      qo_worst_cost (planp);
      return;
    }

  cardinality = (planp->info)->cardinality;
  tail = QO_TERM_TAIL (planp->plan_un.follow.path);
  target_pages = (double) QO_NODE_TCARD (tail);

  if (cardinality < target_pages)
    {
      /* If we expect to fetch fewer objects than there are pages in the target class, just assume that each fetch will
       * touch a new page.
       */
      fetch_ios = cardinality;
    }
  else if (prm_get_integer_value (PRM_ID_PB_NBUFFERS) >= target_pages)
    {
      /* We have more pointers to follow than pages in the target, but fewer target pages than buffer pages.  Assume
       * that the page buffering will limit the number of of page fetches to the number of target pages.
       */
      fetch_ios = target_pages;
    }
  else
    {
      fetch_ios = cardinality * (1.0 - ((double) prm_get_integer_value (PRM_ID_PB_NBUFFERS)) / target_pages);
    }

  planp->fixed_cpu_cost = head->fixed_cpu_cost;
  planp->fixed_io_cost = head->fixed_io_cost;
  planp->variable_cpu_cost = head->variable_cpu_cost + (cardinality * (double) QO_CPU_WEIGHT);
  planp->variable_io_cost = head->variable_io_cost + fetch_ios;

#if TEST_DUMP_PLAN_FOLLOW_COST
  fprintf (stdout, "\nFollow Cost: \n");
  fprintf (stdout, "  -    Fixed CPU Cost: %lf\n", planp->fixed_cpu_cost);
  fprintf (stdout, "  -    Fixed I/O Cost: %lf\n", planp->fixed_io_cost);
  fprintf (stdout, "  - Variable CPU Cost: %lf\n", planp->variable_cpu_cost);
  fprintf (stdout, "  - Variable I/O Cost: %lf\n", planp->variable_io_cost);
  fprintf (stdout, "  -    Total     Cost: %lf\n",
       planp->fixed_cpu_cost + planp->fixed_io_cost + planp->variable_cpu_cost + planp->variable_io_cost);
  if (planp->vtbl != NULL)
    {
      qo_plan_lite_print (planp, stdout, 0);
      fprintf (stdout, "\n");
    }
  fprintf (stdout, "\n");
#endif /* TEST_DUMP_PLAN_FOLLOW_COST */
}


/*
 * qo_cp_new () -
 *   return:
 *   info(in):
 *   outer(in):
 *   inner(in):
 *   sarged_terms(in):
 *   pinned_subqueries(in):
 */
static QO_PLAN *
qo_cp_new (QO_INFO * info, QO_PLAN * outer, QO_PLAN * inner, BITSET * sarged_terms, BITSET * pinned_subqueries)
{
  QO_PLAN *plan;
  BITSET empty_terms;

  bitset_init (&empty_terms, info->env);

  plan = qo_join_new (info, JOIN_INNER /* default */ ,
              QO_JOINMETHOD_NL_JOIN, outer, inner, &empty_terms /* join_terms */ ,
              &empty_terms /* duj_terms */ ,
              &empty_terms /* afj_terms */ ,
              sarged_terms, pinned_subqueries, &empty_terms /* hash_terms */ );

  bitset_delset (&empty_terms);

  return plan;
}


/*
 * qo_worst_new () -
 *   return:
 *   env(in):
 */
static QO_PLAN *
qo_worst_new (QO_ENV * env)
{
  QO_PLAN *plan;

  plan = qo_plan_malloc (env);
  if (plan == NULL)
    {
      return NULL;
    }

  plan->info = NULL;
  plan->refcount = 0;
  plan->top_rooted = true;
  plan->well_rooted = false;
  plan->iscan_sort_list = NULL;
  plan->analytic_eval_list = NULL;
  plan->order = QO_UNORDERED;
  plan->plan_type = QO_PLANTYPE_WORST;
  plan->vtbl = &qo_worst_plan_vtbl;

  plan->multi_range_opt_use = PLAN_MULTI_RANGE_OPT_NO;

  qo_plan_compute_cost (plan);

  return plan;
}

/*
 * qo_worst_fprint () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_worst_fprint (QO_PLAN * plan, FILE * f, int howfar)
{
}

/*
 * qo_worst_info () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
static void
qo_worst_info (QO_PLAN * plan, FILE * f, int howfar)
{
  fprintf (f, "\n%*c%s", (int) howfar, ' ', (plan->vtbl)->info_string);
}

/*
 * qo_worst_cost () -
 *   return:
 *   planp(in):
 */
static void
qo_worst_cost (QO_PLAN * planp)
{
  planp->fixed_cpu_cost = QO_INFINITY;
  planp->fixed_io_cost = QO_INFINITY;
  planp->variable_cpu_cost = QO_INFINITY;
  planp->variable_io_cost = QO_INFINITY;
  planp->use_iscan_descending = false;
}


/*
 * qo_zero_cost () -
 *   return:
 *   planp(in):
 */
static void
qo_zero_cost (QO_PLAN * planp)
{
  planp->fixed_cpu_cost = 0.0;
  planp->fixed_io_cost = 0.0;
  planp->variable_cpu_cost = 0.0;
  planp->variable_io_cost = 0.0;
}


/*
 * qo_plan_order_by () -
 *   return:
 *   plan(in):
 *   order(in):
 */
static QO_PLAN *
qo_plan_order_by (QO_PLAN * plan, QO_EQCLASS * order)
{
  if (plan == NULL || order == QO_UNORDERED || plan->order == order)
    {
      return plan;
    }
  else if (BITSET_MEMBER ((plan->info)->eqclasses, QO_EQCLASS_IDX (order)))
    {
      return qo_sort_new (plan, order, SORT_TEMP);
    }
  else
    {
      return (QO_PLAN *) NULL;
    }
}

/*
 * qo_plan_cmp_prefer_covering_index () - TODO
 *   return: one of {PLAN_COMP_UNK, PLAN_COMP_LT, PLAN_COMP_GT}
 *   scan_plan_p(in):
 *   sort_plan_p(in):
 */
static QO_PLAN_COMPARE_RESULT
qo_plan_cmp_prefer_covering_index (QO_PLAN * scan_plan_p, QO_PLAN * sort_plan_p)
{
  QO_PLAN *sort_subplan_p;

  assert (scan_plan_p->plan_type == QO_PLANTYPE_SCAN);
  assert (sort_plan_p->plan_type == QO_PLANTYPE_SORT);

  sort_subplan_p = sort_plan_p->plan_un.sort.subplan;

  if (!qo_is_interesting_order_scan (scan_plan_p) || !qo_is_interesting_order_scan (sort_subplan_p))
    {
      return PLAN_COMP_UNK;
    }

  if (qo_is_index_iss_scan (scan_plan_p) || qo_is_index_loose_scan (scan_plan_p))
    {
      return PLAN_COMP_UNK;
    }

  if (qo_is_index_iss_scan (sort_subplan_p) || qo_is_index_loose_scan (sort_subplan_p))
    {
      return PLAN_COMP_UNK;
    }

  if (qo_is_index_covering_scan (sort_subplan_p))
    {
      /* if the sort plan contains a index plan with segment covering, prefer it */
      if (qo_is_index_covering_scan (scan_plan_p))
    {
      if (scan_plan_p->plan_un.scan.index->head == sort_subplan_p->plan_un.scan.index->head)
        {
          return PLAN_COMP_LT;
        }
    }
      else
    {
      if (!bitset_is_empty (&(sort_subplan_p->plan_un.scan.terms)))
        {
          /* prefer covering index scan with key-range */
          return PLAN_COMP_GT;
        }
    }
    }

  return PLAN_COMP_UNK;
}

/*
 * qo_plan_cmp () -
 *   return: one of {PLAN_COMP_UNK, PLAN_COMP_LT, PLAN_COMP_EQ, PLAN_COMP_GT}
 *   a(in):
 *   b(in):
 */
static QO_PLAN_COMPARE_RESULT
qo_plan_cmp (QO_PLAN * a, QO_PLAN * b)
{
#if 1               /* TODO - do not delete me */
#define QO_PLAN_CMP_CHECK_COST(a, b)
#else
#define QO_PLAN_CMP_CHECK_COST(a, b) assert ((a) < ((b)*10));
#endif

#ifdef OLD_CODE
  if (QO_PLAN_FIXED_COST (a) <= QO_PLAN_FIXED_COST (b))
    {
      return QO_PLAN_ACCESS_COST (a) <= QO_PLAN_ACCESS_COST (b) ? a : b;
    }
  else
    {
      return QO_PLAN_ACCESS_COST (b) <= QO_PLAN_ACCESS_COST (a) ? b : a;
    }
#else /* OLD_CODE */
  double af, aa, bf, ba, ta, tb;
  QO_NODE *a_node, *b_node;
  QO_PLAN_COMPARE_RESULT temp_res;

  af = a->fixed_cpu_cost + a->fixed_io_cost;
  aa = a->variable_cpu_cost + a->variable_io_cost;
  bf = b->fixed_cpu_cost + b->fixed_io_cost;
  ba = b->variable_cpu_cost + b->variable_io_cost;

  /* Check if RBO is needed */
  ta = af + aa;
  tb = bf + ba;
  if (ta > 0 && tb > 0 && QO_PLAN_HAS_LIMIT (a) && QO_PLAN_HAS_LIMIT (b))
    {
      if (ta * RBO_CHECK_LIMIT_RATIO <= tb)
    {
      return PLAN_COMP_LT;
    }
      else if (ta > tb * RBO_CHECK_LIMIT_RATIO)
    {
      return PLAN_COMP_GT;
    }
    }
  else
    {
      if ((ta + RBO_CHECK_COST <= tb) && (ta * RBO_CHECK_RATIO <= tb))
    {
      return PLAN_COMP_LT;
    }
      else if ((ta > tb + RBO_CHECK_COST) && (ta > tb * RBO_CHECK_RATIO))
    {
      return PLAN_COMP_GT;
    }
    }

  if (qo_is_sort_limit (a))
    {
      if (qo_is_sort_limit (b))
    {
      /* compare subplans */
      a = a->plan_un.sort.subplan;
      b = b->plan_un.sort.subplan;
    }
      else if (a->plan_un.sort.subplan == b)
    {
      /* a is a SORT-LIMIT plan over b */
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }
    }
  else if (qo_is_sort_limit (b) && a == b->plan_un.sort.subplan)
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }

  /* skip out top-level sort plan */
  if (a->top_rooted && b->top_rooted)
    {
      /* skip out the same sort plan */
      while (a->plan_type == QO_PLANTYPE_SORT && b->plan_type == QO_PLANTYPE_SORT
         && a->plan_un.sort.sort_type == b->plan_un.sort.sort_type)
    {
      a = a->plan_un.sort.subplan;
      b = b->plan_un.sort.subplan;
    }
    }
  else
    {
      if (a->top_rooted)
    {
      while (a->plan_type == QO_PLANTYPE_SORT)
        {
          if (a->plan_un.sort.sort_type == SORT_TEMP)
        {
          break;    /* is not top-level plan */
        }
          a = a->plan_un.sort.subplan;
        }
    }
      if (b->top_rooted)
    {
      while (b->plan_type == QO_PLANTYPE_SORT)
        {
          if (b->plan_un.sort.sort_type == SORT_TEMP)
        {
          break;    /* is top-level plan */
        }
          b = b->plan_un.sort.subplan;
        }
    }
    }

  if (a == b)
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_EQ;
    }

  /* check for superset of index */
  if (a->plan_type == QO_PLANTYPE_JOIN && QO_IS_NL_JOIN (a) && qo_is_iscan (a->plan_un.join.inner) &&
      b->plan_type == QO_PLANTYPE_JOIN && QO_IS_NL_JOIN (b) && qo_is_iscan (b->plan_un.join.inner))
    {
      temp_res = qo_plan_iscan_terms_cmp (a->plan_un.join.inner, b->plan_un.join.inner);
      if (temp_res == PLAN_COMP_LT)
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }
      else if (temp_res == PLAN_COMP_GT)
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }
    }

  if ((a->plan_type != QO_PLANTYPE_SCAN && a->plan_type != QO_PLANTYPE_SORT)
      || (b->plan_type != QO_PLANTYPE_SCAN && b->plan_type != QO_PLANTYPE_SORT))
    {
      /* there may be joins with multi range optimizations */
      temp_res = qo_multi_range_opt_plans_cmp (a, b);
      if (temp_res == PLAN_COMP_LT)
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }
      else if (temp_res == PLAN_COMP_GT)
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }
    }

  /* a order by skip plan is always preferred to a sort plan */
  if (a->plan_type == QO_PLANTYPE_SCAN && b->plan_type == QO_PLANTYPE_SORT)
    {
      /* prefer scan if it is multi range opt */
      if (qo_is_index_mro_scan (a))
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }

      temp_res = qo_plan_cmp_prefer_covering_index (a, b);
      if (temp_res == PLAN_COMP_LT)
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }
      else if (temp_res == PLAN_COMP_GT)
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }

      if (a->plan_un.scan.index && a->plan_un.scan.index->head->groupby_skip)
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }
      if (a->plan_un.scan.index && a->plan_un.scan.index->head->orderby_skip)
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }

      if (qo_plan_is_orderby_skip_candidate (b->plan_un.sort.subplan))
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }
    }

  if (b->plan_type == QO_PLANTYPE_SCAN && a->plan_type == QO_PLANTYPE_SORT)
    {
      /* prefer scan if it is multi range opt */
      if (qo_is_index_mro_scan (b))
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }

      temp_res = qo_plan_cmp_prefer_covering_index (b, a);

      /* Since we swapped its position, we have to negate the comp result */
      if (temp_res == PLAN_COMP_LT)
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }
      else if (temp_res == PLAN_COMP_GT)
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }

      if (!qo_is_index_iss_scan (b) && !qo_is_index_loose_scan (b))
    {
      if (b->plan_un.scan.index && b->plan_un.scan.index->head->groupby_skip)
        {
          QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
          return PLAN_COMP_GT;
        }
      if (b->plan_un.scan.index && b->plan_un.scan.index->head->orderby_skip)
        {
          QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
          return PLAN_COMP_GT;
        }
    }

      if (qo_plan_is_orderby_skip_candidate (a->plan_un.sort.subplan))
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }
    }

  if (a->plan_type == QO_PLANTYPE_SCAN && b->plan_type == QO_PLANTYPE_SCAN)
    {
      /* check multi range optimization */
      if (qo_is_index_mro_scan (a) && !qo_is_index_mro_scan (b))
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }
      if (!qo_is_index_mro_scan (a) && qo_is_index_mro_scan (b))
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }

      /* check covering index scan */
      if (qo_is_index_covering_scan (a) && qo_is_seq_scan (b))
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }
      if (qo_is_index_covering_scan (b) && qo_is_seq_scan (a))
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }

      /* check index scan */
      if ((qo_is_iscan (a) || qo_is_iscan_from_orderby (a)) && qo_is_seq_scan (b))
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }
      if ((qo_is_iscan (b) || qo_is_iscan_from_orderby (b)) && qo_is_seq_scan (a))
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }

      /* a plan does order by skip, the other does group by skip - prefer the group by skipping because it's done in
       * the final step
       */
      if (qo_is_interesting_order_scan (a) && qo_is_interesting_order_scan (b))
    {
      if (!qo_is_index_iss_scan (a) && !qo_is_index_loose_scan (a) && !qo_is_index_iss_scan (b)
          && !qo_is_index_loose_scan (b))
        {
          if (a->plan_un.scan.index->head->orderby_skip && b->plan_un.scan.index->head->groupby_skip)
        {
          QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
          return PLAN_COMP_LT;
        }
          else if (a->plan_un.scan.index->head->groupby_skip && b->plan_un.scan.index->head->orderby_skip)
        {
          QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
          return PLAN_COMP_GT;
        }
        }
    }
    }

  /* prefer order by skip plan over sort plan */
  if (a->plan_type == QO_PLANTYPE_JOIN && b->plan_type == QO_PLANTYPE_SORT)
    {
      if (qo_plan_is_orderby_skip_candidate (a->plan_un.join.outer))
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }
    }
  else if (b->plan_type == QO_PLANTYPE_JOIN && a->plan_type == QO_PLANTYPE_SORT)
    {
      if (qo_plan_is_orderby_skip_candidate (b->plan_un.join.outer))
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }
    }

  if (a->plan_type != QO_PLANTYPE_SCAN || b->plan_type != QO_PLANTYPE_SCAN)
    {               /* impossible case */
      goto cost_cmp;        /* give up */
    }

  a_node = a->plan_un.scan.node;
  b_node = b->plan_un.scan.node;

  /* check for empty spec */
  if (QO_NODE_NCARD (a_node) == 0 && QO_NODE_TCARD (a_node) == 0)
    {
      if (QO_NODE_NCARD (b_node) == 0 && QO_NODE_TCARD (b_node) == 0)
    {
      goto cost_cmp;    /* give up */
    }

      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }
  else if (QO_NODE_NCARD (b_node) == 0 && QO_NODE_TCARD (b_node) == 0)
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }

  if (QO_NODE_IDX (a_node) != QO_NODE_IDX (b_node))
    {
      goto cost_cmp;        /* give up */
    }

  /* check for both index scan of the same spec */
  if (!qo_is_interesting_order_scan (a) || !qo_is_interesting_order_scan (b))
    {
      goto cost_cmp;        /* give up */
    }

  /* check multi range optimization */
  temp_res = qo_multi_range_opt_plans_cmp (a, b);
  if (temp_res == PLAN_COMP_LT)
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }
  else if (temp_res == PLAN_COMP_GT)
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }

  /* check index coverage */
  temp_res = qo_index_covering_plans_cmp (a, b);
  if (temp_res == PLAN_COMP_LT)
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }
  else if (temp_res == PLAN_COMP_GT)
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }

  /* check if one of the plans skips the order by, and if so, prefer it */
  temp_res = qo_order_by_skip_plans_cmp (a, b);
  if (temp_res == PLAN_COMP_LT)
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }
  else if (temp_res == PLAN_COMP_GT)
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }

  /* check if one of the plans skips the group by, and if so, prefer it */
  temp_res = qo_group_by_skip_plans_cmp (a, b);
  if (temp_res == PLAN_COMP_LT)
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }
  else if (temp_res == PLAN_COMP_GT)
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }

  if (QO_PLAN_HAS_LIMIT (a) && QO_PLAN_HAS_LIMIT (b))
    {
      if ((ta + RBO_CHECK_COST <= tb) && (ta * RBO_CHECK_RATIO <= tb))
    {
      return PLAN_COMP_LT;
    }
      else if ((ta > tb + RBO_CHECK_COST) && (ta > tb * RBO_CHECK_RATIO))
    {
      return PLAN_COMP_GT;
    }
    }

  /* iscan vs iscan index rule comparison */

  {
    QO_NODE_INDEX_ENTRY *a_ni, *b_ni;
    QO_INDEX_ENTRY *a_ent, *b_ent;
    QO_ATTR_CUM_STATS *a_cum, *b_cum;
    int a_range, b_range;   /* num iscan range terms */
    int a_filter, b_filter; /* num iscan filter terms */
    int a_last, b_last;     /* the last partial-key indicator */
    int a_keys, b_keys;     /* num keys */
    int a_pages, b_pages;   /* num access index pages */
    int a_leafs, b_leafs;   /* num access index leaf pages */
    int i;
    QO_TERM *term;

    /* index entry of spec 'a' */
    a_ni = a->plan_un.scan.index;
    a_ent = (a_ni)->head;
    a_cum = &(a_ni)->cum_stats;

    assert (a_cum->pkeys_size <= BTREE_STATS_PKEYS_NUM);
    for (i = 0; i < a_cum->pkeys_size; i++)
      {
    if (a_cum->pkeys[i] <= 0)
      {
        break;
      }
      }
    a_last = i;

    /* index range terms */
    a_range = bitset_cardinality (&(a->plan_un.scan.terms));
    if (a_range > 0 && !(a->plan_un.scan.index_equi))
      {
    a_range--;      /* set the last equal range term */
      }

    /* index filter terms */
    a_filter = bitset_cardinality (&(a->plan_un.scan.kf_terms));

    /* index entry of spec 'b' */
    b_ni = b->plan_un.scan.index;
    b_ent = (b_ni)->head;
    b_cum = &(b_ni)->cum_stats;

    assert (b_cum->pkeys_size <= BTREE_STATS_PKEYS_NUM);
    for (i = 0; i < b_cum->pkeys_size; i++)
      {
    if (b_cum->pkeys[i] <= 0)
      {
        break;
      }
      }
    b_last = i;

    /* index range terms */
    b_range = bitset_cardinality (&(b->plan_un.scan.terms));
    if (b_range > 0 && !(b->plan_un.scan.index_equi))
      {
    b_range--;      /* set the last equal range term */
      }

    /* index filter terms */
    b_filter = bitset_cardinality (&(b->plan_un.scan.kf_terms));

    /* STEP 1: take the smaller search condition */

    /* check for same index pointer */
    if (a_ent == b_ent)
      {
    /* check for search condition */
    if (a_range == b_range && a_filter == b_filter)
      {
        ;           /* go ahead */
      }
    else if (a_range >= b_range && a_filter >= b_filter)
      {
        QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
        return PLAN_COMP_LT;
      }
    else if (a_range <= b_range && a_filter <= b_filter)
      {
        QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
        return PLAN_COMP_GT;
      }
      }

    /* STEP 2: check by index terms */

    temp_res = qo_plan_iscan_terms_cmp (a, b);
    if (temp_res == PLAN_COMP_LT)
      {
    QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
    return PLAN_COMP_LT;
      }
    else if (temp_res == PLAN_COMP_GT)
      {
    QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
    return PLAN_COMP_GT;
      }

    /* STEP 3: take the smaller access pages */

    if (a->variable_io_cost != b->variable_io_cost)
      {
    goto cost_cmp;      /* give up */
      }

    /* btree partial-key stats */
    if (a_range == a_ent->col_num)
      {
    a_keys = a_cum->keys;
      }
    else if (a_range > 0 && a_range < a_last)
      {
    if (qo_is_index_iss_scan (a))
      {
        a_keys = a_cum->pkeys[a_range];
      }
    else
      {
        a_keys = a_cum->pkeys[a_range - 1];
      }
      }
    else
      {             /* a_range == 0 */
    a_keys = 1;     /* init as full range */
    if (a_last > 0)
      {
        if (bitset_cardinality (&(a->plan_un.scan.terms)) > 0)
          {
        term = QO_ENV_TERM ((a->info)->env, bitset_first_member (&(a->plan_un.scan.terms)));
        a_keys = (int) ceil (1.0 / QO_TERM_SELECTIVITY (term));
          }
        else
          {
        a_keys = (int) ceil (1.0 / DEFAULT_SELECTIVITY);
          }

        a_keys = MIN (a_cum->pkeys[0], a_keys);
      }
      }

    if (a_cum->leafs <= a_keys)
      {
    a_leafs = 1;
      }
    else if (a_keys == 0)
      {
    a_leafs = 0;
      }
    else
      {
    a_leafs = (int) ceil ((double) a_cum->leafs / a_keys);
      }

    /* btree access pages */
    a_pages = a_leafs + a_cum->height - 1;

    /* btree partial-key stats  */
    if (b_range == b_ent->col_num)
      {
    b_keys = b_cum->keys;
      }
    else if (b_range > 0 && b_range < b_last)
      {
    if (qo_is_index_iss_scan (b))
      {
        b_keys = b_cum->pkeys[b_range];
      }
    else
      {
        b_keys = b_cum->pkeys[b_range - 1];
      }
      }
    else
      {             /* b_range == 0 */
    b_keys = 1;     /* init as full range */
    if (b_last > 0)
      {
        if (bitset_cardinality (&(b->plan_un.scan.terms)) > 0)
          {
        term = QO_ENV_TERM ((b->info)->env, bitset_first_member (&(b->plan_un.scan.terms)));
        b_keys = (int) ceil (1.0 / QO_TERM_SELECTIVITY (term));
          }
        else
          {
        b_keys = (int) ceil (1.0 / DEFAULT_SELECTIVITY);
          }

        b_keys = MIN (b_cum->pkeys[0], b_keys);
      }
      }

    if (b_cum->leafs <= b_keys)
      {
    b_leafs = 1;
      }
    else if (b_keys == 0)
      {
    b_leafs = 0;
      }
    else
      {
    b_leafs = (int) ceil ((double) b_cum->leafs / b_keys);
      }

    /* btree access pages */
    b_pages = b_leafs + b_cum->height - 1;

    if (a_pages < b_pages)
      {
    QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
    return PLAN_COMP_LT;
      }
    else if (a_pages > b_pages)
      {
    QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
    return PLAN_COMP_GT;
      }

    /* STEP 4: take the smaller index */
    if (a_cum->pages > a_cum->height && b_cum->pages > b_cum->height)
      {
    /* each index is big enough */
    if (a_cum->pages < b_cum->pages)
      {
        QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
        return PLAN_COMP_LT;
      }
    else if (a_cum->pages > b_cum->pages)
      {
        QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
        return PLAN_COMP_GT;
      }
      }

    /* STEP 5: take the smaller key range */
    if (a_keys > b_keys)
      {
    QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
    return PLAN_COMP_LT;
      }
    else if (a_keys < b_keys)
      {
    QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
    return PLAN_COMP_GT;
      }

    if (af == bf && aa == ba)
      {
    if (a->plan_un.scan.index_equi == b->plan_un.scan.index_equi && qo_is_index_covering_scan (a)
        && qo_is_index_covering_scan (b))
      {
        if (a_ent->col_num > b_ent->col_num)
          {
        QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
        return PLAN_COMP_GT;
          }
        else if (a_ent->col_num < b_ent->col_num)
          {
        QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
        return PLAN_COMP_LT;
          }
      }

    if (qo_is_index_mro_scan (a) && qo_is_index_mro_scan (b))
      {
        if (a_ent->col_num > b_ent->col_num)
          {
        QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
        return PLAN_COMP_GT;
          }
        else if (a_ent->col_num < b_ent->col_num)
          {
        QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
        return PLAN_COMP_LT;
          }
      }

    /* if both plans skip order by and same costs, take the larger one */
    if (!qo_is_index_iss_scan (a) && !qo_is_index_loose_scan (a) && !qo_is_index_iss_scan (b)
        && !qo_is_index_loose_scan (b))
      {
        if (a_ent->orderby_skip && b_ent->orderby_skip)
          {
        if (a_ent->col_num > b_ent->col_num)
          {
            QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
            return PLAN_COMP_LT;
          }
        else if (a_ent->col_num < b_ent->col_num)
          {
            /* if the new plan has more columns, prefer it */
            QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
            return PLAN_COMP_GT;
          }
          }
      }

    /* if both plans skip group by and same costs, take the larger one */
    if (a_ent->groupby_skip && b_ent->groupby_skip)
      {
        if (a_ent->col_num > b_ent->col_num)
          {
        QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
        return PLAN_COMP_LT;
          }
        else if (a_ent->col_num < b_ent->col_num)
          {
        /* if the new plan has more columns, prefer it */
        QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
        return PLAN_COMP_GT;
          }
      }
      }
  }

cost_cmp:

  if (a == b || (af == bf && aa == ba))
    {
      return PLAN_COMP_EQ;
    }
  if (af + aa <= bf + ba)
    {
      QO_PLAN_CMP_CHECK_COST (af + aa, bf + ba);
      return PLAN_COMP_LT;
    }
  else
    {
      QO_PLAN_CMP_CHECK_COST (bf + ba, af + aa);
      return PLAN_COMP_GT;
    }

#endif /* OLD_CODE */
}

/*
 * qo_multi_range_opt_plans_cmp () - compare two plans in regard with multi
 *                   range optimizations
 *
 * return : compare result
 * a (in) : first plan
 * b (in) : second plan
 */
static QO_PLAN_COMPARE_RESULT
qo_multi_range_opt_plans_cmp (QO_PLAN * a, QO_PLAN * b)
{
  QO_PLAN_COMPARE_RESULT temp_res;

  /* if no plan uses multi range optimization, nothing to do here */
  if (!qo_plan_multi_range_opt (a) && !qo_plan_multi_range_opt (b))
    {
      return PLAN_COMP_UNK;
    }

  /* check if only one plan uses multi range optimization */
  if (qo_plan_multi_range_opt (a) && !qo_plan_multi_range_opt (b))
    {
      return PLAN_COMP_LT;
    }
  if (!qo_plan_multi_range_opt (a) && qo_plan_multi_range_opt (b))
    {
      return PLAN_COMP_GT;
    }

  /* at here, both plans use multi range optimization */

  if (a->plan_type == QO_PLANTYPE_JOIN && b->plan_type == QO_PLANTYPE_JOIN)
    {
      /* choose the plan where the optimized index scan is "outer-most" */
      int a_mro_join_idx = -1, b_mro_join_idx = -1;

      if (qo_find_subplan_using_multi_range_opt (a, NULL, &a_mro_join_idx) != NO_ERROR
      || qo_find_subplan_using_multi_range_opt (b, NULL, &b_mro_join_idx) != NO_ERROR)
    {
      assert (0);
      return PLAN_COMP_UNK;
    }
      if (a_mro_join_idx < b_mro_join_idx)
    {
      return PLAN_COMP_LT;
    }
      else if (a_mro_join_idx > b_mro_join_idx)
    {
      return PLAN_COMP_GT;
    }
      else
    {
      return PLAN_COMP_EQ;
    }
    }

  if (a->plan_type == QO_PLANTYPE_JOIN || b->plan_type == QO_PLANTYPE_JOIN)
    {
      /* one plan is join, the other is not join */
      return PLAN_COMP_UNK;
    }

  /* both plans must be optimized index scans */
  assert (qo_is_index_mro_scan (a));
  assert (qo_is_index_mro_scan (b));

  assert (bitset_cardinality (&(a->plan_un.scan.terms)) > 0);
  assert (bitset_cardinality (&(b->plan_un.scan.terms)) > 0);

  /* choose the plan that also covers all segments */
  temp_res = qo_index_covering_plans_cmp (a, b);
  if (temp_res == PLAN_COMP_LT || temp_res == PLAN_COMP_GT)
    {
      return temp_res;
    }

  return qo_plan_iscan_terms_cmp (a, b);
}

/*
 * qo_index_covering_plans_cmp () - compare 2 index scan plans by coverage
 *   return:  one of {PLAN_COMP_UNK, PLAN_COMP_LT, PLAN_COMP_EQ, PLAN_COMP_GT}
 *   a(in):
 *   b(in):
 */
static QO_PLAN_COMPARE_RESULT
qo_index_covering_plans_cmp (QO_PLAN * a, QO_PLAN * b)
{
  int a_range, b_range;     /* num iscan range terms */

  if (!qo_is_interesting_order_scan (a) || !qo_is_interesting_order_scan (b))
    {
      return PLAN_COMP_UNK;
    }

  assert (a->plan_un.scan.index->head != NULL);
  assert (b->plan_un.scan.index->head != NULL);

  if (qo_is_index_iss_scan (a) || qo_is_index_loose_scan (a))
    {
      return PLAN_COMP_UNK;
    }

  if (qo_is_index_iss_scan (b) || qo_is_index_loose_scan (b))
    {
      return PLAN_COMP_UNK;
    }

  a_range = bitset_cardinality (&(a->plan_un.scan.terms));
  b_range = bitset_cardinality (&(b->plan_un.scan.terms));

  assert (a_range >= 0);
  assert (b_range >= 0);

  if (qo_is_index_covering_scan (a))
    {
      if (qo_is_index_covering_scan (b))
    {
      return qo_plan_iscan_terms_cmp (a, b);
    }
      else
    {
      if (a_range >= b_range)
        {
          /* prefer covering index scan with key-range */
          return PLAN_COMP_LT;
        }
    }
    }
  else if (qo_is_index_covering_scan (b))
    {
      if (b_range >= a_range)
    {
      /* prefer covering index scan with key-range */
      return PLAN_COMP_GT;
    }
    }

  return PLAN_COMP_EQ;
}

/*
 * qo_plan_fprint () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 *   title(in):
 */
static void
qo_plan_fprint (QO_PLAN * plan, FILE * f, int howfar, const char *title)
{
  if (howfar < 0)
    {
      howfar = -howfar;
    }
  else
    {
      fputs ("\n", f);
      if (howfar)
    {
      fprintf (f, INDENT_FMT, (int) howfar, ' ');
    }
    }

  if (title)
    {
      fprintf (f, TITLE_FMT, title);
    }

  fputs ((plan->vtbl)->plan_string, f);

  {
    int title_len;

    title_len = title ? (int) strlen (title) : 0;
    howfar += (title_len + INDENT_INCR);
  }

  (*((plan->vtbl)->fprint_fn)) (plan, f, howfar);

  qo_plan_print_projected_segs (plan, f, howfar);
  qo_plan_print_sarged_terms (plan, f, howfar);
  qo_plan_print_subqueries (plan, f, howfar);
  qo_plan_print_sort_spec (plan, f, howfar);
  qo_plan_print_costs (plan, f, howfar);
  qo_plan_print_analytic_eval (plan, f, howfar);
}

/*
 * qo_plan_lite_print () -
 *   return:
 *   plan(in):
 *   f(in):
 *   howfar(in):
 */
void
qo_plan_lite_print (QO_PLAN * plan, FILE * f, int howfar)
{
  (*((plan->vtbl)->info_fn)) (plan, f, howfar);
}


/*
 * qo_plan_finalize () -
 *   return:
 *   plan(in):
 */
static QO_PLAN *
qo_plan_finalize (QO_PLAN * plan)
{
  return qo_plan_add_ref (plan);
}


/*
 * qo_plan_discard () -
 *   return:
 *   plan(in):
 */
void
qo_plan_discard (QO_PLAN * plan)
{
  if (plan)
    {
      QO_ENV *env;
      bool dump_enable;

      /*
       * Be sure to capture dump_enable *before* we free the env
       * structure!
       */
      env = (plan->info)->env;
      dump_enable = env->dump_enable;

      qo_plan_del_ref (plan);
      qo_env_free (env);

      if (dump_enable)
    {
      qo_print_stats (stdout);
    }
    }
}


/*
 * qo_plan_walk () -
 *   return:
 *   plan(in):
 *   child_fn(in):
 *   child_data(in):
 *   parent_fn(in):
 *   parent_data(in):
 */
static void
qo_plan_walk (QO_PLAN * plan, void (*child_fn) (QO_PLAN *, void *), void *child_data,
          void (*parent_fn) (QO_PLAN *, void *), void *parent_data)
{
  (*(plan->vtbl)->walk_fn) (plan, child_fn, child_data, parent_fn, parent_data);
}

/*
 * qo_plan_del_ref_func () -
 *   return:
 *   plan(in):
 */
static void
qo_plan_del_ref_func (QO_PLAN * plan, void *ignore)
{
  qo_plan_del_ref (plan);   /* use the macro */
}

/*
 * qo_plan_add_to_free_list () -
 *   return:
 *   plan(in):
 */
static void
qo_plan_add_to_free_list (QO_PLAN * plan, void *ignore)
{
  bitset_delset (&(plan->sarged_terms));
  bitset_delset (&(plan->subqueries));
  if (plan->iscan_sort_list)
    {
      parser_free_tree (QO_ENV_PARSER ((plan->info)->env), plan->iscan_sort_list);
    }

  if (qo_accumulating_plans)
    {
      memset ((void *) plan, 0, sizeof (*plan));
      plan->plan_un.free.link = qo_plan_free_list;
      qo_plan_free_list = plan;
    }
  else
    {
      ++qo_plans_demalloced;
      free_and_init (plan);
    }
  ++qo_plans_deallocated;
}

/*
 * qo_plan_free () -
 *   return:
 *   plan(in):
 */
static void
qo_plan_free (QO_PLAN * plan)
{
  if (plan->refcount != 0)
    {
#if defined(CUBRID_DEBUG)
      fprintf (stderr, "*** optimizer problem: plan refcount = %d ***\n", plan->refcount);
#endif /* CUBRID_DEBUG */
    }
  else
    {
      if ((plan->vtbl)->free_fn)
    {
      (*(plan->vtbl)->free_fn) (plan);
    }

      qo_plan_walk (plan, qo_plan_del_ref_func, NULL, qo_plan_add_to_free_list, NULL);
    }
}

/*
 * qo_plans_init () -
 *   return:
 *   env(in):
 */
static void
qo_plans_init (QO_ENV * env)
{
  qo_plan_free_list = NULL;
  qo_plans_allocated = 0;
  qo_plans_deallocated = 0;
  qo_plans_malloced = 0;
  qo_plans_demalloced = 0;
  qo_accumulating_plans = false /* true */ ;
  qo_next_tmpfile = 0;
}

/*
 * qo_plans_teardown () -
 *   return:
 *   env(in):
 */
static void
qo_plans_teardown (QO_ENV * env)
{
  while (qo_plan_free_list)
    {
      QO_PLAN *plan = qo_plan_free_list;

      qo_plan_free_list = plan->plan_un.free.link;
      if (plan)
    {
      free_and_init (plan);
    }
      ++qo_plans_demalloced;
    }
  qo_accumulating_plans = false;
}

/*
 * qo_plans_stats () -
 *   return:
 *   f(in):
 */
void
qo_plans_stats (FILE * f)
{
  fprintf (f, "%d/%d plans allocated/deallocated\n", qo_plans_allocated, qo_plans_deallocated);
  fprintf (f, "%d/%d plans malloced/demalloced\n", qo_plans_malloced, qo_plans_demalloced);
}

/*
 * qo_plan_dump () - Print a representation of the plan on the indicated
 *           stream
 *   return: nothing
 *   plan(in): A pointer to a query optimizer plan
 *   output(in): The stream to dump the plan to
 */
void
qo_plan_dump (QO_PLAN * plan, FILE * output)
{
  int level;

  if (output == NULL)
    {
      output = stdout;
    }

  if (plan == NULL)
    {
      fputs ("\nNo optimized plan!\n", output);
      return;
    }

  qo_get_optimization_param (&level, QO_PARAM_LEVEL);
  if (DETAILED_DUMP (level))
    {
      qo_plan_fprint (plan, output, 0, NULL);
    }
  else if (SIMPLE_DUMP (level))
    {
      qo_plan_lite_print (plan, output, 0);
    }

  fputs ("\n", output);
}

/*
 * qo_plan_get_cost_fn
 *
 * arguments:
 *  plan_name: The name of a particular plan type.
 *
 * returns/side-effects: nothing
 *
 * description: Retrieve the current cost function for the named plan.
 */

int
qo_plan_get_cost_fn (const char *plan_name)
{
  int n = DIM (all_vtbls);
  int i = 0;
  int cost = 'u';

  for (i = 0; i < n; i++)
    {
      if (intl_mbs_ncasecmp (plan_name, all_vtbls[i]->plan_string, strlen (all_vtbls[i]->plan_string)) == 0)
    {
      if (all_vtbls[i]->cost_fn == &qo_zero_cost)
        {
          cost = '0';
        }
      else if (all_vtbls[i]->cost_fn == &qo_worst_cost)
        {
          cost = 'i';
        }
      else
        {
          cost = 'd';
        }
      break;
    }
    }

  return cost;
}

/*
 * qo_plan_set_cost_fn () - Changes the optimizer cost function for all plans
 *              of the kind
 *   return: nothing
 *   plan_name(in): The name of a particular plan type
 *   fn(in): The new setting for the optimizer cost unction for the plan
 *
 * Note: Useful for testing different optimizer strategies and for forcing
 *  particular kinds of plans during testing
 */
const char *
qo_plan_set_cost_fn (const char *plan_name, int fn)
{
  int n = DIM (all_vtbls);
  int i = 0;

  for (i = 0; i < n; i++)
    {
      if (intl_mbs_ncasecmp (plan_name, all_vtbls[i]->plan_string, strlen (all_vtbls[i]->plan_string)) == 0)
    {
      switch (fn)
        {
        case 0:
        case '0':
        case 'b':       /* best */
        case 'B':       /* BEST */
        case 'z':       /* zero */
        case 'Z':       /* ZERO */
          all_vtbls[i]->cost_fn = &qo_zero_cost;
          break;

        case 1:
        case '1':
        case 'i':       /* infinite */
        case 'I':       /* INFINITE */
        case 'w':       /* worst */
        case 'W':       /* WORST */
          all_vtbls[i]->cost_fn = &qo_worst_cost;
          break;

        default:
          all_vtbls[i]->cost_fn = all_vtbls[i]->default_cost;
          break;
        }
      return all_vtbls[i]->plan_string;
    }
    }

  return NULL;

}               /* qo_plan_set_cost_fn */

/*
 * qo_init_planvec () -
 *   return:
 *   planvec(in):
 */
static void
qo_init_planvec (QO_PLANVEC * planvec)
{
  int i;

  planvec->overflow = false;
  planvec->nplans = 0;

  for (i = 0; i < NPLANS; ++i)
    {
      planvec->plan[i] = (QO_PLAN *) NULL;
    }
}

/*
 * qo_uninit_planvec () -
 *   return:
 *   planvec(in):
 */
static void
qo_uninit_planvec (QO_PLANVEC * planvec)
{
  int i;

  for (i = 0; i < planvec->nplans; ++i)
    {
      qo_plan_del_ref (planvec->plan[i]);
    }

  planvec->overflow = false;
  planvec->nplans = 0;
}

/*
 * qo_dump_planvec () -
 *   return:
 *   planvec(in):
 *   f(in):
 *   indent(in):
 */
static void
qo_dump_planvec (QO_PLANVEC * planvec, FILE * f, int indent)
{
  int i;
  int positive_indent = indent < 0 ? -indent : indent;

  if (planvec->overflow)
    {
      fputs ("(overflowed) ", f);
    }

  for (i = 0; i < planvec->nplans; ++i)
    {
      qo_plan_fprint (planvec->plan[i], f, indent, NULL);
      fputs ("\n\n", f);
      indent = positive_indent;
    }
}

/*
 * qo_check_planvec () -
 *   return: one of {PLAN_COMP_LT, PLAN_COMP_EQ, PLAN_COMP_GT}
 *   planvec(in):
 *   plan(in):
 */
static QO_PLAN_COMPARE_RESULT
qo_check_planvec (QO_PLANVEC * planvec, QO_PLAN * plan)
{
  /*
   * Check whether the new plan is definitely better than any of the
   * others.  Keep it if it is, or if it is incomparable and we still
   * have room in the planvec.  Return true if we keep the plan, false
   * if not.
   */
  int i;
  int already_retained;
  QO_PLAN_COMPARE_RESULT cmp;
  int num_eq;

  if (plan == NULL)
    {
      /* There is no new plan to be compared. */
      return PLAN_COMP_LT;
    }

  /* init */
  already_retained = 0;
  num_eq = 0;

  for (i = 0; i < planvec->nplans; i++)
    {
      cmp = qo_plan_cmp (planvec->plan[i], plan);

      /* cmp : PLAN_COMP_UNK, PLAN_COMP_LT, PLAN_COMP_EQ, PLAN_COMP_GT */
      if (cmp == PLAN_COMP_GT)
    {
      /*
       * The new plan is better than the previous one in the i'th
       * slot.  Remove the old one, and if we haven't yet retained
       * the new one, put it in the freshly-available slot.  If we
       * have already retained the plan, pull the last element down
       * from the end of the planvec and stuff it in this slot, and
       * then check this slot all over again.  Don't forget to NULL
       * out the old last element.
       */
      if (already_retained)
        {
          planvec->nplans--;
          qo_plan_del_ref (planvec->plan[i]);
          planvec->plan[i] = (i < planvec->nplans) ? planvec->plan[planvec->nplans] : NULL;
          planvec->plan[planvec->nplans] = NULL;
          /*
           * Back up `i' so that we examine this slot again.
           */
          i--;
        }
      else
        {
          (void) qo_plan_add_ref (plan);
          qo_plan_del_ref (planvec->plan[i]);
          planvec->plan[i] = plan;
        }
      already_retained = 1;
    }
      else if (cmp == PLAN_COMP_EQ)
    {
      /* found equal cost plan already found */
      num_eq++;
    }
      else if (cmp == PLAN_COMP_LT)
    {
      /*
       * The new plan is worse than some plan that we already have.
       * There is no point in checking any others; give up and get
       * out.
       */
      return PLAN_COMP_LT;
    }
    }

  /*
   * Ok, we've looked at all of the current plans.  It's possible to
   * get here and still not have retained the new plan if it couldn't
   * be determined whether it was definitely better or worse than any
   * of the plans we're already holding (or if we're not holding any).
   * Try to add it to the vector of plans.
   */
  if (!already_retained && !num_eq)
    {               /* all is PLAN_COMP_UNK */

      if (i < NPLANS)
    {
      planvec->nplans++;
      (void) qo_plan_add_ref (plan);
      qo_plan_del_ref (planvec->plan[i]);
      planvec->plan[i] = plan;
      already_retained = 1;
    }
      else
    {
      int best_vc_pid, best_tc_pid;
      int worst_vc_pid, worst_tc_pid;
      double vc, tc, p_vc, p_tc;
      QO_PLAN *p;

      /*
       * We would like to keep this plan, but we're out of slots in
       * the planvec.  For now, we just throw out one plan with the
       * highest access cost.
       */

      /* STEP 1: found best plan */
      best_vc_pid = best_tc_pid = -1;   /* init */

      vc = plan->variable_cpu_cost + plan->variable_io_cost;
      tc = plan->fixed_cpu_cost + plan->fixed_io_cost + vc;

      for (i = 0; i < planvec->nplans; i++)
        {
          p = planvec->plan[i];
          p_vc = p->variable_cpu_cost + p->variable_io_cost;
          p_tc = p->fixed_cpu_cost + p->fixed_io_cost + p_vc;

          if (p_vc < vc)
        {       /* found best variable cost plan */
          best_vc_pid = i;
          vc = p_vc;    /* save best variable cost */
        }

          if (p_tc < tc)
        {       /* found best total cost plan */
          best_tc_pid = i;
          tc = p_tc;    /* save best total cost */
        }
        }

      /* STEP 2: found worst plan */
      worst_vc_pid = worst_tc_pid = -1; /* init */

      vc = plan->variable_cpu_cost + plan->variable_io_cost;
      tc = plan->fixed_cpu_cost + plan->fixed_io_cost + vc;

      for (i = 0; i < planvec->nplans; i++)
        {
          p = planvec->plan[i];
          p_vc = p->variable_cpu_cost + p->variable_io_cost;
          p_tc = p->fixed_cpu_cost + p->fixed_io_cost + p_vc;

          if (i != best_vc_pid && i != best_tc_pid)
        {
          if (p_vc > vc)
            {       /* found worst variable cost plan */
              worst_vc_pid = i;
              vc = p_vc;    /* save worst variable cost */
            }

          if (p_tc > tc)
            {       /* found worst total cost plan */
              worst_tc_pid = i;
              tc = p_tc;    /* save worst total cost */
            }
        }
        }

      if (worst_tc_pid != -1)
        {           /* release worst total cost plan */
          (void) qo_plan_add_ref (plan);
          qo_plan_del_ref (planvec->plan[worst_tc_pid]);
          planvec->plan[worst_tc_pid] = plan;
          already_retained = 1;
        }
      else if (worst_vc_pid != -1)
        {           /* release worst variable cost plan */
          (void) qo_plan_add_ref (plan);
          qo_plan_del_ref (planvec->plan[worst_vc_pid]);
          planvec->plan[worst_vc_pid] = plan;
          already_retained = 1;
        }
      else
        {
          /*
           * The new plan is worse than some plan that we already have.
           * There is no point in checking any others; give up and get
           * out.
           */
          return PLAN_COMP_LT;
        }
    }
    }

  if (already_retained)
    {
      return PLAN_COMP_GT;
    }
  else if (num_eq)
    {
      return PLAN_COMP_EQ;
    }

  return cmp;
}

/*
 * qo_cmp_planvec () -
 *   return: one of {PLAN_COMP_UNK, PLAN_COMP_LT, PLAN_COMP_EQ, PLAN_COMP_GT}
 *   planvec(in):
 *   plan(in):
 */
static QO_PLAN_COMPARE_RESULT
qo_cmp_planvec (QO_PLANVEC * planvec, QO_PLAN * plan)
{
  int i;
  QO_PLAN_COMPARE_RESULT cmp;

  cmp = PLAN_COMP_UNK;      /* init */

  for (i = 0; i < planvec->nplans; i++)
    {
      cmp = qo_plan_cmp (planvec->plan[i], plan);
      if (cmp != PLAN_COMP_UNK)
    {
      return cmp;
    }
    }

  /* at here, all is PLAN_COMP_UNK */

  return cmp;
}

/*
 * qo_find_best_plan_on_planvec () -
 *   return:
 *   planvec(in):
 *   n(in):
 */
static QO_PLAN *
qo_find_best_plan_on_planvec (QO_PLANVEC * planvec, double n)
{
  int i;
  QO_PLAN *best_plan, *plan;
  double fixed, variable, best_cost, cost;

  /* While initializing the cost to QO_INFINITY and starting the loop at i = 0 might look equivalent to this, it
   * actually loses if all of the elements in the vector have cost QO_INFINITY, because the comparison never succeeds
   * and we never make plan non-NULL.  This is very bad for those callers above who believe that we're returning
   * something useful.
   */

  best_plan = planvec->plan[0];
  fixed = best_plan->fixed_cpu_cost + best_plan->fixed_io_cost;
  variable = best_plan->variable_cpu_cost + best_plan->variable_io_cost;
  best_cost = fixed + (n * variable);
  for (i = 1; i < planvec->nplans; i++)
    {
      plan = planvec->plan[i];
      fixed = plan->fixed_cpu_cost + plan->fixed_io_cost;
      variable = plan->variable_cpu_cost + plan->variable_io_cost;
      cost = fixed + (n * variable);

      if (cost < best_cost)
    {
      best_plan = plan;
      best_cost = cost;
    }
    }

  return best_plan;
}

/*
 * An Info node is associated with a combination of join expressions, and
 * holds plans for producing the result described by that combination.
 * At any time, the node holds the best plans we have yet encountered for
 * producing the result in any of the interesting orders (including the
 * best way we know of when order is not a concern).  Of course, for many
 * join combinations, it will be impossible to produce a result in a
 * given order (i.e., when that order is based on an attribute of a
 * relation that doesn't participate in the particular combination).  In
 * these cases we simply record a NULL plan for that order.
 */

/*
 * qo_info_nodes_init () -
 *   return:
 *   env(in):
 */
static void
qo_info_nodes_init (QO_ENV * env)
{
  infos_allocated = 0;
  infos_deallocated = 0;
}

/*
 * qo_alloc_info () -
 *   return:
 *   planner(in):
 *   nodes(in):
 *   terms(in):
 *   eqclasses(in):
 *   cardinality(in):
 *   total_rows(in):
 */
static QO_INFO *
qo_alloc_info (QO_PLANNER * planner, BITSET * nodes, BITSET * terms, BITSET * eqclasses, double cardinality,
           double total_rows)
{
  QO_INFO *info;
  int i;
  int EQ;

  info = (QO_INFO *) malloc (sizeof (QO_INFO));
  if (info == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, sizeof (QO_INFO));
      return NULL;
    }

  i = 0;
  EQ = planner->EQ;

  info->env = planner->env;
  info->planner = planner;

  bitset_init (&(info->nodes), planner->env);
  bitset_init (&(info->terms), planner->env);
  bitset_init (&(info->eqclasses), planner->env);
  bitset_init (&(info->projected_segs), planner->env);

  bitset_assign (&info->nodes, nodes);
  bitset_assign (&info->terms, terms);
  bitset_assign (&info->eqclasses, eqclasses);
  qo_compute_projected_segs (planner, nodes, terms, &info->projected_segs);
  info->projected_size = qo_compute_projected_size (planner, &info->projected_segs);
  info->cardinality = cardinality;
  info->scan_rows = cardinality;    /* after iscan_cost, sscan_cost. it'll be replaced accurately */
  info->total_rows = total_rows;
  info->group_rows = cardinality;   /* it is recalculated in qo_sort_new() */
  info->hit_prob = 1.0;

  qo_init_planvec (&info->best_no_order);

  /*
   * Set aside an array for ordered plans.  Each element of the array
   * holds a plan that is ordered according to the corresponding
   * equivalence class.
   *
   * If this malloc() fails, we'll lose the memory pointed to by
   * info.  I'll take the chance.
   */
  info->planvec = NULL;
  if (EQ > 0)
    {
      info->planvec = (QO_PLANVEC *) malloc (sizeof (QO_PLANVEC) * EQ);
      if (info->planvec == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, sizeof (QO_PLANVEC) * EQ);
      free_and_init (info);
      return NULL;
    }
    }

  for (i = 0; i < EQ; ++i)
    {
      qo_init_planvec (&info->planvec[i]);
    }

  info->join_unit = planner->join_unit; /* init */

  info->detached = false;

  infos_allocated++;

  /* insert into the head of alloced info list */
  info->next = planner->info_list;
  planner->info_list = info;

  return info;
}

/*
 * qo_free_info () -
 *   return:
 *   info(in):
 */
static void
qo_free_info (QO_INFO * info)
{
  if (info == NULL)
    {
      return;
    }

  qo_detach_info (info);

  bitset_delset (&(info->nodes));
  bitset_delset (&(info->terms));
  bitset_delset (&(info->eqclasses));
  bitset_delset (&(info->projected_segs));

  free_and_init (info);

  infos_deallocated++;
}

/*
 * qo_detach_info () -
 *   return:
 *   info(in):
 */
static void
qo_detach_info (QO_INFO * info)
{
  /*
   * If the node hasn't already been detached, detach it now and give
   * up references to all plans that are no longer needed.
   */
  if (!info->detached)
    {
      int i;
      int EQ = info->planner->EQ;

      for (i = 0; i < EQ; ++i)
    {
      qo_uninit_planvec (&info->planvec[i]);
    }
      free_and_init (info->planvec);
      info->detached = true;

      qo_uninit_planvec (&info->best_no_order);
    }
}

/*
 * qo_check_new_best_plan_on_info () -
 *   return:
 *   info(in):
 *   plan(in):
 */
static bool
qo_check_new_best_plan_on_info (QO_INFO * info, QO_PLAN * plan)
{
  QO_PLAN_COMPARE_RESULT cmp;
  QO_EQCLASS *order;

  order = plan->order;
  if (order && bitset_is_empty (&(QO_EQCLASS_SEGS (order))))
    {
      /* Then this "equivalence class" is a phony fabricated especially for a complex merge term. skip out */
      cmp = PLAN_COMP_LT;
    }
  else
    {
      cmp = qo_check_planvec (&info->best_no_order, plan);

      if (cmp == PLAN_COMP_GT)
    {
      if (plan->plan_type != QO_PLANTYPE_SORT || plan->plan_un.sort.sort_type != SORT_LIMIT)
        {
          int i, EQ;
          QO_PLAN *new_plan, *sort_plan, *best_plan;
          QO_ENV *env;
          QO_PLAN_COMPARE_RESULT new_cmp;

          env = info->env;

          EQ = info->planner->EQ;
          best_plan = qo_find_best_plan_on_planvec (&info->best_no_order, 1.0);
          if (QO_ENV_USE_SORT_LIMIT (env) == QO_SL_USE && !best_plan->has_sort_limit
          && bitset_is_equivalent (&QO_ENV_SORT_LIMIT_NODES (env), &info->nodes))
        {
          /* generate a SORT_LIMIT plan over this plan */
          sort_plan = qo_sort_new (best_plan, QO_UNORDERED, SORT_LIMIT);
          if (sort_plan != NULL)
            {
              if (qo_check_plan_on_info (info, sort_plan) > 0)
            {
              best_plan = sort_plan;
            }
            }
        }
          /*
           * Check to see if any of the ordered solutions can be made
           * cheaper by sorting this new plan.
           */
          for (i = 0; i < EQ; i++)
        {
          order = &info->planner->eqclass[i];

          new_plan = qo_plan_order_by (best_plan, order);
          if (new_plan)
            {
              new_cmp = qo_check_planvec (&info->planvec[i], new_plan);
              if (new_cmp == PLAN_COMP_LT || new_cmp == PLAN_COMP_EQ)
            {
              qo_plan_release (new_plan);
            }
            }
        }
        }
    }
    }

  if (cmp == PLAN_COMP_LT || cmp == PLAN_COMP_EQ)
    {
      qo_plan_release (plan);
      return false;
    }

  return true;
}

/*
 * qo_check_plan_on_info () -
 *   return:
 *   info(in):
 *   plan(in):
 */
static int
qo_check_plan_on_info (QO_INFO * info, QO_PLAN * plan)
{
  QO_INFO *best_info;
  QO_EQCLASS *plan_order;
  QO_PLAN_COMPARE_RESULT cmp;
  bool found_new_best;

  if (info == NULL || plan == NULL)
    {
      return 0;
    }

  /* init */
  found_new_best = false;
  best_info = info->planner->best_info;
  plan_order = plan->order;

  /* if the plan is of type QO_SCANMETHOD_INDEX_ORDERBY_SCAN but it doesn't skip the orderby, we release the plan. */
  if (qo_is_iscan_from_orderby (plan)
      && !(plan->top_rooted ? plan->plan_un.scan.index->head->orderby_skip : qo_plan_is_orderby_skip_candidate (plan)))
    {
      qo_plan_release (plan);
      return 0;
    }

  /* if the plan is of type QO_SCANMETHOD_INDEX_GRUOPBY_SCAN but it doesn't skip the groupby, we release the plan. */
  if (qo_is_iscan_from_groupby (plan) && !plan->plan_un.scan.index->head->groupby_skip)
    {
      qo_plan_release (plan);
      return 0;
    }

  /*
   * If the cost of the new Plan already exceeds the cost of the best
   * known solution with the same order, there is no point in
   * remembering the new plan.
   */
  if (best_info)
    {
      cmp =
    qo_cmp_planvec (plan_order ==
            QO_UNORDERED ? &best_info->best_no_order : &best_info->planvec[QO_EQCLASS_IDX (plan_order)],
            plan);
      /* cmp : PLAN_COMP_UNK, PLAN_COMP_LT, PLAN_COMP_EQ, PLAN_COMP_GT */
      if (cmp == PLAN_COMP_LT || cmp == PLAN_COMP_EQ)
    {
      qo_plan_release (plan);
      return 0;
    }
    }

  /*
   * The only time we will keep an unordered plan is if it is cheaper
   * than any other plan we have seen so far (ordered or unordered).
   * Only ordered plans are kept in the _plan vector.
   */
  if (plan_order == QO_UNORDERED)
    {
      found_new_best = qo_check_new_best_plan_on_info (info, plan);
    }
  else
    {
      /*
       * If we get here, we are dealing with an ordered plan.  Check
       * whether we already have memo-ized a plan for this particular scan
       * order.  If so, see if this new plan is an improvement.
       */

      cmp = qo_check_planvec (&info->planvec[QO_EQCLASS_IDX (plan_order)], plan);
      if (cmp == PLAN_COMP_GT)
    {
      (void) qo_check_new_best_plan_on_info (info, plan);
      found_new_best = true;
    }
      else
    {
      qo_plan_release (plan);
      return 0;
    }
    }

  if (found_new_best != true)
    {
      return 0;
    }

  /* save the last join level; used for cache */
  info->join_unit = info->planner->join_unit;

  return 1;
}

/*
 * qo_find_best_nljoin_inner_plan_on_info () -
 *   return:
 *   outer(in):
 *   info(in):
 *   join_type(in):
 *   idx_join_plan_n(in):
 */
static QO_PLAN *
qo_find_best_nljoin_inner_plan_on_info (QO_PLAN * outer, QO_INFO * info, JOIN_TYPE join_type, int idx_join_plan_n)
{
  QO_PLANVEC *pv;
  QO_PLAN *temp, *best_plan, *inner;
  double temp_cost, best_cost;
  int i;

  /* init */
  best_plan = NULL;
  best_cost = 0;

  /* alloc temporary nl-join plan */
  temp = qo_plan_malloc (info->env);
  if (temp == NULL)
    {
      return NULL;
    }

  temp->info = info;
  temp->refcount = 0;
  temp->top_rooted = false;
  temp->well_rooted = false;
  temp->iscan_sort_list = NULL;
  temp->analytic_eval_list = NULL;
  temp->plan_type = QO_PLANTYPE_JOIN;
  temp->plan_un.join.join_type = join_type; /* set nl-join type */
  temp->plan_un.join.outer = outer; /* set outer */

  temp->multi_range_opt_use = PLAN_MULTI_RANGE_OPT_NO;

  for (i = 0, pv = &info->best_no_order; i < pv->nplans; i++)
    {
      inner = pv->plan[i];  /* set inner */

      /* if already found idx-join, then exclude sequential inner */
      if (idx_join_plan_n > 0)
    {
      if (qo_is_seq_scan (inner) || inner->has_sort_limit)
        {
          continue;
        }
    }

      temp->plan_un.join.inner = inner;
      qo_nljoin_cost (temp);
      temp_cost = temp->fixed_cpu_cost + temp->fixed_io_cost + temp->variable_cpu_cost + temp->variable_io_cost;
      if (best_plan == NULL || temp_cost < best_cost)
    {
      /* save new best inner */
      best_cost = temp_cost;
      best_plan = inner;
    }
    }

  /* free temp plan */
  temp->plan_un.join.outer = NULL;
  temp->plan_un.join.inner = NULL;

  qo_plan_add_to_free_list (temp, NULL);

  return best_plan;
}

/*
 * qo_find_best_plan_on_info () -
 *   return:
 *   info(in):
 *   order(in):
 *   n(in):
 */
static QO_PLAN *
qo_find_best_plan_on_info (QO_INFO * info, QO_EQCLASS * order, double n)
{
  QO_PLANVEC *pv;

  if (order == QO_UNORDERED)
    {
      pv = &info->best_no_order;
    }
  else
    {
      int order_idx = QO_EQCLASS_IDX (order);
      if (info->planvec[order_idx].nplans == 0)
    {
      QO_PLAN *planp;

      planp = qo_sort_new (qo_find_best_plan_on_planvec (&info->best_no_order, n), order, SORT_TEMP);

      qo_check_planvec (&info->planvec[order_idx], planp);
    }
      pv = &info->planvec[order_idx];
    }

  return qo_find_best_plan_on_planvec (pv, n);
}

/*
 * qo_examine_idx_join () -
 *   return:
 *   info(in):
 *   join_type(in):
 *   outer(in):
 *   inner(in):
 *   afj_terms(in):
 *   sarged_terms(in):
 *   pinned_subqueries(in):
 */
static int
qo_examine_idx_join (QO_INFO * info, JOIN_TYPE join_type, QO_INFO * outer, QO_INFO * inner, BITSET * afj_terms,
             BITSET * sarged_terms, BITSET * pinned_subqueries)
{
  int n = 0;
  QO_NODE *inner_node;

  /* check for right outer join; */
  if (join_type == JOIN_RIGHT)
    {
      if (bitset_cardinality (&(outer->nodes)) != 1)
    {           /* not single class spec */
      /* inner of correlated index join should be plain class access */
      goto exit;
    }

      inner_node = QO_ENV_NODE (outer->env, bitset_first_member (&(outer->nodes)));
      if (QO_NODE_HINT (inner_node) & PT_HINT_ORDERED)
    {
      /* join hint: force join left-to-right; skip idx-join because, these are only support left outer join */
      goto exit;
    }
    }
  else
    {
      inner_node = QO_ENV_NODE (inner->env, bitset_first_member (&(inner->nodes)));
    }

  /* inner is single class spec */
  if (QO_NODE_HINT (inner_node) & (PT_HINT_USE_IDX | PT_HINT_USE_NL))
    {
      /* join hint: force idx-join */
    }
  else if (QO_NODE_HINT (inner_node) & PT_HINT_USE_MERGE)
    {
      /* join hint: force merge-join; skip idx-join */
      goto exit;
    }
  else if (!(QO_NODE_HINT (inner_node) & PT_HINT_NO_USE_HASH) && (QO_NODE_HINT (inner_node) & PT_HINT_USE_HASH))
    {
      /* join hint: force hash-join; skip idx-join */
      goto exit;
    }
  else
    {
      /* fall through */
    }

  /* check whether we can build a nested loop join with a correlated index scan. That is, is the inner term a scan of a
   * single node, and can this join term be used as an index with respect to that node? If so, we can build a special
   * kind of plan to exploit that.
   */
  if (join_type == JOIN_RIGHT)
    {
      /* if right outer join, select outer plan from the inner node and inner plan from the outer node, and do left
       * outer join
       */
      n = qo_examine_correlated_index (info, JOIN_LEFT, inner, outer, afj_terms, sarged_terms, pinned_subqueries);
    }
  else
    {
      n = qo_examine_correlated_index (info, join_type, outer, inner, afj_terms, sarged_terms, pinned_subqueries);
    }

exit:

  return n;
}

/*
 * qo_examine_nl_join () -
 *   return:
 *   info(in):
 *   join_type(in):
 *   outer(in):
 *   inner(in):
 *   nl_join_terms(in):
 *   duj_terms(in):
 *   afj_terms(in):
 *   sarged_terms(in):
 *   pinned_subqueries(in):
 *   idx_join_plan_n(in):
 */
static int
qo_examine_nl_join (QO_INFO * info, JOIN_TYPE join_type, QO_INFO * outer, QO_INFO * inner, BITSET * nl_join_terms,
            BITSET * duj_terms, BITSET * afj_terms, BITSET * sarged_terms, BITSET * pinned_subqueries,
            int idx_join_plan_n, BITSET * hash_terms)
{
  int n = 0;
  QO_PLAN *outer_plan, *inner_plan;
  QO_NODE *inner_node;

  if (join_type == JOIN_RIGHT)
    {
      /* converse outer join type */
      join_type = JOIN_LEFT;

      if (bitset_intersects (sarged_terms, &(info->env->fake_terms)))
    {
      goto exit;
    }

      {
    int t;
    QO_TERM *term;
    BITSET_ITERATOR iter;

    for (t = bitset_iterate (nl_join_terms, &iter); t != -1; t = bitset_next_member (&iter))
      {
        term = QO_ENV_TERM (info->env, t);
        if (QO_TERM_CLASS (term) == QO_TC_DEP_LINK)
          {
        goto exit;
          }
      }         /* for (t = ...) */
      }

      if (bitset_cardinality (&(outer->nodes)) == 1)
    {           /* single class spec */
      inner_node = QO_ENV_NODE (outer->env, bitset_first_member (&(outer->nodes)));
      if (QO_NODE_HINT (inner_node) & PT_HINT_ORDERED)
        {
          /* join hint: force join left-to-right; skip idx-join because, these are only support left outer join */
          goto exit;
        }

      if (QO_NODE_HINT (inner_node) & PT_HINT_USE_NL)
        {
          /* join hint: force nl-join */
        }
      else if (QO_NODE_HINT (inner_node) & (PT_HINT_USE_IDX | PT_HINT_USE_MERGE))
        {
          /* join hint: force idx-join, merge-join; skip nl-join */
          goto exit;
        }
      else if (!(QO_NODE_HINT (inner_node) & PT_HINT_NO_USE_HASH) && (QO_NODE_HINT (inner_node) & PT_HINT_USE_HASH))
        {
          /* join hint: force hash-join; skip nl-join */
          goto exit;
        }
      else
        {
          /* fall through */
        }
    }

      outer_plan = qo_find_best_plan_on_info (inner, QO_UNORDERED, 1.0);
      if (outer_plan == NULL)
    {
      goto exit;
    }
      inner_plan = qo_find_best_nljoin_inner_plan_on_info (outer_plan, outer, join_type, idx_join_plan_n);
      if (inner_plan == NULL)
    {
      goto exit;
    }
    }
  else
    {
      /* At here, inner is single class spec */
      inner_node = QO_ENV_NODE (inner->env, bitset_first_member (&(inner->nodes)));
      if (QO_NODE_HINT (inner_node) & PT_HINT_USE_NL)
    {
      /* join hint: force nl-join */
    }
      else if (QO_NODE_HINT (inner_node) & (PT_HINT_USE_IDX | PT_HINT_USE_MERGE))
    {
      /* join hint: force idx-join, merge-join; skip nl-join */
      goto exit;
    }
      else if (!(QO_NODE_HINT (inner_node) & PT_HINT_NO_USE_HASH) && (QO_NODE_HINT (inner_node) & PT_HINT_USE_HASH))
    {
      /* join hint: force hash-join; skip nl-join */
      goto exit;
    }
      else
    {
      /* fall through */
    }

      outer_plan = qo_find_best_plan_on_info (outer, QO_UNORDERED, 1.0);
      if (outer_plan == NULL)
    {
      goto exit;
    }
      inner_plan = qo_find_best_nljoin_inner_plan_on_info (outer_plan, inner, join_type, idx_join_plan_n);
      if (inner_plan == NULL)
    {
      goto exit;
    }
    }

#if 0               /* CHAINS_ONLY */
  /* If CHAINS_ONLY is defined, we want the optimizer constrained to produce only left-linear trees of joins, i.e., no
   * inner term can itself be a join or a follow.
   */

  if (inner_plan->plan_type != QO_PLANTYPE_SCAN)
    {
      if (inner_plan->plan_type == QO_PLANTYPE_SORT && inner_plan->order == QO_UNORDERED)
    {
      /* inner has temporary list file plan; it's ok */
      ;
    }
      else
    {
      goto exit;
    }
    }
#endif /* CHAINS_ONLY */

#if 0               /* JOIN_FOLLOW_RESTRICTION */
  /* Under this restriction, we are not permitted to produce plans that have follow nodes sandwiched between joins.
   * Don't ask why.
   */

  if (outer_plan->plan_type == QO_PLANTYPE_FOLLOW && QO_PLAN_SUBJOINS (outer_plan))
    {
      goto exit;
    }
  if (inner_plan->plan_type == QO_PLANTYPE_FOLLOW && QO_PLAN_SUBJOINS (inner_plan))
    {
      goto exit;
    }
#endif /* JOIN_FOLLOW_RESTRICTION */

  /* look for the best nested loop solution we can find.  Since the subnodes are already keeping track of the
   * lowest-cost plan they have seen, we needn't do any search here to find the cheapest nested loop join we can
   * produce for this combination.
   */
  n =
    qo_check_plan_on_info (info,
               qo_join_new (info, join_type, QO_JOINMETHOD_NL_JOIN, outer_plan, inner_plan, nl_join_terms,
                    duj_terms, afj_terms, sarged_terms, pinned_subqueries, hash_terms));

exit:

  return n;
}

/*
 * qo_examine_merge_join () -
 *   return:
 *   info(in):
 *   join_type(in):
 *   outer(in):
 *   inner(in):
 *   sm_join_terms(in):
 *   duj_terms(in):
 *   afj_terms(in):
 *   sarged_terms(in):
 *   pinned_subqueries(in):
 */
static int
qo_examine_merge_join (QO_INFO * info, JOIN_TYPE join_type, QO_INFO * outer, QO_INFO * inner, BITSET * sm_join_terms,
               BITSET * duj_terms, BITSET * afj_terms, BITSET * sarged_terms, BITSET * pinned_subqueries)
{
  int n = 0;
  QO_PLAN *outer_plan, *inner_plan;
  QO_NODE *inner_node;
  QO_EQCLASS *order = QO_UNORDERED;
  int t;
  BITSET_ITERATOR iter;
  QO_TERM *term;
  BITSET empty_terms;
  bitset_init (&empty_terms, info->env);

  /* If any of the sarged terms are fake terms, we can't implement this join as a merge join, because the timing
   * assumptions required by the fake terms won't be satisfied.  Nested loops are the only joins that will work.
   */
  if (bitset_intersects (sarged_terms, &(info->env->fake_terms)))
    {
      goto exit;
    }

  /* examine ways of producing ordered results.  For each ordering, check whether the inner and outer subresults can be
   * produced in that order.  If so, check a merge join plan on that order.
   */
  for (t = bitset_iterate (sm_join_terms, &iter); t != -1; t = bitset_next_member (&iter))
    {
      term = QO_ENV_TERM (info->env, t);
      order = QO_TERM_EQCLASS (term);
      if (order != QO_UNORDERED)
    {
      break;
    }
    }

  if (order == QO_UNORDERED)
    {
      goto exit;
    }

#ifdef OUTER_MERGE_JOIN_RESTRICTION
  if (IS_OUTER_JOIN_TYPE (join_type))
    {
      int node_idx;

      term = QO_ENV_TERM (info->env, bitset_first_member (sm_join_terms));
      node_idx = (join_type == JOIN_LEFT) ? QO_NODE_IDX (QO_TERM_HEAD (term)) : QO_NODE_IDX (QO_TERM_TAIL (term));
      for (t = bitset_iterate (duj_terms, &iter); t != -1; t = bitset_next_member (&iter))
    {
      term = QO_ENV_TERM (info->env, t);
      if (!BITSET_MEMBER (QO_TERM_NODES (term), node_idx))
        {
          goto exit;
        }
    }
    }
#endif /* OUTER_MERGE_JOIN_RESTRICTION */

  /* At here, inner is single class spec */
  inner_node = QO_ENV_NODE (inner->env, bitset_first_member (&(inner->nodes)));

  if (QO_NODE_HINT (inner_node) & PT_HINT_USE_MERGE)
    {
      /* join hint: force m-join */
    }
  else if (QO_NODE_HINT (inner_node) & (PT_HINT_USE_NL | PT_HINT_USE_IDX))
    {
      /* join hint: force nl-join, idx-join; skip m-join */
      goto exit;
    }
  else if (!(QO_NODE_HINT (inner_node) & PT_HINT_NO_USE_HASH) && (QO_NODE_HINT (inner_node) & PT_HINT_USE_HASH))
    {
      /* join hint: force hash-join; skip m-join */
      goto exit;
    }
  else if (!prm_get_bool_value (PRM_ID_OPTIMIZER_ENABLE_MERGE_JOIN))
    {
      /* optimizer prm: keep out m-join; */
      goto exit;
    }
  else
    {
      /* fall through */
    }

  outer_plan = qo_find_best_plan_on_info (outer, order, 1.0);
  if (outer_plan == NULL)
    {
      goto exit;
    }

  inner_plan = qo_find_best_plan_on_info (inner, order, 1.0);
  if (inner_plan == NULL)
    {
      goto exit;
    }

#ifdef CHAINS_ONLY
  /* If CHAINS_ONLY is defined, we want the optimizer constrained to produce only left-linear trees of joins, i.e., no
   * inner term can itself be a join or a follow.
   */

  if (inner_plan->plan_type != QO_PLANTYPE_SCAN)
    {
      if (inner_plan->plan_type == QO_PLANTYPE_SORT && inner_plan->order == QO_UNORDERED)
    {
      /* inner has temporary list file plan; it's ok */
      ;
    }
      else
    {
      goto exit;
    }
    }
#endif /* CHAINS_ONLY */

#if 0               /* JOIN_FOLLOW_RESTRICTION */
  /* Under this restriction, we are not permitted to produce plans that have follow nodes sandwiched between joins.
   * Don't ask why.
   */

  if (outer_plan->plan_type == QO_PLANTYPE_FOLLOW && QO_PLAN_SUBJOINS (outer_plan))
    {
      goto exit;
    }
  if (inner_plan->plan_type == QO_PLANTYPE_FOLLOW && QO_PLAN_SUBJOINS (inner_plan))
    {
      goto exit;
    }
#endif /* JOIN_FOLLOW_RESTRICTION */

  n =
    qo_check_plan_on_info (info,
               qo_join_new (info, join_type, QO_JOINMETHOD_MERGE_JOIN, outer_plan, inner_plan,
                    sm_join_terms, duj_terms, afj_terms, sarged_terms, pinned_subqueries,
                    &empty_terms));

exit:

  return n;
}

/*
 * qo_examine_hash_join () -
 *   return:
 *   info(in):
 *   join_type(in):
 *   outer(in):
 *   inner(in):
 *   hash_join_terms(in):
 *   duj_terms(in):
 *   afj_terms(in):
 *   sarged_terms(in):
 *   pinned_subqueries(in):
 */
static int
qo_examine_hash_join (QO_INFO * info, JOIN_TYPE join_type, QO_INFO * outer, QO_INFO * inner, BITSET * hash_join_terms,
              BITSET * duj_terms, BITSET * afj_terms, BITSET * sarged_terms, BITSET * pinned_subqueries)
{
  QO_PLAN *outer_plan, *inner_plan;
  QO_NODE *outer_node, *inner_node;
  QO_NODE_INDEX *node_index;
  QO_TERM *term;
  BITSET_ITERATOR bitset_iter;
  int bitset_index;
  int i, n = 0;

  UINT64 mem_limit = prm_get_bigint_value (PRM_ID_MAX_HASH_LIST_SCAN_SIZE);
  if (mem_limit <= 0)
    {
      goto exit;        /* give up */
    }

  /* If any of the sarged terms are fake terms, we can't implement this join as a merge join, because the timing
   * assumptions required by the fake terms won't be satisfied.  Nested loops are the only joins that will work.
   */
  if (bitset_intersects (sarged_terms, &info->env->fake_terms))
    {
      goto exit;
    }

  /* A query using a predicate for an oid is rewritten as a path join query.  The path join query is executed
   * as a left outer join, so if the path join query is not executed with a follow plan, results with NULL values
   * are retrieved even if the join predicate is not satisfied.
   * 
   *   e.g. drop table if exists t;
   *        create table t (c int) dont_reuse_oid;
   *        insert into t values (1);
   *        select dual into :dummy_oid from dual limit 1;
   * 
   *        select * from t where t = :dummy_oid;
   *
   *        -- rewritten query
   *        select dt_1.da_2.t.c from table({:dummy_oid}) dt_1 (da_2)
   * 
   *        -- Query plan: follow
   *        There are no results.
   *        0 row selected.
   *
   *        -- Query plan: hash-join (left outer join)
   *        c1: NULL
   *        1 row selected.
   * 
   * This code prevents the path join query from being executed as a hash join plan rather than as a follow plan.
   */
  for (bitset_index = bitset_iterate (hash_join_terms, &bitset_iter); bitset_index != -1;
       bitset_index = bitset_next_member (&bitset_iter))
    {
      term = QO_ENV_TERM (info->env, bitset_index);
      if (QO_IS_PATH_TERM (term) && QO_TERM_JOIN_TYPE (term) != JOIN_INNER)
    {
      goto exit;        /* give up */
    }
    }

  /* At here, inner is single class spec */
  inner_node = QO_ENV_NODE (inner->env, bitset_first_member (&(inner->nodes)));

  if (QO_NODE_HINT (inner_node) & PT_HINT_NO_USE_HASH)
    {
      /* join hint: disable hash-join */
      goto exit;
    }
  else if (QO_NODE_HINT (inner_node) & PT_HINT_USE_HASH)
    {
      /* join hint: force hash-join */
    }
  else if (QO_NODE_HINT (inner_node) & (PT_HINT_USE_NL | PT_HINT_USE_IDX | PT_HINT_USE_MERGE))
    {
      /* join hint: force nl-join, idx-join, m-join; skip hash-join */
      goto exit;
    }
  else
    {
      /* default: disable hash-join */
#if TEST_HASH_JOIN_ENABLE
      /* fall through */
#else /* TEST_HASH_JOIN_ENABLE */
      goto exit;
#endif /* TEST_HASH_JOIN_ENABLE */
    }

  /* Check if a click counter is set. */
  if (QO_ENV_PT_TREE (info->env)->flag.is_click_counter)
    {
      goto exit;        /* give up */
    }

  /* Check if a key limit is set. */
  node_index = QO_NODE_INDEXES (inner_node);
  if (node_index != NULL)
    {
      for (i = 0; i < QO_NI_N (node_index); i++)
    {
      if (QO_NI_ENTRY (node_index, i)->head->key_limit != NULL)
        {
          goto exit;    /* give up */
        }
    }
    }

  for (bitset_index = bitset_iterate (&outer->nodes, &bitset_iter); bitset_index != -1;
       bitset_index = bitset_next_member (&bitset_iter))
    {
      outer_node = QO_ENV_NODE (outer->env, bitset_index);

      node_index = QO_NODE_INDEXES (outer_node);
      if (node_index != NULL)
    {
      for (i = 0; i < QO_NI_N (node_index); i++)
        {
          if (QO_NI_ENTRY (node_index, i)->head->key_limit != NULL)
        {
          goto exit;    /* give up */
        }
        }
    }
    }

  outer_plan = qo_find_best_plan_on_info (outer, QO_UNORDERED, 1.0);
  if (outer_plan == NULL)
    {
      goto exit;
    }

  inner_plan = qo_find_best_plan_on_info (inner, QO_UNORDERED, 1.0);
  if (inner_plan == NULL)
    {
      goto exit;
    }

  n =
    qo_check_plan_on_info (info,
               qo_join_new (info, join_type, QO_JOINMETHOD_HASH_JOIN, outer_plan, inner_plan,
                    hash_join_terms, duj_terms, afj_terms, sarged_terms, pinned_subqueries,
                    hash_join_terms));

exit:
  return n;
}

/*
 * qo_examine_correlated_index () -
 *   return: int
 *   info(in): The info node to be corresponding to the join being investigated
 *   join_type(in):
 *   outer(in): The info node for the outer join operand
 *   inner(in): The info node for the inner join operand
 *   afj_terms(in): The term being used to join the operands
 *   sarged_terms(in): The residual terms to be evaluated at this level of
 *             the plan tree
 *   pinned_subqueries(in): The subqueries to be pinned to plans at this node
 *
 * Note: Check whether we can build a nested loop join that implements
 *      the join term as a correlated index on the inner term.
 *      Operationally, such a join looks something like
 *
 *          for (every record in the outer term)
 *          {
 *              parameterize join term with values
 *              from outer record;
 *              do index scan on inner node;
 *              for (every record in inner scan)
 *              {
 *              join outer and inner records;
 *              }
 *          }
 */
static int
qo_examine_correlated_index (QO_INFO * info, JOIN_TYPE join_type, QO_INFO * outer, QO_INFO * inner, BITSET * afj_terms,
                 BITSET * sarged_terms, BITSET * pinned_subqueries)
{
  QO_NODE *nodep;
  QO_NODE_INDEX *node_indexp;
  QO_NODE_INDEX_ENTRY *ni_entryp;
  QO_INDEX_ENTRY *index_entryp;
  QO_PLAN *outer_plan;
  int i, n = 0;
  BITSET_ITERATOR iter;
  int t;
  QO_TERM *termp;
  int num_only_args;
  BITSET indexable_terms;

  /* outer plan */
  outer_plan = qo_find_best_plan_on_info (outer, QO_UNORDERED, 1.0);
  if (outer_plan == NULL)
    {
      return 0;
    }

#if 0               /* JOIN_FOLLOW_RESTRICTION */
  /* Under this restriction, we are not permitted to produce plans that have follow nodes sandwiched between joins.
   * Don't ask why.
   */

  if (outer_plan->plan_type == QO_PLANTYPE_FOLLOW && QO_PLAN_SUBJOINS (outer_plan))
    {
      return 0;
    }
#endif /* JOIN_FOLLOW_RESTRICTION */

  /* inner node and its indexes */
  nodep = &info->planner->node[bitset_first_member (&(inner->nodes))];
  node_indexp = QO_NODE_INDEXES (nodep);
  if (node_indexp == NULL)
    {
      /* inner does not have any usable index */
      return 0;
    }

  bitset_init (&indexable_terms, info->env);

  /* We're interested in all of the terms so combine 'join_term' and 'sarged_terms' together. */
  if (IS_OUTER_JOIN_TYPE (join_type))
    {
      for (t = bitset_iterate (sarged_terms, &iter); t != -1; t = bitset_next_member (&iter))
    {

      termp = QO_ENV_TERM (QO_NODE_ENV (nodep), t);

      if (QO_TERM_CLASS (termp) == QO_TC_AFTER_JOIN)
        {
          /* exclude after-join term in 'sarged_terms' */
          continue;
        }

      bitset_add (&indexable_terms, t);
    }
    }
  else
    {
      bitset_union (&indexable_terms, sarged_terms);
    }

  /* finally, combine inner plan's 'sarg term' together */
  bitset_union (&indexable_terms, &(QO_NODE_SARGS (nodep)));

  num_only_args = 0;        /* init */

  /* Iterate through the indexes attached to this node and look for ones which are a subset of the terms that we're
   * interested in. For each applicable index, register a plans and compute the cost.
   */
  for (i = 0; i < QO_NI_N (node_indexp); i++)
    {
      /* pointer to QO_NODE_INDEX_ENTRY structure */
      ni_entryp = QO_NI_ENTRY (node_indexp, i);
      /* pointer to QO_INDEX_ENTRY structure */
      index_entryp = (ni_entryp)->head;
      if (index_entryp->force < 0)
    {
      continue;     /* is disabled index; skip and go ahead */
    }

      /* the index has terms which are a subset of the terms that we're interested in */
      if (bitset_intersects (&indexable_terms, &(index_entryp->terms)))
    {

      if (!bitset_intersects (sarged_terms, &(index_entryp->terms)))
        {
          /* there is not join-edge, only inner sargs */
          num_only_args++;
          continue;
        }

      /* generate join index scan using 'ni_entryp' */
      n +=
        qo_generate_join_index_scan (info, join_type, outer_plan, inner, nodep, ni_entryp, &indexable_terms,
                     afj_terms, sarged_terms, pinned_subqueries);
    }
    }

  if (QO_NODE_HINT (nodep) & PT_HINT_USE_IDX)
    {
      /* join hint: force idx-join */
      if (n == 0 && num_only_args)
    {           /* not found 'idx-join' plan */
      /* Re-Iterate */
      for (i = 0; i < QO_NI_N (node_indexp); i++)
        {
          /* pointer to QO_NODE_INDEX_ENTRY structure */
          ni_entryp = QO_NI_ENTRY (node_indexp, i);
          /* pointer to QO_INDEX_ENTRY structure */
          index_entryp = (ni_entryp)->head;
          if (index_entryp->force < 0)
        {
          continue; /* is disabled index; skip and go ahead */
        }

          /* the index has terms which are a subset of the terms that we're intersted in */
          if (bitset_intersects (&indexable_terms, &(index_entryp->terms)))
        {
          if (bitset_intersects (sarged_terms, &(index_entryp->terms)))
            {
              /* there is join-edge; already examined */
              continue;
            }

          /* generate join index scan using 'ni_entryp' */
          n +=
            qo_generate_join_index_scan (info, join_type, outer_plan, inner, nodep, ni_entryp, &indexable_terms,
                         afj_terms, sarged_terms, pinned_subqueries);
        }
        }
    }
    }

  bitset_delset (&indexable_terms);

  return n;
}

/*
 * qo_examine_follow () -
 *   return:
 *   info(in):
 *   path_term(in):
 *   head_info(in):
 *   sarged_terms(in):
 *   pinned_subqueries(in):
 */
static int
qo_examine_follow (QO_INFO * info, QO_TERM * path_term, QO_INFO * head_info, BITSET * sarged_terms,
           BITSET * pinned_subqueries)
{
  PT_NODE *entity_spec;
  /*
   * Examine the feasibility of a follow plan implementation for this
   * edge.  Don't build follow plans if the tail of the path is an rdb
   * proxy; these things *have* to be implemented via joins.
   */
  entity_spec = path_term->tail->entity_spec;
  if (entity_spec->info.spec.flat_entity_list == NULL)
    {
      return 0;
    }

  return qo_check_plan_on_info (info,
                qo_follow_new (info, qo_find_best_plan_on_info (head_info, QO_UNORDERED, 1.0),
                           path_term, sarged_terms, pinned_subqueries));

}

/*
 * qo_compute_projected_segs () -
 *   return:
 *   planner(in):
 *   nodes(in):
 *   terms(in):
 *   projected(in):
 */
static void
qo_compute_projected_segs (QO_PLANNER * planner, BITSET * nodes, BITSET * terms, BITSET * projected)
{
  /*
   * Figure out which of the attributes of the nodes joined by the
   * terms in 'terms' need to be projected out of the join in order to
   * satisfy the needs of higher-level plans.  An attribute will need
   * to preserved if it is to be produced as part of the final result
   * or if it is needed to compute some term that isn't included in
   * 'terms'.
   */

  BITSET required;
  int i;
  QO_TERM *term;

  BITSET_CLEAR (*projected);
  bitset_init (&required, planner->env);
  bitset_assign (&required, &(planner->final_segs));

  for (i = 0; i < (signed) planner->T; i++)
    {
      if (!BITSET_MEMBER (*terms, i))
    {
      term = &planner->term[i];
      bitset_union (&required, &(QO_TERM_SEGS (term)));
    }
    }

  for (i = 0; i < (signed) planner->N; ++i)
    {
      if (BITSET_MEMBER (*nodes, i))
    bitset_union (projected, &(QO_NODE_SEGS (&planner->node[i])));
    }

  bitset_intersect (projected, &required);
  bitset_delset (&required);
}

/*
 * qo_compute_projected_size () -
 *   return:
 *   planner(in):
 *   segset(in):
 */
static int
qo_compute_projected_size (QO_PLANNER * planner, BITSET * segset)
{
  BITSET_ITERATOR si;
  int i;
  int size;

  /*
   * 8 bytes overhead per record.
   */
  size = 8;

  for (i = bitset_iterate (segset, &si); i != -1; i = bitset_next_member (&si))
    {
      /*
       * Four bytes overhead for each field.
       */
      size += qo_seg_width (QO_ENV_SEG (planner->env, i)) + 4;
    }

  return size;
}

/*
 * qo_dump_info () -
 *   return:
 *   info(in):
 *   f(in):
 */
static void
qo_dump_info (QO_INFO * info, FILE * f)
{
  /*
   * Dump the contents of this node for debugging scrutiny.
   */

  int i;

  fputs ("  projected segments: ", f);
  bitset_print (&(info->projected_segs), f);
  fputs ("\n", f);

  fputs ("  best: ", f);
  if (info->best_no_order.nplans > 0)
    {
      qo_dump_planvec (&info->best_no_order, f, -8);
    }
  else
    {
      fputs ("(empty)\n\n", f);
    }

  if (info->planvec)
    {
      for (i = 0; i < (signed) info->planner->EQ; ++i)
    {
      if (info->planvec[i].nplans > 0)
        {
          char buf[20];

          sprintf (buf, "[%d]: ", i);
          fprintf (f, "%8s", buf);
          qo_dump_planvec (&info->planvec[i], f, -8);
        }
    }
    }
}

/*
 * qo_info_stats () -
 *   return:
 *   f(in):
 */
void
qo_info_stats (FILE * f)
{
  fprintf (f, "%d/%d info nodes allocated/deallocated\n", infos_allocated, infos_deallocated);
}

/*
 * qo_alloc_planner () -
 *   return:
 *   env(in):
 */
static QO_PLANNER *
qo_alloc_planner (QO_ENV * env)
{
  int i;
  QO_PLANNER *planner;

  planner = (QO_PLANNER *) malloc (sizeof (QO_PLANNER));
  if (planner == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, sizeof (QO_PLANNER));
      return NULL;
    }

  env->planner = planner;

  planner->env = env;
  planner->node = env->nodes;
  planner->N = env->nnodes;
  planner->E = env->nedges;
  planner->M = 0;       /* later, set in qo_search_planner() */
  if (planner->N < 32)
    {
      planner->node_mask = (unsigned long) ((unsigned int) (1 << planner->N) - 1);
    }
  else
    {
      planner->node_mask = (unsigned long) DB_UINT32_MAX;
    }
  planner->join_unit = 0;
  planner->term = env->terms;
  planner->T = env->nterms;
  planner->segment = env->segs;
  planner->S = env->nsegs;
  planner->eqclass = env->eqclasses;
  planner->EQ = env->neqclasses;
  planner->subqueries = env->subqueries;
  planner->Q = env->nsubqueries;
  planner->P = env->npartitions;
  planner->partition = env->partitions;

  bitset_init (&(planner->final_segs), env);
  bitset_assign (&(planner->final_segs), &(env->final_segs));
  bitset_init (&(planner->all_subqueries), env);
  for (i = 0; i < (signed) planner->Q; ++i)
    bitset_add (&(planner->all_subqueries), i);

  planner->node_info = NULL;
  planner->join_info = NULL;
  planner->best_info = NULL;
  planner->cp_info = NULL;

  planner->info_list = NULL;

  planner->cleanup_needed = true;
  planner->can_apply_limit_card = qo_can_apply_limit_card (env);

  return planner;
}

/*
 * qo_planner_free () -
 *   return:
 *   planner(in):
 */
void
qo_planner_free (QO_PLANNER * planner)
{
  if (planner->cleanup_needed)
    qo_clean_planner (planner);

  qo_plan_del_ref (planner->worst_plan);

  if (planner->info_list)
    {
      QO_INFO *info, *next_info;

      for (info = planner->info_list; info; info = next_info)
    {
      next_info = info->next;   /* save next link */
      qo_free_info (info);
    }
    }

  if (planner->node_info)
    {
      free_and_init (planner->node_info);
    }

  if (planner->join_info)
    {
      free_and_init (planner->join_info);
    }

  if (planner->cp_info)
    {
      free_and_init (planner->cp_info);
    }

  free_and_init (planner);
}

/*
 * qo_dump_planner_info () -
 *   return:
 *   planner(in):
 *   partition(in):
 *   f(in):
 */
static void
qo_dump_planner_info (QO_PLANNER * planner, QO_PARTITION * partition, FILE * f)
{
  int i, M;
  QO_INFO *info;
  int t;
  BITSET_ITERATOR iter;
  const char *prefix;

  fputs ("\nNode info maps:\n", f);
  for (i = 0; i < (signed) planner->N; i++)
    {
      if (BITSET_MEMBER (QO_PARTITION_NODES (partition), i))
    {
      info = planner->node_info[i];
      if (info && !info->detached)
        {
          fprintf (f, "node_info[%d]:\n", i);
          qo_dump_info (info, f);
        }
    }
    }

  if (!bitset_is_empty (&(QO_PARTITION_EDGES (partition))))
    {
      fputs ("\nJoin info maps:\n", f);
      /* in current implementation, join_info[0..2] does not used */
      i = QO_PARTITION_M_OFFSET (partition);
      M = i + QO_JOIN_INFO_SIZE (partition);
      for (i = i + 3; i < M; i++)
    {
      info = planner->join_info[i];
      if (info && !info->detached)
        {
          fputs ("join_info[", f);
          prefix = "";  /* init */
          for (t = bitset_iterate (&(info->nodes), &iter); t != -1; t = bitset_next_member (&iter))
        {
          fprintf (f, "%s%d", prefix, QO_NODE_IDX (QO_ENV_NODE (planner->env, t)));
          prefix = ",";
        }
          fputs ("]:\n", f);
          qo_dump_info (info, f);
        }
    }
    }
}

/*
 * planner_visit_node () -
 *   return:
 *   planner(in):
 *   partition(in):
 *   hint(in):
 *   head_node(in):
 *   tail_node(in):
 *   visited_nodes(in):
 *   visited_rel_nodes(in):
 *   visited_terms(in):
 *   nested_path_nodes(in):
 *   remaining_nodes(in):
 *   remaining_terms(in):
 *   remaining_subqueries(in):
 *   num_path_inner(in):
 */
/*
 * qo_get_term_hit_prob () -
 *
 * hit_prob = min(1, ndv(tail) / ndv(head))
 *
 * Although filters may reduce data, NDV cannot be adjusted
 * accurately. To avoid biased estimation, we conservatively
 * assume the original NDV relationship is maintained.
 */
static void
qo_get_term_hit_prob (QO_TERM * term, QO_INFO * head_info, QO_INFO * tail_info, QO_ENV * env,
              double *out_head_factor, double *out_tail_factor)
{
  const BITSET *term_segs = (const BITSET *) &(term->segments);
  BITSET_ITERATOR seg_iter;
  int seg_idx;
  QO_SEGMENT *head_seg = NULL, *tail_seg = NULL;
  INT64 head_ndv = 1, tail_ndv = 1;

  *out_head_factor = 1.0;
  *out_tail_factor = 1.0;
  if (bitset_cardinality (term_segs) != 2)
    {
      return;
    }

  for (seg_idx = bitset_iterate (term_segs, &seg_iter); seg_idx != -1; seg_idx = bitset_next_member (&seg_iter))
    {
      QO_SEGMENT *seg = QO_ENV_SEG (env, seg_idx);
      QO_NODE *node = QO_SEG_HEAD (seg);
      int node_idx = QO_NODE_IDX (node);

      if (BITSET_MEMBER (head_info->nodes, node_idx))
    {
      head_seg = seg;
    }
      else if (BITSET_MEMBER (tail_info->nodes, node_idx))
    {
      tail_seg = seg;
    }
    }
  if (head_seg == NULL || tail_seg == NULL)
    {
      return;
    }

  if (QO_SEG_INFO (head_seg) != NULL && QO_SEG_INFO (head_seg)->ndv > 0)
    {
      head_ndv = QO_SEG_INFO (head_seg)->ndv;
    }
  else
    {
      return;
    }

  if (QO_SEG_INFO (tail_seg) != NULL && QO_SEG_INFO (tail_seg)->ndv > 0)
    {
      tail_ndv = QO_SEG_INFO (tail_seg)->ndv;
    }
  else
    {
      return;
    }

  *out_head_factor = MIN (1.0, (double) tail_ndv / (double) head_ndv);
  *out_tail_factor = MIN (1.0, (double) head_ndv / (double) tail_ndv);
}

static void
planner_visit_node (QO_PLANNER * planner, QO_PARTITION * partition, PT_HINT_ENUM hint, QO_NODE * head_node,
            QO_NODE * tail_node, BITSET * visited_nodes, BITSET * visited_rel_nodes, BITSET * visited_terms,
            BITSET * nested_path_nodes, BITSET * remaining_nodes, BITSET * remaining_terms,
            BITSET * remaining_subqueries, int num_path_inner)
{
  JOIN_TYPE join_type = NO_JOIN;
  QO_TERM *follow_term = NULL;
  int idx_join_cnt = 0;     /* number of idx-join edges */
  QO_TERM *term;
  QO_NODE *node;
  QO_INFO *head_info = (QO_INFO *) NULL;
  QO_INFO *tail_info = (QO_INFO *) NULL;
  QO_INFO *new_info = (QO_INFO *) NULL;
  QO_PLAN *new_plan, *best_plan;
  int i, j;
  bool check_afj_terms = false;
  bool is_dummy_term = false;
  BITSET_ITERATOR bi, bj;
  BITSET nl_join_terms;     /* nested-loop join terms */
  BITSET sm_join_terms;     /* sort merge join terms */
  BITSET duj_terms;     /* during join terms */
  BITSET afj_terms;     /* after join terms */
  BITSET sarged_terms;
  BITSET info_terms;
  BITSET pinned_subqueries;
  BITSET visited_segs;

  bitset_init (&nl_join_terms, planner->env);
  bitset_init (&sm_join_terms, planner->env);
  bitset_init (&duj_terms, planner->env);
  bitset_init (&afj_terms, planner->env);
  bitset_init (&sarged_terms, planner->env);
  bitset_init (&info_terms, planner->env);
  bitset_init (&pinned_subqueries, planner->env);
  bitset_init (&visited_segs, planner->env);

  if (head_node == NULL)
    {
      goto wrapup;      /* unknown error */
    }

  if (num_path_inner)
    {               /* check for path connected nodes */
      if (bitset_is_empty (nested_path_nodes))
    {           /* not yet assign path connected nodes */
      int found_num, found_idx;

      for (i = bitset_iterate (remaining_terms, &bi); i != -1; i = bitset_next_member (&bi))
        {
          term = QO_ENV_TERM (planner->env, i);
          if (QO_TERM_CLASS (term) == QO_TC_PATH && QO_NODE_IDX (QO_TERM_HEAD (term)) == QO_NODE_IDX (head_node)
          && QO_NODE_IDX (QO_TERM_TAIL (term)) == QO_NODE_IDX (tail_node))
        {
          bitset_add (nested_path_nodes, QO_NODE_IDX (QO_TERM_TAIL (term)));
          /* Traverse tail link */
          do
            {
              found_num = 0;    /* init */
              for (j = bitset_iterate (remaining_terms, &bj); j != -1; j = bitset_next_member (&bj))
            {
              term = QO_ENV_TERM (planner->env, j);
              if (QO_TERM_CLASS (term) == QO_TC_PATH
                  && BITSET_MEMBER (*nested_path_nodes, QO_NODE_IDX (QO_TERM_HEAD (term))))
                {
                  found_idx = QO_NODE_IDX (QO_TERM_TAIL (term));
                  /* found nested path term */
                  if (!BITSET_MEMBER (*nested_path_nodes, found_idx))
                {
                  bitset_add (nested_path_nodes, found_idx);
                  found_num++;
                }
                }
            }
            }
          while (found_num);

          /* Traverse head link reversely */
          do
            {
              found_num = 0;    /* init */
              for (j = bitset_iterate (remaining_terms, &bj); j != -1; j = bitset_next_member (&bj))
            {
              term = QO_ENV_TERM (planner->env, j);
              if (QO_TERM_CLASS (term) == QO_TC_PATH
                  && BITSET_MEMBER (*nested_path_nodes, QO_NODE_IDX (QO_TERM_TAIL (term))))
                {
                  found_idx = QO_NODE_IDX (QO_TERM_HEAD (term));
                  /* found nested path term */
                  if (!BITSET_MEMBER (*nested_path_nodes, found_idx))
                {
                  bitset_add (nested_path_nodes, found_idx);
                  found_num++;
                }
                }
            }
            }
          while (found_num);

          /* exclude already joined nodes */
          bitset_difference (nested_path_nodes, visited_nodes);
          /* remove tail_node from path connected nodes */
          bitset_remove (nested_path_nodes, QO_NODE_IDX (tail_node));

          break;    /* exit for-loop */
        }
        }
    }
      else
    {           /* already assign path connected nodes */
      if (BITSET_MEMBER (*nested_path_nodes, QO_NODE_IDX (tail_node)))
        {
          /* remove tail_node from path connected nodes */
          bitset_remove (nested_path_nodes, QO_NODE_IDX (tail_node));
        }
      else
        {
          goto wrapup;
        }
    }
    }

  /*
   * STEP 1: set head_info, tail_info, visited_nodes, visited_rel_nodes
   */

  /* head_info points to the current prefix */
  if (bitset_cardinality (visited_nodes) == 1)
    {
      /* current prefix has only one node */
      head_info = planner->node_info[QO_NODE_IDX (head_node)];
    }
  else
    {
      /* current prefix has two or more nodes */
      head_info = planner->join_info[QO_INFO_INDEX (QO_PARTITION_M_OFFSET (partition), *visited_rel_nodes)];
      /* currently, do not permit cross join plan. for future work, NEED MORE CONSIDERAION */
      if (head_info == NULL)
    {
      goto wrapup;
    }
    }

  /* tail_info points to the node for the single class being added to the prefix */
  tail_info = planner->node_info[QO_NODE_IDX (tail_node)];

  /* connect tail_node to the prefix */
  bitset_add (visited_nodes, QO_NODE_IDX (tail_node));
  bitset_add (visited_rel_nodes, QO_NODE_REL_IDX (tail_node));
  bitset_remove (remaining_nodes, QO_NODE_IDX (tail_node));

  new_info = planner->join_info[QO_INFO_INDEX (QO_PARTITION_M_OFFSET (partition), *visited_rel_nodes)];

  /* check for already examined join_info */
  if (new_info && new_info->join_unit < planner->join_unit)
    {
      /* at here, not yet visited at this join level; use cache */

      if (new_info->best_no_order.nplans == 0)
    {
      goto wrapup;      /* give up */
    }

      /* STEP 2: set terms for join_info */
      /* set info terms */
      bitset_assign (&info_terms, &(new_info->terms));
      bitset_difference (&info_terms, visited_terms);

      /* extract visited info terms */
      bitset_union (visited_terms, &info_terms);
      bitset_difference (remaining_terms, &info_terms);

      /* STEP 3: set pinned_subqueries */
      {
    QO_SUBQUERY *subq;

    for (i = bitset_iterate (remaining_subqueries, &bi); i != -1; i = bitset_next_member (&bi))
      {
        subq = &planner->subqueries[i];
        if (bitset_subset (visited_nodes, &(subq->nodes)) && bitset_subset (visited_terms, &(subq->terms)))
          {
        bitset_add (&pinned_subqueries, i);
          }
      }

    /* extract pinned subqueries */
    bitset_difference (remaining_subqueries, &pinned_subqueries);
      }

      goto go_ahead_subvisit;
    }

  /* extract terms of the tail_info subplan. this is necessary to ensure that we are aware of any terms that have been
   * sarged by the subplans */
  bitset_union (&info_terms, &(tail_info->terms));

  /* extract visited info terms */
  bitset_union (visited_terms, &info_terms);
  bitset_difference (remaining_terms, &info_terms);

  /* STEP 2: set specific terms for follow and join */

  /* in given partition, collect terms connected to tail_info */
  {
    int retry_cnt, edge_cnt, path_cnt;
    bool found_edge, skip_term;

    /* set visited segs for removing join terms already logically evaluated. */
    for (i = bitset_iterate (visited_terms, &bi); i != -1; i = bitset_next_member (&bi))
      {
    term = QO_ENV_TERM (planner->env, i);

    if (QO_TERM_NOMINAL_SEG (term))
      {
        bitset_union (&visited_segs, &(QO_TERM_SEGS (term)));
      }
      }

    retry_cnt = 0;      /* init */

  retry_join_edge:

    edge_cnt = path_cnt = 0;    /* init */

    for (i = bitset_iterate (remaining_terms, &bi); i != -1; i = bitset_next_member (&bi))
      {

    term = QO_ENV_TERM (planner->env, i);

    /* check term nodes */
    if (!bitset_subset (visited_nodes, &(QO_TERM_NODES (term))))
      {
        continue;
      }

    /* check location for outer join */
    if (QO_TERM_CLASS (term) == QO_TC_DURING_JOIN)
      {
        QO_ASSERT (planner->env, QO_ON_COND_TERM (term));

        for (j = bitset_iterate (visited_nodes, &bj); j != -1; j = bitset_next_member (&bj))
          {
        node = QO_ENV_NODE (planner->env, j);
        if (QO_NODE_LOCATION (node) == QO_TERM_LOCATION (term))
          {
            break;
          }
          }

        if (j == -1)
          {         /* out of location */
        continue;
          }
      }

    found_edge = false; /* init */
    skip_term = false;

    if (BITSET_MEMBER (QO_TERM_NODES (term), QO_NODE_IDX (tail_node)))
      {
        if (QO_TERM_CLASS (term) == QO_TC_PATH)
          {
        if (retry_cnt == 0)
          {     /* is the first stage */
            /* need to check the direction; head -> tail */
            if (QO_NODE_IDX (QO_TERM_TAIL (term)) == QO_NODE_IDX (tail_node))
              {
            found_edge = true;
              }
            else
              {
            /* save path for the retry stage */
            path_cnt++;
              }
          }
        else
          {
            /* at retry stage; there is only path edge so, need not to check the direction */
            found_edge = true;
          }
          }
        else if (QO_IS_EDGE_TERM (term))
          {
        found_edge = true;
          }
      }

    if (found_edge == true)
      {
        /* found edge */
        edge_cnt++;

        /* set join type */
        if (join_type == NO_JOIN || is_dummy_term)
          {
        /* the first time except dummy term */
        join_type = QO_TERM_JOIN_TYPE (term);
        is_dummy_term = QO_TERM_CLASS (term) == QO_TC_DUMMY_JOIN ? true : false;
          }
        else if (QO_TERM_CLASS (term) == QO_TC_DUMMY_JOIN)
          {
        /* The dummy join term is excluded from the outer join check. */
          }
        else
          {         /* already assigned */
        if (IS_OUTER_JOIN_TYPE (join_type))
          {
            /* outer join type must be the same */
            if (IS_OUTER_JOIN_TYPE (QO_TERM_JOIN_TYPE (term)))
              {
            QO_ASSERT (planner->env, join_type == QO_TERM_JOIN_TYPE (term));
              }
          }
        else
          {
            if (IS_OUTER_JOIN_TYPE (QO_TERM_JOIN_TYPE (term)))
              {
            /* replace to the outer join type */
            join_type = QO_TERM_JOIN_TYPE (term);
              }
          }
          }

        switch (QO_TERM_CLASS (term))
          {
          case QO_TC_DUMMY_JOIN:    /* is always true dummy join term */
        /* check for idx-join */
        if (QO_TERM_CAN_USE_INDEX (term))
          {
            idx_join_cnt++;
          }
        break;

          case QO_TC_PATH:
        if (follow_term == NULL)
          {     /* get the first PATH term idx */
            follow_term = term;
            /* for path-term, if join type is not outer join, we can use idx-join, nl-join */
            if (QO_TERM_JOIN_TYPE (follow_term) == JOIN_INNER)
              {
            /* check for idx-join */
            if (QO_TERM_CAN_USE_INDEX (term))
              {
                idx_join_cnt++;
              }
            bitset_add (&nl_join_terms, i);
              }
            /* check for m-join */
            if (QO_TERM_IS_FLAGED (term, QO_TERM_MERGEABLE_EDGE))
              {
            bitset_add (&sm_join_terms, i);
              }
          }
        else
          {     /* found another PATH term */
            /* unknown error */
            QO_ASSERT (planner->env, UNEXPECTED_CASE);
          }
        break;

          case QO_TC_JOIN:
        /* check for term which is already logically evaluated. */
        if (QO_TERM_NOMINAL_SEG (term))
          {
            if (qo_check_skip_term (planner->env, visited_segs, term, visited_terms, &info_terms))
              {
            skip_term = true;
              }
            else
              {
            bitset_union (&visited_segs, &(QO_TERM_SEGS (term)));
              }
          }

        if (!skip_term)
          {
            /* check for idx-join */
            if (QO_TERM_CAN_USE_INDEX (term))
              {
            idx_join_cnt++;
              }
            bitset_add (&nl_join_terms, i);
            /* check for m-join */
            if (QO_TERM_IS_FLAGED (term, QO_TERM_MERGEABLE_EDGE))
              {
            bitset_add (&sm_join_terms, i);
              }
            else
              {     /* non-eq edge */
            if (IS_OUTER_JOIN_TYPE (join_type) && QO_ON_COND_TERM (term))
              { /* ON clause */
                bitset_add (&duj_terms, i); /* need for m-join */
              }
              }
          }
        break;

          case QO_TC_DEP_LINK:
          case QO_TC_DEP_JOIN:
        bitset_add (&nl_join_terms, i);
        break;

          default:
        QO_ASSERT (planner->env, UNEXPECTED_CASE);
        break;
          }
      }
    else
      {
        /* does not edge */

        if (QO_TERM_CLASS (term) == QO_TC_DURING_JOIN)
          {
        bitset_add (&duj_terms, i);
          }
        else if (QO_TERM_CLASS (term) == QO_TC_AFTER_JOIN)
          {
        check_afj_terms = true;

        /* If visited_nodes is the same as partition's nodes, then we have successfully generated one of the
         * graph permutations(i.e., we have considered every one of the nodes). only include after-join term
         * for this plan.
         */
        if (!bitset_is_equivalent (visited_nodes, &(QO_PARTITION_NODES (partition))))
          {
            continue;
          }
        bitset_add (&afj_terms, i);
          }
        else if (QO_TERM_CLASS (term) == QO_TC_OTHER)
          {
        if (IS_OUTER_JOIN_TYPE (join_type) && QO_ON_COND_TERM (term))
          {     /* ON clause */
            bitset_add (&duj_terms, i);
          }
          }
      }

    bitset_add (&info_terms, i);    /* add to info term */

    /* skip always true dummy join term and do not evaluate */
    if (!skip_term && QO_TERM_CLASS (term) != QO_TC_DUMMY_JOIN)
      {
        bitset_add (&sarged_terms, i);  /* add to sarged term */
      }
      }

    /* currently, do not permit cross join plan. for future work, NEED MORE CONSIDERAION */
    if (edge_cnt == 0)
      {
    if (retry_cnt == 0)
      {         /* is the first stage */
        if (path_cnt > 0)
          {
        /* there is only path edge and the direction is reversed */
        retry_cnt++;
        goto retry_join_edge;
          }
      }
    goto wrapup;
      }
  }

#if 1               /* TO NOT DELETE ME - very special case for Object fetch plan */
  /* re-check for after join term; is depence to Object fetch plan */
  if (check_afj_terms && bitset_is_empty (&afj_terms))
    {
      BITSET path_nodes;

      bitset_init (&path_nodes, planner->env);

      for (i = bitset_iterate (remaining_terms, &bi); i != -1; i = bitset_next_member (&bi))
    {
      term = QO_ENV_TERM (planner->env, i);

      if (QO_TERM_CLASS (term) == QO_TC_PATH)
        {
          bitset_add (&path_nodes, QO_NODE_IDX (QO_TERM_TAIL (term)));
        }
    }

      /* there is only path joined nodes. So, should apply after join terms at here. */
      if (bitset_subset (&path_nodes, remaining_nodes))
    {
      for (i = bitset_iterate (remaining_terms, &bi); i != -1; i = bitset_next_member (&bi))
        {
          term = QO_ENV_TERM (planner->env, i);

          if (QO_TERM_CLASS (term) == QO_TC_AFTER_JOIN)
        {
          bitset_add (&afj_terms, i);
          bitset_add (&info_terms, i);  /* add to info term */
          bitset_add (&sarged_terms, i);    /* add to sarged term */
        }
        }
    }

      bitset_delset (&path_nodes);
    }
#endif

  /* extract visited info terms */
  bitset_union (visited_terms, &info_terms);
  bitset_difference (remaining_terms, &info_terms);

  /* STEP 3: set pinned_subqueries */

  /* Find out if we can pin any of the remaining subqueries.  A subquery is eligible to be pinned here if all of the
   * nodes on which it depends are covered here.  However, it mustn't be pinned here if it is part of a term that
   * hasn't been pinned yet.  Doing so risks improperly pushing a subquery plan down through a merge join during XASL
   * generation, which results in an incorrect plan (the subquery has to be evaluated during the merge, rather than
   * during the scan that feeds the merge).
   */
  {
    QO_SUBQUERY *subq;

    for (i = bitset_iterate (remaining_subqueries, &bi); i != -1; i = bitset_next_member (&bi))
      {
    subq = &planner->subqueries[i];
    if (bitset_subset (visited_nodes, &(subq->nodes)) && bitset_subset (visited_terms, &(subq->terms)))
      {
        bitset_add (&pinned_subqueries, i);
      }
      }

    /* extract pinned subqueries */
    bitset_difference (remaining_subqueries, &pinned_subqueries);
  }

  /* STEP 4: set joined info */

  if (new_info == NULL)
    {

      double selectivity, cardinality, total_rows, head_hit_prob, tail_hit_prob;
      BITSET eqclasses;

      bitset_init (&eqclasses, planner->env);


      selectivity = 1.0;    /* init */

      cardinality = head_info->cardinality * tail_info->cardinality;
      total_rows = head_info->total_rows * tail_info->total_rows;
      head_hit_prob = 1.0;
      tail_hit_prob = 1.0;
      if (IS_OUTER_JOIN_TYPE (join_type))
    {
      /* set lower bound of outer join result */
      if (join_type == JOIN_RIGHT)
        {
          cardinality = MAX (cardinality, tail_info->cardinality);
        }
      else
        {
          cardinality = MAX (cardinality, head_info->cardinality);
        }
    }

      if (cardinality != 0)
    {           /* not empty */
      cardinality = MAX (1.0, cardinality);
      for (i = bitset_iterate (&sarged_terms, &bi); i != -1; i = bitset_next_member (&bi))
        {
          term = &planner->term[i];
          if (QO_IS_PATH_TERM (term) && QO_TERM_JOIN_TYPE (term) != JOIN_INNER)
        {
          /* single-fetch */
          cardinality = head_info->cardinality;
          if (cardinality != 0)
            {       /* not empty */
              cardinality = MAX (1.0, cardinality);
            }
        }
          else
        {
          selectivity *= QO_TERM_SELECTIVITY (term);
          selectivity = MAX (1.0 / MAX (cardinality, 1.0), selectivity);

          double head_factor, tail_factor;
          qo_get_term_hit_prob (term, head_info, tail_info, planner->env, &head_factor, &tail_factor);
          head_hit_prob *= head_factor;
          tail_hit_prob *= tail_factor;
        }
        }
      cardinality *= selectivity;
      cardinality = MAX (1.0, cardinality);
      total_rows *= selectivity;
      total_rows = MAX (1.0, total_rows);

      if (IS_OUTER_JOIN_TYPE (join_type) && bitset_is_empty (&afj_terms))
        {
          /* set lower bound of outer join result */
          if (join_type == JOIN_RIGHT)
        {
          cardinality = MAX (cardinality, tail_info->cardinality);
        }
          else
        {
          cardinality = MAX (cardinality, head_info->cardinality);
        }
        }
    }

      bitset_assign (&eqclasses, &(head_info->eqclasses));
      bitset_union (&eqclasses, &(tail_info->eqclasses));

      head_info->hit_prob = head_hit_prob;
      tail_info->hit_prob = tail_hit_prob;
      if (IS_OUTER_JOIN_TYPE (join_type))
    {
      /* set lower bound of outer join result */
      if (join_type == JOIN_RIGHT)
        {
          tail_info->hit_prob = 1.0;
        }
      else
        {
          head_info->hit_prob = 1.0;
        }
    }

      new_info = planner->join_info[QO_INFO_INDEX (QO_PARTITION_M_OFFSET (partition), *visited_rel_nodes)] =
    qo_alloc_info (planner, visited_nodes, visited_terms, &eqclasses, cardinality, total_rows);

      bitset_delset (&eqclasses);
    }

  /* STEP 5: do EXAMINE follow, join */

  {
    int kept = 0;
    int idx_join_plan_n = 0;

    /* for path-term, if join order is correct, we can use follow. */
    if (follow_term && (QO_NODE_IDX (QO_TERM_TAIL (follow_term)) == QO_NODE_IDX (tail_node)))
      {
    /* STEP 5-1: examine follow */
    kept += qo_examine_follow (new_info, follow_term, head_info, &sarged_terms, &pinned_subqueries);
      }

    if (follow_term && join_type != JOIN_INNER && QO_NODE_IDX (QO_TERM_TAIL (follow_term)) != QO_NODE_IDX (tail_node))
      {
    /* if there is a path-term whose outer join order is not correct, we can not use idx-join, nl-join, m-join */
    ;
      }
    else
      {
#if 1               /* CORRELATED_INDEX */
    /* STEP 5-2: examine idx-join */
    if (idx_join_cnt)
      {
        idx_join_plan_n =
          qo_examine_idx_join (new_info, join_type, head_info, tail_info, &afj_terms, &sarged_terms,
                   &pinned_subqueries);
        kept += idx_join_plan_n;
      }
#endif /* CORRELATED_INDEX */

    /* STEP 5-3: examine nl-join */
    /* sm_join_terms is a mergeable term for SM join. In hash list scan, mergeable term is used as hash term. */
    /* The mergeable term and the hash term have the same characteristics. */
    /* If the characteristics for mergeable terms are changed, the logic for hash terms should be separated. */
    /* mergeable term : equi-term, symmetrical term, e.g. TBL1.a = TBL2.a, function(TAB1.a) = function(TAB2.a) */
    kept +=
      qo_examine_nl_join (new_info, join_type, head_info, tail_info, &nl_join_terms, &duj_terms, &afj_terms,
                  &sarged_terms, &pinned_subqueries, idx_join_plan_n, &sm_join_terms);

#if 1               /* MERGE_JOINS */
    /* STEP 5-4: examine merge-join */
    if (!bitset_is_empty (&sm_join_terms))
      {
        kept +=
          qo_examine_merge_join (new_info, join_type, head_info, tail_info, &sm_join_terms, &duj_terms, &afj_terms,
                     &sarged_terms, &pinned_subqueries);
      }
#endif /* MERGE_JOINS */

#if 1               /* HASH_JOINS */
    /* STEP 5-5: examine hash-join */
    if (!bitset_is_empty (&sm_join_terms))
      {
        kept +=
          qo_examine_hash_join (new_info, join_type, head_info, tail_info, &sm_join_terms, &duj_terms, &afj_terms,
                    &sarged_terms, &pinned_subqueries);
      }
#endif /* HASH_JOINS */
      }

    /* At this point, kept indicates the number of worthwhile plans generated by examine_joins (i.e., plans that where
     * cheaper than some previous equivalent plan).  If that number is 0, then there is no point in continuing this
     * particular branch of permutations: we've already generated all of the suffixes once before, and with a better
     * prefix to boot.  There is no possibility of finding a better plan with this prefix.
     */
    if (!kept)
      {
    goto wrapup;
      }
  }

  /* STEP 7: go on sub permutations */

go_ahead_subvisit:

  /* If visited_nodes' cardinality is the same as join_unit, then we have successfully generated one of the graph
   * permutations (i.e., we have considered every one of the nodes). If not, we need to try to recursively generate
   * suffixes.
   */
  if (bitset_cardinality (visited_nodes) >= planner->join_unit)
    {
      /* If this is the info node that corresponds to the final plan (i.e., every node in the partition is covered by
       * the plans at this node), *AND* we have something to put in it, then record that fact in the planner.  This
       * permits more aggressive pruning, since we can immediately discard any plan (or subplan) that is no better than
       * the best known plan for the entire partition.
       */
      if (!planner->best_info)
    {
      planner->best_info = new_info;
      goto wrapup;
    }

      new_plan = qo_find_best_plan_on_info (new_info, QO_UNORDERED, 1.0);
      best_plan = qo_find_best_plan_on_info (planner->best_info, QO_UNORDERED, 1.0);
      if (best_plan == NULL || new_plan == NULL)
    {           /* unknown error */
      goto wrapup;      /* give up */
    }
      QO_PLAN_COMPARE_RESULT cmp = qo_plan_cmp (best_plan, new_plan);

      if (cmp == PLAN_COMP_GT)
    {
      planner->best_info = new_info;
    }
    }
  else
    {
      for (i = bitset_iterate (remaining_nodes, &bi); i != -1; i = bitset_next_member (&bi))
    {
      node = QO_ENV_NODE (planner->env, i);

      /* node dependency check; */
      if (!bitset_subset (visited_nodes, &(QO_NODE_DEP_SET (node))))
        {
          /* node represents dependent tables, so there is no way this combination can work in isolation.  Give up
           * so we can try some other combinations.
           */
          continue;
        }
      if (!bitset_subset (visited_nodes, &(QO_NODE_OUTER_DEP_SET (node))))
        {
          /* All previous nodes participating in outer join spec should be joined before. QO_NODE_OUTER_DEP_SET()
           * represents all previous nodes which are dependents on the node.
           */
          continue;
        }

      /* now, set node as next tail node, do recursion */
      (void) planner_visit_node (planner, partition, hint, tail_node,   /* next head node */
                     node,  /* next tail node */
                     visited_nodes, visited_rel_nodes, visited_terms, nested_path_nodes,
                     remaining_nodes, remaining_terms, remaining_subqueries, num_path_inner);

      /* join hint: force join left-to-right */
      if (hint & PT_HINT_ORDERED)
        {
          break;
        }
    }
    }

wrapup:

  /* recover to original */

  bitset_remove (visited_nodes, QO_NODE_IDX (tail_node));
  bitset_remove (visited_rel_nodes, QO_NODE_REL_IDX (tail_node));
  bitset_add (remaining_nodes, QO_NODE_IDX (tail_node));

  bitset_difference (visited_terms, &info_terms);
  bitset_union (remaining_terms, &info_terms);

  bitset_union (remaining_subqueries, &pinned_subqueries);

  /* free alloced */
  bitset_delset (&nl_join_terms);
  bitset_delset (&sm_join_terms);
  bitset_delset (&duj_terms);
  bitset_delset (&afj_terms);
  bitset_delset (&sarged_terms);
  bitset_delset (&info_terms);
  bitset_delset (&pinned_subqueries);
  bitset_delset (&visited_segs);
}

/*
 * planner_nodeset_join_cost () -
 *   return:
 *   planner(in):
 *   nodeset(in):
 */
static double
planner_nodeset_join_cost (QO_PLANNER * planner, BITSET * nodeset)
{
  int i;
  BITSET_ITERATOR bi;
  QO_NODE *node;
  QO_INFO *info;
  QO_PLAN *plan, *subplan;
  double total_cost, objects, result_size, pages;

  total_cost = 0.0;     /* init */

  for (i = bitset_iterate (nodeset, &bi); i != -1; i = bitset_next_member (&bi))
    {

      node = QO_ENV_NODE (planner->env, i);
      info = planner->node_info[QO_NODE_IDX (node)];

      plan = qo_find_best_plan_on_info (info, QO_UNORDERED, 1.0);

      if (plan == NULL)
    {           /* something wrong */
      continue;     /* give up */
    }

      objects = (plan->info)->cardinality;
      result_size = objects * (double) (plan->info)->projected_size;
      pages = result_size / (double) IO_PAGESIZE;
      pages = MAX (1.0, pages);

      /* apply join cost; add to the total cost */
      total_cost += pages;

      /* TODO: Consider the priority of hints */
      if (QO_NODE_HINT (node) & (PT_HINT_USE_IDX | PT_HINT_USE_NL))
    {
      /* join hint: force idx-join, nl-join */
    }
      else if (!(QO_NODE_HINT (node) & PT_HINT_NO_USE_HASH) && (QO_NODE_HINT (node) & PT_HINT_USE_HASH))
    {
      /* join hint: force hash-join */
    }
      else if (QO_NODE_HINT (node) & PT_HINT_USE_MERGE)
    {
      /* join hint: force m-join */
      if (plan->plan_type == QO_PLANTYPE_SORT)
        {
          subplan = plan->plan_un.sort.subplan;
        }
      else
        {
          subplan = plan;
        }

      objects = (subplan->info)->cardinality;
      result_size = objects * (double) (subplan->info)->projected_size;
      pages = result_size / (double) IO_PAGESIZE;
      pages = MAX (1.0, pages);

      /* apply merge cost; add to the total cost */
      if (plan->plan_type == QO_PLANTYPE_SORT)
        {
          /* already apply inner cost: apply only outer cost */
          total_cost += pages;
        }
      else
        {
          /* do guessing: apply outer, inner cost */
          total_cost += pages * 2.0;
        }
    }
      else
    {
      /* fall through */
    }
    }

  return total_cost;
}

/*
 * planner_permutate () -
 *   return:
 *   planner(in):
 *   partition(in):
 *   hint(in):
 *   prev_head_node(in):
 *   visited_nodes(in):
 *   visited_rel_nodes(in):
 *   visited_terms(in):
 *   first_nodes(in):
 *   nested_path_nodes(in):
 *   remaining_nodes(in):
 *   remaining_terms(in):
 *   remaining_subqueries(in):
 *   num_path_inner(in):
 *   node_idxp(in):
 */
static void
planner_permutate (QO_PLANNER * planner, QO_PARTITION * partition, PT_HINT_ENUM hint, QO_NODE * prev_head_node,
           BITSET * visited_nodes, BITSET * visited_rel_nodes, BITSET * visited_terms,
           BITSET * nested_path_nodes, BITSET * remaining_nodes, BITSET * remaining_terms,
           BITSET * remaining_subqueries, int num_path_inner, int *node_idxp)
{
  int i, j;
  BITSET_ITERATOR bi, bj;
  QO_INFO *head_info, *best_info;
  QO_NODE *head_node, *tail_node;
  QO_PLAN *best_plan;
  double best_cost, prev_best_cost;
  BITSET rest_nodes;

  bitset_init (&rest_nodes, planner->env);

  planner->best_info = NULL;    /* init */

  prev_best_cost = -1.0;    /* init */

  /* Now perform the actual search.  Entries in join_info will gradually be filled and refined within the calls to
   * examine_xxx_join(). When we finish, planner->best_info will hold information about the best ways discovered to
   * perform the entire join.
   */
  for (i = bitset_iterate (remaining_nodes, &bi); i != -1; i = bitset_next_member (&bi))
    {

      head_node = QO_ENV_NODE (planner->env, i);

      /* head node dependency check; */
      if (!bitset_subset (visited_nodes, &(QO_NODE_DEP_SET (head_node))))
    {
      /* head node represents dependent tables, so there is no way this combination can work in isolation.  Give up
       * so we can try some other combinations.
       */
      continue;
    }
      if (!bitset_subset (visited_nodes, &(QO_NODE_OUTER_DEP_SET (head_node))))
    {
      /* All previous nodes participating in outer join spec should be joined before. QO_NODE_OUTER_DEP_SET()
       * represents all previous nodes which are dependents on the node.
       */
      continue;
    }

      if (bitset_is_empty (visited_nodes))
    {           /* not found outermost nodes */

      head_info = planner->node_info[QO_NODE_IDX (head_node)];

      /* init */
      bitset_add (visited_nodes, QO_NODE_IDX (head_node));
      bitset_add (visited_rel_nodes, QO_NODE_REL_IDX (head_node));
      bitset_remove (remaining_nodes, QO_NODE_IDX (head_node));

      bitset_union (visited_terms, &(head_info->terms));
      bitset_difference (remaining_terms, &(head_info->terms));

      for (j = bitset_iterate (remaining_nodes, &bj); j != -1; j = bitset_next_member (&bj))
        {

          tail_node = QO_ENV_NODE (planner->env, j);

          /* tail node dependency check; */
          if (!bitset_subset (visited_nodes, &(QO_NODE_DEP_SET (tail_node))))
        {
          continue;
        }
          if (!bitset_subset (visited_nodes, &(QO_NODE_OUTER_DEP_SET (tail_node))))
        {
          continue;
        }

          BITSET_CLEAR (*nested_path_nodes);

          (void) planner_visit_node (planner, partition, hint, head_node, tail_node, visited_nodes,
                     visited_rel_nodes, visited_terms, nested_path_nodes, remaining_nodes,
                     remaining_terms, remaining_subqueries, num_path_inner);

          /* join hint: force join left-to-right */
          if (hint & PT_HINT_ORDERED)
        {
          break;
        }
        }

      /* recover to original */
      BITSET_CLEAR (*visited_nodes);
      BITSET_CLEAR (*visited_rel_nodes);
      bitset_add (remaining_nodes, QO_NODE_IDX (head_node));

      bitset_difference (visited_terms, &(head_info->terms));
      bitset_union (remaining_terms, &(head_info->terms));

    }
      else
    {           /* found some outermost nodes */

      BITSET_CLEAR (*nested_path_nodes);

      (void) planner_visit_node (planner, partition, hint, prev_head_node, head_node,   /* next tail node */
                     visited_nodes, visited_rel_nodes, visited_terms, nested_path_nodes,
                     remaining_nodes, remaining_terms, remaining_subqueries, num_path_inner);
    }

      if (node_idxp)
    {           /* is partial node visit */
      best_info = planner->best_info;
      if (best_info == NULL)
        {           /* not found best plan */
          continue;     /* skip and go ahead */
        }

      best_plan = qo_find_best_plan_on_info (best_info, QO_UNORDERED, 1.0);

      if (best_plan == NULL)
        {           /* unknown error */
          break;        /* give up */
        }

      /* set best plan's cost */
      best_cost =
        best_plan->fixed_cpu_cost + best_plan->fixed_io_cost + best_plan->variable_cpu_cost +
        best_plan->variable_io_cost;

      /* apply rest nodes's cost */
      bitset_assign (&rest_nodes, remaining_nodes);
      bitset_difference (&rest_nodes, &(best_info->nodes));
      best_cost += planner_nodeset_join_cost (planner, &rest_nodes);

      if (prev_best_cost == -1.0    /* the first time */
          || best_cost < prev_best_cost)
        {
          *node_idxp = QO_NODE_IDX (head_node);
          prev_best_cost = best_cost;   /* found new best */
        }

      planner->best_info = NULL;    /* clear */
    }

      /* join hint: force join left-to-right */
      if (hint & PT_HINT_ORDERED)
    {
      break;
    }
    }

  if (node_idxp)
    {               /* is partial node visit */
      planner->best_info = NULL;    /* clear */
    }

  bitset_delset (&rest_nodes);

  return;
}

/*
 * qo_planner_search () -
 *   return:
 *   env(in):
 */
QO_PLAN *
qo_planner_search (QO_ENV * env)
{
  QO_PLANNER *planner;
  QO_PLAN *plan;

  planner = NULL;
  plan = NULL;

  planner = qo_alloc_planner (env);
  if (planner == NULL)
    {
      return NULL;
    }

  qo_info_nodes_init (env);
  qo_plans_init (env);
  plan = qo_search_planner (planner);
  qo_clean_planner (planner);

  return plan;
}

/*
 * qo_generate_join_index_scan () -
 *   return:
 *   infop(in):
 *   join_type(in):
 *   outer_plan(in):
 *   inner(in):
 *   nodep(in):
 *   ni_entryp(in):
 *   indexable_terms(in):
 *   afj_terms(in):
 *   sarged_terms(in):
 *   pinned_subqueries(in):
 */
static int
qo_generate_join_index_scan (QO_INFO * infop, JOIN_TYPE join_type, QO_PLAN * outer_plan, QO_INFO * inner,
                 QO_NODE * nodep, QO_NODE_INDEX_ENTRY * ni_entryp, BITSET * indexable_terms,
                 BITSET * afj_terms, BITSET * sarged_terms, BITSET * pinned_subqueries)
{
  QO_ENV *env;
  QO_INDEX_ENTRY *index_entryp;
  BITSET_ITERATOR iter;
  QO_TERM *termp;
  QO_PLAN *inner_plan;
  int i, t, last_t, j, n, seg, rangelist_term_idx;
  bool found_rangelist;
  BITSET range_terms;
  BITSET empty_terms;
  BITSET remaining_terms;

  if (nodep != NULL && QO_NODE_IS_CLASS_HIERARCHY (nodep))
    {
      /* Class hierarchies are split into scan blocks which cannot be used for index joins. However, if the class
       * hierarchy is a partitioning hierarchy, we can use an index join for inner joins
       */
      if (!QO_NODE_IS_CLASS_PARTITIONED (nodep))
    {
      return 0;
    }
      else if (join_type != JOIN_INNER)
    {
      return 0;
    }
    }
  env = infop->env;

  bitset_init (&range_terms, env);
  bitset_init (&empty_terms, env);
  bitset_init (&remaining_terms, env);

  bitset_assign (&remaining_terms, sarged_terms);

  /* pointer to QO_INDEX_ENTRY structure */
  index_entryp = (ni_entryp)->head;
  if (index_entryp->force < 0)
    {
      assert (false);
      return 0;
    }

  found_rangelist = false;
  rangelist_term_idx = -1;
  for (i = 0; i < index_entryp->nsegs; i++)
    {
      seg = index_entryp->seg_idxs[i];
      if (seg == -1)
    {
      break;
    }
      n = 0;
      last_t = -1;
      for (t = bitset_iterate (indexable_terms, &iter); t != -1; t = bitset_next_member (&iter))
    {
      termp = QO_ENV_TERM (env, t);

      /* check for always true dummy join term */
      if (QO_TERM_CLASS (termp) == QO_TC_DUMMY_JOIN)
        {
          /* skip out from all terms */
          bitset_remove (&remaining_terms, t);
          continue;     /* do not add to range_terms */
        }

      if (QO_TERM_IS_FLAGED (termp, QO_TERM_MULTI_COLL_PRED))
        {
          /* case of multi column term ex) (a,b) in ... */
          if (found_rangelist == true && QO_TERM_IDX (termp) != rangelist_term_idx)
        {
          break;    /* already found. give up */
        }
          for (j = 0; j < termp->multi_col_cnt; j++)
        {
          if (QO_TERM_IS_FLAGED (termp, QO_TERM_RANGELIST))
            {
              found_rangelist = true;
              rangelist_term_idx = QO_TERM_IDX (termp);
            }
          /* found term */
          if (termp->multi_col_segs[j] == seg && BITSET_MEMBER (index_entryp->seg_equal_terms[i], t))
            /* multi col term is only indexable when term's class is TC_SARG. so can use seg_equal_terms */
            {
              /* save last found term */
              last_t = t;
              /* found EQ term */
              if (QO_TERM_IS_FLAGED (termp, QO_TERM_EQUAL_OP))
            {
              bitset_add (&range_terms, t);
              bitset_add (&(index_entryp->multi_col_range_segs), seg);
              n++;
            }
            }
        }
        }
      else
        {
          for (j = 0; j < termp->can_use_index; j++)
        {
          /* found term */
          if (QO_SEG_IDX (termp->index_seg[j]) == seg)
            {
              /* save last found term */
              last_t = t;

              /* found EQ term */
              if (QO_TERM_IS_FLAGED (termp, QO_TERM_EQUAL_OP))
            {
              if (QO_TERM_IS_FLAGED (termp, QO_TERM_RANGELIST))
                {
                  if (found_rangelist == true)
                {
                  break;    /* already found. give up */
                }

                  /* is the first time */
                  found_rangelist = true;
                  rangelist_term_idx = QO_TERM_IDX (termp);
                }

              bitset_add (&range_terms, t);
              n++;
            }

              break;
            }
        }
        }

      /* found EQ term. exit term-iteration loop */
      if (n)
        {
          break;
        }
    }

      /* not found EQ term. exit seg-iteration loop */
      if (n == 0)
    {
      /* found term. add last non-EQ term */
      if (last_t != -1)
        {
          if (found_rangelist == true)
        {
          termp = QO_ENV_TERM (env, last_t);
          if (QO_TERM_IS_FLAGED (termp, QO_TERM_RANGELIST))
            {
              break;    /* give up */
            }
        }
          bitset_add (&range_terms, last_t);
        }
      break;
    }
    }

  n = 0;
  if (!bitset_is_empty (&range_terms))
    {
      inner_plan = qo_index_scan_new (inner, nodep, ni_entryp, QO_SCANMETHOD_INDEX_SCAN, &range_terms, indexable_terms);

      if (inner_plan)
    {
      /* now, key-filter is assigned; exclude key-range, key-filter terms from remaining terms */
      bitset_difference (&remaining_terms, &range_terms);
      bitset_difference (&remaining_terms, &(inner_plan->plan_un.scan.kf_terms));

      n =
        qo_check_plan_on_info (infop,
                   qo_join_new (infop, join_type, QO_JOINMETHOD_IDX_JOIN, outer_plan, inner_plan,
                        &empty_terms, &empty_terms, afj_terms, &remaining_terms,
                        pinned_subqueries, &empty_terms));
    }
    }

  bitset_delset (&remaining_terms);
  bitset_delset (&empty_terms);
  bitset_delset (&range_terms);

  return n;
}

/*
 * qo_is_seq_scan ()
 *   return: true/false
 *   plan(in):
 */
bool
qo_is_seq_scan (QO_PLAN * plan)
{
  if (plan && plan->plan_type == QO_PLANTYPE_SCAN && plan->plan_un.scan.scan_method == QO_SCANMETHOD_SEQ_SCAN)
    {
      return true;
    }

  return false;
}

/*
 * qo_generate_seq_scan () - Generates sequential scan plan
 *   return: nothing
 *   infop(in): pointer to QO_INFO (environment info node which holds plans)
 *   nodep(in): pointer to QO_NODE (node in the join graph)
 */
static void
qo_generate_seq_scan (QO_INFO * infop, QO_NODE * nodep)
{
  int n;
  QO_PLAN *planp;
  bool plan_created = false;

  planp = qo_seq_scan_new (infop, nodep);

  n = qo_check_plan_on_info (infop, planp);
  if (n)
    {
      plan_created = true;
    }
}

/*
 * qo_is_iscan ()
 *   return: true/false
 *   plan(in):
 */
bool
qo_is_iscan (QO_PLAN * plan)
{
  if (plan && plan->plan_type == QO_PLANTYPE_SCAN
      && (plan->plan_un.scan.scan_method == QO_SCANMETHOD_INDEX_SCAN
      || plan->plan_un.scan.scan_method == QO_SCANMETHOD_INDEX_SCAN_INSPECT))
    {
      return true;
    }

  return false;
}

/*
 * qo_generate_index_scan () - With index information, generates index scan plan
 *   return: num of index scan plans
 *   infop(in): pointer to QO_INFO (environment info node which holds plans)
 *   nodep(in): pointer to QO_NODE (node in the join graph)
 *   ni_entryp(in): pointer to QO_NODE_INDEX_ENTRY (node index entry)
 *   nsegs(in):
 */
static int
qo_generate_index_scan (QO_INFO * infop, QO_NODE * nodep, QO_NODE_INDEX_ENTRY * ni_entryp, int nsegs)
{
  QO_INDEX_ENTRY *index_entryp;
  BITSET_ITERATOR iter;
  int i, t, n, normal_index_plan_n = 0;
  QO_PLAN *planp;
  BITSET range_terms;
  BITSET seg_other_terms;
  int start_column = 0;

  bitset_init (&range_terms, infop->env);
  bitset_init (&seg_other_terms, infop->env);

  /* pointer to QO_INDEX_ENTRY structure */
  index_entryp = (ni_entryp)->head;
  if (index_entryp->force < 0)
    {
      assert (false);
      return 0;
    }

  if (QO_ENTRY_MULTI_COL (index_entryp))
    {
      assert (nsegs >= 1);
      ;             /* nop */
    }
  else
    {
      assert (nsegs == 1);
      assert (index_entryp->is_iss_candidate == 0);
      assert (!(index_entryp->ils_prefix_len > 0));
    }

  start_column = index_entryp->is_iss_candidate ? 1 : 0;

  for (i = start_column; i < nsegs - 1; i++)
    {
      t = bitset_first_member (&(index_entryp->seg_equal_terms[i]));
      bitset_add (&range_terms, t);

      /* add multi_col_range_segs */
      if (QO_TERM_IS_FLAGED (QO_ENV_TERM (infop->env, t), QO_TERM_MULTI_COLL_PRED))
    {
      bitset_add (&(index_entryp->multi_col_range_segs), index_entryp->seg_idxs[i]);
    }
    }

  /* for each terms associated with the last segment */
  t = bitset_iterate (&(index_entryp->seg_equal_terms[nsegs - 1]), &iter);
  for (; t != -1; t = bitset_next_member (&iter))
    {
      bitset_add (&range_terms, t);
      /* add multi_col_range_segs */
      if (QO_TERM_IS_FLAGED (QO_ENV_TERM (infop->env, t), QO_TERM_MULTI_COLL_PRED))
    {
      bitset_add (&(index_entryp->multi_col_range_segs), index_entryp->seg_idxs[nsegs - 1]);
    }

      /* generate index scan plan */
      planp = qo_index_scan_new (infop, nodep, ni_entryp, QO_SCANMETHOD_INDEX_SCAN, &range_terms, NULL);

      n = qo_check_plan_on_info (infop, planp);
      if (n)
    {
      normal_index_plan_n++;    /* include index skip scan */
    }

      /* is it safe to ignore the result of qo_check_plan_on_info()? */
      bitset_remove (&range_terms, t);
      if (QO_TERM_IS_FLAGED (QO_ENV_TERM (infop->env, t), QO_TERM_MULTI_COLL_PRED))
    {
      bitset_remove (&(index_entryp->multi_col_range_segs), index_entryp->seg_idxs[nsegs - 1]);
    }
    }

  bitset_assign (&seg_other_terms, &(index_entryp->seg_other_terms[nsegs - 1]));
  for (t = bitset_iterate (&seg_other_terms, &iter); t != -1; t = bitset_next_member (&iter))
    {
      bitset_add (&range_terms, t);

      /* generate index scan plan */
      planp = qo_index_scan_new (infop, nodep, ni_entryp, QO_SCANMETHOD_INDEX_SCAN, &range_terms, NULL);

      n = qo_check_plan_on_info (infop, planp);
      if (n)
    {
      normal_index_plan_n++;    /* include index skip scan */
    }

      /* is it safe to ignore the result of qo_check_plan_on_info()? */
      bitset_remove (&range_terms, t);
    }

  bitset_delset (&seg_other_terms);
  bitset_delset (&range_terms);

  return normal_index_plan_n;
}

/*
 * qo_generate_loose_index_scan () -
 *   return: num of index loosed scan plans
 *   infop(in): pointer to QO_INFO (environment info node which holds plans)
 *   nodep(in): pointer to QO_NODE (node in the join graph)
 *   ni_entryp(in): pointer to QO_NODE_INDEX_ENTRY (node index entry)
 */
static int
qo_generate_loose_index_scan (QO_INFO * infop, QO_NODE * nodep, QO_NODE_INDEX_ENTRY * ni_entryp)
{
  QO_INDEX_ENTRY *index_entryp;
  int n = 0;
  QO_PLAN *planp;
  BITSET range_terms;

  bitset_init (&range_terms, infop->env);

  /* pointer to QO_INDEX_ENTRY structure */
  index_entryp = (ni_entryp)->head;
  if (index_entryp->force < 0)
    {
      assert (false);
      return 0;
    }

  assert (bitset_is_empty (&(index_entryp->seg_equal_terms[0])));
  assert (index_entryp->ils_prefix_len > 0);
  assert (QO_ENTRY_MULTI_COL (index_entryp));
  assert (index_entryp->cover_segments == true);
  assert (index_entryp->is_iss_candidate == false);

  assert (bitset_is_empty (&range_terms));

  planp = qo_index_scan_new (infop, nodep, ni_entryp, QO_SCANMETHOD_INDEX_SCAN, &range_terms, NULL);

  n = qo_check_plan_on_info (infop, planp);

  bitset_delset (&range_terms);

  return n;
}

/*
 * qo_generate_sort_limit_plan () - generate SORT_LIMIT plans
 * return : number of plans generated
 * env (in) :
 * infop (in)   : info for the plan
 * subplan (in) : subplan over which to generate the SORT_LIMIT plan
 */
static int
qo_generate_sort_limit_plan (QO_ENV * env, QO_INFO * infop, QO_PLAN * subplan)
{
  int n;
  QO_PLAN *plan;

  if (subplan->order != QO_UNORDERED)
    {
      /* Do not put a SORT_LIMIT plan over an ordered plan because we have to keep the ordered principle. At best, we
       * can place a SORT_LIMIT plan directly under an ordered one.
       */
      return 0;
    }

  plan = qo_sort_new (subplan, QO_UNORDERED, SORT_LIMIT);
  if (plan == NULL)
    {
      return 0;
    }
  n = qo_check_plan_on_info (infop, plan);
  return n;
}

/*
 * qo_has_is_not_null_term () - Check if whether a given node has sarg term
 *                              with not null operation
 *   return: 1 if it is found, otherwise 0
 *   node(in): pointer to QO_NODE
 */
static int
qo_has_is_not_null_term (QO_NODE * node)
{
  QO_ENV *env;
  QO_TERM *term;
  PT_NODE *expr;
  int i;
  bool found;

  assert (node != NULL && node->env != NULL);
  if (node == NULL || node->env == NULL)
    {
      return 0;
    }

  env = QO_NODE_ENV (node);
  for (i = 0; i < env->nterms; i++)
    {
      term = QO_ENV_TERM (env, i);

      /* term should belong to the given node */
      if (!bitset_intersects (&(QO_TERM_SEGS (term)), &(QO_NODE_SEGS (node))))
    {
      continue;
    }

      expr = QO_TERM_PT_EXPR (term);
      if (!PT_IS_EXPR_NODE (expr))
    {
      continue;
    }

      found = false;
      while (expr)
    {
      if (expr->info.expr.op == PT_IS_NOT_NULL)
        {
          found = true;
        }
      else if (expr->info.expr.op == PT_IS_NULL)
        {
          found = false;
          break;
        }

      expr = expr->or_next;
    }

      /* return if one of sarg term has not null operation */
      if (found)
    {
      return 1;
    }
    }
  return 0;
}

/*
 * qo_search_planner () -
 *   return:
 *   planner(in):
 */
static QO_PLAN *
qo_search_planner (QO_PLANNER * planner)
{
  int i, j, nsegs;
  bool broken;
  QO_PLAN *plan;
  QO_NODE *node;
  QO_INFO *info;
  BITSET_ITERATOR si;
  int subq_idx;
  QO_SUBQUERY *subq;
  QO_NODE_INDEX *node_index;
  QO_NODE_INDEX_ENTRY *ni_entry;
  QO_INDEX_ENTRY *index_entry;
  BITSET seg_terms;
  BITSET nodes, subqueries, remaining_subqueries;
  int join_info_bytes;
  int n;
  int start_column = 0;
  PT_NODE *tree = NULL;
  bool special_index_scan = false;

  bitset_init (&nodes, planner->env);
  bitset_init (&subqueries, planner->env);
  bitset_init (&remaining_subqueries, planner->env);

  planner->worst_plan = qo_worst_new (planner->env);
  if (planner->worst_plan == NULL)
    {
      plan = NULL;
      goto end;
    }

  planner->worst_info = qo_alloc_info (planner, &nodes, &nodes, &nodes, QO_INFINITY, QO_INFINITY);
  (planner->worst_plan)->info = planner->worst_info;
  (void) qo_plan_add_ref (planner->worst_plan);

  /*
   * At this point, N (and node), S (and seg), E (and edge), and
   * EQ (and eqclass) have been initialized; we now need to set up the
   * various info vectors.
   *
   * For the time being, we assume that N is never "too large", and we
   * go ahead and allocate the full join_info vector of M elements.
   */
  if (planner->N > 1)
    {
      planner->M =
    QO_PARTITION_M_OFFSET (&planner->partition[planner->P - 1]) +
    QO_JOIN_INFO_SIZE (&planner->partition[planner->P - 1]);

      join_info_bytes = planner->M * sizeof (QO_INFO *);
      if (join_info_bytes > 0)
    {
      planner->join_info = (QO_INFO **) malloc (join_info_bytes);
      if (planner->join_info == NULL)
        {
          er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, (size_t) join_info_bytes);
          plan = NULL;
          goto end;
        }
    }
      else
    {
      plan = NULL;
      goto end;
    }

      memset (planner->join_info, 0, join_info_bytes);
    }

  bitset_assign (&remaining_subqueries, &(planner->all_subqueries));

  /*
   * Add appropriate scan plans for each node.
   */
  planner->node_info = NULL;
  if (planner->N > 0)
    {
      size_t size = sizeof (QO_INFO *) * planner->N;

      planner->node_info = (QO_INFO **) malloc (size);
      if (planner->node_info == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, size);
      plan = NULL;
      goto end;
    }
    }

  for (i = 0; i < (signed) planner->N; ++i)
    {
      node = &planner->node[i];
      BITSET_CLEAR (nodes);
      bitset_add (&nodes, i);
      planner->node_info[i] =
    qo_alloc_info (planner, &nodes, &QO_NODE_SARGS (node), &QO_NODE_EQCLASSES (node),
               QO_NODE_SELECTIVITY (node) * (double) QO_NODE_NCARD (node), (double) QO_NODE_NCARD (node));

      if (planner->node_info[i] == NULL)
    {
      plan = NULL;
      goto end;
    }

      BITSET_CLEAR (subqueries);
      for (subq_idx = bitset_iterate (&remaining_subqueries, &si); subq_idx != -1; subq_idx = bitset_next_member (&si))
    {
      subq = &planner->subqueries[subq_idx];
      if (bitset_is_empty (&subq->nodes)    /* uncorrelated */
          || (bitset_subset (&nodes, &(subq->nodes))    /* correlated */
          && bitset_subset (&(QO_NODE_SARGS (node)), &(subq->terms))))
        {
          bitset_add (&subqueries, subq_idx);
          bitset_remove (&remaining_subqueries, subq_idx);
        }
    }
      bitset_assign (&(QO_NODE_SUBQUERIES (node)), &subqueries);
    }

  /*
   * Check all of the terms to determine which are eligible to serve as
   * index scans.
   */
  for (i = 0; i < (signed) planner->N; i++)
    {
      node = &planner->node[i];
      info = planner->node_info[QO_NODE_IDX (node)];

      node_index = QO_NODE_INDEXES (node);

      /* Set special_index_scan to true if spec if flagged as: 1. Scan for b-tree key info. 2. Scan for b-tree node
       * info. These are special cases which need index scan forced.
       */
      special_index_scan = PT_SPEC_SPECIAL_INDEX_SCAN (QO_NODE_ENTITY_SPEC (node));
      if (special_index_scan)
    {
      /* Make sure there is only one index entry */
      assert (node_index != NULL && QO_NI_N (node_index) == 1);
      ni_entry = QO_NI_ENTRY (node_index, 0);
      n =
        qo_check_plan_on_info (info,
                   qo_index_scan_new (info, node, ni_entry, QO_SCANMETHOD_INDEX_SCAN_INSPECT, NULL,
                              NULL));
      assert (n == 1);
      continue;
    }

      /*
       *  It is possible that this node will not have indexes.  This would
       *  happen (for instance) if the node represented a derived table.
       *  There is no purpose looking for index scans for a node without
       *  indexes so skip the search in this case.
       */
      if (node_index != NULL)
    {
      bitset_init (&seg_terms, planner->env);

      for (j = 0; j < QO_NI_N (node_index); j++)
        {
          ni_entry = QO_NI_ENTRY (node_index, j);
          index_entry = (ni_entry)->head;
          if (index_entry->force < 0)
        {
          continue; /* is disabled index; skip and go ahead */
        }

          /* If the index is a candidate for index skip scan, then it will not have any terms for seg_equal or
           * seg_other[0], so we should skip that first column from initial checks. Set the start column to 1.
           */
          start_column = index_entry->is_iss_candidate ? 1 : 0;

          /* seg_terms will contain all the indexable terms that refer segments from this node; stops at the first
           * one that has no equals or other terms
           */
          BITSET_CLEAR (seg_terms);
          for (nsegs = start_column; nsegs < index_entry->nsegs; nsegs++)
        {
          bitset_union (&seg_terms, &(index_entry->seg_equal_terms[nsegs]));
          bitset_union (&seg_terms, &(index_entry->seg_other_terms[nsegs]));

          if (bitset_is_empty (&(index_entry->seg_equal_terms[nsegs])))
            {
              if (!bitset_is_empty (&(index_entry->seg_other_terms[nsegs])))
            {
              nsegs++;  /* include this term */
            }
              break;
            }
        }

          bitset_intersect (&seg_terms, &(QO_NODE_SARGS (node)));

          n = 0;        /* init */

          if (!bitset_is_empty (&seg_terms))
        {
          assert (nsegs > 0);

          n = qo_generate_index_scan (info, node, ni_entry, nsegs);
        }
          else if (index_entry->constraints->filter_predicate && index_entry->force > 0)
        {
          assert (bitset_is_empty (&seg_terms));

          /* Currently, CUBRID does not allow null values in index. The filter index expression must contain at
           * least one term different than "is null". Otherwise, the index will be empty. Having at least one
           * term different than "is null" in a filter index expression, the user knows from beginning that
           * null values can't appear when scan filter index.
           */

          n =
            qo_check_plan_on_info (info,
                       qo_index_scan_new (info, node, ni_entry, QO_SCANMETHOD_INDEX_SCAN,
                                  &seg_terms, NULL));
        }
          else if (index_entry->ils_prefix_len > 0)
        {
          assert (bitset_is_empty (&seg_terms));

          n = qo_generate_loose_index_scan (info, node, ni_entry);
        }
          else
        {
          assert (bitset_is_empty (&seg_terms));

          /* if the index didn't normally skipped the order by, we try the new plan, maybe this will be better.
           * DO NOT generate a order by index if there is no order by! Skip generating index from order by if
           * multi_range_opt is true (multi range optimized plan is already better)
           */
          tree = QO_ENV_PT_TREE (info->env);
          if (tree == NULL)
            {
              assert (false);   /* is invalid case */
              continue; /* nop */
            }

          if (tree->info.query.q.select.connect_by != NULL || qo_is_prefix_index (index_entry))
            {
              continue; /* nop; go ahead */
            }

          /* if the index didn't normally skipped the group/order by, we try the new plan, maybe this will be
           * better. DO NOT generate if there is no group/order by!
           */
          if (!n && !index_entry->groupby_skip && tree->info.query.q.select.group_by
              && qo_validate_index_for_groupby (info->env, ni_entry))
            {
              n =
            qo_check_plan_on_info (info,
                           qo_index_scan_new (info, node, ni_entry,
                                  QO_SCANMETHOD_INDEX_GROUPBY_SCAN, &seg_terms, NULL));
            }

          if (!n && !index_entry->orderby_skip && !tree->info.query.q.select.group_by
              && tree->info.query.order_by && qo_validate_index_for_orderby (info->env, ni_entry))
            {
              n =
            qo_check_plan_on_info (info,
                           qo_index_scan_new (info, node, ni_entry,
                                  QO_SCANMETHOD_INDEX_ORDERBY_SCAN, &seg_terms, NULL));
            }
        }
        }

      bitset_delset (&seg_terms);
    }

      /* Create a sequential scan plan for each node. */
      qo_generate_seq_scan (info, node);

      if (QO_ENV_USE_SORT_LIMIT (planner->env) && QO_NODE_SORT_LIMIT_CANDIDATE (node))
    {
      /* generate a stop plan over the current best plan of the */
      QO_PLAN *best_plan;
      best_plan = qo_find_best_plan_on_info (info, QO_UNORDERED, 1.0);
      if (best_plan->plan_type == QO_PLANTYPE_SCAN && !qo_plan_multi_range_opt (best_plan)
          && !qo_is_iscan_from_groupby (best_plan))
        {
          qo_generate_sort_limit_plan (planner->env, info, best_plan);
        }
    }
    }

  /*
   * Now remaining_subqueries should contain only entries that depend
   * on more than one class.
   */

  if (planner->P > 1)
    {
      size_t size = sizeof (QO_INFO *) * planner->P;

      planner->cp_info = (QO_INFO **) malloc (size);
      if (planner->cp_info == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, size);
      plan = NULL;
      goto end;
    }

      for (i = 0; i < (signed) planner->P; i++)
    {
      planner->cp_info[i] = NULL;
    }
    }

  broken = false;
  for (i = 0; i < (signed) planner->P; ++i)
    {
      /*
       * If any partition fails, give up.  We'll have to build an
       * unoptimized plan elsewhere.
       */
      if (qo_search_partition (planner, &planner->partition[i], QO_UNORDERED, &remaining_subqueries) == NULL)
    {
      for (j = 0; j < i; ++j)
        {
          qo_plan_del_ref (planner->partition[j].plan);
        }
      broken = true;
      break;
    }
    }
  plan = broken ? NULL : qo_combine_partitions (planner, &remaining_subqueries);

  /* if we have use_desc_idx hint and order by or group by, do some checking */

  if (plan == NULL)
    {
      goto end;
    }

  if (plan->use_iscan_descending == true && qo_plan_multi_range_opt (plan) == false)
    {
      qo_set_use_desc (plan);
    }

  tree = QO_ENV_PT_TREE (planner->env);
  assert (tree != NULL);

  if (tree->info.query.q.select.hint & PT_HINT_USE_IDX_DESC)
    {
      /* check direction of the first order by column. */
      if (tree->info.query.order_by != NULL && tree->info.query.q.select.connect_by == NULL)
    {
      /* if we have order by and the hint, we allow the hint only if we have order by descending on first column.
       * Otherwise we clear it */
      if (tree->info.query.order_by->info.sort_spec.asc_or_desc == PT_ASC)
        {
          tree->info.query.q.select.hint &= ~PT_HINT_USE_IDX_DESC;
        }
    }

      /* check direction of the first order by column. */
      if (tree->info.query.q.select.group_by != NULL && tree->info.query.q.select.group_by->flag.with_rollup == false)
    {
      /* if we have group by and the hint, we allow the hint only if we have group by descending on first column.
       * Otherwise we clear it */
      if (tree->info.query.q.select.group_by->info.sort_spec.asc_or_desc == PT_ASC)
        {
          tree->info.query.q.select.hint &= ~PT_HINT_USE_IDX_DESC;
        }
    }
    }

  /* some indexes may be marked with multi range optimization (as candidates) However, if the chosen top plan is not
   * marked as using multi range optimization it means that the optimization has been invalidated, or maybe another
   * plan was chosen. Make sure to un-mark indexes in this case
   */
  if (!qo_plan_multi_range_opt (plan))
    {
      qo_walk_plan_tree (plan, qo_unset_multi_range_optimization, NULL);
      if (plan->info->env != NULL && plan->info->env->multi_range_opt_candidate)
    {
      plan->multi_range_opt_use = PLAN_MULTI_RANGE_OPT_CAN_USE;
    }
    }

  qo_walk_plan_tree (plan, qo_unset_hint_use_desc_idx, NULL);

end:

  bitset_delset (&nodes);
  bitset_delset (&subqueries);
  bitset_delset (&remaining_subqueries);

  return plan;
}


/*
 * qo_clean_planner () -
 *   return:
 *   planner(in):
 */
static void
qo_clean_planner (QO_PLANNER * planner)
{
  /*
   * This cleans up everything that isn't needed for the surviving
   * plan.  In particular, it will give back all excess QO_PLAN
   * structures that we have allocated during the search.  All
   * detachable QO_INFO should already have been detached, so we don't
   * worry about them here.
   */
  planner->cleanup_needed = false;
  bitset_delset (&(planner->all_subqueries));
  bitset_delset (&(planner->final_segs));
  qo_plans_teardown (planner->env);
}

/* Tables considered at a time during a join
 * -------------------------------------------
 * Tables joined | Tables considered at a time
 * --------------+----------------------------
 *  8..25        | 8
 * 26..37        | 3
 * 38..          | 2
 * -------------------------------------------
 * Refer Sybase Ataptive Server
 */

/*
 * qo_search_partition_join () -
 *   return:
 *   planner(in):
 *   partition(in):
 *   remaining_subqueries(in):
 */
static QO_INFO *
qo_search_partition_join (QO_PLANNER * planner, QO_PARTITION * partition, BITSET * remaining_subqueries)
{
  QO_ENV *env;
  int i, nodes_cnt, node_idx;
  PT_NODE *tree;
  PT_HINT_ENUM hint;
  QO_TERM *term;
  QO_NODE *node;
  int num_path_inner;
  QO_INFO *visited_info;
  BITSET visited_nodes;
  BITSET visited_rel_nodes;
  BITSET visited_terms;
  BITSET nested_path_nodes;
  BITSET remaining_nodes;
  BITSET remaining_terms;

  env = planner->env;
  bitset_init (&visited_nodes, env);
  bitset_init (&visited_rel_nodes, env);
  bitset_init (&visited_terms, env);
  bitset_init (&nested_path_nodes, env);
  bitset_init (&remaining_nodes, env);
  bitset_init (&remaining_terms, env);

  /* include useful nodes */
  bitset_assign (&remaining_nodes, &(QO_PARTITION_NODES (partition)));
  nodes_cnt = bitset_cardinality (&remaining_nodes);

  num_path_inner = 0;       /* init */

  /* include useful terms */
  for (i = 0; i < (signed) planner->T; i++)
    {
      term = &planner->term[i];
      if (QO_TERM_CLASS (term) == QO_TC_TOTALLY_AFTER_JOIN)
    {
      continue;     /* skip and go ahead */
    }

      if (bitset_subset (&remaining_nodes, &(QO_TERM_NODES (term))))
    {
      bitset_add (&remaining_terms, i);
      if (QO_TERM_CLASS (term) == QO_TC_PATH)
        {
          num_path_inner++; /* path-expr sargs */
        }
    }

    }               /* for (i = ...) */

  /* set hint info */
  tree = QO_ENV_PT_TREE (env);
  hint = tree->info.query.q.select.hint;

  /* set #tables consider at a time */
  if (num_path_inner || (hint & PT_HINT_ORDERED))
    {
      /* inner join type path term exist; WHERE x.y.z = ? or there is a SQL hint ORDERED */
      planner->join_unit = nodes_cnt;   /* give up */
    }
  else
    {
      planner->join_unit = (nodes_cnt <= 25) ? MIN (8, nodes_cnt) : (nodes_cnt <= 37) ? 3 : 2;
    }

  /* STEP 1: do join search with visited nodes */

  node = NULL;          /* init */

  while (1)
    {
      node_idx = -1;        /* init */
      (void) planner_permutate (planner, partition, hint, node, /* previous head node */
                &visited_nodes, &visited_rel_nodes, &visited_terms, &nested_path_nodes,
                &remaining_nodes, &remaining_terms, remaining_subqueries, num_path_inner,
                (planner->join_unit < nodes_cnt) ? &node_idx
                /* partial join search */
                : NULL /* total join search */ );
      if (planner->best_info)
    {           /* OK */
      break;        /* found best total join plan */
    }

      if (planner->join_unit >= nodes_cnt)
    {
      /* something wrong for total join search */
      break;        /* give up */
    }
      else
    {
      if (node_idx == -1)
        {
          /* something wrong for partial join search; rollback and retry total join search */
          bitset_union (&remaining_nodes, &visited_nodes);
          bitset_union (&remaining_terms, &visited_terms);

          BITSET_CLEAR (nested_path_nodes);
          BITSET_CLEAR (visited_nodes);
          BITSET_CLEAR (visited_rel_nodes);
          BITSET_CLEAR (visited_terms);

          /* set #tables consider at a time */
          planner->join_unit = nodes_cnt;

          /* STEP 2: do total join search without visited nodes */

          continue;
        }
    }

      /* at here, still do partial join search */

      /* extract the outermost nodes at this join level */
      node = QO_ENV_NODE (env, node_idx);
      bitset_add (&visited_nodes, node_idx);
      bitset_add (&visited_rel_nodes, QO_NODE_REL_IDX (node));
      bitset_remove (&remaining_nodes, node_idx);

      /* extract already used terms at this join level */
      if (bitset_cardinality (&visited_nodes) == 1)
    {
      /* current prefix has only one node */
      visited_info = planner->node_info[node_idx];
    }
      else
    {
      /* current prefix has two or more nodes */
      visited_info = planner->join_info[QO_INFO_INDEX (QO_PARTITION_M_OFFSET (partition), visited_rel_nodes)];
    }

      if (visited_info == NULL)
    {           /* something wrong */
      break;        /* give up */
    }

      bitset_assign (&visited_terms, &(visited_info->terms));
      bitset_difference (&remaining_terms, &(visited_info->terms));

      planner->join_unit++; /* increase join unit level */

    }

  bitset_delset (&visited_rel_nodes);
  bitset_delset (&visited_nodes);
  bitset_delset (&visited_terms);
  bitset_delset (&nested_path_nodes);
  bitset_delset (&remaining_nodes);
  bitset_delset (&remaining_terms);

  return planner->best_info;
}

/*
 * qo_search_partition () -
 *   return:
 *   planner(in):
 *   partition(in):
 *   order(in):
 *   remaining_subqueries(in):
 */
static QO_PLAN *
qo_search_partition (QO_PLANNER * planner, QO_PARTITION * partition, QO_EQCLASS * order, BITSET * remaining_subqueries)
{
  int i, nodes_cnt;

  nodes_cnt = bitset_cardinality (&(QO_PARTITION_NODES (partition)));

  /* nodes are multi if there is a join to be done. If not, this is just a degenerate search to determine which of the
   * indexes (if available) to use for the (single) class involved in the query.
   */
  if (nodes_cnt > 1)
    {
      planner->best_info = qo_search_partition_join (planner, partition, remaining_subqueries);
    }
  else
    {
      QO_NODE *node;

      i = bitset_first_member (&(QO_PARTITION_NODES (partition)));
      node = QO_ENV_NODE (planner->env, i);
      planner->best_info = planner->node_info[QO_NODE_IDX (node)];
    }

  if (planner->env->dump_enable)
    {
      qo_dump_planner_info (planner, partition, stdout);
    }

  if (planner->best_info)
    {
      QO_PARTITION_PLAN (partition) = qo_plan_finalize (qo_find_best_plan_on_info (planner->best_info, order, 1.0));
    }
  else
    {
      QO_PARTITION_PLAN (partition) = NULL;
    }

  /* Now clean up after ourselves.  Free all of the plans that aren't part of the winner for this partition, but retain
   * the nodes: they contain information that the winning plan requires.
   */

  if (nodes_cnt > 1)
    {
      QO_INFO *info;

      for (info = planner->info_list; info; info = info->next)
    {
      if (bitset_subset (&(QO_PARTITION_NODES (partition)), &(info->nodes)))
        {
          qo_detach_info (info);
        }
    }
    }
  else
    {               /* single class */
      for (i = 0; i < (signed) planner->N; i++)
    {
      if (BITSET_MEMBER (QO_PARTITION_NODES (partition), i))
        {
          qo_detach_info (planner->node_info[i]);
        }
    }
    }

  return QO_PARTITION_PLAN (partition);
}

/*
 * sort_partitions () -
 *   return:
 *   planner(in):
 */
static void
sort_partitions (QO_PLANNER * planner)
{
  int i, j;
  QO_PARTITION *i_part, *j_part;
  QO_PARTITION tmp;

  for (i = 1; i < (signed) planner->P; ++i)
    {
      i_part = &planner->partition[i];
      for (j = 0; j < i; ++j)
    {
      j_part = &planner->partition[j];
      /*
       * If the higher partition (i_part) supplies something that
       * the lower partition (j_part) needs, swap them.
       */
      if (bitset_intersects (&(QO_PARTITION_NODES (i_part)), &(QO_PARTITION_DEPENDENCIES (j_part))))
        {
          tmp = *i_part;
          *i_part = *j_part;
          *j_part = tmp;
        }
    }
    }
}

/*
 * qo_combine_partitions () -
 *   return:
 *   planner(in):
 *   reamining_subqueries(in):
 */
static QO_PLAN *
qo_combine_partitions (QO_PLANNER * planner, BITSET * reamining_subqueries)
{
  QO_PARTITION *partition = planner->partition;
  QO_PLAN *plan, *t_plan;
  BITSET nodes;
  BITSET terms;
  BITSET eqclasses;
  BITSET sarged_terms;
  BITSET subqueries;
  BITSET_ITERATOR bi;
  int i, t, s;
  double cardinality, total_rows;
  QO_PLAN *next_plan;

  bitset_init (&nodes, planner->env);
  bitset_init (&terms, planner->env);
  bitset_init (&eqclasses, planner->env);
  bitset_init (&sarged_terms, planner->env);
  bitset_init (&subqueries, planner->env);

  /*
   * Order the partitions by dependency information.  We could probably
   * undertake a more sophisticated search here that takes the
   * remaining sargable terms into account, but this code is probably
   * hardly ever exercised anyway, and this query is already known to
   * be a loser, so don't worry about it.
   */
  sort_partitions (planner);

  for (i = 0; i < (signed) planner->P; ++i)
    {
      (QO_PARTITION_PLAN (&planner->partition[i]))->refcount--;
    }

  /*
   * DON'T initialize these until after the sorting is done.
   */
  plan = QO_PARTITION_PLAN (partition);
  cardinality = (plan->info)->cardinality;
  total_rows = (plan->info)->total_rows;

  bitset_assign (&nodes, &((plan->info)->nodes));
  bitset_assign (&terms, &((plan->info)->terms));
  bitset_assign (&eqclasses, &((plan->info)->eqclasses));

  for (++partition, i = 1; i < (signed) planner->P; ++partition, ++i)
    {
      next_plan = QO_PARTITION_PLAN (partition);

      bitset_union (&nodes, &((next_plan->info)->nodes));
      bitset_union (&terms, &((next_plan->info)->terms));
      bitset_union (&eqclasses, &((next_plan->info)->eqclasses));
      cardinality *= (next_plan->info)->cardinality;
      total_rows *= (next_plan->info)->total_rows;

      planner->cp_info[i] = qo_alloc_info (planner, &nodes, &terms, &eqclasses, cardinality, total_rows);

      for (t = planner->E; t < (signed) planner->T; ++t)
    {
      if (!bitset_is_empty (&(QO_TERM_NODES (&planner->term[t]))) && !BITSET_MEMBER (terms, t)
          && bitset_subset (&nodes, &(QO_TERM_NODES (&planner->term[t])))
          && (QO_TERM_CLASS (&planner->term[t]) != QO_TC_TOTALLY_AFTER_JOIN))
        {
          bitset_add (&sarged_terms, t);
        }
    }

      BITSET_CLEAR (subqueries);
      for (s = bitset_iterate (reamining_subqueries, &bi); s != -1; s = bitset_next_member (&bi))
    {
      QO_SUBQUERY *subq = &planner->subqueries[s];
      if (bitset_subset (&nodes, &(subq->nodes)) && bitset_subset (&sarged_terms, &(subq->terms)))
        {
          bitset_add (&subqueries, s);
          bitset_remove (reamining_subqueries, s);
        }
    }

      plan = qo_cp_new (planner->cp_info[i], plan, next_plan, &sarged_terms, &subqueries);
      qo_detach_info (planner->cp_info[i]);
      BITSET_CLEAR (sarged_terms);
    }

  /*
   * Now finalize the topmost node of the tree.
   */
  if (plan != NULL)
    {
      qo_plan_finalize (plan);
    }

  for (i = planner->E; i < (signed) planner->T; ++i)
    {
      if (bitset_is_empty (&(QO_TERM_NODES (&planner->term[i]))))
    {
      bitset_add (&sarged_terms, i);
    }
    }

  /* skip empty sort plan */
  for (t_plan = plan; t_plan && t_plan->plan_type == QO_PLANTYPE_SORT; t_plan = t_plan->plan_un.sort.subplan)
    {
      if (!bitset_is_empty (&(t_plan->sarged_terms)))
    {
      break;
    }
    }

  if (t_plan)
    {
      bitset_union (&(t_plan->sarged_terms), &sarged_terms);
    }
  else if (plan != NULL)
    {
      /* invalid plan structure. occur error */
      qo_plan_discard (plan);
      plan = NULL;
    }

  bitset_delset (&nodes);
  bitset_delset (&terms);
  bitset_delset (&eqclasses);
  bitset_delset (&sarged_terms);
  bitset_delset (&subqueries);

  return plan;
}

/*
 * qo_expr_selectivity () -
 *   return: double
 *   env(in): optimizer environment
 *   pt_expr(in): expression to evaluate
 */
double
qo_expr_selectivity (QO_ENV * env, PT_NODE * pt_expr)
{
  double lhs_selectivity, rhs_selectivity, selectivity, total_selectivity;
  PT_NODE *node;

  QO_ASSERT (env, pt_expr != NULL);
  QO_ASSERT (env, pt_expr->node_type == PT_EXPR);

  selectivity = 0.0;
  total_selectivity = 0.0;

  /* traverse OR list */
  for (node = pt_expr; node; node = node->or_next)
    {
      switch (node->info.expr.op)
    {
    case PT_OR:
      lhs_selectivity = qo_expr_selectivity (env, node->info.expr.arg1);
      rhs_selectivity = qo_expr_selectivity (env, node->info.expr.arg2);
      selectivity = qo_or_selectivity (env, lhs_selectivity, rhs_selectivity);
      break;

    case PT_AND:
      lhs_selectivity = qo_expr_selectivity (env, node->info.expr.arg1);
      rhs_selectivity = qo_expr_selectivity (env, node->info.expr.arg2);
      selectivity = qo_and_selectivity (env, lhs_selectivity, rhs_selectivity);
      break;

    case PT_NOT:
      lhs_selectivity = qo_expr_selectivity (env, node->info.expr.arg1);
      selectivity = qo_not_selectivity (env, lhs_selectivity);
      break;

    case PT_EQ:
      selectivity = qo_equal_selectivity (env, node);
      break;

    case PT_NE:
      lhs_selectivity = qo_equal_selectivity (env, node);
      selectivity = qo_not_selectivity (env, lhs_selectivity);
      break;

    case PT_NULLSAFE_EQ:
      selectivity = qo_equal_selectivity (env, node);
      break;

    case PT_GE:
    case PT_GT:
    case PT_LT:
    case PT_LE:
      selectivity = qo_comp_selectivity (env, node);
      break;

    case PT_BETWEEN:
      selectivity = qo_between_selectivity (env, node);
      break;

    case PT_NOT_BETWEEN:
      lhs_selectivity = qo_between_selectivity (env, node);
      selectivity = qo_not_selectivity (env, lhs_selectivity);
      break;

    case PT_RANGE:
      selectivity = qo_range_selectivity (env, node);
      break;

    case PT_LIKE_ESCAPE:
    case PT_LIKE:
      selectivity = (double) prm_get_float_value (PRM_ID_LIKE_TERM_SELECTIVITY);
      break;

    case PT_NOT_LIKE:
      lhs_selectivity = (double) prm_get_float_value (PRM_ID_LIKE_TERM_SELECTIVITY);
      selectivity = qo_not_selectivity (env, lhs_selectivity);
      break;

    case PT_SETNEQ:
    case PT_SETEQ:
    case PT_SUPERSETEQ:
    case PT_SUPERSET:
    case PT_SUBSET:
    case PT_SUBSETEQ:
    case PT_IS:
    case PT_XOR:
      selectivity = DEFAULT_SELECTIVITY;
      break;

    case PT_IS_NOT:
      selectivity = qo_not_selectivity (env, DEFAULT_SELECTIVITY);
      break;

    case PT_EQ_SOME:
    case PT_NE_SOME:
    case PT_GE_SOME:
    case PT_GT_SOME:
    case PT_LT_SOME:
    case PT_LE_SOME:
    case PT_EQ_ALL:
    case PT_NE_ALL:
    case PT_GE_ALL:
    case PT_GT_ALL:
    case PT_LT_ALL:
    case PT_LE_ALL:
    case PT_IS_IN:
      selectivity = qo_all_some_in_selectivity (env, node);
      break;

    case PT_IS_NOT_IN:
      lhs_selectivity = qo_all_some_in_selectivity (env, node);
      selectivity = qo_not_selectivity (env, lhs_selectivity);
      break;

    case PT_IS_NULL:
      selectivity = DEFAULT_NULL_SELECTIVITY;   /* make a guess */
      break;

    case PT_IS_NOT_NULL:
      selectivity = qo_not_selectivity (env, DEFAULT_NULL_SELECTIVITY);
      break;

    case PT_EXISTS:
      selectivity = DEFAULT_EXISTS_SELECTIVITY; /* make a guess */
      break;

    default:
      break;
    }

      total_selectivity = qo_or_selectivity (env, total_selectivity, selectivity);
      total_selectivity = MAX (total_selectivity, 0.0);
      total_selectivity = MIN (total_selectivity, 1.0);
    }

  return total_selectivity;
}

/*
 * qo_or_selectivity () - Calculate the selectivity of an OR expression
 *                        from the selectivities of the lhs and rhs
 *   return: double
 *   env(in):
 *   lhs_sel(in):
 *   rhs_sel(in):
 */
static double
qo_or_selectivity (QO_ENV * env, double lhs_sel, double rhs_sel)
{
  double result;

  QO_ASSERT (env, lhs_sel >= 0.0);
  QO_ASSERT (env, lhs_sel <= 1.0);
  QO_ASSERT (env, rhs_sel >= 0.0);
  QO_ASSERT (env, rhs_sel <= 1.0);

  result = lhs_sel + rhs_sel - (lhs_sel * rhs_sel);

  return result;
}

/*
 * qo_and_selectivity () -
 *   return:
 *   env(in):
 *   lhs_sel(in):
 *   rhs_sel(in):
 */
static double
qo_and_selectivity (QO_ENV * env, double lhs_sel, double rhs_sel)
{
  double result;

  QO_ASSERT (env, lhs_sel >= 0.0);
  QO_ASSERT (env, lhs_sel <= 1.0);
  QO_ASSERT (env, rhs_sel >= 0.0);
  QO_ASSERT (env, rhs_sel <= 1.0);

  result = lhs_sel * rhs_sel;

  return result;
}

/*
 * qo_not_selectivity () - Calculate the selectivity of a not expresssion
 *   return: double
 *   env(in):
 *   sel(in):
 */
static double
qo_not_selectivity (QO_ENV * env, double sel)
{
  QO_ASSERT (env, sel >= 0.0);
  QO_ASSERT (env, sel <= 1.0);

  return 1.0 - sel;
}

/*
 * qo_equal_selectivity () - Compute the selectivity of an equality predicate
 *   return: double
 *   env(in):
 *   pt_expr(in):
 *
 * Note: This uses the System R algorithm
 */
static double
qo_equal_selectivity (QO_ENV * env, PT_NODE * pt_expr)
{
  PT_NODE *lhs, *rhs, *multi_attr;
  PRED_CLASS pc_lhs, pc_rhs;
  int lhs_icard, rhs_icard, icard;
  double selectivity;

  lhs = pt_expr->info.expr.arg1;
  rhs = pt_expr->info.expr.arg2;

  /* the class of lhs and rhs */
  pc_lhs = qo_classify (lhs);
  pc_rhs = qo_classify (rhs);

  selectivity = DEFAULT_EQUAL_SELECTIVITY;

  switch (pc_lhs)
    {
    case PC_ATTR:

      switch (pc_rhs)
    {
    case PC_ATTR:
      /* attr = attr */

      /* check for indexes on either of the attributes */
      lhs_icard = qo_index_cardinality (env, lhs);
      rhs_icard = qo_index_cardinality (env, rhs);

      icard = MAX (lhs_icard, rhs_icard);
      if (icard != 0)
        {
          selectivity = (1.0 / icard);
        }
      else
        {
          selectivity = DEFAULT_EQUIJOIN_SELECTIVITY;
        }

      break;

    case PC_CONST:
    case PC_HOST_VAR:
    case PC_SUBQUERY:
    case PC_SET:
    case PC_OTHER:
      /* attr = const */

      /* check for index on the attribute.  NOTE: For an equality predicate, we treat subqueries as constants. */
      lhs_icard = qo_index_cardinality (env, lhs);
      if (lhs_icard != 0)
        {
          selectivity = (1.0 / lhs_icard);
        }
      else
        {
          selectivity = DEFAULT_EQUAL_SELECTIVITY;
        }

      break;

    case PC_MULTI_ATTR:
      /* attr = (attr,attr) syntactic impossible case */
      selectivity = DEFAULT_EQUAL_SELECTIVITY;
      break;
    }

      break;

    case PC_CONST:
    case PC_HOST_VAR:
    case PC_SUBQUERY:
    case PC_SET:
    case PC_OTHER:

      switch (pc_rhs)
    {
    case PC_ATTR:
      /* const = attr */

      /* check for index on the attribute.  NOTE: For an equality predicate, we treat subqueries as constants. */
      rhs_icard = qo_index_cardinality (env, rhs);
      if (rhs_icard != 0)
        {
          selectivity = (1.0 / rhs_icard);
        }
      else
        {
          selectivity = DEFAULT_EQUAL_SELECTIVITY;
        }

      break;

    case PC_CONST:
    case PC_HOST_VAR:
    case PC_SUBQUERY:
    case PC_SET:
    case PC_OTHER:
      /* const = const */

      selectivity = DEFAULT_EQUAL_SELECTIVITY;
      break;

    case PC_MULTI_ATTR:
      /* const = (attr,attr) */
      multi_attr = rhs->info.function.arg_list;
      rhs_icard = 0;
      for ( /* none */ ; multi_attr; multi_attr = multi_attr->next)
        {
          /* get index cardinality */
          icard = qo_index_cardinality (env, multi_attr);
          if (icard <= 0)
        {
          /* the only interesting case is PT_BETWEEN_EQ_NA */
          icard = 1 / DEFAULT_EQUAL_SELECTIVITY;
        }
          if (rhs_icard == 0)
        {
          /* first time */
          rhs_icard = icard;
        }
          else
        {
          rhs_icard *= icard;
        }
        }
      if (rhs_icard != 0)
        {
          selectivity = (1.0 / rhs_icard);
        }
      else
        {
          selectivity = DEFAULT_EQUAL_SELECTIVITY;
        }
      break;
    }
      break;

    case PC_MULTI_ATTR:
      switch (pc_rhs)
    {
    case PC_ATTR:
      /* (attr,attr) = attr  syntactic impossible case */
      selectivity = DEFAULT_EQUAL_SELECTIVITY;
      break;

    case PC_CONST:
    case PC_HOST_VAR:
    case PC_SUBQUERY:
    case PC_SET:
    case PC_OTHER:
      /* (attr,attr) = const */

      multi_attr = lhs->info.function.arg_list;
      lhs_icard = 0;
      for ( /* none */ ; multi_attr; multi_attr = multi_attr->next)
        {
          /* get index cardinality */
          icard = qo_index_cardinality (env, multi_attr);
          if (icard <= 0)
        {
          /* the only interesting case is PT_BETWEEN_EQ_NA */
          icard = 1 / DEFAULT_EQUAL_SELECTIVITY;
        }
          if (lhs_icard == 0)
        {
          /* first time */
          lhs_icard = icard;
        }
          else
        {
          lhs_icard *= icard;
        }
        }
      if (lhs_icard != 0)
        {
          selectivity = (1.0 / lhs_icard);
        }
      else
        {
          selectivity = DEFAULT_EQUAL_SELECTIVITY;
        }
      break;

    case PC_MULTI_ATTR:
      /* (attr,attr) = (attr,attr) */
      multi_attr = lhs->info.function.arg_list;
      lhs_icard = 0;
      for ( /* none */ ; multi_attr; multi_attr = multi_attr->next)
        {
          /* get index cardinality */
          icard = qo_index_cardinality (env, multi_attr);
          if (icard <= 0)
        {
          /* the only interesting case is PT_BETWEEN_EQ_NA */
          icard = 1 / DEFAULT_EQUAL_SELECTIVITY;
        }
          if (lhs_icard == 0)
        {
          /* first time */
          lhs_icard = icard;
        }
          else
        {
          lhs_icard *= icard;
        }
        }

      multi_attr = rhs->info.function.arg_list;
      rhs_icard = 0;
      for ( /* none */ ; multi_attr; multi_attr = multi_attr->next)
        {
          /* get index cardinality */
          icard = qo_index_cardinality (env, multi_attr);
          if (icard <= 0)
        {
          /* the only interesting case is PT_BETWEEN_EQ_NA */
          icard = 1 / DEFAULT_EQUAL_SELECTIVITY;
        }
          if (rhs_icard == 0)
        {
          /* first time */
          rhs_icard = icard;
        }
          else
        {
          rhs_icard *= icard;
        }
        }

      icard = MAX (lhs_icard, rhs_icard);
      if (icard != 0)
        {
          selectivity = (1.0 / icard);
        }
      else
        {
          selectivity = DEFAULT_EQUIJOIN_SELECTIVITY;
        }
      break;
    }

      break;
      break;
    }

  return selectivity;
}

/*
 * qo_comp_selectivity () - Compute the selectivity of a comparison predicate.
 *   return: double
 *   env(in): Pointer to an environment structure
 *   pt_expr(in): comparison expression
 *
 * Note: This uses the System R algorithm
 */
static double
qo_comp_selectivity (QO_ENV * env, PT_NODE * pt_expr)
{
  return DEFAULT_COMP_SELECTIVITY;
}

/*
 * qo_between_selectivity () - Compute the selectivity of a between predicate
 *   return: double
 *   env(in): Pointer to an environment structure
 *   pt_expr(in): between expression
 *
 * Note: This uses the System R algorithm
 */
static double
qo_between_selectivity (QO_ENV * env, PT_NODE * pt_expr)
{
  PT_NODE *and_node;

  and_node = pt_expr->info.expr.arg2;

  QO_ASSERT (env, and_node->node_type == PT_EXPR);
  QO_ASSERT (env, pt_is_between_range_op (and_node->info.expr.op));

  return DEFAULT_BETWEEN_SELECTIVITY;
}

/*
 * qo_range_selectivity () -
 *   return:
 *   env(in):
 *   pt_expr(in):
 */
static double
qo_range_selectivity (QO_ENV * env, PT_NODE * pt_expr)
{
  PT_NODE *lhs, *arg1, *arg2;
  PRED_CLASS pc1, pc2;
  double total_selectivity, selectivity;
  int lhs_icard = 0, rhs_icard = 0, icard = 0;
  PT_NODE *range_node;
  PT_OP_TYPE op_type;

  lhs = pt_expr->info.expr.arg1;

  pc2 = qo_classify (lhs);

  /* the only interesting case is 'attr RANGE {=1,=2}' or '(attr,attr) RANGE {={..},..}' */
  if (pc2 == PC_MULTI_ATTR)
    {
      lhs = lhs->info.function.arg_list;
      lhs_icard = 0;
      for ( /* none */ ; lhs; lhs = lhs->next)
    {
      /* get index cardinality */
      icard = qo_index_cardinality (env, lhs);
      if (icard <= 0)
        {
          /* the only interesting case is PT_BETWEEN_EQ_NA */
          icard = 1 / DEFAULT_EQUAL_SELECTIVITY;
        }
      if (lhs_icard == 0)
        {
          /* first time */
          lhs_icard = icard;
        }
      else
        {
          lhs_icard *= icard;
        }
    }
    }
  else if (pc2 == PC_ATTR)
    {
      /* get index cardinality */
      lhs_icard = qo_index_cardinality (env, lhs);
    }
  else
    {
      return DEFAULT_RANGE_SELECTIVITY;
    }
#if 1               /* unused anymore - DO NOT DELETE ME */
  QO_ASSERT (env, !PT_EXPR_INFO_IS_FLAGED (pt_expr, PT_EXPR_INFO_FULL_RANGE));
#endif

  total_selectivity = 0.0;

  for (range_node = pt_expr->info.expr.arg2; range_node; range_node = range_node->or_next)
    {
      QO_ASSERT (env, range_node->node_type == PT_EXPR);

      op_type = range_node->info.expr.op;
      QO_ASSERT (env, pt_is_between_range_op (op_type));

      arg1 = range_node->info.expr.arg1;
      arg2 = range_node->info.expr.arg2;

      pc1 = qo_classify (arg1);

      if (op_type == PT_BETWEEN_GE_LE || op_type == PT_BETWEEN_GE_LT || op_type == PT_BETWEEN_GT_LE
      || op_type == PT_BETWEEN_GT_LT)
    {
      selectivity = DEFAULT_BETWEEN_SELECTIVITY;
    }
      else if (op_type == PT_BETWEEN_EQ_NA)
    {
      /* PT_BETWEEN_EQ_NA have only one argument */

      selectivity = DEFAULT_EQUAL_SELECTIVITY;

      if (pc1 == PC_ATTR)
        {
          /* attr1 range (attr2 = ) */
          rhs_icard = qo_index_cardinality (env, arg1);

          icard = MAX (lhs_icard, rhs_icard);
          if (icard != 0)
        {
          selectivity = (1.0 / icard);
        }
          else
        {
          selectivity = DEFAULT_EQUIJOIN_SELECTIVITY;
        }
        }
      else
        {
          /* attr1 range (const = ) */
          if (lhs_icard != 0)
        {
          selectivity = (1.0 / lhs_icard);
        }
          else
        {
          selectivity = DEFAULT_EQUAL_SELECTIVITY;
        }
        }
    }
      else
    {
      /* PT_BETWEEN_INF_LE, PT_BETWEEN_INF_LT, PT_BETWEEN_GE_INF, and PT_BETWEEN_GT_INF have only one argument */

      selectivity = DEFAULT_COMP_SELECTIVITY;
    }

      selectivity = MAX (selectivity, 0.0);
      selectivity = MIN (selectivity, 1.0);

      total_selectivity = qo_or_selectivity (env, total_selectivity, selectivity);
      total_selectivity = MAX (total_selectivity, 0.0);
      total_selectivity = MIN (total_selectivity, 1.0);
    }

  return total_selectivity;
}

/*
 * qo_all_some_in_selectivity () - Compute the selectivity of an in predicate
 *   return: double
 *   env(in): Pointer to an environment structure
 *   pt_expr(in): in expression
 *
 * Note: This uses the System R algorithm
 */
static double
qo_all_some_in_selectivity (QO_ENV * env, PT_NODE * pt_expr)
{
  PRED_CLASS pc_lhs, pc_rhs;
  double list_card = 0.0, icard = 0.0;
  double equal_selectivity = 1.0;
  PT_NODE *lhs;
  PT_NODE *arg1, *arg2;

  /* To avoid repeated dereferencing */
  arg1 = pt_expr->info.expr.arg1;
  arg2 = pt_expr->info.expr.arg2;

  /* determine the class of each side of the range */
  pc_lhs = qo_classify (arg1);
  pc_rhs = qo_classify (arg2);

  /* The only interesting cases are: attr IN set or (attr,attr) IN set or attr IN subquery */
  if ((pc_lhs == PC_MULTI_ATTR || pc_lhs == PC_ATTR) && (pc_rhs == PC_SET || pc_rhs == PC_SUBQUERY))
    {
      if (pc_lhs == PC_MULTI_ATTR)
    {
      for (lhs = arg1->info.function.arg_list; lhs; lhs = lhs->next)
        {
          /* get index cardinality */
          icard = qo_index_cardinality (env, lhs);
          if (icard > 0.0)
        {
          equal_selectivity *= (1.0 / icard);
        }
          else
        {
          /* If no index, multiply by default selectivity for each attribute */
          equal_selectivity *= DEFAULT_EQUAL_SELECTIVITY;
        }
        }
    }
      else if (pc_lhs == PC_ATTR)
    {
      /* check for index on the attribute.  */
      icard = qo_index_cardinality (env, arg1);
      if (icard > 0.0)
        {
          equal_selectivity *= (1.0 / icard);
        }
      else
        {
          equal_selectivity = DEFAULT_EQUAL_SELECTIVITY;
        }
    }

      /* determine cardinality of set or subquery */
      if (pc_rhs == PC_SET)
    {
      if (pt_is_function (arg2))
        {
          list_card = pt_length_of_list (arg2->info.function.arg_list);
        }
      else
        {
          list_card = pt_length_of_list (arg2->info.value.data_value.set);
        }
    }
      else if (pc_rhs == PC_SUBQUERY)
    {
      if (arg2->info.query.xasl)
        {
          list_card = ((XASL_NODE *) arg2->info.query.xasl)->cardinality;
        }
      else
        {
          /* legacy default list_card is 1000. Maybe it won't come in here */
          list_card = 1000.0;
        }
    }

      /* compute selectivity--cap at 0.5 */
      double in_selectivity = list_card * equal_selectivity;
      return in_selectivity > 0.5 ? 0.5 : in_selectivity;
    }

  return DEFAULT_IN_SELECTIVITY;
}

/*
 * qo_classify () - Determine which predicate class the node belongs in
 *   return: PRED_CLASS
 *   attr(in): pt node to classify
 */
static PRED_CLASS
qo_classify (PT_NODE * attr)
{
  switch (attr->node_type)
    {
    case PT_NAME:
    case PT_DOT_:
      return PC_ATTR;

    case PT_VALUE:
      if (PT_IS_SET_TYPE (attr))
    {
      return PC_SET;
    }
      else if (attr->type_enum == PT_TYPE_NULL)
    {
      return PC_OTHER;
    }
      else
    {
      return PC_CONST;
    }

    case PT_HOST_VAR:
      return PC_HOST_VAR;

    case PT_SELECT:
    case PT_UNION:
    case PT_INTERSECTION:
    case PT_DIFFERENCE:
      return PC_SUBQUERY;

    case PT_FUNCTION:
      /* (attr,attr) or (?,?) */
      if (PT_IS_SET_TYPE (attr))
    {
      PT_NODE *func_arg;
      func_arg = attr->info.function.arg_list;
      for ( /* none */ ; func_arg; func_arg = func_arg->next)
        {
          if (func_arg->node_type == PT_NAME)
        {
          /* none */
        }
          else if (func_arg->node_type == PT_HOST_VAR)
        {
          return PC_SET;
        }
          else
        {
          return PC_OTHER;
        }
        }
      return PC_MULTI_ATTR;
    }
      else
    {
      return PC_OTHER;
    }

    case PT_EXPR:
      if (pt_is_function_index_expression (attr))
    {
      return PC_ATTR;
    }
      [[fallthrough]];
    default:
      return PC_OTHER;
    }
}

/*
 * qo_index_cardinality () - Determine if the attribute has an index
 *   return: cardinality of the index if the index exists, otherwise return 0
 *   env(in): optimizer environment
 *   attr(in): pt node for the attribute for which we want the index cardinality
 */
static int
qo_index_cardinality (QO_ENV * env, PT_NODE * attr)
{
  PT_NODE *dummy;
  QO_NODE *nodep;
  QO_SEGMENT *segp;
  QO_ATTR_INFO *info;

  if (attr->node_type == PT_DOT_)
    {
      attr = attr->info.dot.arg2;
    }

  QO_ASSERT (env, (attr->node_type == PT_NAME || pt_is_function_index_expression (attr)));

  nodep = lookup_node (attr, env, &dummy);
  if (nodep == NULL)
    {
      return 0;
    }

  segp = lookup_seg (nodep, attr, env);
  if (segp == NULL)
    {
      return 0;
    }

  if (attr->info.name.meta_class == PT_RESERVED)
    {
      return 0;
    }

  info = QO_SEG_INFO (segp);
  if (info == NULL)
    {
      return 0;
    }

  if (info->ndv > 0)
    {
      int ndv = (info->ndv > INT_MAX) ? INT_MAX : info->ndv;    /* need to change type to INT64 */

      if (info->cum_stats.is_indexed == true && info->cum_stats.pkeys[0] > 0)
    {
      /* Choose the better NDV of the two. */
      return MIN (ndv, info->cum_stats.pkeys[0]);
    }
      return ndv;
    }

  if (info->cum_stats.is_indexed != true)
    {
      return 0;
    }

  QO_ASSERT (env, info->cum_stats.pkeys_size > 0);
  QO_ASSERT (env, info->cum_stats.pkeys_size <= BTREE_STATS_PKEYS_NUM);
  QO_ASSERT (env, info->cum_stats.pkeys != NULL);

  /* return number of the first partial-key of the index on the attribute shown in the expression */
  return info->cum_stats.pkeys[0];
}

/*
 * qo_index_cardinality_with_dedup () - Determine if the attribute has an index with duplicate column checking
 *   return: cardinality of the index if the index exists, otherwise return 0
 *   env(in): optimizer environment
 *   attr(in): pt node for the attribute for which we want the index cardinality
 *   seg_bitset(in): segment bitset for checking if there are duplicate columns
 */
static int
qo_index_cardinality_with_dedup (QO_ENV * env, PT_NODE * attr, BITSET * seg_bitset)
{
  PT_NODE *dummy;
  QO_NODE *nodep;
  QO_SEGMENT *segp;
  QO_ATTR_INFO *info;

  if (attr->node_type == PT_DOT_)
    {
      attr = attr->info.dot.arg2;
    }

  QO_ASSERT (env, (attr->node_type == PT_NAME || pt_is_function_index_expression (attr)));

  nodep = lookup_node (attr, env, &dummy);
  if (nodep == NULL)
    {
      return 0;
    }

  segp = lookup_seg (nodep, attr, env);
  if (segp == NULL)
    {
      return 0;
    }

  /* check if there are duplicate columns */
  if (seg_bitset)
    {
      if (BITSET_MEMBER (*seg_bitset, QO_SEG_IDX (segp)))
    {
      return 0;
    }
      else
    {
      bitset_add (seg_bitset, QO_SEG_IDX (segp));
    }
    }

  if (attr->info.name.meta_class == PT_RESERVED)
    {
      return 0;
    }

  info = QO_SEG_INFO (segp);
  if (info == NULL)
    {
      return 0;
    }

  if (info->ndv > 0)
    {
      int ndv = (info->ndv > INT_MAX) ? INT_MAX : info->ndv;    /* need to change type to INT64 */

      if (info->cum_stats.is_indexed == true && info->cum_stats.pkeys[0] > 0)
    {
      /* Choose the better NDV of the two. */
      return MIN (ndv, info->cum_stats.pkeys[0]);
    }
      return ndv;
    }

  if (info->cum_stats.is_indexed != true)
    {
      return 0;
    }

  QO_ASSERT (env, info->cum_stats.pkeys_size > 0);
  QO_ASSERT (env, info->cum_stats.pkeys_size <= BTREE_STATS_PKEYS_NUM);
  QO_ASSERT (env, info->cum_stats.pkeys != NULL);

  /* return number of the first partial-key of the index on the attribute shown in the expression */
  return info->cum_stats.pkeys[0];
}

/*
 * qo_is_all_unique_index_columns_are_equi_terms () -
 *   check if the current plan uses and
 *   index scan with all_unique_index_columns_are_equi_terms
 *
 * return    : true/false
 * plan (in) : plan to verify
 */
bool
qo_is_all_unique_index_columns_are_equi_terms (QO_PLAN * plan)
{
  if (qo_is_iscan (plan) && plan->plan_un.scan.index && plan->plan_un.scan.index->head
      && (plan->plan_un.scan.index->head->all_unique_index_columns_are_equi_terms))
    {
      return true;
    }
  return false;
}

/*
 * qo_is_iscan_from_orderby ()
 *   return: true/false
 *   plan(in):
 */
bool
qo_is_iscan_from_orderby (QO_PLAN * plan)
{
  if (plan && plan->plan_type == QO_PLANTYPE_SCAN && plan->plan_un.scan.scan_method == QO_SCANMETHOD_INDEX_ORDERBY_SCAN)
    {
      return true;
    }

  return false;
}

/*
 * qo_validate_index_term_notnull ()
 *   return: true/false
 */
static bool
qo_validate_index_term_notnull (QO_ENV * env, QO_INDEX_ENTRY * index_entryp)
{
  bool term_notnull = false;    /* init */
  PT_NODE *node;
  const char *node_name;
  QO_CLASS_INFO_ENTRY *index_class;
  int t;
  QO_TERM *termp;
  int iseg;
  QO_SEGMENT *segp;

  assert (env != NULL);
  assert (index_entryp != NULL);
  assert (index_entryp->class_ != NULL);

  index_class = index_entryp->class_;

  /* do a check on the first column - it should be present in the where clause check if exists a simple expression
   * with PT_IS_NOT_NULL on the first key this should not contain OR operator and the PT_IS_NOT_NULL should contain the
   * column directly as parameter (PT_NAME)
   */
  for (t = 0; t < env->nterms && !term_notnull; t++)
    {
      /* get the pointer to QO_TERM structure */
      termp = QO_ENV_TERM (env, t);
      assert (termp != NULL);
      if (QO_ON_COND_TERM (termp))
    {
      continue;
    }

      node = QO_TERM_PT_EXPR (termp);
      if (node == NULL)
    {
      continue;
    }

      if (node && node->or_next)
    {
      continue;
    }

      if (node->node_type == PT_EXPR && node->info.expr.op == PT_IS_NOT_NULL
      && node->info.expr.arg1->node_type == PT_NAME)
    {
      iseg = index_entryp->seg_idxs[0];
      if (iseg != -1 && BITSET_MEMBER (QO_TERM_SEGS (termp), iseg))
        {
          /* check it's the same column as the first in the index */
          node_name = pt_get_name (node->info.expr.arg1);
          segp = QO_ENV_SEG (env, iseg);
          assert (segp != NULL);
          if (!intl_identifier_casecmp (node_name, QO_SEG_NAME (segp)))
        {
          /* we have found a term with no OR and with IS_NOT_NULL on our key. The plan is ready for group by
           * skip!
           */
          term_notnull = true;
          break;
        }
        }
    }
    }

  return term_notnull;
}

/*
 * qo_validate_index_attr_notnull ()
 *   return: true/false
 */
static bool
qo_validate_index_attr_notnull (QO_ENV * env, QO_INDEX_ENTRY * index_entryp, PT_NODE * col)
{
  bool attr_notnull = false;    /* init */
  QO_NODE *node;
  PT_NODE *dummy;
  int i;
  QO_CLASS_INFO_ENTRY *index_class;
  QO_SEGMENT *segp = NULL;
  SM_ATTRIBUTE *attr;
  void *env_seg[2];

  /* key_term_status is -1 if no term with key, 0 if isnull or is not null terms with key and 1 if other term with key */
  int old_bail_out, key_term_status;

  assert (env != NULL);
  assert (index_entryp != NULL);
  assert (index_entryp->class_ != NULL);
  assert (index_entryp->class_->smclass != NULL);
  assert (col != NULL);

  index_class = index_entryp->class_;

  if (col->node_type != PT_NAME)
    {
      return false;     /* give up */
    }

  node = lookup_node (col, env, &dummy);
  if (node == NULL)
    {
      return false;
    }

  segp = lookup_seg (node, col, env);
  if (segp == NULL)
    {               /* is invalid case */
      assert (false);
      return false;
    }

  for (i = 0; i < index_entryp->col_num; i++)
    {
      if (index_entryp->seg_idxs[i] == QO_SEG_IDX (segp))
    {
      break;        /* found */
    }
    }
  if (i >= index_entryp->col_num)
    {
      /* col is not included in this index */
      return false;
    }

  assert (segp != NULL);
#if !defined(NDEBUG)
  {
    const char *col_name = pt_get_name (col);

    assert (!intl_identifier_casecmp (QO_SEG_NAME (segp), col_name));
  }
#endif

  /* we now search in the class columns for the index key */
  for (i = 0; i < index_class->smclass->att_count; i++)
    {
      attr = &index_class->smclass->attributes[i];
      if (attr && !intl_identifier_casecmp (QO_SEG_NAME (segp), attr->header.name))
    {
      if (attr->flags & SM_ATTFLAG_NON_NULL)
        {
          attr_notnull = true;
        }
      else
        {
          attr_notnull = false;
        }

      break;
    }
    }
  if (i >= index_class->smclass->att_count)
    {
      /* column wasn't found - this should not happen! */
      assert (false);
      return false;
    }

  /* now search for not terms with the key */
  if (attr_notnull != true)
    {
      /* save old value of bail_out */
      old_bail_out = env->bail_out;
      env->bail_out = -1;   /* no term found value */

      /* check for isnull terms with the key */
      env_seg[0] = (void *) env;
      env_seg[1] = (void *) segp;
      parser_walk_tree (env->parser, QO_ENV_PT_TREE (env)->info.query.q.select.where, qo_search_isnull_key_expr,
            env_seg, NULL, NULL);

      /* restore old value and keep walk_tree result in key_term_status */
      key_term_status = env->bail_out;
      env->bail_out = old_bail_out;

      /* if there is no isnull on the key, check that the key appears in some term and if so, make sure that that term
       * doesn't have a OR
       */
      if (key_term_status == 1)
    {
      BITSET expr_segments, key_segment;
      QO_TERM *termp;
      PT_NODE *pt_expr;

      bitset_init (&expr_segments, env);
      bitset_init (&key_segment, env);

      /* key segment bitset */
      bitset_add (&key_segment, QO_SEG_IDX (segp));

      /* key found in a term */
      for (i = 0; i < env->nterms; i++)
        {
          termp = QO_ENV_TERM (env, i);
          assert (termp != NULL);

          pt_expr = QO_TERM_PT_EXPR (termp);
          if (pt_expr == NULL)
        {
          continue;
        }

          if (pt_expr->or_next)
        {
          BITSET_CLEAR (expr_segments);

          qo_expr_segs (env, pt_expr, &expr_segments);
          if (bitset_intersects (&expr_segments, &key_segment))
            {
              break;    /* give up */
            }
        }
        }

      if (i >= env->nterms)
        {
          attr_notnull = true;  /* OK */
        }

      bitset_delset (&key_segment);
      bitset_delset (&expr_segments);
    }
    }

  return attr_notnull;
}

/*
 * qo_validate_index_for_orderby () - checks for isnull(key) or not null flag
 *  env(in): pointer to the optimizer environment
 *  ni_entryp(in): pointer to QO_NODE_INDEX_ENTRY (node index entry)
 *  return: 1 if the index can be used, 0 elseware
 */
static int
qo_validate_index_for_orderby (QO_ENV * env, QO_NODE_INDEX_ENTRY * ni_entryp)
{
  bool key_notnull = false; /* init */
  QO_INDEX_ENTRY *index_entryp;
  QO_CLASS_INFO_ENTRY *index_class;

  int pos;
  PT_NODE *node = NULL;

  assert (ni_entryp != NULL);
  assert (ni_entryp->head != NULL);
  assert (ni_entryp->head->class_ != NULL);

  index_entryp = ni_entryp->head;
  index_class = index_entryp->class_;

  if (!QO_ENV_PT_TREE (env) || !QO_ENV_PT_TREE (env)->info.query.order_by)
    {
      goto end;
    }

  key_notnull = qo_validate_index_term_notnull (env, index_entryp);
  if (key_notnull)
    {
      goto final_;
    }

  pos = QO_ENV_PT_TREE (env)->info.query.order_by->info.sort_spec.pos_descr.pos_no;
  node = QO_ENV_PT_TREE (env)->info.query.q.select.list;

  while (pos > 1 && node)
    {
      node = node->next;
      pos--;
    }
  if (!node)
    {
      goto end;
    }

  if (node->node_type == PT_EXPR && node->info.expr.op == PT_CAST)
    {
      node = node->info.expr.arg1;
      if (!node)
    {
      goto end;
    }
    }

  node = pt_get_end_path_node (node);

  assert (key_notnull == false);

  key_notnull = qo_validate_index_attr_notnull (env, index_entryp, node);
  if (key_notnull)
    {
      goto final_;
    }

  /* Now we have the information we need: if the key column can be null and if there is a PT_IS_NULL or PT_IS_NOT_NULL
   * expression with this key column involved and also if we have other terms with the key. We must decide if there can
   * be NULLs in the results and if so, drop this index. 1. If the key cannot have null values, we have a winner. 2.
   * Otherwise, if we found a term isnull/isnotnull(key) we drop it (because we cannot evaluate if this yields true or
   * false so we skip all, for safety) 3. If we have a term with other operator except isnull/isnotnull and does not
   * have an OR following we have a winner again! (because we cannot have a null value).
   */
final_:
  if (key_notnull)
    {
      return 1;
    }
end:
  return 0;
}

/*
 * qo_search_isnull_key_expr () -
 *   return: PT_NODE *
 *   parser(in): parser environment
 *   tree(in): tree to walk
 *   arg(in):
 *   continue_walk(in):
 *
 * Note: for env->bail_out values, check key_term_status in
 *    qo_validate_index_for_groupby, qo_validate_index_for_orderby
 */
static PT_NODE *
qo_search_isnull_key_expr (PARSER_CONTEXT * parser, PT_NODE * tree, void *arg, int *continue_walk)
{
  BITSET expr_segments, key_segment;
  QO_ENV *env;
  QO_SEGMENT *segm;
  void **env_seg = (void **) arg;

  env = (QO_ENV *) env_seg[0];
  segm = (QO_SEGMENT *) env_seg[1];

  *continue_walk = PT_CONTINUE_WALK;

  /* key segment bitset */
  bitset_init (&key_segment, env);
  bitset_add (&key_segment, QO_SEG_IDX (segm));

  bitset_init (&expr_segments, env);
  if (tree->node_type == PT_EXPR)
    {
      /* get all segments in this expression */
      qo_expr_segs (env, tree, &expr_segments);

      /* now check if the key segment is in there */
      if (bitset_intersects (&expr_segments, &key_segment))
    {
      int nullable_terms = 0;
      qo_check_nullable_expr (parser, tree, &nullable_terms, NULL);
      /* this expr contains the key segment */
      if (nullable_terms >= 1)
        {
          /* 0 all the way, suppress other terms found */
          env->bail_out = 0;
          *continue_walk = PT_STOP_WALK;

          return tree;
        }
      else if (env->bail_out == -1)
        {
          /* set as 1 only if we haven't found any isnull terms */
          env->bail_out = 1;
        }
    }
    }

  return tree;
}

/*
 * qo_get_col_product_ndv () -
 *   return: PT_NODE *
 *   parser(in): parser environment
 *   tree(in): tree to walk
 *   arg(in):
 *   continue_walk(in):
 *
 * Note: get product of NDV of each column on GROUP BY
 */
static PT_NODE *
qo_get_col_product_ndv (PARSER_CONTEXT * parser, PT_NODE * tree, void *arg, int *continue_walk)
{
  NDV_INFO *ndv_info = (NDV_INFO *) arg;
  int ndv;

  *continue_walk = PT_CONTINUE_WALK;

  if (PT_IS_QUERY_NODE_TYPE (tree->node_type))
    {
      *continue_walk = PT_LIST_WALK;
      return tree;
    }
  else if (pt_is_attr (tree))
    {
      ndv = qo_index_cardinality_with_dedup (ndv_info->env, pt_get_end_path_node (tree), &ndv_info->seg_bitset);
      ndv_info->total_ndv *= (ndv == 0) ? 1 : ndv;
    }

  return tree;
}

/*
 * qo_plan_iscan_terms_cmp () - compare 2 index scan plans with terms
 *   return:  one of {PLAN_COMP_UNK, PLAN_COMP_LT, PLAN_COMP_EQ, PLAN_COMP_GT}
 *   a(in):
 *   b(in):
 */
static QO_PLAN_COMPARE_RESULT
qo_plan_iscan_terms_cmp (QO_PLAN * a, QO_PLAN * b)
{
  QO_NODE_INDEX_ENTRY *a_ni, *b_ni;
  QO_INDEX_ENTRY *a_ent, *b_ent;
  QO_ATTR_CUM_STATS *a_cum, *b_cum;
  int a_range, b_range;     /* num iscan range terms */
  int a_filter, b_filter;   /* num iscan filter terms */

  if (QO_NODE_IDX (a->plan_un.scan.node) != QO_NODE_IDX (b->plan_un.scan.node))
    {
      return PLAN_COMP_UNK;
    }

  if (!qo_is_interesting_order_scan (a) || !qo_is_interesting_order_scan (b))
    {
      assert_release (qo_is_interesting_order_scan (a));
      assert_release (qo_is_interesting_order_scan (b));

      return PLAN_COMP_UNK;
    }

  /* index entry of spec 'a' */
  a_ni = a->plan_un.scan.index;
  a_ent = (a_ni)->head;
  a_cum = &(a_ni)->cum_stats;
  assert (a_cum != NULL);

  /* index range terms */
  a_range = bitset_cardinality (&(a->plan_un.scan.terms));
  if (a_range > 0 && !(a->plan_un.scan.index_equi))
    {
      a_range--;        /* set the last equal range term */
    }

  /* index filter terms */
  a_filter = bitset_cardinality (&(a->plan_un.scan.kf_terms));

  /* index entry of spec 'b' */
  b_ni = b->plan_un.scan.index;
  b_ent = (b_ni)->head;
  b_cum = &(b_ni)->cum_stats;
  assert (b_cum != NULL);

  /* index range terms */
  b_range = bitset_cardinality (&(b->plan_un.scan.terms));
  if (b_range > 0 && !(b->plan_un.scan.index_equi))
    {
      b_range--;        /* set the last equal range term */
    }

  /* index filter terms */
  b_filter = bitset_cardinality (&(b->plan_un.scan.kf_terms));

  assert (a_range >= 0);
  assert (b_range >= 0);

  /* STEP 1: check by terms containment */

  if (bitset_is_equivalent (&(a->plan_un.scan.terms), &(b->plan_un.scan.terms)))
    {
      /* both plans have the same range terms we will check now the key filter terms */
      if (a_filter > b_filter)
    {
      return PLAN_COMP_LT;
    }
      else if (a_filter < b_filter)
    {
      return PLAN_COMP_GT;
    }

      /* prefer covering scan */
      if (qo_is_index_covering_scan (a) && !qo_is_index_covering_scan (b))
    {
      return PLAN_COMP_LT;
    }
      else if (!qo_is_index_covering_scan (a) && qo_is_index_covering_scan (b))
    {
      return PLAN_COMP_GT;
    }

      /* both have the same range terms and same number of filters */
      if (a_cum && b_cum)
    {
      /* take the smaller index pages */
      if (a_cum->pages < b_cum->pages)
        {
          return PLAN_COMP_LT;
        }
      else if (a_cum->pages > b_cum->pages)
        {
          return PLAN_COMP_GT;
        }

      assert (a_cum->pkeys_size <= BTREE_STATS_PKEYS_NUM);
      assert (b_cum->pkeys_size <= BTREE_STATS_PKEYS_NUM);

      /* take the smaller index pkeys_size */
      if (a_cum->pkeys_size < b_cum->pkeys_size)
        {
          return PLAN_COMP_LT;
        }
      else if (a_cum->pkeys_size > b_cum->pkeys_size)
        {
          return PLAN_COMP_GT;
        }
    }

      /* both have the same number of index pages and pkeys_size */
      return PLAN_COMP_EQ;
    }
  else if (a_range > 0 && bitset_subset (&(a->plan_un.scan.terms), &(b->plan_un.scan.terms)))
    {
      return PLAN_COMP_LT;
    }
  else if (b_range > 0 && bitset_subset (&(b->plan_un.scan.terms), &(a->plan_un.scan.terms)))
    {
      return PLAN_COMP_GT;
    }

  /* check if it is an unique index and all columns are equi */
  if (qo_is_all_unique_index_columns_are_equi_terms (a) && !qo_is_all_unique_index_columns_are_equi_terms (b))
    {
      return PLAN_COMP_LT;
    }
  else if (!qo_is_all_unique_index_columns_are_equi_terms (a) && qo_is_all_unique_index_columns_are_equi_terms (b))
    {
      return PLAN_COMP_GT;
    }

  /* STEP 2: check by term cardinality */

  if (a->plan_un.scan.index_equi == b->plan_un.scan.index_equi)
    {
      if (a_range > b_range)
    {
      return PLAN_COMP_LT;
    }
      else if (a_range < b_range)
    {
      return PLAN_COMP_GT;
    }

      assert (a_range == b_range);

      /* both plans have the same number of range terms we will check now the key filter terms */
      if (a_filter > b_filter)
    {
      return PLAN_COMP_LT;
    }
      else if (a_filter < b_filter)
    {
      return PLAN_COMP_GT;
    }

      /* both have the same number of range terms and same number of filters */
      return PLAN_COMP_EQ;
    }

  return PLAN_COMP_EQ;      /* is equal with terms not cost */
}

/*
 * qo_group_by_skip_plans_cmp () - compare 2 index scan plans by group by skip
 *   return:  one of {PLAN_COMP_UNK, PLAN_COMP_LT, PLAN_COMP_EQ, PLAN_COMP_GT}
 *   a(in):
 *   b(in):
 */
static QO_PLAN_COMPARE_RESULT
qo_group_by_skip_plans_cmp (QO_PLAN * a, QO_PLAN * b)
{
  QO_INDEX_ENTRY *a_ent, *b_ent;

  if (!qo_is_interesting_order_scan (a) || !qo_is_interesting_order_scan (b))
    {
      return PLAN_COMP_UNK;
    }

  a_ent = a->plan_un.scan.index->head;
  b_ent = b->plan_un.scan.index->head;

  if (a_ent == NULL || b_ent == NULL)
    {
      assert (false);
      return PLAN_COMP_UNK;
    }

  if (qo_is_index_iss_scan (a) || qo_is_index_loose_scan (a))
    {
      return PLAN_COMP_UNK;
    }

  if (qo_is_index_iss_scan (b) || qo_is_index_loose_scan (b))
    {
      return PLAN_COMP_UNK;
    }

  if (a_ent->groupby_skip)
    {
      if (b_ent->groupby_skip)
    {
      return qo_plan_iscan_terms_cmp (a, b);
    }
      else
    {
      return PLAN_COMP_LT;
    }
    }
  else
    {
      if (b_ent->groupby_skip)
    {
      return PLAN_COMP_GT;
    }
    }

  return PLAN_COMP_EQ;
}

/*
 * qo_order_by_skip_plans_cmp () - compare 2 index scan plans by order by skip
 *   return:  one of {PLAN_COMP_UNK, PLAN_COMP_LT, PLAN_COMP_EQ, PLAN_COMP_GT}
 *   a(in):
 *   b(in):
 */
static QO_PLAN_COMPARE_RESULT
qo_order_by_skip_plans_cmp (QO_PLAN * a, QO_PLAN * b)
{
  QO_INDEX_ENTRY *a_ent, *b_ent;

  if (!qo_is_interesting_order_scan (a) || !qo_is_interesting_order_scan (b))
    {
      return PLAN_COMP_UNK;
    }

  a_ent = a->plan_un.scan.index->head;
  b_ent = b->plan_un.scan.index->head;

  if (a_ent == NULL || b_ent == NULL)
    {
      assert (false);
      return PLAN_COMP_UNK;
    }

  if (qo_is_index_iss_scan (a) || qo_is_index_loose_scan (a))
    {
      return PLAN_COMP_UNK;
    }

  if (qo_is_index_iss_scan (b) || qo_is_index_loose_scan (b))
    {
      return PLAN_COMP_UNK;
    }

  if (a_ent->orderby_skip)
    {
      if (b_ent->orderby_skip)
    {
      return qo_plan_iscan_terms_cmp (a, b);
    }
      else
    {
      return PLAN_COMP_LT;
    }
    }
  else
    {
      if (b_ent->orderby_skip)
    {
      return PLAN_COMP_GT;
    }
    }

  return PLAN_COMP_EQ;
}

/*
 * qo_check_orderby_skip_descending - checks whether an index plan will
 *      skip order by if its columns changes their direction.
 *   return:  true or false
 *   plan (in): input index plan to be analyzed
 */
static bool
qo_check_orderby_skip_descending (QO_PLAN * plan)
{
  bool orderby_skip = false;
  QO_ENV *env;
  PT_NODE *tree, *trav, *order_by;

  env = NULL;
  tree = order_by = NULL;

  if (plan == NULL)
    {
      return false;
    }
  if (plan->info)
    {
      env = plan->info->env;
    }

  if (env == NULL)
    {
      return false;
    }

  tree = QO_ENV_PT_TREE (env);

  if (tree == NULL)
    {
      return false;
    }

  if (tree->info.query.q.select.hint & PT_HINT_NO_IDX_DESC)
    {
      return false;
    }

  order_by = tree->info.query.order_by;

  for (trav = plan->iscan_sort_list; trav; trav = trav->next)
    {
      /* change PT_ASC to PT_DESC and vice-versa */
      trav->info.sort_spec.asc_or_desc = (PT_MISC_TYPE) (PT_ASC + PT_DESC - trav->info.sort_spec.asc_or_desc);
    }

  /* test again the order by skip */
  orderby_skip = pt_sort_spec_cover (plan->iscan_sort_list, order_by);

  /* change back directions */
  for (trav = plan->iscan_sort_list; trav; trav = trav->next)
    {
      /* change PT_ASC to PT_DESC and vice-versa */
      trav->info.sort_spec.asc_or_desc = (PT_MISC_TYPE) (PT_ASC + PT_DESC - trav->info.sort_spec.asc_or_desc);
    }

  return orderby_skip;
}

/*
 * qo_check_skip_term - checks whether term can be skipped.
 *      skip term which is already logically evaluated.
 *   return:  true or false
 *   plan (in): input index plan to be analyzed
 */
static bool
qo_check_skip_term (QO_ENV * env, BITSET visited_segs, QO_TERM * term, BITSET * visited_terms,
            BITSET * cur_visited_terms)
{
  BITSET remaining_terms, connected_segs, all_visited_terms, eq_visited_segs;
  BITSET_ITERATOR bi;
  QO_TERM *tmp_term;
  int i, prev_card;
  bool result;

  /* check unvisited segments */
  if (!bitset_subset (&visited_segs, &(QO_TERM_SEGS (term))))
    {
      return false;
    }
  bitset_init (&remaining_terms, env);
  bitset_init (&connected_segs, env);
  bitset_init (&all_visited_terms, env);
  bitset_init (&eq_visited_segs, env);

  /* gather terms having same eqclass */
  bitset_union (&all_visited_terms, visited_terms);
  bitset_union (&all_visited_terms, cur_visited_terms);

  for (i = bitset_iterate (&all_visited_terms, &bi); i != -1; i = bitset_next_member (&bi))
    {
      tmp_term = QO_ENV_TERM (env, i);

      if (QO_TERM_EQCLASS (tmp_term) == QO_TERM_EQCLASS (term))
    {
      bitset_add (&remaining_terms, i);
      bitset_union (&eq_visited_segs, &(QO_TERM_SEGS (tmp_term)));
    }
    }

  /* check number of remaining terms. at least n-1 terms can be fully connected. */
  if (bitset_cardinality (&remaining_terms) < bitset_cardinality (&eq_visited_segs) - 1)
    {
      result = false;
      goto end;
    }

  /* check whether segments of eqclass are fully connected */
  prev_card = bitset_cardinality (&remaining_terms);
  while (!bitset_is_empty (&remaining_terms))
    {
      for (i = bitset_iterate (&remaining_terms, &bi); i != -1; i = bitset_next_member (&bi))
    {
      tmp_term = QO_ENV_TERM (env, i);

      if (bitset_is_empty (&connected_segs) || bitset_intersects (&connected_segs, &(QO_TERM_SEGS (tmp_term))))
        {
          /* first time or connected segs */
          bitset_union (&connected_segs, &(QO_TERM_SEGS (tmp_term)));
          bitset_remove (&remaining_terms, i);
        }
    }

      if (prev_card == bitset_cardinality (&remaining_terms))
    {
      /* There are no more connected terms. */
      break;
    }
      prev_card = bitset_cardinality (&remaining_terms);
    }

  if (bitset_subset (&connected_segs, &(QO_TERM_SEGS (term))))
    {
      /* already evaluated */
      result = true;
    }
  else
    {
      result = false;
    }

end:
  bitset_delset (&remaining_terms);
  bitset_delset (&connected_segs);
  bitset_delset (&all_visited_terms);
  bitset_delset (&eq_visited_segs);

  return result;
}

/*
 * qo_is_iscan_from_groupby ()
 *   return: true/false
 *   plan(in):
 */
bool
qo_is_iscan_from_groupby (QO_PLAN * plan)
{
  if (plan && plan->plan_type == QO_PLANTYPE_SCAN && plan->plan_un.scan.scan_method == QO_SCANMETHOD_INDEX_GROUPBY_SCAN)
    {
      return true;
    }

  return false;
}

/*
 * qo_validate_index_for_groupby () - checks for isnull(key) or not null flag
 *  env(in): pointer to the optimizer environment
 *  ni_entryp(in): pointer to QO_NODE_INDEX_ENTRY (node index entry)
 *  return: 1 if the index can be used, 0 elseware
 */
static int
qo_validate_index_for_groupby (QO_ENV * env, QO_NODE_INDEX_ENTRY * ni_entryp)
{
  bool key_notnull = false; /* init */
  QO_INDEX_ENTRY *index_entryp;
  QO_CLASS_INFO_ENTRY *index_class;

  PT_NODE *groupby_expr = NULL;

  assert (ni_entryp != NULL);
  assert (ni_entryp->head != NULL);
  assert (ni_entryp->head->class_ != NULL);

  index_entryp = ni_entryp->head;
  index_class = index_entryp->class_;

  if (!QO_ENV_PT_TREE (env) || !QO_ENV_PT_TREE (env)->info.query.q.select.group_by)
    {
      goto end;
    }

  key_notnull = qo_validate_index_term_notnull (env, index_entryp);
  if (key_notnull)
    {
      goto final;
    }

  /* get the name of the first column in the group by list */
  groupby_expr = QO_ENV_PT_TREE (env)->info.query.q.select.group_by->info.sort_spec.expr;

  assert (key_notnull == false);

  key_notnull = qo_validate_index_attr_notnull (env, index_entryp, groupby_expr);
  if (key_notnull)
    {
      goto final;
    }

  /* Now we have the information we need: if the key column can be null and if there is a PT_IS_NULL or PT_IS_NOT_NULL
   * expression with this key column involved and also if we have other terms with the key. We must decide if there can
   * be NULLs in the results and if so, drop this index. 1. If the key cannot have null values, we have a winner. 2.
   * Otherwise, if we found a term isnull/isnotnull(key) we drop it (because we cannot evaluate if this yields true or
   * false so we skip all, for safety) 3. If we have a term with other operator except isnull/isnotnull and does not
   * have an OR following we have a winner again! (because we cannot have a null value).
   */
final:
  if (key_notnull)
    {
      return 1;
    }
end:
  return 0;
}

/*
 * qo_check_groupby_skip_descending - checks whether an index plan will
 *      skip group by if its columns changes their direction.
 *   return:  true or false
 *   plan (in): input index plan to be analyzed
 */
static bool
qo_check_groupby_skip_descending (QO_PLAN * plan, PT_NODE * list)
{
  bool groupby_skip = false;
  QO_ENV *env;
  PT_NODE *tree, *trav, *group_by;

  env = NULL;
  tree = group_by = NULL;

  if (plan == NULL)
    {
      return false;
    }

  if (plan->info)
    {
      env = plan->info->env;
    }

  if (env == NULL)
    {
      return false;
    }

  tree = QO_ENV_PT_TREE (env);

  if (tree == NULL)
    {
      return false;
    }

  if (tree->info.query.q.select.hint & PT_HINT_NO_IDX_DESC)
    {
      return false;
    }

  group_by = tree->info.query.q.select.group_by;

  for (trav = list; trav; trav = trav->next)
    {
      /* change PT_ASC to PT_DESC and vice-versa */
      trav->info.sort_spec.asc_or_desc = (PT_MISC_TYPE) (PT_ASC + PT_DESC - trav->info.sort_spec.asc_or_desc);
    }

  /* test again the group by skip */
  groupby_skip = pt_sort_spec_cover_groupby (env->parser, list, group_by, tree);

  /* change back directions */
  for (trav = list; trav; trav = trav->next)
    {
      /* change PT_ASC to PT_DESC and vice-versa */
      trav->info.sort_spec.asc_or_desc = (PT_MISC_TYPE) (PT_ASC + PT_DESC - trav->info.sort_spec.asc_or_desc);
    }

  return groupby_skip;
}

/*
 * qo_plan_compute_iscan_sort_list () -
 *   return: sort_list
 *   root(in):
 *   group_by(in):
 *   is_index_w_prefix(out):
 *
 */
PT_NODE *
qo_plan_compute_iscan_sort_list (QO_PLAN * root, PT_NODE * group_by, bool * is_index_w_prefix,
                 bool for_min_max_optimize)
{
  QO_PLAN *plan;
  QO_ENV *env;
  PARSER_CONTEXT *parser;
  PT_NODE *tree, *sort_list, *sort, *col, *node, *expr;
  QO_NODE_INDEX_ENTRY *ni_entryp;
  QO_INDEX_ENTRY *index_entryp;
  int nterms, equi_nterms, seg_idx, i, j;
  QO_SEGMENT *seg;
  PT_MISC_TYPE asc_or_desc;
  QFILE_TUPLE_VALUE_POSITION pos_descr;
  TP_DOMAIN *key_type, *col_type;
  BITSET *terms;
  BITSET_ITERATOR bi;
  bool is_const_eq_term;

  sort_list = NULL;     /* init */
  col = NULL;
  *is_index_w_prefix = false;

  /* find sortable plan */
  plan = root;
  while (plan && plan->plan_type != QO_PLANTYPE_SCAN)
    {
      switch (plan->plan_type)
    {
    case QO_PLANTYPE_FOLLOW:
      plan = plan->plan_un.follow.head;
      break;

    case QO_PLANTYPE_JOIN:
      plan = plan->plan_un.join.outer;
      break;

    case QO_PLANTYPE_SORT:
      plan = plan->plan_un.sort.subplan;
      break;

    default:
      plan = NULL;
      break;
    }
    }

  /* check for plan type */
  if (plan == NULL || plan->plan_type != QO_PLANTYPE_SCAN)
    {
      goto exit_on_end;     /* nop */
    }
  else if (QO_NODE_INFO (plan->plan_un.scan.node) == NULL)
    {
      /* if there's no class information or the class is not normal class */
      goto exit_on_end;     /* nop */
    }
  else if (QO_NODE_IS_CLASS_HIERARCHY (plan->plan_un.scan.node))
    {
      /* exclude class hierarchy scan */
      goto exit_on_end;     /* nop */
    }

  /* check for index scan plan */
  if (!qo_is_interesting_order_scan (plan) || (env = (plan->info)->env) == NULL
      || (parser = QO_ENV_PARSER (env)) == NULL || (tree = QO_ENV_PT_TREE (env)) == NULL)
    {
      goto exit_on_end;     /* nop */
    }

  /* pointer to QO_NODE_INDEX_ENTRY structure in QO_PLAN */
  ni_entryp = plan->plan_un.scan.index;
  /* pointer to linked list of index node, 'head' field(QO_INDEX_ENTRY strucutre) of QO_NODE_INDEX_ENTRY */
  index_entryp = (ni_entryp)->head;

  nterms = bitset_cardinality (&(plan->plan_un.scan.terms));
  if (nterms > 0)
    {
      equi_nterms = plan->plan_un.scan.index_equi ? nterms : nterms - 1;
    }
  else
    {
      equi_nterms = 0;
    }
  assert (equi_nterms >= 0);

  if (index_entryp->rangelist_seg_idx != -1)
    {
      equi_nterms = MIN (equi_nterms, index_entryp->rangelist_seg_idx);
    }

  /* we must have the first index column appear as the first sort column, so we pretend the number of index_equi
   * columns is zero, to force it to match the sort list and the index columns one-for-one.
   */
  if (qo_is_index_iss_scan (plan))
    {
      equi_nterms = 0;
    }
  assert (equi_nterms >= 0);
  assert (equi_nterms <= index_entryp->nsegs);

  if (equi_nterms >= index_entryp->nsegs)
    {
      /* is all constant col's order node */
      goto exit_on_end;     /* nop */
    }

  /* check if this is an index with prefix */
  *is_index_w_prefix = qo_is_prefix_index (index_entryp);

  asc_or_desc = (SM_IS_CONSTRAINT_REVERSE_INDEX_FAMILY (index_entryp->constraints->type) ? PT_DESC : PT_ASC);

  key_type = index_entryp->key_type;
  if (key_type == NULL)
    {               /* is invalid case */
      assert (false);
      goto exit_on_end;     /* nop */
    }

  if (asc_or_desc == PT_DESC)
    {
      col_type = NULL;      /* nop; do not care asc_or_desc anymore */
    }
  else
    {
      if (TP_DOMAIN_TYPE (key_type) == DB_TYPE_MIDXKEY)
    {
      assert (QO_ENTRY_MULTI_COL (index_entryp));

      col_type = key_type->setdomain;
      assert (col_type != NULL);

      /* get the first non-equal range key domain */
      for (j = 0; j < equi_nterms && col_type; j++)
        {
          col_type = col_type->next;
        }
    }
      else
    {
      col_type = key_type;

      assert (col_type != NULL);
      assert (col_type->next == NULL);
      assert (equi_nterms <= 1);

      /* get the first non-equal range key domain */
      if (equi_nterms > 0)
        {
          col_type = NULL;  /* give up */
        }
    }

      assert (col_type != NULL || equi_nterms > 0);
    }

  for (i = equi_nterms; i < index_entryp->nsegs; i++)
    {
      if (index_entryp->ils_prefix_len > 0 && i >= index_entryp->ils_prefix_len)
    {
      /* sort list should contain only prefix when using loose index scan */
      break;
    }

      seg_idx = (index_entryp->seg_idxs[i]);
      if (seg_idx == -1)
    {           /* not exist in query */
      break;        /* give up */
    }

      seg = QO_ENV_SEG (env, seg_idx);
      node = QO_SEG_PT_NODE (seg);
      if (node->node_type == PT_DOT_)
    {
      /* FIXME :: we do not handle path-expr here */
      break;        /* give up */
    }


      if (QO_SEG_FUNC_INDEX (seg) == true)
    {
      asc_or_desc = index_entryp->constraints->func_index_info->fi_domain->is_desc ? PT_DESC : PT_ASC;
      if (col_type)
        {
          col_type = col_type->next;
        }
    }
      else
    {
      if (col_type)
        {
          asc_or_desc = (col_type->is_desc) ? PT_DESC : PT_ASC;
          col_type = col_type->next;
        }

      /* skip segment of const eq term */
      terms = &(QO_SEG_INDEX_TERMS (seg));
      is_const_eq_term = false;
      for (j = bitset_iterate (terms, &bi); j != -1; j = bitset_next_member (&bi))
        {
          expr = QO_TERM_PT_EXPR (QO_ENV_TERM (env, j));
          if (PT_IS_EXPR_NODE_WITH_OPERATOR (expr, PT_EQ)
          && (PT_IS_CONST (expr->info.expr.arg1) || PT_IS_CONST (expr->info.expr.arg2)))
        {
          is_const_eq_term = true;
        }
        }
      if (is_const_eq_term)
        {
          continue;
        }
    }

      /* is for order_by skip */

      /* check for constant col's order node */
      pt_to_pos_descr (parser, &pos_descr, node, tree, NULL, for_min_max_optimize);
      if (pos_descr.pos_no > 0)
    {
      col = tree->info.query.q.select.list;
      for (j = 1; j < pos_descr.pos_no && col; j++)
        {
          col = col->next;
        }

      if (col)
        {
          col = pt_get_end_path_node (col);
          if (col && col->node_type == PT_NAME && PT_NAME_INFO_IS_FLAGED (col, PT_NAME_INFO_CONSTANT))
        {
          continue; /* skip out constant order */
        }
        }
    }

      /* is for group_by skip */
      if (group_by != NULL)
    {
      assert (!group_by->flag.with_rollup);

      /* check for constant col's group node */
      pt_to_pos_descr_groupby (parser, &pos_descr, node, tree);
      if (pos_descr.pos_no > 0)
        {
          assert (group_by == tree->info.query.q.select.group_by);
          for (col = group_by, j = 1; j < pos_descr.pos_no && col; j++)
        {
          col = col->next;
        }

          while (col && col->node_type == PT_SORT_SPEC)
        {
          col = col->info.sort_spec.expr;
        }

          if (col)
        {
          col = pt_get_end_path_node (col);
          if (col && col->node_type == PT_NAME && PT_NAME_INFO_IS_FLAGED (col, PT_NAME_INFO_CONSTANT))
            {
              continue; /* skip out constant order */
            }
        }
        }
    }

      if (pos_descr.pos_no <= 0 || col == NULL)
    {           /* not found i-th key element */
      break;        /* give up */
    }

      /* set sort info */
      sort = parser_new_node (parser, PT_SORT_SPEC);
      if (sort == NULL)
    {
      PT_ERRORm (parser, node, MSGCAT_SET_PARSER_SEMANTIC, MSGCAT_SEMANTIC_OUT_OF_MEMORY);
      break;        /* give up */
    }

      sort->info.sort_spec.expr = pt_point (parser, col);
      sort->info.sort_spec.pos_descr = pos_descr;
      sort->info.sort_spec.asc_or_desc = asc_or_desc;

      sort_list = parser_append_node (sort, sort_list);
    }

exit_on_end:

  return sort_list;
}

/*
 * qo_is_interesting_order_scan ()
 *   return: true/false
 *   plan(in):
 */
bool
qo_is_interesting_order_scan (QO_PLAN * plan)
{
  if (qo_is_iscan (plan) || qo_is_iscan_from_groupby (plan) || qo_is_iscan_from_orderby (plan))
    {
      return true;
    }

  return false;
}

/*
 * qo_plan_is_orderby_skip_candidate () - verify if a plan is a candidate for
 *                    orderby skip
 * return : true/false
 * plan (in) : plan to verify
 */
static bool
qo_plan_is_orderby_skip_candidate (QO_PLAN * plan)
{
  PARSER_CONTEXT *parser;
  PT_NODE *order_by, *statement, *entity;
  QO_ENV *env;
  bool is_prefix = false, is_orderby_skip = false;
  bool need_cleanup = false;

  if (plan == NULL || plan->info == NULL)
    {
      assert (false);
      return false;
    }

  env = plan->info->env;

  switch (plan->skip_orderby_opt)
    {
    case QO_PLAN_SKIP_ORDERBY_USE:
    case QO_PLAN_SKIP_ORDERBY_CAN_USE:
      return true;

    case QO_PLAN_SKIP_ORDERBY_CANNOT_USE:
      return false;

    case QO_PLAN_SKIP_ORDERBY_NO:
      /* need check */
      break;

    default:
      /* impossible case */
      assert (false);

      /* need check */
      break;
    }

  parser = QO_ENV_PARSER (env);
  statement = QO_ENV_PT_TREE (env);
  order_by = statement->info.query.order_by;

  if (plan->iscan_sort_list == NULL)
    {
      plan->iscan_sort_list = qo_plan_compute_iscan_sort_list (plan, NULL, &is_prefix, false);
      need_cleanup = true;
    }

  if (plan->iscan_sort_list == NULL || is_prefix)
    {
      is_orderby_skip = false;
      goto cleanup;
    }

  is_orderby_skip = pt_sort_spec_cover (plan->iscan_sort_list, order_by);
  if (!is_orderby_skip)
    {
      /* verify descending */
      is_orderby_skip = qo_check_orderby_skip_descending (plan);
    }

  /*
   * In RIGHT OUTER JOIN, all leading tables are used as null-supplying,
   * so skip ORDER BY cannot be applied.
   * In LEFT OUTER JOIN, trailing tables may also be null-supplying,
   * but not always, so skip ORDER BY can be applied.
   * Since trailing tables in LEFT OUTER JOIN usually have join conditions in the ON clause,
   * a general Index Scan plan is more likely than an Index Scan plan for skip ORDER BY.
   * Here, we only check RIGHT OUTER JOIN.
   */
  if (qo_is_iscan_from_orderby (plan))
    {
      entity = QO_NODE_ENTITY_SPEC (plan->plan_un.scan.node)->next;
      for (; entity != NULL; entity = entity->next)
    {
      if (entity->info.spec.join_type == PT_JOIN_RIGHT_OUTER)
        {
          is_orderby_skip = false;
        }
    }
    }

cleanup:
  if (need_cleanup)
    {
      if (!is_orderby_skip && plan->iscan_sort_list != NULL)
    {
      parser_free_tree (parser, plan->iscan_sort_list);
      plan->iscan_sort_list = NULL;
    }
    }

  if (is_orderby_skip)
    {
      plan->skip_orderby_opt = QO_PLAN_SKIP_ORDERBY_CAN_USE;
    }
  else
    {
      plan->skip_orderby_opt = QO_PLAN_SKIP_ORDERBY_CANNOT_USE;
    }

  return is_orderby_skip;
}

/*
 * qo_is_sort_limit () - verify if plan is a SORT-LIMIT plan
 * return : true/false
 * plan (in) :
 */
static bool
qo_is_sort_limit (QO_PLAN * plan)
{
  return (plan != NULL && plan->plan_type == QO_PLANTYPE_SORT && plan->plan_un.sort.sort_type == SORT_LIMIT);
}

/*
 * qo_has_sort_limit_subplan () - verify if a plan has a SORT-LIMIT subplan
 * return : true if plan has SORT-LIMIT subplan, false otherwise
 * plan (in) : plan to verify
 */
bool
qo_has_sort_limit_subplan (QO_PLAN * plan)
{
  if (plan == NULL)
    {
      return false;
    }

  switch (plan->plan_type)
    {
    case QO_PLANTYPE_SCAN:
      return false;

    case QO_PLANTYPE_SORT:
      if (plan->plan_un.sort.sort_type == SORT_LIMIT)
    {
      return true;
    }
      return qo_has_sort_limit_subplan (plan->plan_un.sort.subplan);

    case QO_PLANTYPE_JOIN:
      return (qo_has_sort_limit_subplan (plan->plan_un.join.outer)
          || qo_has_sort_limit_subplan (plan->plan_un.join.inner));

    case QO_PLANTYPE_FOLLOW:
    case QO_PLANTYPE_WORST:
      return false;
    }

  return false;
}

/*
 * qo_check_like_recompile_candidate () - check if the plan is a LIKE recompile candidate
 * return : true if the plan is a LIKE recompile candidate, false otherwise
 * parser (in) : parser
 * plan (in) : plan to check
 */
static int
qo_check_like_recompile_candidate (QO_PLAN * plan, void *arg)
{
  BITSET terms_set, temp_segs_set;
  int term_idx, seg_idx;
  BITSET_ITERATOR terms_iter;
  QO_ENV *env;
  QO_TERM *termp;
  PT_NODE *expr;
  QO_SEGMENT *seg;

  bool *result = (bool *) arg;

  env = (plan->info)->env;
  bitset_init (&terms_set, env);

  bitset_assign (&terms_set, &(plan->sarged_terms));
  bitset_union (&terms_set, &(plan->plan_un.scan.terms));
  bitset_union (&terms_set, &(plan->plan_un.scan.kf_terms));

  for (term_idx = bitset_iterate (&terms_set, &terms_iter); term_idx != -1; term_idx = bitset_next_member (&terms_iter))
    {
      termp = QO_ENV_TERM (env, term_idx);
      expr = QO_TERM_PT_EXPR (termp);
      if (expr == NULL)
    {
      continue;
    }

      bitset_init (&temp_segs_set, env);

      if (expr->info.expr.op != PT_LIKE)
    {
      continue;
    }

      qo_expr_segs (env, pt_left_part (expr), &temp_segs_set);
      seg_idx = bitset_first_member (&temp_segs_set);
      if (seg_idx == -1)
    {
      continue;
    }

      seg = QO_ENV_SEG (env, seg_idx);
      if (seg->is_not_null)
    {
      *result = true;
      return NO_ERROR;
    }
    }

  return NO_ERROR;
}

int
qo_has_like_recompile_candidate (QO_PLAN * plan, void *arg)
{
  return qo_walk_plan_tree (plan, qo_check_like_recompile_candidate, arg);
}

/*
 * plan dump for query profile
 */

/*
 * qo_plan_scan_print_json ()
 *   return:
 *   plan(in):
 */
static json_t *
qo_plan_scan_print_json (QO_PLAN * plan)
{
  BITSET_ITERATOR bi;
  QO_ENV *env;
  bool natural_desc_index = false;
  json_t *scan, *range, *filter;
  const char *scan_string = "";
  const char *class_name;
  char buf[257] = { '\0', };
  int i;

  scan = json_object ();

  class_name = QO_NODE_NAME (plan->plan_un.scan.node);
  if (class_name == NULL)
    {
      class_name = "unknown";
    }

  json_object_set_new (scan, "table", json_string (class_name));

  switch (plan->plan_un.scan.scan_method)
    {
    case QO_SCANMETHOD_SEQ_SCAN:
      scan_string = "TABLE SCAN";
      break;

    case QO_SCANMETHOD_INDEX_SCAN:
    case QO_SCANMETHOD_INDEX_ORDERBY_SCAN:
    case QO_SCANMETHOD_INDEX_GROUPBY_SCAN:
    case QO_SCANMETHOD_INDEX_SCAN_INSPECT:
      scan_string = "INDEX SCAN";
      json_object_set_new (scan, "index", json_string (plan->plan_un.scan.index->head->constraints->name));

      env = (plan->info)->env;
      range = json_array ();

      for (i = bitset_iterate (&(plan->plan_un.scan.terms), &bi); i != -1; i = bitset_next_member (&bi))
    {
      json_array_append_new (range, json_string (qo_term_string (QO_ENV_TERM (env, i), buf)));
    }

      json_object_set_new (scan, "key range", range);

      if (bitset_cardinality (&(plan->plan_un.scan.kf_terms)) > 0)
    {
      filter = json_array ();
      for (i = bitset_iterate (&(plan->plan_un.scan.kf_terms), &bi); i != -1; i = bitset_next_member (&bi))
        {
          json_array_append_new (filter, json_string (qo_term_string (QO_ENV_TERM (env, i), buf)));
        }

      json_object_set_new (scan, "key filter", filter);
    }

      if (qo_is_index_covering_scan (plan))
    {
      json_object_set_new (scan, "covered", json_true ());
    }

      if (plan->plan_un.scan.index && plan->plan_un.scan.index->head->use_descending)
    {
      json_object_set_new (scan, "desc_index", json_true ());
      natural_desc_index = true;
    }

      if (!natural_desc_index && (QO_ENV_PT_TREE (plan->info->env)->info.query.q.select.hint & PT_HINT_USE_IDX_DESC))
    {
      json_object_set_new (scan, "desc_index forced", json_true ());
    }

      if (qo_is_index_loose_scan (plan))
    {
      json_object_set_new (scan, "loose", json_true ());
    }

      break;
    }

  return json_pack ("{s:o}", scan_string, scan);
}

/*
 * qo_plan_sort_print_json ()
 *   return:
 *   plan(in):
 */
static json_t *
qo_plan_sort_print_json (QO_PLAN * plan)
{
  json_t *sort, *subplan = NULL;
  const char *type;

  switch (plan->plan_un.sort.sort_type)
    {
    case SORT_TEMP:
      type = "SORT (temp)";
      break;

    case SORT_GROUPBY:
      type = "SORT (group by)";
      break;

    case SORT_ORDERBY:
      type = "SORT (order by)";
      break;

    case SORT_DISTINCT:
      type = "SORT (distinct)";
      break;

    case SORT_LIMIT:
      type = "SORT (limit)";
      break;

    default:
      assert (false);
      type = "";
      break;
    }

  sort = json_object ();

  if (plan->plan_un.sort.subplan)
    {
      subplan = qo_plan_print_json (plan->plan_un.sort.subplan);
      json_object_set_new (sort, type, subplan);
    }
  else
    {
      json_object_set_new (sort, type, json_string (""));
    }

  return sort;
}

/*
 * qo_plan_join_print_json ()
 *   return:
 *   plan(in):
 */
static json_t *
qo_plan_join_print_json (QO_PLAN * plan)
{
  json_t *join, *outer, *inner;
  const char *type, *method = "";
  char buf[32];

  switch (plan->plan_un.join.join_method)
    {
    case QO_JOINMETHOD_NL_JOIN:
    case QO_JOINMETHOD_IDX_JOIN:
      method = "NESTED LOOPS";
      break;

    case QO_JOINMETHOD_MERGE_JOIN:
      method = "MERGE JOIN";
      break;

    case QO_JOINMETHOD_HASH_JOIN:
      method = "HASH JOIN";
      break;

    default:
      method = "UNKNOWN";
      break;
    }

  switch (plan->plan_un.join.join_type)
    {
    case JOIN_INNER:
      if (!bitset_is_empty (&(plan->plan_un.join.join_terms)))
    {
      type = "inner join";
    }
      else
    {
      if (plan->plan_un.join.join_method == QO_JOINMETHOD_IDX_JOIN)
        {
          type = "inner join";
        }
      else
        {
          type = "cross join";
        }
    }
      break;
    case JOIN_LEFT:
      type = "left outer join";
      break;
    case JOIN_RIGHT:
      type = "right outer join";
      break;
    case JOIN_OUTER:        /* not used */
      type = "full outer join";
      break;
    case JOIN_CSELECT:
      type = "cselect";
      break;
    case NO_JOIN:
    default:
      type = "unknown";
      break;
    }

  outer = qo_plan_print_json (plan->plan_un.join.outer);
  inner = qo_plan_print_json (plan->plan_un.join.inner);

  sprintf (buf, "%s (%s)", method, type);

  join = json_pack ("{s:[o,o]}", buf, outer, inner);

  return join;
}

/*
 * qo_plan_follow_print_json ()
 *   return:
 *   plan(in):
 */
static json_t *
qo_plan_follow_print_json (QO_PLAN * plan)
{
  json_t *head, *follow;
  char buf[257] = { '\0', };

  head = qo_plan_print_json (plan->plan_un.follow.head);

  follow = json_object ();
  json_object_set_new (follow, "edge", json_string (qo_term_string (plan->plan_un.follow.path, buf)));
  json_object_set_new (follow, "head", head);

  return json_pack ("{s:o}", "FOLLOW", follow);
}

/*
 * qo_plan_print_json ()
 *   return:
 *   plan(in):
 */
static json_t *
qo_plan_print_json (QO_PLAN * plan)
{
  json_t *json = NULL;

  switch (plan->plan_type)
    {
    case QO_PLANTYPE_SCAN:
      json = qo_plan_scan_print_json (plan);
      break;

    case QO_PLANTYPE_SORT:
      json = qo_plan_sort_print_json (plan);
      break;

    case QO_PLANTYPE_JOIN:
      json = qo_plan_join_print_json (plan);
      break;

    case QO_PLANTYPE_FOLLOW:
      json = qo_plan_follow_print_json (plan);
      break;

    default:
      break;
    }

  return json;
}

/*
 * qo_top_plan_print_json ()
 *   return:
 *   parser(in):
 *   xasl(in):
 *   select(in):
 *   plan(in):
 */
void
qo_top_plan_print_json (PARSER_CONTEXT * parser, xasl_node * xasl, PT_NODE * select, QO_PLAN * plan)
{
  json_t *json;
  unsigned int save_custom;

  assert (parser != NULL && xasl != NULL && plan != NULL && select != NULL);

  if (parser->num_plan_trace >= MAX_NUM_PLAN_TRACE)
    {
      return;
    }

  json = qo_plan_print_json (plan);

  if (select->info.query.order_by)
    {
      if (xasl && xasl->spec_list && xasl->spec_list->indexptr && xasl->spec_list->indexptr->orderby_skip)
    {
      json_object_set_new (json, "skip order by", json_true ());
    }
    }

  if (select->info.query.q.select.group_by)
    {
      if (xasl && xasl->spec_list && xasl->spec_list->indexptr && xasl->spec_list->indexptr->groupby_skip)
    {
      json_object_set_new (json, "group by nosort", json_true ());
    }
    }

  save_custom = parser->custom_print;
  parser->custom_print |= PT_CONVERT_RANGE;

  json_object_set_new (json, "rewritten query", json_string (parser_print_tree (parser, select)));

  parser->custom_print = save_custom;

  parser->plan_trace[parser->num_plan_trace].format = QUERY_TRACE_JSON;
  parser->plan_trace[parser->num_plan_trace].trace.json_plan = json;
  parser->num_plan_trace++;

  return;
}

/*
 * qo_plan_scan_print_text ()
 *   return:
 *   fp(in):
 *   plan(in):
 *   indent(in):
 */
static void
qo_plan_scan_print_text (FILE * fp, QO_PLAN * plan, int indent)
{
  BITSET_ITERATOR bi;
  QO_ENV *env;
  bool natural_desc_index = false;
  const char *class_name;
  char buf[257] = { '\0', };
  int i;

  indent += 2;
  fprintf (fp, "%*c", indent, ' ');

  class_name = QO_NODE_NAME (plan->plan_un.scan.node);
  if (class_name == NULL)
    {
      class_name = "unknown";
    }

  switch (plan->plan_un.scan.scan_method)
    {
    case QO_SCANMETHOD_SEQ_SCAN:
      fprintf (fp, "TABLE SCAN (%s)", class_name);
      break;

    case QO_SCANMETHOD_INDEX_SCAN:
    case QO_SCANMETHOD_INDEX_ORDERBY_SCAN:
    case QO_SCANMETHOD_INDEX_GROUPBY_SCAN:
    case QO_SCANMETHOD_INDEX_SCAN_INSPECT:
      fprintf (fp, "INDEX SCAN (%s.%s)", class_name, plan->plan_un.scan.index->head->constraints->name);

      env = (plan->info)->env;
      fprintf (fp, " (");

      bool first = true;

      for (i = bitset_iterate (&(plan->plan_un.scan.terms), &bi); i != -1; i = bitset_next_member (&bi))
    {
      fprintf (fp, "key range: %s", qo_term_string (QO_ENV_TERM (env, i), buf));
      first = false;
    }

      if (bitset_cardinality (&(plan->plan_un.scan.kf_terms)) > 0)
    {
      for (i = bitset_iterate (&(plan->plan_un.scan.kf_terms), &bi); i != -1; i = bitset_next_member (&bi))
        {
          fprintf (fp, "%skey filter: %s", first ? "" : ", ", qo_term_string (QO_ENV_TERM (env, i), buf));
        }
      first = false;
    }

      if (qo_is_index_covering_scan (plan))
    {
      fprintf (fp, "%scovered: true", first ? "" : ", ");
      first = false;
    }

      if (plan->plan_un.scan.index && plan->plan_un.scan.index->head->use_descending)
    {
      fprintf (fp, "%sdesc_index: true", first ? "" : ", ");
      natural_desc_index = true;
      first = false;
    }

      if (!natural_desc_index && (QO_ENV_PT_TREE (plan->info->env)->info.query.q.select.hint & PT_HINT_USE_IDX_DESC))
    {
      fprintf (fp, "%sdesc_index forced: true", first ? "" : ", ");
      first = false;
    }

      if (qo_is_index_loose_scan (plan))
    {
      fprintf (fp, "%sloose: true", first ? "" : ", ");
      first = false;
    }

      fprintf (fp, ")");
      break;
    }

  fprintf (fp, "\n");
}

/*
 * qo_plan_sort_print_text ()
 *   return:
 *   fp(in):
 *   plan(in):
 *   indent(in):
 */
static void
qo_plan_sort_print_text (FILE * fp, QO_PLAN * plan, int indent)
{
  const char *type;

  indent += 2;

  switch (plan->plan_un.sort.sort_type)
    {
    case SORT_TEMP:
      type = "SORT (temp)";
      break;

    case SORT_GROUPBY:
      type = "SORT (group by)";
      break;

    case SORT_ORDERBY:
      type = "SORT (order by)";
      break;

    case SORT_DISTINCT:
      type = "SORT (distinct)";
      break;

    case SORT_LIMIT:
      type = "SORT (limit)";
      break;

    default:
      assert (false);
      type = "";
      break;
    }

  fprintf (fp, "%*c%s\n", indent, ' ', type);

  if (plan->plan_un.sort.subplan)
    {
      qo_plan_print_text (fp, plan->plan_un.sort.subplan, indent);
    }
}

/*
 * qo_plan_join_print_text ()
 *   return:
 *   fp(in):
 *   plan(in):
 *   indent(in):
 */
static void
qo_plan_join_print_text (FILE * fp, QO_PLAN * plan, int indent)
{
  const char *type, *method = "";

  indent += 2;

  switch (plan->plan_un.join.join_method)
    {
    case QO_JOINMETHOD_NL_JOIN:
    case QO_JOINMETHOD_IDX_JOIN:
      method = "NESTED LOOPS";
      break;

    case QO_JOINMETHOD_MERGE_JOIN:
      method = "MERGE JOIN";
      break;

    case QO_JOINMETHOD_HASH_JOIN:
      method = "HASH JOIN";
      break;

    default:
      method = "UNKNOWN";
      break;
    }

  switch (plan->plan_un.join.join_type)
    {
    case JOIN_INNER:
      if (!bitset_is_empty (&(plan->plan_un.join.join_terms)))
    {
      type = "inner join";
    }
      else
    {
      if (plan->plan_un.join.join_method == QO_JOINMETHOD_IDX_JOIN)
        {
          type = "inner join";
        }
      else
        {
          type = "cross join";
        }
    }
      break;
    case JOIN_LEFT:
      type = "left outer join";
      break;
    case JOIN_RIGHT:
      type = "right outer join";
      break;
    case JOIN_OUTER:        /* not used */
      type = "full outer join";
      break;
    case JOIN_CSELECT:
      type = "cselect";
      break;
    case NO_JOIN:
    default:
      type = "unknown";
      break;
    }

  fprintf (fp, "%*c%s (%s)\n", indent, ' ', method, type);
  qo_plan_print_text (fp, plan->plan_un.join.outer, indent);
  qo_plan_print_text (fp, plan->plan_un.join.inner, indent);
}

/*
 * qo_plan_follow_print_text ()
 *   return:
 *   fp(in):
 *   plan(in):
 *   indent(in):
 */
static void
qo_plan_follow_print_text (FILE * fp, QO_PLAN * plan, int indent)
{
  char buf[257] = { '\0', };
  indent += 2;

  fprintf (fp, "%*cFOLLOW (edge: %s)\n", indent, ' ', qo_term_string (plan->plan_un.follow.path, buf));

  qo_plan_print_text (fp, plan->plan_un.follow.head, indent);
}

/*
 * qo_plan_print_text ()
 *   return:
 *   fp(in):
 *   plan(in):
 *   indent(in):
 */
static void
qo_plan_print_text (FILE * fp, QO_PLAN * plan, int indent)
{
  switch (plan->plan_type)
    {
    case QO_PLANTYPE_SCAN:
      qo_plan_scan_print_text (fp, plan, indent);
      break;

    case QO_PLANTYPE_SORT:
      qo_plan_sort_print_text (fp, plan, indent);
      break;

    case QO_PLANTYPE_JOIN:
      qo_plan_join_print_text (fp, plan, indent);
      break;

    case QO_PLANTYPE_FOLLOW:
      qo_plan_follow_print_text (fp, plan, indent);
      break;

    default:
      break;
    }
}

/*
 * qo_top_plan_print_text ()
 *   return:
 *   parser(in):
 *   xasl(in):
 *   select(in):
 *   plan(in):
 */
void
qo_top_plan_print_text (PARSER_CONTEXT * parser, xasl_node * xasl, PT_NODE * select, QO_PLAN * plan)
{
  size_t sizeloc;
  char *ptr, *sql;
  FILE *fp;
  int indent;
  unsigned int save_custom;

  assert (parser != NULL && xasl != NULL && plan != NULL && select != NULL);

  if (parser->num_plan_trace >= MAX_NUM_PLAN_TRACE)
    {
      return;
    }

  fp = port_open_memstream (&ptr, &sizeloc);
  if (fp == NULL)
    {
      return;
    }

  indent = 0;
  qo_plan_print_text (fp, plan, indent);

  indent += 2;

  if (select->info.query.order_by)
    {
      if (xasl && xasl->spec_list && xasl->spec_list->indexptr && xasl->spec_list->indexptr->orderby_skip)
    {
      fprintf (fp, "%*cskip order by: true\n", indent, ' ');
    }
    }

  if (select->info.query.q.select.group_by)
    {
      if (xasl && xasl->spec_list && xasl->spec_list->indexptr && xasl->spec_list->indexptr->groupby_skip)
    {
      fprintf (fp, "%*cgroup by nosort: true\n", indent, ' ');
    }
    }

  save_custom = parser->custom_print;
  parser->custom_print |= PT_CONVERT_RANGE;
  sql = parser_print_tree (parser, select);
  parser->custom_print = save_custom;

  if (sql != NULL)
    {
      fprintf (fp, "\n%*crewritten query: %s\n", indent, ' ', sql);
    }

  port_close_memstream (fp, &ptr, &sizeloc);

  if (ptr != NULL)
    {
      parser->plan_trace[parser->num_plan_trace].format = QUERY_TRACE_TEXT;
      parser->plan_trace[parser->num_plan_trace].trace.text_plan = ptr;
      parser->num_plan_trace++;
    }

  return;
}

/*
 * qo_check_hjoin_for_parallel_opt() -
 *   return: One of the following QO_PLAN_PARALLEL_OPT_USE values:
 *           - PLAN_PARALLEL_OPT_USE: Parallel hash join is enabled by hint.
 *           - PLAN_PARALLEL_OPT_NO: Parallel hash join is disabled by hint.
 *           - PLAN_PARALLEL_OPT_CANNOT_USE: Parallel hash join not possible.
 *           - PLAN_PARALLEL_OPT_CAN_USE: Parallel hash join is possible; depends on runtime conditions.
 *   plan(in): Query plan node to check (must be a hash join plan).
 */
QO_PLAN_PARALLEL_OPT_USE
qo_check_hjoin_for_parallel_opt (QO_PLAN * plan)
{
  PARSER_CONTEXT *parser = NULL;
  PT_NODE *tree = NULL, *expr = NULL;

  QO_ENV *env = NULL;
  QO_TERM *term = NULL;
  BITSET_ITERATOR bitset_iter;
  int bitset_index;

  bool is_method_call = false;

  if (plan == NULL || plan->info == NULL || plan->plan_type != QO_PLANTYPE_JOIN
      || plan->plan_un.join.join_method != QO_JOINMETHOD_HASH_JOIN)
    {
      return PLAN_PARALLEL_OPT_CANNOT_USE;
    }

  env = plan->info->env;
  if (env == NULL)
    {
      /* impossible case */
      assert (false);
      return PLAN_PARALLEL_OPT_CANNOT_USE;
    }

  parser = QO_ENV_PARSER (env);
  if (parser == NULL)
    {
      /* impossible case */
      assert (false);
      return PLAN_PARALLEL_OPT_CANNOT_USE;
    }

  tree = QO_ENV_PT_TREE (env);
  if (tree == NULL)
    {
      /* impossible case */
      assert (false);
      return PLAN_PARALLEL_OPT_CANNOT_USE;
    }

  if (!PT_IS_SELECT (tree)) // TODO: check merge, update, delete
    {
      /* impossible case */
      assert (false);
      return PLAN_PARALLEL_OPT_CANNOT_USE;
    }

  if (PT_SELECT_INFO_IS_FLAGED (tree, PT_SELECT_INFO_IS_MERGE_QUERY))
    {
      /* MERGE queries cannot use parallel execution because they use
       * xtran_server_start_topop() to ensure statement atomicity, as each row may
       * require both UPDATE and INSERT operations to be performed atomically.
       * xtran_server_start_topop() internally calls log_sysop_start().
       * log_sysop_start() uses a transaction-level mutex (rmutex_topop) that does not
       * support concurrent access from multiple worker threads sharing the same transaction. */
      return PLAN_PARALLEL_OPT_CANNOT_USE;
    }

  if (!bitset_is_empty (&plan->plan_un.join.during_join_terms))
    {
      for (bitset_index = bitset_iterate (&plan->plan_un.join.during_join_terms, &bitset_iter); bitset_index != -1;
       bitset_index = bitset_next_member (&bitset_iter))
    {
      term = QO_ENV_TERM (env, bitset_index);
      if (term == NULL)
        {
          return PLAN_PARALLEL_OPT_CANNOT_USE;
        }

      expr = QO_TERM_PT_EXPR (term);
      if (expr == NULL)
        {
          return PLAN_PARALLEL_OPT_CANNOT_USE;
        }

      (void) parser_walk_tree (parser, expr, pt_is_method_call_node, &is_method_call, NULL, NULL);

      if (is_method_call)
        {
          return PLAN_PARALLEL_OPT_CANNOT_USE;
        }
    }
    }

  if (tree->info.query.q.select.hint & PT_HINT_NO_PARALLEL_HASH_JOIN)
    {
      return PLAN_PARALLEL_OPT_NO;
    }

  if (tree->info.query.q.select.hint & PT_HINT_PARALLEL)
    {
      assert (tree->info.query.q.select.num_parallel_threads >= 0);

      if (tree->info.query.q.select.num_parallel_threads > 1)
    {
      return PLAN_PARALLEL_OPT_USE;
    }
      else
    {
      /* hint 0 or 1 disables parallel execution */
      return PLAN_PARALLEL_OPT_NO;
    }
    }

  return PLAN_PARALLEL_OPT_CAN_USE;
}