Package org.apache.derby.impl.sql.execute

Source Code of org.apache.derby.impl.sql.execute.InternalTriggerExecutionContext

/*

   Derby - Class org.apache.derby.impl.sql.execute.InternalTriggerExecutionContext

   Copyright 1999, 2004 The Apache Software Foundation or its licensors, as applicable.

   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.

*/

package org.apache.derby.impl.sql.execute;

import org.apache.derby.iapi.services.sanity.SanityManager;
import org.apache.derby.iapi.error.PublicAPI;
import org.apache.derby.iapi.db.TriggerExecutionContext;
import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
import org.apache.derby.iapi.sql.ResultSet;
import org.apache.derby.iapi.sql.execute.CursorResultSet;
import org.apache.derby.iapi.sql.execute.ExecRow;
import org.apache.derby.iapi.types.DataValueDescriptor;
import org.apache.derby.iapi.sql.execute.ExecutionStmtValidator;
import org.apache.derby.iapi.sql.execute.ConstantAction;
import org.apache.derby.iapi.sql.dictionary.TriggerDescriptor;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.services.i18n.MessageService;
import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.jdbc.ConnectionContext;
import org.apache.derby.catalog.UUID;
import java.util.Enumeration;
import java.util.Vector;
import java.util.Hashtable;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.derby.iapi.error.ExceptionSeverity;
/**
* There is one of these beasts per INSERT/DELETE/UPDATE
* statement.  It fulfills the contract for the externally
* visible trigger execution context and it validates
* that a statement that is about to be executed doesn't
* violate the restrictions placed upon what can be executed
* from a trigger.
* <p>
* Note that it is crucial that cleanup() is called once
* the DML has completed, cleanup() makes sure that users
* can't do something invalid on a tec reference that they
* were holding from when the trigger fired.
*
*/
public class InternalTriggerExecutionContext implements TriggerExecutionContext, ExecutionStmtValidator
{
  /*
  ** Immutable
  */
  protected int[]          changedColIds;
  protected String[]        changedColNames;
  protected int          dmlType;
  protected String        statementText;
  protected ConnectionContext    cc;
  protected UUID          targetTableId;
  protected String        targetTableName;
  protected LanguageConnectionContext lcc;

  /*
  ** Mutable
  */
  protected CursorResultSet    beforeResultSet;
  protected CursorResultSet    afterResultSet;

  /**
   * used exclusively for InsertResultSets which have autoincrement
   * columns.
   */
  protected ExecRow        afterRow;
               
  protected boolean        cleanupCalled;
  protected TriggerEvent      event;
  protected TriggerDescriptor    triggerd;

  /*
  ** Used to track all the result sets we have given out to
  ** users.  When the trigger context is no longer valid,
  ** we close all the result sets that may be in the user
  ** space because they can no longer provide meaningful
  ** results.
  */
  private Vector  resultSetVector;

  /**
   * aiCounters is a vector of AutoincrementCounters used to keep state which
   * might be used by the trigger. This is only used by Insert triggers--
   * Delete and Update triggers do not use this variable.
   *
   * @see AutoincrementCounter
   *
   */
  private Vector aiCounters;
 
  /**
   * aiHT is a hashtable of autincrement <key, value> pairs. This is used for
   * ai values generated by the trigger.
   */
  private Hashtable aiHT;

  /**
   * Build me a big old nasty trigger execution context.
   * Damnit.
   * <p>
   * About the only thing of real interest to outside observers
   * is that it pushes itself as the trigger execution context
   * in the lcc.  Be sure to call <i>cleanup()</i> when you
   * are done, or you will be flogged like the reprobate that
   * you are.
   *
   * @param lcc  the lcc
   * @param jdbcFactory  the jdbc factory
   * @param statementText  the text of the statement that caused the
   *    trigger to fire.  may be null if we are replicating
   * @param changedColIds  the list of columns that changed.  Null
   *    for all columns or INSERT/DELETE.
   * @param changedColNames  the names that correspond to changedColIds
   * @param targetTableId  the UUID of the table upon which the trigger
   *    fired
   * @param targetTableName  the name of the table upon which the trigger
   *    fired
   * @param aiCounters    A vector of AutoincrementCounters to keep state
   *               of the ai columns in this insert trigger.a
   *
   * @exception StandardException on error
   */
  public InternalTriggerExecutionContext
  (
    LanguageConnectionContext  lcc,
    ConnectionContext      cc,
    String             statementText,
    int             dmlType,
    int[]            changedColIds,
    String[]          changedColNames,
    UUID            targetTableId,
    String            targetTableName,
    Vector            aiCounters
  ) throws StandardException
  {
    this.dmlType = dmlType;
    this.changedColIds = changedColIds;
    this.changedColNames = changedColNames;
    this.statementText = statementText;
    this.cc = cc;
    this.lcc = lcc;
    this.targetTableId = targetTableId;
    this.targetTableName = targetTableName;
    this.resultSetVector = new Vector();
    this.aiCounters = aiCounters;

    if (SanityManager.DEBUG)
    { 
      if ((changedColIds == null) != (changedColNames == null))
      {
        SanityManager.THROWASSERT("bad changed cols, "+
          "(changedColsIds == null) = "+(changedColIds == null)+
          "  (changedColsNames == null) = "+(changedColNames == null));
      }
      if (changedColIds != null)
      {
        SanityManager.ASSERT(changedColIds.length == changedColNames.length,
          "different number of changed col ids vs names");
      }
    }

    lcc.pushTriggerExecutionContext(this);
  }

  void setBeforeResultSet(CursorResultSet rs)
  {
    beforeResultSet = rs; 
  }

  void setAfterResultSet(CursorResultSet rs)
       throws StandardException
  {
    afterResultSet = rs; 
   
    if (aiCounters != null)
    {
      if (triggerd.isRowTrigger())
      {
        // An after row trigger needs to see the "first" row inserted
        rs.open();
        afterRow = rs.getNextRow();
        rs.close();
      }
      else
      {
        // after statement trigger needs to look at the last value.
        if (!triggerd.isBeforeTrigger())
          resetAICounters(false);
      }
    }
  }

  void setCurrentTriggerEvent(TriggerEvent event)
  {
    this.event = event;
  }

  void clearCurrentTriggerEvent()
  {
    event = null;
  }

  void setTrigger(TriggerDescriptor triggerd)
  {
    this.triggerd = triggerd;
  }
 
  void clearTrigger() throws StandardException
  {
    event = null;
    triggerd = null;
    if (afterResultSet != null)
    {
      afterResultSet.close();
      afterResultSet = null;
    }
    if (beforeResultSet != null)
    {
      beforeResultSet.close();
      beforeResultSet = null;
    }
  }

  /**
   * Cleanup the trigger execution context.  <B>MUST</B>
   * be called when the caller is done with the trigger
   * execution context.
   * <p>
   * We go to somewhat exaggerated lengths to free up
   * all our resources here because a user may hold on
   * to a TEC after it is valid, so we clean everything
   * up to be on the safe side.
   *
   * @exception StandardException on unexpected error
   */
  protected void cleanup()
    throws StandardException
  {
    lcc.popTriggerExecutionContext(this);

    /*
    ** Explicitly close all result sets that we have
    ** given out to the user. 
     */
    for (Enumeration e = resultSetVector.elements();
       e.hasMoreElements(); )
    {
      java.sql.ResultSet rs = (java.sql.ResultSet)e.nextElement();
      try
      {
        rs.close();
      } catch (SQLException se) {}
    }
    resultSetVector = null;
 
    /*
    ** We should have already closed our underlying
    ** ExecResultSets by closing the jdbc result sets,
    ** but in case we got an error that we caught and
    ** ignored, explicitly close them.
    */ 
    if (afterResultSet != null)
    {
      afterResultSet.close();
      afterResultSet = null;
    }
    if (beforeResultSet != null)
    {
      beforeResultSet.close();
      beforeResultSet = null;
    }

    lcc = null;
    cleanupCalled = true;
  }

  /**
   * Make sure that the user isn't trying to get a result
   * set after we have cleaned up.
   */
  private void ensureProperContext() throws SQLException
  {
    if (cleanupCalled)
    {
      throw new SQLException(
        MessageService.getTextMessage(
                  SQLState.LANG_STATEMENT_CLOSED_NO_REASON),
                  "XCL31",
                  ExceptionSeverity.STATEMENT_SEVERITY
                  );
    }
  }

  /////////////////////////////////////////////////////////
  //
  // ExecutionStmtValidator
  //
  /////////////////////////////////////////////////////////
  /**
   * Make sure that whatever statement is about to be executed
   * is ok from the context of this trigger.
   * <p>
   * Note that we are sub classed in replication for checks
   * for replication specific language.
   *
   * @param constantAction the constant action of the action
   *  that we are to validate
   *
   * @exception StandardException on error
   */
  public void validateStatement(ConstantAction constantAction) throws StandardException
  {
    if (SanityManager.DEBUG)
    {
      if (constantAction instanceof DDLConstantAction)
        SanityManager.THROWASSERT("DDL NOT SUPPORTED IN TRIGGER");
    }

    /*
    ** No INSERT/UPDATE/DELETE on trigger table
    ** for a before trigger.
     */
    else if (triggerd.isBeforeTrigger() &&
        constantAction instanceof WriteCursorConstantAction)
    {
      if (constantAction.modifiesTableId(targetTableId))
      {
        throw StandardException.newException(SQLState.LANG_NO_DML_IN_TRIGGER, triggerd.getName(), targetTableName);
      }
    }
  }

  /////////////////////////////////////////////////////////
  //
  // TriggerExectionContext
  //
  /////////////////////////////////////////////////////////

  /**
   * Get the target table name upon which the
   * trigger event is declared.
   *
   * @return the target table
   */
  public String getTargetTableName()
  {
    return targetTableName;
  }

  /**
   * Get the target table UUID upon which the
   * trigger event is declared.
   *
   * @return the uuid of the target table
   */
  public UUID getTargetTableId()
  {
    return targetTableId;
  }

  /**
   * Get the type for the event that caused the
   * trigger to fire.
   *
   * @return the event type (e.g. UPDATE_EVENT)
   */
  public int getEventType()
  {
    return dmlType;
  }

  /**
   * Get the text of the statement that caused the
   * trigger to fire.
   *
   * @return the statement text
   */
  public String getEventStatementText()
  {
    return statementText;
  }

  /**
   * Get the columns that have been modified by the statement
   * that caused this trigger to fire.  If all columns are
   * modified, will return null (e.g. for INSERT or DELETE will
   * return null).
   *
   * @return an array of Strings
   */
  public String[] getModifiedColumns()
  {
    return changedColNames;
  }

  /**
   * Find out of a column was changed, by column name
   *
   * @param columnName the column to check
    *
   * @return true if the column was modified by this statement.
   * Note that this will always return true for INSERT
   * and DELETE regardless of the column name passed in.
   */
  public boolean wasColumnModified(String columnName)
  {
    if (changedColNames == null)
    {
      return true;
    }

    for (int i = 0; i < changedColNames.length; i++)
    {
      if (changedColNames[i].equals(columnName))
      {
        return true;
      }
    }
    return false;
  }

  /**
   * Find out of a column was changed, by column number
   *
   * @param columnNumber the column to check
    *
   * @return true if the column was modified by this statement.
   * Note that this will always return true for INSERT
   * and DELETE regardless of the column name passed in.
   */
  public boolean wasColumnModified(int columnNumber)
  {
    if (changedColIds == null)
    {
      return true;
    }

    for (int i = 0; i < changedColNames.length; i++)
    {
      if (changedColIds[i] == columnNumber)
      {
        return true;
      }
    }
    return false;
  }

  /**
   * Returns a result set row the old images of the changed rows.
   * For a row trigger, the result set will have a single row.  For
   * a statement trigger, this result set has every row that has
   * changed or will change.  If a statement trigger does not affect
   * a row, then the result set will be empty (i.e. ResultSet.next()
   * will return false).
   *
   * @return the ResultSet containing before images of the rows
   * changed by the triggering event.
   *
   * @exception SQLException if called after the triggering event has
   * completed
   */
  public java.sql.ResultSet getOldRowSet() throws SQLException
  {
    ensureProperContext();
    if (beforeResultSet == null)
    {
      return null;
    }

    try
    {
      CursorResultSet brs = beforeResultSet;
      /* We should really shallow clone the result set, because it could be used
       * at multiple places independently in trigger action.  This is a bug found
       * during the fix of beetle 4373.
       */
      if (brs instanceof TemporaryRowHolderResultSet)
        brs = (CursorResultSet) ((TemporaryRowHolderResultSet) brs).clone();
      else if (brs instanceof TableScanResultSet)
        brs = (CursorResultSet) ((TableScanResultSet) brs).clone();
      brs.open();
      java.sql.ResultSet rs = cc.getResultSet(brs);
      resultSetVector.addElement(rs);
      return rs;
    } catch (StandardException se)
    {
      throw PublicAPI.wrapStandardException(se);
    }
  }

  /**
   * Returns a result set row the new images of the changed rows.
   * For a row trigger, the result set will have a single row.  For
   * a statement trigger, this result set has every row that has
   * changed or will change.  If a statement trigger does not affect
   * a row, then the result set will be empty (i.e. ResultSet.next()
   * will return false).
   *
   * @return the ResultSet containing after images of the rows
   * changed by the triggering event.
   *
   * @exception SQLException if called after the triggering event has
   * completed
   */
  public java.sql.ResultSet getNewRowSet() throws SQLException
  {
    ensureProperContext();

    if (afterResultSet == null)
    {
      return null;
    }
    try
    {
      /* We should really shallow clone the result set, because it could be used
       * at multiple places independently in trigger action.  This is a bug found
       * during the fix of beetle 4373.
       */
      CursorResultSet ars = afterResultSet;
      if (ars instanceof TemporaryRowHolderResultSet)
        ars = (CursorResultSet) ((TemporaryRowHolderResultSet) ars).clone();
      else if (ars instanceof TableScanResultSet)
        ars = (CursorResultSet) ((TableScanResultSet) ars).clone();
      ars.open();
      java.sql.ResultSet rs = cc.getResultSet(ars);
      resultSetVector.addElement(rs);
      return rs;
    } catch (StandardException se)
    {
      throw PublicAPI.wrapStandardException(se);
    }
  }

  /**
   * Like getBeforeResultSet(), but returns a result set positioned
   * on the first row of the before result set.  Used as a convenience
   * to get a column for a row trigger.  Equivalent to getBeforeResultSet()
   * followed by next().
   *
   * @return the ResultSet positioned on the old row image.
   *
   * @exception SQLException if called after the triggering event has
   * completed
   */
  public java.sql.ResultSet getOldRow() throws SQLException
  {
    java.sql.ResultSet rs = getOldRowSet();
    if (rs != null)
      rs.next();
   
    return rs;
  }

  /**
   * Like getAfterResultSet(), but returns a result set positioned
   * on the first row of the before result set.  Used as a convenience
   * to get a column for a row trigger.  Equivalent to getAfterResultSet()
   * followed by next().
   *
   * @return the ResultSet positioned on the new row image.
   *
   * @exception SQLException if called after the triggering event has
   * completed
   */
  public java.sql.ResultSet getNewRow() throws SQLException
  {
    java.sql.ResultSet rs = getNewRowSet();
    if (rs != null)
      rs.next();
    return rs;
  }
 
  public Long getAutoincrementValue(String identity)
  {
    // first search the hashtable-- this represents the ai values generated
    // by this trigger.
    if (aiHT != null)
      {
        Long value = (Long)aiHT.get(identity);
        if (value != null)
          return value;
      }
   
   
    // If we didn't find it in the hashtable search in the counters which
    // represent values inherited by trigger from insert statements.
    if (aiCounters != null)
    {
      for (int i = 0; i < aiCounters.size(); i++)
      {
        AutoincrementCounter aic =
          (AutoincrementCounter)aiCounters.elementAt(i);

        //        System.out.println("in itec:getaivalue " + aic);
        if (identity.equals(aic.getIdentity()))
        {
          //          System.out.println("in itec:getvalue--returning " +  aic.getCurrentValue());
          return aic.getCurrentValue();
        }
      }
    }
   
    // didn't find it-- return NULL.
    return null;
  }
  /**
   * Copy a hashtable of autoincrement values into the trigger
   * execution context hashtable of autoincrement values.
   */
  public void copyHashtableToAIHT(Hashtable from)
  {
    if (from == null)
      return;
    if (aiHT == null)
      aiHT = new Hashtable();
    for (Enumeration e = from.keys(); e.hasMoreElements(); )
    {
      Object key = e.nextElement();
      Object value = from.get(key);
      aiHT.put(key, value);
      //      System.out.println(" in itec:chte-- " + key + " " + value);
    }
  }
   
  /**
   * Reset Autoincrement counters to the beginning or the end.
   *
   * @param    begin    if True, reset the AutoincremnetCounter to the
   *                          beginning-- used to reset the counters for the
   *               next trigger. If false, reset it to the end--
   *                          this sets up the counter appropriately for a
   *                          AFTER STATEMENT trigger.
   */
  public void resetAICounters(boolean begin)
  {
    if (aiCounters == null)
      return;

    afterRow = null;

    int size = aiCounters.size();
    for (int i = 0; i < size; i++)
    {
      AutoincrementCounter aic =
        (AutoincrementCounter)aiCounters.elementAt(i);
      aic.reset(begin);
    }
  } 
 
  /**
   * Update Autoincrement Counters from the last row inserted.
   *
   */
  public void updateAICounters() throws StandardException
  {
    if (aiCounters == null)
      return;

    int size = aiCounters.size();
    for (int i = 0; i < size; i++)
    {
      AutoincrementCounter aic =
        (AutoincrementCounter)aiCounters.elementAt(i);
      DataValueDescriptor dvd = afterRow.getColumn(aic.getColumnPosition());
      aic.update(dvd.getLong());
    }
  }


  public String toString() {
    return triggerd.getName();
  }


}
TOP

Related Classes of org.apache.derby.impl.sql.execute.InternalTriggerExecutionContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.