Skip to content

File px_scan_checker.cpp

File List > cubrid > src > query > parallel > px_scan > px_scan_checker.cpp

Go to the documentation of this file

/*
 *
 * 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.
 *
 */

/*
 * px_scan_checker.cpp - module that checks whether parallel scan is possible.
 */

#include "px_scan_checker.hpp"

#include "dbtype_def.h"
#include "error_manager.h"
#include "regu_var.hpp"
#include "schema_manager.h"
#include "storage_common.h"
#include "work_space.h"
#include "xasl_predicate.hpp"
#include "xasl.h"
#include "xasl_aggregate.hpp"
#include <unordered_map>
#include <unordered_set>

// XXX: SHOULD BE THE LAST INCLUDE HEADER
#include "memory_wrapper.hpp"

namespace parallel_scan
{
  /* modes: list_merge (fast, workers merge partial lists) | row_by_row (fallback) | buildvalue_opt (agg-only selects; blocked by ROWNUM in row_by_row). */

  using possible_flags = uint32_t;
  const possible_flags CANNOT_PARALLEL_SCAN = 0x1 << 0;
  const possible_flags CANNOT_LIST_MERGE = 0x1 << 1;
  const possible_flags CANNOT_BUILDVALUE_OPT = 0x1 << 2;

  static bool
  is_buildvalue_opt_supported_function (FUNC_CODE function)
  {
    switch (function)
      {
      case PT_COUNT_STAR:
      case PT_COUNT:
      case PT_MIN:
      case PT_MAX:
      case PT_SUM:
      case PT_AVG:
      case PT_STDDEV:
      case PT_STDDEV_POP:
      case PT_STDDEV_SAMP:
      case PT_VARIANCE:
      case PT_VAR_POP:
      case PT_VAR_SAMP:
    return true;
      default:
    return false;
      }
  }

  /* thread-local cache: memoizes check results and breaks circular XASL refs. */
  thread_local std::unordered_map<XASL_NODE *, possible_flags> xasl_check_cache;

  /* cycle guard for the process_* recursion. */
  thread_local std::unordered_set<XASL_NODE *> xasl_processing_set;

  static void set_flag (possible_flags &flags, possible_flags flag)
  {
    flags |= flag;
  }
  static bool is_flag_set (possible_flags flags, possible_flags flag)
  {
    return (flags & flag) != 0;
  }

  using rv_list_node = struct regu_variable_list_node;

  /* prototypes */
  template <typename T, bool is_outptr_list>
  possible_flags check (T *arg);

  template <bool is_outptr_list>
  possible_flags check (REGU_VARIABLE *arg);
  template <bool is_outptr_list>
  possible_flags check (PRED_EXPR *arg);
  template <bool is_outptr_list>
  possible_flags check (rv_list_node *arg);
  template <bool is_outptr_list>
  possible_flags check (ARITH_TYPE *arg);
  template <bool is_outptr_list>
  possible_flags check (PRED *arg);
  template <bool is_outptr_list>
  possible_flags check (EVAL_TERM *arg);
  template <bool is_outptr_list>
  possible_flags check (COMP_EVAL_TERM *arg);
  template <bool is_outptr_list>
  possible_flags check (ALSM_EVAL_TERM *arg);
  template <bool is_outptr_list>
  possible_flags check (LIKE_EVAL_TERM *arg);
  template <bool is_outptr_list>
  possible_flags check (RLIKE_EVAL_TERM *arg);
  template <bool is_outptr_list>
  possible_flags check (ACCESS_SPEC_TYPE *arg);
  template <bool is_outptr_list>
  possible_flags check (XASL_NODE *arg);
  template <bool is_outptr_list>
  possible_flags sibling_check (XASL_NODE *sibling);
  template <bool is_outptr_list>
  possible_flags sibling_check (ACCESS_SPEC_TYPE *arg);

  void process_xasl_node_recursive (XASL_NODE *arg);
  void process_xasl_node_recursive_force_cannot_parallel (XASL_NODE *arg);
  void block_parallel_index_and_temp_in_subtree (XASL_NODE *arg);

  template <bool is_outptr_list>
  possible_flags check (REGU_VARIABLE *arg)
  {
    possible_flags result = 0, temp = 0;
    if (!arg)
      {
    return result;
      }

    if (arg->xasl)
      {
    temp = sibling_check<is_outptr_list> (arg->xasl);
    if (is_flag_set (temp, CANNOT_PARALLEL_SCAN))
      {
        set_flag (result, CANNOT_PARALLEL_SCAN);
      }
      }

    switch (arg->type)
      {
      case TYPE_ATTR_ID:        /* fetch object attribute value */
      case TYPE_SHARED_ATTR_ID:
      case TYPE_CLASS_ATTR_ID:
    if (arg->value.attr_descr.type == DB_TYPE_OBJECT || arg->value.attr_descr.type == DB_TYPE_OID)
      {
        set_flag (result, CANNOT_PARALLEL_SCAN);
      }
    break;
      case TYPE_CONSTANT:
      case TYPE_OID:
      case TYPE_DBVAL:
      case TYPE_POSITION:
      case TYPE_POS_VALUE:
      case TYPE_LIST_ID:
    break;
      case TYPE_ORDERBY_NUM:
      case TYPE_CLASSOID:
      case TYPE_REGUVAL_LIST:
    set_flag (result, CANNOT_PARALLEL_SCAN);
    break;
      case TYPE_INARITH:
      case TYPE_OUTARITH:
    temp = check<is_outptr_list> (arg->value.arithptr);
    result |= temp;
    break;
      case TYPE_SP:
    result |= check<is_outptr_list> (arg->value.sp_ptr->args);
    /* SP not executable in child threads. */
    if (is_outptr_list)
      {
        set_flag (result, CANNOT_LIST_MERGE);
      }
    else
      {
        set_flag (result, CANNOT_PARALLEL_SCAN);
      }
    break;
      case TYPE_FUNC:
    temp = check<is_outptr_list> (arg->value.funcp->operand);
    result |= temp;
    break;
      case TYPE_REGU_VAR_LIST:
    temp = check<is_outptr_list> (arg->value.regu_var_list);
    if (is_flag_set (temp, CANNOT_PARALLEL_SCAN))
      {
        set_flag (result, CANNOT_PARALLEL_SCAN);
      }
    else
      {
        set_flag (result, CANNOT_LIST_MERGE);
      }
    break;
      default:
    set_flag (result, CANNOT_PARALLEL_SCAN);
    break;
      }

    return result;
  }

  template <bool is_outptr_list>
  possible_flags check (PRED_EXPR *arg)
  {
    if (!arg)
      {
    return 0;
      }
    switch (arg->type)
      {
      case T_PRED:
    return check<is_outptr_list> (&arg->pe.m_pred);
      case T_EVAL_TERM:
    return check<is_outptr_list> (&arg->pe.m_eval_term);
      case T_NOT_TERM:
    return check<is_outptr_list> (arg->pe.m_not_term);
      default:
    return 0;
      }
  }

  template <bool is_outptr_list>
  possible_flags check (rv_list_node *arg)
  {
    if (!arg)
      {
    return 0;
      }
    possible_flags result = 0;
    rv_list_node *curr = arg;
    while (curr)
      {
    result |= check<is_outptr_list> (&curr->value);
    curr = curr->next;
      }
    return result;
  }

  template <bool is_outptr_list>
  possible_flags check (ARITH_TYPE *arg)
  {
    if (!arg)
      {
    return 0;
      }
    possible_flags result = 0;
    result |= check<is_outptr_list> (arg->leftptr);
    result |= check<is_outptr_list> (arg->rightptr);
    result |= check<is_outptr_list> (arg->thirdptr);
    result |= check<is_outptr_list> (arg->pred);
    if (arg->opcode == T_TRACE_STATS || arg->opcode == T_EVALUATE_VARIABLE || arg->opcode == T_DEFINE_VARIABLE
    || arg->opcode == T_CURRENT_VALUE || arg->opcode == T_NEXT_VALUE)
      {
    set_flag (result, CANNOT_PARALLEL_SCAN);
      }
    return result;
  }

  template <bool is_outptr_list>
  possible_flags check (PRED *arg)
  {
    if (!arg)
      {
    return 0;
      }
    possible_flags result = 0;
    result |= check<is_outptr_list> (arg->lhs);
    result |= check<is_outptr_list> (arg->rhs);
    return result;
  }

  template <bool is_outptr_list>
  possible_flags check (EVAL_TERM *arg)
  {
    if (!arg)
      {
    return 0;
      }
    switch (arg->et_type)
      {
      case T_COMP_EVAL_TERM:
    return check<is_outptr_list> (&arg->et.et_comp);
      case T_ALSM_EVAL_TERM:
    return check<is_outptr_list> (&arg->et.et_alsm);
      case T_LIKE_EVAL_TERM:
    return check<is_outptr_list> (&arg->et.et_like);
      case T_RLIKE_EVAL_TERM:
    return check<is_outptr_list> (&arg->et.et_rlike);
      default:
    return 0;
      }
  }

  template <bool is_outptr_list>
  possible_flags check (COMP_EVAL_TERM *arg)
  {
    if (!arg)
      {
    return 0;
      }
    return check<is_outptr_list> (arg->lhs) | check<is_outptr_list> (arg->rhs);
  }

  template <bool is_outptr_list>
  possible_flags check (ALSM_EVAL_TERM *arg)
  {
    if (!arg)
      {
    return 0;
      }
    return check<is_outptr_list> (arg->elem) | check<is_outptr_list> (arg->elemset);
  }

  template <bool is_outptr_list>
  possible_flags check (LIKE_EVAL_TERM *arg)
  {
    if (!arg)
      {
    return 0;
      }
    return check<is_outptr_list> (arg->src) | check<is_outptr_list> (arg->pattern) | check<is_outptr_list> (arg->esc_char);
  }

  template <bool is_outptr_list>
  possible_flags check (RLIKE_EVAL_TERM *arg)
  {
    if (!arg)
      {
    return 0;
      }
    return check<is_outptr_list> (arg->src) | check<is_outptr_list> (arg->pattern) | check<is_outptr_list>
       (arg->case_sensitive);
  }

  /* filtered index → serial: bug-prone + low usage, excluded as a constraint. function indexes are plain B-tree keys, not blocked. */
  static bool
  is_filtered_index (const INDX_INFO *indexptr)
  {
    if (indexptr == NULL)
      {
    return false;
      }
    if (OID_ISNULL (&indexptr->class_oid))
      {
    return false;
      }
    MOP class_mop = ws_mop (&indexptr->class_oid, NULL);
    if (class_mop == NULL)
      {
    return false;
      }
    SM_CLASS_CONSTRAINT *cons = sm_class_constraints (class_mop);
    for (; cons != NULL; cons = cons->next)
      {
    if (BTID_IS_EQUAL (&cons->index_btid, &indexptr->btid))
      {
        if (cons->filter_predicate != NULL)
          {
        return true;
          }
        break;
      }
      }
    return false;
  }

  template <>
  possible_flags check<false> (ACCESS_SPEC_TYPE *arg)
  {
    possible_flags result = 0;
    if (!arg)
      {
    return 0;
      }
    if (arg->type == TARGET_CLASS)
      {
    if (arg->access == ACCESS_METHOD_SEQUENTIAL)
      {
      }
    else if (arg->access == ACCESS_METHOD_INDEX)
      {
        if (ACCESS_SPEC_IS_FLAGED (arg, ACCESS_SPEC_FLAG_ONLY_MIN_MAX_SCAN))
          {
        /* min/max agg scan emits no rows. */
        set_flag (result, CANNOT_PARALLEL_SCAN);
        return result;
          }
        if (arg->indexptr != NULL)
          {
        /* ISS/ILS dynamically rewrites curr_keyno/key-ranges; conflicts with leaf-page cursor. */
        if (arg->indexptr->use_iss || arg->indexptr->ils_prefix_len > 0)
          {
            set_flag (result, CANNOT_PARALLEL_SCAN);
          }

        /* keylimit: global cap incompatible with per-worker page split. */
        if (arg->indexptr->key_info.is_user_given_keylimit)
          {
            set_flag (result, CANNOT_PARALLEL_SCAN);
          }

        /* orderby/groupby skip+desc need globally ordered traversal. */
        if (arg->indexptr->orderby_skip || arg->indexptr->groupby_skip
            || arg->indexptr->orderby_desc || arg->indexptr->groupby_desc)
          {
            set_flag (result, CANNOT_PARALLEL_SCAN);
          }

        /* filtered index: bug-prone + low usage, excluded. */
        if (is_filtered_index (arg->indexptr))
          {
            set_flag (result, CANNOT_PARALLEL_SCAN);
          }
          }
      }
    else
      {
        set_flag (result, CANNOT_PARALLEL_SCAN);
        return result;
      }
      }
    else if (arg->type == TARGET_LIST)
      {
      }
    else
      {
    set_flag (result, CANNOT_PARALLEL_SCAN);
    return result;
      }
    if (arg->next)
      {
    set_flag (result, CANNOT_PARALLEL_SCAN);
    return result;
      }
    if (arg->type == TARGET_CLASS)
      {
    result |= check<false> (arg->s.cls_node.cls_regu_list_pred);
    result |= check<false> (arg->s.cls_node.cls_regu_list_rest);
    result |= check<false> (arg->where_pred);
    if (arg->access == ACCESS_METHOD_INDEX)
      {
        result |= check<false> (arg->s.cls_node.cls_regu_list_key);
        result |= check<false> (arg->where_key);
        result |= check<false> (arg->s.cls_node.cls_regu_list_range);
        result |= check<false> (arg->where_range);
      }
    if (!arg->s.cls_node.cls_regu_list_pred && !arg->s.cls_node.cls_regu_list_rest)
      {
        set_flag (result, CANNOT_LIST_MERGE);
      }
      }
    else if (arg->type == TARGET_LIST)
      {
    result |= check<false> (arg->s.list_node.list_regu_list_pred);
    result |= check<false> (arg->s.list_node.list_regu_list_rest);
    result |= check<false> (arg->where_pred);
    if (!arg->s.list_node.list_regu_list_pred && !arg->s.list_node.list_regu_list_rest)
      {
        set_flag (result, CANNOT_LIST_MERGE);
      }
      }
    return result;
  }

  template <bool is_outptr_list>
  possible_flags sibling_check (ACCESS_SPEC_TYPE *arg)
  {
    possible_flags result = 0;
    if (!arg)
      {
    return 0;
      }
    if (arg->next)
      {
    set_flag (result, CANNOT_PARALLEL_SCAN);
    return result;
      }
    if (arg->type != TARGET_CLASS && arg->type != TARGET_LIST)
      {
    set_flag (result, CANNOT_LIST_MERGE);
      }
    else
      {
    result |= check<false> (arg->s.cls_node.cls_regu_list_pred);
    result |= check<false> (arg->s.cls_node.cls_regu_list_rest);
    result |= check<false> (arg->where_pred);
      }
    return result;
  }

  template <bool is_outptr_list>
  possible_flags sibling_check (XASL_NODE *sibling)
  {
    if (!sibling)
      {
    return 0;
      }

    possible_flags result = 0, temp = 0;

    if (sibling->selected_upd_list || sibling->scan_op_type != S_SELECT || sibling->upd_del_class_cnt > 0
    || XASL_IS_FLAGED (sibling, XASL_MULTI_UPDATE_AGG))
      {
    set_flag (result, CANNOT_PARALLEL_SCAN);
      }

    for (XASL_NODE *xaslp = sibling->aptr_list; xaslp; xaslp = xaslp->next)
      {
    result |= check<is_outptr_list> (xaslp);
      }

    if (sibling->bptr_list || sibling->fptr_list)
      {
    set_flag (result, CANNOT_PARALLEL_SCAN);
      }

    for (XASL_NODE *xaslp = sibling->dptr_list; xaslp; xaslp = xaslp->next)
      {
    temp = sibling_check<false> (xaslp);
    if (is_flag_set (temp, CANNOT_PARALLEL_SCAN))
      {
        set_flag (result, CANNOT_PARALLEL_SCAN);
      }
      }

    if (sibling->connect_by_ptr)
      {
    set_flag (result, CANNOT_LIST_MERGE);
      }

    if (sibling->if_pred)
      {
    temp = check<is_outptr_list> (sibling->if_pred);
    if (is_flag_set (temp, CANNOT_PARALLEL_SCAN))
      {
        set_flag (result, CANNOT_PARALLEL_SCAN);
      }
      }

    if (sibling->instnum_pred || sibling->instnum_val)
      {
    set_flag (result, CANNOT_LIST_MERGE);
      }

    for (ACCESS_SPEC_TYPE *specp = sibling->spec_list; specp; specp = specp->next)
      {
    result |= sibling_check<is_outptr_list> (specp);
      }

    return result;
  }

  template <bool is_outptr_list>
  possible_flags check (XASL_NODE *arg)
  {
    if (!arg)
      {
    return 0;
      }

    auto it = xasl_check_cache.find (arg);
    if (it != xasl_check_cache.end ())
      {
    return it->second;
      }

    /* mark visited (sentinel 0) before recursing — breaks XASL ref cycles. */
    xasl_check_cache[arg] = 0;

    possible_flags result = 0, temp = 0;
    bool buildvalue_opt = false;
    switch (arg->type)
      {
      case BUILDLIST_PROC:
    break;
      case BUILDVALUE_PROC:
    if (arg->proc.buildvalue.agg_list)
      {
        set_flag (result, CANNOT_LIST_MERGE);
        buildvalue_opt = true;
        AGGREGATE_TYPE *agg_it = arg->proc.buildvalue.agg_list;
        int agg_cnt = 0;
        temp = 0;
        for (; agg_it; agg_it = agg_it->next)
          {
        agg_cnt++;
        if (!is_buildvalue_opt_supported_function (agg_it->function))
          {
            buildvalue_opt = false;
            break;
          }
        temp |= check<false> (agg_it->operands);
        if (is_flag_set (temp, CANNOT_PARALLEL_SCAN))
          {
            buildvalue_opt = false;
            break;
          }
          }
        if (agg_cnt != arg->outptr_list->valptr_cnt)
          {
        buildvalue_opt = false;
          }
      }
    break;
      case CTE_PROC:
    if (arg->proc.cte.recursive_part)
      {
        set_flag (result, CANNOT_PARALLEL_SCAN);
      }
    break;
      case MERGE_PROC:
    set_flag (result, CANNOT_PARALLEL_SCAN);
    break;
      case HASHJOIN_PROC:
      case UNION_PROC:
      case DIFFERENCE_PROC:
      case INTERSECTION_PROC:
      case INSERT_PROC:
    break;
      case MERGELIST_PROC:
    set_flag (result, CANNOT_PARALLEL_SCAN);
    break;
      case OBJFETCH_PROC:
      case UPDATE_PROC:
      case DELETE_PROC:
      case CONNECTBY_PROC:
      case DO_PROC:
      case BUILD_SCHEMA_PROC:
      case SCAN_PROC:
      default:
    set_flag (result, CANNOT_PARALLEL_SCAN);
    break;
      }

    if (arg->selected_upd_list || arg->scan_op_type != S_SELECT || arg->upd_del_class_cnt > 0
    || XASL_IS_FLAGED (arg, XASL_MULTI_UPDATE_AGG))
      {
    set_flag (result, CANNOT_PARALLEL_SCAN);
      }
    for (XASL_NODE *xaslp = arg->aptr_list; xaslp; xaslp = xaslp->next)
      {
    if (XASL_IS_FLAGED (xaslp, XASL_LINK_TO_REGU_VARIABLE))
      {
        temp = sibling_check<false> (xaslp);
        if (is_flag_set (temp, CANNOT_PARALLEL_SCAN))
          {
        set_flag (result, CANNOT_PARALLEL_SCAN);
          }
      }
    else
      {
        /* this xasl not belong to current arg */
      }
      }

    if (arg->bptr_list || arg->fptr_list || arg->connect_by_ptr)
      {
    set_flag (result, CANNOT_PARALLEL_SCAN);
      }

    std::unordered_set<XASL_NODE *> dptrs;
    for (XASL_NODE *xaslp1 = arg; xaslp1; xaslp1 = xaslp1->scan_ptr)
      {
    for (XASL_NODE *xaslp2 = xaslp1->dptr_list; xaslp2; xaslp2 = xaslp2->next)
      {
        dptrs.insert (xaslp2);
      }
      }

    for (XASL_NODE *xaslp : dptrs)
      {
    temp = sibling_check<false> (xaslp);
    if (is_flag_set (temp, CANNOT_PARALLEL_SCAN))
      {
        set_flag (result, CANNOT_PARALLEL_SCAN);
      }
      }

    if (dptrs.size() > 0)
      {
    std::unordered_set<XASL_NODE *> dptrs2 (dptrs);
    for (XASL_NODE *xaslp : dptrs2)
      {
        if (XASL_IS_FLAGED (xaslp, XASL_LINK_TO_REGU_VARIABLE))
          {
        dptrs.erase (xaslp);
          }
      }
    if (dptrs.size() > 0)
      {
        set_flag (result, CANNOT_PARALLEL_SCAN);
      }
      }

    if (arg->scan_ptr)
      {
    for (XASL_NODE *xaslp = arg->scan_ptr; xaslp; xaslp = xaslp->scan_ptr)
      {
        result |= sibling_check<is_outptr_list> (xaslp);
      }
      }

    if (arg->connect_by_ptr)
      {
    set_flag (result, CANNOT_LIST_MERGE);
    buildvalue_opt = false;
      }

    if (arg->if_pred)
      {
    temp = check<is_outptr_list> (arg->if_pred);
    if (is_flag_set (temp, CANNOT_PARALLEL_SCAN))
      {
        set_flag (result, CANNOT_PARALLEL_SCAN);
      }
      }

    if (arg->instnum_pred || arg->instnum_val)
      {
    set_flag (result, CANNOT_LIST_MERGE);
    buildvalue_opt = false;
      }

    if (arg->outptr_list)
      {
    result |= check<true> (arg->outptr_list->valptrp);
      }

    for (ACCESS_SPEC_TYPE *specp = arg->spec_list; specp; specp = specp->next)
      {
    result |= check<false> (specp);
      }
    for (ACCESS_SPEC_TYPE *specp = arg->merge_spec; specp; specp = specp->next)
      {
    result |= check<false> (specp);
      }

    if (!buildvalue_opt)
      {
    set_flag (result, CANNOT_BUILDVALUE_OPT);
      }

    /* Update cache with computed result */
    xasl_check_cache[arg] = result;

    return result;
  }

  void process_xasl_node_recursive (XASL_NODE *arg)
  {
    if (!arg)
      {
    return;
      }

    if (xasl_processing_set.find (arg) != xasl_processing_set.end ())
      {
    return;
      }
    xasl_processing_set.insert (arg);

    possible_flags result = 0;
    switch (arg->type)
      {
      case CTE_PROC:
    if (arg->proc.cte.recursive_part)
      {
        process_xasl_node_recursive_force_cannot_parallel (arg->proc.cte.recursive_part);
        set_flag (result, CANNOT_PARALLEL_SCAN);
      }
    if (arg->proc.cte.non_recursive_part)
      {
        process_xasl_node_recursive (arg->proc.cte.non_recursive_part);
      }
    break;
      case MERGE_PROC:
    if (arg->proc.merge.insert_xasl)
      {
        process_xasl_node_recursive_force_cannot_parallel (arg->proc.merge.insert_xasl);
      }
    if (arg->proc.merge.update_xasl)
      {
        process_xasl_node_recursive_force_cannot_parallel (arg->proc.merge.update_xasl);
      }
    set_flag (result, CANNOT_PARALLEL_SCAN);
    break;
      case MERGELIST_PROC:
    for (ACCESS_SPEC_TYPE *specp = arg->proc.mergelist.outer_spec_list; specp; specp = specp->next)
      {
        ACCESS_SPEC_SET_FLAG (specp, ACCESS_SPEC_FLAG_NO_PARALLEL_SCAN);
      }
    for (ACCESS_SPEC_TYPE *specp = arg->proc.mergelist.inner_spec_list; specp; specp = specp->next)
      {
        ACCESS_SPEC_SET_FLAG (specp, ACCESS_SPEC_FLAG_NO_PARALLEL_SCAN);
      }
    for (XASL_NODE *xaslp = arg->aptr_list; xaslp; xaslp = xaslp->next)
      {
        block_parallel_index_and_temp_in_subtree (xaslp);
      }
    break;
      case BUILDLIST_PROC:
      case BUILDVALUE_PROC:
      case HASHJOIN_PROC:
      case UNION_PROC:
      case DIFFERENCE_PROC:
      case INTERSECTION_PROC:
      case INSERT_PROC:
    break;
      case OBJFETCH_PROC:
      case UPDATE_PROC:
      case DELETE_PROC:
      case CONNECTBY_PROC:
      case DO_PROC:
      case BUILD_SCHEMA_PROC:
      case SCAN_PROC:
      default:
    process_xasl_node_recursive_force_cannot_parallel (arg);
    set_flag (result, CANNOT_PARALLEL_SCAN);
    break;
      }

    for (XASL_NODE *xaslp = arg->aptr_list; xaslp; xaslp = xaslp->next)
      {
    if (XASL_IS_FLAGED (xaslp, XASL_LINK_TO_REGU_VARIABLE))
      {
        process_xasl_node_recursive_force_cannot_parallel (xaslp);
      }
    else
      {
        process_xasl_node_recursive (xaslp);
      }
      }
    for (XASL_NODE *xaslp = arg->bptr_list; xaslp; xaslp = xaslp->next)
      {
    process_xasl_node_recursive_force_cannot_parallel (xaslp);
      }
    for (XASL_NODE *xaslp = arg->dptr_list; xaslp; xaslp = xaslp->next)
      {
    process_xasl_node_recursive_force_cannot_parallel (xaslp);
      }
    for (XASL_NODE *xaslp = arg->fptr_list; xaslp; xaslp = xaslp->next)
      {
    process_xasl_node_recursive_force_cannot_parallel (xaslp);
      }
    for (XASL_NODE *xaslp = arg->scan_ptr; xaslp; xaslp = xaslp->scan_ptr)
      {
    process_xasl_node_recursive_force_cannot_parallel (xaslp);
      }
    for (XASL_NODE *xaslp = arg->connect_by_ptr; xaslp; xaslp = xaslp->next)
      {
    process_xasl_node_recursive_force_cannot_parallel (xaslp);
      }

    result |= check<false> (arg);

    const bool block_index_spec =
        (arg->instnum_pred || arg->instnum_val)
        || XASL_IS_FLAGED (arg, XASL_ANALYTIC_SKIP_SORT)
        || XASL_IS_FLAGED (arg, XASL_ANALYTIC_USES_LIMIT_OPT);

    const bool block_all_specs = XASL_IS_FLAGED (arg, XASL_SKIP_ORDERBY_LIST);

    if (is_flag_set (result, CANNOT_PARALLEL_SCAN) || block_all_specs)
      {
    for (ACCESS_SPEC_TYPE *specp = arg->spec_list; specp; specp = specp->next)
      {
        ACCESS_SPEC_SET_FLAG (specp, ACCESS_SPEC_FLAG_NO_PARALLEL_SCAN);
      }
      }
    else
      {
    if (block_index_spec)
      {
        for (ACCESS_SPEC_TYPE *specp = arg->spec_list; specp; specp = specp->next)
          {
        if (specp->type == TARGET_CLASS && specp->access == ACCESS_METHOD_INDEX)
          {
            ACCESS_SPEC_SET_FLAG (specp, ACCESS_SPEC_FLAG_NO_PARALLEL_SCAN);
          }
          }
      }

    if (!is_flag_set (result, CANNOT_BUILDVALUE_OPT))
      {
        for (ACCESS_SPEC_TYPE *specp = arg->spec_list; specp; specp = specp->next)
          {
        ACCESS_SPEC_SET_FLAG (specp, ACCESS_SPEC_FLAG_BUILDVALUE_OPT);
          }
      }
    else
      {
        if (!is_flag_set (result, CANNOT_LIST_MERGE))
          {
        for (ACCESS_SPEC_TYPE *specp = arg->spec_list; specp; specp = specp->next)
          {
            ACCESS_SPEC_SET_FLAG (specp, ACCESS_SPEC_FLAG_MERGEABLE_LIST);
          }
          }
        else
          {
        /* list merge blocked → row-by-row fallback. */
        for (ACCESS_SPEC_TYPE *specp = arg->spec_list; specp; specp = specp->next)
          {
            ACCESS_SPEC_UNSET_FLAG (specp, ACCESS_SPEC_FLAG_MERGEABLE_LIST);
            if (specp->type == TARGET_LIST
            || (specp->type == TARGET_CLASS && specp->access == ACCESS_METHOD_INDEX))
              {
            ACCESS_SPEC_SET_FLAG (specp, ACCESS_SPEC_FLAG_NO_PARALLEL_SCAN);
              }
          }
          }
      }
      }

  }

  void block_parallel_index_and_temp_in_subtree (XASL_NODE *arg)
  {
    if (!arg)
      {
    return;
      }
    for (ACCESS_SPEC_TYPE *specp = arg->spec_list; specp; specp = specp->next)
      {
    if (specp->type == TARGET_LIST
        || (specp->type == TARGET_CLASS && IS_ANY_INDEX_ACCESS (specp->access)))
      {
        ACCESS_SPEC_SET_FLAG (specp, ACCESS_SPEC_FLAG_NO_PARALLEL_SCAN);
      }
      }
    for (XASL_NODE *xaslp = arg->aptr_list; xaslp; xaslp = xaslp->next)
      {
    block_parallel_index_and_temp_in_subtree (xaslp);
      }
    for (XASL_NODE *xaslp = arg->bptr_list; xaslp; xaslp = xaslp->next)
      {
    block_parallel_index_and_temp_in_subtree (xaslp);
      }
    for (XASL_NODE *xaslp = arg->dptr_list; xaslp; xaslp = xaslp->next)
      {
    block_parallel_index_and_temp_in_subtree (xaslp);
      }
    for (XASL_NODE *xaslp = arg->fptr_list; xaslp; xaslp = xaslp->next)
      {
    block_parallel_index_and_temp_in_subtree (xaslp);
      }
    for (XASL_NODE *xaslp = arg->scan_ptr; xaslp; xaslp = xaslp->scan_ptr)
      {
    block_parallel_index_and_temp_in_subtree (xaslp);
      }
  }

  void process_xasl_node_recursive_force_cannot_parallel (XASL_NODE *arg)
  {
    if (!arg)
      {
    return;
      }

    if (xasl_processing_set.find (arg) != xasl_processing_set.end ())
      {
    return;
      }
    xasl_processing_set.insert (arg);

    for (ACCESS_SPEC_TYPE *specp = arg->spec_list; specp; specp = specp->next)
      {
    ACCESS_SPEC_SET_FLAG (specp, ACCESS_SPEC_FLAG_NO_PARALLEL_SCAN);
      }

    for (XASL_NODE *xaslp = arg->aptr_list; xaslp; xaslp = xaslp->next)
      {
    process_xasl_node_recursive_force_cannot_parallel (xaslp);
      }
    for (XASL_NODE *xaslp = arg->bptr_list; xaslp; xaslp = xaslp->next)
      {
    process_xasl_node_recursive_force_cannot_parallel (xaslp);
      }
    for (XASL_NODE *xaslp = arg->dptr_list; xaslp; xaslp = xaslp->next)
      {
    process_xasl_node_recursive_force_cannot_parallel (xaslp);
      }
    for (XASL_NODE *xaslp = arg->fptr_list; xaslp; xaslp = xaslp->next)
      {
    process_xasl_node_recursive_force_cannot_parallel (xaslp);
      }
    for (XASL_NODE *xaslp = arg->scan_ptr; xaslp; xaslp = xaslp->scan_ptr)
      {
    process_xasl_node_recursive_force_cannot_parallel (xaslp);
      }
    for (XASL_NODE *xaslp = arg->connect_by_ptr; xaslp; xaslp = xaslp->next)
      {
    process_xasl_node_recursive_force_cannot_parallel (xaslp);
      }

    switch (arg->type)
      {
      case CTE_PROC:
    if (arg->proc.cte.recursive_part)
      {
        process_xasl_node_recursive_force_cannot_parallel (arg->proc.cte.recursive_part);
      }
    if (arg->proc.cte.non_recursive_part)
      {
        process_xasl_node_recursive_force_cannot_parallel (arg->proc.cte.non_recursive_part);
      }
    break;
      case MERGE_PROC:
    if (arg->proc.merge.insert_xasl)
      {
        process_xasl_node_recursive_force_cannot_parallel (arg->proc.merge.insert_xasl);
      }
    if (arg->proc.merge.update_xasl)
      {
        process_xasl_node_recursive_force_cannot_parallel (arg->proc.merge.update_xasl);
      }
    break;
      case MERGELIST_PROC:
    /* guard llsid union from pllsid_parallel overwrite via outer/inner spec_list. */
    for (ACCESS_SPEC_TYPE *specp = arg->proc.mergelist.outer_spec_list; specp; specp = specp->next)
      {
        ACCESS_SPEC_SET_FLAG (specp, ACCESS_SPEC_FLAG_NO_PARALLEL_SCAN);
      }
    for (ACCESS_SPEC_TYPE *specp = arg->proc.mergelist.inner_spec_list; specp; specp = specp->next)
      {
        ACCESS_SPEC_SET_FLAG (specp, ACCESS_SPEC_FLAG_NO_PARALLEL_SCAN);
      }
    break;
      default:
    break;
      }
  }
}

extern int
scan_check_parallel_scan_possible (XASL_NODE *xasl)
{
  parallel_scan::xasl_check_cache.clear ();
  parallel_scan::xasl_processing_set.clear ();

  parallel_scan::process_xasl_node_recursive (xasl);

  parallel_scan::xasl_check_cache.clear ();
  parallel_scan::xasl_processing_set.clear ();

  return NO_ERROR;
}