Skip to content

File query_rewrite_select.c

File List > cubrid > src > optimizer > rewriter > query_rewrite_select.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_select.c
 */

#include "parse_tree.h"
#ident "$Id$"

#include <assert.h>
#include "query_rewrite.h"


static PT_NODE *qo_reset_location (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk);
static PT_NODE *qo_get_name_cnt_by_spec (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk);
static PT_NODE *qo_get_name_cnt_keep_unique (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk);
static PT_NODE *qo_collect_name_with_eq_const (PARSER_CONTEXT * parser, PT_NODE * on_cond, PT_NODE * spec);
static PT_NODE *qo_reduce_outer_joined_tbls (PARSER_CONTEXT * parser, PT_NODE * spec, PT_NODE * query);
static void qo_reduce_joined_tbls_ref_by_fk (PARSER_CONTEXT * parser, PT_NODE * query);
static bool qo_is_exclude_spec (PT_NODE * exclude_spec_point_list, PT_NODE * spec);
static bool qo_check_pk_ref_by_fk_in_parent_spec (PARSER_CONTEXT * parser, PT_NODE * query,
                          QO_REDUCE_REFERENCE_INFO * reduce_reference_info);
static bool qo_check_fks_ref_pk_in_child_spec (PARSER_CONTEXT * parser, PT_NODE * query,
                           QO_REDUCE_REFERENCE_INFO * reduce_reference_info);
static bool qo_check_fk_ref_pk_in_child_spec (PARSER_CONTEXT * parser, PT_NODE * query,
                          QO_REDUCE_REFERENCE_INFO * reduce_reference_info);
static bool qo_check_reduce_predicate_for_parent_spec (PARSER_CONTEXT * parser, PT_NODE * query,
                               QO_REDUCE_REFERENCE_INFO * reduce_reference_info);
static void qo_reduce_predicate_for_parent_spec (PARSER_CONTEXT * parser, PT_NODE * query,
                         QO_REDUCE_REFERENCE_INFO * reduce_reference_info);
static int qo_reduce_order_by (PARSER_CONTEXT * parser, PT_NODE * node);
static PT_NODE *qo_rewrite_oid_equality (PARSER_CONTEXT * parser, PT_NODE * node, PT_NODE * pred, int *seqno);
static PT_NODE *qo_rewrite_innerjoin (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk);
static PT_NODE *qo_rewrite_outerjoin (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk);
static PT_NODE *qo_get_next_oid_pred (PT_NODE * pred);

/*
 * qo_rewrite_select_queries () - Rewrite select queries
 *   return: bool
 *   parser(in): parser environment
 *   nodep(in/out): &select node
 *   wherep(in/out): &where node
 *   seqno(in): sequence number
 */
bool
qo_rewrite_select_queries (PARSER_CONTEXT * parser, PT_NODE ** nodep, PT_NODE ** wherep, int *seqno)
{
  PT_NODE *pred;
  PT_NODE *spec, *next;
  if ((*nodep)->node_type == PT_SELECT)
    {
      int continue_walk;

      /* rewrite outer join to inner join */
      qo_rewrite_outerjoin (parser, *nodep, NULL, &continue_walk);

      /* rewrite explicit inner join to implicit inner join */
      qo_rewrite_innerjoin (parser, *nodep, NULL, &continue_walk);

      pred = qo_get_next_oid_pred (*wherep);
      if (pred)
    {
      while (pred)
        {
          next = pred->next;
          *nodep = qo_rewrite_oid_equality (parser, *nodep, pred, seqno);
          assert_release ((*nodep) != NULL);
          if ((*nodep) == NULL)
        {
          return false;
        }

          pred = qo_get_next_oid_pred (next);
        }           /* while (pred) */

      /* re-analyze paths for possible optimizations */
      (*nodep)->info.query.q.select.from =
        parser_walk_tree (parser, (*nodep)->info.query.q.select.from, qo_analyze_path_join_pre, NULL,
                  qo_analyze_path_join, (*nodep)->info.query.q.select.where);
    }           /* if (pred) */

      if (qo_reduce_order_by (parser, (*nodep)) != NO_ERROR)
    {
      return false;     /* give up */
    }

      PT_NODE *point_list = NULL;
      PT_NODE *point, *tmp_spec;
      spec = (*nodep)->info.query.q.select.from;
      /* predicate push */
      while (spec)
    {
      if (spec->info.spec.derived_table_type == PT_IS_SUBQUERY
          || spec->info.spec.derived_table_type == PT_DERIVED_DBLINK_TABLE)
        {
          (void) mq_copypush_sargable_terms (parser, (*nodep), spec);
        }
      else
        {
          point_list = parser_append_previous_node (pt_point (parser, spec), point_list);
        }
      spec = spec->next;
    }
      /* reduce unnecessary tables */
      point = point_list;
      while (point)
    {
      tmp_spec = point;
      CAST_POINTER_TO_NODE (tmp_spec);
      if (mq_is_outer_join_spec (parser, tmp_spec) && !PT_SPEC_IS_CTE (tmp_spec))
        {
          (*nodep) = qo_reduce_outer_joined_tbls (parser, tmp_spec, (*nodep));
        }
      point = point->next;
    }
      if (point_list != NULL)
    {
      parser_free_tree (parser, point_list);
    }

      qo_reduce_joined_tbls_ref_by_fk (parser, (*nodep));
    }

  return true;
}

/*
 * qo_rewrite_index_hints () - Rewrite index hint list, removing useless hints
 *   return: PT_NODE *
 *   parser(in):
 *   node(in): QUERY node
 *   parent_node(in):
 */
void
qo_rewrite_index_hints (PARSER_CONTEXT * parser, PT_NODE * statement)
{
  PT_NODE *using_index = NULL, *hint_node, *prev_node, *next_node;

  bool is_sorted, is_idx_reversed, is_idx_match_nokl, is_hint_masked;

  PT_NODE *hint_none, *root_node;
  PT_NODE dummy_hint_local, *dummy_hint;

  switch (statement->node_type)
    {
    case PT_SELECT:
      using_index = statement->info.query.q.select.using_index;
      break;
    case PT_UPDATE:
      using_index = statement->info.update.using_index;
      break;
    case PT_DELETE:
      using_index = statement->info.delete_.using_index;
      break;
    default:
      /* USING index clauses are not allowed for other query types */
      assert (false);
      return;
    }

  if (using_index == NULL)
    {
      /* no index hints, nothing to do here */
      return;
    }

  /* Main logic - we can safely assume that pt_check_using_index() has already checked for possible semantic errors or
   * incompatible index hints. */

  /* basic rewrite, for USING INDEX NONE */
  hint_node = using_index;
  prev_node = NULL;
  hint_none = NULL;
  while (hint_node != NULL)
    {
      if (hint_node->etc == (void *) PT_IDX_HINT_NONE)
    {
      hint_none = hint_node;
      break;
    }
      prev_node = (prev_node == NULL) ? hint_node : prev_node->next;
      hint_node = hint_node->next;
    }

  if (hint_none != NULL)
    {
      /* keep only the using_index_none hint stored in hint_none */
      /* update links and discard the first part of the hint list */
      if (prev_node != NULL)
    {
      prev_node->next = NULL;
      parser_free_tree (parser, using_index);
      using_index = NULL;
    }
      /* update links and discard the last part of the hint list */
      hint_node = hint_none->next;
      if (hint_node != NULL)
    {
      parser_free_tree (parser, hint_node);
      hint_node = NULL;
    }
      /* update links and keep only the USING INDEX NONE node */
      hint_none->next = NULL;
      using_index = hint_none;
      goto exit;
    }

  if (using_index->etc == (void *) PT_IDX_HINT_ALL_EXCEPT)
    {
      /* find all t.none index hints and mark them for later removal */
      /* the first node, when USING INDEX ALL EXCEPT, is a '*', so use this node as a constant list root */
      hint_node = using_index;
      while (hint_node != NULL && (next_node = hint_node->next) != NULL)
    {
      if (next_node->info.name.original == NULL && next_node->info.name.resolved != NULL
          && strcmp (next_node->info.name.resolved, "*") != 0)
        {
          /* found a t.none identifier; remove it from the list */
          hint_node->next = next_node->next;
          next_node->next = NULL;
          parser_free_node (parser, next_node);
        }
      else
        {
          hint_node = hint_node->next;
        }
    }

      /* if only the '*' marker node is left in the list, it means that USING INDEX ALL EXCEPT contains only
       * t.none-like hints, so it is actually an empty hint list */
      if (using_index->next == NULL)
    {
      parser_free_node (parser, using_index);
      using_index = NULL;
      goto exit;
    }

      root_node = prev_node = using_index;
      hint_node = using_index->next;
    }
  else
    {
      /* there is no USING INDEX {NONE|ALL EXCEPT ...} in the query; the dummy node is necessary for faster operation;
       * use local variable dummy_hint */
      dummy_hint = &dummy_hint_local;
      dummy_hint->next = using_index;
      /* just need something else than PT_IDX_HINT_ALL AEXCEPT, so that this node won't be kept later */
      dummy_hint->etc = (void *) PT_IDX_HINT_USE;
      root_node = prev_node = dummy_hint;
      hint_node = using_index;
    }

  /* remove duplicate index hints and sort them; keep the same order for the hints of the same type with keylimit */
  /* order: class_none, ignored, forced, used */
  is_sorted = false;
  while (!is_sorted)
    {
      prev_node = root_node;
      hint_node = prev_node->next;
      is_sorted = true;
      while ((next_node = hint_node->next) != NULL)
    {
      is_idx_reversed = false;
      is_idx_match_nokl = false;
      if (PT_IDX_HINT_ORDER (hint_node) > PT_IDX_HINT_ORDER (next_node))
        {
          is_idx_reversed = true;
        }
      else if (hint_node->etc == next_node->etc)
        {
          /* if hints have the same type, check if they need to be swapped or are identical and one of them needs
           * to be removed */
          int res_cmp_tbl_names = -1;
          /* unless USING INDEX NONE, which is rewritten above, all indexes should have table names already
           * resolved */
          assert (hint_node->info.name.resolved != NULL && next_node->info.name.resolved != NULL);

          /*
           * Case of comparing names after dot(.).
           * 1. When comparing owner_name.class_name and class_name.
           *    class_name used in index_name must be in from. So, only class_name should be compared,
           *    not owner_name.
           *    e.g. select c1 from t1 force index (i1) where c1 >= 0 using index i1(+);
           *      - resolved_name of "force index (i1)"  : "t1"
           *      - resolved_name of "using index i1(+)" : "dba.t1"
           */

          /* compare the tables on which the indexes are defined */
          res_cmp_tbl_names =
        pt_user_specified_name_compare (hint_node->info.name.resolved, next_node->info.name.resolved);

          if (res_cmp_tbl_names == 0)
        {
          /* also compare index names */
          if (hint_node->info.name.original != NULL && next_node->info.name.original != NULL)
            {
              /* index names can be null if t.none */
              int res_cmp_idx_names;

              res_cmp_idx_names =
            intl_identifier_casecmp (hint_node->info.name.original, next_node->info.name.original);
              if (res_cmp_idx_names == 0)
            {
              is_idx_match_nokl = true;
            }
              else
            {
              is_idx_reversed = (res_cmp_idx_names > 0);
            }
            }
          else
            {
              /* hints are of the same type, name.original is either NULL or not NULL for both hints */
              assert (hint_node->info.name.original == NULL && next_node->info.name.original == NULL);
              /* both hints are "same-table.none"; identical */
              is_idx_match_nokl = true;
            }
        }
          else
        {
          is_idx_reversed = (res_cmp_tbl_names > 0);
        }

          if (is_idx_match_nokl)
        {
          /* The same index is used in both hints; examine the keylimit clauses; if search_node does not have
           * keylimit, the IF below will skip, and search_node will be deleted */
          if (next_node->info.name.indx_key_limit != NULL)
            {
              /* search_node has keylimit */
              if (hint_node->info.name.indx_key_limit != NULL)
            {
              /* hint_node has keylimit; no action is performed; we want to preserve the order of index
               * hints for the same index, with keylimit */
              is_idx_reversed = false;
              is_idx_match_nokl = false;
            }
              else
            {
              /* special case; need to delete hint_node and keep search_node, because this one has
               * keylimit; */
              assert (!is_idx_reversed);
              is_idx_reversed = true;
              /* reverse the two nodes so the code below can be reused for this situation */
            }
            }       /* endif (search_node) */
        }       /* endif (is_idx_match_nokl) */
        }

      if (is_idx_reversed)
        {
          /* Interchange the two hints */
          hint_node->next = next_node->next;
          next_node->next = hint_node;
          prev_node->next = next_node;
          is_sorted = false;
          /* update hint_node and search_node, for possible delete */
          hint_node = prev_node->next;
          next_node = hint_node->next;
        }

      if (is_idx_match_nokl)
        {
          /* remove search_node */
          hint_node->next = next_node->next;
          next_node->next = NULL;
          parser_free_node (parser, next_node);
          /* node removed, use prev_node and hint_node in next loop */
          continue;
        }
      prev_node = prev_node->next;
      hint_node = prev_node->next;
    }
    }

  /* Find index hints to remove later. At this point, the only index hints that can be found in using_index are
   * {USE|FORCE|IGNORE} INDEX and USING INDEX {idx|idx(-)|idx(+)|t.none}... Need to ignore duplicate hints, and hints
   * that are masked by applying the hint operation rules. */
  hint_node = root_node->next;
  while (hint_node != NULL)
    {
      next_node = hint_node->next;
      prev_node = hint_node;
      while (next_node != NULL)
    {
      if (next_node->etc == hint_node->etc)
        {
          /* same hint type; duplicates were already removed, skip hint */
          prev_node = next_node;
          next_node = next_node->next;
          continue;
        }

      /* Main logic for removing redundant/masked index hints */
      /* The hint list is now sorted, first by index type, then by table and index name, so the next_node type is
       * the same as hint_node or lower in importance (class.none > ignore > force > use), so it is not necessary
       * to check next_index hint type */
      is_hint_masked = false;

      if ((hint_node->etc == (void *) PT_IDX_HINT_CLASS_NONE
           || ((hint_node->etc == (void *) PT_IDX_HINT_IGNORE || hint_node->etc == (void *) PT_IDX_HINT_FORCE)
           && (intl_identifier_casecmp (hint_node->info.name.original, next_node->info.name.original) == 0)))
          && (pt_user_specified_name_compare (hint_node->info.name.resolved, next_node->info.name.resolved) == 0))
        {
          is_hint_masked = true;
        }

      if (is_hint_masked)
        {
          /* hint search_node is masked; remove it from the hint list */
          prev_node->next = next_node->next;
          next_node->next = NULL;
          parser_free_node (parser, next_node);
          next_node = prev_node;
        }
      prev_node = next_node;
      next_node = next_node->next;
    }
      hint_node = hint_node->next;
    }

  /* remove the dummy first node, if any */
  if (root_node->etc != (void *) PT_IDX_HINT_ALL_EXCEPT)
    {
      using_index = root_node->next;
      root_node->next = NULL;
    }
  else
    {
      using_index = root_node;
    }

exit:
  /* Save changes to query node */
  switch (statement->node_type)
    {
    case PT_SELECT:
      statement->info.query.q.select.using_index = using_index;
      break;
    case PT_UPDATE:
      statement->info.update.using_index = using_index;
      break;
    case PT_DELETE:
      statement->info.delete_.using_index = using_index;
      break;
    default:
      break;
    }
}


/*
 * qo_find_best_path_type () -
 *   return: PT_NODE *
 *   spec(in): path entity to test
 *
 * Note: prunes non spec's
 */
static PT_MISC_TYPE
qo_find_best_path_type (PT_NODE * spec)
{
  PT_MISC_TYPE best_path_type = PT_PATH_OUTER;
  PT_MISC_TYPE path_type;

  /* if any is an inner, the result is inner. if all are outer, the result is outer */

  while (spec)
    {
      path_type = spec->info.spec.meta_class;
      if (path_type == PT_PATH_INNER)
    return PT_PATH_INNER;
      if (path_type != PT_PATH_OUTER)
    best_path_type = PT_PATH_OUTER_WEASEL;

      path_type = qo_find_best_path_type (spec->info.spec.path_entities);
      if (path_type == PT_PATH_INNER)
    return PT_PATH_INNER;
      if (path_type != PT_PATH_OUTER)
    best_path_type = PT_PATH_OUTER_WEASEL;

      spec = spec->next;
    }

  return best_path_type;
}

/*
 * qo_move_on_of_explicit_join_to_where () - move on clause of explicit join to where clause
 *   return: void
 *   parser(in): parser environment
 *   fromp(in/out): &from of SELECT, &spec of UPDATE/DELETE
 *   wherep(in/out): &where of SELECT/UPDATE/DELETE
 *
 * NOTE: It moves on clause of explicit join for SELECT/UPDATE/DELETE to where clase for temporary purpose.
 *       qo_rewrite_queries_post will restore them after several optimizations, for instance, range merge/intersection,
 *       auto-parameterization.
 *
 */
void
qo_move_on_of_explicit_join_to_where (PARSER_CONTEXT * parser, PT_NODE ** fromp, PT_NODE ** wherep)
{
  PT_NODE *t_node, *spec;

  t_node = *wherep;
  while (t_node != NULL && t_node->next != NULL)
    {
      t_node = t_node->next;
    }

  for (spec = *fromp; spec != NULL; spec = spec->next)
    {
      if (spec->node_type == PT_SPEC && spec->info.spec.on_cond != NULL)
    {
      if (t_node == NULL)
        {
          t_node = *wherep = spec->info.spec.on_cond;
        }
      else
        {
          t_node->next = spec->info.spec.on_cond;
        }

      spec->info.spec.on_cond = NULL;

      while (t_node->next != NULL)
        {
          t_node = t_node->next;
        }
    }
    }
}

/*
 * qo_analyze_path_join_pre () -
 *   return: PT_NODE *
 *   parser(in): parser environment
 *   spec(in): path entity to test
 *   arg(in): where clause to test
 *   continue_walk(in):
 *
 * Note : prunes non spec's
 */
PT_NODE *
qo_analyze_path_join_pre (PARSER_CONTEXT * parser, PT_NODE * spec, void *arg, int *continue_walk)
{
  *continue_walk = PT_CONTINUE_WALK;

  if (spec->node_type != PT_SPEC)
    {
      *continue_walk = PT_STOP_WALK;
    }

  return spec;
}

/*
 * qo_analyze_path_join () -
 *   return: PT_NODE *
 *   parser(in): parser environment
 *   path_spec(in): path entity to test
 *   arg(in): where clause to test
 *   continue_walk(in):
 *
 * Note: tests all non-selector path spec's for the type of join
 *  that can be done.
 *  if a null path can be guaranteed to produce no row
 *  tags spec as PT_INNER_PATH
 *
 *  if a null path can have no effect on
 *  (does not appear in) the where clause
 *  tags spec as PT_PATH_OUTER
 *
 *  if a null path COULD affect the where clause (appears),
 *  but cannot be guaranteed to have no effect,
 *  tags the spec as PT_PATH_OUTER_WEASEL. This means
 *  no merge, since I can't prove that this is equivalent
 *  to PT_PATH_INNER. This is treated the same as
 *  PT_PATH_OUTER, with apologies for the silly name.
 *
 */
PT_NODE *
qo_analyze_path_join (PARSER_CONTEXT * parser, PT_NODE * path_spec, void *arg, int *continue_walk)
{
  PT_NODE *where = (PT_NODE *) arg;
  PT_MISC_TYPE path_type;
  SPEC_ID_INFO info;

  *continue_walk = PT_CONTINUE_WALK;

  if (path_spec->node_type == PT_SPEC && path_spec->info.spec.path_conjuncts
      && path_spec->info.spec.meta_class != PT_PATH_INNER)
    {
      /* to get here, this must be a 'normal' outer path entity We may be able to optimize this to an inner path if
       * any sub path is an PT_PATH_INNER, so is this one. otherwise, if any sub-path is NOT an PT_PATH_OUTER, the best
       * we can be is a WEASEL :). Since we are a post function, sub-paths are already set. */
      path_type = qo_find_best_path_type (path_spec->info.spec.path_entities);

      path_spec->info.spec.meta_class = path_type;

      if (path_type != PT_PATH_INNER)
    {
      info.id = path_spec->info.spec.id;
      info.appears = false;
      parser_walk_tree (parser, where, qo_get_name_by_spec_id, &info, NULL, NULL);

      if (info.appears)
        {
          if (qo_check_condition_null (parser, path_spec, where))
        {
          path_spec->info.spec.meta_class = PT_PATH_INNER;
        }
          else
        {
          path_spec->info.spec.meta_class = PT_PATH_OUTER_WEASEL;
        }
        }
      else
        {
          /* best path type already assigned above */
        }
    }
    }

  return path_spec;
}


/*
 * qo_convert_attref_to_dotexpr_pre () -
 *   return:
 *   parser(in):
 *   spec(in):
 *   arg(in):
 *   continue_walk(in):
 *
 * Note: prunes PT_SPEC
 */
static PT_NODE *
qo_convert_attref_to_dotexpr_pre (PARSER_CONTEXT * parser, PT_NODE * spec, void *arg, int *continue_walk)
{
  TO_DOT_INFO *info = (TO_DOT_INFO *) arg;

  *continue_walk = PT_CONTINUE_WALK;

  if (spec->node_type == PT_SPEC && spec->info.spec.id == info->old_spec->info.spec.id)
    {
      *continue_walk = PT_LIST_WALK;
    }
  return spec;
}

/*
 * qo_convert_attref_to_dotexpr () -
 *   return:
 *   parser(in):
 *   node(in):
 *   arg(in):
 *   continue_walk(in):
 *
 * Note: looks for any attribute reference x.i in
 *      select x.i, ... from c x, ... where x.i ... and x {=|IN} expr
 *      and rewrites those into path expressions t.x.i in
 *      select t.x.i, ... from table({expr}) as t(x), ... where t.x.i ...
 */
static PT_NODE *
qo_convert_attref_to_dotexpr (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk)
{
  TO_DOT_INFO *info = (TO_DOT_INFO *) arg;
  PT_NODE *arg1, *arg2, *attr, *rvar;
  PT_NODE *new_spec = info->new_spec;

  if (node->node_type == PT_NAME && node->info.name.spec_id == info->old_spec->info.spec.id)
    {
      attr = new_spec->info.spec.as_attr_list;
      rvar = new_spec->info.spec.range_var;

      switch (node->info.name.meta_class)
    {
    case PT_CLASS:
      /* must be a data_type entity, so don't change its original name because later xasl domain handling code may
       * use that name to look up the class. */
      break;
    case PT_OID_ATTR:
      /* resolve the name to the new_spec */
      node->info.name.spec_id = new_spec->info.spec.id;
      node->info.name.original = attr->info.name.original;
      node->info.name.resolved = rvar->info.name.original;
      /* an OID_ATTR becomes a NORMAL attribute reference */
      if (node->info.name.meta_class == PT_OID_ATTR)
        node->info.name.meta_class = PT_NORMAL;
      break;
    case PT_NORMAL:
      /* we must transform this NAME node into a DOT node in place to preserve its address. (Otherwise, we have to
       * find all the places that point to it and change them all.) */
      {
        arg2 = parser_copy_tree (parser, node);
        if (arg2)
          {
        arg2->next = NULL;
          }
        arg1 = pt_name (parser, attr->info.name.original);
        if (arg1)
          {
        arg1->info.name.resolved = rvar->info.name.original;
        arg1->info.name.spec_id = new_spec->info.spec.id;
        arg1->info.name.meta_class = PT_NORMAL;
        arg1->type_enum = attr->type_enum;
        arg1->data_type = parser_copy_tree (parser, attr->data_type);
          }

        int coll_modifier = node->info.name.coll_modifier;
        short tag_click_counter = node->info.name.tag_click_counter;

        pt_init_node (node, PT_DOT_);
        node->info.dot.arg1 = arg1;
        node->info.dot.arg2 = arg2;
        node->info.dot.coll_modifier = coll_modifier;
        node->info.dot.tag_click_counter = tag_click_counter;
      }
      break;
    default:
      break;
    }
    }
  else if (node->node_type == PT_SPEC && node->info.spec.id == info->old_spec->info.spec.id)
    {
      *continue_walk = PT_LIST_WALK;
    }
  return node;
}

/*
 * qo_get_next_oid_pred () -
 *   return:
 *   pred(in): cursor into a subquery's where clause
 *
 * Note:
 *   It requires pred is a cursor into a subquery's where clause that has been
 *   transformed into conjunctive normal form and
 *   effects that returns a pointer to subquery's next CNF-term that can be
 *   rewritten into an oid attribute equality test, if one exists.
 *   returns a NULL pointer otherwise.
 */
static PT_NODE *
qo_get_next_oid_pred (PT_NODE * pred)
{
  while (pred && pred->node_type == PT_EXPR && pred->or_next == NULL)
    {
      if (pred->info.expr.op == PT_EQ || pred->info.expr.op == PT_IS_IN)
    {
      if (pred->info.expr.arg1 && pred->info.expr.arg1->node_type == PT_NAME
          && pred->info.expr.arg1->info.name.meta_class == PT_OID_ATTR)
        {
          return pred;
        }
      if (pred->info.expr.arg2 && pred->info.expr.arg2->node_type == PT_NAME
          && pred->info.expr.arg2->info.name.meta_class == PT_OID_ATTR)
        {
          return pred;
        }
    }
      pred = pred->next;
    }

  return pred;
}


/*
 * qo_is_oid_const () -
 *   return: Returns true iff the argument looks like a constant for
 *       the purposes f the oid equality rewrite optimization
 *   node(in):
 */
static int
qo_is_oid_const (PT_NODE * node)
{
  if (node == NULL)
    {
      return 0;
    }

  switch (node->node_type)
    {
    case PT_VALUE:
    case PT_HOST_VAR:
      return 1;

    case PT_NAME:
      /*
       * This *could* look to see if the name is correlated to the same
       * level as the caller, but that's going to require more context
       * to come in...
       */
      return node->info.name.meta_class == PT_PARAMETER;

    case PT_FUNCTION:
      if (node->info.function.function_type != F_SET && node->info.function.function_type != F_MULTISET
      && node->info.function.function_type != F_SEQUENCE)
    {
      return 0;
    }
      else
    {
      /*
       * The is the case for an expression like
       *
       *  {:a, :b, :c}
       *
       * Here the the expression '{:a, :b, :c}' comes in as a
       * sequence function call, with PT_NAMEs 'a', 'b', and 'c' as
       * its arglist.
       */
      PT_NODE *p;

      for (p = node->info.function.arg_list; p; p = p->next)
        {
          if (!qo_is_oid_const (p))
        return 0;
        }
      return 1;
    }

    case PT_SELECT:
    case PT_UNION:
    case PT_DIFFERENCE:
    case PT_INTERSECTION:
      return node->info.query.correlation_level != 1;

    default:
      return 0;
    }
}

/*
 * qo_construct_new_set () -
 *   return:
 *   parser(in): parser context
 *   node(in): an OID_ATTR equality/IN predicate
 *
 * Note:
 *   It requires that node is an OID_ATTR predicate (x {=|IN} expr) from
 *        select ... from c x, ... where ... and x {=|IN} expr
 *   and modifies parser heap
 *   and effects that creates, initializes, returns a new set constructor
 *   subtree that can be used for the derived table field of a new PT_SPEC
 *    node representing 'table({expr}) as t(x)' in the rewritten
 *        select ... from table({expr}) as t(x), ... where ...
 */
static PT_NODE *
qo_construct_new_set (PARSER_CONTEXT * parser, PT_NODE * node)
{
  PT_NODE *arg = NULL, *set = NULL;
  /* jabaek: modify SQLM */
  PT_NODE *targ = NULL;

  /* make sure we have reasonable arguments */
  if (!node || node->node_type != PT_EXPR)
    return set;

  /* if control reaches here, then qo_get_next_oid_pred must have succeeded in finding a CNF-term: 'x {=|IN} expr' from
   * a query select ... from c x, ... where ... and x {=|IN} expr Now, copy 'expr' into a derived table:
   * 'table({expr})' which the caller will put into the transformed query select ... from table({expr}) as t(x), ...
   * where ... */
  switch (node->info.expr.op)
    {
    case PT_EQ:
      if (node->info.expr.arg1 && node->info.expr.arg1->node_type == PT_NAME
      && node->info.expr.arg1->info.name.meta_class == PT_OID_ATTR && qo_is_oid_const (node->info.expr.arg2))
    {
      arg = parser_copy_tree (parser, node->info.expr.arg2);
      targ = node->info.expr.arg1;
    }
      else if (node->info.expr.arg2 && node->info.expr.arg2->node_type == PT_NAME
           && node->info.expr.arg2->info.name.meta_class == PT_OID_ATTR && qo_is_oid_const (node->info.expr.arg1))
    {
      arg = parser_copy_tree (parser, node->info.expr.arg1);
      targ = node->info.expr.arg2;
    }
      break;
    case PT_IS_IN:
      if (PT_IS_OID_NAME (node->info.expr.arg1) && PT_IS_FUNCTION (node->info.expr.arg2)
      && PT_IS_CONST_INPUT_HOSTVAR (node->info.expr.arg2->info.function.arg_list))
    {
      arg = parser_copy_tree (parser, node->info.expr.arg2->info.function.arg_list);
      targ = node->info.expr.arg1;
    }
      else if (PT_IS_OID_NAME (node->info.expr.arg2) && PT_IS_FUNCTION (node->info.expr.arg1)
           && PT_IS_CONST_INPUT_HOSTVAR (node->info.expr.arg1->info.function.arg_list))
    {
      arg = parser_copy_tree (parser, node->info.expr.arg1->info.function.arg_list);
      targ = node->info.expr.arg2;
    }
      break;
    default:
      break;
    }

  /* create mset constructor subtree */
  if (arg && (set = parser_new_node (parser, PT_FUNCTION)) != NULL)
    {
      set->info.function.function_type = F_SEQUENCE;
      set->info.function.arg_list = arg;
      set->type_enum = PT_TYPE_SEQUENCE;

      set->data_type = parser_copy_tree_list (parser, arg->data_type);
    }

  return set;
}

/*
 * qo_make_new_derived_tblspec () -
 *   return:
 *   parser(in): parser context
 *   node(in): a PT_SPEC node
 *   pred(in): node's OID_ATTR predicate
 *   seqno(in/out): sequence number for generating unique derived table names
 *
 * Note:
 *   It requires that node is the PT_SPEC node (c x) and
 *   pred is the OID_ATTR predicate (x {=|IN} expr) from
 *        select ... from c x, ... where ... and x {=|IN} expr
 *   and modifies parser heap, node
 *   and effects that creates, initializes, returns a new derived table
 *   type PT_SPEC node representing 'table({expr}) as t(x)' in the rewritten
 *        select ... from table({expr}) as t(x), ... where ...
 */
static PT_NODE *
qo_make_new_derived_tblspec (PARSER_CONTEXT * parser, PT_NODE * node, PT_NODE * pred, int *seqno)
{
  PT_NODE *spec = NULL, *dtbl, *eq = NULL, *rvar;
  UINTPTR spec_id;
  const char *dtblnam, *dattnam;

  dtbl = qo_construct_new_set (parser, pred);
  if (!dtbl)
    {
      return NULL;
    }

  spec = parser_new_node (parser, PT_SPEC);
  if (spec)
    {
      spec_id = (UINTPTR) spec;
      spec->info.spec.id = spec_id;
      spec->info.spec.only_all = PT_ONLY;
      spec->info.spec.derived_table_type = PT_IS_SET_EXPR;
      spec->info.spec.derived_table = dtbl;
      dtblnam = mq_generate_name (parser, "dt", seqno);
      dattnam = mq_generate_name (parser, "da", seqno);
      spec->info.spec.range_var = pt_name (parser, dtblnam);
      if (spec->info.spec.range_var == NULL)
    {
      goto exit_on_error;
    }
      spec->info.spec.range_var->info.name.spec_id = spec_id;
      spec->info.spec.as_attr_list = pt_name (parser, dattnam);
      if (spec->info.spec.as_attr_list == NULL)
    {
      goto exit_on_error;
    }
      spec->info.spec.as_attr_list->info.name.spec_id = spec_id;
      spec->info.spec.as_attr_list->info.name.meta_class = PT_NORMAL;
      spec->info.spec.as_attr_list->type_enum = PT_TYPE_OBJECT;
      spec->info.spec.as_attr_list->data_type = parser_copy_tree (parser, dtbl->data_type);
      if (node && node->node_type == PT_SPEC && (rvar = node->info.spec.range_var) != NULL)
    {
      /* new derived table spec needs path entities */
      spec->info.spec.path_entities = node;

      /* we also need to graft a path conjunct to node */
      node->info.spec.path_conjuncts = eq = parser_new_node (parser, PT_EXPR);
      if (eq)
        {
          eq->type_enum = PT_TYPE_LOGICAL;
          eq->info.expr.op = PT_EQ;
          eq->info.expr.arg1 = pt_name (parser, dattnam);
          if (eq->info.expr.arg1 == NULL)
        {
          goto exit_on_error;
        }
          eq->info.expr.arg1->info.name.spec_id = spec_id;
          eq->info.expr.arg1->info.name.resolved = dtblnam;
          eq->info.expr.arg1->info.name.meta_class = PT_NORMAL;
          eq->info.expr.arg1->type_enum = PT_TYPE_OBJECT;
          eq->info.expr.arg1->data_type = parser_copy_tree (parser, dtbl->data_type);
          eq->info.expr.arg2 = pt_name (parser, "");
          if (eq->info.expr.arg2 == NULL)
        {
          goto exit_on_error;
        }
          eq->info.expr.arg2->info.name.spec_id = node->info.spec.id;
          eq->info.expr.arg2->info.name.resolved = rvar->info.name.original;
          eq->info.expr.arg2->info.name.meta_class = PT_OID_ATTR;
          eq->info.expr.arg2->type_enum = PT_TYPE_OBJECT;
          eq->info.expr.arg2->data_type = parser_copy_tree (parser, dtbl->data_type);
        }
    }
    }
  return spec;

exit_on_error:
  if (eq)
    {
      if (eq->info.expr.arg1)
    {
      parser_free_node (parser, eq->info.expr.arg1);
    }
      if (eq->info.expr.arg2)
    {
      parser_free_node (parser, eq->info.expr.arg2);
    }
    }
  if (spec->info.spec.range_var)
    {
      parser_free_node (parser, spec->info.spec.range_var);
    }
  if (spec->info.spec.as_attr_list)
    {
      parser_free_node (parser, spec->info.spec.as_attr_list);
    }
  parser_free_node (parser, spec);
  return NULL;
}

/*
 * qo_rewrite_oid_equality () -
 *   return:
 *   parser(in): parser context
 *   node(in): a subquery
 *   pred(in): subquery's OID_ATTR equality/IN predicate
 *   seqno(in/out): seq number for generating unique derived table/attr names
 *
 * Note:
 *   It requires that node is a subquery of the form
 *       select ... from c x, ... where ... and x {=|IN} expr
 *       pred is x {=|IN} expr
 *   and modifies node
 *   and effects that rewrites node into the form
 *       select ... from table({expr}) as t(x), ... where ...
 */
static PT_NODE *
qo_rewrite_oid_equality (PARSER_CONTEXT * parser, PT_NODE * node, PT_NODE * pred, int *seqno)
{
  PT_NODE *prev, *next, *from, *new_spec, *prev_spec = NULL;
  UINTPTR spec_id = 0;
  int found;

  /* make sure we have reasonable arguments */
  if (pred->node_type != PT_EXPR || pred->type_enum != PT_TYPE_LOGICAL
      || (pred->info.expr.op != PT_EQ && pred->info.expr.op != PT_IS_IN))
    {
      return node;
    }
  else if (pred->info.expr.arg1 && pred->info.expr.arg1->node_type == PT_NAME
       && pred->info.expr.arg1->info.name.meta_class == PT_OID_ATTR && qo_is_oid_const (pred->info.expr.arg2))
    {
      spec_id = pred->info.expr.arg1->info.name.spec_id;
    }
  else if (pred->info.expr.arg2 && pred->info.expr.arg2->node_type == PT_NAME
       && pred->info.expr.arg2->info.name.meta_class == PT_OID_ATTR && qo_is_oid_const (pred->info.expr.arg1))
    {
      spec_id = pred->info.expr.arg2->info.name.spec_id;
    }
  else
    {
      return node;      /* bail out without rewriting node */
    }

  /* make sure spec_id resolves to a regular spec in node */
  from = node->info.query.q.select.from;
  if (from && from->node_type == PT_SPEC && from->info.spec.id == spec_id)
    {
      found = 1;
    }
  else
    {
      found = 0;
      prev_spec = from;
      while (from && from->node_type == PT_SPEC)
    {
      if (from->info.spec.id == spec_id)
        {
          found = 1;
          break;
        }
      prev_spec = from;
      from = from->next;
    }
    }

  if (!found)
    {
      return node;      /* bail out without rewriting node */
    }

  /* There is no advantage to rewriting class OID predicates like select ... from class c x, ... where x = expr so
   * screen those cases out now. */
  if (from->info.spec.meta_class == PT_META_CLASS)
    return node;        /* bail out without rewriting node */

  /* put node's PT_SPEC into a new derived table type PT_SPEC */
  new_spec = qo_make_new_derived_tblspec (parser, from, pred, seqno);
  if (!new_spec)
    return node;        /* bail out without rewriting node */

  /* excise pred from node's where clause */
  if (pred == node->info.query.q.select.where)
    {
      node->info.query.q.select.where = pred->next;
    }
  else
    {
      prev = next = node->info.query.q.select.where;
      while (next)
    {
      if (next == pred)
        {
          prev->next = next->next;
          break;
        }
      prev = next;
      next = next->next;
    }
    }

  /* replace old PT_SPEC with new_spec in node's from list */
  new_spec->next = from->next;
  from->next = NULL;
  if (from == node->info.query.q.select.from)
    {
      node->info.query.q.select.from = new_spec;
    }
  else if (prev_spec != NULL)
    {
      prev_spec->next = new_spec;
    }

  /* transform attribute references x.i in select x.i, ... from c x, ... where x.i ... and x {=|IN} expr into path
   * expressions t.x.i in select t.x.i, ... from table({expr}) as t(x), ... where t.x.i ... */
  {
    TO_DOT_INFO dinfo;
    dinfo.old_spec = from;
    dinfo.new_spec = new_spec;
    parser_walk_tree (parser, node, qo_convert_attref_to_dotexpr_pre, &dinfo, qo_convert_attref_to_dotexpr, &dinfo);
  }

  node = mq_reset_ids_in_statement (parser, node);
  return node;
}

/*
 * qo_reduce_order_by_for () - move orderby_num() to groupby_num()
 *   return: NO_ERROR if successful, otherwise returns error number
 *   parser(in): parser global context info for reentrancy
 *   node(in): query node has ORDER BY
 *
 * Note:
 *   It modifies parser's heap of PT_NODEs(parser->error_msgs)
 *   and effects that remove order by for clause
 */
static int
qo_reduce_order_by_for (PARSER_CONTEXT * parser, PT_NODE * node)
{
  int error = NO_ERROR;
  PT_NODE *ord_num, *grp_num;

  if (node->node_type != PT_SELECT)
    {
      return error;
    }

  ord_num = NULL;
  grp_num = NULL;

  /* move orderby_num() to groupby_num() */
  if (node->info.query.orderby_for)
    {
      /* generate orderby_num(), groupby_num() */
      if (!(ord_num = parser_new_node (parser, PT_EXPR)) || !(grp_num = parser_new_node (parser, PT_FUNCTION)))
    {
      if (ord_num)
        {
          parser_free_tree (parser, ord_num);
        }
      PT_ERRORm (parser, node, MSGCAT_SET_PARSER_SEMANTIC, MSGCAT_SEMANTIC_OUT_OF_MEMORY);
      goto exit_on_error;
    }

      ord_num->type_enum = PT_TYPE_BIGINT;
      ord_num->info.expr.op = PT_ORDERBY_NUM;
      PT_EXPR_INFO_SET_FLAG (ord_num, PT_EXPR_INFO_ORDERBYNUM_C);

      grp_num->type_enum = PT_TYPE_BIGINT;
      grp_num->info.function.function_type = PT_GROUPBY_NUM;
      grp_num->info.function.arg_list = NULL;
      grp_num->info.function.all_or_distinct = PT_ALL;

      /* replace orderby_num() to groupby_num() */
      node->info.query.orderby_for = pt_lambda_with_arg (parser, node->info.query.orderby_for, ord_num, grp_num,
                             false /* loc_check: DEFAULT */ ,
                             2 /* type: don't walk into subquery */ ,
                             false /* dont_replace: DEFAULT */ );

      /* Even though node->info.q.query.q.select has no orderby_num so far, it is a safe guard to prevent potential
       * rewrite problem. */
      node->info.query.q.select.list = pt_lambda_with_arg (parser, node->info.query.q.select.list, ord_num, grp_num,
                               false /* loc_check: DEFAULT */ ,
                               2 /* type: don't walk into subquery */ ,
                               false /* dont_replace: DEFAULT */ );

      node->info.query.q.select.having =
    parser_append_node (node->info.query.orderby_for, node->info.query.q.select.having);

      node->info.query.orderby_for = NULL;

      parser_free_tree (parser, ord_num);
      parser_free_tree (parser, grp_num);
    }

exit_on_end:

  return error;

exit_on_error:

  if (error == NO_ERROR)
    {
      /* missing compiler error list */
      error = ER_GENERIC_ERROR;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, error, 0);
    }
  goto exit_on_end;
}

/*
 * qo_reduce_order_by () -
 *   return: NO_ERROR, if successful, otherwise returns error number
 *   parser(in): parser global context info for reentrancy
 *   node(in): query node has ORDER BY
 *
 * Note:
 *   It modifies parser's heap of PT_NODEs(parser->error_msgs)
 *   and effects that reduce the constant orders
 */
static int
qo_reduce_order_by (PARSER_CONTEXT * parser, PT_NODE * node)
{
  int error = NO_ERROR;
  PT_NODE *order, *order_next, *order_prev, *col, *col2, *col2_next;
  PT_NODE *r, *new_r;
  int i, j;
  int const_order_count, order_move_count;
  bool need_merge_check;
  bool has_orderbynum_with_groupby;

  /* do not reduce order by siblings */
  if (node->node_type != PT_SELECT || node->info.query.q.select.connect_by)
    {
      return error;
    }

  /* init */
  const_order_count = order_move_count = 0;
  need_merge_check = false;
  has_orderbynum_with_groupby = false;

  /* check for merge order by to group by( without DISTINCT and HAVING clause) */

  if (node->info.query.all_distinct == PT_DISTINCT)
    {
      ;             /* give up */
    }
  else
    {
      if (node->info.query.q.select.group_by && node->info.query.q.select.having == NULL && node->info.query.order_by)
    {
      bool ordbynum_flag;

      ordbynum_flag = false;    /* init */

      /* check for orderby_num() in the select list */
      (void) parser_walk_tree (parser, node->info.query.q.select.list, pt_check_orderbynum_pre, NULL,
                   pt_check_orderbynum_post, &ordbynum_flag);

      if (ordbynum_flag)
        {           /* found orderby_num() in the select list */
          has_orderbynum_with_groupby = true;   /* give up */
        }
      else
        {
          need_merge_check = true;  /* mark to checking */
        }
    }
    }

  /* the first phase, do check the current order by */
  if (need_merge_check)
    {
      if (pt_sort_spec_cover (node->info.query.q.select.group_by, node->info.query.order_by))
    {
      if (qo_reduce_order_by_for (parser, node) != NO_ERROR)
        {
          goto exit_on_error;
        }

      if (node->info.query.orderby_for == NULL && !node->info.query.q.select.connect_by
          && !node->info.query.q.select.group_by->flag.with_rollup)
        {
          /* clear unnecessary node info */
          parser_free_tree (parser, node->info.query.order_by);
          node->info.query.order_by = NULL;
        }

      need_merge_check = false; /* clear */
    }
    }

  order_prev = NULL;
  for (order = node->info.query.order_by; order; order = order_next)
    {
      order_next = order->next;

      r = order->info.sort_spec.expr;

      /*
       * safe guard: check for integer value. */
      if (r->node_type != PT_VALUE)
    {
      goto exit_on_error;
    }

      col = node->info.query.q.select.list;
      for (i = 1; i < r->info.value.data_value.i; i++)
    {
      if (col == NULL)
        {           /* impossible case */
          break;
        }
      col = col->next;
    }

      /*
       * safe guard: invalid parse tree */
      if (col == NULL)
    {
      goto exit_on_error;
    }

      col = pt_get_end_path_node (col);
      if (col->node_type == PT_NAME)
    {
      if (PT_NAME_INFO_IS_FLAGED (col, PT_NAME_INFO_CONSTANT))
        {
          /* remove constant order node */
          if (order_prev == NULL)
        {       /* the first */
          node->info.query.order_by = order->next;  /* re-link */
        }
          else
        {
          order_prev->next = order->next;   /* re-link */
        }
          order->next = NULL;   /* cut-off */
          parser_free_tree (parser, order);

          const_order_count++;  /* increase const entry remove count */

          continue;     /* go ahead */
        }

      /* for non-constant order, change order position to the same left-most col's position */
      col2 = node->info.query.q.select.list;
      for (j = 1; j < i; j++)
        {
          col2_next = col2->next;   /* save next link */

          col2 = pt_get_end_path_node (col2);

          /* change to the same left-most col */
          if (pt_name_equal (parser, col2, col))
        {
          new_r = parser_new_node (parser, PT_VALUE);
          if (new_r == NULL)
            {
              error = MSGCAT_SEMANTIC_OUT_OF_MEMORY;
              PT_ERRORm (parser, col, MSGCAT_SET_PARSER_SEMANTIC, error);
              goto exit_on_error;
            }

          new_r->type_enum = PT_TYPE_INTEGER;
          new_r->info.value.data_value.i = j;
          pt_value_to_db (parser, new_r);
          parser_free_tree (parser, r);
          order->info.sort_spec.expr = new_r;
          order->info.sort_spec.pos_descr.pos_no = j;

          order_move_count++;   /* increase entry move count */

          break;    /* exit for-loop */
        }

          col2 = col2_next; /* restore next link */
        }
    }

      order_prev = order;   /* go ahead */
    }

  if (order_move_count > 0)
    {
      PT_NODE *match;

      /* now check for duplicate entries.  - If they match on ascending/descending, remove the second.  - If they do
       * not, generate an error. */
      for (order = node->info.query.order_by; order; order = order->next)
    {
      while ((match = pt_find_order_value_in_list (parser, order->info.sort_spec.expr, order->next)))
        {
          if ((order->info.sort_spec.asc_or_desc != match->info.sort_spec.asc_or_desc)
          || (pt_to_null_ordering (order) != pt_to_null_ordering (match)))
        {
          error = MSGCAT_SEMANTIC_SORT_DIR_CONFLICT;
          PT_ERRORmf (parser, match, MSGCAT_SET_PARSER_SEMANTIC, error, pt_short_print (parser, match));
          goto exit_on_error;
        }
          else
        {
          order->next = pt_remove_from_list (parser, match, order->next);
        }
        }           /* while */
    }           /* for (order = ...) */
    }

  if (const_order_count > 0)
    {               /* is reduced */
      /* the second phase, do check with reduced order by */
      if (need_merge_check)
    {
      if (pt_sort_spec_cover (node->info.query.q.select.group_by, node->info.query.order_by))
        {
          if (qo_reduce_order_by_for (parser, node) != NO_ERROR)
        {
          goto exit_on_error;
        }

          if (node->info.query.orderby_for == NULL && !node->info.query.q.select.connect_by)
        {
          /* clear unnecessary node info */
          parser_free_tree (parser, node->info.query.order_by);
          node->info.query.order_by = NULL;
        }

          need_merge_check = false; /* clear */
        }
    }
      else
    {
      if (node->info.query.order_by == NULL)
        {
          /* move orderby_num() to inst_num() */
          if (node->info.query.orderby_for)
        {
          PT_NODE *ord_num, *ins_num;

          ord_num = NULL;
          ins_num = NULL;

          /* generate orderby_num(), inst_num() */
          if (!(ord_num = parser_new_node (parser, PT_EXPR)) || !(ins_num = parser_new_node (parser, PT_EXPR)))
            {
              if (ord_num)
            {
              parser_free_tree (parser, ord_num);
            }
              PT_ERRORm (parser, node, MSGCAT_SET_PARSER_SEMANTIC, MSGCAT_SEMANTIC_OUT_OF_MEMORY);
              goto exit_on_error;
            }

          ord_num->type_enum = PT_TYPE_BIGINT;
          ord_num->info.expr.op = PT_ORDERBY_NUM;
          PT_EXPR_INFO_SET_FLAG (ord_num, PT_EXPR_INFO_ORDERBYNUM_C);

          ins_num->type_enum = PT_TYPE_BIGINT;
          ins_num->info.expr.op = PT_INST_NUM;
          PT_EXPR_INFO_SET_FLAG (ins_num, PT_EXPR_INFO_INSTNUM_C);

          /* replace orderby_num() to inst_num() */
          node->info.query.orderby_for =
            pt_lambda_with_arg (parser, node->info.query.orderby_for, ord_num, ins_num,
                    false /* loc_check: DEFAULT */ ,
                    2 /* type: don't walk into subquery */ ,
                    false /* dont_replace: DEFAULT */ );

          node->info.query.q.select.list =
            pt_lambda_with_arg (parser, node->info.query.q.select.list, ord_num, ins_num,
                    false /* loc_check: DEFAULT */ ,
                    2 /* type: don't walk into subquery */ ,
                    false /* dont_replace: DEFAULT */ );

          node->info.query.q.select.where =
            parser_append_node (node->info.query.orderby_for, node->info.query.q.select.where);

          node->info.query.orderby_for = NULL;

          parser_free_tree (parser, ord_num);
          parser_free_tree (parser, ins_num);
        }
          else if (has_orderbynum_with_groupby == true)
        {
          PT_NODE *ord_num, *grp_num;

          ord_num = NULL;
          grp_num = NULL;

          /* generate orderby_num(), groupby_num() */
          if (!(ord_num = parser_new_node (parser, PT_EXPR))
              || !(grp_num = parser_new_node (parser, PT_FUNCTION)))
            {
              if (ord_num)
            {
              parser_free_tree (parser, ord_num);
            }
              PT_ERRORm (parser, node, MSGCAT_SET_PARSER_SEMANTIC, MSGCAT_SEMANTIC_OUT_OF_MEMORY);
              goto exit_on_error;
            }

          ord_num->type_enum = PT_TYPE_BIGINT;
          ord_num->info.expr.op = PT_ORDERBY_NUM;
          PT_EXPR_INFO_SET_FLAG (ord_num, PT_EXPR_INFO_ORDERBYNUM_C);

          grp_num->type_enum = PT_TYPE_BIGINT;
          grp_num->info.function.function_type = PT_GROUPBY_NUM;
          grp_num->info.function.arg_list = NULL;
          grp_num->info.function.all_or_distinct = PT_ALL;

          /* replace orderby_num() to groupby_num() */
          node->info.query.q.select.list = pt_lambda_with_arg (parser, node->info.query.q.select.list, ord_num,
                                       grp_num, false /* loc_check: DEFAULT */ ,
                                       2 /* type: don't walk into subquery */ ,
                                       false /* dont_replace: DEFAULT */ );

          parser_free_tree (parser, ord_num);
          parser_free_tree (parser, grp_num);
        }
          else
        {
          /* for select-list */
          PT_NODE *ord_num, *ins_num;

          ord_num = NULL;
          ins_num = NULL;

          /* generate orderby_num(), inst_num() */
          if (!(ord_num = parser_new_node (parser, PT_EXPR)) || !(ins_num = parser_new_node (parser, PT_EXPR)))
            {
              if (ord_num)
            {
              parser_free_tree (parser, ord_num);
            }
              PT_ERRORm (parser, node, MSGCAT_SET_PARSER_SEMANTIC, MSGCAT_SEMANTIC_OUT_OF_MEMORY);
              goto exit_on_error;
            }

          ord_num->type_enum = PT_TYPE_BIGINT;
          ord_num->info.expr.op = PT_ORDERBY_NUM;
          PT_EXPR_INFO_SET_FLAG (ord_num, PT_EXPR_INFO_ORDERBYNUM_C);

          ins_num->type_enum = PT_TYPE_BIGINT;
          ins_num->info.expr.op = PT_INST_NUM;
          PT_EXPR_INFO_SET_FLAG (ins_num, PT_EXPR_INFO_INSTNUM_C);

          node->info.query.q.select.list =
            pt_lambda_with_arg (parser, node->info.query.q.select.list, ord_num, ins_num,
                    false /* loc_check: DEFAULT */ ,
                    2 /* type: don't walk into subquery */ ,
                    false /* dont_replace: DEFAULT */ );

          parser_free_tree (parser, ord_num);
          parser_free_tree (parser, ins_num);
        }
        }
    }
    }

exit_on_end:

  return error;

exit_on_error:

  if (error == NO_ERROR)
    {
      /* missing compiler error list */
      error = ER_GENERIC_ERROR;
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, error, 0);
    }
  goto exit_on_end;
}

/*
 * qo_get_name_cnt_by_spec () - looks for a name with a matching id
 *   return: PT_NODE *
 *   parser(in): parser environment
 *   spec(in):
 *   arg(in): info of spec and result
 *   continue_walk(in):
 */
static PT_NODE *
qo_get_name_cnt_by_spec (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk)
{
  SPEC_CNT_INFO *info = (SPEC_CNT_INFO *) arg;

  if (node->node_type == PT_NAME)
    {
      if (node->info.name.spec_id == info->spec->info.spec.id)
    {
      info->my_spec_cnt++;
      info->my_spec_node = node;
    }
      else
    {
      info->other_spec_cnt++;
    }
    }

  if (info->my_spec_cnt >= 2 || (info->my_spec_cnt == 1 && info->other_spec_cnt >= 1))
    {
      *continue_walk = PT_STOP_WALK;
    }
  return node;
}

/*
 * qo_get_name_cnt_keep_unique () - looks for a name with a matching id
 *   return: PT_NODE *
 *   parser(in): parser environment
 *   spec(in):
 *   arg(in): info of spec and result
 *   continue_walk(in):
 */
static PT_NODE *
qo_get_name_cnt_keep_unique (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk)
{
  SPEC_CNT_INFO *info = (SPEC_CNT_INFO *) arg;

  *continue_walk = PT_CONTINUE_WALK;

  if (node->node_type == PT_NAME)
    {
      if (node->info.name.spec_id == info->spec->info.spec.id)
    {
      info->my_spec_cnt++;
      info->my_spec_node = node;
    }
      else
    {
      info->other_spec_cnt++;
    }
    }
  else if (node->node_type == PT_EXPR && pt_expr_keep_uniqueness (node))
    {
      /* keep going */
    }
  else if (node->node_type == PT_DATA_TYPE || node->node_type == PT_VALUE || node->node_type == PT_HOST_VAR)
    {
      /* don't walk into node */
      *continue_walk = PT_LIST_WALK;
    }
  else
    {
      /* impossible case */
      info->my_spec_cnt = 2;
      *continue_walk = PT_STOP_WALK;
    }

  if (info->my_spec_cnt >= 2 || (info->my_spec_cnt == 1 && info->other_spec_cnt >= 1))
    {
      *continue_walk = PT_STOP_WALK;
    }
  return node;
}

/*
 * qo_get_name_cnt_by_spec_no_on () - looks for a name with a matching id
 *   return: PT_NODE *
 *   parser(in): parser environment
 *   spec(in):
 *   arg(in): info of spec and result
 *   continue_walk(in):
 */
static PT_NODE *
qo_get_name_cnt_by_spec_no_on (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk)
{
  SPEC_CNT_INFO *info = (SPEC_CNT_INFO *) arg;
  *continue_walk = PT_CONTINUE_WALK;

  /* ignore on_cond of my spec */
  if ((pt_is_expr_node (node) && node->info.expr.location == info->spec->info.spec.location)
      || (pt_is_value_node (node) && node->info.value.location == info->spec->info.spec.location)
      || (node->node_type == PT_SPEC && node->info.spec.id == info->spec->info.spec.id))
    {
      *continue_walk = PT_LIST_WALK;
      return node;
    }

  if (node->node_type == PT_NAME)
    {
      if (node->info.name.spec_id == info->spec->info.spec.id)
    {
      info->my_spec_cnt++;
      info->my_spec_node = node;
    }
      else
    {
      info->other_spec_cnt++;
    }
    }

  if (info->my_spec_cnt >= 1)
    {
      *continue_walk = PT_STOP_WALK;
    }
  return node;
}

/*
 * qo_collect_name_with_eq_const () - collect name node with equal OP and constant value
 *   return: node_list
 *   parser(in):
 *   on_cond(in):
 *   spec(in):
 *
 * Note:
 * collect name nodes with following conditions
 *                arg1            op             arg2
 *     only have one of my spec   '='    not have my spec node
 * e.g.)      col(my spec)         =     2 (constant)
 *            a.col(my spec)       =     b.col
 *            a.col(my spec)       =     b.col + c.col
 */
static PT_NODE *
qo_collect_name_with_eq_const (PARSER_CONTEXT * parser, PT_NODE * on_cond, PT_NODE * spec)
{
  PT_NODE *pred, *arg1, *arg2;
  PT_NODE *point, *s_name, *point_list = NULL;
  SPEC_CNT_INFO info1, info2;

  info1.spec = spec;
  info2.spec = spec;

  pred = on_cond;
  while (pred != NULL)
    {
      if (pred->or_next != NULL)
    {
      pred = pred->next;
      continue;
    }
      if (!PT_IS_EXPR_NODE_WITH_OPERATOR (pred, PT_EQ))
    {
      pred = pred->next;
      continue;
    }
      /* check on_cond predicate */
      if (spec->info.spec.location != pred->info.expr.location)
    {
      pred = pred->next;
      continue;
    }

      arg1 = pred->info.expr.arg1;
      arg2 = pred->info.expr.arg2;
      /* find name with spec id */
      info1.my_spec_cnt = 0;
      info1.other_spec_cnt = 0;
      info2.my_spec_cnt = 0;
      info2.other_spec_cnt = 0;
      parser_walk_tree (parser, arg1, qo_get_name_cnt_keep_unique, &info1, NULL, NULL);
      parser_walk_tree (parser, arg2, qo_get_name_cnt_keep_unique, &info2, NULL, NULL);

      /* col1(my spec) = const */
      if (info1.my_spec_cnt == 1 && info1.other_spec_cnt == 0)
    {
      /* const */
      if (info2.my_spec_cnt == 0)
        {
          point_list = parser_append_node (pt_point (parser, info1.my_spec_node), point_list);
        }
    }
      /* const = col(my spec) */
      else if (info2.my_spec_cnt == 1 && info2.other_spec_cnt == 0)
    {
      /* const */
      if (info1.my_spec_cnt == 0)
        {
          point_list = parser_append_node (pt_point (parser, info2.my_spec_node), point_list);
        }
    }
      pred = pred->next;
    }
  return point_list;
}

/*
 * qo_modify_location () -
 *   return:
 *   parser(in):
 *   node(in):
 *   arg(in):
 *   continue_walk(in):
 */
static PT_NODE *
qo_modify_location (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk)
{
  RESET_LOCATION_INFO *infop = (RESET_LOCATION_INFO *) arg;

  if (node->node_type == PT_EXPR && node->info.expr.location == infop->start)
    {
      node->info.expr.location = infop->end;
    }

  if (node->node_type == PT_NAME && node->info.name.location == infop->start)
    {
      node->info.name.location = infop->end;
    }

  if (node->node_type == PT_VALUE && node->info.value.location == infop->start)
    {
      node->info.value.location = infop->end;
    }

  return node;
}

/*
 * qo_reset_spec_location () - reset location of spec
 *   return: node_list
 *   parser(in):
 *   on_cond(in):
 *   spec(in):
 *
 * Note:
 */
static void
qo_reset_spec_location (PARSER_CONTEXT * parser, PT_NODE * spec, PT_NODE * query)
{
  short curr_loc, after_loc;
  PT_NODE *where;
  RESET_LOCATION_INFO locate_info;

  while (spec != NULL)
    {
      curr_loc = spec->info.spec.location;
      after_loc = curr_loc - 1;

      if (curr_loc <= 0 || after_loc < 0)
    {
      spec = spec->next;
      continue;
    }

      /* reset location of spec */
      spec->info.spec.location = after_loc;

      /* reset location of predicate */
      locate_info.start = curr_loc;
      locate_info.end = after_loc;
      where = query->info.query.q.select.where;
      (void) parser_walk_tree (parser, where, qo_modify_location, &locate_info, NULL, NULL);

      spec = spec->next;
    }
}

/*
 * qo_reduce_outer_joined_tbls () - reduce outer joined tables with unique join predicates
 *   return:
 *   parser(in):
 *   spec(in):
 *   query(in):
 *
 * Note:
 *  examples:
 *                select count(*)
 *                  from tbl1 a left outer join tbl2 b on a.pk = b.pk <== outer joined table with unique join columns
 *                ==>
 *                select count(*)
 *                  from tbl1 a
 */
static PT_NODE *
qo_reduce_outer_joined_tbls (PARSER_CONTEXT * parser, PT_NODE * spec, PT_NODE * query)
{
  MOP cls;
  SM_CLASS_CONSTRAINT *consp;
  SM_ATTRIBUTE *attrp;
  PT_NODE *point_list = NULL, *point, *where, *col, *tmp_spec, *prev_spec, *pred, *prev_pred, *next_pred;
  PT_NODE *next_spec;
  SPEC_CNT_INFO info;
  bool all_unique_col_match = false;
  int i;

  /* check left outer join */
  /* TO_DO : for right outer join */
  if (spec == NULL || spec->info.spec.join_type != PT_JOIN_LEFT_OUTER)
    {
      return query;
    }

  /* check query */
  if (!PT_IS_SELECT (query))
    {
      return query;
    }

  /* check Inheritance of spec */
  if (spec->info.spec.only_all == PT_ALL)
    {
      return query;
    }

  /* check no_eliminate_join sql hint */
  if (query->info.query.q.select.hint & PT_HINT_NO_ELIMINATE_JOIN)
    {
      return query;
    }

  /* check referenced columns except on_cond */
  info.spec = spec;
  info.my_spec_cnt = 0;
  info.other_spec_cnt = 0;
  parser_walk_tree (parser, query, qo_get_name_cnt_by_spec_no_on, &info, NULL, NULL);
  if (info.my_spec_cnt >= 1)
    {
      /* Except for on_cond, the referenced columns exist. */
      return query;
    }

  where = query->info.query.q.select.where;
  /* get columns with equal op and constant in on_cond */
  point_list = qo_collect_name_with_eq_const (parser, where, spec);
  if (point_list == NULL)
    {
      return query;
    }

  /* get class info */
  cls = sm_find_class (spec->info.spec.flat_entity_list->info.name.original);
  if (cls == NULL)
    {
      goto end;
    }

  /* get index info of spec */
  consp = sm_class_constraints (cls);
  while (consp != NULL)
    {
      if (!SM_IS_CONSTRAINT_UNIQUE_FAMILY (consp->type))
    {
      consp = consp->next;
      continue;
    }

      /* check columns on this constraint */
      for (i = 0; consp->attributes[i]; i++)
    {
      attrp = consp->attributes[i];
      point = point_list;
      col = point;
      CAST_POINTER_TO_NODE (col);
      while (col != NULL)
        {
          if (intl_identifier_casecmp (col->info.name.original, attrp->header.name) == 0)
        {
          break;
        }
          point = point->next;
          col = point;
          CAST_POINTER_TO_NODE (col);
        }
      /* not find */
      if (col == NULL)
        {
          break;
        }
    }
      /* matche all columns of the unique index */
      if (consp->attributes[i] == NULL)
    {
      all_unique_col_match = true;
      break;
    }

      consp = consp->next;
    }

  if (all_unique_col_match)
    {
      /* remove unnecessary table spec */
      /* find previous spec */
      prev_spec = NULL;
      tmp_spec = query->info.query.q.select.from;
      while (tmp_spec && tmp_spec->info.spec.id != spec->info.spec.id)
    {
      prev_spec = tmp_spec;
      tmp_spec = tmp_spec->next;
    }
      if (tmp_spec == NULL || prev_spec == NULL)
    {
      goto end;
    }

      /* cut off unnecessary spec */
      prev_spec->next = next_spec = spec->next;
      spec->next = NULL;

      /* remove on_cond predicate */
      prev_pred = NULL;
      pred = query->info.query.q.select.where;
      while (pred != NULL)
    {
      next_pred = pred->next;
      if ((pt_is_expr_node (pred) && pred->info.expr.location == spec->info.spec.location)
          || (pt_is_value_node (pred) && pred->info.value.location == spec->info.spec.location))
        {
          if (prev_pred == NULL)
        {
          /* first time */
          query->info.query.q.select.where = next_pred;
          pred->next = NULL;
          parser_free_tree (parser, pred);
        }
          else
        {
          prev_pred->next = next_pred;
          pred->next = NULL;
          parser_free_tree (parser, pred);
        }
        }
      else
        {
          prev_pred = pred;
        }
      pred = next_pred;
    }

      /* reset location */
      qo_reset_spec_location (parser, next_spec, query);

      /* free spec */
      parser_free_tree (parser, spec);
    }

end:
  if (point_list != NULL)
    {
      parser_free_tree (parser, point_list);
    }
  return query;
}

/*
 * qo_reduce_joined_tbls_ref_by_fk () - Removes a table with a primary key from a join
 *                                                        with a table with a foreign key referencing it.
 *   return: void
 *   parser(in): parser context
 *   query(in): query to check
 *
 * Note: A table with a primary key is removed from a join with a table with a foreign key
 *       that references the primary key. There must be no references to the table to be removed
 *       other than join predicates. This is because the relationship between a primary key and a foreign key
 *       replaces a data filter of a join. If a table with a primary key is removed, a predicate for IS NOT NULL
 *       may be added, as it cannot filter on NULL.
 *
 *    e.g. drop if exists child, parent;
 *         create table parent (c1 int primary key, c2 int);
 *         create table child (c1 int, parent_c1 int references parent (c1), c2 int);
 *
 *         select c.* from child c inner join parent p on p.c1 = c.parent_c1 where c.c2 = 1;
 *         select c.* from child c, parent p where c.parent_c1 = p.c1 and c.c2 = 1;
 *           -> select c.* from child c where c.c2 = 1 and c.parent_c1 is not null;
 *
 *         select c.* from child c inner join parent p on p.c1 = c.parent_c1 where p.c2 = 1;
 *         select c.* from child c, parent p where c.parent_c1 = p.c1 and p.c2 = 1;
 *           -> do not change.
 */
static void
qo_reduce_joined_tbls_ref_by_fk (PARSER_CONTEXT * parser, PT_NODE * query)
{
  QO_REDUCE_REFERENCE_INFO reduce_reference_info;
  PT_NODE *curr_pk_spec = NULL, *prev_pk_spec = NULL, *next_pk_spec = NULL;
  PT_NODE *curr_fk_spec = NULL;
  bool has_reduce = false;

  assert (parser != NULL && query != NULL);

  memset (&reduce_reference_info, 0, sizeof (QO_REDUCE_REFERENCE_INFO));

  if (query->node_type != PT_SELECT)
    {
      return;
    }

  if (query->info.query.q.select.hint & PT_HINT_NO_ELIMINATE_JOIN)
    {
      return;
    }

  if (query->info.query.q.select.where == NULL)
    {
      return;
    }

  do
    {
      has_reduce = false;

      for (prev_pk_spec = NULL, curr_pk_spec = query->info.query.q.select.from; curr_pk_spec != NULL;
       prev_pk_spec = curr_pk_spec, curr_pk_spec = curr_pk_spec->next)
    {
      if (qo_is_exclude_spec (reduce_reference_info.exclude_pk_spec_point_list, curr_pk_spec))
        {
          continue;     /* curr_pk_spec->next */
        }

      reduce_reference_info.pk_spec = curr_pk_spec;

      if (!qo_check_pk_ref_by_fk_in_parent_spec (parser, query, &reduce_reference_info))
        {
          if (er_has_error ())
        {
          goto exit_on_fail_with_cleanup;
        }

          continue;     /* curr_pk_spec->next */
        }

      for (curr_fk_spec = query->info.query.q.select.from; curr_fk_spec != NULL; curr_fk_spec = curr_fk_spec->next)
        {
          if (curr_pk_spec == curr_fk_spec)
        {
          continue; /* curr_fk_spec->next */
        }

          if (qo_is_exclude_spec (reduce_reference_info.exclude_fk_spec_point_list, curr_fk_spec))
        {
          continue; /* curr_fk_spec->next */
        }

          reduce_reference_info.fk_spec = curr_fk_spec;

          if (qo_check_fks_ref_pk_in_child_spec (parser, query, &reduce_reference_info))
        {
          break;
        }
          else
        {
          if (er_has_error ())
            {
              goto exit_on_fail_with_cleanup;
            }

          continue; /* curr_fk_spec->next */
        }
        }

      if (curr_fk_spec == NULL)
        {
          /* not found */
          continue;     /* curr_pk_spec->next */
        }

      /* Do not use next_pk_spec in the for-loop because curr_pk_spec may change. */
      next_pk_spec = curr_pk_spec->next;

      /* safe guard */
      if (prev_pk_spec == NULL
          && (next_pk_spec->info.spec.join_type == PT_JOIN_LEFT_OUTER
          || next_pk_spec->info.spec.join_type == PT_JOIN_RIGHT_OUTER
          || next_pk_spec->info.spec.join_type == PT_JOIN_FULL_OUTER))
        {
          continue;     /* give up */
        }

      qo_reduce_predicate_for_parent_spec (parser, query, &reduce_reference_info);

      assert (reduce_reference_info.join_pred_point_list == NULL);
      assert (reduce_reference_info.parent_pred_point_list == NULL);
      assert (reduce_reference_info.append_not_null_pred_list == NULL);

      if (prev_pk_spec != NULL)
        {
          prev_pk_spec->next = next_pk_spec;
        }
      else
        {
          query->info.query.q.select.from = next_pk_spec;

          next_pk_spec->info.spec.join_type = PT_JOIN_NONE;
          next_pk_spec->info.spec.natural = false;
        }

      /* reset location */
      qo_reset_spec_location (parser, next_pk_spec, query);

      parser_free_node (parser, curr_pk_spec);
      curr_pk_spec = next_pk_spec;

      has_reduce = true;

      if (curr_pk_spec == NULL)
        {
          /* first again */
          break;
        }
    }
    }
  while (has_reduce);

  /* end */

exit_on_fail_with_cleanup:
  if (reduce_reference_info.exclude_pk_spec_point_list != NULL)
    {
      parser_free_tree (parser, reduce_reference_info.exclude_pk_spec_point_list);
      reduce_reference_info.exclude_pk_spec_point_list = NULL;
    }

  if (reduce_reference_info.exclude_fk_spec_point_list != NULL)
    {
      parser_free_tree (parser, reduce_reference_info.exclude_fk_spec_point_list);
      reduce_reference_info.exclude_fk_spec_point_list = NULL;
    }

  if (reduce_reference_info.join_pred_point_list != NULL)
    {
      parser_free_tree (parser, reduce_reference_info.join_pred_point_list);
      reduce_reference_info.join_pred_point_list = NULL;
    }

  if (reduce_reference_info.parent_pred_point_list != NULL)
    {
      parser_free_tree (parser, reduce_reference_info.parent_pred_point_list);
      reduce_reference_info.parent_pred_point_list = NULL;
    }

  if (reduce_reference_info.append_not_null_pred_list != NULL)
    {
      parser_free_tree (parser, reduce_reference_info.append_not_null_pred_list);
      reduce_reference_info.append_not_null_pred_list = NULL;
    }

  return;
}

/*
 * qo_is_exclude_spec () - Whether the given spec exists in the list of specs to exclude.
 *   return: bool
 *   exclude_spec_point_list(in): list of specs to exclude
 *   spec(in): spec to find
 *
 * Note: In the qo_reduce_joined_tbls_ref_by_fk() function,
 *       if the checked spec does not need to be checked again, it is added to the list of specs to exclude.
 */
static bool
qo_is_exclude_spec (PT_NODE * exclude_spec_point_list, PT_NODE * spec)
{
  PT_NODE *exclude_spec_point = NULL, *exclude_spec = NULL;

  for (exclude_spec_point = exclude_spec_point_list; exclude_spec_point != NULL;
       exclude_spec_point = exclude_spec_point->next)
    {
      exclude_spec = exclude_spec_point;
      CAST_POINTER_TO_NODE (exclude_spec);

      if (exclude_spec == spec)
    {
      return true;
    }
    }

  return false;
}

/*
 * qo_check_pk_ref_by_fk_in_parent_spec () - Whether the given spec has a primary key
 *                                                                    referenced by a foreign key.
 *   return: bool
 *   parser(in): parser context
 *   query(in): query to check
 *   reduce_reference_info(in/out): Information needed to check
 *
 * Note: If the given spec can be removed, set the memory object pointer of the given spec and a constraint pointer
 *       of a primary key to reduce_reference_info. And the predicates of the given spec are added
 *       to the join_pred_point_list.
 * 
 *       In the following cases, add to the exclude_pk_spec_point_list.
 *         1. Access to hierarchical tables.
 *         2. Not an inner join or natural join.
 *         3. CTEs or derived tables.
 *         4. No primary key.
 *         5. No foreign key referencing the primary key.
 *         6. Non-join predicates exist.
 */
static bool
qo_check_pk_ref_by_fk_in_parent_spec (PARSER_CONTEXT * parser, PT_NODE * query,
                      QO_REDUCE_REFERENCE_INFO * reduce_reference_info)
{
  PT_NODE *curr_pk_spec = NULL;
  MOP curr_pk_mop = NULL;
  SM_CLASS_CONSTRAINT *curr_pk_cons = NULL;
  PT_NODE *join_pred_point_list = NULL;
  PT_NODE *parent_pred_point_list = NULL;
  PT_NODE *curr_pred_point = NULL;
  PT_NODE *curr_pred = NULL, *next_pred = NULL;
  int cons_attr_cnt;
  unsigned int cons_attr_flag;
  int i;

  assert (parser != NULL && query != NULL);
  assert (reduce_reference_info != NULL);
  assert (reduce_reference_info->pk_spec != NULL);

  reduce_reference_info->pk_mop = NULL;
  reduce_reference_info->pk_cons = NULL;

  if (reduce_reference_info->join_pred_point_list != NULL)
    {
      parser_free_tree (parser, reduce_reference_info->join_pred_point_list);
      reduce_reference_info->join_pred_point_list = NULL;
    }

  if (reduce_reference_info->parent_pred_point_list != NULL)
    {
      parser_free_tree (parser, reduce_reference_info->parent_pred_point_list);
      reduce_reference_info->parent_pred_point_list = NULL;
    }

  curr_pk_spec = reduce_reference_info->pk_spec;
  assert (PT_NODE_IS_SPEC (curr_pk_spec));

  /* PT_ALL is not supported. */
  if (PT_SPEC_IS_ALL (curr_pk_spec))
    {
      goto exit_on_fail_with_exclude;
    }

  switch (PT_SPEC_JOIN_TYPE (curr_pk_spec))
    {
    case PT_JOIN_NONE:
    case PT_JOIN_INNER:
    case PT_JOIN_NATURAL:
      break;

    case PT_JOIN_CROSS:
    case PT_JOIN_LEFT_OUTER:
    case PT_JOIN_RIGHT_OUTER:
    case PT_JOIN_FULL_OUTER:
    case PT_JOIN_UNION:
    default:
      goto exit_on_fail_with_exclude;
    }

  PT_SPEC_GET_DB_OBJECT (curr_pk_spec, curr_pk_mop);
  if (curr_pk_mop == NULL)
    {
      /* CTEs and derived tables are excluded. */
      goto exit_on_fail_with_exclude;
    }

  for (curr_pk_cons = sm_class_constraints (curr_pk_mop); curr_pk_cons != NULL; curr_pk_cons = curr_pk_cons->next)
    {
      if (curr_pk_cons->type == SM_CONSTRAINT_PRIMARY_KEY)
    {
      /* found */
      break;
    }
    }

  if (curr_pk_cons == NULL || curr_pk_cons->fk_info == NULL)
    {
      /* No primary key or no foreign key referencing the primary key. */
      goto exit_on_fail_with_exclude;
    }

  /* There must be no non-join predicates. */
  for (curr_pred = query->info.query.q.select.where; curr_pred != NULL; curr_pred = curr_pred->next)
    {
      SPEC_CNT_INFO info;

      memset (&info, 0, sizeof (SPEC_CNT_INFO));
      info.spec = curr_pk_spec;

      next_pred = curr_pred->next;
      curr_pred->next = NULL;

      parser_walk_tree (parser, curr_pred, qo_get_name_cnt_by_spec, &info, NULL, NULL);

      curr_pred->next = next_pred;

      if (info.my_spec_cnt >= 1)
    {
      if (curr_pred->or_next == NULL && PT_NODE_IS_EXPR (curr_pred) && PT_EXPR_OP (curr_pred) == PT_EQ)
        {
          if (pt_is_attr (PT_EXPR_ARG1 (curr_pred)) && pt_is_attr (PT_EXPR_ARG2 (curr_pred)))
        {
          join_pred_point_list = parser_append_node (pt_point (parser, curr_pred), join_pred_point_list);
          continue;
        }

          /* Some predicates are non-join predicates, but can be reduced by predicate fulfillment.
           * 
           *   e.g. drop table if exists child, parent;
           *        reate table parent (c1 int, c2 int, primary key (c1, c2));
           *        create table child (c1 int, c2 int);
           *        alter table child add constraint foreign key (c1, c2) references parent (c1, c2);
           *
           *        select c.* from child c, parent p where c.c1 = p.c1 and c.c2 = p.c2 and p.c1 = 1;
           * 
           *        -- rewritten query
           *        select c.* from child c, parent p where c.c1 = p.c1 and c.c2 = p.c2 and p.c1 = 1 and c.c1 = 1;
           * 
           *        -- execute query
           *        select c.* from child c where c.c1 = 1;
           *
           * 'p.c1 = 1' is a reference to the parent, but can be reduced if 'c.c1 = 1' exists.
           * In the rewrite query, since 'c.c1 = 1' is added by predicate fulfillment, eliminate join is possible
           * even if 'p.c1 = 1' exists.
           */
          if ((pt_is_attr (PT_EXPR_ARG1 (curr_pred)) && qo_is_reduceable_const (PT_EXPR_ARG2 (curr_pred))) ||
          (pt_is_attr (PT_EXPR_ARG2 (curr_pred)) && qo_is_reduceable_const (PT_EXPR_ARG1 (curr_pred))))
        {
          parent_pred_point_list = parser_append_node (pt_point (parser, curr_pred), parent_pred_point_list);
          continue;
        }

          goto exit_on_fail_with_exclude;
        }
      else
        {
          /* Non-join predicates exist. */
          goto exit_on_fail_with_exclude;
        }
    }
    }

  if (join_pred_point_list == NULL)
    {
      /* There are no join predicates. */
      goto exit_on_fail_with_exclude;
    }

  /* We need to check if all the attributes of the constraint are used in predicated. */
  for (i = 0; curr_pk_cons->attributes[i] != NULL; i++);

  /* Set the bits of cons_attr_flag to 1 as many as the number of attributes in the constraint.
   * And if an attribute of the constraint is used in a predicate, it sets the bit of that index to 0.
   * After checking the predicates, if cons_attr_flag is 0, we know that all the attributes of the constraint
   * are used in the predicate. */
  cons_attr_cnt = i;
  cons_attr_flag = (1 << i) - 1;

  /* The columns of join predicates must be in the primary key. */
  for (curr_pred_point = join_pred_point_list; curr_pred_point != NULL; curr_pred_point = curr_pred_point->next)
    {
      SM_ATTRIBUTE *pk_cons_attr = NULL;
      PT_NODE *pk_pred_attr = NULL;
      PT_NODE *arg1 = NULL, *arg2 = NULL;
      int i = 0;

      curr_pred = curr_pred_point;
      CAST_POINTER_TO_NODE (curr_pred);

      assert (PT_NODE_IS_EXPR (curr_pred));
      assert (PT_EXPR_OP (curr_pred) == PT_EQ);
      assert (PT_NODE_IS_NAME (PT_EXPR_ARG1 (curr_pred)));
      assert (PT_NODE_IS_NAME (PT_EXPR_ARG2 (curr_pred)));

      arg1 = PT_EXPR_ARG1 (curr_pred);
      arg2 = PT_EXPR_ARG2 (curr_pred);

      if (PT_SPEC_ID (curr_pk_spec) == PT_NAME_SPEC_ID (arg1))
    {
      pk_pred_attr = PT_EXPR_ARG1 (curr_pred);
    }
      else if (PT_SPEC_ID (curr_pk_spec) == PT_NAME_SPEC_ID (arg2))
    {
      pk_pred_attr = PT_EXPR_ARG2 (curr_pred);
    }
      else
    {
      /* Already checked before. */
      assert (false);
      goto exit_on_fail_with_exclude;
    }

      for (i = 0; curr_pk_cons->attributes[i] != NULL; i++)
    {
      if (intl_identifier_casecmp (curr_pk_cons->attributes[i]->header.name, PT_NAME_ORIGINAL (pk_pred_attr)) == 0)
        {
          /* found */
          pk_cons_attr = curr_pk_cons->attributes[i];

          /* If an attribute of the constraint is used in a predicate, it sets the bit of that index to 0. */
          cons_attr_flag &= ~(1 << i);

          break;        /* curr_pred_point->next */
        }
    }

      if (pk_cons_attr == NULL)
    {
      /* not found */
      goto exit_on_fail_with_cleanup;
    }
    }

  assert (curr_pred_point == NULL);

  /* If cons_attr_flag is non-zero, then all the attributes of the constraint are not used in the predicate. */
  if (cons_attr_flag != 0)
    {
      goto exit_on_fail_with_exclude;
    }

  {
    SPEC_CNT_INFO info;
    PT_NODE *backup_from = NULL;
    PT_NODE *backup_where = NULL;

    /* qo_get_name_cnt_by_spec_no_on does not check PT_EXPR in the select_list.
     * qo_get_name_cnt_by_spec increases my_spec_cnt too much if from exists.
     * So I check both.
     */

    /* STEP 1 */
    memset (&info, 0, sizeof (SPEC_CNT_INFO));
    info.spec = curr_pk_spec;

    backup_from = query->info.query.q.select.from;
    backup_where = query->info.query.q.select.where;
    query->info.query.q.select.from = NULL;
    query->info.query.q.select.where = NULL;

    parser_walk_tree (parser, query, qo_get_name_cnt_by_spec, &info, NULL, NULL);

    query->info.query.q.select.from = backup_from;
    query->info.query.q.select.where = backup_where;

    if (info.my_spec_cnt >= 1)
      {
    goto exit_on_fail_with_exclude;
      }

    /* STEP 2 */
    memset (&info, 0, sizeof (SPEC_CNT_INFO));
    info.spec = curr_pk_spec;

    parser_walk_tree (parser, query->info.query.q.select.from, qo_get_name_cnt_by_spec_no_on, &info, NULL, NULL);

    if (info.my_spec_cnt >= 1)
      {
    goto exit_on_fail_with_exclude;
      }
  }

  reduce_reference_info->pk_mop = curr_pk_mop;
  reduce_reference_info->pk_cons = curr_pk_cons;
  reduce_reference_info->join_pred_point_list = join_pred_point_list;
  reduce_reference_info->parent_pred_point_list = parent_pred_point_list;

  return true;

exit_on_fail_with_exclude:
  reduce_reference_info->exclude_pk_spec_point_list =
    parser_append_node (pt_point (parser, curr_pk_spec), reduce_reference_info->exclude_pk_spec_point_list);
  /* fallthrough */

exit_on_fail_with_cleanup:
  if (join_pred_point_list != NULL)
    {
      parser_free_tree (parser, join_pred_point_list);
      join_pred_point_list = NULL;
    }

  if (parent_pred_point_list != NULL)
    {
      parser_free_tree (parser, parent_pred_point_list);
      parent_pred_point_list = NULL;
    }

  return false;
}

/*
 * qo_check_fks_ref_pk_in_child_spec () - Whether the given spec has a foreign key
 *                                                                  referencing a primary key.
 *   return: bool
 *   parser(in): parser context
 *   query(in): query to check
 *   reduce_reference_info(in/out): Information needed to check
 *
 * Note: For each foreign key of a given spec, it is checked
 *       in the qo_check_fk_ref_pk_in_child_spec() function.
 * 
 *       In the following cases, add to the exclude_fk_spec_point_list.
 *         1. Access to hierarchical tables.
 *         2. Not an inner join or natural join.
 *         3. CTEs or derived tables.
 *         4. No foreign key.
 */
static bool
qo_check_fks_ref_pk_in_child_spec (PARSER_CONTEXT * parser, PT_NODE * query,
                   QO_REDUCE_REFERENCE_INFO * reduce_reference_info)
{
  PT_NODE *curr_fk_spec = NULL;
  MOP curr_fk_mop = NULL;
  SM_CLASS_CONSTRAINT *curr_fk_cons = NULL;

  assert (parser != NULL && query != NULL);
  assert (reduce_reference_info != NULL);
  assert (reduce_reference_info->pk_spec != NULL);
  assert (reduce_reference_info->pk_mop != NULL);
  assert (reduce_reference_info->pk_cons != NULL);
  assert (reduce_reference_info->fk_spec != NULL);
  assert (reduce_reference_info->join_pred_point_list != NULL);

  reduce_reference_info->fk_cons = NULL;

  if (reduce_reference_info->append_not_null_pred_list != NULL)
    {
      parser_free_tree (parser, reduce_reference_info->append_not_null_pred_list);
      reduce_reference_info->append_not_null_pred_list = NULL;
    }

  curr_fk_spec = reduce_reference_info->fk_spec;
  assert (PT_NODE_IS_SPEC (curr_fk_spec));

  /* PT_ALL is not supported. */
  if (PT_SPEC_IS_ALL (curr_fk_spec))
    {
      goto exit_on_fail_with_exclude;
    }

  switch (PT_SPEC_JOIN_TYPE (curr_fk_spec))
    {
    case PT_JOIN_NONE:
    case PT_JOIN_INNER:
    case PT_JOIN_NATURAL:
      break;

    case PT_JOIN_CROSS:
    case PT_JOIN_LEFT_OUTER:
    case PT_JOIN_RIGHT_OUTER:
    case PT_JOIN_FULL_OUTER:
    case PT_JOIN_UNION:
      [[fallthrough]];
    default:
      goto exit_on_fail_with_exclude;
    }

  PT_SPEC_GET_DB_OBJECT (curr_fk_spec, curr_fk_mop);
  if (curr_fk_mop == NULL)
    {
      /* CTEs and derived tables are excluded. */
      goto exit_on_fail_with_exclude;
    }

  for (curr_fk_cons = sm_class_constraints (curr_fk_mop); curr_fk_cons != NULL; curr_fk_cons = curr_fk_cons->next)
    {
      if (curr_fk_cons->type != SM_CONSTRAINT_FOREIGN_KEY)
    {
      continue;
    }

      reduce_reference_info->fk_cons = curr_fk_cons;

      if (!qo_check_fk_ref_pk_in_child_spec (parser, query, reduce_reference_info))
    {
      if (er_has_error ())
        {
          goto exit_on_fail;
        }

      continue;
    }

      if (reduce_reference_info->parent_pred_point_list == NULL)
    {
      return true;
    }

      if (qo_check_reduce_predicate_for_parent_spec (parser, query, reduce_reference_info))
    {
      return true;
    }
      else
    {
      if (er_has_error ())
        {
          goto exit_on_fail;
        }

      continue;
    }
    }

  assert (curr_fk_cons == NULL);

  if (reduce_reference_info->fk_cons == NULL)
    {
      /* No foreign key */
      goto exit_on_fail_with_exclude;
    }

  goto exit_on_fail;

exit_on_fail_with_exclude:
  reduce_reference_info->exclude_fk_spec_point_list =
    parser_append_node (pt_point (parser, curr_fk_spec), reduce_reference_info->exclude_fk_spec_point_list);

exit_on_fail:
  return false;
}

/*
 * qo_check_fk_ref_pk_in_child_spec () - Whether the given spec has a foreign key
 *                                                                 referencing a primary key.
 *   return: bool
 *   parser(in): parser context
 *   query(in): query to check
 *   reduce_reference_info(in/out): Information needed to check
 *
 * Note: Check whether the primary key of pk_spec in reduce_reference_info is referenced by the foreign key of fk_spec
 *       in reduce_reference_info. And check whether the column used in the join predicates exists in the primary key
 *       and the foreign key. If there is no not null constraint on the column existing in the foreign key,
 *       add a predicate for IS NOT NULL.
 */
static bool
qo_check_fk_ref_pk_in_child_spec (PARSER_CONTEXT * parser, PT_NODE * query,
                  QO_REDUCE_REFERENCE_INFO * reduce_reference_info)
{
  PT_NODE *curr_pk_spec = NULL, *curr_fk_spec = NULL;
  MOP curr_pk_mop = NULL;
  SM_CLASS_CONSTRAINT *curr_pk_cons = NULL, *curr_fk_cons = NULL;
  SM_ATTRIBUTE *pk_cons_attr, *fk_cons_attr;
  PT_NODE *join_pred_point_list = NULL;
  PT_NODE *append_not_null_pred_list = NULL;
  PT_NODE *curr_pred_point = NULL;
  PT_NODE *curr_pred = NULL;
  PT_NODE *pk_pred_attr, *fk_pred_attr;
  PT_NODE *copy_fk_pred_attr, *fk_not_null_pred;
  int cons_attr_cnt;
  unsigned int cons_attr_flag;
  PT_NODE *arg1, *arg2;
  int i = 0;

  assert (parser != NULL && query != NULL);
  assert (reduce_reference_info != NULL);
  assert (reduce_reference_info->pk_spec != NULL);
  assert (reduce_reference_info->pk_mop != NULL);
  assert (reduce_reference_info->pk_cons != NULL);
  assert (reduce_reference_info->fk_spec != NULL);
  assert (reduce_reference_info->fk_cons != NULL);
  assert (reduce_reference_info->join_pred_point_list != NULL);
  assert (reduce_reference_info->append_not_null_pred_list == NULL);

  curr_pk_spec = reduce_reference_info->pk_spec;
  curr_pk_mop = reduce_reference_info->pk_mop;
  curr_pk_cons = reduce_reference_info->pk_cons;
  curr_fk_spec = reduce_reference_info->fk_spec;
  curr_fk_cons = reduce_reference_info->fk_cons;
  join_pred_point_list = reduce_reference_info->join_pred_point_list;

  assert (curr_fk_cons->type == SM_CONSTRAINT_FOREIGN_KEY);

  /* We must check that the oid of the parent table is equal to the fk_info->ref_class_oid
   * of the foreign key constraint.
   * 
   *   e.g. drop table if exists child, parent, super_parent;
   *        create table super_parent (c1 int primary key);
   *        create table parent under super_parent (c2 int);
   *        create table child (c1 int);
   *        alter table child add constraint foreign key (c1) references parent (c1);
   *
   *        -- irreducible
   *        select c.* from child c, super_parent s where c.c1 = s.c1;
   *
   *        -- reducible
   *        select c.* from child c, parent s where c.c1 = s.c1;
   */

  /* WS_OID or WS_REAL_OID, which one? */
  if (!OID_EQ (WS_REAL_OID (curr_pk_mop), &curr_fk_cons->fk_info->ref_class_oid))
    {
      goto exit_on_fail;
    }

  if (!BTID_IS_EQUAL (&(curr_pk_cons->index_btid), &(curr_fk_cons->fk_info->ref_class_pk_btid)))
    {
      goto exit_on_fail;
    }

  /* We need to check if all the attributes of the constraint are used in predicated.
   *
   *   e.g. drop table if exists child, parent;
   *        create table parent (c1 int, c2 int, primary key (c1, c2));
   *        create table child (c1 int, c2 int);
   *        alter table child add constraint foreign key (c1, c2) references parent (c1, c2);
   *
   *        -- irreducible
   *        select c.* from child c, parent p where c.c1 = p.c1;
   *        select c.* from child c, parent p where c.c2 = p.c2;
   *        select c.* from child c, parent p where c.c1 = p.c1 and c.c1 = p.c1;
   *
   *        -- reducible
   *        select c.* from child c, parent p where c.c1 = p.c1 and c.c2 = p.c2;
   */
  for (i = 0; curr_pk_cons->attributes[i] != NULL && curr_fk_cons->attributes[i] != NULL; i++);

  /* Set the bits of cons_attr_flag to 1 as many as the number of attributes in the constraint.
   * And if an attribute of the constraint is used in a predicate, it sets the bit of that index to 0.
   * After checking the predicates, if cons_attr_flag is 0, we know that all the attributes of the constraint
   * are used in the predicate. */
  cons_attr_cnt = i;
  cons_attr_flag = (1 << i) - 1;

  for (curr_pred_point = join_pred_point_list; curr_pred_point != NULL; curr_pred_point = curr_pred_point->next)
    {
      curr_pred = curr_pred_point;
      CAST_POINTER_TO_NODE (curr_pred);

      assert (PT_NODE_IS_EXPR (curr_pred));
      assert (PT_EXPR_OP (curr_pred) == PT_EQ);
      assert (PT_NODE_IS_NAME (PT_EXPR_ARG1 (curr_pred)));
      assert (PT_NODE_IS_NAME (PT_EXPR_ARG2 (curr_pred)));

      arg1 = PT_EXPR_ARG1 (curr_pred);
      arg2 = PT_EXPR_ARG2 (curr_pred);

      if (PT_SPEC_ID (curr_pk_spec) == PT_NAME_SPEC_ID (arg1))
    {
      pk_pred_attr = PT_EXPR_ARG1 (curr_pred);
    }
      else if (PT_SPEC_ID (curr_pk_spec) == PT_NAME_SPEC_ID (arg2))
    {
      pk_pred_attr = PT_EXPR_ARG2 (curr_pred);
    }
      else
    {
      /* Already checked before. */
      assert (false);
      goto exit_on_fail_with_cleanup;
    }

      if (PT_SPEC_ID (curr_fk_spec) == PT_NAME_SPEC_ID (arg1))
    {
      fk_pred_attr = PT_EXPR_ARG1 (curr_pred);
    }
      else if (PT_SPEC_ID (curr_fk_spec) == PT_NAME_SPEC_ID (arg2))
    {
      fk_pred_attr = PT_EXPR_ARG2 (curr_pred);
    }
      else
    {
      goto exit_on_fail_with_cleanup;
    }

      assert (pk_pred_attr != NULL);
      assert (fk_pred_attr != NULL);

      for (i = 0; curr_pk_cons->attributes[i] != NULL && curr_fk_cons->attributes[i] != NULL; i++)
    {
      pk_cons_attr = curr_pk_cons->attributes[i];
      fk_cons_attr = curr_fk_cons->attributes[i];

      if (intl_identifier_casecmp (pk_cons_attr->header.name, PT_NAME_ORIGINAL (pk_pred_attr)) == 0)
        {
          if (intl_identifier_casecmp (fk_cons_attr->header.name, PT_NAME_ORIGINAL (fk_pred_attr)) == 0)
        {
          /* If there is no not null constraint on a column of the table with the foreign key,
           * predicates for IS NOT NULL must be added. */
          if (!(fk_cons_attr->flags & SM_ATTFLAG_NON_NULL))
            {
              copy_fk_pred_attr = parser_copy_tree (parser, fk_pred_attr);
              fk_not_null_pred = parser_make_expression (parser, PT_IS_NOT_NULL, copy_fk_pred_attr, NULL, NULL);
              append_not_null_pred_list = parser_append_node (fk_not_null_pred, append_not_null_pred_list);
            }

          /* If an attribute of the constraint is used in a predicate, it sets the bit of that index to 0. */
          cons_attr_flag &= ~(1 << i);

          break;    /* curr_pred_point->next */
        }
          else
        {
          /* It cannot be reduced in fk_child_c1_c2 but can be reduced in fk_child_c2_c1.
           * 
           *   e.g. drop if exists child, parent;
           *        create table parent (c1 int, c2 int, primary key (c1, c2));
           *        create table child (c1 int, c2 int);
           *        alter table child add constraint foreign key (c1, c2) references parent (c1, c2);
           *        alter table child add constraint foreign key (c2, c1) references parent (c1, c2);
           *
           *        select c.* from child c, parent p where c.c1 = p.c2 and c.c2 = p.c1;
           */
          goto exit_on_fail_with_cleanup;
        }
        }
    }
    }

  assert (curr_pred_point == NULL);

  /* If cons_attr_flag is non-zero, then all the attributes of the constraint are not used in the predicate. */
  if (cons_attr_flag != 0)
    {
      goto exit_on_fail_with_cleanup;
    }

  reduce_reference_info->append_not_null_pred_list = append_not_null_pred_list;

  return true;

exit_on_fail_with_cleanup:
  if (append_not_null_pred_list != NULL)
    {
      parser_free_tree (parser, append_not_null_pred_list);
      append_not_null_pred_list = NULL;
    }
  /* fallthrough */

exit_on_fail:
  return false;
}

/*
 * qo_check_reduce_predicate_for_parent_spec () - Whether the non-join predicate on the parent is reducible.
 *
 *   return: bool
 *   parser(in): parser context
 *   query(in): query to check
 *   reduce_reference_info(in/out): Information needed to check
 *
 * Note: Checks if there is a predicate on the child equal to the non-join predicate of the parent.
 */
static bool
qo_check_reduce_predicate_for_parent_spec (PARSER_CONTEXT * parser, PT_NODE * query,
                       QO_REDUCE_REFERENCE_INFO * reduce_reference_info)
{
  PT_NODE *fk_spec;
  SM_CLASS_CONSTRAINT *pk_cons, *fk_cons;
  SM_ATTRIBUTE *pk_cons_attr, *fk_cons_attr;
  PT_NODE *join_pred_point_list;
  PT_NODE *parent_pred_point_list;
  PT_NODE *child_pred_point_list;
  PT_NODE *curr_pred, *next_pred;
  PT_NODE *curr_parent_pred_point, *curr_parent_pred;
  PT_NODE *curr_child_pred_point, *curr_child_pred;
  PT_NODE *parent_pred_attr, *parent_pred_const;
  PT_NODE *child_pred_attr, *child_pred_const;
  const char *parent_pred_attr_str, *parent_pred_const_str;
  const char *child_pred_attr_str, *child_pred_const_str;
  PT_NODE *arg1, *arg2;
  int i;

  assert (parser != NULL && query != NULL);
  assert (reduce_reference_info != NULL);
  assert (reduce_reference_info->pk_cons != NULL);
  assert (reduce_reference_info->fk_spec != NULL);
  assert (reduce_reference_info->fk_cons != NULL);
  assert (reduce_reference_info->join_pred_point_list != NULL);
  assert (reduce_reference_info->parent_pred_point_list != NULL);

  pk_cons = reduce_reference_info->pk_cons;
  fk_spec = reduce_reference_info->fk_spec;
  fk_cons = reduce_reference_info->fk_cons;
  join_pred_point_list = reduce_reference_info->join_pred_point_list;
  parent_pred_point_list = reduce_reference_info->parent_pred_point_list;
  child_pred_point_list = NULL;

  /* child_pred_point_list */
  for (curr_pred = query->info.query.q.select.where; curr_pred != NULL; curr_pred = curr_pred->next)
    {
      SPEC_CNT_INFO info;

      if (curr_pred->or_next != NULL)
    {
      continue;
    }

      memset (&info, 0, sizeof (SPEC_CNT_INFO));
      info.spec = fk_spec;

      next_pred = curr_pred->next;
      curr_pred->next = NULL;

      parser_walk_tree (parser, curr_pred, qo_get_name_cnt_by_spec, &info, NULL, NULL);

      curr_pred->next = next_pred;

      if (info.my_spec_cnt >= 1)
    {
      if (curr_pred->node_type == PT_EXPR && curr_pred->info.expr.op == PT_EQ)
        {
          arg1 = curr_pred->info.expr.arg1;
          arg2 = curr_pred->info.expr.arg2;

          if ((pt_is_attr (arg1) && qo_is_reduceable_const (arg2)) ||
          (pt_is_attr (arg2) && qo_is_reduceable_const (arg1)))
        {
          child_pred_point_list = parser_append_node (pt_point (parser, curr_pred), child_pred_point_list);
          continue;
        }
        }
    }
    }

  /* parent_pred_point_list */
  for (curr_parent_pred_point = parent_pred_point_list; curr_parent_pred_point != NULL;
       curr_parent_pred_point = curr_parent_pred_point->next)
    {
      curr_parent_pred = curr_parent_pred_point;
      CAST_POINTER_TO_NODE (curr_parent_pred);

      assert (curr_parent_pred->node_type == PT_EXPR);

      arg1 = curr_parent_pred->info.expr.arg1;
      arg2 = curr_parent_pred->info.expr.arg2;

      if (pt_is_attr (arg1))
    {
      parent_pred_attr = arg1;
      parent_pred_const = arg2;
    }
      else
    {
      assert (arg2->node_type == PT_NAME);
      parent_pred_attr = arg2;
      parent_pred_const = arg1;
    }

      parent_pred_attr_str = parent_pred_attr->info.name.original;

      fk_cons_attr = NULL;
      for (i = 0; pk_cons->attributes[i] != NULL && fk_cons->attributes[i] != NULL; i++)
    {
      pk_cons_attr = pk_cons->attributes[i];

      if (intl_identifier_casecmp (pk_cons_attr->header.name, parent_pred_attr_str) == 0)
        {
          fk_cons_attr = fk_cons->attributes[i];
          break;
        }
    }

      if (fk_cons_attr == NULL)
    {
      /* not found */
      goto exit_on_fail_with_cleanup;
    }

      /* child_pred_point_list */
      for (curr_child_pred_point = child_pred_point_list; curr_child_pred_point != NULL;
       curr_child_pred_point = curr_child_pred_point->next)
    {
      curr_child_pred = curr_child_pred_point;
      CAST_POINTER_TO_NODE (curr_child_pred);

      assert (curr_child_pred->node_type == PT_EXPR);

      arg1 = curr_child_pred->info.expr.arg1;
      arg2 = curr_child_pred->info.expr.arg2;

      if (pt_is_attr (arg1))
        {
          child_pred_attr = arg1;
          child_pred_const = arg2;
        }
      else
        {
          assert (arg2->node_type == PT_NAME);
          child_pred_attr = arg2;
          child_pred_const = arg1;
        }

      child_pred_attr_str = child_pred_attr->info.name.original;

      if (intl_identifier_casecmp (fk_cons_attr->header.name, child_pred_attr_str) == 0)
        {
          unsigned int save_custom;

          save_custom = parser->custom_print;   /* save */
          parser->custom_print |= PT_CONVERT_RANGE;

          parent_pred_const_str = parser_print_tree (parser, parent_pred_const);
          child_pred_const_str = parser_print_tree (parser, child_pred_const);

          parser->custom_print = save_custom;   /* restore */

          if (pt_str_compare (parent_pred_const_str, child_pred_const_str, CASE_INSENSITIVE) == 0)
        {
          break;
        }
        }
    }

      if (child_pred_point_list == NULL)
    {
      /* not found */
      goto exit_on_fail_with_cleanup;
    }
    }

  assert (curr_parent_pred_point == NULL);

  if (child_pred_point_list != NULL)
    {
      parser_free_tree (parser, child_pred_point_list);
      child_pred_point_list = NULL;
    }

  return true;

exit_on_fail_with_cleanup:
  if (child_pred_point_list != NULL)
    {
      parser_free_tree (parser, child_pred_point_list);
      child_pred_point_list = NULL;
    }
  /* fallthrough */

  return false;
}

/*
 * qo_reduce_predicate_for_parent_spec () - The join predicates for pk_spec in reduce_reference_info are removed.
 *   return: bool
 *   parser(in): parser context
 *   query(in): query to check
 *   reduce_reference_info(in/out): Information needed to check
 *
 * Note: Predicates in append_not_null_pred_list of reduce_reference_info are added without duplicates.
 */
static void
qo_reduce_predicate_for_parent_spec (PARSER_CONTEXT * parser, PT_NODE * query,
                     QO_REDUCE_REFERENCE_INFO * reduce_reference_info)
{
  PT_NODE *curr_pred_point = NULL, *prev_pred_point = NULL, *next_pred_point = NULL;
  PT_NODE *curr_pred = NULL, *prev_pred = NULL, *next_pred = NULL, *parent_pred = NULL;
  PT_NODE *curr_append_pred = NULL, *prev_append_pred = NULL, *next_append_pred = NULL;
  PT_NODE *curr_pred_arg = NULL, *curr_append_pred_arg = NULL;

  assert (parser != NULL && query != NULL);
  assert (reduce_reference_info->join_pred_point_list != NULL);

  prev_pred = NULL;
  curr_pred = query->info.query.q.select.where;
  while (curr_pred != NULL)
    {
      for (parent_pred = NULL, prev_pred_point = NULL, curr_pred_point = reduce_reference_info->join_pred_point_list;
       curr_pred_point != NULL; prev_pred_point = curr_pred_point, curr_pred_point = curr_pred_point->next)
    {
      parent_pred = curr_pred_point;
      CAST_POINTER_TO_NODE (parent_pred);

      if (curr_pred == parent_pred)
        {
          /* found */
          break;
        }
    }

      next_pred = curr_pred->next;

      if (curr_pred_point == NULL)
    {
      /* not found */
      prev_pred = curr_pred;
      curr_pred = next_pred;
      continue;
    }

      /* found */
      if (prev_pred != NULL)
    {
      prev_pred->next = next_pred;
    }
      else
    {
      query->info.query.q.select.where = next_pred;
    }
      parser_free_node (parser, curr_pred);
      curr_pred = next_pred;

      next_pred_point = curr_pred_point->next;

      if (prev_pred_point != NULL)
    {
      prev_pred_point->next = next_pred_point;
    }
      else
    {
      reduce_reference_info->join_pred_point_list = next_pred_point;
    }
      parser_free_node (parser, curr_pred_point);
      curr_pred_point = next_pred_point;
    }

  assert (reduce_reference_info->join_pred_point_list == NULL);

  prev_pred = NULL;
  curr_pred = query->info.query.q.select.where;
  while (curr_pred != NULL && reduce_reference_info->parent_pred_point_list != NULL)
    {
      for (parent_pred = NULL, prev_pred_point = NULL, curr_pred_point =
       reduce_reference_info->parent_pred_point_list; curr_pred_point != NULL;
       prev_pred_point = curr_pred_point, curr_pred_point = curr_pred_point->next)
    {
      parent_pred = curr_pred_point;
      CAST_POINTER_TO_NODE (parent_pred);

      if (curr_pred == parent_pred)
        {
          /* found */
          break;
        }
    }

      next_pred = curr_pred->next;

      if (curr_pred_point == NULL)
    {
      /* not found */
      prev_pred = curr_pred;
      curr_pred = next_pred;
      continue;
    }

      /* found */
      if (prev_pred != NULL)
    {
      prev_pred->next = next_pred;
    }
      else
    {
      query->info.query.q.select.where = next_pred;
    }
      parser_free_node (parser, curr_pred);
      curr_pred = next_pred;

      next_pred_point = curr_pred_point->next;

      if (prev_pred_point != NULL)
    {
      prev_pred_point->next = next_pred_point;
    }
      else
    {
      reduce_reference_info->parent_pred_point_list = next_pred_point;
    }
      parser_free_node (parser, curr_pred_point);
      curr_pred_point = next_pred_point;
    }

  assert (reduce_reference_info->parent_pred_point_list == NULL);

  prev_append_pred = NULL;
  curr_append_pred = reduce_reference_info->append_not_null_pred_list;
  while (curr_append_pred != NULL)
    {
      assert (PT_NODE_IS_EXPR (curr_append_pred));
      assert (PT_EXPR_OP (curr_append_pred) == PT_IS_NOT_NULL);
      assert (PT_NODE_IS_NAME (PT_EXPR_ARG1 (curr_append_pred)));

      next_append_pred = curr_append_pred->next;

      for (curr_pred = next_append_pred; curr_pred != NULL; curr_pred = curr_pred->next)
    {
      curr_pred_arg = PT_EXPR_ARG1 (curr_pred);
      curr_append_pred_arg = PT_EXPR_ARG1 (curr_append_pred);

      if (pt_name_equal (parser, curr_pred_arg, curr_append_pred_arg))
        {
          /* found */
          break;
        }
    }

      if (curr_pred == NULL)
    {
      /* not found */
      prev_append_pred = curr_append_pred;
      curr_append_pred = next_append_pred;
      continue;
    }

      /* found */
      if (prev_append_pred != NULL)
    {
      prev_append_pred->next = next_append_pred;
    }
      else
    {
      reduce_reference_info->append_not_null_pred_list = next_append_pred;
    }
      parser_free_node (parser, curr_append_pred);
      curr_append_pred = next_append_pred;
    }

  prev_append_pred = NULL;
  curr_append_pred = reduce_reference_info->append_not_null_pred_list;
  while (curr_append_pred != NULL)
    {
      for (curr_pred = query->info.query.q.select.where; curr_pred != NULL; curr_pred = curr_pred->next)
    {
      if (PT_NODE_IS_EXPR (curr_pred) && PT_EXPR_OP (curr_pred) == PT_IS_NOT_NULL
          && PT_NODE_IS_NAME (PT_EXPR_ARG1 (curr_pred)))
        {
          curr_pred_arg = PT_EXPR_ARG1 (curr_pred);
          curr_append_pred_arg = PT_EXPR_ARG1 (curr_append_pred);

          if (pt_name_equal (parser, curr_pred_arg, curr_append_pred_arg))
        {
          /* found */
          break;
        }
        }
    }

      next_append_pred = curr_append_pred->next;

      if (curr_pred == NULL)
    {
      /* not found */
      prev_append_pred = curr_append_pred;
      curr_append_pred = next_append_pred;
      continue;
    }

      /* found */
      if (prev_append_pred != NULL)
    {
      prev_append_pred->next = next_append_pred;
    }
      else
    {
      reduce_reference_info->append_not_null_pred_list = next_append_pred;
    }
      parser_free_node (parser, curr_append_pred);
      curr_append_pred = next_append_pred;
    }

  if (reduce_reference_info->append_not_null_pred_list != NULL)
    {
      query->info.query.q.select.where =
    parser_append_node (reduce_reference_info->append_not_null_pred_list, query->info.query.q.select.where);
      reduce_reference_info->append_not_null_pred_list = NULL;
    }

  return;
}

/*
 * qo_rewrite_outerjoin () - Rewrite outer join to inner join
 *   return: PT_NODE *
 *   parser(in):
 *   node(in): SELECT node
 *   arg(in):
 *   continue_walk(in):
 *
 * Note: do parser_walk_tree() pre function
 */
static PT_NODE *
qo_rewrite_outerjoin (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk)
{
  PT_NODE *spec, *expr, *ns, *save_next;
  SPEC_ID_INFO info, info_spec;
  RESET_LOCATION_INFO locate_info;
  bool rewrite_again, is_outer_joined;

  if (node->node_type != PT_SELECT)
    {
      return node;
    }

  if (node->info.query.q.select.connect_by)
    {
      /* don't rewrite if the query is hierarchical because conditions in 'where' must be applied after HQ evaluation;
       * HQ uses as input the result of joins */
      return node;
    }

  do
    {
      rewrite_again = false;
      /* traverse spec list */
      for (spec = node->info.query.q.select.from; spec; spec = spec->next)
    {
      /* check outer join spec. */
      is_outer_joined = mq_is_outer_join_spec (parser, spec);
      if (is_outer_joined)
        {
          info.id = info_spec.id = spec->info.spec.id;

          /* search where list */
          for (expr = node->info.query.q.select.where; expr; expr = expr->next)
        {
          if (expr->node_type == PT_EXPR && expr->info.expr.location == 0 && expr->info.expr.op != PT_IS_NULL
              && expr->or_next == NULL && expr->info.expr.op != PT_AND && expr->info.expr.op != PT_OR)
            {
              info_spec.appears = false;
              info.nullable = false;

              save_next = expr->next;
              expr->next = NULL;
              (void) parser_walk_tree (parser, expr, NULL, NULL, qo_check_nullable_expr_with_spec, &info);
              (void) parser_walk_tree (parser, expr, qo_get_name_by_spec_id, &info_spec, NULL, NULL);
              expr->next = save_next;

              /* have found a term which makes outer join to inner */
              /* there are predicate referenced by spec and all preds are not nullable */
              if (info_spec.appears && !info.nullable)
            {
              rewrite_again = true;
              if (spec->info.spec.join_type == PT_JOIN_LEFT_OUTER)
                {
                  spec->info.spec.join_type = PT_JOIN_INNER;

                  locate_info.start = spec->info.spec.location;
                  locate_info.end = locate_info.start;
                  (void) parser_walk_tree (parser, node->info.query.q.select.where, qo_reset_location,
                               &locate_info, NULL, NULL);
                }

              /* rewrite the following connected right outer join to inner join */
              for (ns = spec->next; /* traverse next spec */
                   ns && ns->info.spec.join_type != PT_JOIN_NONE; ns = ns->next)
                {
                  if (ns->info.spec.join_type == PT_JOIN_RIGHT_OUTER)
                {
                  ns->info.spec.join_type = PT_JOIN_INNER;
                  locate_info.start = ns->info.spec.location;
                  locate_info.end = locate_info.start;
                  (void) parser_walk_tree (parser, node->info.query.q.select.where, qo_reset_location,
                               &locate_info, NULL, NULL);
                }
                }
              break;
            }
            }
        }
        }

      if (spec->info.spec.derived_table && spec->info.spec.derived_table_type == PT_IS_SUBQUERY)
        {
          /* apply qo_rewrite_outerjoin() to derived table's subquery */
          (void) parser_walk_tree (parser, spec->info.spec.derived_table, qo_rewrite_outerjoin, NULL, NULL, NULL);
        }
    }
    }
  while (rewrite_again);

  *continue_walk = PT_LIST_WALK;

  return node;
}

/*
 * qo_reset_location () -
 *   return:
 *   parser(in):
 *   node(in):
 *   arg(in):
 *   continue_walk(in):
 */
static PT_NODE *
qo_reset_location (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk)
{
  RESET_LOCATION_INFO *infop = (RESET_LOCATION_INFO *) arg;

  if (node->node_type == PT_EXPR && node->info.expr.location >= infop->start && node->info.expr.location <= infop->end)
    {
      node->info.expr.location = 0;
    }

  if (node->node_type == PT_NAME && node->info.name.location >= infop->start && node->info.name.location <= infop->end)
    {
      node->info.name.location = 0;
    }

  if (node->node_type == PT_VALUE && node->info.value.location >= infop->start
      && node->info.value.location <= infop->end)
    {
      node->info.value.location = 0;
    }

  return node;
}

/*
 * qo_rewrite_innerjoin () - Rewrite explicit(ordered) inner join
 *            to implicit(unordered) inner join
 *   return: PT_NODE *
 *   parser(in):
 *   node(in): SELECT node
 *   arg(in):
 *   continue_walk(in):
 *
 * Note: If join order hint is set, skip and go ahead.
 *   do parser_walk_tree() pre function
 */
static PT_NODE *
qo_rewrite_innerjoin (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk)
{
  PT_NODE *spec, *spec2;
  RESET_LOCATION_INFO info; /* spec location reset info */

  if (node->node_type != PT_SELECT)
    {
      return node;
    }

  if (node->info.query.q.select.connect_by)
    {
      /* don't rewrite if the query is hierarchical because conditions in 'where' must be applied after HQ evaluation;
       * HQ uses as input the result of joins */
      return node;
    }

  if (node->info.query.q.select.hint & PT_HINT_ORDERED)
    {
      /* join hint: force join left-to-right. skip and go ahead. */
      return node;
    }

  info.start = 0;
  info.end = 0;
  info.found_outerjoin = false;

  /* traverse spec list to find disconnected spec list */
  for (info.start_spec = spec = node->info.query.q.select.from; spec; spec = spec->next)
    {

      switch (spec->info.spec.join_type)
    {
    case PT_JOIN_LEFT_OUTER:
    case PT_JOIN_RIGHT_OUTER:
      /* case PT_JOIN_FULL_OUTER: */
      info.found_outerjoin = true;
      break;
    default:
      break;
    }

      if (spec->info.spec.join_type == PT_JOIN_NONE && info.found_outerjoin == false && info.start < info.end)
    {
      /* rewrite explicit inner join to implicit inner join */
      for (spec2 = info.start_spec; spec2 != spec; spec2 = spec2->next)
        {
          if (spec2->info.spec.join_type == PT_JOIN_INNER)
        {
          spec2->info.spec.join_type = PT_JOIN_NONE;
        }
        }

      /* reset location of spec list */
      (void) parser_walk_tree (parser, node->info.query.q.select.where, qo_reset_location, &info, NULL, NULL);

      /* reset start spec, found_outerjoin */
      info.start = spec->info.spec.location;
      info.start_spec = spec;
      info.found_outerjoin = false;
    }

      info.end = spec->info.spec.location;

      if (spec->info.spec.derived_table && spec->info.spec.derived_table_type == PT_IS_SUBQUERY)
    {
      /* apply qo_rewrite_innerjoin() to derived table's subquery */
      (void) parser_walk_tree (parser, spec->info.spec.derived_table, qo_rewrite_innerjoin, NULL, NULL, NULL);
    }
    }

  if (info.found_outerjoin == false && info.start < info.end)
    {
      /* rewrite explicit inner join to implicit inner join */
      for (spec2 = info.start_spec; spec2; spec2 = spec2->next)
    {
      if (spec2->info.spec.join_type == PT_JOIN_INNER)
        {
          spec2->info.spec.join_type = PT_JOIN_NONE;
        }
    }

      /* reset location of spec list */
      (void) parser_walk_tree (parser, node->info.query.q.select.where, qo_reset_location, &info, NULL, NULL);
    }

  *continue_walk = PT_LIST_WALK;

  return node;
}

/*
 * qo_check_generate_single_tbl_connect_by () - checks a SELECT ... CONNECT BY
 *                                              query for single-table
 *                                              optimizations
 *   return: whether single-table optimization can be performed
 *   parser(in): parser environment
 *   node(in): SELECT ... CONNECT BY query
 * Note: The single-table optimizations (potentially using indexes for table
 *       access in START WITH and CONNECT BY predicates) can be performed if
 *       the query does not involve joins or partitioned tables.
 */
bool
qo_check_generate_single_tbl_connect_by (PARSER_CONTEXT * parser, PT_NODE * node)
{
  int level = 0;
  PT_NODE *name = NULL;
  PT_NODE *spec = NULL;
  PT_NODE *select = NULL;

  assert (node->node_type == PT_SELECT && node->info.query.q.select.connect_by != NULL);

  spec = node->info.query.q.select.from;

  if (node->info.query.q.select.where || spec->next)
    {
      /* joins */
      return false;
    }

  select = node->info.query.q.select.list;

  while (select != NULL)
    {
      if (select->node_type == PT_METHOD_CALL)
    {
      /* method call can be rewritten as subquery later. */
      return false;
    }
      select = select->next;
    }

  qo_get_optimization_param (&level, QO_PARAM_LEVEL);
  if (!OPTIMIZATION_ENABLED (level))
    {
      return false;
    }

  assert (spec->next == NULL);
  if (spec->node_type != PT_SPEC)
    {
      assert (false);
      return false;
    }

  if (spec->info.spec.only_all != PT_ONLY)
    {
      /* class hierarchy */
      return false;
    }

  name = spec->info.spec.entity_name;
  if (name == NULL)
    {
      return false;
    }
  assert (name->node_type == PT_NAME);
  if (name == NULL || name->node_type != PT_NAME)
    {
      assert (false);
      return false;
    }

  if (sm_is_partitioned_class (name->info.name.db_object) > 0)
    {
      return false;
    }
  return true;
}

static PT_NODE *
qo_rewrite_nonnull_count (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk)
{
  PT_NODE *from = (PT_NODE *) arg;
  PT_NODE *count_arg;
  PT_NAME_INFO *name_info;
  MOP cls;
  SM_CLASS_CONSTRAINT *consp, *cons_iter;
  SM_ATTRIBUTE *attrp;
  PT_NODE *spec_node;
  int i;
  const char *class_name;
  bool is_rewrite_to_count_star_available = false;
  if (node->node_type == PT_FUNCTION && node->info.function.function_type == PT_COUNT
      && node->info.function.all_or_distinct == PT_ALL)
    {
      count_arg = node->info.function.arg_list;
      assert (count_arg->next == NULL);
      if (count_arg->node_type == PT_NAME)
    {
      name_info = &count_arg->info.name;
      spec_node = pt_find_entity (parser, from, name_info->spec_id);
      if (spec_node == NULL || spec_node->info.spec.entity_name == NULL
          || spec_node->info.spec.join_type == PT_JOIN_LEFT_OUTER)
        {
          return node;
        }
      class_name = spec_node->info.spec.entity_name->info.name.original;
      if (class_name == NULL)
        {
          return node;
        }
      cls = sm_find_class (class_name);
      if (cls == NULL)
        {
          return node;
        }
      consp = sm_class_constraints (cls);
      if (consp == NULL)
        {
          return node;
        }
      for (cons_iter = consp; cons_iter != NULL && !is_rewrite_to_count_star_available; cons_iter = cons_iter->next)
        {
          if (SM_IS_CONSTRAINT_NOT_NULL_FAMILY (cons_iter->type))
        {
          for (i = 0; cons_iter->attributes[i]; i++)
            {
              attrp = cons_iter->attributes[i];
              if (intl_identifier_casecmp (count_arg->info.name.original, attrp->header.name) == 0)
            {
              is_rewrite_to_count_star_available = true;
              break;
            }
            }
        }
        }
      for (cons_iter = consp; cons_iter != NULL; cons_iter = cons_iter->next)
        {
          if (cons_iter->filter_predicate)
        {
          is_rewrite_to_count_star_available = false;
        }
        }
      if (is_rewrite_to_count_star_available)
        {
          node->info.function.function_type = PT_COUNT_STAR;
          parser_free_tree (parser, node->info.function.arg_list);
          node->info.function.arg_list = NULL;
        }
    }
    }
  return node;
}

void
qo_rewrite_nonnull_count_select_list (PARSER_CONTEXT * parser, PT_NODE * select)
{
  bool is_rewrite_to_count_star_available = false;
  PT_NODE *select_list = select->info.query.q.select.list;
  PT_NODE *from = select->info.query.q.select.from;
  PT_NODE *spec;
  SM_ATTRIBUTE *attrp;
  int i;
  int continue_walk = PT_LEAF_WALK;
  for (spec = from; spec; spec = spec->next)
    {
      if (spec->info.spec.join_type == PT_JOIN_RIGHT_OUTER || spec->info.spec.join_type == PT_JOIN_FULL_OUTER
      || spec->info.spec.join_type == PT_JOIN_CROSS)
    {
      return;
    }
    }
  parser_walk_tree (parser, select_list, qo_rewrite_nonnull_count, (void *) from, NULL, NULL);
}