blob: 4bed042b0215a083ab1c05c75fdcc0903e47415e [file] [log] [blame]
/**********************************************************************
// @@@ START COPYRIGHT @@@
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.
//
// @@@ END COPYRIGHT @@@
**********************************************************************/
/* -*-C++-*-
******************************************************************************
*
* File: Triggers.cpp
* Description:
* Created: 06/23/98
*
*
******************************************************************************
*/
#include "AllRelExpr.h"
#include "ItemFunc.h"
#include "ItemOther.h" // for Convert
#include "Triggers.h"
#include "TriggerDB.h"
#include "parser.h"
#include "StmtNode.h"
#include "ComDiags.h"
#include "StmtDDLCreateTrigger.h"
#include "BindWA.h"
#include "ChangesTable.h"
#include "MvRefreshBuilder.h"
const static short MAX_RECURSION_DEPTH = 16; // limit for recursive triggers
//-----------------------------------------------------------------------------
//
// Take the name of the subject table; return the name of its trig-temp table
//
NAString
subjectNameToTrigTemp( const NAString & subjectTableName )
{
NAString trigTempTableName( subjectTableName );
ChangesTable::addSuffixToTableName(trigTempTableName, TRIG_TEMP_TABLE_SUFFIX);
return trigTempTableName;
}
//
// Take the name of the trig-temp table; return the name of its subject table
// ( Name of the trig-temp _MUST_ be "well formatted" !!! (i.e., generated
// by subjectNameToTrigTemp() ) )
//
// no longer used -buggy
NAString
trigTempToSubjectName( const NAString & trigTempTableName )
{
NAString subjectTableName( trigTempTableName );
size_t nameLen = subjectTableName.length();
ComBoolean isQuoted = FALSE;
// special case: string ends with a quote char
if ( '"' == subjectTableName[ nameLen - 1 ] )
{
subjectTableName = subjectTableName.strip(NAString::trailing,'"');
isQuoted = TRUE;
}
if ( TRIG_TEMP_TABLE_SUFFIX NEQ NULL )
{
CMPASSERT(subjectTableName.length() >= sizeof(TRIG_TEMP_TABLE_SUFFIX));
subjectTableName.remove(subjectTableName.length() - sizeof(TRIG_TEMP_TABLE_SUFFIX) + 1);
}
if ( isQuoted ) subjectTableName.append( "\"" );
return subjectTableName;
}
//-----------------------------------------------------------------------------
//
// -- class UpdateColumns Methods
// Ctor from STOI column list
UpdateColumns::UpdateColumns(SqlTableOpenInfo *stoi)
: allColumns_(FALSE)
{
columns_ = new (Trigger::Heap()) SET(Lng32) (Trigger::Heap());
if (stoi != NULL)
{
for (short i=0; i<stoi->getColumnListCount(); i++)
addColumn(stoi->getUpdateColumn(i));
}
}
//
// -- UpdateColumns::~UpdateColumns
//
UpdateColumns::~UpdateColumns()
{
if (columns_)
{
delete columns_;
}
}
//
// -- UpdateColumns::match
//
// Implements match-any: It is enough that there is a common column in the
// subject columns and in the updated columns for the relevant trigger to fire.
//
NABoolean
UpdateColumns::match(const UpdateColumns &other) const
{
// all columns are specified iff the columns hash is not allocated
CMPASSERT(isAllColumns() == !columns_);
// if either the trigger is defined on all columns or the update is on all
// columns then return true at once
if (isAllColumns() || other.isAllColumns())
return TRUE;
CollIndex numEntries = other.columns_->entries();
for (CollIndex i = 0; i < numEntries; i++)
if (columns_->contains((*other.columns_)[i]))
return TRUE;
return FALSE;
}
//
// -- UpdateColumns::markColumnsOnBitmap
//
// Create a bitmap of the updated columns.
//
void
UpdateColumns::markColumnsOnBitmap(unsigned char *bitmap, CollIndex numBytes) const
{
// For every updated column, mark the corresponding bit.
if (isAllColumns())
{
// Mark all the bits as TRUE.
for (Int32 i=0; i<numBytes; i++)
bitmap[i] = 0xFF;
}
else
{
// zero out the bitmap first
for (Int32 i=0; i<numBytes; i++)
bitmap[i] = 0;
CollIndex numEntries = columns_->entries();
for (CollIndex i = 0; i < numEntries; i++)
{
Int32 byteIx = (*columns_)[i] / 8;
Int32 bitIx = (*columns_)[i] % 8;
if (byteIx < numBytes)
bitmap[byteIx] |= 0x01 << bitIx;
}
}
}
//
// -- UpdateColumns::print
//
// Debugging aid
//
// used for debugging only
void
UpdateColumns::print(ostream & os, const char* indent, const char* title) const
{
os << indent << title << " ";
if (isAllColumns())
{
os << "All Columns\n";
return;
}
CollIndex numEntries = columns_->entries();
for (CollIndex i = 0; i < numEntries; i++)
os << (*columns_)[i] << ", ";
os << endl;
}
//-----------------------------------------------------------------------------
//
// -- class Trigger Methods
//
// -- Trigger::~Trigger
//
// only used when triggers are allocated from the cntext heap
// are trigger object's get desructed explicitly
// Currently triggers are allocated from the statement heap.
// See method Trigger::Heap() in file Triggers.h for more details
Trigger::~Trigger()
{
// only when persistence is active, trigger object's get desructed
// explicitly
CMPASSERT(Heap() == CmpCommon::contextHeap());
// delete the relExpr tree
if (parsedTrigger_)
delete (parsedTrigger_);
//-----------------------------------------------------------------
// NOTE: the following fields are pointers that are not
// allocated by this object, yet it is the allocation policy -
// Trigger object members are allocated by the catman, but destroyed
// by the Trigger dtor.
//-----------------------------------------------------------------
if (updateCols_)
delete updateCols_;
if (sqlText_)
delete sqlText_;
}
//
// -- Trigger:: equality
//
NABoolean
Trigger::operator ==(const Trigger &other) const
{
return name_ == other.name_;
}
//------------------------------------------------------------------------------
//
// -- Trigger::parseTriggerText
//
// Parse the SQL trigger text in sqlText_ .
// If the parsing fails NULL is returned (and the caller, i.e., binder) has to
// handle the error.
// The returned RelExpr tree is allocated from the StatementHeap.
// A dummy select list (with arity 1) is forced for the RelRoot, since this
// subtree returns nothing, and it is essential for (ordered-) union
// compatability.
//
StmtDDLCreateTrigger *
Trigger::parseTriggerText() const
{
ExprNode *parsedNode;
RelExpr *triggerBody;
CMPASSERT(sqlText_ != NULL);
if (sqlTextCharSet_ == CharInfo::UnknownCharSet)
const_cast<Trigger*>(this)->sqlTextCharSet_ = CharInfo::UTF8;
// Parse the SQL text.
Parser parser(CmpCommon::context());
if (parser.parseDML(sqlText_->data(), sqlText_->length(), sqlTextCharSet_, &parsedNode))
return NULL;
// Extract the trigger body without the DDL nodes on top
DDLExpr *parseTree =
(DDLExpr *)((StmtNode *)parsedNode->castToStatementExpr())->
getQueryExpression()->getChild(0);
CMPASSERT(parseTree->getOperatorType() == REL_DDL);
CMPASSERT(parseTree->getDDLNode()->getOperatorType()==DDL_CREATE_TRIGGER);
StmtDDLCreateTrigger *createTriggerNode =
(StmtDDLCreateTrigger*)parseTree->getDDLNode();
triggerBody = createTriggerNode->getActionExpression();
if (isAfterTrigger())
{
CMPASSERT(triggerBody->getOperatorType() == REL_ROOT);
((RelRoot *)triggerBody)->setRootFlag(FALSE);
((RelRoot *)triggerBody)->setEmptySelectList();
}
return createTriggerNode;
}
//
// -- Trigger::getParsedTrigger
//
// 1) check the recursion limit. if the recursion depth limit is exceeded then
// (a) the trigger action is replaced to generate a RUN_TIME error
// return an 'error node' allocated from the StatementHeap
// (b) a COMPILE-TIME warning is generated indicating the potential for a
// run time error in case the execution path will indeed reach this point.
// (else)
// 2) if this is the first call to this function, then parse the trigger's text
// and return it, without initializing parsedTrigger_ (this is an
// optimization for the common case in which triggers are not recursive).
// 3) if this is the second call to this func, initialize parsedTriggers_
// and return a COPY of it to the caller. All other calls to this func
// will return only a COPY of parsedTrigger_ (optimization again - most
// likely that the recursion will not end after 2 steps only, thus we save
// parsing over and over again each time.
//
// (modified by <aviv>)
//
RelExpr *
Trigger::getParsedTrigger(BindWA *bindWA)
{
RelExpr *result = NULL;
// check the resursion limit -- only after triggers can be recursive
if (isAfterTrigger() && recursionCounter_ > MAX_RECURSION_DEPTH)
{
// prepare error generating RelExpr for runtime
RaiseError *error = new (CmpCommon::statementHeap())
RaiseError(11003, getTriggerName(), getSubjectTableName());
Tuple *tuple = new (CmpCommon::statementHeap())
TupleList(new (CmpCommon::statementHeap()) Convert(error));
// When execution path gets here, limit was already exceeded, and
// therefore error will be emitted unconditionally.
result = new (CmpCommon::statementHeap()) RelRoot(tuple);
((RelRoot *)result)->setRootFlag(FALSE);
((RelRoot *)result)->setEmptySelectList();
// emit a compile-time warning
*CmpCommon::diags() << DgSqlCode(11002) <<
DgString0(getTriggerName()) <<
DgString1(getSubjectTableName());
}
else
{ // recursion limit not exceeded
if (parsedTrigger_ == NULL)
{
//first call to this func
parsedTrigger_ = parseTriggerText();
if (parsedTrigger_ == NULL)
{
bindWA->setErrStatus();
return NULL;
}
// parsing must succeed since it passed DDL parsing
CMPASSERT(parsedTrigger_ != NULL);
result = parsedTrigger_->getActionExpression();
}
else
{
// not the first call to this func
// wasParsed_ == TRUE;
if (!isVirginCopy_)
{ // second call
// Call the parser again since parsedTrigger_ points to an
// already bound RelExpr tree. Save the pointer for the future
parsedTrigger_ = parseTriggerText();
// parsing must succeed since it passed DDL parsing
CMPASSERT(parsedTrigger_ != NULL);
}
// Return a copy allocated from Trigger::Heap
result =
parsedTrigger_->getActionExpression()->copyTree(CmpCommon::statementHeap());
isVirginCopy_ = TRUE;
}
}
return result;
}
//
// -- Trigger::print
//
// Debugging aid.
//
// used for debugging only
void
Trigger::print(ostream &os, const char* indent, const char* title) const
{
os << indent << title << " ";
os << indent << getTriggerName() << " : " << endl;
BUMP_INDENT(indent);
os << indent << "Subject Table Name : " << getSubjectTableName() << endl;
os << indent << "Granularity: " <<
(isStatementTrigger() ? "Statement " : "Row ") << endl;
os << indent << "Activation: " <<
(isBeforeTrigger() ? "Before " : "After ") << endl;
os << indent << "Operation: ";
switch (getOperation())
{
case COM_UPDATE: os << "Update";
break;
case COM_DELETE: os << "Delete";
break;
case COM_INSERT: os << "Insert";
break;
}
os << endl;
if (operation_ == COM_UPDATE)
updateCols_->print(os, indent, "Subject Columns");
os << "TimeStamp: " << convertInt64ToDouble(timeStamp_) << endl;
os << "Recursion Counter: " << recursionCounter_ << endl;
}
//------------------------------------------------------------------------------
//
// -- MVImmediate::getParsedTrigger
//
// Returns the refresh tree for this MVImmediate object, i.e. ON STATEMENT MV.
RelExpr *MVImmediate::getParsedTrigger(BindWA *bindWA)
{
return triggerBuilder_->buildRefreshTree();
}
//------------------------------------------------------------------------------
//
// -- TriggerList::calcColumnMatchingTriggers
//
// Used by BeforeAndAfterTriggers::calcColumnMatchingTriggers
//
TriggerList*
TriggerList::getColumnMatchingTriggers(const UpdateColumns *updateCols)
{
CollIndex i;
TriggerList* relevantTrgs = NULL;
Trigger* trg;
if (updateCols == NULL)
return this;
for (i=0; i<entries(); i++)
{
trg=(*this)[i];
if (trg->getUpdateColumns()->match(*updateCols))
{
if (!relevantTrgs)
relevantTrgs = new (CmpCommon::statementHeap())
TriggerList(CmpCommon::statementHeap());
relevantTrgs->insert(trg);
}
}
return relevantTrgs;
}
//
// -- TriggerList::sortByTimeStamp
//
// internally sorts the list by time stamp, so that (*this)[0] is the oldest Trigger
//
typedef Trigger* TriggerPtr;
// used in qsort
//
// Return Value
// -1 elem1 less than elem2
// 0 elem1 equivalent elem2
// 1 elem1 greater than elem2
//-----------------------------------------------------------------------------------
static Int32 triggerTimeCompare(const void *elem1, const void *elem2)
{
ComTimestamp ts1 = (*((TriggerPtr *)elem1))->getTimeStamp();
ComTimestamp ts2 = (*((TriggerPtr *)elem2))->getTimeStamp();
if (ts1 > ts2) return 1;
else if(ts1 < ts2 ) return -1;
else return 0;
}
void
TriggerList::sortByTimeStamp()
{
CollIndex mySize = this->entries();
// prepare an array of Trigger* for qsort
TriggerPtr* trigArray = new TriggerPtr[mySize];
// copy the list's entries into trigArray
for (CollIndex idx = 0; idx < mySize; idx++)
trigArray[idx] = (*this)[idx];
// sort trigArray by timestamp
opt_qsort(trigArray,
mySize,
sizeof(Trigger*),
triggerTimeCompare);
// set triggerList_ based on the reordered array optTriggerPtrArray
this->clear();
for (CollIndex k = 0 ; k < mySize; k++)
this->insert(trigArray[k]);
// cleanup
delete[] trigArray;
}
//
// -- TriggerList::print
//
// Debugging aid.
//
// used for debugging only
void
TriggerList::print(ostream & os, const char* indent, const char* title) const
{
os << title << endl;
BUMP_INDENT(indent);
for (CollIndex i=0; i<entries(); i++)
{
(*this)[i]->print(os, indent, "Trigger ");
os << ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ";
}
}
//
// -- TriggerList::clearAndDestroy()
//
// free the memory of all the Trigger* entries by calling their destructors
// (operator delete). This should deallocate them from the CollHeap from which
// they were allocated. The list itself is also 'cleared'
// only used when triggers are allocated from the cntext heap.
// Currently triggers are allocated from the statement heap.
// See method Trigger::Heap() in file Triggers.h
void
TriggerList::clearAndDestroy()
{
Trigger *currEntry;
for (CollIndex i=0; i < entries(); i++)
{
currEntry = this->at(i);
if (currEntry != NULL)
delete currEntry;
}
this->clear();
}
//------------------------------------------------------------------------------
//
// -- class BeforeAndAfterTriggers Methods
//
// -- BeforeAndAfterTriggers::print()
//
// Debugging aid.
//
// used for debugging only
void
BeforeAndAfterTriggers::print(ostream& os,
const char *indent,
const char *title) const
{
if (getBeforeTriggers())
getBeforeTriggers()->print(os, indent, "Before Triggers: ");
if (getAfterStatementTriggers())
getAfterStatementTriggers()->print(os, indent, "After Statement Triggers: ");
if (getAfterRowTriggers())
getAfterRowTriggers()->print(os, indent, "After Row Triggers: ");
}
//
// -- BeforeAndAfterTriggers::clearAndDestroyAllEntries()
//
// free the memory of all the TriggerLists referenced by this object
//
// only used when triggers are allocated from the cntext heap
// are trigger object's get desructed explicitly
// Currently triggers are allocated from the statement heap.
// See method Trigger::Heap() in file Triggers.h for more details
void
BeforeAndAfterTriggers::clearAndDestroy()
{
if (NULL != afterRowTriggers_)
{
afterRowTriggers_->clearAndDestroy();
delete afterRowTriggers_;
}
if (NULL != afterStatementTriggers_)
{
afterStatementTriggers_->clearAndDestroy();
delete afterStatementTriggers_;
}
if (NULL != beforeTriggers_)
{
beforeTriggers_->clearAndDestroy();
delete beforeTriggers_;
}
}
//
// -- BeforeAndAfterTriggers::~BeforeAndAfterTriggers
//
// only used when triggers are allocated from the cntext heap
// are trigger object's get desructed explicitly
// Currently triggers are allocated from the statement heap.
// See method Trigger::Heap() in file Triggers.h for more details
BeforeAndAfterTriggers::~BeforeAndAfterTriggers()
{
// NOTE: if deep-destruction is required then
// call this->clearAndDestroyAllEntries() here
}
//
// -- BeforeAndAfterTriggers::entries
//
// returns the total number of triggers in all the lists together.
Lng32 BeforeAndAfterTriggers::entries() const
{
Lng32 triggerCount = 0;
// add before triggers to the total count
if (beforeTriggers_)
{
triggerCount += beforeTriggers_->entries();
}
// add after statement triggers to the total count
if (afterStatementTriggers_)
{
triggerCount += afterStatementTriggers_->entries();
}
// add after row triggers to the total count
if (afterRowTriggers_)
{
triggerCount += afterRowTriggers_->entries();
}
return triggerCount;
}
//
// -- BeforeAndAfterTriggers::addNewAfterStatementTrigger
//
// Add a new trigger into the list of after statement triggers. If the list was
// not allocated yet, it is allocated just before the insertion
// (aka lazy evaluation).
void
BeforeAndAfterTriggers::addNewAfterStatementTrigger(Trigger *newTrigger)
{
if (!afterStatementTriggers_)
{
afterStatementTriggers_ = new(CmpCommon::statementHeap())
TriggerList(CmpCommon::statementHeap());
}
afterStatementTriggers_->insert(newTrigger);
}
//
// -- BeforeAndAfterTriggers::addNewAfterRowTrigger
//
// Add a new trigger into the list of after row triggers. If the list was
// not allocated yet, it is allocated just before the insertion
// (aka lazy evaluation).
void
BeforeAndAfterTriggers::addNewAfterRowTrigger(Trigger *newTrigger)
{
if (!afterRowTriggers_)
{
afterRowTriggers_ = new(CmpCommon::statementHeap())
TriggerList(CmpCommon::statementHeap());
}
afterRowTriggers_->insert(newTrigger);
}