mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-23 04:43:03 +01:00
Major refactoring of savepoints. Beware of possible regressions.
This commit is contained in:
parent
a5fcb82173
commit
174c252e0c
@ -34,7 +34,7 @@
|
||||
#include "../common/classes/array.h"
|
||||
#include "../common/classes/ByteChunk.h"
|
||||
#include "../common/classes/Nullable.h"
|
||||
#include "../jrd/vio_proto.h"
|
||||
#include "../jrd/Savepoint.h"
|
||||
#include "../dsql/errd_proto.h"
|
||||
|
||||
namespace Jrd {
|
||||
|
@ -11091,7 +11091,7 @@ dsc* UdfCallNode::execute(thread_db* tdbb, jrd_req* request) const
|
||||
|
||||
jrd_tra* transaction = request->req_transaction;
|
||||
const SLONG savePointNumber = transaction->tra_save_point ?
|
||||
transaction->tra_save_point->sav_number : 0;
|
||||
transaction->tra_save_point->getNumber() : 0;
|
||||
|
||||
jrd_req* funcRequest = function->getStatement()->findRequest(tdbb);
|
||||
|
||||
@ -11115,10 +11115,10 @@ dsc* UdfCallNode::execute(thread_db* tdbb, jrd_req* request) const
|
||||
|
||||
// Clean up all savepoints started during execution of the procedure.
|
||||
|
||||
if (transaction != attachment->getSysTransaction())
|
||||
if (!(transaction->tra_flags & TRA_system))
|
||||
{
|
||||
while (transaction->tra_save_point &&
|
||||
transaction->tra_save_point->sav_number > savePointNumber)
|
||||
transaction->tra_save_point->getNumber() > savePointNumber)
|
||||
{
|
||||
transaction->rollforwardSavepoint(tdbb);
|
||||
}
|
||||
|
@ -223,6 +223,16 @@ namespace
|
||||
AutoSetRestore<USHORT> autoFlags;
|
||||
AutoSetRestore<USHORT> autoScopeLevel;
|
||||
};
|
||||
|
||||
class SavepointChangeMarker : public Savepoint::ChangeMarker
|
||||
{
|
||||
public:
|
||||
explicit SavepointChangeMarker(jrd_tra* transaction)
|
||||
: Savepoint::ChangeMarker(transaction->tra_flags & TRA_system ?
|
||||
NULL : transaction->tra_save_point)
|
||||
{}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@ -515,17 +525,15 @@ BlockNode* BlockNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
||||
const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState* exeState) const
|
||||
{
|
||||
jrd_tra* transaction = request->req_transaction;
|
||||
jrd_tra* sysTransaction = request->req_attachment->getSysTransaction();
|
||||
SLONG count;
|
||||
|
||||
switch (request->req_operation)
|
||||
{
|
||||
case jrd_req::req_evaluate:
|
||||
if (transaction != sysTransaction)
|
||||
if (!(transaction->tra_flags & TRA_system))
|
||||
{
|
||||
VIO_start_save_point(tdbb, transaction);
|
||||
const Savepoint* save_point = transaction->tra_save_point;
|
||||
count = save_point->sav_number;
|
||||
const Savepoint* const savepoint = Savepoint::start(transaction);
|
||||
count = savepoint->getNumber();
|
||||
*request->getImpure<SLONG>(impureOffset) = count;
|
||||
}
|
||||
return action;
|
||||
@ -540,12 +548,12 @@ const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
|
||||
// BREAK/LEAVE/CONTINUE statement in the SP/trigger code.
|
||||
// Do not perform the error handling stuff.
|
||||
|
||||
if (transaction != sysTransaction)
|
||||
if (!(transaction->tra_flags & TRA_system))
|
||||
{
|
||||
count = *request->getImpure<SLONG>(impureOffset);
|
||||
|
||||
while (transaction->tra_save_point &&
|
||||
transaction->tra_save_point->sav_number >= count)
|
||||
transaction->tra_save_point->getNumber() >= count)
|
||||
{
|
||||
transaction->rollforwardSavepoint(tdbb);
|
||||
}
|
||||
@ -559,7 +567,7 @@ const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
|
||||
if (handlers && handlers->statements.getCount() > 0)
|
||||
{
|
||||
// First of all rollback failed work
|
||||
if (transaction != sysTransaction)
|
||||
if (!(transaction->tra_flags & TRA_system))
|
||||
{
|
||||
count = *request->getImpure<SLONG>(impureOffset);
|
||||
|
||||
@ -568,20 +576,22 @@ const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
|
||||
// That's why transaction->rollbackToSavepoint() cannot be used here
|
||||
// The savepoint of this block will be dealt with below.
|
||||
// Do this only if error handlers exist. If not - leave rollbacking to caller node
|
||||
|
||||
while (transaction->tra_save_point &&
|
||||
count < transaction->tra_save_point->sav_number &&
|
||||
transaction->tra_save_point->sav_next &&
|
||||
count < transaction->tra_save_point->sav_next->sav_number)
|
||||
count < transaction->tra_save_point->getNumber() &&
|
||||
transaction->tra_save_point->getNext() &&
|
||||
count < transaction->tra_save_point->getNext()->getNumber())
|
||||
{
|
||||
transaction->rollforwardSavepoint(tdbb);
|
||||
}
|
||||
|
||||
// There can be no savepoints above the given one
|
||||
if (transaction->tra_save_point && transaction->tra_save_point->sav_number > count)
|
||||
{
|
||||
|
||||
if (transaction->tra_save_point && transaction->tra_save_point->getNumber() > count)
|
||||
transaction->rollbackSavepoint(tdbb);
|
||||
}
|
||||
|
||||
// after that we still have to have our savepoint. If not - CORE-4424/4483 is sneaking around
|
||||
fb_assert(transaction->tra_save_point && transaction->tra_save_point->sav_number == count);
|
||||
fb_assert(transaction->tra_save_point && transaction->tra_save_point->getNumber() == count);
|
||||
}
|
||||
|
||||
temp = parentStmt;
|
||||
@ -646,7 +656,7 @@ const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
|
||||
// The error is dealt with by the application, cleanup
|
||||
// this block's savepoint.
|
||||
|
||||
if (handled && transaction != sysTransaction)
|
||||
if (handled && !(transaction->tra_flags & TRA_system))
|
||||
{
|
||||
// Check that exception handlers were executed in context of right savepoint.
|
||||
// If not - mirror copy of CORE-4424 or CORE-4483 is around here.
|
||||
@ -656,10 +666,11 @@ const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
|
||||
// outer before
|
||||
// outer after (this block)
|
||||
// inner after
|
||||
// Because of this following assert is commentd out
|
||||
//fb_assert(transaction->tra_save_point && transaction->tra_save_point->sav_number == count);
|
||||
// Because of this following assert is commented out
|
||||
//fb_assert(transaction->tra_save_point && transaction->tra_save_point->getNumber() == count);
|
||||
|
||||
for (const Savepoint* save_point = transaction->tra_save_point;
|
||||
save_point && count <= save_point->sav_number;
|
||||
save_point && count <= save_point->getNumber();
|
||||
save_point = transaction->tra_save_point)
|
||||
{
|
||||
transaction->rollforwardSavepoint(tdbb);
|
||||
@ -676,13 +687,13 @@ const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
|
||||
}
|
||||
|
||||
case jrd_req::req_return:
|
||||
if (transaction != sysTransaction)
|
||||
if (!(transaction->tra_flags & TRA_system))
|
||||
{
|
||||
count = *request->getImpure<SLONG>(impureOffset);
|
||||
|
||||
// rollforward all savepoints
|
||||
for (const Savepoint* save_point = transaction->tra_save_point;
|
||||
save_point && save_point->sav_next && count <= save_point->sav_number;
|
||||
save_point && save_point->getNext() && count <= save_point->getNumber();
|
||||
save_point = transaction->tra_save_point)
|
||||
{
|
||||
transaction->rollforwardSavepoint(tdbb);
|
||||
@ -2351,7 +2362,6 @@ const StmtNode* EraseNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
|
||||
// Perform erase operation.
|
||||
const StmtNode* EraseNode::erase(thread_db* tdbb, jrd_req* request, WhichTrigger whichTrig) const
|
||||
{
|
||||
Jrd::Attachment* attachment = tdbb->getAttachment();
|
||||
jrd_tra* transaction = request->req_transaction;
|
||||
record_param* rpb = &request->req_rpb[stream];
|
||||
jrd_rel* relation = rpb->rpb_relation;
|
||||
@ -2397,8 +2407,7 @@ const StmtNode* EraseNode::erase(thread_db* tdbb, jrd_req* request, WhichTrigger
|
||||
rpb->rpb_runtime_flags &= ~RPB_refetch;
|
||||
}
|
||||
|
||||
if (transaction != attachment->getSysTransaction())
|
||||
++transaction->tra_save_point->sav_verb_count;
|
||||
SavepointChangeMarker scMarker(transaction);
|
||||
|
||||
// Handle pre-operation trigger.
|
||||
preModifyEraseTriggers(tdbb, &relation->rel_pre_erase, whichTrig, rpb, NULL, TRIGGER_DELETE);
|
||||
@ -2445,9 +2454,6 @@ const StmtNode* EraseNode::erase(thread_db* tdbb, jrd_req* request, WhichTrigger
|
||||
request->req_records_affected.bumpModified(true);
|
||||
}
|
||||
|
||||
if (transaction != attachment->getSysTransaction())
|
||||
--transaction->tra_save_point->sav_verb_count;
|
||||
|
||||
rpb->rpb_number.setValid(false);
|
||||
|
||||
return parentStmt;
|
||||
@ -2930,8 +2936,6 @@ void ExecProcedureNode::executeProcedure(thread_db* tdbb, jrd_req* request) cons
|
||||
Arg::Str(procedure->getName().identifier) << Arg::Str(procedure->getName().package));
|
||||
}
|
||||
|
||||
Jrd::Attachment* attachment = tdbb->getAttachment();
|
||||
|
||||
ULONG inMsgLength = 0;
|
||||
UCHAR* inMsg = NULL;
|
||||
|
||||
@ -2972,7 +2976,7 @@ void ExecProcedureNode::executeProcedure(thread_db* tdbb, jrd_req* request) cons
|
||||
|
||||
jrd_tra* transaction = request->req_transaction;
|
||||
const SLONG savePointNumber = transaction->tra_save_point ?
|
||||
transaction->tra_save_point->sav_number : 0;
|
||||
transaction->tra_save_point->getNumber() : 0;
|
||||
|
||||
jrd_req* procRequest = procedure->getStatement()->findRequest(tdbb);
|
||||
|
||||
@ -2994,10 +2998,10 @@ void ExecProcedureNode::executeProcedure(thread_db* tdbb, jrd_req* request) cons
|
||||
|
||||
// Clean up all savepoints started during execution of the procedure.
|
||||
|
||||
if (transaction != attachment->getSysTransaction())
|
||||
if (!(transaction->tra_flags & TRA_system))
|
||||
{
|
||||
while (transaction->tra_save_point &&
|
||||
transaction->tra_save_point->sav_number > savePointNumber)
|
||||
transaction->tra_save_point->getNumber() > savePointNumber)
|
||||
{
|
||||
transaction->rollforwardSavepoint(tdbb);
|
||||
}
|
||||
@ -3704,8 +3708,8 @@ const StmtNode* InAutonomousTransactionNode::execute(thread_db* tdbb, jrd_req* r
|
||||
request->req_auto_trans.push(org_transaction);
|
||||
impure->traNumber = transaction->tra_number;
|
||||
|
||||
VIO_start_save_point(tdbb, transaction);
|
||||
impure->savNumber = transaction->tra_save_point->sav_number;
|
||||
const Savepoint* const savepoint = Savepoint::start(transaction);
|
||||
impure->savNumber = savepoint->getNumber();
|
||||
|
||||
if (!(attachment->att_flags & ATT_no_db_triggers))
|
||||
{
|
||||
@ -3717,7 +3721,7 @@ const StmtNode* InAutonomousTransactionNode::execute(thread_db* tdbb, jrd_req* r
|
||||
}
|
||||
|
||||
jrd_tra* transaction = request->req_transaction;
|
||||
fb_assert(transaction && transaction != attachment->getSysTransaction());
|
||||
fb_assert(transaction && !(transaction->tra_flags & TRA_system));
|
||||
|
||||
if (!impure->traNumber)
|
||||
return parentStmt;
|
||||
@ -3734,8 +3738,8 @@ const StmtNode* InAutonomousTransactionNode::execute(thread_db* tdbb, jrd_req* r
|
||||
}
|
||||
|
||||
if (transaction->tra_save_point &&
|
||||
!(transaction->tra_save_point->sav_flags & SAV_user) &&
|
||||
!transaction->tra_save_point->sav_verb_count)
|
||||
transaction->tra_save_point->isSystem() &&
|
||||
transaction->tra_save_point->isChanging())
|
||||
{
|
||||
transaction->rollforwardSavepoint(tdbb);
|
||||
}
|
||||
@ -3759,8 +3763,8 @@ const StmtNode* InAutonomousTransactionNode::execute(thread_db* tdbb, jrd_req* r
|
||||
}
|
||||
|
||||
if (transaction->tra_save_point &&
|
||||
!(transaction->tra_save_point->sav_flags & SAV_user) &&
|
||||
!transaction->tra_save_point->sav_verb_count)
|
||||
transaction->tra_save_point->isSystem() &&
|
||||
transaction->tra_save_point->isChanging())
|
||||
{
|
||||
transaction->rollforwardSavepoint(tdbb);
|
||||
}
|
||||
@ -4697,18 +4701,17 @@ StmtNode* ForNode::pass2(thread_db* tdbb, CompilerScratch* csb)
|
||||
const StmtNode* ForNode::execute(thread_db* tdbb, jrd_req* request, ExeState* /*exeState*/) const
|
||||
{
|
||||
jrd_tra* transaction = request->req_transaction;
|
||||
jrd_tra* sysTransaction = request->req_attachment->getSysTransaction();
|
||||
|
||||
switch (request->req_operation)
|
||||
{
|
||||
case jrd_req::req_evaluate:
|
||||
*request->getImpure<SLONG>(impureOffset) = 0;
|
||||
if (transaction != sysTransaction &&
|
||||
transaction->tra_save_point && transaction->tra_save_point->sav_verb_actions)
|
||||
if (!(transaction->tra_flags & TRA_system) &&
|
||||
transaction->tra_save_point &&
|
||||
transaction->tra_save_point->hasChanges())
|
||||
{
|
||||
VIO_start_save_point(tdbb, transaction);
|
||||
const Savepoint* save_point = transaction->tra_save_point;
|
||||
*request->getImpure<SLONG>(impureOffset) = save_point->sav_number;
|
||||
const Savepoint* const savepoint = Savepoint::start(transaction);
|
||||
*request->getImpure<SLONG>(impureOffset) = savepoint->getNumber();
|
||||
}
|
||||
cursor->open(tdbb);
|
||||
request->req_records_affected.clear();
|
||||
@ -4750,9 +4753,12 @@ const StmtNode* ForNode::execute(thread_db* tdbb, jrd_req* request, ExeState* /*
|
||||
if (sav_number)
|
||||
{
|
||||
while (transaction->tra_save_point &&
|
||||
transaction->tra_save_point->sav_number >= sav_number)
|
||||
transaction->tra_save_point->getNumber() >= sav_number)
|
||||
{
|
||||
VIO_verb_cleanup(tdbb, transaction);
|
||||
if (transaction->tra_save_point->isChanging()) // we must rollback this savepoint
|
||||
transaction->rollbackSavepoint(tdbb);
|
||||
else
|
||||
transaction->rollforwardSavepoint(tdbb);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6107,7 +6113,6 @@ const StmtNode* ModifyNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
|
||||
// Execute a MODIFY statement.
|
||||
const StmtNode* ModifyNode::modify(thread_db* tdbb, jrd_req* request, WhichTrigger whichTrig) const
|
||||
{
|
||||
Jrd::Attachment* attachment = tdbb->getAttachment();
|
||||
jrd_tra* transaction = request->req_transaction;
|
||||
impure_state* impure = request->getImpure<impure_state>(impureOffset);
|
||||
|
||||
@ -6142,8 +6147,7 @@ const StmtNode* ModifyNode::modify(thread_db* tdbb, jrd_req* request, WhichTrigg
|
||||
// varchar field whose tail may contain garbage.
|
||||
cleanupRpb(tdbb, newRpb);
|
||||
|
||||
if (transaction != attachment->getSysTransaction())
|
||||
++transaction->tra_save_point->sav_verb_count;
|
||||
SavepointChangeMarker scMarker(transaction);
|
||||
|
||||
preModifyEraseTriggers(tdbb, &relation->rel_pre_modify, whichTrig, orgRpb, newRpb,
|
||||
TRIGGER_UPDATE);
|
||||
@ -6177,9 +6181,6 @@ const StmtNode* ModifyNode::modify(thread_db* tdbb, jrd_req* request, WhichTrigg
|
||||
if (!relation->rel_file && !relation->rel_view_rse && !relation->isVirtual())
|
||||
IDX_modify_check_constraints(tdbb, orgRpb, newRpb, transaction);
|
||||
|
||||
if (transaction != attachment->getSysTransaction())
|
||||
--transaction->tra_save_point->sav_verb_count;
|
||||
|
||||
// CVC: Increment the counter only if we called VIO/EXT_modify() and
|
||||
// we were successful.
|
||||
if (!(request->req_view_flags & req_first_modify_return))
|
||||
@ -6959,7 +6960,6 @@ const StmtNode* StoreNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
|
||||
// Execute a STORE statement.
|
||||
const StmtNode* StoreNode::store(thread_db* tdbb, jrd_req* request, WhichTrigger whichTrig) const
|
||||
{
|
||||
Jrd::Attachment* attachment = tdbb->getAttachment();
|
||||
jrd_tra* transaction = request->req_transaction;
|
||||
impure_state* impure = request->getImpure<impure_state>(impureOffset);
|
||||
|
||||
@ -6979,79 +6979,76 @@ const StmtNode* StoreNode::store(thread_db* tdbb, jrd_req* request, WhichTrigger
|
||||
break;
|
||||
|
||||
case jrd_req::req_return:
|
||||
if (impure->sta_state)
|
||||
return parentStmt;
|
||||
|
||||
if (transaction != attachment->getSysTransaction())
|
||||
++transaction->tra_save_point->sav_verb_count;
|
||||
|
||||
if (relation->rel_pre_store && whichTrig != POST_TRIG)
|
||||
if (!impure->sta_state)
|
||||
{
|
||||
EXE_execute_triggers(tdbb, &relation->rel_pre_store, NULL, rpb,
|
||||
TRIGGER_INSERT, PRE_TRIG);
|
||||
}
|
||||
SavepointChangeMarker scMarker(transaction);
|
||||
|
||||
if (validations.hasData())
|
||||
validateExpressions(tdbb, validations);
|
||||
if (relation->rel_pre_store && whichTrig != POST_TRIG)
|
||||
{
|
||||
EXE_execute_triggers(tdbb, &relation->rel_pre_store, NULL, rpb,
|
||||
TRIGGER_INSERT, PRE_TRIG);
|
||||
}
|
||||
|
||||
// For optimum on-disk record compression, zero all unassigned
|
||||
// fields. In addition, zero the tail of assigned varying fields
|
||||
// so that previous remnants don't defeat compression efficiency.
|
||||
if (validations.hasData())
|
||||
validateExpressions(tdbb, validations);
|
||||
|
||||
// CVC: The code that was here was moved to its own routine: cleanupRpb()
|
||||
// and replaced by the call shown below.
|
||||
// For optimum on-disk record compression, zero all unassigned
|
||||
// fields. In addition, zero the tail of assigned varying fields
|
||||
// so that previous remnants don't defeat compression efficiency.
|
||||
|
||||
cleanupRpb(tdbb, rpb);
|
||||
// CVC: The code that was here was moved to its own routine: cleanupRpb()
|
||||
// and replaced by the call shown below.
|
||||
|
||||
if (relation->rel_file)
|
||||
EXT_store(tdbb, rpb);
|
||||
else if (relation->isVirtual())
|
||||
VirtualTable::store(tdbb, rpb);
|
||||
else if (!relation->rel_view_rse)
|
||||
{
|
||||
VIO_store(tdbb, rpb, transaction);
|
||||
IDX_store(tdbb, rpb, transaction);
|
||||
}
|
||||
cleanupRpb(tdbb, rpb);
|
||||
|
||||
rpb->rpb_number.setValid(true);
|
||||
if (relation->rel_file)
|
||||
EXT_store(tdbb, rpb);
|
||||
else if (relation->isVirtual())
|
||||
VirtualTable::store(tdbb, rpb);
|
||||
else if (!relation->rel_view_rse)
|
||||
{
|
||||
VIO_store(tdbb, rpb, transaction);
|
||||
IDX_store(tdbb, rpb, transaction);
|
||||
}
|
||||
|
||||
if (relation->rel_post_store && whichTrig != PRE_TRIG)
|
||||
{
|
||||
EXE_execute_triggers(tdbb, &relation->rel_post_store, NULL, rpb,
|
||||
TRIGGER_INSERT, POST_TRIG);
|
||||
}
|
||||
rpb->rpb_number.setValid(true);
|
||||
|
||||
// CVC: Increment the counter only if we called VIO/EXT_store() and we were successful.
|
||||
if (!(request->req_view_flags & req_first_store_return))
|
||||
{
|
||||
request->req_view_flags |= req_first_store_return;
|
||||
if (relation->rel_view_rse)
|
||||
request->req_top_view_store = relation;
|
||||
}
|
||||
if (relation->rel_post_store && whichTrig != PRE_TRIG)
|
||||
{
|
||||
EXE_execute_triggers(tdbb, &relation->rel_post_store, NULL, rpb,
|
||||
TRIGGER_INSERT, POST_TRIG);
|
||||
}
|
||||
|
||||
if (relation == request->req_top_view_store)
|
||||
{
|
||||
if (!subStore && (whichTrig == ALL_TRIGS || whichTrig == POST_TRIG))
|
||||
// CVC: Increment the counter only if we called VIO/EXT_store() and we were successful.
|
||||
if (!(request->req_view_flags & req_first_store_return))
|
||||
{
|
||||
request->req_view_flags |= req_first_store_return;
|
||||
if (relation->rel_view_rse)
|
||||
request->req_top_view_store = relation;
|
||||
}
|
||||
|
||||
if (relation == request->req_top_view_store)
|
||||
{
|
||||
if (!subStore && (whichTrig == ALL_TRIGS || whichTrig == POST_TRIG))
|
||||
{
|
||||
request->req_records_inserted++;
|
||||
request->req_records_affected.bumpModified(true);
|
||||
}
|
||||
}
|
||||
else if (relation->rel_file || !relation->rel_view_rse)
|
||||
{
|
||||
request->req_records_inserted++;
|
||||
request->req_records_affected.bumpModified(true);
|
||||
}
|
||||
}
|
||||
else if (relation->rel_file || !relation->rel_view_rse)
|
||||
{
|
||||
request->req_records_inserted++;
|
||||
request->req_records_affected.bumpModified(true);
|
||||
}
|
||||
|
||||
if (transaction != attachment->getSysTransaction())
|
||||
--transaction->tra_save_point->sav_verb_count;
|
||||
|
||||
if (statement2)
|
||||
{
|
||||
impure->sta_state = 1;
|
||||
request->req_operation = jrd_req::req_evaluate;
|
||||
return statement2;
|
||||
if (statement2)
|
||||
{
|
||||
impure->sta_state = 1;
|
||||
request->req_operation = jrd_req::req_evaluate;
|
||||
return statement2;
|
||||
}
|
||||
}
|
||||
// fall into
|
||||
|
||||
default:
|
||||
return parentStmt;
|
||||
@ -7139,83 +7136,75 @@ const StmtNode* UserSavepointNode::execute(thread_db* tdbb, jrd_req* request, Ex
|
||||
jrd_tra* transaction = request->req_transaction;
|
||||
|
||||
if (request->req_operation == jrd_req::req_evaluate &&
|
||||
transaction != request->req_attachment->getSysTransaction())
|
||||
!(transaction->tra_flags & TRA_system))
|
||||
{
|
||||
// Skip the savepoint created by EXE_start
|
||||
Savepoint* savepoint = transaction->tra_save_point->sav_next;
|
||||
Savepoint* previous = transaction->tra_save_point;
|
||||
Savepoint* const previous = transaction->tra_save_point;
|
||||
|
||||
// Find savepoint
|
||||
bool found = false;
|
||||
while (true)
|
||||
Savepoint* savepoint = NULL;
|
||||
|
||||
for (Savepoint::Iterator iter(previous); *iter; ++iter)
|
||||
{
|
||||
if (!savepoint || !(savepoint->sav_flags & SAV_user))
|
||||
Savepoint* const current = *iter;
|
||||
|
||||
if (current == previous)
|
||||
continue;
|
||||
|
||||
if (current->isSystem())
|
||||
break;
|
||||
|
||||
if (name == savepoint->sav_name)
|
||||
if (current->getName() == name)
|
||||
{
|
||||
found = true;
|
||||
savepoint = current;
|
||||
break;
|
||||
}
|
||||
|
||||
previous = savepoint;
|
||||
savepoint = savepoint->sav_next;
|
||||
}
|
||||
|
||||
if (!found && command != CMD_SET)
|
||||
if (!savepoint && command != CMD_SET)
|
||||
ERR_post(Arg::Gds(isc_invalid_savepoint) << Arg::Str(name));
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case CMD_SET:
|
||||
// Release the savepoint
|
||||
if (found)
|
||||
{
|
||||
savepoint->rollforward(tdbb);
|
||||
previous->sav_next = savepoint->sav_next;
|
||||
savepoint->sav_next = transaction->tra_save_free;
|
||||
transaction->tra_save_free = savepoint;
|
||||
}
|
||||
if (savepoint)
|
||||
savepoint->rollforward(tdbb, previous);
|
||||
|
||||
// Use the savepoint created by EXE_start
|
||||
transaction->tra_save_point->sav_flags |= SAV_user;
|
||||
transaction->tra_save_point->sav_name = name;
|
||||
transaction->tra_save_point->setName(name);
|
||||
break;
|
||||
|
||||
case CMD_RELEASE_ONLY:
|
||||
{
|
||||
// Release the savepoint
|
||||
savepoint->rollforward(tdbb);
|
||||
previous->sav_next = savepoint->sav_next;
|
||||
savepoint->sav_next = transaction->tra_save_free;
|
||||
transaction->tra_save_free = savepoint;
|
||||
savepoint->rollforward(tdbb, previous);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_RELEASE:
|
||||
{
|
||||
const SLONG sav_number = savepoint->sav_number;
|
||||
const SLONG sav_number = savepoint->getNumber();
|
||||
|
||||
// Release the savepoint and all subsequent ones
|
||||
while (transaction->tra_save_point &&
|
||||
transaction->tra_save_point->sav_number >= sav_number)
|
||||
transaction->tra_save_point->getNumber() >= sav_number)
|
||||
{
|
||||
transaction->rollforwardSavepoint(tdbb);
|
||||
}
|
||||
|
||||
// Restore the savepoint initially created by EXE_start
|
||||
VIO_start_save_point(tdbb, transaction);
|
||||
Savepoint::start(transaction);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_ROLLBACK:
|
||||
{
|
||||
transaction->rollbackToSavepoint(tdbb, savepoint->sav_number);
|
||||
transaction->rollbackToSavepoint(tdbb, savepoint->getNumber());
|
||||
|
||||
// Now set the savepoint again to allow to return to it later
|
||||
VIO_start_save_point(tdbb, transaction);
|
||||
transaction->tra_save_point->sav_flags |= SAV_user;
|
||||
transaction->tra_save_point->sav_name = name;
|
||||
Savepoint* const savepoint = Savepoint::start(transaction);
|
||||
savepoint->setName(name);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -7817,7 +7806,6 @@ void SavePointNode::genBlr(DsqlCompilerScratch* dsqlScratch)
|
||||
const StmtNode* SavePointNode::execute(thread_db* tdbb, jrd_req* request, ExeState* exeState) const
|
||||
{
|
||||
jrd_tra* transaction = request->req_transaction;
|
||||
jrd_tra* sysTransaction = request->req_attachment->getSysTransaction();
|
||||
|
||||
switch (blrOp)
|
||||
{
|
||||
@ -7825,8 +7813,8 @@ const StmtNode* SavePointNode::execute(thread_db* tdbb, jrd_req* request, ExeSta
|
||||
if (request->req_operation == jrd_req::req_evaluate)
|
||||
{
|
||||
// Start a save point.
|
||||
if (transaction != sysTransaction)
|
||||
VIO_start_save_point(tdbb, transaction);
|
||||
if (!(transaction->tra_flags & TRA_system))
|
||||
Savepoint::start(transaction);
|
||||
|
||||
request->req_operation = jrd_req::req_return;
|
||||
}
|
||||
@ -7837,7 +7825,7 @@ const StmtNode* SavePointNode::execute(thread_db* tdbb, jrd_req* request, ExeSta
|
||||
request->req_operation == jrd_req::req_unwind)
|
||||
{
|
||||
// If any requested modify/delete/insert ops have completed, forget them.
|
||||
if (transaction != sysTransaction)
|
||||
if (!(transaction->tra_flags & TRA_system))
|
||||
{
|
||||
// If an error is still pending when the savepoint is supposed to end, then the
|
||||
// application didn't handle the error and the savepoint should be undone.
|
||||
|
@ -36,12 +36,10 @@ enum BlockType
|
||||
type_irl,
|
||||
type_idl,
|
||||
type_sdw,
|
||||
type_vct,
|
||||
type_blf,
|
||||
type_arr,
|
||||
type_map,
|
||||
type_prm,
|
||||
type_sav,
|
||||
type_idb,
|
||||
type_tpc,
|
||||
type_svc,
|
||||
|
629
src/jrd/Savepoint.cpp
Normal file
629
src/jrd/Savepoint.cpp
Normal file
@ -0,0 +1,629 @@
|
||||
/*
|
||||
* The contents of this file are subject to the Interbase Public
|
||||
* License Version 1.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.Inprise.com/IPL.html
|
||||
*
|
||||
* Software distributed under the License is distributed on an
|
||||
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing
|
||||
* rights and limitations under the License.
|
||||
*
|
||||
* The Original Code was created by Inprise Corporation
|
||||
* and its predecessors. Portions created by Inprise Corporation are
|
||||
* Copyright (C) Inprise Corporation.
|
||||
*
|
||||
* All Rights Reserved.
|
||||
* Contributor(s): ______________________________________.
|
||||
*/
|
||||
|
||||
#include "firebird.h"
|
||||
#include "../common/gdsassert.h"
|
||||
#include "../jrd/tra.h"
|
||||
#include "../jrd/blb_proto.h"
|
||||
#include "../jrd/cch_proto.h"
|
||||
#include "../jrd/dfw_proto.h"
|
||||
#include "../jrd/dpm_proto.h"
|
||||
#include "../jrd/idx_proto.h"
|
||||
#include "../jrd/vio_proto.h"
|
||||
|
||||
#include "Savepoint.h"
|
||||
|
||||
using namespace Firebird;
|
||||
using namespace Jrd;
|
||||
|
||||
|
||||
// UndoItem implementation
|
||||
|
||||
UndoItem::UndoItem(jrd_tra* transaction, RecordNumber recordNumber, const Record* record)
|
||||
: m_number(recordNumber.getValue()), m_format(record->getFormat())
|
||||
{
|
||||
fb_assert(m_format);
|
||||
m_offset = transaction->getUndoSpace()->allocateSpace(m_format->fmt_length);
|
||||
transaction->getUndoSpace()->write(m_offset, record->getData(), record->getLength());
|
||||
}
|
||||
|
||||
Record* UndoItem::setupRecord(jrd_tra* transaction) const
|
||||
{
|
||||
if (m_format)
|
||||
{
|
||||
Record* const record = transaction->getUndoRecord(m_format);
|
||||
transaction->getUndoSpace()->read(m_offset, record->getData(), record->getLength());
|
||||
return record;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void UndoItem::release(jrd_tra* transaction)
|
||||
{
|
||||
if (m_format)
|
||||
{
|
||||
transaction->getUndoSpace()->releaseSpace(m_offset, m_format->fmt_length);
|
||||
m_format = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// VerbAction implementation
|
||||
|
||||
void VerbAction::garbageCollectIdxLite(thread_db* tdbb, jrd_tra* transaction, SINT64 recordNumber,
|
||||
VerbAction* nextAction, Record* goingRecord)
|
||||
{
|
||||
// Clean up index entries and referenced BLOBs.
|
||||
// This routine uses smaller set of staying record than original VIO_garbage_collect_idx().
|
||||
//
|
||||
// Notes:
|
||||
//
|
||||
// This speed trick is possible only because btr.cpp:insert_node() allows duplicate nodes
|
||||
// which work as an index entry reference counter.
|
||||
|
||||
record_param rpb;
|
||||
rpb.rpb_relation = vct_relation;
|
||||
rpb.rpb_number.setValue(recordNumber);
|
||||
rpb.rpb_record = NULL;
|
||||
rpb.getWindow(tdbb).win_flags = 0;
|
||||
rpb.rpb_transaction_nr = transaction->tra_number;
|
||||
|
||||
Record* next_ver;
|
||||
AutoUndoRecord undo_next_ver(transaction->findNextUndo(this, vct_relation, recordNumber));
|
||||
AutoPtr<Record> real_next_ver;
|
||||
|
||||
next_ver = undo_next_ver;
|
||||
|
||||
if (!DPM_get(tdbb, &rpb, LCK_read))
|
||||
BUGCHECK(186); // msg 186 record disappeared
|
||||
else
|
||||
{
|
||||
if (next_ver || (rpb.rpb_flags & rpb_deleted))
|
||||
CCH_RELEASE(tdbb, &rpb.getWindow(tdbb));
|
||||
else
|
||||
{
|
||||
VIO_data(tdbb, &rpb, transaction->tra_pool);
|
||||
next_ver = real_next_ver = rpb.rpb_record;
|
||||
}
|
||||
}
|
||||
|
||||
if (rpb.rpb_transaction_nr != transaction->tra_number)
|
||||
BUGCHECK(185); // msg 185 wrong record version
|
||||
|
||||
Record* prev_ver = NULL;
|
||||
AutoUndoRecord undo_prev_ver;
|
||||
AutoPtr<Record> real_prev_ver;
|
||||
|
||||
if (nextAction && nextAction->vct_undo && nextAction->vct_undo->locate(recordNumber))
|
||||
{
|
||||
prev_ver = undo_prev_ver = nextAction->vct_undo->current().setupRecord(transaction);
|
||||
}
|
||||
else if (rpb.rpb_b_page) // previous version exists and we have to find it in a hard way
|
||||
{
|
||||
record_param temp = rpb;
|
||||
temp.rpb_record = NULL;
|
||||
temp.rpb_page = rpb.rpb_b_page;
|
||||
temp.rpb_line = rpb.rpb_b_line;
|
||||
|
||||
if (!DPM_fetch(tdbb, &temp, LCK_read))
|
||||
BUGCHECK(291); // Back version disappeared
|
||||
|
||||
if (temp.rpb_flags & rpb_deleted)
|
||||
CCH_RELEASE(tdbb, &temp.getWindow(tdbb));
|
||||
else
|
||||
VIO_data(tdbb, &temp, transaction->tra_pool);
|
||||
|
||||
prev_ver = real_prev_ver = temp.rpb_record;
|
||||
}
|
||||
|
||||
RecordStack going, staying;
|
||||
going.push(goingRecord);
|
||||
|
||||
if (prev_ver)
|
||||
staying.push(prev_ver);
|
||||
|
||||
if (next_ver)
|
||||
staying.push(next_ver);
|
||||
|
||||
IDX_garbage_collect(tdbb, &rpb, going, staying);
|
||||
BLB_garbage_collect(tdbb, going, staying, rpb.rpb_page, vct_relation);
|
||||
}
|
||||
|
||||
void VerbAction::mergeTo(thread_db* tdbb, jrd_tra* transaction, VerbAction* nextAction)
|
||||
{
|
||||
// Post bitmap of modified records and undo data to the next savepoint.
|
||||
//
|
||||
// Notes:
|
||||
//
|
||||
// If previous savepoint already touched a record, undo data must be dropped and
|
||||
// all BLOBs it refers to should be cleaned out because under no circumstances
|
||||
// this undo data can become an active record.
|
||||
|
||||
// Merge undo records first
|
||||
|
||||
if (vct_undo && vct_undo->getFirst())
|
||||
{
|
||||
do
|
||||
{
|
||||
UndoItem& item = vct_undo->current();
|
||||
|
||||
if (item.hasData()) // this item wasn't released yet
|
||||
{
|
||||
const SINT64 recordNumber = item.generate(NULL, item);
|
||||
|
||||
if (nextAction && !(RecordBitmap::test(nextAction->vct_records, recordNumber)))
|
||||
{
|
||||
if (!nextAction->vct_undo)
|
||||
{
|
||||
nextAction->vct_undo =
|
||||
FB_NEW_POOL(*transaction->tra_pool) UndoItemTree(*transaction->tra_pool);
|
||||
// We cannot just push whole current undo items list, some items still may be released
|
||||
}
|
||||
else
|
||||
{
|
||||
if (nextAction->vct_undo->locate(recordNumber))
|
||||
{
|
||||
// It looks like something went wrong on previous loop and undo record
|
||||
// was moved to next action but record bit in bitmap wasn't set
|
||||
fb_assert(false);
|
||||
item.clear();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
nextAction->vct_undo->add(item);
|
||||
item.clear(); // Do not release undo data, it now belongs to next action
|
||||
continue;
|
||||
}
|
||||
|
||||
// garbage cleanup and release
|
||||
// because going version for sure has all index entries successfully set up (in contrast with undo)
|
||||
// we can use lightweigth version of garbage collection without collection of full staying list
|
||||
|
||||
AutoUndoRecord this_ver(item.setupRecord(transaction));
|
||||
|
||||
garbageCollectIdxLite(tdbb, transaction, recordNumber, nextAction, this_ver);
|
||||
|
||||
item.release(transaction);
|
||||
}
|
||||
} while (vct_undo->getNext());
|
||||
|
||||
delete vct_undo;
|
||||
vct_undo = NULL;
|
||||
}
|
||||
|
||||
// Now merge bitmap
|
||||
|
||||
if (nextAction)
|
||||
{
|
||||
if (nextAction->vct_records)
|
||||
{
|
||||
RecordBitmap** bm_or = RecordBitmap::bit_or(&vct_records, &nextAction->vct_records);
|
||||
|
||||
if (*bm_or == vct_records) // if next bitmap has been merged into this bitmap - swap them
|
||||
{
|
||||
RecordBitmap* temp = nextAction->vct_records;
|
||||
nextAction->vct_records = vct_records;
|
||||
vct_records = temp;
|
||||
}
|
||||
}
|
||||
else // just push current bitmap as is
|
||||
{
|
||||
nextAction->vct_records = vct_records;
|
||||
vct_records = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
release(transaction);
|
||||
}
|
||||
|
||||
void VerbAction::undo(thread_db* tdbb, jrd_tra* transaction)
|
||||
{
|
||||
// Undo changes recorded for this verb action.
|
||||
// After that, clear the verb action and prepare it for later reuse.
|
||||
|
||||
record_param rpb;
|
||||
rpb.rpb_relation = vct_relation;
|
||||
rpb.rpb_number.setValue(BOF_NUMBER);
|
||||
rpb.rpb_record = NULL;
|
||||
rpb.getWindow(tdbb).win_flags = 0;
|
||||
rpb.rpb_transaction_nr = transaction->tra_number;
|
||||
|
||||
RecordBitmap::Accessor accessor(vct_records);
|
||||
if (accessor.getFirst())
|
||||
{
|
||||
do
|
||||
{
|
||||
rpb.rpb_number.setValue(accessor.current());
|
||||
|
||||
const bool have_undo = vct_undo && vct_undo->locate(rpb.rpb_number.getValue());
|
||||
|
||||
if (!DPM_get(tdbb, &rpb, LCK_read))
|
||||
BUGCHECK(186); // msg 186 record disappeared
|
||||
|
||||
if (have_undo && !(rpb.rpb_flags & rpb_deleted))
|
||||
VIO_data(tdbb, &rpb, transaction->tra_pool);
|
||||
else
|
||||
CCH_RELEASE(tdbb, &rpb.getWindow(tdbb));
|
||||
|
||||
if (rpb.rpb_transaction_nr != transaction->tra_number)
|
||||
BUGCHECK(185); // msg 185 wrong record version
|
||||
|
||||
if (!have_undo)
|
||||
VIO_backout(tdbb, &rpb, transaction);
|
||||
else
|
||||
{
|
||||
AutoUndoRecord record(vct_undo->current().setupRecord(transaction));
|
||||
|
||||
Record* const save_record = rpb.rpb_record;
|
||||
record_param new_rpb = rpb;
|
||||
|
||||
if (rpb.rpb_flags & rpb_deleted)
|
||||
rpb.rpb_record = NULL;
|
||||
|
||||
new_rpb.rpb_record = record;
|
||||
new_rpb.rpb_address = record->getData();
|
||||
new_rpb.rpb_length = record->getLength();
|
||||
new_rpb.rpb_flags = 0;
|
||||
|
||||
Record* const dead_record = rpb.rpb_record;
|
||||
|
||||
// This record will be in staying list twice. Ignorable overhead.
|
||||
VIO_update_in_place(tdbb, transaction, &rpb, &new_rpb);
|
||||
|
||||
if (dead_record)
|
||||
{
|
||||
rpb.rpb_record = NULL; // VIO_garbage_collect_idx will play with this record dirty tricks
|
||||
VIO_garbage_collect_idx(tdbb, transaction, &rpb, dead_record);
|
||||
}
|
||||
|
||||
rpb.rpb_record = save_record;
|
||||
}
|
||||
} while (accessor.getNext());
|
||||
|
||||
delete rpb.rpb_record;
|
||||
}
|
||||
|
||||
release(transaction);
|
||||
}
|
||||
|
||||
void VerbAction::release(jrd_tra* transaction)
|
||||
{
|
||||
// Release resources used by this verb action
|
||||
|
||||
RecordBitmap::reset(vct_records);
|
||||
|
||||
if (vct_undo)
|
||||
{
|
||||
if (vct_undo->getFirst())
|
||||
{
|
||||
do {
|
||||
vct_undo->current().release(transaction);
|
||||
} while (vct_undo->getNext());
|
||||
}
|
||||
|
||||
delete vct_undo;
|
||||
vct_undo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Savepoint implementation
|
||||
|
||||
Savepoint* Savepoint::start(jrd_tra* transaction, bool root)
|
||||
{
|
||||
// Start a new savepoint. Reuse some priorly allocated one, if exists.
|
||||
|
||||
Savepoint* savepoint = transaction->tra_save_free;
|
||||
|
||||
if (savepoint)
|
||||
transaction->tra_save_free = savepoint->m_next;
|
||||
else
|
||||
savepoint = FB_NEW_POOL(*transaction->tra_pool) Savepoint(transaction);
|
||||
|
||||
savepoint->m_number = ++transaction->tra_save_point_number;
|
||||
savepoint->m_flags = root ? SAV_root : 0;
|
||||
|
||||
savepoint->m_next = transaction->tra_save_point;
|
||||
transaction->tra_save_point = savepoint;
|
||||
|
||||
return savepoint;
|
||||
}
|
||||
|
||||
VerbAction* Savepoint::createAction(jrd_rel* relation)
|
||||
{
|
||||
// Create action for the given relation. If it already exists, just return.
|
||||
|
||||
VerbAction* action = getAction(relation);
|
||||
|
||||
if (!action)
|
||||
{
|
||||
if ( (action = m_freeActions) )
|
||||
m_freeActions = action->vct_next;
|
||||
else
|
||||
action = FB_NEW_POOL(*m_transaction->tra_pool) VerbAction();
|
||||
|
||||
action->vct_next = m_actions;
|
||||
m_actions = action;
|
||||
|
||||
action->vct_relation = relation;
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
|
||||
void Savepoint::cleanupTempData()
|
||||
{
|
||||
// Find all global temporary tables with DELETE ROWS action
|
||||
// and release their undo data
|
||||
|
||||
for (VerbAction* action = m_actions; action; action = action->vct_next)
|
||||
{
|
||||
if (action->vct_relation->rel_flags & REL_temp_tran)
|
||||
{
|
||||
RecordBitmap::reset(action->vct_records);
|
||||
|
||||
if (action->vct_undo)
|
||||
{
|
||||
if (action->vct_undo->getFirst())
|
||||
{
|
||||
do
|
||||
{
|
||||
action->vct_undo->current().release(m_transaction);
|
||||
} while (action->vct_undo->getNext());
|
||||
}
|
||||
|
||||
delete action->vct_undo;
|
||||
action->vct_undo = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Savepoint* Savepoint::rollback(thread_db* tdbb, Savepoint* prior)
|
||||
{
|
||||
// Undo changes made in this savepoint.
|
||||
// Perform index and BLOB cleanup if needed.
|
||||
// At the exit savepoint is clear and safe to reuse.
|
||||
|
||||
jrd_tra* const old_tran = tdbb->getTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
DFW_delete_deferred(m_transaction, m_number);
|
||||
m_flags &= ~SAV_force_dfw;
|
||||
|
||||
tdbb->tdbb_flags |= TDBB_verb_cleanup;
|
||||
tdbb->setTransaction(m_transaction);
|
||||
|
||||
while (m_actions)
|
||||
{
|
||||
m_actions->undo(tdbb, m_transaction);
|
||||
releaseAction(m_actions);
|
||||
}
|
||||
|
||||
tdbb->setTransaction(old_tran);
|
||||
tdbb->tdbb_flags &= ~TDBB_verb_cleanup;
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
Arg::StatusVector error(ex);
|
||||
tdbb->setTransaction(old_tran);
|
||||
tdbb->tdbb_flags &= ~TDBB_verb_cleanup;
|
||||
m_transaction->tra_flags |= TRA_invalidated;
|
||||
error.prepend(Arg::Gds(isc_savepoint_backout_err));
|
||||
error.raise();
|
||||
}
|
||||
|
||||
return release(prior);
|
||||
}
|
||||
|
||||
Savepoint* Savepoint::rollforward(thread_db* tdbb, Savepoint* prior)
|
||||
{
|
||||
// Merge changes made in this savepoint into next one.
|
||||
// Perform index and BLOB cleanup if needed.
|
||||
// At the exit savepoint is clear and safe to reuse.
|
||||
|
||||
jrd_tra* const old_tran = tdbb->getTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
// If the current to-be-cleaned-up savepoint is very big, and the next
|
||||
// level savepoint is the transaction level savepoint, then get rid of
|
||||
// the transaction level savepoint now (instead of after making the
|
||||
// transaction level savepoint very very big).
|
||||
|
||||
if (m_next && m_next->isRoot() && this->isLarge())
|
||||
{
|
||||
fb_assert(!m_next->m_next); // check that transaction savepoint is the last in list
|
||||
// get rid of tx-level savepoint
|
||||
m_next->rollforward(tdbb);
|
||||
m_next->release();
|
||||
m_next = NULL;
|
||||
}
|
||||
|
||||
// Cleanup/merge deferred work/event post
|
||||
|
||||
if (m_actions || (m_flags & SAV_force_dfw))
|
||||
{
|
||||
DFW_merge_work(m_transaction, m_number, m_next ? m_next->m_number : 0);
|
||||
|
||||
if (m_next && (m_flags & SAV_force_dfw))
|
||||
m_next->m_flags |= SAV_force_dfw;
|
||||
|
||||
m_flags &= ~SAV_force_dfw;
|
||||
}
|
||||
|
||||
tdbb->tdbb_flags |= TDBB_verb_cleanup;
|
||||
tdbb->setTransaction(m_transaction);
|
||||
|
||||
while (m_actions)
|
||||
{
|
||||
VerbAction* nextAction = NULL;
|
||||
|
||||
if (m_next)
|
||||
{
|
||||
nextAction = m_next->getAction(m_actions->vct_relation);
|
||||
|
||||
if (!nextAction) // next savepoint didn't touch this table yet - send whole action
|
||||
{
|
||||
propagateAction(m_actions);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// No luck, merge actions in a slow way
|
||||
m_actions->mergeTo(tdbb, m_transaction, nextAction);
|
||||
releaseAction(m_actions);
|
||||
}
|
||||
|
||||
tdbb->setTransaction(old_tran);
|
||||
tdbb->tdbb_flags &= ~TDBB_verb_cleanup;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
m_transaction->tra_flags |= TRA_invalidated;
|
||||
tdbb->setTransaction(old_tran);
|
||||
tdbb->tdbb_flags &= ~TDBB_verb_cleanup;
|
||||
throw;
|
||||
}
|
||||
|
||||
// If the only remaining savepoint is the 'transaction-level' savepoint
|
||||
// that was started by TRA_start, then check if it hasn't grown out of
|
||||
// bounds yet. If it has, then give up on this transaction-level savepoint.
|
||||
if (m_next && m_next->isRoot() && m_next->isLarge())
|
||||
{
|
||||
fb_assert(!m_next->m_next); // check that transaction savepoint is the last in list
|
||||
// get rid of tx-level savepoint
|
||||
m_next->rollforward(tdbb);
|
||||
m_next->release();
|
||||
m_next = NULL;
|
||||
}
|
||||
|
||||
return release(prior);
|
||||
}
|
||||
|
||||
bool Savepoint::isLarge() const
|
||||
{
|
||||
// Returns whether the current savepoint is large enough (has many verbs posted).
|
||||
//
|
||||
// Notes:
|
||||
//
|
||||
// - This routine does not take into account the data allocated to 'vct_undo'.
|
||||
// Why? Because this routine is used to estimate size of transaction-level
|
||||
// savepoint and transaction-level savepoint may not contain undo data as it is
|
||||
// always the first savepoint in transaction.
|
||||
//
|
||||
// - We use U_IPTR, not ULONG to care of case when user savepoint gets very,
|
||||
// very big on 64-bit machine. Its size may overflow 32 significant bits of
|
||||
// ULONG in this case
|
||||
|
||||
U_IPTR size = 0;
|
||||
|
||||
// Iterate all tables changed under this savepoint
|
||||
|
||||
for (VerbAction* action = m_actions; action; action = action->vct_next)
|
||||
{
|
||||
// Estimate size used for record backout bitmaps for this table
|
||||
|
||||
if (action->vct_records)
|
||||
{
|
||||
size += action->vct_records->approxSize();
|
||||
|
||||
if (size > SIZE_THRESHOLD)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Savepoint* Savepoint::release(Savepoint* prior)
|
||||
{
|
||||
// Clear savepoint and prepare it for later reuse.
|
||||
// If prior savepoint is specified, relink its next pointer.
|
||||
// Return the next savepoint, if exists.
|
||||
|
||||
m_flags = 0;
|
||||
m_count = 0;
|
||||
m_name = "";
|
||||
|
||||
Savepoint* const next = m_next;
|
||||
|
||||
if (prior)
|
||||
prior->m_next = next;
|
||||
|
||||
m_next = m_transaction->tra_save_free;
|
||||
m_transaction->tra_save_free = this;
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
|
||||
// AutoSavePoint implementation
|
||||
|
||||
AutoSavePoint::AutoSavePoint(thread_db* tdbb, jrd_tra* trans)
|
||||
: m_tdbb(tdbb), m_transaction(trans), m_released(false)
|
||||
{
|
||||
Savepoint::start(trans);
|
||||
}
|
||||
|
||||
AutoSavePoint::~AutoSavePoint()
|
||||
{
|
||||
if (!(m_tdbb->getDatabase()->dbb_flags & DBB_bugcheck))
|
||||
{
|
||||
if (m_released)
|
||||
m_transaction->rollforwardSavepoint(m_tdbb);
|
||||
else
|
||||
m_transaction->rollbackSavepoint(m_tdbb);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// StableCursorSavePoint implementation
|
||||
|
||||
StableCursorSavePoint::StableCursorSavePoint(thread_db* tdbb, jrd_tra* trans, bool start)
|
||||
: m_tdbb(tdbb), m_transaction(trans), m_number(0)
|
||||
{
|
||||
if (!start)
|
||||
return;
|
||||
|
||||
if (trans->tra_flags & TRA_system)
|
||||
return;
|
||||
|
||||
if (!trans->tra_save_point)
|
||||
return;
|
||||
|
||||
const Savepoint* const savepoint = Savepoint::start(trans);
|
||||
m_number = savepoint->getNumber();
|
||||
}
|
||||
|
||||
|
||||
void StableCursorSavePoint::release()
|
||||
{
|
||||
if (!m_number)
|
||||
return;
|
||||
|
||||
while (m_transaction->tra_save_point && m_transaction->tra_save_point->getNumber() >= m_number)
|
||||
m_transaction->rollforwardSavepoint(m_tdbb);
|
||||
|
||||
m_number = 0;
|
||||
}
|
355
src/jrd/Savepoint.h
Normal file
355
src/jrd/Savepoint.h
Normal file
@ -0,0 +1,355 @@
|
||||
/*
|
||||
* The contents of this file are subject to the Interbase Public
|
||||
* License Version 1.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.Inprise.com/IPL.html
|
||||
*
|
||||
* Software distributed under the License is distributed on an
|
||||
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing
|
||||
* rights and limitations under the License.
|
||||
*
|
||||
* The Original Code was created by Inprise Corporation
|
||||
* and its predecessors. Portions created by Inprise Corporation are
|
||||
* Copyright (C) Inprise Corporation.
|
||||
*
|
||||
* All Rights Reserved.
|
||||
* Contributor(s): ______________________________________.
|
||||
*/
|
||||
|
||||
#ifndef JRD_SAVEPOINT_H
|
||||
#define JRD_SAVEPOINT_H
|
||||
|
||||
#include "../common/classes/File.h"
|
||||
#include "../common/classes/MetaName.h"
|
||||
#include "../jrd/Record.h"
|
||||
#include "../jrd/RecordNumber.h"
|
||||
|
||||
namespace Jrd
|
||||
{
|
||||
class jrd_tra;
|
||||
|
||||
// Verb actions
|
||||
|
||||
class UndoItem
|
||||
{
|
||||
public:
|
||||
static const SINT64& generate(const void* /*sender*/, const UndoItem& item)
|
||||
{
|
||||
return item.m_number;
|
||||
}
|
||||
|
||||
UndoItem()
|
||||
: m_number(0), m_offset(0), m_format(NULL)
|
||||
{}
|
||||
|
||||
UndoItem(RecordNumber recordNumber)
|
||||
: m_number(recordNumber.getValue()), m_offset(0), m_format(NULL)
|
||||
{}
|
||||
|
||||
UndoItem(jrd_tra* transaction, RecordNumber recordNumber, const Record* record);
|
||||
|
||||
Record* setupRecord(jrd_tra* transaction) const;
|
||||
void release(jrd_tra* transaction);
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_format = NULL;
|
||||
}
|
||||
|
||||
bool hasData() const
|
||||
{
|
||||
return (m_format != NULL);
|
||||
}
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return (m_format == NULL);
|
||||
}
|
||||
|
||||
private:
|
||||
SINT64 m_number;
|
||||
offset_t m_offset;
|
||||
const Format* m_format;
|
||||
};
|
||||
|
||||
typedef Firebird::BePlusTree<UndoItem, SINT64, MemoryPool, UndoItem> UndoItemTree;
|
||||
|
||||
class VerbAction
|
||||
{
|
||||
public:
|
||||
VerbAction()
|
||||
: vct_next(NULL), vct_relation(NULL), vct_records(NULL), vct_undo(NULL)
|
||||
{}
|
||||
|
||||
~VerbAction()
|
||||
{
|
||||
delete vct_records;
|
||||
delete vct_undo;
|
||||
}
|
||||
|
||||
VerbAction* vct_next; // Next action within verb
|
||||
jrd_rel* vct_relation; // Relation involved
|
||||
RecordBitmap* vct_records; // Record involved
|
||||
UndoItemTree* vct_undo; // Data for undo records
|
||||
|
||||
void mergeTo(thread_db* tdbb, jrd_tra* transaction, VerbAction* nextAction);
|
||||
void undo(thread_db* tdbb, jrd_tra* transaction);
|
||||
void garbageCollectIdxLite(thread_db* tdbb, jrd_tra* transaction, SINT64 recordNumber,
|
||||
VerbAction* nextAction, Record* goingRecord);
|
||||
|
||||
private:
|
||||
void release(jrd_tra* transaction);
|
||||
};
|
||||
|
||||
// Savepoint class
|
||||
|
||||
class Savepoint
|
||||
{
|
||||
// Maximum size in bytes of transaction-level savepoint data.
|
||||
// When transaction-level savepoint gets past this size we drop it and use GC
|
||||
// mechanisms to clean out changes done in transaction
|
||||
static const U_IPTR SIZE_THRESHOLD = 1024 * 32;
|
||||
|
||||
// Savepoint flags
|
||||
static const USHORT SAV_root = 1; // transaction-level savepoint
|
||||
static const USHORT SAV_force_dfw = 2; // DFW is present even if savepoint is empty
|
||||
|
||||
public:
|
||||
explicit Savepoint(jrd_tra* transaction)
|
||||
: m_transaction(transaction), m_number(0), m_flags(0), m_count(0),
|
||||
m_next(NULL), m_actions(NULL), m_freeActions(NULL)
|
||||
{}
|
||||
|
||||
~Savepoint()
|
||||
{
|
||||
while (m_actions)
|
||||
{
|
||||
VerbAction* next = m_actions->vct_next;
|
||||
delete m_actions;
|
||||
m_actions = next;
|
||||
}
|
||||
|
||||
while (m_freeActions)
|
||||
{
|
||||
VerbAction* next = m_freeActions->vct_next;
|
||||
delete m_freeActions;
|
||||
m_freeActions = next;
|
||||
}
|
||||
}
|
||||
|
||||
VerbAction* getAction(const jrd_rel* relation) const
|
||||
{
|
||||
for (VerbAction* action = m_actions; action; action = action->vct_next)
|
||||
{
|
||||
if (action->vct_relation == relation)
|
||||
return action;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Savepoint* getNext() const
|
||||
{
|
||||
return m_next;
|
||||
}
|
||||
|
||||
SLONG getNumber() const
|
||||
{
|
||||
return m_number;
|
||||
}
|
||||
|
||||
const Firebird::MetaName& getName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void setName(const Firebird::MetaName& name)
|
||||
{
|
||||
m_name = name;
|
||||
}
|
||||
|
||||
bool isSystem() const
|
||||
{
|
||||
return m_name.isEmpty();
|
||||
}
|
||||
|
||||
bool isRoot() const
|
||||
{
|
||||
return (m_flags & SAV_root);
|
||||
}
|
||||
|
||||
bool isChanging() const
|
||||
{
|
||||
return (m_count != 0);
|
||||
}
|
||||
|
||||
bool hasChanges() const
|
||||
{
|
||||
return (m_actions != NULL);
|
||||
}
|
||||
|
||||
void forceDeferredWork()
|
||||
{
|
||||
m_flags |= SAV_force_dfw;
|
||||
}
|
||||
|
||||
Savepoint* mergeTo(Savepoint*& target)
|
||||
{
|
||||
Savepoint* const next = m_next;
|
||||
m_next = target;
|
||||
target = this;
|
||||
return next;
|
||||
}
|
||||
|
||||
VerbAction* createAction(jrd_rel* relation);
|
||||
|
||||
void releaseAction(VerbAction* action)
|
||||
{
|
||||
m_actions = action->vct_next;
|
||||
action->vct_next = m_freeActions;
|
||||
m_freeActions = action;
|
||||
}
|
||||
|
||||
void propagateAction(VerbAction* action)
|
||||
{
|
||||
m_actions = action->vct_next;
|
||||
action->vct_next = m_next->m_actions;
|
||||
m_next->m_actions = action;
|
||||
}
|
||||
|
||||
void cleanupTempData();
|
||||
|
||||
Savepoint* rollback(thread_db* tdbb, Savepoint* prior = NULL);
|
||||
Savepoint* rollforward(thread_db* tdbb, Savepoint* prior = NULL);
|
||||
|
||||
static Savepoint* start(jrd_tra* transaction, bool root = false);
|
||||
|
||||
static void destroy(Savepoint*& savepoint)
|
||||
{
|
||||
while (savepoint)
|
||||
{
|
||||
Savepoint* const next = savepoint->m_next;
|
||||
delete savepoint;
|
||||
savepoint = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void merge(Savepoint*& target, Savepoint*& source)
|
||||
{
|
||||
while (source)
|
||||
source = source->mergeTo(target);
|
||||
}
|
||||
|
||||
class Iterator
|
||||
{
|
||||
public:
|
||||
explicit Iterator(Savepoint* savepoint)
|
||||
: m_savepoint(savepoint)
|
||||
{}
|
||||
|
||||
Iterator& operator++()
|
||||
{
|
||||
if (m_savepoint)
|
||||
m_savepoint = m_savepoint->m_next;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Savepoint* operator*() const
|
||||
{
|
||||
return m_savepoint;
|
||||
}
|
||||
|
||||
private:
|
||||
Iterator(const Iterator&);
|
||||
Iterator& operator=(const Iterator&);
|
||||
|
||||
Savepoint* m_savepoint;
|
||||
};
|
||||
|
||||
class ChangeMarker
|
||||
{
|
||||
public:
|
||||
explicit ChangeMarker(Savepoint* savepoint)
|
||||
: m_savepoint(savepoint)
|
||||
{
|
||||
if (m_savepoint)
|
||||
++m_savepoint->m_count;
|
||||
}
|
||||
|
||||
~ChangeMarker()
|
||||
{
|
||||
if (m_savepoint)
|
||||
--m_savepoint->m_count;
|
||||
}
|
||||
|
||||
private:
|
||||
ChangeMarker(const ChangeMarker&);
|
||||
ChangeMarker& operator=(const ChangeMarker&);
|
||||
|
||||
Savepoint* const m_savepoint;
|
||||
};
|
||||
|
||||
private:
|
||||
// Prohibit unwanted creation/copying
|
||||
Savepoint(const Savepoint&);
|
||||
Savepoint& operator=(const Savepoint&);
|
||||
|
||||
bool isLarge() const;
|
||||
Savepoint* release(Savepoint* prior = NULL);
|
||||
|
||||
jrd_tra* m_transaction; // transaction this savepoint belongs to
|
||||
SLONG m_number; // savepoint number
|
||||
USHORT m_flags; // misc flags
|
||||
USHORT m_count; // active verb count
|
||||
Firebird::MetaName m_name; // savepoint name
|
||||
Savepoint* m_next; // next savepoint in the list
|
||||
|
||||
|
||||
VerbAction* m_actions; // verb action list
|
||||
VerbAction* m_freeActions; // free verb actions
|
||||
};
|
||||
|
||||
|
||||
// Starts a savepoint and rollback it in destructor if release() is not called
|
||||
|
||||
class AutoSavePoint
|
||||
{
|
||||
public:
|
||||
AutoSavePoint(thread_db* tdbb, jrd_tra* trans);
|
||||
~AutoSavePoint();
|
||||
|
||||
void release()
|
||||
{
|
||||
m_released = true;
|
||||
}
|
||||
|
||||
private:
|
||||
thread_db* const m_tdbb;
|
||||
jrd_tra* const m_transaction;
|
||||
bool m_released;
|
||||
};
|
||||
|
||||
class StableCursorSavePoint
|
||||
{
|
||||
public:
|
||||
StableCursorSavePoint(thread_db* tdbb, jrd_tra* trans, bool start);
|
||||
|
||||
~StableCursorSavePoint()
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
void release();
|
||||
|
||||
private:
|
||||
thread_db* const m_tdbb;
|
||||
jrd_tra* const m_transaction;
|
||||
SLONG m_number;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // JRD_SAVEPOINT_H
|
||||
|
@ -1490,7 +1490,7 @@ DeferredWork* DFW_post_work(jrd_tra* transaction, enum dfw_t type, const string&
|
||||
// get the current save point number
|
||||
|
||||
const SLONG sav_number = transaction->tra_save_point ?
|
||||
transaction->tra_save_point->sav_number : 0;
|
||||
transaction->tra_save_point->getNumber() : 0;
|
||||
|
||||
// initialize transaction if needed
|
||||
|
||||
@ -1533,7 +1533,7 @@ DeferredWork* DFW_post_work(jrd_tra* transaction, enum dfw_t type, const string&
|
||||
// fall down ...
|
||||
case dfw_post_event:
|
||||
if (transaction->tra_save_point)
|
||||
transaction->tra_save_point->sav_flags |= SAV_force_dfw;
|
||||
transaction->tra_save_point->forceDeferredWork();
|
||||
break;
|
||||
default:
|
||||
transaction->tra_flags |= TRA_deferred_meta;
|
||||
|
@ -195,7 +195,6 @@ void StatusXcp::as_sqlstate(char* sqlstate) const
|
||||
static void execute_looper(thread_db*, jrd_req*, jrd_tra*, const StmtNode*, jrd_req::req_s);
|
||||
static void looper_seh(thread_db*, jrd_req*, const StmtNode*);
|
||||
static void release_blobs(thread_db*, jrd_req*);
|
||||
static void release_proc_save_points(jrd_req*);
|
||||
static void trigger_failure(thread_db*, jrd_req*);
|
||||
static void stuff_stack_trace(const jrd_req*);
|
||||
|
||||
@ -616,26 +615,22 @@ void EXE_receive(thread_db* tdbb,
|
||||
|
||||
if (transaction->tra_save_point)
|
||||
{
|
||||
merge_sav_number = transaction->tra_save_point->sav_number;
|
||||
merge_sav_number = transaction->tra_save_point->getNumber();
|
||||
|
||||
if (request->req_proc_sav_point)
|
||||
{
|
||||
// Push all saved savepoints to the top of transaction savepoints stack
|
||||
while (request->req_proc_sav_point)
|
||||
{
|
||||
Savepoint* const sav_point = request->req_proc_sav_point;
|
||||
request->req_proc_sav_point = sav_point->sav_next;
|
||||
sav_point->sav_next = transaction->tra_save_point;
|
||||
transaction->tra_save_point = sav_point;
|
||||
}
|
||||
Savepoint::merge(transaction->tra_save_point, request->req_proc_sav_point);
|
||||
fb_assert(!request->req_proc_sav_point);
|
||||
}
|
||||
else
|
||||
{
|
||||
VIO_start_save_point(tdbb, transaction);
|
||||
Savepoint::start(transaction);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
VIO_start_save_point(tdbb, transaction);
|
||||
Savepoint::start(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
@ -690,22 +685,27 @@ void EXE_receive(thread_db* tdbb,
|
||||
if (request->req_flags & req_proc_fetch)
|
||||
{
|
||||
// At this point request->req_proc_sav_point == NULL that is assured by code above
|
||||
fb_assert(!request->req_proc_sav_point);
|
||||
|
||||
try
|
||||
{
|
||||
// merge work into target savepoint and save request's savepoints (with numbers!!!) till the next loop
|
||||
while (transaction->tra_save_point && transaction->tra_save_point->sav_number > merge_sav_number)
|
||||
// Merge work into target savepoint and save request's savepoints (with numbers!!!)
|
||||
// till the next looper iteration
|
||||
while (transaction->tra_save_point &&
|
||||
transaction->tra_save_point->getNumber() > merge_sav_number)
|
||||
{
|
||||
Savepoint* const save_sav_point = transaction->tra_save_point;
|
||||
save_sav_point->rollforward(tdbb);
|
||||
transaction->tra_save_point = save_sav_point->sav_next;
|
||||
save_sav_point->sav_next = request->req_proc_sav_point;
|
||||
request->req_proc_sav_point = save_sav_point;
|
||||
Savepoint* const savepoint = transaction->tra_save_point;
|
||||
transaction->rollforwardSavepoint(tdbb);
|
||||
fb_assert(transaction->tra_save_free == savepoint);
|
||||
transaction->tra_save_free = savepoint->mergeTo(request->req_proc_sav_point);
|
||||
fb_assert(request->req_proc_sav_point == savepoint);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// If something went wrong, drop already stored savepoints to prevent memory leak
|
||||
release_proc_save_points(request);
|
||||
Savepoint::destroy(request->req_proc_sav_point);
|
||||
fb_assert(!request->req_proc_sav_point);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@ -863,7 +863,6 @@ void EXE_start(thread_db* tdbb, jrd_req* request, jrd_tra* transaction)
|
||||
*
|
||||
**************************************/
|
||||
SET_TDBB(tdbb);
|
||||
Jrd::Attachment* attachment = tdbb->getAttachment();
|
||||
|
||||
BLKCHK(request, type_req);
|
||||
BLKCHK(transaction, type_tra);
|
||||
@ -984,7 +983,11 @@ void EXE_unwind(thread_db* tdbb, jrd_req* request)
|
||||
request->req_sorts.unlinkAll();
|
||||
|
||||
if (request->req_proc_sav_point && (request->req_flags & req_proc_fetch))
|
||||
release_proc_save_points(request);
|
||||
{
|
||||
// Release savepoints used by this request
|
||||
Savepoint::destroy(request->req_proc_sav_point);
|
||||
fb_assert(!request->req_proc_sav_point);
|
||||
}
|
||||
|
||||
TRA_detach_request(request);
|
||||
|
||||
@ -1029,8 +1032,8 @@ static void execute_looper(thread_db* tdbb,
|
||||
|
||||
if (!(request->req_flags & req_proc_fetch) && request->req_transaction)
|
||||
{
|
||||
if (transaction && (transaction != attachment->getSysTransaction()))
|
||||
VIO_start_save_point(tdbb, transaction);
|
||||
if (transaction && !(transaction->tra_flags & TRA_system))
|
||||
Savepoint::start(transaction);
|
||||
}
|
||||
|
||||
request->req_flags &= ~req_stall;
|
||||
@ -1042,10 +1045,10 @@ static void execute_looper(thread_db* tdbb,
|
||||
|
||||
if (!(request->req_flags & req_proc_fetch) && request->req_transaction)
|
||||
{
|
||||
if (transaction && (transaction != attachment->getSysTransaction()) &&
|
||||
if (transaction && !(transaction->tra_flags & TRA_system) &&
|
||||
transaction->tra_save_point &&
|
||||
!(transaction->tra_save_point->sav_flags & SAV_user) &&
|
||||
!transaction->tra_save_point->sav_verb_count)
|
||||
transaction->tra_save_point->isSystem() &&
|
||||
!transaction->tra_save_point->isChanging())
|
||||
{
|
||||
// Forget about any undo for this verb
|
||||
transaction->rollforwardSavepoint(tdbb);
|
||||
@ -1269,8 +1272,6 @@ const StmtNode* EXE_looper(thread_db* tdbb, jrd_req* request, const StmtNode* no
|
||||
ERR_post(Arg::Gds(isc_req_no_trans));
|
||||
|
||||
SET_TDBB(tdbb);
|
||||
Jrd::Attachment* attachment = tdbb->getAttachment();
|
||||
jrd_tra* sysTransaction = attachment->getSysTransaction();
|
||||
Database* dbb = tdbb->getDatabase();
|
||||
|
||||
if (!node || node->kind != DmlNode::KIND_STATEMENT)
|
||||
@ -1284,7 +1285,7 @@ const StmtNode* EXE_looper(thread_db* tdbb, jrd_req* request, const StmtNode* no
|
||||
request->req_caller = exeState.oldRequest;
|
||||
|
||||
const SLONG save_point_number = (request->req_transaction->tra_save_point) ?
|
||||
request->req_transaction->tra_save_point->sav_number : 0;
|
||||
request->req_transaction->tra_save_point->getNumber() : 0;
|
||||
|
||||
tdbb->tdbb_flags &= ~(TDBB_stack_trace_done | TDBB_sys_error);
|
||||
|
||||
@ -1387,10 +1388,8 @@ const StmtNode* EXE_looper(thread_db* tdbb, jrd_req* request, const StmtNode* no
|
||||
|
||||
if (exeState.errorPending)
|
||||
{
|
||||
if (request->req_transaction != sysTransaction)
|
||||
{
|
||||
if (!(request->req_transaction->tra_flags & TRA_system))
|
||||
request->req_transaction->rollbackToSavepoint(tdbb, save_point_number);
|
||||
}
|
||||
|
||||
ERR_punt();
|
||||
}
|
||||
@ -1504,28 +1503,6 @@ static void release_blobs(thread_db* tdbb, jrd_req* request)
|
||||
}
|
||||
}
|
||||
|
||||
static void release_proc_save_points(jrd_req* request)
|
||||
{
|
||||
/**************************************
|
||||
*
|
||||
* r e l e a s e _ p r o c _ s a v e _ p o i n t s
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* Release savepoints used by this request.
|
||||
*
|
||||
**************************************/
|
||||
Savepoint* sav_point = request->req_proc_sav_point;
|
||||
|
||||
while (sav_point)
|
||||
{
|
||||
Savepoint* const temp_sav_point = sav_point->sav_next;
|
||||
delete sav_point;
|
||||
sav_point = temp_sav_point;
|
||||
}
|
||||
request->req_proc_sav_point = NULL;
|
||||
}
|
||||
|
||||
static void trigger_failure(thread_db* tdbb, jrd_req* trigger)
|
||||
{
|
||||
|
@ -6986,9 +6986,8 @@ static void run_commit_triggers(thread_db* tdbb, jrd_tra* transaction)
|
||||
*
|
||||
**************************************/
|
||||
SET_TDBB(tdbb);
|
||||
Jrd::Attachment* attachment = tdbb->getAttachment();
|
||||
|
||||
if (transaction == attachment->getSysTransaction())
|
||||
if (transaction->tra_flags & TRA_system)
|
||||
return;
|
||||
|
||||
// start a savepoint to rollback changes of all triggers
|
||||
|
577
src/jrd/tra.cpp
577
src/jrd/tra.cpp
@ -353,11 +353,10 @@ void TRA_commit(thread_db* tdbb, jrd_tra* transaction, const bool retaining_flag
|
||||
transaction_flush(tdbb, FLUSH_SYSTEM, 0);
|
||||
|
||||
transaction->tra_flags &= ~TRA_prepared;
|
||||
|
||||
// Get rid of all user savepoints
|
||||
while (transaction->tra_save_point && !(transaction->tra_save_point->sav_flags & SAV_trans_level))
|
||||
{
|
||||
while (transaction->tra_save_point && !transaction->tra_save_point->isRoot())
|
||||
transaction->rollforwardSavepoint(tdbb);
|
||||
}
|
||||
|
||||
trace.finish(ITracePlugin::RESULT_SUCCESS);
|
||||
return;
|
||||
@ -1318,15 +1317,17 @@ void TRA_rollback(thread_db* tdbb, jrd_tra* transaction, const bool retaining_fl
|
||||
{
|
||||
// Free all savepoint data
|
||||
// Undo data space and BLOBs will be released in destructor
|
||||
while (transaction->tra_save_point)
|
||||
{
|
||||
Savepoint* const next = transaction->tra_save_point->sav_next;
|
||||
delete transaction->tra_save_point;
|
||||
transaction->tra_save_point = next;
|
||||
}
|
||||
Savepoint::destroy(transaction->tra_save_point);
|
||||
fb_assert(!transaction->tra_save_point);
|
||||
}
|
||||
else
|
||||
VIO_temp_cleanup(transaction);
|
||||
{
|
||||
// Remove undo data for GTT ON COMMIT DELETE ROWS as their data will be released
|
||||
// at transaction end anyway and we don't need to waste time backing it out
|
||||
|
||||
for (Savepoint::Iterator iter(transaction->tra_save_point); *iter; ++iter)
|
||||
(*iter)->cleanupTempData();
|
||||
}
|
||||
|
||||
int state = tra_dead;
|
||||
|
||||
@ -1340,10 +1341,8 @@ void TRA_rollback(thread_db* tdbb, jrd_tra* transaction, const bool retaining_fl
|
||||
// Release all user savepoints except transaction one
|
||||
// It will clean up blob ids and temporary space anyway but faster than rollback
|
||||
// because record data won't be updated with intermediate versions
|
||||
while (transaction->tra_save_point && !(transaction->tra_save_point->sav_flags & SAV_trans_level))
|
||||
{
|
||||
while (transaction->tra_save_point && !transaction->tra_save_point->isRoot())
|
||||
transaction->rollforwardSavepoint(tdbb);
|
||||
}
|
||||
|
||||
if (transaction->tra_save_point) // we still can use undo log for rollback, it wasn't reset because of no_auto_undo flag or size
|
||||
{
|
||||
@ -2447,10 +2446,7 @@ static void retain_context(thread_db* tdbb, jrd_tra* transaction, bool commit, i
|
||||
|
||||
// All savepoint were already released in TRA_commit/TRA_rollback except, may be, empty transaction-level one
|
||||
if (!transaction->tra_save_point && !(transaction->tra_flags & TRA_no_auto_undo))
|
||||
{
|
||||
VIO_start_save_point(tdbb, transaction); // start new savepoint if necessary
|
||||
transaction->tra_save_point->sav_flags |= SAV_trans_level;
|
||||
}
|
||||
Savepoint::start(transaction, true); // start new savepoint if necessary
|
||||
|
||||
if (transaction->tra_flags & TRA_precommitted)
|
||||
{
|
||||
@ -3324,11 +3320,8 @@ static void transaction_start(thread_db* tdbb, jrd_tra* trans)
|
||||
// a savepoint to be started. This savepoint will be used to
|
||||
// undo the transaction if it rolls back.
|
||||
|
||||
if (trans != attachment->getSysTransaction() && !(trans->tra_flags & TRA_no_auto_undo))
|
||||
{
|
||||
VIO_start_save_point(tdbb, trans);
|
||||
trans->tra_save_point->sav_flags |= SAV_trans_level;
|
||||
}
|
||||
if (!(trans->tra_flags & TRA_system) && !(trans->tra_flags & TRA_no_auto_undo))
|
||||
Savepoint::start(trans, true);
|
||||
|
||||
// if the user asked us to restart all requests in this attachment,
|
||||
// do so now using the new transaction
|
||||
@ -3465,7 +3458,7 @@ MemoryPool* jrd_tra::getAutonomousPool()
|
||||
return tra_autonomous_pool;
|
||||
}
|
||||
|
||||
Record* jrd_tra::findNextUndo(VerbAction* before_this, jrd_rel* relation, SINT64 number)
|
||||
Record* jrd_tra::findNextUndo(VerbAction* stopAction, jrd_rel* relation, SINT64 number)
|
||||
/**************************************
|
||||
*
|
||||
* f i n d N e x t U n d o
|
||||
@ -3473,22 +3466,24 @@ Record* jrd_tra::findNextUndo(VerbAction* before_this, jrd_rel* relation, SINT64
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* for given record find next undo data in stack of savepoint (if any).
|
||||
* For given record find next undo data in stack of savepoint (if any).
|
||||
*
|
||||
**************************************/
|
||||
{
|
||||
UndoItem* result = NULL;
|
||||
for (Savepoint* itr = tra_save_point; itr; itr = itr->sav_next)
|
||||
|
||||
for (Savepoint::Iterator iter(tra_save_point); *iter; ++iter)
|
||||
{
|
||||
for (VerbAction* action = itr->sav_verb_actions; action; action = action->vct_next)
|
||||
{
|
||||
if (action == before_this)
|
||||
return result?result->setupRecord(this):NULL;
|
||||
if (action->vct_relation == relation && action->vct_undo && action->vct_undo->locate(number))
|
||||
result = &(action->vct_undo->current());
|
||||
}
|
||||
VerbAction* const action = (*iter)->getAction(relation);
|
||||
|
||||
if (action == stopAction)
|
||||
return result ? result->setupRecord(this) : NULL;
|
||||
|
||||
if (action && action->vct_undo && action->vct_undo->locate(number))
|
||||
result = &(action->vct_undo->current());
|
||||
}
|
||||
fb_assert(false); // verb_action disappeared from savepoint stack.
|
||||
|
||||
fb_assert(false); // verb_action disappeared from savepoint stack
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -3500,18 +3495,17 @@ void jrd_tra::listStayingUndo(jrd_rel* relation, SINT64 number, RecordStack &sta
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* for given record find undo data in stack of savepoint (if any) and push it into list.
|
||||
* For given record find undo data in stack of savepoint (if any) and push it into list.
|
||||
* Except one from given verb action.
|
||||
*
|
||||
**************************************/
|
||||
{
|
||||
for (Savepoint* itr = tra_save_point; itr; itr = itr->sav_next)
|
||||
for (Savepoint::Iterator iter(tra_save_point); *iter; ++iter)
|
||||
{
|
||||
for (VerbAction* action = itr->sav_verb_actions; action; action = action->vct_next)
|
||||
{
|
||||
if (action->vct_relation == relation && action->vct_undo && action->vct_undo->locate(number))
|
||||
staying.push(action->vct_undo->current().setupRecord(this));
|
||||
}
|
||||
VerbAction* const action = (*iter)->getAction(relation);
|
||||
|
||||
if (action && action->vct_undo && action->vct_undo->locate(number))
|
||||
staying.push(action->vct_undo->current().setupRecord(this));
|
||||
}
|
||||
}
|
||||
|
||||
@ -3528,35 +3522,26 @@ void jrd_tra::releaseAutonomousPool(MemoryPool* toRelease)
|
||||
void jrd_tra::rollbackSavepoint(thread_db* tdbb)
|
||||
/**************************************
|
||||
*
|
||||
* ro l l b a c k S a v e p o i n t
|
||||
* r o l l b a c k S a v e p o i n t
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* Rollback one savepoint and free it.
|
||||
* Rollback last savepoint and free it.
|
||||
*
|
||||
**************************************/
|
||||
{
|
||||
if (tra_flags & TRA_system)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (tra_save_point)
|
||||
if (tra_save_point && !(tra_flags & TRA_system))
|
||||
{
|
||||
Jrd::ContextPoolHolder context(tdbb, tra_pool);
|
||||
tra_save_point->rollback(tdbb); // this call can change sav_next
|
||||
Savepoint* temp = tra_save_point->sav_next;
|
||||
tra_save_point->sav_next = tra_save_free;
|
||||
tra_save_free = tra_save_point;
|
||||
tra_save_point = temp;
|
||||
tra_save_point = tra_save_point->rollback(tdbb);
|
||||
}
|
||||
}
|
||||
|
||||
void jrd_tra::rollbackToSavepoint(thread_db* tdbb, SLONG number)
|
||||
/**************************************
|
||||
*
|
||||
* ro l l b a c k T o S a v e p o i n t
|
||||
* r o l l b a c k T o S a v e p o i n t
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
@ -3568,15 +3553,17 @@ void jrd_tra::rollbackToSavepoint(thread_db* tdbb, SLONG number)
|
||||
*
|
||||
**************************************/
|
||||
{
|
||||
// merge all but one folowing savepoints into one
|
||||
while (tra_save_point && tra_save_point->sav_number > number &&
|
||||
tra_save_point->sav_next && tra_save_point->sav_next->sav_number >= number)
|
||||
// Merge all but one folowing savepoints into one
|
||||
while (tra_save_point && tra_save_point->getNumber() > number &&
|
||||
tra_save_point->getNext() && tra_save_point->getNext()->getNumber() >= number)
|
||||
{
|
||||
rollforwardSavepoint(tdbb);
|
||||
}
|
||||
|
||||
// Check that savepoint with given number really exists
|
||||
fb_assert(tra_save_point && tra_save_point->sav_number == number);
|
||||
if (tra_save_point && tra_save_point->sav_number >= number) // second line of defence
|
||||
fb_assert(tra_save_point && tra_save_point->getNumber() == number);
|
||||
|
||||
if (tra_save_point && tra_save_point->getNumber() >= number) // second line of defence
|
||||
// under no circumstances a savepoint with smaller number should be rolled back
|
||||
{
|
||||
// Undo the savepoint
|
||||
@ -3588,29 +3575,19 @@ void jrd_tra::rollbackToSavepoint(thread_db* tdbb, SLONG number)
|
||||
void jrd_tra::rollforwardSavepoint(thread_db* tdbb)
|
||||
/**************************************
|
||||
*
|
||||
* ro l l f o r w a r d S a v e p o i n t
|
||||
* r o l l f o r w a r d S a v e p o i n t
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* Apply one savepoint and free it.
|
||||
* Apply last savepoint and free it.
|
||||
*
|
||||
**************************************/
|
||||
{
|
||||
if (tra_flags & TRA_system)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (tra_save_point)
|
||||
if (tra_save_point && !(tra_flags & TRA_system))
|
||||
{
|
||||
Jrd::ContextPoolHolder context(tdbb, tra_pool);
|
||||
|
||||
tra_save_point->rollforward(tdbb); // this call can change sav_next
|
||||
Savepoint* temp = tra_save_point->sav_next;
|
||||
tra_save_point->sav_next = tra_save_free;
|
||||
tra_save_free = tra_save_point;
|
||||
tra_save_point = temp;
|
||||
tra_save_point = tra_save_point->rollforward(tdbb);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3790,457 +3767,3 @@ void jrd_tra::eraseSecDbContext()
|
||||
delete tra_sec_db_context;
|
||||
tra_sec_db_context = NULL;
|
||||
}
|
||||
|
||||
void Savepoint::rollback(thread_db* tdbb)
|
||||
/**************************************
|
||||
*
|
||||
* r o l l b a c k
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* Undo changes made in this savepoint.
|
||||
* Perform index and BLOB cleanup if needed.
|
||||
* At the exit Savepoint is clear and safe to reuse.
|
||||
*
|
||||
**************************************/
|
||||
{
|
||||
jrd_tra* old_tran = tdbb->getTransaction();
|
||||
try
|
||||
{
|
||||
DFW_delete_deferred(sav_trans, sav_number);
|
||||
sav_flags &= ~SAV_force_dfw;
|
||||
|
||||
tdbb->tdbb_flags |= TDBB_verb_cleanup;
|
||||
tdbb->setTransaction(sav_trans);
|
||||
|
||||
while (sav_verb_actions)
|
||||
{
|
||||
sav_verb_actions->undo(tdbb, sav_trans);
|
||||
VerbAction* temp = sav_verb_actions;
|
||||
sav_verb_actions = sav_verb_actions->vct_next;
|
||||
temp->vct_next = sav_verb_free;
|
||||
sav_verb_free = temp;
|
||||
}
|
||||
tdbb->setTransaction(old_tran);
|
||||
tdbb->tdbb_flags &= ~TDBB_verb_cleanup;
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
Arg::StatusVector error(ex);
|
||||
tdbb->setTransaction(old_tran);
|
||||
tdbb->tdbb_flags &= ~TDBB_verb_cleanup;
|
||||
sav_trans->tra_flags |= TRA_invalidated;
|
||||
error.prepend(Arg::Gds(isc_savepoint_backout_err));
|
||||
error.raise();
|
||||
}
|
||||
sav_verb_count = 0;
|
||||
sav_flags = 0;
|
||||
}
|
||||
|
||||
void Savepoint::rollforward(thread_db* tdbb)
|
||||
/**************************************
|
||||
*
|
||||
* r o l l f o r w a r d
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* Merge changes made in this savepoint into next one.
|
||||
* Perform index and BLOB cleanup if needed.
|
||||
* At the exit Savepoint is clear and safe to reuse.
|
||||
*
|
||||
**************************************/
|
||||
{
|
||||
jrd_tra* old_tran = tdbb->getTransaction();
|
||||
try
|
||||
{
|
||||
// If the current to-be-cleaned-up savepoint is very big, and the next
|
||||
// level savepoint is the transaction level savepoint, then get rid of
|
||||
// the transaction level savepoint now (instead of after making the
|
||||
// transaction level savepoint very very big).
|
||||
if (sav_next && (sav_next->sav_flags & SAV_trans_level) && is_large(SAV_LARGE) < 0)
|
||||
{
|
||||
fb_assert(!sav_next->sav_next); // check that transaction savepoint is the last in list
|
||||
// get rid of tx-level savepoint
|
||||
sav_next->rollforward(tdbb);
|
||||
sav_next->sav_next = sav_trans->tra_save_free;
|
||||
sav_trans->tra_save_free = sav_next;
|
||||
sav_next = NULL;
|
||||
}
|
||||
|
||||
// Cleanup/merge deferred work/event post
|
||||
|
||||
if (sav_verb_actions || (sav_flags & SAV_force_dfw))
|
||||
{
|
||||
DFW_merge_work(sav_trans, sav_number, sav_next ? sav_next->sav_number : 0);
|
||||
|
||||
if (sav_next && (sav_flags & SAV_force_dfw))
|
||||
{
|
||||
sav_next->sav_flags |= SAV_force_dfw;
|
||||
}
|
||||
sav_flags &= ~SAV_force_dfw;
|
||||
}
|
||||
|
||||
tdbb->tdbb_flags |= TDBB_verb_cleanup;
|
||||
tdbb->setTransaction(sav_trans);
|
||||
|
||||
while (sav_verb_actions)
|
||||
{
|
||||
VerbAction* next_action = NULL;
|
||||
if (sav_next)
|
||||
{
|
||||
next_action = sav_next->getAction(sav_verb_actions->vct_relation);
|
||||
if (!next_action) // next savepoint didn't touch this table yet - send whole action
|
||||
{
|
||||
VerbAction* temp = sav_verb_actions->vct_next;
|
||||
sav_verb_actions->vct_next = sav_next->sav_verb_actions;
|
||||
sav_next->sav_verb_actions = sav_verb_actions;
|
||||
sav_verb_actions = temp;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// No luck, merge actions in a slow way
|
||||
sav_verb_actions->mergeTo(tdbb, sav_trans, next_action);
|
||||
|
||||
// Save merged action for reuse because allocation-deallocation is slow
|
||||
VerbAction* temp = sav_verb_actions->vct_next;
|
||||
sav_verb_actions->vct_next = sav_verb_free;
|
||||
sav_verb_free = sav_verb_actions;
|
||||
sav_verb_actions = temp;
|
||||
}
|
||||
tdbb->setTransaction(old_tran);
|
||||
tdbb->tdbb_flags &= ~TDBB_verb_cleanup;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
sav_trans->tra_flags |= TRA_invalidated;
|
||||
tdbb->setTransaction(old_tran);
|
||||
tdbb->tdbb_flags &= ~TDBB_verb_cleanup;
|
||||
throw;
|
||||
}
|
||||
|
||||
sav_verb_count = 0;
|
||||
sav_flags = 0;
|
||||
// preserve sav_number for further reuse
|
||||
|
||||
// If the only remaining savepoint is the 'transaction-level' savepoint
|
||||
// that was started by TRA_start, then check if it hasn't grown out of
|
||||
// bounds yet. If it has, then give up on this transaction-level savepoint.
|
||||
if (sav_next && (sav_next->sav_flags & SAV_trans_level) && sav_next->is_large(SAV_LARGE) < 0)
|
||||
{
|
||||
fb_assert(!sav_next->sav_next); // check that transaction savepoint is the last in list
|
||||
// get rid of tx-level savepoint
|
||||
sav_next->rollforward(tdbb);
|
||||
sav_next->sav_next = sav_trans->tra_save_free;
|
||||
sav_trans->tra_save_free = sav_next;
|
||||
sav_next = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
VerbAction* Savepoint::getAction(jrd_rel* relation)
|
||||
{
|
||||
for (VerbAction* result = sav_verb_actions; result; result=result->vct_next)
|
||||
{
|
||||
if (result->vct_relation == relation)
|
||||
return result;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
IPTR Savepoint::is_large(IPTR size)
|
||||
{
|
||||
/**************************************
|
||||
*
|
||||
* S a v e p o i n t : : i s _ l a r g e
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* Returns an approximate size in bytes of savepoint in-memory data, i.e. a
|
||||
* measure of how big the current savepoint has gotten.
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* - This routine does not take into account the data allocated to 'vct_undo'.
|
||||
* Why? Because this routine is used to estimate size of transaction-level
|
||||
* savepoint and transaction-level savepoint may not contain undo data as it is
|
||||
* always the first savepoint in transaction.
|
||||
*
|
||||
* - Function stops counting when return value gets negative.
|
||||
*
|
||||
* - We use IPTR, not SLONG to care of case when user savepoint gets very,
|
||||
* very big on 64-bit machine. Its size may overflow 32 significant bits of
|
||||
* SLONG in this case
|
||||
*
|
||||
**************************************/
|
||||
const VerbAction* verb_actions = sav_verb_actions;
|
||||
|
||||
// Iterate all tables changed under this savepoint
|
||||
while (verb_actions)
|
||||
{
|
||||
// Estimate size used for record backout bitmaps for this table
|
||||
if (verb_actions->vct_records) {
|
||||
size -= verb_actions->vct_records->approxSize();
|
||||
}
|
||||
|
||||
if (size < 0) {
|
||||
break;
|
||||
}
|
||||
verb_actions = verb_actions->vct_next;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void VerbAction::garbage_collect_idx_lite(thread_db* tdbb, jrd_tra* transaction, SINT64 RecNumber, VerbAction* next_action, Record* going_record)
|
||||
/**************************************
|
||||
*
|
||||
* g a r b a g e _ c o l l e c t _ i d x _ l i t e
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* Clean up index entries and referenced BLOBs.
|
||||
* This routine uses smaller set of staying record than original VIO_garbage_collect_idx().
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* This speed trick is possible only because btr.cpp:insert_node() allows duplicate nodes
|
||||
* which work as an index entry reference counter.
|
||||
*
|
||||
**************************************/
|
||||
{
|
||||
record_param rpb;
|
||||
rpb.rpb_relation = vct_relation;
|
||||
rpb.rpb_number.setValue(RecNumber);
|
||||
rpb.rpb_record = NULL;
|
||||
rpb.getWindow(tdbb).win_flags = 0;
|
||||
rpb.rpb_transaction_nr = transaction->tra_number;
|
||||
|
||||
Record* next_ver;
|
||||
AutoUndoRecord undo_next_ver(transaction->findNextUndo(this, vct_relation, RecNumber));
|
||||
AutoPtr<Record> real_next_ver;
|
||||
|
||||
next_ver = undo_next_ver;
|
||||
|
||||
if (!DPM_get(tdbb, &rpb, LCK_read))
|
||||
{
|
||||
BUGCHECK(186); // msg 186 record disappeared
|
||||
}
|
||||
else
|
||||
{
|
||||
if (next_ver || (rpb.rpb_flags & rpb_deleted))
|
||||
{
|
||||
CCH_RELEASE(tdbb, &rpb.getWindow(tdbb));
|
||||
}
|
||||
else
|
||||
{
|
||||
VIO_data(tdbb, &rpb, transaction->tra_pool);
|
||||
next_ver = real_next_ver = rpb.rpb_record;
|
||||
}
|
||||
}
|
||||
if (rpb.rpb_transaction_nr != transaction->tra_number)
|
||||
BUGCHECK(185); // msg 185 wrong record version
|
||||
|
||||
Record* prev_ver(NULL);
|
||||
AutoUndoRecord undo_prev_ver;
|
||||
AutoPtr<Record> real_prev_ver;
|
||||
if (next_action && next_action->vct_undo && next_action->vct_undo->locate(RecNumber))
|
||||
{
|
||||
prev_ver = undo_prev_ver = next_action->vct_undo->current().setupRecord(transaction);
|
||||
}
|
||||
else if (rpb.rpb_b_page) // previous version exists and we have to find it in a hard way
|
||||
{
|
||||
record_param temp = rpb;
|
||||
temp.rpb_record = NULL;
|
||||
temp.rpb_page = rpb.rpb_b_page;
|
||||
temp.rpb_line = rpb.rpb_b_line;
|
||||
if (!DPM_fetch(tdbb, &temp, LCK_read))
|
||||
BUGCHECK(291); // Back version disappeared
|
||||
if (temp.rpb_flags & rpb_deleted)
|
||||
CCH_RELEASE(tdbb, &temp.getWindow(tdbb));
|
||||
else
|
||||
VIO_data(tdbb, &temp, transaction->tra_pool);
|
||||
prev_ver = real_prev_ver = temp.rpb_record;
|
||||
}
|
||||
RecordStack going, staying;
|
||||
going.push(going_record);
|
||||
if (prev_ver)
|
||||
staying.push(prev_ver);
|
||||
if (next_ver)
|
||||
staying.push(next_ver);
|
||||
|
||||
IDX_garbage_collect(tdbb, &rpb, going, staying);
|
||||
BLB_garbage_collect(tdbb, going, staying, rpb.rpb_page, vct_relation);
|
||||
}
|
||||
|
||||
void VerbAction::mergeTo(thread_db* tdbb, jrd_tra* transaction, VerbAction* next_action)
|
||||
/**************************************
|
||||
*
|
||||
* m e r g e T o
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* Post bitmap of modified records and undo data to the next savepoint.
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* If previous savepoint already touched a record, undo data must be dropped and
|
||||
* all BLOBs it refers to should be cleaned out because under no circumstances
|
||||
* this undo data can become an active record.
|
||||
*
|
||||
**************************************/
|
||||
{
|
||||
// Merge undo records first
|
||||
if (vct_undo && vct_undo->getFirst())
|
||||
{
|
||||
do
|
||||
{
|
||||
UndoItem& item = vct_undo->current();
|
||||
if (item.hasData()) // this item wasn't released yet
|
||||
{
|
||||
SINT64 RecNumber = item.generate(NULL, item);
|
||||
if (next_action && !(RecordBitmap::test(next_action->vct_records, RecNumber)))
|
||||
{
|
||||
if (!next_action->vct_undo)
|
||||
{
|
||||
next_action->vct_undo = new UndoItemTree(transaction->tra_pool);
|
||||
// We cannot just push whole current undo items list, some items still may to be released
|
||||
}
|
||||
else
|
||||
{
|
||||
if (next_action->vct_undo->locate(RecNumber))
|
||||
// It looks like something went wrong on previous loop and undo record was moved to next action but
|
||||
// record bit in bitmap wasn't set
|
||||
{
|
||||
fb_assert(false);
|
||||
item.clear();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
next_action->vct_undo->add(item);
|
||||
item.clear(); // Do not release undo data, it now belongs to next action
|
||||
continue;
|
||||
}
|
||||
// garbage cleanup and release
|
||||
// because going version for sure has all index entries successfully set up (in contrast with undo)
|
||||
// we can use lightweigth version of garbage collection without collection of full staying list
|
||||
|
||||
AutoUndoRecord this_ver(item.setupRecord(transaction));
|
||||
|
||||
garbage_collect_idx_lite(tdbb, transaction, RecNumber, next_action, this_ver);
|
||||
|
||||
item.release(transaction);
|
||||
}
|
||||
}
|
||||
while (vct_undo->getNext());
|
||||
delete vct_undo;
|
||||
vct_undo = NULL;
|
||||
}
|
||||
|
||||
// Now - bitmap
|
||||
if (next_action)
|
||||
{
|
||||
if (next_action->vct_records)
|
||||
{
|
||||
RecordBitmap** bm_or = RecordBitmap::bit_or(&vct_records, &next_action->vct_records);
|
||||
|
||||
if (*bm_or == vct_records) // if next bitmap has been merged into this bitmap - swap them
|
||||
{
|
||||
RecordBitmap* temp = next_action->vct_records;
|
||||
next_action->vct_records = vct_records;
|
||||
vct_records = temp;
|
||||
}
|
||||
RecordBitmap::reset(vct_records);
|
||||
}
|
||||
else // just push current bitmap as is
|
||||
{
|
||||
next_action->vct_records = vct_records;
|
||||
vct_records = NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
RecordBitmap::reset(vct_records);
|
||||
}
|
||||
|
||||
void VerbAction::undo(thread_db* tdbb, jrd_tra* transaction)
|
||||
{
|
||||
record_param rpb;
|
||||
rpb.rpb_relation = vct_relation;
|
||||
rpb.rpb_number.setValue(BOF_NUMBER);
|
||||
rpb.rpb_record = NULL;
|
||||
rpb.getWindow(tdbb).win_flags = 0;
|
||||
rpb.rpb_transaction_nr = transaction->tra_number;
|
||||
|
||||
RecordBitmap::Accessor accessor(vct_records);
|
||||
if (accessor.getFirst())
|
||||
{
|
||||
do
|
||||
{
|
||||
rpb.rpb_number.setValue(accessor.current());
|
||||
bool have_undo = vct_undo && vct_undo->locate(rpb.rpb_number.getValue());
|
||||
if (!DPM_get(tdbb, &rpb, LCK_read))
|
||||
{
|
||||
BUGCHECK(186); // msg 186 record disappeared
|
||||
}
|
||||
if (have_undo && !(rpb.rpb_flags & rpb_deleted))
|
||||
{
|
||||
VIO_data(tdbb, &rpb, transaction->tra_pool);
|
||||
}
|
||||
else
|
||||
{
|
||||
CCH_RELEASE(tdbb, &rpb.getWindow(tdbb));
|
||||
}
|
||||
if (rpb.rpb_transaction_nr != transaction->tra_number) {
|
||||
BUGCHECK(185); // msg 185 wrong record version
|
||||
}
|
||||
if (!have_undo)
|
||||
{
|
||||
VIO_backout(tdbb, &rpb, transaction);
|
||||
}
|
||||
else
|
||||
{
|
||||
AutoUndoRecord record(vct_undo->current().setupRecord(transaction));
|
||||
|
||||
Record* save_record = rpb.rpb_record;
|
||||
record_param new_rpb = rpb;
|
||||
if (rpb.rpb_flags & rpb_deleted)
|
||||
{
|
||||
rpb.rpb_record = NULL;
|
||||
}
|
||||
new_rpb.rpb_record = record;
|
||||
new_rpb.rpb_address = record->getData();
|
||||
new_rpb.rpb_length = record->getLength();
|
||||
new_rpb.rpb_flags = 0;
|
||||
Record* dead_record = rpb.rpb_record;
|
||||
// This record will be in staying list twice. Ignorable overhead.
|
||||
VIO_update_in_place(tdbb, transaction, &rpb, &new_rpb);
|
||||
if (dead_record)
|
||||
{
|
||||
rpb.rpb_record = NULL; // VIO_garbage_collect_idx will play with this record dirty tricks
|
||||
VIO_garbage_collect_idx(tdbb, transaction, &rpb, dead_record);
|
||||
}
|
||||
rpb.rpb_record = save_record;
|
||||
}
|
||||
} while (accessor.getNext());
|
||||
|
||||
delete rpb.rpb_record;
|
||||
}
|
||||
|
||||
RecordBitmap::reset(vct_records);
|
||||
if (vct_undo)
|
||||
{
|
||||
if (vct_undo->getFirst())
|
||||
{
|
||||
do {
|
||||
vct_undo->current().release(transaction);
|
||||
} while (vct_undo->getNext());
|
||||
}
|
||||
delete vct_undo;
|
||||
vct_undo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
146
src/jrd/tra.h
146
src/jrd/tra.h
@ -45,6 +45,7 @@
|
||||
#include "../jrd/TempSpace.h"
|
||||
#include "../jrd/obj.h"
|
||||
#include "../jrd/EngineInterface.h"
|
||||
#include "../jrd/Savepoint.h"
|
||||
|
||||
namespace EDS {
|
||||
class Transaction;
|
||||
@ -56,14 +57,11 @@ class blb;
|
||||
class Lock;
|
||||
class jrd_rel;
|
||||
template <typename T> class vec;
|
||||
class Savepoint;
|
||||
class Record;
|
||||
class VerbAction;
|
||||
class ArrayField;
|
||||
class Attachment;
|
||||
class DeferredWork;
|
||||
class DeferredJob;
|
||||
class dsql_opn;
|
||||
class UserManagement;
|
||||
class MappingList;
|
||||
class DbCreatorsList;
|
||||
@ -430,50 +428,6 @@ const int tra_committed = 3;
|
||||
const int tra_us = 4; // Transaction is us
|
||||
const int tra_precommitted = 5; // Transaction is precommitted
|
||||
|
||||
// Savepoint block
|
||||
|
||||
class Savepoint : public pool_alloc<type_sav>
|
||||
{
|
||||
public:
|
||||
Savepoint(jrd_tra* transaction) { sav_trans = transaction; }
|
||||
~Savepoint()
|
||||
{
|
||||
deleteActions(sav_verb_actions);
|
||||
deleteActions(sav_verb_free);
|
||||
}
|
||||
|
||||
VerbAction* sav_verb_actions; // verb action list
|
||||
VerbAction* sav_verb_free; // free verb actions
|
||||
USHORT sav_verb_count; // active verb count
|
||||
SLONG sav_number; // save point number
|
||||
Savepoint* sav_next;
|
||||
USHORT sav_flags;
|
||||
Firebird::MetaName sav_name; // savepoint name
|
||||
|
||||
void rollback(thread_db* tdbb);
|
||||
void rollforward(thread_db* tdbb);
|
||||
|
||||
VerbAction* getAction(jrd_rel* relation);
|
||||
IPTR is_large(IPTR size);
|
||||
|
||||
private:
|
||||
Savepoint(); // Disable default constructor
|
||||
void deleteActions(VerbAction* list);
|
||||
|
||||
jrd_tra* sav_trans; // Savepoint must know which transaction it belongs to
|
||||
};
|
||||
|
||||
// Savepoint block flags.
|
||||
|
||||
const int SAV_trans_level = 1; // savepoint was started by TRA_start
|
||||
const int SAV_force_dfw = 2; // DFW is present even if savepoint is empty
|
||||
const int SAV_user = 4; // named user savepoint as opposed to system ones
|
||||
|
||||
// Maximum size in bytes of transaction-level savepoint data.
|
||||
// When transaction-level savepoint gets past this size we drop it and use GC
|
||||
// mechanisms to clean out changes done in transaction
|
||||
const IPTR SAV_LARGE = 1024 * 32;
|
||||
|
||||
// Deferred work blocks are used by the meta data handler to keep track
|
||||
// of work deferred to commit time. This are usually used to perform
|
||||
// meta data updates
|
||||
@ -545,104 +499,6 @@ enum dfw_t {
|
||||
dfw_clear_mapping // clear user mapping cache
|
||||
};
|
||||
|
||||
// Verb actions
|
||||
|
||||
class UndoItem
|
||||
{
|
||||
public:
|
||||
static const SINT64& generate(const void* /*sender*/, const UndoItem& item)
|
||||
{
|
||||
return item.m_number;
|
||||
}
|
||||
|
||||
UndoItem() {}
|
||||
|
||||
UndoItem(RecordNumber recordNumber)
|
||||
: m_number(recordNumber.getValue()), m_offset(0), m_format(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
UndoItem(jrd_tra* transaction, RecordNumber recordNumber, const Record* record)
|
||||
: m_number(recordNumber.getValue()), m_format(record->getFormat())
|
||||
{
|
||||
m_offset = transaction->getUndoSpace()->allocateSpace(m_format->fmt_length);
|
||||
transaction->getUndoSpace()->write(m_offset, record->getData(), record->getLength());
|
||||
}
|
||||
|
||||
Record* setupRecord(jrd_tra* transaction) const
|
||||
{
|
||||
if (m_format)
|
||||
{
|
||||
Record* const record = transaction->getUndoRecord(m_format);
|
||||
transaction->getUndoSpace()->read(m_offset, record->getData(), record->getLength());
|
||||
return record;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void release(jrd_tra* transaction)
|
||||
{
|
||||
if (m_format)
|
||||
{
|
||||
transaction->getUndoSpace()->releaseSpace(m_offset, m_format->fmt_length);
|
||||
m_format = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_format = NULL;
|
||||
}
|
||||
|
||||
bool hasData() const
|
||||
{
|
||||
return (m_format != NULL);
|
||||
}
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return (m_format == NULL);
|
||||
}
|
||||
|
||||
private:
|
||||
SINT64 m_number;
|
||||
offset_t m_offset;
|
||||
const Format* m_format;
|
||||
};
|
||||
|
||||
typedef Firebird::BePlusTree<UndoItem, SINT64, MemoryPool, UndoItem> UndoItemTree;
|
||||
|
||||
class VerbAction : public pool_alloc<type_vct>
|
||||
{
|
||||
public:
|
||||
~VerbAction()
|
||||
{
|
||||
delete vct_records;
|
||||
delete vct_undo;
|
||||
}
|
||||
|
||||
VerbAction* vct_next; // Next action within verb
|
||||
jrd_rel* vct_relation; // Relation involved
|
||||
RecordBitmap* vct_records; // Record involved
|
||||
UndoItemTree* vct_undo; // Data for undo records
|
||||
|
||||
void mergeTo(thread_db* tdbb, jrd_tra* transaction, VerbAction* next_action);
|
||||
void undo(thread_db* tdbb, jrd_tra* transaction);
|
||||
void garbage_collect_idx_lite(thread_db* tdbb, jrd_tra* transaction, SINT64 RecNumber, VerbAction* next_action, Record* going_record);
|
||||
};
|
||||
|
||||
|
||||
inline void Savepoint::deleteActions(VerbAction* list)
|
||||
{
|
||||
while (list)
|
||||
{
|
||||
VerbAction* next = list->vct_next;
|
||||
delete list;
|
||||
list = next;
|
||||
}
|
||||
};
|
||||
|
||||
} //namespace Jrd
|
||||
|
||||
#endif // JRD_TRA_H
|
||||
|
258
src/jrd/vio.cpp
258
src/jrd/vio.cpp
@ -1824,10 +1824,10 @@ void VIO_erase(thread_db* tdbb, record_param* rpb, jrd_tra* transaction)
|
||||
if (rpb->rpb_transaction_nr == transaction->tra_number)
|
||||
{
|
||||
VIO_update_in_place(tdbb, transaction, rpb, &temp);
|
||||
if (transaction->tra_save_point && transaction->tra_save_point->sav_verb_count)
|
||||
{
|
||||
|
||||
if (transaction->tra_save_point && transaction->tra_save_point->isChanging())
|
||||
verb_post(tdbb, transaction, rpb, rpb->rpb_undo);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1883,10 +1883,9 @@ void VIO_erase(thread_db* tdbb, record_param* rpb, jrd_tra* transaction)
|
||||
MET_revoke(tdbb, transaction, object_name, revokee, privilege);
|
||||
}
|
||||
}
|
||||
if (transaction->tra_save_point && transaction->tra_save_point->sav_verb_count)
|
||||
{
|
||||
|
||||
if (transaction->tra_save_point && transaction->tra_save_point->isChanging())
|
||||
verb_post(tdbb, transaction, rpb, 0);
|
||||
}
|
||||
|
||||
// for an autocommit transaction, mark a commit as necessary
|
||||
|
||||
@ -1895,9 +1894,7 @@ void VIO_erase(thread_db* tdbb, record_param* rpb, jrd_tra* transaction)
|
||||
|
||||
// VIO_erase
|
||||
if ((tdbb->getDatabase()->dbb_flags & DBB_gc_background) && !rpb->rpb_relation->isTemporary())
|
||||
{
|
||||
notify_garbage_collector(tdbb, rpb, transaction->tra_number);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2814,11 +2811,13 @@ void VIO_modify(thread_db* tdbb, record_param* org_rpb, record_param* new_rpb, j
|
||||
{
|
||||
IDX_modify_flag_uk_modified(tdbb, org_rpb, new_rpb, transaction);
|
||||
VIO_update_in_place(tdbb, transaction, org_rpb, new_rpb);
|
||||
|
||||
if (!(transaction->tra_flags & TRA_system) &&
|
||||
transaction->tra_save_point && transaction->tra_save_point->sav_verb_count)
|
||||
transaction->tra_save_point && transaction->tra_save_point->isChanging())
|
||||
{
|
||||
verb_post(tdbb, transaction, org_rpb, org_rpb->rpb_undo);
|
||||
}
|
||||
|
||||
tdbb->bumpRelStats(RuntimeStatistics::RECORD_UPDATES, relation->rel_id);
|
||||
return;
|
||||
}
|
||||
@ -2849,7 +2848,7 @@ void VIO_modify(thread_db* tdbb, record_param* org_rpb, record_param* new_rpb, j
|
||||
replace_record(tdbb, org_rpb, &stack, transaction);
|
||||
|
||||
if (!(transaction->tra_flags & TRA_system) &&
|
||||
transaction->tra_save_point && transaction->tra_save_point->sav_verb_count)
|
||||
transaction->tra_save_point && transaction->tra_save_point->isChanging())
|
||||
{
|
||||
verb_post(tdbb, transaction, org_rpb, 0);
|
||||
}
|
||||
@ -3058,32 +3057,6 @@ bool VIO_refetch_record(thread_db* tdbb, record_param* rpb, jrd_tra* transaction
|
||||
}
|
||||
|
||||
|
||||
void VIO_start_save_point(thread_db* tdbb, jrd_tra* transaction)
|
||||
{
|
||||
/**************************************
|
||||
*
|
||||
* V I O _ s t a r t _ s a v e _ p o i n t
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* Start a new save point for a transaction.
|
||||
*
|
||||
**************************************/
|
||||
SET_TDBB(tdbb);
|
||||
Savepoint* sav_point = transaction->tra_save_free;
|
||||
|
||||
if (sav_point)
|
||||
transaction->tra_save_free = sav_point->sav_next;
|
||||
else
|
||||
sav_point = FB_NEW_POOL(*transaction->tra_pool) Savepoint(transaction);
|
||||
|
||||
sav_point->sav_number = ++transaction->tra_save_point_number;
|
||||
sav_point->sav_next = transaction->tra_save_point;
|
||||
transaction->tra_save_point = sav_point;
|
||||
}
|
||||
|
||||
|
||||
void VIO_store(thread_db* tdbb, record_param* rpb, jrd_tra* transaction)
|
||||
{
|
||||
/**************************************
|
||||
@ -3466,7 +3439,7 @@ void VIO_store(thread_db* tdbb, record_param* rpb, jrd_tra* transaction)
|
||||
#endif
|
||||
|
||||
if (!(transaction->tra_flags & TRA_system) &&
|
||||
transaction->tra_save_point && transaction->tra_save_point->sav_verb_count)
|
||||
transaction->tra_save_point && transaction->tra_save_point->isChanging())
|
||||
{
|
||||
verb_post(tdbb, transaction, rpb, 0);
|
||||
}
|
||||
@ -3589,91 +3562,6 @@ bool VIO_sweep(thread_db* tdbb, jrd_tra* transaction, TraceSweepEvent* traceSwee
|
||||
}
|
||||
|
||||
|
||||
void VIO_temp_cleanup(jrd_tra* transaction)
|
||||
/**************************************
|
||||
*
|
||||
* V I O _ t e m p _ c l e a n u p
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* Remove undo data for GTT ON COMMIT DELETE ROWS as their data will be released
|
||||
* at transaction end anyway and we don't need to waste time backing it out on
|
||||
* rollback.
|
||||
*
|
||||
**************************************/
|
||||
{
|
||||
Savepoint* sav_point = transaction->tra_save_point;
|
||||
|
||||
for (; sav_point; sav_point = sav_point->sav_next)
|
||||
{
|
||||
for (VerbAction* action = sav_point->sav_verb_actions; action; action = action->vct_next)
|
||||
{
|
||||
if (action->vct_relation->rel_flags & REL_temp_tran)
|
||||
{
|
||||
RecordBitmap::reset(action->vct_records);
|
||||
|
||||
if (action->vct_undo)
|
||||
{
|
||||
if (action->vct_undo->getFirst())
|
||||
{
|
||||
do
|
||||
{
|
||||
action->vct_undo->current().release(transaction);
|
||||
} while (action->vct_undo->getNext());
|
||||
}
|
||||
|
||||
delete action->vct_undo;
|
||||
action->vct_undo = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void VIO_verb_cleanup(thread_db* tdbb, jrd_tra* transaction)
|
||||
{
|
||||
/**************************************
|
||||
*
|
||||
* V I O _ v e r b _ c l e a n u p
|
||||
*
|
||||
**************************************
|
||||
*
|
||||
* Functional description
|
||||
* Cleanup after a verb. If the verb count in the transaction block
|
||||
* is non zero, the verb failed and should be cleaned up. Cleaning
|
||||
* up ordinarily means just backing out the change, but if we have
|
||||
* an old version we created in this transaction, we replace the current
|
||||
* version with old version.
|
||||
*
|
||||
* All changes made by the transaction are kept in one bitmap per
|
||||
* relation. A second bitmap per relation tracks records for which
|
||||
* we have old data. The actual data is kept in a linked list stack.
|
||||
* Note that although a record may be changed several times, it will
|
||||
* have only ONE old value -- the value it had before this verb
|
||||
* started.
|
||||
*
|
||||
**************************************/
|
||||
SET_TDBB(tdbb);
|
||||
|
||||
#ifdef VIO_DEBUG
|
||||
VIO_trace(DEBUG_TRACE,
|
||||
"VIO_verb_cleanup (transaction %" SQUADFORMAT")\n",
|
||||
transaction ? transaction->tra_number : 0);
|
||||
#endif
|
||||
|
||||
if (transaction->tra_save_point->sav_verb_count) // we must rollback this savepoint
|
||||
{
|
||||
transaction->rollbackSavepoint(tdbb);
|
||||
}
|
||||
else
|
||||
{
|
||||
transaction->rollforwardSavepoint(tdbb);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool VIO_writelock(thread_db* tdbb, record_param* org_rpb, jrd_tra* transaction)
|
||||
{
|
||||
/**************************************
|
||||
@ -4705,34 +4593,31 @@ static UndoDataRet get_undo_data(thread_db* tdbb, jrd_tra* transaction,
|
||||
if (!transaction->tra_save_point)
|
||||
return udNone;
|
||||
|
||||
VerbAction* action = transaction->tra_save_point->sav_verb_actions;
|
||||
VerbAction* const action = transaction->tra_save_point->getAction(rpb->rpb_relation);
|
||||
|
||||
for (; action; action = action->vct_next)
|
||||
if (action)
|
||||
{
|
||||
if (action->vct_relation == rpb->rpb_relation)
|
||||
{
|
||||
const SINT64 recno = rpb->rpb_number.getValue();
|
||||
if (!RecordBitmap::test(action->vct_records, recno))
|
||||
return udNone;
|
||||
const SINT64 recno = rpb->rpb_number.getValue();
|
||||
if (!RecordBitmap::test(action->vct_records, recno))
|
||||
return udNone;
|
||||
|
||||
rpb->rpb_runtime_flags |= RPB_undo_read;
|
||||
rpb->rpb_runtime_flags |= RPB_undo_read;
|
||||
|
||||
if (!action->vct_undo || !action->vct_undo->locate(recno))
|
||||
return udForceBack;
|
||||
if (!action->vct_undo || !action->vct_undo->locate(recno))
|
||||
return udForceBack;
|
||||
|
||||
const UndoItem& undo = action->vct_undo->current();
|
||||
const UndoItem& undo = action->vct_undo->current();
|
||||
|
||||
rpb->rpb_runtime_flags |= RPB_undo_data;
|
||||
CCH_RELEASE(tdbb, &rpb->getWindow(tdbb));
|
||||
rpb->rpb_runtime_flags |= RPB_undo_data;
|
||||
CCH_RELEASE(tdbb, &rpb->getWindow(tdbb));
|
||||
|
||||
AutoUndoRecord undoRecord(undo.setupRecord(transaction));
|
||||
AutoUndoRecord undoRecord(undo.setupRecord(transaction));
|
||||
|
||||
Record* const record = VIO_record(tdbb, rpb, undoRecord->getFormat(), pool);
|
||||
record->copyFrom(undoRecord);
|
||||
Record* const record = VIO_record(tdbb, rpb, undoRecord->getFormat(), pool);
|
||||
record->copyFrom(undoRecord);
|
||||
|
||||
rpb->rpb_flags &= ~rpb_deleted;
|
||||
return udExists;
|
||||
}
|
||||
rpb->rpb_flags &= ~rpb_deleted;
|
||||
return udExists;
|
||||
}
|
||||
|
||||
return udNone;
|
||||
@ -5938,31 +5823,11 @@ static void verb_post(thread_db* tdbb,
|
||||
**************************************/
|
||||
SET_TDBB(tdbb);
|
||||
|
||||
Jrd::ContextPoolHolder context(tdbb, transaction->tra_pool);
|
||||
|
||||
// Find action block for relation
|
||||
VerbAction* action;
|
||||
for (action = transaction->tra_save_point->sav_verb_actions; action; action = action->vct_next)
|
||||
{
|
||||
if (action->vct_relation == rpb->rpb_relation)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!action)
|
||||
{
|
||||
if ( (action = transaction->tra_save_point->sav_verb_free) )
|
||||
transaction->tra_save_point->sav_verb_free = action->vct_next;
|
||||
else
|
||||
action = FB_NEW_POOL(*tdbb->getDefaultPool()) VerbAction();
|
||||
|
||||
action->vct_next = transaction->tra_save_point->sav_verb_actions;
|
||||
transaction->tra_save_point->sav_verb_actions = action;
|
||||
action->vct_relation = rpb->rpb_relation;
|
||||
}
|
||||
VerbAction* const action = transaction->tra_save_point->createAction(rpb->rpb_relation);
|
||||
|
||||
if (!RecordBitmap::test(action->vct_records, rpb->rpb_number.getValue()))
|
||||
{
|
||||
RBM_SET(tdbb->getDefaultPool(), &action->vct_records, rpb->rpb_number.getValue());
|
||||
RBM_SET(transaction->tra_pool, &action->vct_records, rpb->rpb_number.getValue());
|
||||
|
||||
if (old_data)
|
||||
{
|
||||
@ -5970,7 +5835,10 @@ static void verb_post(thread_db* tdbb,
|
||||
// savepoint hasn't seen this record before.
|
||||
|
||||
if (!action->vct_undo)
|
||||
action->vct_undo = FB_NEW UndoItemTree(tdbb->getDefaultPool());
|
||||
{
|
||||
action->vct_undo =
|
||||
FB_NEW_POOL(*transaction->tra_pool) UndoItemTree(*transaction->tra_pool);
|
||||
}
|
||||
|
||||
action->vct_undo->add(UndoItem(transaction, rpb->rpb_number, old_data));
|
||||
}
|
||||
@ -5980,66 +5848,6 @@ static void verb_post(thread_db* tdbb,
|
||||
// Double update us posting. The old_data will not be used,
|
||||
// so make sure we garbage collect before we lose track of the
|
||||
// in-place-updated record.
|
||||
action->garbage_collect_idx_lite(tdbb, transaction, rpb->rpb_number.getValue(), action, old_data);
|
||||
action->garbageCollectIdxLite(tdbb, transaction, rpb->rpb_number.getValue(), action, old_data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------
|
||||
|
||||
|
||||
AutoSavePoint::AutoSavePoint(thread_db* tdbb, jrd_tra* aTransaction)
|
||||
: transaction(aTransaction),
|
||||
released(false)
|
||||
{
|
||||
VIO_start_save_point(tdbb, transaction);
|
||||
}
|
||||
|
||||
AutoSavePoint::~AutoSavePoint()
|
||||
{
|
||||
thread_db* tdbb = JRD_get_thread_data();
|
||||
|
||||
if (!(tdbb->getDatabase()->dbb_flags & DBB_bugcheck))
|
||||
{
|
||||
if (released)
|
||||
transaction->rollforwardSavepoint(tdbb);
|
||||
else
|
||||
transaction->rollbackSavepoint(tdbb);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// class StableCursorSavePoint
|
||||
|
||||
StableCursorSavePoint::StableCursorSavePoint(thread_db* tdbb, jrd_tra* transaction, bool start)
|
||||
: m_tdbb(tdbb),
|
||||
m_tran(transaction),
|
||||
m_number(0)
|
||||
{
|
||||
if (!start)
|
||||
return;
|
||||
|
||||
if (m_tran == m_tdbb->getAttachment()->getSysTransaction())
|
||||
return;
|
||||
|
||||
const Savepoint* save_point = m_tran->tra_save_point;
|
||||
if (!save_point)
|
||||
return;
|
||||
|
||||
VIO_start_save_point(m_tdbb, m_tran);
|
||||
m_number = m_tran->tra_save_point->sav_number;
|
||||
}
|
||||
|
||||
|
||||
void StableCursorSavePoint::release()
|
||||
{
|
||||
if (!m_number)
|
||||
return;
|
||||
|
||||
while (m_tran->tra_save_point && m_tran->tra_save_point->sav_number >= m_number)
|
||||
{
|
||||
m_tran->rollforwardSavepoint(m_tdbb);
|
||||
}
|
||||
|
||||
m_number = 0;
|
||||
}
|
||||
|
@ -55,50 +55,9 @@ void VIO_modify(Jrd::thread_db*, Jrd::record_param*, Jrd::record_param*, Jrd::jr
|
||||
bool VIO_next_record(Jrd::thread_db*, Jrd::record_param*, Jrd::jrd_tra*, MemoryPool*, bool);
|
||||
Jrd::Record* VIO_record(Jrd::thread_db*, Jrd::record_param*, const Jrd::Format*, MemoryPool*);
|
||||
bool VIO_refetch_record(Jrd::thread_db*, Jrd::record_param*, Jrd::jrd_tra*, bool, bool);
|
||||
void VIO_start_save_point(Jrd::thread_db*, Jrd::jrd_tra*);
|
||||
void VIO_store(Jrd::thread_db*, Jrd::record_param*, Jrd::jrd_tra*);
|
||||
bool VIO_sweep(Jrd::thread_db*, Jrd::jrd_tra*, Jrd::TraceSweepEvent*);
|
||||
void VIO_verb_cleanup(Jrd::thread_db*, Jrd::jrd_tra*);
|
||||
void VIO_temp_cleanup(Jrd::jrd_tra*);
|
||||
void VIO_garbage_collect_idx(Jrd::thread_db*, Jrd::jrd_tra*, Jrd::record_param*, Jrd::Record*);
|
||||
void VIO_update_in_place(Jrd::thread_db*, Jrd::jrd_tra*, Jrd::record_param*, Jrd::record_param*);
|
||||
|
||||
namespace Jrd
|
||||
{
|
||||
// Starts a savepoint and rollback it in destructor if release() is not called.
|
||||
class AutoSavePoint
|
||||
{
|
||||
public:
|
||||
AutoSavePoint(thread_db* tdbb, jrd_tra* aTransaction);
|
||||
~AutoSavePoint();
|
||||
|
||||
void release()
|
||||
{
|
||||
released = true;
|
||||
}
|
||||
|
||||
private:
|
||||
jrd_tra* transaction;
|
||||
bool released;
|
||||
};
|
||||
|
||||
class StableCursorSavePoint
|
||||
{
|
||||
public:
|
||||
StableCursorSavePoint(thread_db* tdbb, jrd_tra* transaction, bool start);
|
||||
|
||||
~StableCursorSavePoint()
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
void release();
|
||||
|
||||
private:
|
||||
thread_db* m_tdbb;
|
||||
jrd_tra* m_tran;
|
||||
SLONG m_number;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // JRD_VIO_PROTO_H
|
||||
|
Loading…
Reference in New Issue
Block a user