8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-23 02:03:04 +01:00

Refactored PSQL savepoint envelopes. Made the savepoint undo logic consistent across the codebase. Added assertions to prevent releasing the savepoint that must be undone.

This commit is contained in:
Dmitry Yemanov 2020-09-25 11:15:31 +03:00
parent 01f3316380
commit d50aa0918d
10 changed files with 191 additions and 187 deletions

View File

@ -1801,6 +1801,8 @@ bool RseBoolNode::execute(thread_db* tdbb, jrd_req* request) const
subQuery->close(tdbb); subQuery->close(tdbb);
savePoint.release();
if (blrOp == blr_any || blrOp == blr_unique) if (blrOp == blr_any || blrOp == blr_unique)
request->req_flags &= ~req_null; request->req_flags &= ~req_null;

View File

@ -11327,11 +11327,11 @@ dsc* SubQueryNode::execute(thread_db* tdbb, jrd_req* request) const
ULONG flag = req_null; ULONG flag = req_null;
StableCursorSavePoint savePoint(tdbb, request->req_transaction,
blrOp == blr_via && ownSavepoint);
try try
{ {
StableCursorSavePoint savePoint(tdbb, request->req_transaction,
blrOp == blr_via && ownSavepoint);
subQuery->open(tdbb); subQuery->open(tdbb);
SLONG count = 0; SLONG count = 0;
@ -11438,6 +11438,9 @@ dsc* SubQueryNode::execute(thread_db* tdbb, jrd_req* request) const
// Close stream and return value. // Close stream and return value.
subQuery->close(tdbb); subQuery->close(tdbb);
savePoint.release();
request->req_flags &= ~req_null; request->req_flags &= ~req_null;
request->req_flags |= flag; request->req_flags |= flag;
@ -12995,6 +12998,7 @@ dsc* UdfCallNode::execute(thread_db* tdbb, jrd_req* request) const
} }
jrd_tra* transaction = request->req_transaction; jrd_tra* transaction = request->req_transaction;
const SavNumber savNumber = transaction->tra_save_point ? const SavNumber savNumber = transaction->tra_save_point ?
transaction->tra_save_point->getNumber() : 0; transaction->tra_save_point->getNumber() : 0;
@ -13018,7 +13022,7 @@ dsc* UdfCallNode::execute(thread_db* tdbb, jrd_req* request) const
EXE_receive(tdbb, funcRequest, 1, outMsgLength, outMsg); EXE_receive(tdbb, funcRequest, 1, outMsgLength, outMsg);
// Clean up all savepoints started during execution of the procedure. // Clean up all savepoints started during execution of the function
if (!(transaction->tra_flags & TRA_system)) if (!(transaction->tra_flags & TRA_system))
{ {

View File

@ -1402,7 +1402,6 @@ public:
TYPE_RECEIVE, TYPE_RECEIVE,
TYPE_RETURN, TYPE_RETURN,
TYPE_SAVEPOINT, TYPE_SAVEPOINT,
TYPE_SAVEPOINT_ENCLOSE,
TYPE_SELECT, TYPE_SELECT,
TYPE_SESSION_MANAGEMENT_WRAPPER, TYPE_SESSION_MANAGEMENT_WRAPPER,
TYPE_SET_GENERATOR, TYPE_SET_GENERATOR,
@ -1565,28 +1564,6 @@ public:
}; };
// Add savepoint pair of nodes to statement having error handlers.
class SavepointEncloseNode : public TypedNode<DsqlOnlyStmtNode, StmtNode::TYPE_SAVEPOINT_ENCLOSE>
{
public:
explicit SavepointEncloseNode(MemoryPool& pool, StmtNode* aStmt)
: TypedNode<DsqlOnlyStmtNode, StmtNode::TYPE_SAVEPOINT_ENCLOSE>(pool),
stmt(aStmt)
{
}
public:
static StmtNode* make(MemoryPool& pool, DsqlCompilerScratch* dsqlScratch, StmtNode* node);
public:
virtual Firebird::string internalPrint(NodePrinter& printer) const;
virtual void genBlr(DsqlCompilerScratch* dsqlScratch);
private:
NestConst<StmtNode> stmt;
};
struct ScaledNumber struct ScaledNumber
{ {
FB_UINT64 number; FB_UINT64 number;

View File

@ -293,39 +293,6 @@ string StmtNode::internalPrint(NodePrinter& printer) const
//-------------------- //--------------------
StmtNode* SavepointEncloseNode::make(MemoryPool& pool, DsqlCompilerScratch* dsqlScratch, StmtNode* node)
{
if (dsqlScratch->errorHandlers)
{
node = FB_NEW_POOL(pool) SavepointEncloseNode(pool, node);
node->dsqlPass(dsqlScratch);
}
return node;
}
string SavepointEncloseNode::internalPrint(NodePrinter& printer) const
{
DsqlOnlyStmtNode::internalPrint(printer);
NODE_PRINT(printer, stmt);
return "SavepointEncloseNode";
}
void SavepointEncloseNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{
dsqlScratch->appendUChar(blr_begin);
dsqlScratch->appendUChar(blr_start_savepoint);
stmt->genBlr(dsqlScratch);
dsqlScratch->appendUChar(blr_end_savepoint);
dsqlScratch->appendUChar(blr_end);
}
//--------------------
static RegisterNode<AssignmentNode> regAssignmentNode({blr_assignment}); static RegisterNode<AssignmentNode> regAssignmentNode({blr_assignment});
DmlNode* AssignmentNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/) DmlNode* AssignmentNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
@ -690,17 +657,17 @@ const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
savNumber = *request->getImpure<SavNumber>(impureOffset); savNumber = *request->getImpure<SavNumber>(impureOffset);
// Since there occurred an error (req_unwind), undo all savepoints // Since there occurred an error (req_unwind), undo all savepoints
// up to, _but not including_, the savepoint of this block. // up to, *but not including*, the savepoint of this block.
// That's why transaction->rollbackToSavepoint() cannot be used here // That's why transaction->rollbackToSavepoint() cannot be used here.
// The savepoint of this block will be dealt with below. // The savepoint of this block will be dealt with below.
// Do this only if error handlers exist. If not - leave rollbacking to caller node // Do this only if error handlers exist. Otherwise, leave undo up to callers.
while (transaction->tra_save_point && while (transaction->tra_save_point &&
savNumber < transaction->tra_save_point->getNumber() && transaction->tra_save_point->getNumber() > savNumber &&
transaction->tra_save_point->getNext() && transaction->tra_save_point->getNext() &&
savNumber < transaction->tra_save_point->getNext()->getNumber()) transaction->tra_save_point->getNext()->getNumber() > savNumber)
{ {
transaction->rollforwardSavepoint(tdbb); transaction->rollforwardSavepoint(tdbb, false);
} }
// There can be no savepoints above the given one // There can be no savepoints above the given one
@ -767,8 +734,8 @@ const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
} }
} }
} }
// The error is dealt with by the application, cleanup
// this block's savepoint. // The error is dealt with by the application, cleanup our savepoint
if (handled && !(transaction->tra_flags & TRA_system)) if (handled && !(transaction->tra_flags & TRA_system))
{ {
@ -783,9 +750,8 @@ const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
// Because of this following assert is commented out // Because of this following assert is commented out
//fb_assert(transaction->tra_save_point && transaction->tra_save_point->getNumber() == savNumber); //fb_assert(transaction->tra_save_point && transaction->tra_save_point->getNumber() == savNumber);
for (const Savepoint* save_point = transaction->tra_save_point; while (transaction->tra_save_point &&
save_point && savNumber <= save_point->getNumber(); transaction->tra_save_point->getNumber() >= savNumber)
save_point = transaction->tra_save_point)
{ {
transaction->rollforwardSavepoint(tdbb); transaction->rollforwardSavepoint(tdbb);
} }
@ -804,9 +770,9 @@ const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
savNumber = *request->getImpure<SavNumber>(impureOffset); savNumber = *request->getImpure<SavNumber>(impureOffset);
// rollforward all savepoints // rollforward all savepoints
for (const Savepoint* save_point = transaction->tra_save_point; while (transaction->tra_save_point &&
save_point && save_point->getNext() && savNumber <= save_point->getNumber(); transaction->tra_save_point->getNext() &&
save_point = transaction->tra_save_point) transaction->tra_save_point->getNumber() >= savNumber)
{ {
transaction->rollforwardSavepoint(tdbb); transaction->rollforwardSavepoint(tdbb);
} }
@ -2579,7 +2545,6 @@ const StmtNode* EraseNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
if (request->req_operation == jrd_req::req_unwind) if (request->req_operation == jrd_req::req_unwind)
retNode = parentStmt; retNode = parentStmt;
else if (request->req_operation == jrd_req::req_return && subStatement) else if (request->req_operation == jrd_req::req_return && subStatement)
{ {
if (!exeState->topNode) if (!exeState->topNode)
@ -3262,6 +3227,7 @@ void ExecProcedureNode::executeProcedure(thread_db* tdbb, jrd_req* request) cons
} }
jrd_tra* transaction = request->req_transaction; jrd_tra* transaction = request->req_transaction;
const SavNumber savNumber = transaction->tra_save_point ? const SavNumber savNumber = transaction->tra_save_point ?
transaction->tra_save_point->getNumber() : 0; transaction->tra_save_point->getNumber() : 0;
@ -3287,7 +3253,7 @@ void ExecProcedureNode::executeProcedure(thread_db* tdbb, jrd_req* request) cons
EXE_receive(tdbb, procRequest, 1, outMsgLength, outMsg); EXE_receive(tdbb, procRequest, 1, outMsgLength, outMsg);
// Clean up all savepoints started during execution of the procedure. // Clean up all savepoints started during execution of the procedure
if (!(transaction->tra_flags & TRA_system)) if (!(transaction->tra_flags & TRA_system))
{ {
@ -4071,7 +4037,7 @@ const StmtNode* InAutonomousTransactionNode::execute(thread_db* tdbb, jrd_req* r
transaction->tra_save_point->isSystem() && transaction->tra_save_point->isSystem() &&
transaction->tra_save_point->isChanging()) transaction->tra_save_point->isChanging())
{ {
transaction->rollforwardSavepoint(tdbb); transaction->rollforwardSavepoint(tdbb, false);
} }
{ // scope { // scope
@ -4096,7 +4062,7 @@ const StmtNode* InAutonomousTransactionNode::execute(thread_db* tdbb, jrd_req* r
transaction->tra_save_point->isSystem() && transaction->tra_save_point->isSystem() &&
transaction->tra_save_point->isChanging()) transaction->tra_save_point->isChanging())
{ {
transaction->rollforwardSavepoint(tdbb); transaction->rollforwardSavepoint(tdbb, false);
} }
AutoSetRestore2<jrd_req*, thread_db> autoNullifyRequest( AutoSetRestore2<jrd_req*, thread_db> autoNullifyRequest(
@ -5089,11 +5055,13 @@ const StmtNode* ForNode::execute(thread_db* tdbb, jrd_req* request, ExeState* /*
} }
cursor->open(tdbb); cursor->open(tdbb);
request->req_records_affected.clear(); request->req_records_affected.clear();
// fall into // fall into
case jrd_req::req_return: case jrd_req::req_return:
if (stall) if (stall)
return stall; return stall;
// fall into // fall into
case jrd_req::req_sync: case jrd_req::req_sync:
@ -5130,36 +5098,47 @@ const StmtNode* ForNode::execute(thread_db* tdbb, jrd_req* request, ExeState* /*
restartRequest(request, transaction); restartRequest(request, transaction);
request->req_operation = jrd_req::req_return; request->req_operation = jrd_req::req_return;
// fall into
case jrd_req::req_unwind: if (impure->savepoint)
{
const LabelNode* label = nodeAs<LabelNode>(parentStmt.getObject());
if (label && request->req_label == label->labelNumber &&
(request->req_flags & req_continue_loop))
{ {
request->req_flags &= ~req_continue_loop; while (transaction->tra_save_point &&
request->req_operation = jrd_req::req_sync; transaction->tra_save_point->getNumber() >= impure->savepoint)
return this; {
transaction->rollforwardSavepoint(tdbb);
}
} }
// fall into // fall into
}
default: default:
{ {
const SavNumber savNumber = impure->savepoint; if (request->req_operation == jrd_req::req_unwind)
if (savNumber)
{ {
while (transaction->tra_save_point && if (request->req_flags & (req_leave | req_continue_loop))
transaction->tra_save_point->getNumber() >= savNumber)
{ {
if (transaction->tra_save_point->isChanging()) // we must rollback this savepoint const auto label = nodeAs<LabelNode>(parentStmt.getObject());
transaction->rollbackSavepoint(tdbb);
else // If CONTINUE matches our label, restart fetching records
transaction->rollforwardSavepoint(tdbb);
if (label && request->req_label == label->labelNumber &&
(request->req_flags & req_continue_loop))
{
request->req_flags &= ~req_continue_loop;
request->req_operation = jrd_req::req_sync;
return this;
}
// Otherwise (BREAK/LEAVE/EXIT or mismatched CONTINUE), we should unwind further.
// Thus cleanup our savepoint.
if (impure->savepoint)
{
while (transaction->tra_save_point &&
transaction->tra_save_point->getNumber() >= impure->savepoint)
{
transaction->rollforwardSavepoint(tdbb);
}
}
} }
} }
@ -8333,72 +8312,94 @@ void ReturnNode::genBlr(DsqlCompilerScratch* dsqlScratch)
//-------------------- //--------------------
static RegisterNode<SavePointNode> regSavePointNode({blr_start_savepoint, blr_end_savepoint}); static RegisterNode<SavepointEncloseNode> regSavePointNode({blr_start_savepoint});
DmlNode* SavePointNode::parse(thread_db* /*tdbb*/, MemoryPool& pool, CompilerScratch* /*csb*/, const UCHAR blrOp) DmlNode* SavepointEncloseNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR /*blrOp*/)
{ {
SavePointNode* node = FB_NEW_POOL(pool) SavePointNode(pool, blrOp); const auto statement = PAR_parse_stmt(tdbb, csb);
const auto node = FB_NEW_POOL(pool) SavepointEncloseNode(pool, statement);
// skip blr_end_savepoint
const auto blrOp = csb->csb_blr_reader.getByte();
fb_assert(blrOp == blr_end_savepoint);
return node; return node;
} }
SavePointNode* SavePointNode::dsqlPass(DsqlCompilerScratch* /*dsqlScratch*/) StmtNode* SavepointEncloseNode::make(MemoryPool& pool, DsqlCompilerScratch* dsqlScratch, StmtNode* node)
{ {
return this; // Add savepoint wrapper around the statement having error handlers
return dsqlScratch->errorHandlers ?
FB_NEW_POOL(pool) SavepointEncloseNode(pool, node) : node;
} }
string SavePointNode::internalPrint(NodePrinter& printer) const SavepointEncloseNode* SavepointEncloseNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
const auto node = FB_NEW_POOL(dsqlScratch->getPool()) SavepointEncloseNode(dsqlScratch->getPool(), statement);
node->statement = statement->dsqlPass(dsqlScratch);
return node;
}
string SavepointEncloseNode::internalPrint(NodePrinter& printer) const
{ {
StmtNode::internalPrint(printer); StmtNode::internalPrint(printer);
NODE_PRINT(printer, blrOp); NODE_PRINT(printer, statement);
return "SavePointNode"; return "SavepointEncloseNode";
} }
void SavePointNode::genBlr(DsqlCompilerScratch* dsqlScratch) void SavepointEncloseNode::genBlr(DsqlCompilerScratch* dsqlScratch)
{ {
dsqlScratch->appendUChar(blrOp); dsqlScratch->appendUChar(blr_begin);
dsqlScratch->appendUChar(blr_start_savepoint);
statement->genBlr(dsqlScratch);
dsqlScratch->appendUChar(blr_end_savepoint);
dsqlScratch->appendUChar(blr_end);
} }
const StmtNode* SavePointNode::execute(thread_db* tdbb, jrd_req* request, ExeState* exeState) const SavepointEncloseNode* SavepointEncloseNode::pass1(thread_db* tdbb, CompilerScratch* csb)
{ {
jrd_tra* transaction = request->req_transaction; doPass1(tdbb, csb, statement.getAddress());
return this;
}
switch (blrOp) SavepointEncloseNode* SavepointEncloseNode::pass2(thread_db* tdbb, CompilerScratch* csb)
{
doPass2(tdbb, csb, statement.getAddress(), this);
impureOffset = csb->allocImpure<SavNumber>();
return this;
}
const StmtNode* SavepointEncloseNode::execute(thread_db* tdbb, jrd_req* request, ExeState* /*exeState*/) const
{
const auto transaction = request->req_transaction;
if (request->req_operation == jrd_req::req_evaluate)
{ {
case blr_start_savepoint: if (!(transaction->tra_flags & TRA_system))
if (request->req_operation == jrd_req::req_evaluate) {
const auto savepoint = transaction->startSavepoint();
*request->getImpure<SavNumber>(impureOffset) = savepoint->getNumber();
}
return statement;
}
if (request->req_operation == jrd_req::req_return)
{
if (!(transaction->tra_flags & TRA_system))
{
const auto savNumber = *request->getImpure<SavNumber>(impureOffset);
while (transaction->tra_save_point &&
transaction->tra_save_point->getNumber() >= savNumber)
{ {
// Start a save point. transaction->rollforwardSavepoint(tdbb);
if (!(transaction->tra_flags & TRA_system))
transaction->startSavepoint();
request->req_operation = jrd_req::req_return;
} }
break; }
case blr_end_savepoint:
if (request->req_operation == jrd_req::req_evaluate ||
request->req_operation == jrd_req::req_unwind)
{
// If any requested modify/delete/insert ops have completed, forget them.
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.
if (exeState->errorPending)
transaction->rollbackSavepoint(tdbb);
else
transaction->rollforwardSavepoint(tdbb);
}
if (request->req_operation == jrd_req::req_evaluate)
request->req_operation = jrd_req::req_return;
}
break;
default:
fb_assert(false);
} }
return parentStmt; return parentStmt;

View File

@ -1488,37 +1488,31 @@ public:
}; };
class SavePointNode : public TypedNode<StmtNode, StmtNode::TYPE_SAVEPOINT> class SavepointEncloseNode : public TypedNode<StmtNode, StmtNode::TYPE_SAVEPOINT>
{ {
public: public:
explicit SavePointNode(MemoryPool& pool, UCHAR aBlrOp) explicit SavepointEncloseNode(MemoryPool& pool, StmtNode* stmt)
: TypedNode<StmtNode, StmtNode::TYPE_SAVEPOINT>(pool), : TypedNode<StmtNode, StmtNode::TYPE_SAVEPOINT>(pool),
blrOp(aBlrOp) statement(stmt)
{ {
fb_assert(blrOp == blr_start_savepoint || blrOp == blr_end_savepoint);
} }
public: public:
static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp); static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp);
static StmtNode* make(MemoryPool& pool, DsqlCompilerScratch* dsqlScratch, StmtNode* node);
virtual Firebird::string internalPrint(NodePrinter& printer) const; virtual Firebird::string internalPrint(NodePrinter& printer) const;
virtual SavePointNode* dsqlPass(DsqlCompilerScratch* dsqlScratch); virtual SavepointEncloseNode* dsqlPass(DsqlCompilerScratch* dsqlScratch);
virtual void genBlr(DsqlCompilerScratch* dsqlScratch); virtual void genBlr(DsqlCompilerScratch* dsqlScratch);
virtual SavePointNode* pass1(thread_db* /*tdbb*/, CompilerScratch* /*csb*/) virtual SavepointEncloseNode* pass1(thread_db* tdbb, CompilerScratch* csb);
{ virtual SavepointEncloseNode* pass2(thread_db* tdbb, CompilerScratch* csb);
return this;
}
virtual SavePointNode* pass2(thread_db* /*tdbb*/, CompilerScratch* /*csb*/)
{
return this;
}
virtual const StmtNode* execute(thread_db* tdbb, jrd_req* request, ExeState* exeState) const; virtual const StmtNode* execute(thread_db* tdbb, jrd_req* request, ExeState* exeState) const;
public: public:
UCHAR blrOp; NestConst<StmtNode> statement;
}; };

View File

@ -855,12 +855,14 @@ void DsqlDmlRequest::execute(thread_db* tdbb, jrd_tra** traHandle,
if (req_transaction && (req_transaction->tra_flags & TRA_read_consistency) && if (req_transaction && (req_transaction->tra_flags & TRA_read_consistency) &&
statement->getType() != DsqlCompiledStatement::TYPE_SAVEPOINT) statement->getType() != DsqlCompiledStatement::TYPE_SAVEPOINT)
{ {
AutoSavePoint savePoint(tdbb, req_transaction);
req_request->req_flags &= ~req_update_conflict; req_request->req_flags &= ~req_update_conflict;
int numTries = 0; int numTries = 0;
const int MAX_RESTARTS = 10; const int MAX_RESTARTS = 10;
while (true) while (true)
{ {
AutoSavePoint savePoint(tdbb, req_transaction);
// Don't set req_restart_ready flas at last attempt to restart request. // Don't set req_restart_ready flas at last attempt to restart request.
// It allows to raise update conflict error (if any) as usual and // It allows to raise update conflict error (if any) as usual and
// handle error by PSQL handler. // handle error by PSQL handler.
@ -893,6 +895,7 @@ void DsqlDmlRequest::execute(thread_db* tdbb, jrd_tra** traHandle,
ERRD_post_warning(Arg::Warning(isc_random) << Arg::Str(s)); ERRD_post_warning(Arg::Warning(isc_random) << Arg::Str(s));
} }
#endif #endif
savePoint.release(); // everything is ok
break; break;
} }
@ -902,8 +905,9 @@ void DsqlDmlRequest::execute(thread_db* tdbb, jrd_tra** traHandle,
req_transaction->tra_flags &= ~TRA_ex_restart; req_transaction->tra_flags &= ~TRA_ex_restart;
fb_utils::init_status(tdbb->tdbb_status_vector); fb_utils::init_status(tdbb->tdbb_status_vector);
req_transaction->rollbackSavepoint(tdbb, true); // Undo current savepoint but preserve already taken locks.
req_transaction->startSavepoint(tdbb); // Savepoint will be restarted at the next loop iteration.
savePoint.rollback(true);
numTries++; numTries++;
if (numTries >= MAX_RESTARTS) if (numTries >= MAX_RESTARTS)
@ -913,7 +917,6 @@ void DsqlDmlRequest::execute(thread_db* tdbb, jrd_tra** traHandle,
"\tQuery:\n%s\n", numTries, req_request->getStatement()->sqlText->c_str() ); "\tQuery:\n%s\n", numTries, req_request->getStatement()->sqlText->c_str() );
} }
} }
savePoint.release(); // everything is ok
} else { } else {
doExecute(tdbb, traHandle, inMetadata, inMsg, outMetadata, outMsg, singleton); doExecute(tdbb, traHandle, inMetadata, inMsg, outMetadata, outMsg, singleton);
} }

View File

@ -621,22 +621,44 @@ Savepoint* Savepoint::release(Savepoint* prior)
// AutoSavePoint implementation // AutoSavePoint implementation
AutoSavePoint::AutoSavePoint(thread_db* tdbb, jrd_tra* trans) AutoSavePoint::AutoSavePoint(thread_db* tdbb, jrd_tra* trans)
: m_tdbb(tdbb), m_transaction(trans), m_released(false) : m_tdbb(tdbb), m_transaction(trans), m_number(0)
{ {
trans->startSavepoint(); const auto savepoint = trans->startSavepoint();
m_number = savepoint->getNumber();
} }
AutoSavePoint::~AutoSavePoint() AutoSavePoint::~AutoSavePoint()
{ {
if (!(m_tdbb->getDatabase()->dbb_flags & DBB_bugcheck)) if (m_number && !(m_tdbb->getDatabase()->dbb_flags & DBB_bugcheck))
{ {
if (m_released) fb_assert(m_transaction->tra_save_point);
m_transaction->rollforwardSavepoint(m_tdbb); fb_assert(m_transaction->tra_save_point->getNumber() == m_number);
else m_transaction->rollbackSavepoint(m_tdbb);
m_transaction->rollbackSavepoint(m_tdbb);
} }
} }
void AutoSavePoint::release()
{
if (!m_number)
return;
fb_assert(m_transaction->tra_save_point);
fb_assert(m_transaction->tra_save_point->getNumber() == m_number);
m_transaction->rollforwardSavepoint(m_tdbb);
m_number = 0;
}
void AutoSavePoint::rollback(bool preserveLocks)
{
if (!m_number)
return;
fb_assert(m_transaction->tra_save_point);
fb_assert(m_transaction->tra_save_point->getNumber() == m_number);
m_transaction->rollbackSavepoint(m_tdbb, preserveLocks);
m_number = 0;
}
// StableCursorSavePoint implementation // StableCursorSavePoint implementation
@ -652,7 +674,7 @@ StableCursorSavePoint::StableCursorSavePoint(thread_db* tdbb, jrd_tra* trans, bo
if (!trans->tra_save_point) if (!trans->tra_save_point)
return; return;
const Savepoint* const savepoint = trans->startSavepoint(); const auto savepoint = trans->startSavepoint();
m_number = savepoint->getNumber(); m_number = savepoint->getNumber();
} }
@ -662,8 +684,11 @@ void StableCursorSavePoint::release()
if (!m_number) if (!m_number)
return; return;
while (m_transaction->tra_save_point && m_transaction->tra_save_point->getNumber() >= m_number) while (m_transaction->tra_save_point &&
m_transaction->tra_save_point->getNumber() >= m_number)
{
m_transaction->rollforwardSavepoint(m_tdbb); m_transaction->rollforwardSavepoint(m_tdbb);
}
m_number = 0; m_number = 0;
} }

View File

@ -324,8 +324,8 @@ namespace Jrd
VerbAction* m_freeActions; // free verb actions VerbAction* m_freeActions; // free verb actions
}; };
// Start a savepoint and rollback it in destructor,
// Starts a savepoint and rollback it in destructor if release() is not called // unless it was released / rolled back explicitly
class AutoSavePoint class AutoSavePoint
{ {
@ -333,26 +333,22 @@ namespace Jrd
AutoSavePoint(thread_db* tdbb, jrd_tra* trans); AutoSavePoint(thread_db* tdbb, jrd_tra* trans);
~AutoSavePoint(); ~AutoSavePoint();
void release() void release();
{ void rollback(bool preserveLocks = false);
m_released = true;
}
private: private:
thread_db* const m_tdbb; thread_db* const m_tdbb;
jrd_tra* const m_transaction; jrd_tra* const m_transaction;
bool m_released; SavNumber m_number;
}; };
// Conditional savepoint used to ensure cursor stability in sub-queries
class StableCursorSavePoint class StableCursorSavePoint
{ {
public: public:
StableCursorSavePoint(thread_db* tdbb, jrd_tra* trans, bool start); StableCursorSavePoint(thread_db* tdbb, jrd_tra* trans, bool start);
~StableCursorSavePoint() {} // undo is left up to the callers
~StableCursorSavePoint()
{
release();
}
void release(); void release();

View File

@ -3896,14 +3896,14 @@ void jrd_tra::rollbackToSavepoint(thread_db* tdbb, SavNumber number)
* *
**************************************/ **************************************/
{ {
// Merge all but one folowing savepoints into one // Merge all savepoints (except the given one) into a single one
while (tra_save_point && tra_save_point->getNumber() > number && while (tra_save_point && tra_save_point->getNumber() > number &&
tra_save_point->getNext() && tra_save_point->getNext()->getNumber() >= number) tra_save_point->getNext() && tra_save_point->getNext()->getNumber() >= number)
{ {
rollforwardSavepoint(tdbb); rollforwardSavepoint(tdbb, false);
} }
// Check that savepoint with given number really exists // Check that savepoint with the given number really exists
fb_assert(tra_save_point && tra_save_point->getNumber() == number); fb_assert(tra_save_point && tra_save_point->getNumber() == number);
if (tra_save_point && tra_save_point->getNumber() >= number) // second line of defence if (tra_save_point && tra_save_point->getNumber() >= number) // second line of defence
@ -3915,7 +3915,7 @@ void jrd_tra::rollbackToSavepoint(thread_db* tdbb, SavNumber number)
} }
void jrd_tra::rollforwardSavepoint(thread_db* tdbb) void jrd_tra::rollforwardSavepoint(thread_db* tdbb, bool assertChanging)
/************************************** /**************************************
* *
* r o 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
@ -3929,6 +3929,8 @@ void jrd_tra::rollforwardSavepoint(thread_db* tdbb)
{ {
if (tra_save_point && !(tra_flags & TRA_system)) if (tra_save_point && !(tra_flags & TRA_system))
{ {
fb_assert(!assertChanging || !tra_save_point->isChanging());
REPL_save_cleanup(tdbb, this, tra_save_point, false); REPL_save_cleanup(tdbb, this, tra_save_point, false);
Jrd::ContextPoolHolder context(tdbb, tra_pool); Jrd::ContextPoolHolder context(tdbb, tra_pool);

View File

@ -389,7 +389,7 @@ public:
Savepoint* startSavepoint(bool root = false); Savepoint* startSavepoint(bool root = false);
void rollbackSavepoint(thread_db* tdbb, bool preserveLocks = false); void rollbackSavepoint(thread_db* tdbb, bool preserveLocks = false);
void rollbackToSavepoint(thread_db* tdbb, SavNumber number); void rollbackToSavepoint(thread_db* tdbb, SavNumber number);
void rollforwardSavepoint(thread_db* tdbb); void rollforwardSavepoint(thread_db* tdbb, bool assertChanging = true);
DbCreatorsList* getDbCreatorsList(); DbCreatorsList* getDbCreatorsList();
void checkBlob(thread_db* tdbb, const bid* blob_id, jrd_fld* fld, bool punt); void checkBlob(thread_db* tdbb, const bid* blob_id, jrd_fld* fld, bool punt);