Skip to content

File compile.c

File List > cubrid > src > parser > compile.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.
 *
 */

/*
 * compile.c - compile parse tree into executable form
 */

#ident "$Id$"

#include "config.h"

#include <assert.h>

#include "authenticate.h"
#include "dbi.h"
#include "parser.h"
#include "semantic_check.h"
#include "locator_cl.h"
#include "memory_alloc.h"
#include "schema_manager.h"
#include "parser_message.h"
#include "view_transform.h"
#include "intl_support.h"
#include "server_interface.h"
#include "network_interface_cl.h"
#include "execute_statement.h"
#include "transaction_cl.h"
#include "dbtype.h"

typedef struct trigger_exec_info TRIGGER_EXEC_INFO;
struct trigger_exec_info
{
  UINTPTR spec_id1;
  UINTPTR spec_id2;
  DB_OBJECT *object1;
  DB_OBJECT *object2;
  const char *name1;
  const char *name2;
  int path_expr_level;
  int trig_corr_path;       /* path expr rooted by trigger corr name */
  bool is_update_object;
};

/* structure used for parser_walk_tree in pt_class_pre_fetch */
typedef struct pt_class_locks PT_CLASS_LOCKS;
struct pt_class_locks
{
  int num_classes;
  int allocated_count;
  DB_FETCH_MODE lock_type;
  char **classes;
  int *only_all;
  LOCK *locks;
  LC_PREFETCH_FLAGS *flags;
};

enum pt_order_by_adjustment
{
  PT_ADD_ONE,
  PT_TIMES_TWO
};

static PT_NODE *pt_add_oid_to_select_list (PARSER_CONTEXT * parser, PT_NODE * statement, VIEW_HANDLING how);
static PT_NODE *pt_count_entities (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk);
static int pt_add_lock_class (PARSER_CONTEXT * parser, PT_CLASS_LOCKS * lcks, PT_NODE * spec, LC_PREFETCH_FLAGS flags);
static PT_NODE *pt_find_lck_classes (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk);
static PT_NODE *pt_find_lck_class_from_partition (PARSER_CONTEXT * parser, PT_NODE * node, PT_CLASS_LOCKS * locks);
static int pt_in_lck_array (PT_CLASS_LOCKS * lcks, const char *str, LC_PREFETCH_FLAGS flags);

static void remove_appended_trigger_info (char *msg, int with_evaluate);
static char *change_trigger_action_query (PARSER_CONTEXT * parser, PT_NODE * statement, int with_evaluate);

static PT_NODE *pt_set_trigger_obj_pre (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk);
static PT_NODE *pt_set_trigger_obj_post (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk);
static PT_NODE *pt_set_class_chn (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk);

/*
 * pt_spec_to_oid_attr () - Generate an oid attribute from a resolved spec.
 *              Can be called any time after name resolution.
 *   return:  a PT_NAME node, or a NULL
 *   parser(in): the parser context used to derive stmt
 *   spec(in/out): an entity spec. requires spec has been resolved
 *   how(in):
 */

PT_NODE *
pt_spec_to_oid_attr (PARSER_CONTEXT * parser, PT_NODE * spec, VIEW_HANDLING how)
{
  PT_NODE *node = NULL, *oid = NULL;
  PT_NODE *flat;
  PT_NODE *range;

  if (spec->info.spec.range_var == NULL)
    return NULL;

  flat = spec->info.spec.flat_entity_list;
  range = spec->info.spec.range_var;

  if (PT_SPEC_IS_DERIVED (spec) && spec->info.spec.flat_entity_list && spec->info.spec.as_attr_list)
    {
      /* this spec should have come from a vclass that was rewritten as a derived table; pull ROWOID/CLASSOID from
       * as_attr_list NOTE: see mq_rewrite_derived_table_for_update () */
      switch (how)
    {
    case OID_NAME:
      return parser_copy_tree (parser, spec->info.spec.as_attr_list);

    case CLASSOID_NAME:
    case HIDDEN_CLASSOID_NAME:
      node = parser_copy_tree (parser, spec->info.spec.as_attr_list);
      break;

    default:
      /* should not be here */
      return NULL;
    }
    }

  if (how == OID_NAME || how == CLASSOID_NAME || how == HIDDEN_CLASSOID_NAME || !flat
      || (!flat->info.name.virt_object || mq_is_updatable (flat->info.name.virt_object)))
    {
      /* just generate an oid name, if that is what is asked for or the class is not a proxy and there is no view or
       * the view is updatable */
      if (node != NULL)
    {
      oid = node;
    }
      else
    {
      oid = pt_name (parser, "");
      if (oid)
        {
          oid->info.name.resolved = range->info.name.original;
          oid->info.name.meta_class = PT_OID_ATTR;
          oid->info.name.spec_id = spec->info.spec.id;
          oid->type_enum = PT_TYPE_OBJECT;
          oid->data_type = parser_new_node (parser, PT_DATA_TYPE);
        }
      else
        {
          return NULL;
        }
      if (oid->data_type)
        {
          oid->data_type->type_enum = PT_TYPE_OBJECT;
          oid->data_type->info.data_type.entity = parser_copy_tree_list (parser, flat);
        }
      else
        {
          return NULL;
        }

      if (flat)
        {
          oid->data_type->info.data_type.virt_object = flat->info.name.virt_object;
          oid->data_type->info.data_type.virt_type_enum = flat->info.name.virt_type_enum;
        }
    }
      if (how == CLASSOID_NAME || how == HIDDEN_CLASSOID_NAME)
    {
      PT_NODE *func, *tmp;

      /* make into a class_of function with the generated OID as the argument */
      func = parser_new_node (parser, PT_FUNCTION);
      if (func)
        {
          func->info.function.function_type = F_CLASS_OF;
          func->info.function.arg_list = oid;
          func->type_enum = PT_TYPE_OBJECT;
          func->data_type = parser_new_node (parser, PT_DATA_TYPE);
        }
      else
        {
          return NULL;
        }
      if (func->data_type)
        {
          func->data_type->type_enum = PT_TYPE_OBJECT;
          func->data_type->info.data_type.entity = parser_copy_tree_list (parser, flat);
          for (tmp = func->data_type->info.data_type.entity; tmp != NULL; tmp = tmp->next)
        {
          tmp->info.name.meta_class = PT_META_CLASS;
        }
        }
      else
        {
          return NULL;
        }


      if (flat)
        {
          func->data_type->info.data_type.virt_object = flat->info.name.virt_object;
          func->data_type->info.data_type.virt_type_enum = flat->info.name.virt_type_enum;
        }

      if (how == HIDDEN_CLASSOID_NAME)
        {
          func->info.function.hidden_column = 1;
        }

      node = func;      /* return the function */
    }
      else
    {
      node = oid;       /* return the oid */
    }
    }
  else
    {
      node = mq_oid (parser, spec);
    }

  return node;
}


/*
 * pt_add_oid_to_select_list () - augment a statement's select_list
 *                                to select the oid
 *   return:  none
 *   parser(in): the parser context used to derive statement
 *   statement(in/out): a SELECT/UNION/DIFFERENCE/INTERSECTION statement
 *   how(in):
 */

static PT_NODE *
pt_add_oid_to_select_list (PARSER_CONTEXT * parser, PT_NODE * statement, VIEW_HANDLING how)
{
  PT_NODE *oid, *from;

  if (!statement)
    {
      return NULL;
    }

  if (PT_IS_QUERY_NODE_TYPE (statement->node_type))
    {
      PT_NODE *p, *ord;

      /*
       * It would be nice to make this adjustment more automatic by
       * actually counting the number of "invisible" columns and keeping a
       * running adjustment bias, but right now there doesn't seem to be a
       * way of distinguishing invisible columns from ordinary ones, short
       * of just knowing which style of adjustment is being made (single
       * oid or multiple oids).  If a new style is added, this code will
       * need to be extended.
       */
      p = statement->info.query.order_by;
      while (p)
    {
      ord = p->info.sort_spec.expr;
      assert (ord->node_type == PT_VALUE);

      /* adjust value */
      p->info.sort_spec.pos_descr.pos_no += 1;
      p->info.sort_spec.expr->info.value.data_value.i += 1;
      p->info.sort_spec.expr->info.value.db_value.data.i += 1;

      /* not needed any more */
      p->info.sort_spec.expr->info.value.text = NULL;

      p = p->next;

    }
    }

  if (statement->node_type == PT_SELECT)
    {
      /* value query doesn't have oid attr */
      if (!PT_IS_VALUE_QUERY (statement))
    {
      statement->info.query.oids_included = DB_ROW_OIDS;
      from = statement->info.query.q.select.from;
      if (from && from->node_type == PT_SPEC)
        {
          oid = pt_spec_to_oid_attr (parser, from, how);
          if (oid)
        {
          /* prepend oid to the statement's select_list */
          oid->next = statement->info.query.q.select.list;
          statement->info.query.q.select.list = oid;
        }
        }
    }
    }
  else if (statement->node_type == PT_UNION || statement->node_type == PT_INTERSECTION
       || statement->node_type == PT_DIFFERENCE)
    {

      statement->info.query.oids_included = DB_ROW_OIDS;
      statement->info.query.q.union_.arg1 =
    pt_add_oid_to_select_list (parser, statement->info.query.q.union_.arg1, how);
      statement->info.query.q.union_.arg2 =
    pt_add_oid_to_select_list (parser, statement->info.query.q.union_.arg2, how);

      if (statement->info.query.q.union_.select_list != NULL)
    {
      /* after adding oid, we need to get select_list again */
      parser_free_tree (parser, statement->info.query.q.union_.select_list);
      statement->info.query.q.union_.select_list = NULL;

      (void) pt_get_select_list (parser, statement);
    }
    }

  return statement;
}

/*
 * pt_add_row_oid () - augment a statement's select_list to select the row oid
 *   return:  none
 *   parser(in): the parser context used to derive statement
 *   statement(in/out): a SELECT/UNION/DIFFERENCE/INTERSECTION statement
 */

PT_NODE *
pt_add_row_oid (PARSER_CONTEXT * parser, PT_NODE * statement)
{
  return pt_add_oid_to_select_list (parser, statement, VIEW_OID);
}

/*
 * pt_add_row_oid_name () - augment a statement's select_list to
 *              select the row oid
 *   return:  none
 *   parser(in): the parser context used to derive statement
 *   statement(in/out): a SELECT/UNION/DIFFERENCE/INTERSECTION statement
 */

PT_NODE *
pt_add_row_oid_name (PARSER_CONTEXT * parser, PT_NODE * statement)
{
  return pt_add_oid_to_select_list (parser, statement, OID_NAME);
}

/*
 * pt_add_row_classoid_name () - augment a statement's select_list
 *               to select the row oid
 *   return:  none
 *   parser(in): the parser context used to derive statement
 *   statement(in/out): a SELECT/UNION/DIFFERENCE/INTERSECTION statement
 */

PT_NODE *
pt_add_row_classoid_name (PARSER_CONTEXT * parser, PT_NODE * statement, int server_op)
{
  if (server_op)
    {
      return pt_add_oid_to_select_list (parser, statement, CLASSOID_NAME);
    }
  else
    {
      return pt_add_oid_to_select_list (parser, statement, HIDDEN_CLASSOID_NAME);
    }
}

/*
 * pt_compile () - Semantic check and convert parse tree into executable form
 *   return:
 *   parser(in):
 *   statement(in/out):
 */
PT_NODE *
pt_compile (PARSER_CONTEXT * parser, PT_NODE * volatile statement)
{
  PT_NODE *next;

  PT_SET_JMP_ENV (parser);

  if (statement)
    {
      next = statement->next;
      statement->next = NULL;

      statement = pt_semantic_check (parser, statement);

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

  PT_CLEAR_JMP_ENV (parser);

  return statement;
}


/*
 * pt_class_pre_fetch () - minimize potential deadlocks by prefetching
 *      the classes the statement will need in the correct lock mode
 *   return:
 *   parser(in):
 *   statement(in):
 *
 * Note :
 * This routine will not avoid deadlock altogether because classes which
 * are implicitly accessed via path expressions are not know at this time.
 * We are assured however that these implicit classes will be read only
 * classes and will not need lock escalation.  These implicit classes
 * will be locked during semantic analysis.
 *
 * This routine will only prefetch for the following statement types:
 * UPDATE, DELETE, INSERT, SELECT, UNION, DIFFERENCE, INTERSECTION.
 *
 * There are two types of errors:
 * 1) lock timeout.  In this case, we set an error and return.
 * 2) unknown class.  In this case, the user has a semantic error.
 *    We need to continue with semantic analysis so that the proper
 *    error msg will be returned.  Thus WE DO NOT set an error for this case.
 */

PT_NODE *
pt_class_pre_fetch (PARSER_CONTEXT * parser, PT_NODE * statement)
{
  PT_CLASS_LOCKS lcks;
  PT_NODE *node = NULL;
  LOCK lock_rr_tran = NULL_LOCK;
  LC_FIND_CLASSNAME find_result;

  lcks.classes = NULL;
  lcks.only_all = NULL;
  lcks.locks = NULL;
  lcks.flags = NULL;

  /* we don't pre fetch for non query statements */
  if (statement == NULL)
    {
      return NULL;
    }

  switch (statement->node_type)
    {
    case PT_DELETE:
    case PT_INSERT:
    case PT_UPDATE:
    case PT_SELECT:
    case PT_UNION:
    case PT_DIFFERENCE:
    case PT_INTERSECTION:
    case PT_MERGE:
      if (TM_TRAN_ISOLATION () >= TRAN_REPEATABLE_READ && TM_TRAN_REP_READ_LOCK () == NULL_LOCK)
    {
      lock_rr_tran = S_LOCK;
    }
      break;
    default:
      if (TM_TRAN_ISOLATION () >= TRAN_REPEATABLE_READ)
    {
      if (TM_TRAN_REP_READ_LOCK () == NULL_LOCK)
        {
          lock_rr_tran = S_LOCK;
        }
      if (statement->node_type == PT_ALTER && statement->info.alter.code == PT_ADD_ATTR_MTHD)
        {
          for (node = statement->info.alter.alter_clause.attr_mthd.attr_def_list; node; node = node->next)
        {
          if (node->info.attr_def.constrain_not_null)
            {
              lock_rr_tran = X_LOCK;
              break;
            }
        }
        }
      if (tran_lock_rep_read (lock_rr_tran) != NO_ERROR)
        {
          return NULL;
        }
    }

      if (statement->node_type == PT_CREATE_ENTITY && statement->info.create_entity.entity_type == PT_CLASS)
    {
      (void) parser_walk_tree (parser, statement, NULL, NULL, pt_resolve_cte_specs, NULL);
    }

      return statement;
    }

  /* specs referring a CTE will have an entity name, just like a normal class;
   * in order to not try and prefetch (and possibly fail) such classes, we must first resolve such specs */
  (void) parser_walk_tree (parser, statement, NULL, NULL, pt_resolve_cte_specs, NULL);

  lcks.num_classes = 0;

  /* pt_count_entities() will give us too large a count if a class is mentioned more than once, but this will not hurt
   * us. */
  (void) parser_walk_tree (parser, statement, pt_count_entities, &lcks.num_classes, NULL, NULL);

  if (lcks.num_classes == 0)
    {
      return statement;     /* caught in semantic check */
    }

  /* allocate the arrays */
  lcks.allocated_count = lcks.num_classes;
  lcks.classes = (char **) malloc ((lcks.num_classes + 1) * sizeof (char *));
  if (lcks.classes == NULL)
    {
      PT_ERRORmf (parser, statement, MSGCAT_SET_PARSER_RUNTIME, MSGCAT_RUNTIME_OUT_OF_MEMORY,
          lcks.num_classes * sizeof (char *));
      goto cleanup;
    }

  lcks.only_all = (int *) malloc (lcks.num_classes * sizeof (int));
  if (lcks.only_all == NULL)
    {
      PT_ERRORmf (parser, statement, MSGCAT_SET_PARSER_RUNTIME, MSGCAT_RUNTIME_OUT_OF_MEMORY,
          lcks.num_classes * sizeof (int));
      goto cleanup;
    }

  lcks.locks = (LOCK *) malloc (lcks.num_classes * sizeof (LOCK));
  if (lcks.locks == NULL)
    {
      PT_ERRORmf (parser, statement, MSGCAT_SET_PARSER_RUNTIME, MSGCAT_RUNTIME_OUT_OF_MEMORY,
          lcks.num_classes * sizeof (DB_FETCH_MODE));
      goto cleanup;
    }
  memset (lcks.classes, 0, (lcks.num_classes + 1) * sizeof (char *));

  lcks.flags = (LC_PREFETCH_FLAGS *) malloc (lcks.num_classes * sizeof (LC_PREFETCH_FLAGS));
  if (lcks.flags == NULL)
    {
      PT_ERRORmf (parser, statement, MSGCAT_SET_PARSER_RUNTIME, MSGCAT_RUNTIME_OUT_OF_MEMORY,
          lcks.num_classes * sizeof (DB_FETCH_MODE));
      goto cleanup;
    }
  memset (lcks.flags, 0, lcks.num_classes * sizeof (LC_PREFETCH_FLAGS));

  /* reset so parser_walk_tree can step through arrays */
  lcks.num_classes = 0;

  (void) parser_walk_tree (parser, statement, pt_find_lck_classes, &lcks, NULL, NULL);

  if (!pt_has_error (parser)
      && (find_result =
      locator_lockhint_classes (lcks.num_classes, (const char **) lcks.classes, lcks.locks, lcks.only_all,
                    lcks.flags, true, lock_rr_tran)) != LC_CLASSNAME_EXIST)
    {
      if (find_result == LC_CLASSNAME_ERROR
      && (er_errid () == ER_LK_UNILATERALLY_ABORTED || er_errid () == ER_TM_SERVER_DOWN_UNILATERALLY_ABORTED))
    {
      /*
       * Transaction has been aborted, the dirty objects and cached
       * locks has been cleared in current client during the above
       * locator_lockhint_classes () process. Therefore, must return from
       * this function immediately, otherwise the released 'statement'
       * (or other resources) will cause the unexpected problems.
       */
      goto cleanup;
    }

      PT_ERRORc (parser, statement, db_error_string (3));
    }

  /* free already assigned parser->lcks_classes if exist */
  parser_free_lcks_classes (parser);

  /* parser->lcks_classes will be freed at parser_free_parser() */
  parser->lcks_classes = lcks.classes;
  parser->num_lcks_classes = lcks.num_classes;
  lcks.classes = NULL;

  (void) parser_walk_tree (parser, statement, pt_set_class_chn, NULL, NULL, NULL);

cleanup:
  if (lcks.classes)
    {
      free_and_init (lcks.classes);
    }

  if (lcks.only_all)
    {
      free_and_init (lcks.only_all);
    }

  if (lcks.locks)
    {
      free_and_init (lcks.locks);
    }
  if (lcks.flags)
    {
      free_and_init (lcks.flags);
    }
  return statement;
}

/*
 * pt_set_class_chn() -
 *   return:
 *   parser(in):
 *   node(in):
 *   arg(in):
 *   continue_walk(out):
 */
static PT_NODE *
pt_set_class_chn (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk)
{
  PT_NODE *class_;
  MOP clsmop = NULL;
  int is_class = 0;

  if (node->node_type == PT_SPEC)
    {
      for (class_ = node->info.spec.flat_entity_list; class_; class_ = class_->next)
    {
      clsmop = class_->info.name.db_object;
      if (clsmop == NULL)
        {
          continue;
        }
      is_class = db_is_class (clsmop);
      if (is_class < 0)
        {
          PT_ERRORc (parser, class_, er_msg ());
          continue;
        }
      if (is_class == 0)
        {
          continue;
        }

      if (locator_flush_class (clsmop) != NO_ERROR)
        {
          PT_ERRORc (parser, class_, er_msg ());
        }

      class_->info.name.db_object_chn = locator_get_cache_coherency_number (clsmop);
    }
    }

  return node;
}

/*
 * pt_count_entities () - If the node is an entity spec, bump counter
 *   return:
 *   parser(in):
 *   node(in): the node to check, leave node unchanged
 *   arg(out): count of entities
 *   continue_walk(in):
 */
static PT_NODE *
pt_count_entities (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk)
{
  int *cnt = (int *) arg;

  if (node->node_type == PT_SPEC)
    {
      (*cnt)++;
      if (node->info.spec.partition != NULL)
    {
      (*cnt)++;
    }
    }

  return node;
}

/*
 * pt_add_lock_class () - add class locks in the prefetch structure
 *   return : error code or NO_ERROR
 *   parser (in)    : parser context
 *   lcks (in/out)  : pointer to PT_CLASS_LOCKS structure
 *   spec (in)      : spec to add in locks list.
 *   flags(in)      : the scope for which this class is added (either for
 *            locking or for count optimization)
 */
int
pt_add_lock_class (PARSER_CONTEXT * parser, PT_CLASS_LOCKS * lcks, PT_NODE * spec, LC_PREFETCH_FLAGS flags)
{
  MOP synonym_mop = NULL;
  char realname[DB_MAX_IDENTIFIER_LENGTH] = { '\0' };
  const char *class_name = NULL;
  char target_name[DB_MAX_IDENTIFIER_LENGTH] = { '\0' };
  int len = 0;
  int error = NO_ERROR;

  if (lcks->num_classes >= lcks->allocated_count)
    {
      /* Need to allocate more space in the locks array. Do not free locks array if memory allocation fails, it will be
       * freed by the caller of this function */
      size_t new_size = lcks->allocated_count + 1;

      /* expand classes */
      char **const realloc_ptr_classes = (char **) realloc (lcks->classes, new_size * sizeof (char *));
      if (realloc_ptr_classes == NULL)
    {
      PT_ERRORmf (parser, spec, MSGCAT_SET_PARSER_RUNTIME, MSGCAT_RUNTIME_OUT_OF_MEMORY,
              new_size * sizeof (char *));
      return ER_FAILED;
    }
      lcks->classes = realloc_ptr_classes;

      /* expand only_all */
      int *const realloc_ptr_only_all = (int *) realloc (lcks->only_all, new_size * sizeof (int));
      if (realloc_ptr_only_all == NULL)
    {
      PT_ERRORmf (parser, spec, MSGCAT_SET_PARSER_RUNTIME, MSGCAT_RUNTIME_OUT_OF_MEMORY, new_size * sizeof (int));
      return ER_FAILED;
    }
      lcks->only_all = realloc_ptr_only_all;

      /* expand locks */
      LOCK *const realloc_ptr_locks = (LOCK *) realloc (lcks->locks, new_size * sizeof (LOCK));
      if (realloc_ptr_locks == NULL)
    {
      PT_ERRORmf (parser, spec, MSGCAT_SET_PARSER_RUNTIME, MSGCAT_RUNTIME_OUT_OF_MEMORY, new_size * sizeof (LOCK));
      return ER_FAILED;
    }
      lcks->locks = realloc_ptr_locks;

      /* flags */
      LC_PREFETCH_FLAGS *const realloc_ptr_flags
    = (LC_PREFETCH_FLAGS *) realloc (lcks->flags, new_size * sizeof (LC_PREFETCH_FLAGS));
      if (realloc_ptr_flags == NULL)
    {
      PT_ERRORmf (parser, spec, MSGCAT_SET_PARSER_RUNTIME, MSGCAT_RUNTIME_OUT_OF_MEMORY,
              new_size * sizeof (LC_PREFETCH_FLAGS));
      return ER_FAILED;
    }
      lcks->flags = realloc_ptr_flags;

      lcks->allocated_count++;
    }

  /* If it is a synonym name, change it to the target name. */
  class_name = spec->info.spec.entity_name->info.name.original;
  synonym_mop = db_find_synonym (class_name);
  if (synonym_mop != NULL)
    {
      class_name = db_get_synonym_target_name (synonym_mop, target_name, DB_MAX_IDENTIFIER_LENGTH);
      if (class_name == NULL)
    {
      ASSERT_ERROR_AND_SET (error);
      return error;
    }
    }
  else
    {
      /* synonym_mop == NULL */
      ASSERT_ERROR_AND_SET (error);

      if (error == ER_SYNONYM_NOT_EXIST)
    {
      er_clear ();
      error = NO_ERROR;
    }
      else
    {
      return error;
    }
    }

  sm_user_specified_name (class_name, realname, DB_MAX_IDENTIFIER_LENGTH);

  /* need to lowercase the class name so that the lock manager can find it. */
  len = (int) strlen (realname);
  /* parser->lcks_classes[n] will be freed at parser_free_parser() */
  lcks->classes[lcks->num_classes] = (char *) calloc (1, len + 1);
  if (lcks->classes[lcks->num_classes] == NULL)
    {
      PT_ERRORmf (parser, spec, MSGCAT_SET_PARSER_RUNTIME, MSGCAT_RUNTIME_OUT_OF_MEMORY, len + 1);
      return MSGCAT_RUNTIME_OUT_OF_MEMORY;
    }

  memcpy (lcks->classes[lcks->num_classes], realname, len + 1);

  if (spec->info.spec.only_all == PT_ONLY)
    {
      lcks->only_all[lcks->num_classes] = 0;
    }
  else
    {
      lcks->only_all[lcks->num_classes] = 1;
    }

  if (flags & LC_PREF_FLAG_LOCK)
    {
      lcks->locks[lcks->num_classes] = locator_fetch_mode_to_lock (lcks->lock_type, LC_CLASS, LC_FETCH_CURRENT_VERSION);
    }
  else
    {
      lcks->locks[lcks->num_classes] = NA_LOCK;
    }
  lcks->flags[lcks->num_classes] = flags;

  lcks->num_classes++;

  return NO_ERROR;
}

/*
 * pt_find_lck_classes () - identifies classes and adds an unique entry in the
 *                          prefetch structure with the SCH-S lock mode
 *   return:
 *   parser(in):
 *   node(in): the node to check, returns node unchanged
 *   arg(in/out): pointer to PT_CLASS_LOCKS structure
 *   continue_walk(in):
 */
static PT_NODE *
pt_find_lck_classes (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk)
{
  PT_CLASS_LOCKS *lcks = (PT_CLASS_LOCKS *) arg;

  lcks->lock_type = DB_FETCH_READ;

  /* Temporary disable count optimization. To enable it just restore the condition and also remove deactivation in
   * qexec_evaluate_aggregates_optimize */
  if (node->node_type == PT_SELECT)
    {
      /* count optimization */
      PT_NODE *list = node->info.query.q.select.list;
      PT_NODE *from = node->info.query.q.select.from;

      /* Check if query is of form 'SELECT count(*) from t' */
      if (list != NULL && list->next == NULL && list->node_type == PT_FUNCTION
      && list->info.function.function_type == PT_COUNT_STAR && from != NULL && from->next == NULL
      && from->info.spec.entity_name != NULL && from->info.spec.entity_name->node_type == PT_NAME
      && node->info.query.q.select.where == NULL)
    {
      /* only add to the array, if not there already in this lock mode. */
      if (!pt_in_lck_array (lcks, from->info.spec.entity_name->info.name.original, LC_PREF_FLAG_COUNT_OPTIM))
        {
          if (pt_add_lock_class (parser, lcks, from, LC_PREF_FLAG_COUNT_OPTIM) != NO_ERROR)
        {
          *continue_walk = PT_STOP_WALK;
          return node;
        }
        }
    }
    }

  /* if its not an entity, there's nothing left to do */
  if (node->node_type != PT_SPEC)
    {
      /* if its not an entity, there's nothing left to do */
      return node;
    }

  /* check if this is a parenthesized entity list */
  if (!node->info.spec.entity_name || (node->info.spec.entity_name->node_type != PT_NAME))
    {
      return node;
    }

  /* for DBLink DML */
  if (node->info.spec.remote_server_name)
    {
      return node;
    }

  if (node->info.spec.partition != NULL)
    {
      /* add specified lock on specified partition */
      node = pt_find_lck_class_from_partition (parser, node, lcks);
      if (node == NULL)
    {
      *continue_walk = PT_STOP_WALK;
      return NULL;
    }
    }
  /* only add to the array, if not there already in this lock mode. */
  if (!pt_in_lck_array (lcks, node->info.spec.entity_name->info.name.original, LC_PREF_FLAG_LOCK))
    {
      if (pt_add_lock_class (parser, lcks, node, LC_PREF_FLAG_LOCK) != NO_ERROR)
    {
      *continue_walk = PT_STOP_WALK;
      return node;
    }
    }

  return node;
}

/*
 * pt_find_lck_class_from_partition - add pre-fetch locks for specified
 *                    partition
 * return: spec
 * parser (in) : parser context
 * node   (in) : partition spec
 * locks (in)  : locks array
 */
static PT_NODE *
pt_find_lck_class_from_partition (PARSER_CONTEXT * parser, PT_NODE * node, PT_CLASS_LOCKS * locks)
{
  int error = NO_ERROR;
  const char *entity_name = NULL;
  const char *partition_name = NULL;

  if (node == NULL || node->info.spec.partition == NULL)
    {
      return node;
    }

  entity_name = node->info.spec.entity_name->info.name.original;
  partition_name = pt_partition_name (parser, entity_name, node->info.spec.partition->info.name.original);
  if (partition_name == NULL)
    {
      if (!pt_has_error (parser))
    {
      PT_INTERNAL_ERROR (parser, "allocate memory");
      return NULL;
    }
    }

  if (!pt_in_lck_array (locks, partition_name, LC_PREF_FLAG_LOCK))
    {
      /* Set lock on specified partition. Only add to the array if not there already in this lock mode. */
      node->info.spec.entity_name->info.name.original = partition_name;
      error = pt_add_lock_class (parser, locks, node, LC_PREF_FLAG_LOCK);

      /* restore spec name */
      node->info.spec.entity_name->info.name.original = entity_name;
      if (error != NO_ERROR)
    {
      return NULL;
    }
    }

  return node;
}

/*
 * pt_in_lck_array () -
 *   return: true if string found in array with given lockmode, false otherwise
 *   lcks(in):
 *   str(in):
 */
static int
pt_in_lck_array (PT_CLASS_LOCKS * lcks, const char *str, LC_PREFETCH_FLAGS flags)
{
  int i, no_lock_idx = -1;
  LOCK chk_lock;

  chk_lock = locator_fetch_mode_to_lock (lcks->lock_type, LC_CLASS, LC_FETCH_CURRENT_VERSION);
  for (i = 0; i < lcks->num_classes; i++)
    {
      if (intl_identifier_casecmp (str, lcks->classes[i]) == 0)
    {
      if (flags & LC_PREF_FLAG_LOCK)
        {
          if (lcks->flags[i] & LC_PREF_FLAG_LOCK)
        {
          if (lcks->locks[i] == chk_lock)
            {
              return true;
            }
        }
          else
        {
          no_lock_idx = i;
        }
        }
      else if (flags & LC_PREF_FLAG_COUNT_OPTIM)
        {
          lcks->flags[i] = (LC_PREFETCH_FLAGS) (lcks->flags[i] | LC_PREF_FLAG_COUNT_OPTIM);
          return true;
        }
    }
    }
  if (no_lock_idx >= 0)
    {
      lcks->locks[no_lock_idx] = chk_lock;
      lcks->flags[no_lock_idx] = (LC_PREFETCH_FLAGS) (lcks->flags[no_lock_idx] | LC_PREF_FLAG_LOCK);
      return true;
    }

  return false;         /* not found */
}

/*
 * remove_appended_trigger_info () - remove appended trigger info
 *   msg(in/out):
 *   with_evaluate(in):
 */
static void
remove_appended_trigger_info (char *msg, int with_evaluate)
{
  size_t i;
  const char *scope_str = "SCOPE___ ";
  const char *from_on_str = " FROM ON ";
  const char *eval_prefix = "EVALUATE ( ";
  const char *eval_suffix = " ) ";
  const char single_quote_char = '\'';
  const char semicolon = ';';
  char *p = NULL;

  if (msg == NULL)
    {
      return;
    }

  if (with_evaluate)
    {
      p = strstr (msg, eval_prefix);
      if (p != NULL)
    {
      p = (char *) memmove (p, p + strlen (eval_prefix), strlen (p) - strlen (eval_prefix) + 1);
    }

      p = strstr (msg, eval_suffix);
      if (p != NULL)
    {
      p = (char *) memmove (p, p + strlen (eval_suffix), strlen (p) - strlen (eval_suffix) + 1);
    }
    }

  p = strstr (msg, scope_str);
  if (p != NULL)
    {
      p = (char *) memmove (p, p + strlen (scope_str), strlen (p) - strlen (scope_str) + 1);
    }

  p = strstr (msg, from_on_str);
  if (p != NULL)
    {
      for (i = 0; p[i] != single_quote_char && p[i] != '\0'; i++)
    ;

      if (i > 0 && p[i - 1] == semicolon)
    {
      p = (char *) memmove (p, p + i, strlen (p) - i + 1);
    }
    }
}

/*
 * change_trigger_action_query () - remove appended trigger info
 *   parser(in):
 *   action_stmt(in):
 *   with_evaluate(in):
 */
static char *
change_trigger_action_query (PARSER_CONTEXT * parser, PT_NODE * statement, int with_evaluate)
{
  int result_len = 0;
  char *result = NULL;
  char *new_trigger_stmt_str = NULL;
  unsigned int save_custom;

  assert (parser != NULL || statement != NULL);

  save_custom = parser->custom_print;
  parser->flag.is_parsing_trigger = 1;

  result = parser_print_tree_with_quotes (parser, statement);

  parser->flag.is_parsing_trigger = 0;
  parser->custom_print = save_custom;

  /* remove appended trigger evaluate info */
  result = remove_appended_trigger_evaluate (result, with_evaluate);
  if (result == NULL)
    {
      return NULL;
    }

  result_len = strlen (result) + 1;
  if (result_len < 0)
    {
      return NULL;
    }

  new_trigger_stmt_str = (char *) malloc (result_len);
  if (new_trigger_stmt_str == NULL)
    {
      er_set (ER_ERROR_SEVERITY, ARG_FILE_LINE, ER_OUT_OF_VIRTUAL_MEMORY, 1, (size_t) result_len);
      return NULL;
    }

  snprintf (new_trigger_stmt_str, result_len, "%s", result);

  return new_trigger_stmt_str;
}

/*
 * pt_compile_trigger_stmt () - Compiles the trigger_stmt so that it can be
 *  executed by pt_exec_trigger_stmt
 *   return:
 *   parser(in): the parser context
 *   trigger_stmt(in): trigger_stmt to compile
 *   class_op(in): class name to resolve name1 and name2 to
 *   name1(in): name to resolve
 *   name2(in): name to resolve
 *   new_trigger_stmt(in):
 *   with_evaluate(in):
 *
 */

PT_NODE *
pt_compile_trigger_stmt (PARSER_CONTEXT * parser, const char *trigger_stmt, DB_OBJECT * class_op, const char *name1,
             const char *name2, char **new_trigger_stmt, int with_evaluate)
{
  char *stmt_str = NULL;
  const char *class_name;
  PT_NODE **statement_p, *statement;
  int is_update_object;
  PT_NODE *err_node;

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

  if (!trigger_stmt)
    return NULL;

  /* is this an update object statement? if so it gets more complex */
  is_update_object = (intl_mbs_ncasecmp (trigger_stmt, "update object", strlen ("update object")) == 0);

  if (is_update_object)
    {
      if (class_op == NULL)
    {
      return NULL;      /* deleted object */
    }

      /* The name that could be an argument to UPDATE OBJECT will always be the first name supplied here. Don't need to
       * initialize a label as we'll convert the PT_PARAMETER node into a PT_TRIGGER_OID later. */
      if (name1 != NULL)
    {
      name1 = name2;
      name2 = NULL;
    }
    }

  /* wrap a scope around the statement */
  stmt_str = pt_append_string (parser, stmt_str, "SCOPE___ ");
  stmt_str = pt_append_string (parser, stmt_str, trigger_stmt);

  class_name = NULL;
  if (class_op && name1 != NULL)
    {
      class_name = db_get_class_name (class_op);
      if (!class_name)
    {
      return (PT_NODE *) 0;
    }

      stmt_str = pt_append_string (parser, stmt_str, " FROM ON ");
      if (name1)
    {
      stmt_str = pt_append_string (parser, stmt_str, " [");
      stmt_str = pt_append_string (parser, stmt_str, class_name);
      stmt_str = pt_append_string (parser, stmt_str, "] ");
      stmt_str = pt_append_string (parser, stmt_str, name1);
    }

      if (name2)
    {
      if (!name1)
        {
          return (PT_NODE *) 0;
        }
      stmt_str = pt_append_string (parser, stmt_str, ", [");
      stmt_str = pt_append_string (parser, stmt_str, class_name);
      stmt_str = pt_append_string (parser, stmt_str, "] ");
      stmt_str = pt_append_string (parser, stmt_str, name2);
    }
    }
  stmt_str = pt_append_string (parser, stmt_str, ";");

  statement_p = parser_parse_string_use_sys_charset (parser, stmt_str);
  if (statement_p == NULL || *statement_p == NULL)
    return NULL;
  statement = *statement_p;

  /* If this is an update object statement, setup a spec to allow binding to the :obj parameter.  This code was copied
   * from some other pt_ function, should consider making this a subroutine if it is generally useful. */
  if (is_update_object)
    {
      PT_NODE *upd, *entity, *entity_name;

      upd = statement->info.scope.stmt->info.trigger_action.expression;
      entity = parser_new_node (parser, PT_SPEC);
      entity->info.spec.id = (UINTPTR) entity;
      entity->line_number = upd->line_number;
      entity->column_number = upd->column_number;
      entity->info.spec.entity_name = parser_new_node (parser, PT_NAME);
      entity_name = entity->info.spec.entity_name;
      entity_name->info.name.spec_id = entity->info.spec.id;
      entity_name->info.name.meta_class = PT_CLASS;
      entity_name->info.name.original = (const char *) db_get_class_name (class_op);

      entity->info.spec.only_all = PT_ONLY;
      entity->info.spec.range_var = parser_copy_tree (parser, entity_name);
      if (entity->info.spec.range_var == NULL)
    {
      PT_INTERNAL_ERROR (parser, "parser_copy_tree");
      return NULL;
    }

      entity->info.spec.range_var->info.name.resolved = NULL;
      upd->info.update.spec = entity;
    }

  /* prevents forced cast() within the code. (parser->flag.is_parsing_trigger == 1 && p->info.expr.flag != 0) */
  parser->flag.is_parsing_trigger = 1;

  statement = pt_compile (parser, statement);

  parser->flag.is_parsing_trigger = 0;

  /* Remove those info we append, which users can't understand them */
  if (pt_has_error (parser))
    {
      err_node = pt_get_errors (parser);
      while (err_node)
    {
      remove_appended_trigger_info (err_node->info.error_msg.error_message, with_evaluate);
      err_node = err_node->next;
    }
    }

  /* We need to do view translation here on the expression to be executed. */
  if (statement)
    {
      char *new_trigger_stmt_str = NULL;

      new_trigger_stmt_str =
    change_trigger_action_query (parser, statement->info.scope.stmt->info.trigger_action.expression, with_evaluate);
      if (new_trigger_stmt_str == NULL)
    {
      assert (false);
      return NULL;
    }

      if (*new_trigger_stmt)
    {
      free_and_init (*new_trigger_stmt);
    }
      *new_trigger_stmt = new_trigger_stmt_str;

      statement->info.scope.stmt->info.trigger_action.expression =
    mq_translate (parser, statement->info.scope.stmt->info.trigger_action.expression);
      /*
       * Trigger statement node must use the datetime information of the
       * node corresponding the action to be made.
       */
      if (statement->info.scope.stmt && statement->info.scope.stmt->info.trigger_action.expression)
    {
      statement->flag.si_datetime |= statement->info.scope.stmt->info.trigger_action.expression->flag.si_datetime;
    }
    }

  /* split the delete statement */
  if (statement != NULL && statement->info.scope.stmt->info.trigger_action.expression != NULL
      && statement->info.scope.stmt->info.trigger_action.expression->node_type == PT_DELETE)
    {
      if (pt_split_delete_stmt (parser, statement->info.scope.stmt->info.trigger_action.expression) != NO_ERROR)
    {
      return NULL;
    }
    }

  return statement;
}


/*
 * pt_set_trigger_obj_pre () - sets pt_trigger_exec_info's object1 and object2
 *   return:
 *   parser(in):  the parser context
 *   node(in): trigger_stmt statement node
 *   arg(in): a struct pt_trigger_exec_info
 *   walk_on(in/out): flag that tells when to stop traversal
 *
 * Note :
 * Handling UPDATE OBJECT statements is a bit different from regular
 * update statements.  The "obj" argument for the update will actually
 * start in the parse tree with metaclass PT_PARAMETER and the spec_id
 * will be zero the first time we walk over the tree.  This gets changed
 * to a PT_TRIGGER_OID so at exec time we don't have to deal with setting
 * a value for this label.
 *
 * In the usual case, we just look for PT_NAME nodes with spec_id's that
 * match the spec_id's given to the correlation names in the outer scope.
 */
static PT_NODE *
pt_set_trigger_obj_pre (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk)
{
  TRIGGER_EXEC_INFO *exec_info = (TRIGGER_EXEC_INFO *) arg;


  if (exec_info->is_update_object)
    {
      /* Its an "update object" statement, the object target will initially be a PT_PARAMETER meta_class node. When we
       * find one of these, convert it to be a normal PT_TRIGGER_OID node so we avoid ambiguities in name resolution
       * and don't have to actually set a :obj label for run time evaluation. Note that since the meta_class of the
       * node changes after the first time through here, the following clause has to be smart about recognzing this in
       * the future with a spec_id of zero. */
      switch (node->node_type)
    {
    case PT_SELECT:
      node->info.query.xasl = (void *) 0;
      break;

    case PT_UPDATE:
      /* this is the root of the update statement, the "object" field must be set to a non-NULL value in order for
       * do_update() to believe that this is an update object statement. Given that, I'm not sure if it is
       * necessary to be setting the parameter value in the object_parameter field as well but it doesn't seem to
       * hurt. */
      node->info.update.object = exec_info->object1;
      break;

    case PT_NAME:
      if (node->info.name.meta_class == PT_PARAMETER)
        {
          /* it could be our :obj or :new parameter, ignore spec_id, it could * be zero or could be resolved
           * depending on where we are. this shouldn't matter, the symbolic names tell us what this is. */
          if ((exec_info->name1 != NULL && strcmp (node->info.name.original, exec_info->name1) == 0)
          || (exec_info->name2 != NULL && strcmp (node->info.name.original, exec_info->name2) == 0))
        {
          /* its a parameter reference to one of our magic names */
          node->info.name.db_object = exec_info->object1;
          node->info.name.meta_class = PT_TRIGGER_OID;
          if (exec_info->path_expr_level)
            {
              exec_info->trig_corr_path = true;
            }
        }
        }
      else
        {
          /* its some other non-paremter name node */
          if (exec_info->spec_id1 && node->info.name.spec_id == exec_info->spec_id1)
        {
          node->info.name.db_object = exec_info->object1;
          node->info.name.meta_class = PT_TRIGGER_OID;
          if (exec_info->path_expr_level)
            {
              exec_info->trig_corr_path = true;
            }
        }
          else if (exec_info->spec_id2 && node->info.name.spec_id == exec_info->spec_id2)
        {
          node->info.name.db_object = exec_info->object2;
          node->info.name.meta_class = PT_TRIGGER_OID;
          if (exec_info->path_expr_level)
            {
              exec_info->trig_corr_path = true;
            }
        }
          else if (node->info.name.spec_id == 0 && node->info.name.meta_class == PT_TRIGGER_OID)
        {
          /* this was our former PT_PARAMETER node that we hacked into a PT_TRIGGER_OID above. */
          node->info.name.db_object = exec_info->object1;
          if (exec_info->path_expr_level)
            {
              exec_info->trig_corr_path = true;
            }
        }
        }
      break;

    case PT_DOT_:
      exec_info->path_expr_level++;
      break;

    default:
      break;
    }
    }
  else
    {
      /* its a "normal" update/insert/delete statement surrounded by a SCOPE___ block with one or more correlation
       * names. */
      switch (node->node_type)
    {
    case PT_SELECT:
      node->info.query.xasl = (void *) 0;
      break;

    case PT_NAME:
      if (exec_info->spec_id1 && node->info.name.spec_id == exec_info->spec_id1)
        {
          node->info.name.db_object = exec_info->object1;
          node->info.name.meta_class = PT_TRIGGER_OID;
          if (exec_info->path_expr_level)
        {
          exec_info->trig_corr_path = true;
        }
        }
      else if (exec_info->spec_id2 && node->info.name.spec_id == exec_info->spec_id2)
        {
          node->info.name.db_object = exec_info->object2;
          node->info.name.meta_class = PT_TRIGGER_OID;
          if (exec_info->path_expr_level)
        {
          exec_info->trig_corr_path = true;
        }
        }
      break;

    case PT_DOT_:
      exec_info->path_expr_level++;
      break;

    default:
      break;
    }
    }

  return node;
}

/*
 * pt_set_trigger_obj_post () -
 *   return:
 *   parser(in):
 *   node(in):
 *   arg(in):
 *   walk_on(in):
 */
static PT_NODE *
pt_set_trigger_obj_post (PARSER_CONTEXT * parser, PT_NODE * node, void *arg, int *continue_walk)
{
  PT_NODE *temp, *next;
  TRIGGER_EXEC_INFO *exec_info = (TRIGGER_EXEC_INFO *) arg;

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

  exec_info->path_expr_level--;
  if (!exec_info->path_expr_level && exec_info->trig_corr_path)
    {
      /* This is a path expression rooted in a trigger correlation variable We need to replace this path expression by
       * evaluating it */
      exec_info->trig_corr_path = false;

      /* save the next pointer or it will be lost */
      next = node->next;
      node->next = NULL;
      temp = pt_eval_value_path (parser, node);
      parser_free_tree (parser, node);
      node = temp;

      /* pt_eval_value_path can return NULL, so be careful. */
      if (node)
    {
      node->next = next;
    }
    }

  return node;
}


/*
 * pt_exec_trigger_stmt () - Executes the trigger_stmt after setting object1
 *                           and object2.
 *   return: NO_ERROR on success, non-zero for ERROR
 *   parser(in): the parser context
 *   trigger_stmt(in): trigger_stmt to exec
 *   object1(in): object to associate with compiled name1
 *   object2(in): object to associate with compiled name2
 *   result(out): result of the trigger_stmt
 *
 * Note :
 * Special handling for the "update object" statement has made this rather
 * complex.  It would be worth considering changing the way. we parse update
 * object statements so that the target can be an correlation name
 * as well as a parameter.
 */
int
pt_exec_trigger_stmt (PARSER_CONTEXT * parser, PT_NODE * trigger_stmt, DB_OBJECT * object1, DB_OBJECT * object2,
              DB_VALUE * result)
{
  int error = NO_ERROR;
  PT_NODE *node;
  PT_NODE *tmp_trigger = NULL;
  TRIGGER_EXEC_INFO exec_info;
  DB_VALUE **src;
  int unhide1, unhide2;
  int server_info_bits;

  assert (parser != NULL && trigger_stmt != NULL && trigger_stmt->node_type == PT_SCOPE);

  server_info_bits = 0;     /* init */

  /* set sys_date, sys_time, sys_timestamp, sys_datetime values for trigger statement. */
  if (trigger_stmt->flag.si_datetime)
    {
      server_info_bits |= SI_SYS_DATETIME;
    }

  if (trigger_stmt->flag.si_tran_id)
    {
      server_info_bits |= SI_LOCAL_TRANSACTION_ID;
    }

  /* request to the server */
  if (server_info_bits)
    {
      error = qp_get_server_info (parser, server_info_bits);
      if (error != NO_ERROR)
    {
      return error;
    }
    }

  /* initialize our parser_walk_tree state */
  (void) memset (&exec_info, 0, sizeof (exec_info));
  exec_info.object1 = object1;
  exec_info.object2 = object2;

  if (trigger_stmt->info.scope.stmt != NULL && trigger_stmt->info.scope.stmt->node_type == PT_TRIGGER_ACTION)
    {
      node = trigger_stmt->info.scope.stmt->info.trigger_action.expression;
      if (node != NULL && node->node_type == PT_UPDATE && node->info.update.object_parameter != NULL)
    {
      PT_NODE *temp;

      exec_info.is_update_object = true;

      /* make sure the spec we created up in pt_compile_trigger_stmt gets updated to have the actual class of this
       * instance, not sure that's really necessary. */
      temp = node->info.update.spec->info.spec.flat_entity_list;
      temp->info.name.db_object = db_get_class (object1);

      /* first object will be the parameter */
      temp = node->info.update.object_parameter;
      exec_info.spec_id1 = 0;   /* nothing yet */
      exec_info.name1 = temp->info.name.original;

      /* second object will be in the scope */
      node = trigger_stmt->info.scope.from;
      if (node != NULL && node->info.spec.entity_name != NULL)
        {
          exec_info.spec_id2 = node->info.spec.entity_name->info.name.spec_id;
          exec_info.name2 = node->info.spec.range_var->info.name.original;
        }
    }
      else
    {
      node = trigger_stmt->info.scope.from;
      /* both names will be in the scope */
      /* first name, "obj" or "new" */
      if (node != NULL && node->info.spec.entity_name != NULL)
        {
          exec_info.spec_id1 = node->info.spec.entity_name->info.name.spec_id;
          exec_info.name1 = node->info.spec.range_var->info.name.original;
        }

      /* second name, "obj" or "old" */
      if (node != NULL && (node = node->next) != NULL && node->info.spec.entity_name != NULL)
        {
          exec_info.spec_id2 = node->info.spec.entity_name->info.name.spec_id;
          exec_info.name2 = node->info.spec.range_var->info.name.original;
        }
    }
    }

  /* We need to copy the trigger statement because pt_set_trigger_obj_post() may change the statement by evaluating
   * path expressions rooted by trigger correlation variable names. */
  tmp_trigger = parser_copy_tree (parser, trigger_stmt);
  if (tmp_trigger == NULL)
    {
      assert (er_errid () != NO_ERROR);
      return er_errid ();
    }
  /* Due to tree copy, the spec ids are broken. Reset spec ids */
  tmp_trigger = mq_reset_ids_in_statement (parser, tmp_trigger);
  if (tmp_trigger == NULL)
    {
      assert (er_errid () != NO_ERROR);
      return er_errid ();
    }

  (void) parser_walk_tree (parser, tmp_trigger->info.scope.stmt, pt_set_trigger_obj_pre, (void *) &exec_info,
               pt_set_trigger_obj_post, (void *) &exec_info);

  unhide1 = ws_hide_new_old_trigger_obj (object1);
  unhide2 = ws_hide_new_old_trigger_obj (object2);

  error = do_scope (parser, tmp_trigger);
  if (unhide1)
    {
      ws_unhide_new_old_trigger_obj (object1);
    }
  if (unhide2)
    {
      ws_unhide_new_old_trigger_obj (object2);
    }

  /* Rather than cloning, simply transfer the contents of the DB_VALUE in the "etc" field to the user supplied
   * container. Be sure to free the "etc" value when we're done, otherwise it becomes dangling the next time we
   * evaluate this trigger expresssion. */
  src = NULL;
  if (tmp_trigger->info.scope.stmt->node_type == PT_TRIGGER_ACTION)
    {
      src = (DB_VALUE **) (&tmp_trigger->info.scope.stmt->info.trigger_action.expression->etc);
    }
  else if (tmp_trigger->info.scope.stmt->node_type == PT_EVALUATE)
    {
      src = (DB_VALUE **) (&tmp_trigger->info.scope.stmt->etc);
    }

  if (src == NULL || *src == NULL)
    {
      db_make_null (result);
    }
  else
    {
      /* transfer the contents without cloning since we're going to free the source container */
      *result = **src;

      /* free this so it doesn't become garbage the next time */
      db_make_null (*src);
      db_value_free (*src);
    }

  /* reset the parser values */
  if (trigger_stmt->flag.si_datetime)
    {
      db_make_null (&parser->sys_datetime);
      db_make_null (&parser->sys_epochtime);
    }
  if (trigger_stmt->flag.si_tran_id)
    {
      db_make_null (&parser->local_transaction_id);
    }

  parser_free_tree (parser, tmp_trigger);

  return error;
}

/*
 * pt_name_occurs_in_from_list() - counts the number of times a name
 * appears as an exposed name in a list of entity_spec's
 *   return:
 *   parser(in):
 *   name(in):
 *   from_list(in):
 */
int
pt_name_occurs_in_from_list (PARSER_CONTEXT * parser, const char *name, PT_NODE * from_list)
{
  PT_NODE *spec;
  int i = 0;

  if (!name || !from_list)
    {
      return i;
    }

  for (spec = from_list; spec != NULL; spec = spec->next)
    {
      if (spec->info.spec.range_var && spec->info.spec.range_var->info.name.original
      && (intl_identifier_casecmp (name, spec->info.spec.range_var->info.name.original) == 0))
    {
      i++;
    }
    }

  return i;
}