From: Frazer Clement Date: May 25 2011 1:19pm Subject: bzr commit into mysql-5.1-telco-7.0 branch (frazer.clement:4418) List-Archive: http://lists.mysql.com/commits/138073 Message-Id: <201105251319.p4PDJFEm018577@acsmt358.oracle.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============7821843858136016303==" --===============7821843858136016303== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At file:///home/frazer/bzr/mysql-5.1-telco-7.0/ based on revid:jonas@stripped 4418 Frazer Clement 2011-05-25 WL5353 Primary Cluster Conflict Resolution Implementation of refreshTuple() mechanism. A new operation type, refreshTuple() is defined. When executed, it causes an NdbApi data change event to be generated for the affected row. If the row does not exist, a DELETE event, with the primary key, is generated. If the row does exist, an INSERT event, with the primary key and all values is generated. A refreshTuple() operation must be the last operation on a particular tuple in a transaction. An error will be returned if any other operation (including refreshTuple) is defined on a refreshed row in a transaction. refreshTuple() does not currently support tables with BLOB columns. modified: storage/ndb/include/kernel/kernel_types.h storage/ndb/include/ndb_version.h.in storage/ndb/include/ndbapi/NdbOperation.hpp storage/ndb/include/ndbapi/NdbTransaction.hpp storage/ndb/src/common/debugger/signaldata/TcKeyReq.cpp storage/ndb/src/kernel/blocks/dbacc/Dbacc.hpp storage/ndb/src/kernel/blocks/dbacc/DbaccMain.cpp storage/ndb/src/kernel/blocks/dblqh/Dblqh.hpp storage/ndb/src/kernel/blocks/dblqh/DblqhMain.cpp storage/ndb/src/kernel/blocks/dbtc/Dbtc.hpp storage/ndb/src/kernel/blocks/dbtc/DbtcMain.cpp storage/ndb/src/kernel/blocks/dbtup/Dbtup.hpp storage/ndb/src/kernel/blocks/dbtup/DbtupCommit.cpp storage/ndb/src/kernel/blocks/dbtup/DbtupExecQuery.cpp storage/ndb/src/kernel/blocks/dbtup/DbtupTrigger.cpp storage/ndb/src/ndbapi/NdbOperationExec.cpp storage/ndb/src/ndbapi/NdbTransaction.cpp storage/ndb/src/ndbapi/ndberror.c storage/ndb/test/include/HugoOperations.hpp storage/ndb/test/include/HugoTransactions.hpp storage/ndb/test/ndbapi/testBasic.cpp storage/ndb/test/ndbapi/testIndex.cpp storage/ndb/test/run-test/daily-devel-tests.txt storage/ndb/test/src/HugoOperations.cpp storage/ndb/test/src/HugoTransactions.cpp storage/ndb/test/tools/hugoPkUpdate.cpp === modified file 'storage/ndb/include/kernel/kernel_types.h' --- a/storage/ndb/include/kernel/kernel_types.h 2011-04-19 09:01:07 +0000 +++ b/storage/ndb/include/kernel/kernel_types.h 2011-05-25 13:19:02 +0000 @@ -36,9 +36,7 @@ enum Operation_t { ,ZDELETE = 3 ,ZWRITE = 4 ,ZREAD_EX = 5 -#if 0 - ,ZREAD_CONSISTENT = 6 -#endif + ,ZREFRESH = 6 ,ZUNLOCK = 7 }; === modified file 'storage/ndb/include/ndb_version.h.in' --- a/storage/ndb/include/ndb_version.h.in 2011-05-17 23:29:55 +0000 +++ b/storage/ndb/include/ndb_version.h.in 2011-05-25 13:19:02 +0000 @@ -652,4 +652,28 @@ ndb_tup_extrabits(Uint32 x) } } +#define NDBD_REFRESH_TUPLE_70 NDB_MAKE_VERSION(7,0,26) +#define NDBD_REFRESH_TUPLE_71 NDB_MAKE_VERSION(7,1,15) +#define NDBD_REFRESH_TUPLE_72 NDB_MAKE_VERSION(7,2,1) + +static +inline +int +ndb_refresh_tuple(Uint32 x) +{ + { + const Uint32 major = (x >> 16) & 0xFF; + const Uint32 minor = (x >> 8) & 0xFF; + + if (major == 7 && minor < 2) + { + if (minor == 0) + return x >= NDBD_REFRESH_TUPLE_70; + else if (minor == 1) + return x >= NDBD_REFRESH_TUPLE_71; + } + return x >= NDBD_REFRESH_TUPLE_72; + } +} + #endif === modified file 'storage/ndb/include/ndbapi/NdbOperation.hpp' --- a/storage/ndb/include/ndbapi/NdbOperation.hpp 2011-04-28 07:47:53 +0000 +++ b/storage/ndb/include/ndbapi/NdbOperation.hpp 2011-05-25 13:19:02 +0000 @@ -914,6 +914,7 @@ public: DeleteRequest = 3, ///< Delete Operation WriteRequest = 4, ///< Write Operation ReadExclusive = 5, ///< Read exclusive + RefreshRequest = 6, ///< UnlockRequest = 7, ///< Unlock operation OpenScanRequest, ///< Scan Operation OpenRangeScanRequest, ///< Range scan operation === modified file 'storage/ndb/include/ndbapi/NdbTransaction.hpp' --- a/storage/ndb/include/ndbapi/NdbTransaction.hpp 2011-04-27 10:48:16 +0000 +++ b/storage/ndb/include/ndbapi/NdbTransaction.hpp 2011-05-25 13:19:02 +0000 @@ -752,6 +752,12 @@ public: const NdbOperation::OperationOptions *opts = 0, Uint32 sizeOfOptions = 0); +#ifndef DOXYGEN_SHOULD_SKIP_INTERNAL + const NdbOperation *refreshTuple(const NdbRecord *key_rec, const char *key_row, + const NdbOperation::OperationOptions *opts = 0, + Uint32 sizeOfOptions = 0); +#endif + /** * Scan a table, using NdbRecord to read out column data. * === modified file 'storage/ndb/src/common/debugger/signaldata/TcKeyReq.cpp' --- a/storage/ndb/src/common/debugger/signaldata/TcKeyReq.cpp 2011-04-28 07:47:53 +0000 +++ b/storage/ndb/src/common/debugger/signaldata/TcKeyReq.cpp 2011-05-25 13:19:02 +0000 @@ -36,6 +36,7 @@ printTCKEYREQ(FILE * output, const Uint3 sig->getOperationType(requestInfo) == ZDELETE ? "Delete" : sig->getOperationType(requestInfo) == ZWRITE ? "Write" : sig->getOperationType(requestInfo) == ZUNLOCK ? "Unlock" : + sig->getOperationType(requestInfo) == ZREFRESH ? "Refresh" : "Unknown"); { if(sig->getDirtyFlag(requestInfo)){ === modified file 'storage/ndb/src/kernel/blocks/dbacc/Dbacc.hpp' --- a/storage/ndb/src/kernel/blocks/dbacc/Dbacc.hpp 2011-04-19 09:01:07 +0000 +++ b/storage/ndb/src/kernel/blocks/dbacc/Dbacc.hpp 2011-05-25 13:19:02 +0000 @@ -140,7 +140,7 @@ ndbout << "Ptr: " << ptr.p->word32 << " /** * Check kernel_types for other operation types */ -#define ZSCAN_OP 6 +#define ZSCAN_OP 8 #define ZSCAN_REC_SIZE 256 #define ZSTAND_BY 2 #define ZTABLESIZE 16 @@ -642,6 +642,7 @@ public: class Dblqh* c_lqh; void execACCMINUPDATE(Signal* signal); + void removerow(Uint32 op, const Local_key*); private: BLOCK_DEFINES(Dbacc); === modified file 'storage/ndb/src/kernel/blocks/dbacc/DbaccMain.cpp' --- a/storage/ndb/src/kernel/blocks/dbacc/DbaccMain.cpp 2011-04-20 11:58:16 +0000 +++ b/storage/ndb/src/kernel/blocks/dbacc/DbaccMain.cpp 2011-05-25 13:19:02 +0000 @@ -971,9 +971,12 @@ void Dbacc::initOpRec(Signal* signal) Uint32 readFlag = (((Treqinfo >> 4) & 0x3) == 0); // Only 1 if Read Uint32 dirtyFlag = (((Treqinfo >> 6) & 0x1) == 1); // Only 1 if Dirty Uint32 dirtyReadFlag = readFlag & dirtyFlag; + Uint32 operation = Treqinfo & 0xf; + if (operation == ZREFRESH) + operation = ZWRITE; /* Insert if !exist, otherwise lock */ Uint32 opbits = 0; - opbits |= Treqinfo & 0x7; + opbits |= operation; opbits |= ((Treqinfo >> 4) & 0x3) ? (Uint32) Operationrec::OP_LOCK_MODE : 0; opbits |= ((Treqinfo >> 4) & 0x3) ? (Uint32) Operationrec::OP_ACC_LOCK_MODE : 0; opbits |= (dirtyReadFlag) ? (Uint32) Operationrec::OP_DIRTY_READ : 0; @@ -2323,6 +2326,27 @@ void Dbacc::execACCMINUPDATE(Signal* sig ndbrequire(false); }//Dbacc::execACCMINUPDATE() +void +Dbacc::removerow(Uint32 opPtrI, const Local_key* key) +{ + jamEntry(); + operationRecPtr.i = opPtrI; + ptrCheckGuard(operationRecPtr, coprecsize, operationrec); + Uint32 opbits = operationRecPtr.p->m_op_bits; + fragrecptr.i = operationRecPtr.p->fragptr; + + /* Mark element disappeared */ + opbits |= Operationrec::OP_ELEMENT_DISAPPEARED; + opbits &= ~Uint32(Operationrec::OP_COMMIT_DELETE_CHECK); + operationRecPtr.p->m_op_bits = opbits; + +#ifdef VM_TRACE + ptrCheckGuard(fragrecptr, cfragmentsize, fragmentrec); + ndbrequire(operationRecPtr.p->localdata[0] == key->m_page_no); + ndbrequire(operationRecPtr.p->localdata[1] == key->m_page_idx); +#endif +}//Dbacc::execACCMINUPDATE() + /* ******************--------------------------------------------------------------- */ /* ACC_COMMITREQ COMMIT TRANSACTION */ /* SENDER: LQH, LEVEL B */ @@ -2371,6 +2395,16 @@ void Dbacc::execACC_COMMITREQ(Signal* si }//if } else { jam(); /* EXPAND PROCESS HANDLING */ + if (unlikely(opbits & Operationrec::OP_ELEMENT_DISAPPEARED)) + { + jam(); + /* Commit of refresh of non existing tuple. + * ZREFRESH->ZWRITE->ZINSERT + * Do not affect element count + */ + ndbrequire((opbits & Operationrec::OP_MASK) == ZINSERT); + return; + } fragrecptr.p->noOfElements++; fragrecptr.p->slack -= fragrecptr.p->elementLength; if (fragrecptr.p->slack >= (1u << 31)) { === modified file 'storage/ndb/src/kernel/blocks/dblqh/Dblqh.hpp' --- a/storage/ndb/src/kernel/blocks/dblqh/Dblqh.hpp 2011-04-28 07:47:53 +0000 +++ b/storage/ndb/src/kernel/blocks/dblqh/Dblqh.hpp 2011-05-25 13:19:02 +0000 @@ -3159,6 +3159,7 @@ public: bool is_same_trans(Uint32 opId, Uint32 trid1, Uint32 trid2); void get_op_info(Uint32 opId, Uint32 *hash, Uint32* gci_hi, Uint32* gci_lo); void accminupdate(Signal*, Uint32 opPtrI, const Local_key*); + void accremoverow(Signal*, Uint32 opPtrI, const Local_key*); /** * @@ -3368,6 +3369,16 @@ Dblqh::accminupdate(Signal* signal, Uint } inline +void +Dblqh::accremoverow(Signal* signal, Uint32 opId, const Local_key* key) +{ + TcConnectionrecPtr regTcPtr; + regTcPtr.i= opId; + ptrCheckGuard(regTcPtr, ctcConnectrecFileSize, tcConnectionrec); + c_acc->removerow(regTcPtr.p->accConnectrec, key); +} + +inline bool Dblqh::TRACE_OP_CHECK(const TcConnectionrec* regTcPtr) { === modified file 'storage/ndb/src/kernel/blocks/dblqh/DblqhMain.cpp' --- a/storage/ndb/src/kernel/blocks/dblqh/DblqhMain.cpp 2011-05-17 23:29:55 +0000 +++ b/storage/ndb/src/kernel/blocks/dblqh/DblqhMain.cpp 2011-05-25 13:19:02 +0000 @@ -148,6 +148,7 @@ operator<<(NdbOut& out, Operation_t op) case ZDELETE: out << "DELETE"; break; case ZWRITE: out << "WRITE"; break; case ZUNLOCK: out << "UNLOCK"; break; + case ZREFRESH: out << "REFRESH"; break; } return out; } @@ -4533,6 +4534,7 @@ void Dblqh::execLQHKEYREQ(Signal* signal regTcPtr->lockType = op == ZREAD_EX ? ZUPDATE : (Operation_t) op == ZWRITE ? ZINSERT : + (Operation_t) op == ZREFRESH ? ZINSERT : (Operation_t) op == ZUNLOCK ? ZREAD : // lockType not relevant for unlock req (Operation_t) op; } @@ -5072,6 +5074,7 @@ void Dblqh::prepareContinueAfterBlockedL case ZINSERT: TRACENR("INSERT"); break; case ZDELETE: TRACENR("DELETE"); break; case ZUNLOCK: TRACENR("UNLOCK"); break; + case ZREFRESH: TRACENR("REFRESH"); break; default: TRACENR("operation << ">"); break; } @@ -5121,7 +5124,6 @@ Dblqh::exec_acckeyreq(Signal* signal, Tc Uint32 taccreq; regTcPtr.p->transactionState = TcConnectionrec::WAIT_ACC; taccreq = regTcPtr.p->operation; - taccreq = taccreq + (regTcPtr.p->opSimple << 3); taccreq = taccreq + (regTcPtr.p->lockType << 4); taccreq = taccreq + (regTcPtr.p->dirtyOp << 6); taccreq = taccreq + (regTcPtr.p->replicaType << 7); @@ -5286,15 +5288,17 @@ Dblqh::handle_nr_copy(Signal* signal, Pt if (match) { jam(); - if (op != ZDELETE) + if (op != ZDELETE && op != ZREFRESH) { if (TRACENR_FLAG) - TRACENR(" Changing from to ZWRITE" << endl); + TRACENR(" Changing from INSERT/UPDATE to ZWRITE" << endl); regTcPtr.p->operation = ZWRITE; } goto run; } - + + ndbassert(!match && op == ZINSERT); + /** * 1) Delete row at specified rowid (if len > 0) * 2) Delete specified row at different rowid (if exists) @@ -6006,7 +6010,7 @@ Dblqh::acckeyconf_tupkeyreq(Signal* sign TRACE_OP(regTcPtr, "TUPKEYREQ"); - regTcPtr->m_use_rowid |= (op == ZINSERT); + regTcPtr->m_use_rowid |= (op == ZINSERT || op == ZREFRESH); regTcPtr->m_row_id.m_page_no = page_no; regTcPtr->m_row_id.m_page_idx = page_idx; === modified file 'storage/ndb/src/kernel/blocks/dbtc/Dbtc.hpp' --- a/storage/ndb/src/kernel/blocks/dbtc/Dbtc.hpp 2011-04-28 07:47:53 +0000 +++ b/storage/ndb/src/kernel/blocks/dbtc/Dbtc.hpp 2011-05-25 13:19:02 +0000 @@ -1694,6 +1694,7 @@ private: void checkNodeFailComplete(Signal* signal, Uint32 failedNodeId, Uint32 bit); void apiFailBlockCleanupCallback(Signal* signal, Uint32 failedNodeId, Uint32 ignoredRc); + bool isRefreshSupported() const; // Initialisation void initData(); === modified file 'storage/ndb/src/kernel/blocks/dbtc/DbtcMain.cpp' --- a/storage/ndb/src/kernel/blocks/dbtc/DbtcMain.cpp 2011-05-25 09:31:27 +0000 +++ b/storage/ndb/src/kernel/blocks/dbtc/DbtcMain.cpp 2011-05-25 13:19:02 +0000 @@ -3061,6 +3061,7 @@ void Dbtc::execTCKEYREQ(Signal* signal) case ZINSERT: case ZDELETE: case ZWRITE: + case ZREFRESH: jam(); break; default: @@ -3142,6 +3143,34 @@ handle_reorg_trigger(DiGetNodesConf * co } } +bool +Dbtc::isRefreshSupported() const +{ + const NodeVersionInfo& nvi = getNodeVersionInfo(); + const Uint32 minVer = nvi.m_type[NodeInfo::DB].m_min_version; + const Uint32 maxVer = nvi.m_type[NodeInfo::DB].m_max_version; + + if (likely (minVer == maxVer)) + { + /* Normal case, use function */ + return ndb_refresh_tuple(minVer); + } + + /* As refresh feature was introduced across three minor versions + * we check that all data nodes support it. This slow path + * should only be hit during upgrades between versions + */ + for (Uint32 i=1; i < MAX_NODES; i++) + { + const NodeInfo& nodeInfo = getNodeInfo(i); + if ((nodeInfo.m_type == NODE_TYPE_DB) && + (nodeInfo.m_connected) && + (! ndb_refresh_tuple(nodeInfo.m_version))) + return false; + } + return true; +} + /** * tckeyreq050Lab * This method is executed once all KeyInfo has been obtained for @@ -3368,6 +3397,14 @@ void Dbtc::tckeyreq050Lab(Signal* signal TlastReplicaNo = tnoOfBackup + tnoOfStandby; regTcPtr->lastReplicaNo = (Uint8)TlastReplicaNo; regTcPtr->noOfNodes = (Uint8)(TlastReplicaNo + 1); + + if (unlikely((Toperation == ZREFRESH) && + (! isRefreshSupported()))) + { + /* Function not implemented yet */ + TCKEY_abort(signal,63); + return; + } }//if if (regCachePtr->isLongTcKeyReq || === modified file 'storage/ndb/src/kernel/blocks/dbtup/Dbtup.hpp' --- a/storage/ndb/src/kernel/blocks/dbtup/Dbtup.hpp 2011-05-20 05:11:01 +0000 +++ b/storage/ndb/src/kernel/blocks/dbtup/Dbtup.hpp 2011-05-25 13:19:02 +0000 @@ -268,6 +268,7 @@ inline const Uint32* ALIGN_WORD(const vo #define ZMUST_BE_ABORTED_ERROR 898 #define ZTUPLE_DELETED_ERROR 626 #define ZINSERT_ERROR 630 +#define ZOP_AFTER_REFRESH_ERROR 920 #define ZINVALID_CHAR_FORMAT 744 #define ZROWID_ALLOCATED 899 @@ -843,6 +844,19 @@ struct Operationrec { * version even if in the same transaction. */ Uint16 tupVersion; + + /* + * When refreshing a row, there are four scenarios + * The actual scenario is encoded in the 'copy tuple location' + * to enable special handling at commit time + */ + enum RefreshScenario + { + RF_SINGLE_NOT_EXIST = 1, /* Refresh op first in trans, no row */ + RF_SINGLE_EXIST = 2, /* Refresh op first in trans, row exists */ + RF_MULTI_NOT_EXIST = 3, /* Refresh op !first in trans, row deleted */ + RF_MULTI_EXIST = 4 /* Refresh op !first in trans, row exists */ + }; }; typedef Ptr OperationrecPtr; @@ -2080,6 +2094,13 @@ private: KeyReqStruct* req_struct, bool disk); + int handleRefreshReq(Signal* signal, + Ptr, + Ptr, + Tablerec*, + KeyReqStruct*, + bool disk); + //------------------------------------------------------------------ //------------------------------------------------------------------ int updateStartLab(Signal* signal, @@ -3406,6 +3427,8 @@ private: const Dbtup::ScanOp& op); void commit_operation(Signal*, Uint32, Uint32, Tuple_header*, PagePtr, Operationrec*, Fragrecord*, Tablerec*); + void commit_refresh(Signal*, Uint32, Uint32, Tuple_header*, PagePtr, + KeyReqStruct*, Operationrec*, Fragrecord*, Tablerec*); int retrieve_data_page(Signal*, Page_cache_client::Request, OperationrecPtr); === modified file 'storage/ndb/src/kernel/blocks/dbtup/DbtupCommit.cpp' --- a/storage/ndb/src/kernel/blocks/dbtup/DbtupCommit.cpp 2011-05-17 23:29:55 +0000 +++ b/storage/ndb/src/kernel/blocks/dbtup/DbtupCommit.cpp 2011-05-25 13:19:02 +0000 @@ -849,7 +849,15 @@ skip_disk: tuple_ptr->m_operation_ptr_i = RNIL; - if(regOperPtr.p->op_struct.op_type != ZDELETE) + if (regOperPtr.p->op_struct.op_type == ZDELETE) + { + jam(); + if (get_page) + ndbassert(tuple_ptr->m_header_bits & Tuple_header::DISK_PART); + dealloc_tuple(signal, gci_hi, gci_lo, page.p, tuple_ptr, + &req_struct, regOperPtr.p, regFragPtr.p, regTabPtr.p); + } + else if(regOperPtr.p->op_struct.op_type != ZREFRESH) { jam(); commit_operation(signal, gci_hi, gci_lo, tuple_ptr, page, @@ -858,14 +866,10 @@ skip_disk: else { jam(); - if (get_page) - { - ndbassert(tuple_ptr->m_header_bits & Tuple_header::DISK_PART); - } - dealloc_tuple(signal, gci_hi, gci_lo, page.p, tuple_ptr, - &req_struct, regOperPtr.p, regFragPtr.p, regTabPtr.p); + commit_refresh(signal, gci_hi, gci_lo, tuple_ptr, page, + &req_struct, regOperPtr.p, regFragPtr.p, regTabPtr.p); } - } + } if (nextOp != RNIL) { @@ -917,3 +921,48 @@ Dbtup::set_commit_change_mask_info(const } } } + +void +Dbtup::commit_refresh(Signal* signal, + Uint32 gci_hi, + Uint32 gci_lo, + Tuple_header* tuple_ptr, + PagePtr pagePtr, + KeyReqStruct * req_struct, + Operationrec* regOperPtr, + Fragrecord* regFragPtr, + Tablerec* regTabPtr) +{ + /* Committing a refresh operation. + * Refresh of an existing row looks like an update + * and can commit normally. + * Refresh of a non-existing row looks like an Insert which + * is 'undone' at commit time. + * This is achieved by making special calls to ACC to get + * it to forget, before deallocating the tuple locally. + */ + switch(regOperPtr->m_copy_tuple_location.m_file_no){ + case Operationrec::RF_SINGLE_NOT_EXIST: + case Operationrec::RF_MULTI_NOT_EXIST: + break; + case Operationrec::RF_SINGLE_EXIST: + case Operationrec::RF_MULTI_EXIST: + // "Normal" update + commit_operation(signal, gci_hi, gci_lo, tuple_ptr, pagePtr, + regOperPtr, regFragPtr, regTabPtr); + return; + + default: + ndbrequire(false); + } + + Local_key key = regOperPtr->m_tuple_location; + key.m_page_no = pagePtr.p->frag_page_id; + + /** + * Tell ACC to delete + */ + c_lqh->accremoverow(signal, regOperPtr->userpointer, &key); + dealloc_tuple(signal, gci_hi, gci_lo, pagePtr.p, tuple_ptr, + req_struct, regOperPtr, regFragPtr, regTabPtr); +} === modified file 'storage/ndb/src/kernel/blocks/dbtup/DbtupExecQuery.cpp' --- a/storage/ndb/src/kernel/blocks/dbtup/DbtupExecQuery.cpp 2011-05-20 05:11:01 +0000 +++ b/storage/ndb/src/kernel/blocks/dbtup/DbtupExecQuery.cpp 2011-05-25 13:19:02 +0000 @@ -214,7 +214,14 @@ Dbtup::insertActiveOpList(OperationrecPt prevOpPtr.p->op_struct.delete_insert_flag= true; regOperPtr.p->op_struct.delete_insert_flag= true; return true; - } else { + } + else if (op == ZREFRESH) + { + /* ZREFRESH after Delete - ok */ + return true; + } + else + { terrorCode= ZTUPLE_DELETED_ERROR; return false; } @@ -224,6 +231,12 @@ Dbtup::insertActiveOpList(OperationrecPt terrorCode= ZINSERT_ERROR; return false; } + else if (prevOp == ZREFRESH) + { + /* No operation after a ZREFRESH */ + terrorCode= ZOP_AFTER_REFRESH_ERROR; + return false; + } return true; } else @@ -283,21 +296,39 @@ Dbtup::setup_read(KeyReqStruct *req_stru dirty= false; } + /* found == true indicates that savepoint is some state + * within tuple's current transaction's uncommitted operations + */ bool found= find_savepoint(currOpPtr, savepointId); Uint32 currOp= currOpPtr.p->op_struct.op_type; + /* is_insert==true if tuple did not exist before its current + * transaction + */ bool is_insert = (bits & Tuple_header::ALLOC); + + /* If savepoint is in transaction, and post-delete-op + * OR + * Tuple didn't exist before + * AND + * Read is dirty + * OR + * Savepoint is before-transaction + * + * Tuple does not exist in read's view + */ if((found && currOp == ZDELETE) || ((dirty || !found) && is_insert)) { + /* Tuple not visible to this read operation */ terrorCode= ZTUPLE_DELETED_ERROR; break; } if(dirty || !found) { - + /* Read existing committed tuple */ } else { @@ -351,6 +382,17 @@ Dbtup::load_diskpage(Signal* signal, jam(); regOperPtr->op_struct.m_wait_log_buffer= 1; regOperPtr->op_struct.m_load_diskpage_on_commit= 1; + if (unlikely((flags & 7) == ZREFRESH)) + { + jam(); + /* Refresh of previously nonexistant DD tuple. + * No diskpage to load at commit time + */ + regOperPtr->op_struct.m_wait_log_buffer= 0; + regOperPtr->op_struct.m_load_diskpage_on_commit= 0; + } + + /* In either case return 1 for 'proceed' */ return 1; } @@ -410,6 +452,7 @@ Dbtup::load_diskpage(Signal* signal, case ZUPDATE: case ZINSERT: case ZWRITE: + case ZREFRESH: regOperPtr->op_struct.m_wait_log_buffer= 1; regOperPtr->op_struct.m_load_diskpage_on_commit= 1; } @@ -556,7 +599,7 @@ void Dbtup::execTUPKEYREQ(Signal* signal Uint32 Rstoredid= tupKeyReq->storedProcedure; regOperPtr->fragmentPtr= Rfragptr; - regOperPtr->op_struct.op_type= (TrequestInfo >> 6) & 0xf; + regOperPtr->op_struct.op_type= (TrequestInfo >> 6) & 0x7; regOperPtr->op_struct.delete_insert_flag = false; regOperPtr->op_struct.m_reorg = (TrequestInfo >> 12) & 3; @@ -635,10 +678,16 @@ void Dbtup::execTUPKEYREQ(Signal* signal if (Roptype == ZINSERT && Local_key::isInvalid(pageid, pageidx)) { - // No tuple allocatated yet + // No tuple allocated yet goto do_insert; } + if (Roptype == ZREFRESH && Local_key::isInvalid(pageid, pageidx)) + { + // No tuple allocated yet + goto do_refresh; + } + if (unlikely(isCopyTuple(pageid, pageidx))) { /** @@ -832,6 +881,23 @@ void Dbtup::execTUPKEYREQ(Signal* signal sendTUPKEYCONF(signal, &req_struct, regOperPtr); return; } + else if (Roptype == ZREFRESH) + { + /** + * No TUX or immediate triggers, just detached triggers + */ + do_refresh: + if (unlikely(handleRefreshReq(signal, operPtr, + fragptr, regTabPtr, + &req_struct, disk_page != RNIL) == -1)) + { + return; + } + + sendTUPKEYCONF(signal, &req_struct, regOperPtr); + return; + + } else { ndbrequire(false); // Invalid op type @@ -2055,6 +2121,197 @@ error: return -1; } +int +Dbtup::handleRefreshReq(Signal* signal, + Ptr regOperPtr, + Ptr regFragPtr, + Tablerec* regTabPtr, + KeyReqStruct *req_struct, + bool disk) +{ + /* Here we setup the tuple so that a transition to its current + * state can be observed by SUMA's detached triggers. + * + * If the tuple does not exist then we fabricate a tuple + * so that it can appear to be 'deleted'. + * The fabricated tuple may have invalid NULL values etc. + * If the tuple does exist then we fabricate a null-change + * update to the tuple. + * + * The logic differs depending on whether there are already + * other operations on the tuple in this transaction. + * No other operations (including Refresh) are allowed after + * a refresh. + */ + Uint32 refresh_case; + if (regOperPtr.p->is_first_operation()) + { + jam(); + if (Local_key::isInvalid(req_struct->frag_page_id, + regOperPtr.p->m_tuple_location.m_page_idx)) + { + jam(); + refresh_case = Operationrec::RF_SINGLE_NOT_EXIST; + //ndbout_c("case 1"); + /** + * This is refresh of non-existing tuple... + * i.e "delete", reuse initial insert + */ + Local_key accminupdate; + Local_key * accminupdateptr = &accminupdate; + + /** + * We don't need ...in this scenario + * - disk + * - default values + */ + Uint32 save_disk = regTabPtr->m_no_of_disk_attributes; + Local_key save_defaults = regTabPtr->m_default_value_location; + Bitmask save_mask = + regTabPtr->notNullAttributeMask; + + regTabPtr->m_no_of_disk_attributes = 0; + regTabPtr->m_default_value_location.setNull(); + regOperPtr.p->op_struct.op_type = ZINSERT; + + /** + * Update notNullAttributeMask to only include primary keys + */ + regTabPtr->notNullAttributeMask.clear(); + const Uint32 * primarykeys = + (Uint32*)&tableDescriptor[regTabPtr->readKeyArray].tabDescr; + for (Uint32 i = 0; inoOfKeyAttr; i++) + regTabPtr->notNullAttributeMask.set(primarykeys[i] >> 16); + + int res = handleInsertReq(signal, regOperPtr, + regFragPtr, regTabPtr, req_struct, + &accminupdateptr); + + regTabPtr->m_no_of_disk_attributes = save_disk; + regTabPtr->m_default_value_location = save_defaults; + regTabPtr->notNullAttributeMask = save_mask; + + if (unlikely(res == -1)) + { + return -1; + } + + regOperPtr.p->op_struct.op_type = ZREFRESH; + + if (accminupdateptr) + { + /** + * Update ACC local-key, once *everything* has completed succesfully + */ + c_lqh->accminupdate(signal, + regOperPtr.p->userpointer, + accminupdateptr); + } + } + else + { + refresh_case = Operationrec::RF_SINGLE_EXIST; + //ndbout_c("case 2"); + jam(); + + Uint32 tup_version_save = req_struct->m_tuple_ptr->get_tuple_version(); + Uint32 new_tup_version = decr_tup_version(tup_version_save); + Tuple_header* origTuple = req_struct->m_tuple_ptr; + origTuple->set_tuple_version(new_tup_version); + int res = handleUpdateReq(signal, regOperPtr.p, regFragPtr.p, + regTabPtr, req_struct, disk); + /* Now we must reset the original tuple header back + * to the original version. + * The copy tuple will have the correct version due to + * the update incrementing it. + * On commit, the tuple becomes the copy tuple. + * On abort, the original tuple remains. If we don't + * reset it here, then aborts cause the version to + * decrease + */ + origTuple->set_tuple_version(tup_version_save); + if (res == -1) + return -1; + } + } + else + { + /* Not first operation on tuple in transaction */ + jam(); + + Uint32 tup_version_save = req_struct->prevOpPtr.p->tupVersion; + Uint32 new_tup_version = decr_tup_version(tup_version_save); + req_struct->prevOpPtr.p->tupVersion = new_tup_version; + + int res; + if (req_struct->prevOpPtr.p->op_struct.op_type == ZDELETE) + { + refresh_case = Operationrec::RF_MULTI_NOT_EXIST; + //ndbout_c("case 3"); + + jam(); + /** + * We don't need ...in this scenario + * - default values + * + * We keep disk attributes to avoid issues with 'insert' + */ + Local_key save_defaults = regTabPtr->m_default_value_location; + Bitmask save_mask = + regTabPtr->notNullAttributeMask; + + regTabPtr->m_default_value_location.setNull(); + regOperPtr.p->op_struct.op_type = ZINSERT; + + /** + * Update notNullAttributeMask to only include primary keys + */ + regTabPtr->notNullAttributeMask.clear(); + const Uint32 * primarykeys = + (Uint32*)&tableDescriptor[regTabPtr->readKeyArray].tabDescr; + for (Uint32 i = 0; inoOfKeyAttr; i++) + regTabPtr->notNullAttributeMask.set(primarykeys[i] >> 16); + + /** + * This is multi-update + DELETE + REFRESH + */ + Local_key * accminupdateptr = 0; + res = handleInsertReq(signal, regOperPtr, + regFragPtr, regTabPtr, req_struct, + &accminupdateptr); + + regTabPtr->m_default_value_location = save_defaults; + regTabPtr->notNullAttributeMask = save_mask; + + if (unlikely(res == -1)) + { + return -1; + } + + regOperPtr.p->op_struct.op_type = ZREFRESH; + } + else + { + jam(); + refresh_case = Operationrec::RF_MULTI_EXIST; + //ndbout_c("case 4"); + /** + * This is multi-update + INSERT/UPDATE + REFRESH + */ + res = handleUpdateReq(signal, regOperPtr.p, regFragPtr.p, + regTabPtr, req_struct, disk); + } + req_struct->prevOpPtr.p->tupVersion = tup_version_save; + if (res == -1) + return -1; + } + + /* Store the refresh scenario in the copy tuple location */ + // TODO : Verify this is never used as a copy tuple location! + regOperPtr.p->m_copy_tuple_location.m_file_no = refresh_case; + return 0; +} + bool Dbtup::checkNullAttributes(KeyReqStruct * req_struct, Tablerec* regTabPtr) === modified file 'storage/ndb/src/kernel/blocks/dbtup/DbtupTrigger.cpp' --- a/storage/ndb/src/kernel/blocks/dbtup/DbtupTrigger.cpp 2011-04-28 07:47:53 +0000 +++ b/storage/ndb/src/kernel/blocks/dbtup/DbtupTrigger.cpp 2011-05-25 13:19:02 +0000 @@ -863,6 +863,7 @@ void Dbtup::checkDetachedTriggers(KeyReq switch (save_type) { case ZUPDATE: case ZINSERT: + case ZREFRESH: req_struct->m_tuple_ptr =get_copy_tuple(®OperPtr->m_copy_tuple_location); break; } @@ -878,7 +879,10 @@ void Dbtup::checkDetachedTriggers(KeyReq return; goto end; } - regOperPtr->op_struct.op_type = ZINSERT; + else if (save_type != ZREFRESH) + { + regOperPtr->op_struct.op_type = ZINSERT; + } } else if (save_type == ZINSERT) { /** @@ -930,6 +934,29 @@ void Dbtup::checkDetachedTriggers(KeyReq regTablePtr->subscriptionUpdateTriggers, regOperPtr, disk); break; + case ZREFRESH: + jam(); + /* Depending on the Refresh scenario, fire Delete or Insert + * triggers to simulate the effect of arriving at the tuple's + * current state. + */ + switch(regOperPtr->m_copy_tuple_location.m_file_no){ + case Operationrec::RF_SINGLE_NOT_EXIST: + case Operationrec::RF_MULTI_NOT_EXIST: + fireDetachedTriggers(req_struct, + regTablePtr->subscriptionDeleteTriggers, + regOperPtr, disk); + break; + case Operationrec::RF_SINGLE_EXIST: + case Operationrec::RF_MULTI_EXIST: + fireDetachedTriggers(req_struct, + regTablePtr->subscriptionInsertTriggers, + regOperPtr, disk); + break; + default: + ndbrequire(false); + } + break; default: ndbrequire(false); break; @@ -1375,12 +1402,14 @@ out: switch(regOperPtr->op_struct.op_type) { case(ZINSERT): + is_insert: jam(); // Send AttrInfo signals with new attribute values trigAttrInfo->setAttrInfoType(TrigAttrInfo::AFTER_VALUES); sendTrigAttrInfo(signal, afterBuffer, noAfterWords, executeDirect, ref); break; case(ZDELETE): + is_delete: if (trigPtr->sendBeforeValues) { jam(); trigAttrInfo->setAttrInfoType(TrigAttrInfo::BEFORE_VALUES); @@ -1397,6 +1426,23 @@ out: trigAttrInfo->setAttrInfoType(TrigAttrInfo::AFTER_VALUES); sendTrigAttrInfo(signal, afterBuffer, noAfterWords, executeDirect, ref); break; + case ZREFRESH: + jam(); + /* Reuse Insert/Delete trigger firing code as necessary */ + switch(regOperPtr->m_copy_tuple_location.m_file_no){ + case Operationrec::RF_SINGLE_NOT_EXIST: + jam(); + case Operationrec::RF_MULTI_NOT_EXIST: + jam(); + goto is_delete; + case Operationrec::RF_SINGLE_EXIST: + jam(); + case Operationrec::RF_MULTI_EXIST: + jam(); + goto is_insert; + default: + ndbrequire(false); + } default: ndbrequire(false); } @@ -1424,6 +1470,25 @@ out: jam(); fireTrigOrd->m_triggerEvent = TriggerEvent::TE_DELETE; break; + case ZREFRESH: + jam(); + switch(regOperPtr->m_copy_tuple_location.m_file_no){ + case Operationrec::RF_SINGLE_NOT_EXIST: + jam(); + case Operationrec::RF_MULTI_NOT_EXIST: + jam(); + fireTrigOrd->m_triggerEvent = TriggerEvent::TE_DELETE; + break; + case Operationrec::RF_SINGLE_EXIST: + jam(); + case Operationrec::RF_MULTI_EXIST: + jam(); + fireTrigOrd->m_triggerEvent = TriggerEvent::TE_INSERT; + break; + default: + ndbrequire(false); + } + break; default: ndbrequire(false); break; @@ -1615,7 +1680,7 @@ bool Dbtup::readTriggerInfo(TupTriggerDa // Delete without sending before values only read Primary Key //-------------------------------------------------------------------- return true; - } else { + } else if (regOperPtr->op_struct.op_type != ZREFRESH){ jam(); //-------------------------------------------------------------------- // All others send all attributes that are monitored, except: @@ -1632,6 +1697,27 @@ bool Dbtup::readTriggerInfo(TupTriggerDa numAttrsToRead = setAttrIds(attributeMask, regTabPtr->m_no_of_attributes, &readBuffer[0]); } + else + { + jam(); + ndbassert(regOperPtr->op_struct.op_type == ZREFRESH); + /* Refresh specific before/after value hacks */ + switch(regOperPtr->m_copy_tuple_location.m_file_no){ + case Operationrec::RF_SINGLE_NOT_EXIST: + case Operationrec::RF_MULTI_NOT_EXIST: + return true; // generate ZDELETE...no before values + case Operationrec::RF_SINGLE_EXIST: + case Operationrec::RF_MULTI_EXIST: + // generate ZINSERT...all after values + numAttrsToRead = setAttrIds(trigPtr->attributeMask, + regTabPtr->m_no_of_attributes, + &readBuffer[0]); + break; + default: + ndbrequire(false); + } + } + ndbrequire(numAttrsToRead <= MAX_ATTRIBUTES_IN_TABLE); //-------------------------------------------------------------------- // Read Main tuple values @@ -1875,6 +1961,9 @@ Dbtup::executeTuxCommitTriggers(Signal* return; jam(); tupVersion= regOperPtr->tupVersion; + } else if (regOperPtr->op_struct.op_type == ZREFRESH) { + /* Refresh should not affect TUX */ + return; } else { ndbrequire(false); tupVersion= 0; // remove warning @@ -1907,6 +1996,10 @@ Dbtup::executeTuxAbortTriggers(Signal* s } else if (regOperPtr->op_struct.op_type == ZDELETE) { jam(); return; + } else if (regOperPtr->op_struct.op_type == ZREFRESH) { + jam(); + /* Refresh should not affect TUX */ + return; } else { ndbrequire(false); tupVersion= 0; // remove warning === modified file 'storage/ndb/src/ndbapi/NdbOperationExec.cpp' --- a/storage/ndb/src/ndbapi/NdbOperationExec.cpp 2011-05-11 13:31:44 +0000 +++ b/storage/ndb/src/ndbapi/NdbOperationExec.cpp 2011-05-25 13:19:02 +0000 @@ -1120,7 +1120,8 @@ NdbOperation::buildSignalsNdbRecord(Uint /* Final update signal words */ if ((tOpType == InsertRequest) || (tOpType == WriteRequest) || - (tOpType == UpdateRequest)) + (tOpType == UpdateRequest) || + (tOpType == RefreshRequest)) { updRow= m_attribute_row; NdbBlob *currentBlob= theBlobList; @@ -1333,7 +1334,8 @@ NdbOperation::buildSignalsNdbRecord(Uint if ((tOpType == InsertRequest) || (tOpType == WriteRequest) || - (tOpType == UpdateRequest)) + (tOpType == UpdateRequest) || + (tOpType == RefreshRequest)) { /* Handle setAnyValue() for all cases except delete */ if ((m_flags & OF_USE_ANY_VALUE) != 0) === modified file 'storage/ndb/src/ndbapi/NdbTransaction.cpp' --- a/storage/ndb/src/ndbapi/NdbTransaction.cpp 2011-04-27 10:48:16 +0000 +++ b/storage/ndb/src/ndbapi/NdbTransaction.cpp 2011-05-25 13:19:02 +0000 @@ -2209,6 +2209,7 @@ NdbTransaction::receiveTCKEY_FAILCONF(co case NdbOperation::DeleteRequest: case NdbOperation::WriteRequest: case NdbOperation::UnlockRequest: + case NdbOperation::RefreshRequest: tOp = tOp->next(); break; case NdbOperation::ReadRequest: @@ -2713,6 +2714,52 @@ NdbTransaction::writeTuple(const NdbReco return op; } +const NdbOperation * +NdbTransaction::refreshTuple(const NdbRecord *key_rec, const char *key_row, + const NdbOperation::OperationOptions *opts, + Uint32 sizeOfOptions) +{ + /* Check TC node version lockless */ + { + Uint32 tcVer = theNdb->theImpl->getNodeInfo(theDBnode).m_info.m_version; + if (unlikely(! ndb_refresh_tuple(tcVer))) + { + /* Function not implemented yet */ + setOperationErrorCodeAbort(4003); + return NULL; + } + } + + /* Check that the NdbRecord specifies the full primary key. */ + if (!(key_rec->flags & NdbRecord::RecHasAllKeys)) + { + setOperationErrorCodeAbort(4292); + return NULL; + } + + Uint8 keymask[NDB_MAX_ATTRIBUTES_IN_TABLE/8]; + bzero(keymask, sizeof(keymask)); + for (Uint32 i = 0; ikey_index_length; i++) + { + Uint32 id = key_rec->columns[key_rec->key_indexes[i]].attrId; + keymask[(id / 8)] |= (1 << (id & 7)); + } + + NdbOperation *op= setupRecordOp(NdbOperation::RefreshRequest, + NdbOperation::LM_Exclusive, + NdbOperation::AbortOnError, + key_rec, key_row, + key_rec, key_row, + keymask /* mask */, + opts, + sizeOfOptions); + if(!op) + return op; + + theSimpleState= 0; + + return op; +} NdbScanOperation * NdbTransaction::scanTable(const NdbRecord *result_record, === modified file 'storage/ndb/src/ndbapi/ndberror.c' --- a/storage/ndb/src/ndbapi/ndberror.c 2011-05-07 06:17:02 +0000 +++ b/storage/ndb/src/ndbapi/ndberror.c 2011-05-25 13:19:02 +0000 @@ -750,6 +750,7 @@ ErrorBundle ErrorCodes[] = { { 2810, DMEC, TR, "No space left on the device" }, { 2811, DMEC, TR, "Error with file permissions, please check file system" }, { 2815, DMEC, TR, "Error in reading files, please check file system" }, + { 920, DMEC, AE, "Row operation defined after refreshTuple()" }, /** * NdbQueryBuilder API errors === modified file 'storage/ndb/test/include/HugoOperations.hpp' --- a/storage/ndb/test/include/HugoOperations.hpp 2011-02-02 00:40:07 +0000 +++ b/storage/ndb/test/include/HugoOperations.hpp 2011-05-25 13:19:02 +0000 @@ -87,6 +87,11 @@ public: int recordNo, int numRecords = 1); + int pkRefreshRecord(Ndb*, + int recordNo, + int numRecords = 1, + int anyValueInfo = 0); /* 0 - none, 1+ Val | record */ + int execute_Commit(Ndb*, AbortOption ao = AbortOnError); int execute_NoCommit(Ndb*, === modified file 'storage/ndb/test/include/HugoTransactions.hpp' --- a/storage/ndb/test/include/HugoTransactions.hpp 2011-02-02 00:40:07 +0000 +++ b/storage/ndb/test/include/HugoTransactions.hpp 2011-05-25 13:19:02 +0000 @@ -110,6 +110,9 @@ public: int batch = 1, bool allowConstraintViolation = true, int doSleep = 0); + + int pkRefreshRecords(Ndb*, int startFrom, int count = 1, int batch = 1); + int lockRecords(Ndb*, int records, int percentToLock = 1, === modified file 'storage/ndb/test/ndbapi/testBasic.cpp' --- a/storage/ndb/test/ndbapi/testBasic.cpp 2011-05-07 06:17:02 +0000 +++ b/storage/ndb/test/ndbapi/testBasic.cpp 2011-05-25 13:19:02 +0000 @@ -2415,6 +2415,811 @@ runEnd899(NDBT_Context* ctx, NDBT_Step* } +int initSubscription(NDBT_Context* ctx, NDBT_Step* step){ + /* Subscribe to events on the table, and put access + * to the subscription somewhere handy + */ + Ndb* pNdb = GETNDB(step); + const NdbDictionary::Table& tab = *ctx->getTab(); + bool merge_events = false; + bool report = false; + + char eventName[1024]; + sprintf(eventName,"%s_EVENT",tab.getName()); + + NdbDictionary::Dictionary *myDict = pNdb->getDictionary(); + + if (!myDict) { + g_err << "Dictionary not found " + << pNdb->getNdbError().code << " " + << pNdb->getNdbError().message << endl; + return NDBT_FAILED; + } + + myDict->dropEvent(eventName); + + NdbDictionary::Event myEvent(eventName); + myEvent.setTable(tab.getName()); + myEvent.addTableEvent(NdbDictionary::Event::TE_ALL); + for(int a = 0; a < tab.getNoOfColumns(); a++){ + myEvent.addEventColumn(a); + } + myEvent.mergeEvents(merge_events); + + if (report) + myEvent.setReport(NdbDictionary::Event::ER_SUBSCRIBE); + + int res = myDict->createEvent(myEvent); // Add event to database + + if (res == 0) + myEvent.print(); + else if (myDict->getNdbError().classification == + NdbError::SchemaObjectExists) + { + g_info << "Event creation failed event exists\n"; + res = myDict->dropEvent(eventName); + if (res) { + g_err << "Failed to drop event: " + << myDict->getNdbError().code << " : " + << myDict->getNdbError().message << endl; + return NDBT_FAILED; + } + // try again + res = myDict->createEvent(myEvent); // Add event to database + if (res) { + g_err << "Failed to create event (1): " + << myDict->getNdbError().code << " : " + << myDict->getNdbError().message << endl; + return NDBT_FAILED; + } + } + else + { + g_err << "Failed to create event (2): " + << myDict->getNdbError().code << " : " + << myDict->getNdbError().message << endl; + return NDBT_FAILED; + } + + return NDBT_OK; +} + +int removeSubscription(NDBT_Context* ctx, NDBT_Step* step){ + /* Remove subscription created above */ + Ndb* pNdb = GETNDB(step); + const NdbDictionary::Table& tab = *ctx->getTab(); + + char eventName[1024]; + sprintf(eventName,"%s_EVENT",tab.getName()); + + NdbDictionary::Dictionary *myDict = pNdb->getDictionary(); + + if (!myDict) { + g_err << "Dictionary not found " + << pNdb->getNdbError().code << " " + << pNdb->getNdbError().message << endl; + return NDBT_FAILED; + } + + myDict->dropEvent(eventName); + + return NDBT_OK; +} + +int runVerifyRowCount(NDBT_Context* ctx, NDBT_Step* step) +{ + Ndb* ndb = GETNDB(step); + + /* Check that number of results returned by a normal scan + * and per-fragment rowcount sum are equal + */ + Uint32 rowCountSum = 0; + Uint32 rowScanCount = 0; + + int result = NDBT_OK; + do + { + NdbTransaction* trans = ndb->startTransaction(); + CHECK(trans != NULL); + + NdbScanOperation* scan = trans->getNdbScanOperation(ctx->getTab()); + CHECK(scan != NULL); + + CHECK(scan->readTuples(NdbScanOperation::LM_CommittedRead) == 0); + + NdbInterpretedCode code; + + CHECK(code.interpret_exit_last_row() == 0); + CHECK(code.finalise() == 0); + + NdbRecAttr* rowCountRA = scan->getValue(NdbDictionary::Column::ROW_COUNT); + CHECK(rowCountRA != NULL); + CHECK(scan->setInterpretedCode(&code) == 0); + + CHECK(trans->execute(NoCommit) == 0); + + while (scan->nextResult() == 0) + rowCountSum+= rowCountRA->u_32_value(); + + trans->close(); + + trans = ndb->startTransaction(); + CHECK(trans != NULL); + + scan = trans->getNdbScanOperation(ctx->getTab()); + CHECK(scan != NULL); + + CHECK(scan->readTuples(NdbScanOperation::LM_CommittedRead) == 0); + + rowCountRA = scan->getValue(NdbDictionary::Column::ROW_COUNT); + CHECK(rowCountRA != NULL); + + CHECK(trans->execute(NoCommit) == 0); + + while (scan->nextResult() == 0) + rowScanCount++; + + trans->close(); + } + while(0); + + if (result == NDBT_OK) + { + ndbout_c("Sum of fragment row counts : %u Number rows scanned : %u", + rowCountSum, + rowScanCount); + + if (rowCountSum != rowScanCount) + { + ndbout_c("MISMATCH"); + result = NDBT_FAILED; + } + } + + return result; +} + +enum ApiEventType { Insert, Update, Delete }; + +template class Vector; + +struct EventInfo +{ + ApiEventType type; + int id; + Uint64 gci; +}; +template class Vector; + +int collectEvents(Ndb* ndb, + HugoCalculator& calc, + const NdbDictionary::Table& tab, + Vector& receivedEvents, + int idCol, + int updateCol, + Vector* beforeAttrs, + Vector* afterAttrs) +{ + int MaxTimeouts = 5; + while (true) + { + int res = ndb->pollEvents(1000); + + if (res > 0) + { + NdbEventOperation* pOp; + while ((pOp = ndb->nextEvent())) + { + bool isDelete = (pOp->getEventType() == NdbDictionary::Event::TE_DELETE); + Vector* whichVersion = + isDelete? + beforeAttrs : + afterAttrs; + int id = (*whichVersion)[idCol]->u_32_value(); + Uint64 gci = pOp->getGCI(); + Uint32 anyValue = pOp->getAnyValue(); + Uint32 scenario = ((anyValue >> 24) & 0xff) -1; + Uint32 optype = ((anyValue >> 16) & 0xff); + Uint32 recNum = (anyValue & 0xffff); + + g_err << "# " << receivedEvents.size() + << " GCI : " << (gci >> 32) + << "/" + << (gci & 0xffffffff) + << " id : " + << id + << " scenario : " << scenario + << " optype : " << optype + << " record : " << recNum + << " "; + + /* Check event has self-consistent data */ + int updatesValue = (*whichVersion)[updateCol]->u_32_value(); + + if ((*whichVersion)[updateCol]->isNULL() || + (*whichVersion)[idCol]->isNULL()) + { + g_err << "Null update/id cols : REFRESH of !EXISTS "; + } + + g_err << "(Updates val = " << updatesValue << ")"; + + for (int i=0; i < (int) whichVersion->size(); i++) + { + /* Check PK columns and also other columns for non-delete */ + if (!isDelete || + tab.getColumn(i)->getPrimaryKey()) + { + NdbRecAttr* ra = (*whichVersion)[i]; + if (calc.verifyRecAttr(recNum, updatesValue, ra) != 0) + { + g_err << "Verify failed on recNum : " << recNum << " with updates value " + << updatesValue << " for column " << ra->getColumn()->getAttrId() + << endl; + return NDBT_FAILED; + } + } + } + + EventInfo ei; + + switch (pOp->getEventType()) + { + case NdbDictionary::Event::TE_INSERT: + g_err << " Insert event" << endl; + ei.type = Insert; + break; + case NdbDictionary::Event::TE_DELETE: + ei.type = Delete; + g_err << " Delete event" << endl; + break; + case NdbDictionary::Event::TE_UPDATE: + ei.type = Update; + g_err << " Update event" << endl; + break; + default: + g_err << " Event type : " << pOp->getEventType() << endl; + abort(); + break; + } + + ei.id = recNum; + ei.gci = gci; + + receivedEvents.push_back(ei); + } + } + else + { + if (--MaxTimeouts == 0) + { + break; + } + } + } + + return NDBT_OK; +} + +int verifyEvents(const Vector& receivedEvents, + const Vector& expectedEvents, + int records) +{ + /* Now verify received events against expected + * This is messy as events occurring in the same epoch are unordered + * except via id, so we use id-duplicates to determine which event + * sequence we're looking at. + */ + g_err << "Received total of " << receivedEvents.size() << " events" << endl; + Vector keys; + Vector gcis; + Uint32 z = 0; + Uint64 z2 = 0; + keys.fill(records, z); + gcis.fill(records, z2); + Uint64 currGci = 0; + + for (Uint32 e=0; e < receivedEvents.size(); e++) + { + EventInfo ei = receivedEvents[e]; + + if (ei.gci != currGci) + { + if (ei.gci < currGci) + abort(); + + /* Epoch boundary */ + /* At this point, all id counts must be equal */ + for (int i=0; i < records; i++) + { + if (keys[i] != keys[0]) + { + g_err << "Count for id " << i + << " is " << keys[i] + << " but should be " << keys[0] << endl; + return NDBT_OK; + } + } + + currGci = ei.gci; + } + + Uint32 eventIndex = keys[ei.id]; + keys[ei.id]++; + + ApiEventType et = expectedEvents[eventIndex]; + + if (ei.type != et) + { + g_err << "Expected event of type " << et + << " but found " << ei.type + << " at expectedEvent " << eventIndex + << " and event num " << e << endl; + return NDBT_FAILED; + } + } + + return NDBT_OK; +} + +int runRefreshTuple(NDBT_Context* ctx, NDBT_Step* step){ + int records = ctx->getNumRecords(); + Ndb* ndb = GETNDB(step); + + /* Now attempt to create EventOperation */ + NdbEventOperation* pOp; + const NdbDictionary::Table& tab = *ctx->getTab(); + + char eventName[1024]; + sprintf(eventName,"%s_EVENT",tab.getName()); + + pOp = ndb->createEventOperation(eventName); + if (pOp == NULL) + { + g_err << "Failed to create event operation\n"; + return NDBT_FAILED; + } + + HugoCalculator calc(tab); + Vector eventAfterRecAttr; + Vector eventBeforeRecAttr; + int updateCol = -1; + int idCol = -1; + + /* Now request all attributes */ + for (int a = 0; a < tab.getNoOfColumns(); a++) + { + eventAfterRecAttr.push_back(pOp->getValue(tab.getColumn(a)->getName())); + eventBeforeRecAttr.push_back(pOp->getPreValue(tab.getColumn(a)->getName())); + if (calc.isIdCol(a)) + idCol = a; + if (calc.isUpdateCol(a)) + updateCol = a; + } + + /* Now execute the event */ + if (pOp->execute()) + { + g_err << "Event operation execution failed : " << pOp->getNdbError() << endl; + return NDBT_FAILED; + } + + HugoOperations hugoOps(*ctx->getTab()); + int scenario = 0; + + Vector expectedEvents; + + for (scenario = 0; scenario < 2; scenario++) + { + g_err << "Scenario = " << scenario + << " ( Refresh " + << ((scenario == 0)? "before":"after") + << " operations )" << endl; + int optype = 0; + bool done = false; + int expectedError = 0; + do + { + check(hugoOps.startTransaction(ndb) == 0, hugoOps); + + if (scenario == 0) + { + g_err << "Refresh before operations" << endl; + int anyValue = + ((1) << 8) | + optype; + check(hugoOps.pkRefreshRecord(ndb, 0, records, anyValue) == 0, hugoOps); + } + + switch(optype) + { + case 0: + { + /* Refresh with no data present */ + g_err << " Do nothing" << endl; + expectedError = 0; /* Single refresh should always be fine */ + expectedEvents.push_back(Delete); + break; + } + case 1: + { + /* [Refresh] Insert [Refresh] */ + g_err << " Insert" << endl; + check(hugoOps.pkInsertRecord(ndb, 0, records, 1) == 0, hugoOps); + if (scenario == 0) + { + /* Tuple already existed error when we insert after refresh */ + expectedError = 630; + expectedEvents.push_back(Delete); + } + else + { + expectedError = 0; + expectedEvents.push_back(Insert); + } + /* Tuple already existed error when we insert after refresh */ + break; + } + case 2: + { + /* Refresh */ + g_err << " Refresh" << endl; + if (scenario == 0) + { + expectedEvents.push_back(Delete); + } + else + { + expectedEvents.push_back(Insert); + } + expectedError = 0; + break; + } + case 3: + { + /* [Refresh] Update [Refresh] */ + g_err << " Update" << endl; + check(hugoOps.pkUpdateRecord(ndb, 0, records, 3) == 0, hugoOps); + if (scenario == 0) + { + expectedError = 920; + expectedEvents.push_back(Delete); + } + else + { + expectedError = 0; + expectedEvents.push_back(Insert); + } + break; + } + case 4: + { + /* [Refresh] Delete [Refresh] */ + g_err << " [Refresh] Delete [Refresh]" << endl; + if (scenario == 0) + { + expectedError = 920; + expectedEvents.push_back(Delete); + } + else + { + expectedError = 0; + expectedEvents.push_back(Delete); + } + check(hugoOps.pkDeleteRecord(ndb, 0, records) == 0, hugoOps); + break; + } + case 5: + { + g_err << " Refresh" << endl; + expectedError = 0; + expectedEvents.push_back(Delete); + /* Refresh with no data present */ + break; + } + case 6: + { + g_err << " Double refresh" << endl; + int anyValue = + ((2) << 8) | + optype; + check(hugoOps.pkRefreshRecord(ndb, 0, records, anyValue) == 0, hugoOps); + expectedError = 920; /* Row operation defined after refreshTuple() */ + expectedEvents.push_back(Delete); + } + default: + done = true; + break; + } + + if (scenario == 1) + { + g_err << "Refresh after operations" << endl; + int anyValue = + ((4) << 8) | + optype; + check(hugoOps.pkRefreshRecord(ndb, 0, records, anyValue) == 0, hugoOps); + } + + int rc = hugoOps.execute_Commit(ndb, AO_IgnoreError); + check(rc == expectedError, hugoOps); + + check(hugoOps.closeTransaction(ndb) == 0, hugoOps); + + optype++; + + + /* Now check fragment counts vs findable row counts */ + if (runVerifyRowCount(ctx, step) != NDBT_OK) + return NDBT_FAILED; + + } while (!done); + } // for scenario... + + /* Now check fragment counts vs findable row counts */ + if (runVerifyRowCount(ctx, step) != NDBT_OK) + return NDBT_FAILED; + + /* Now let's dump and check the events */ + g_err << "Expecting the following sequence..." << endl; + for (Uint32 i=0; i < expectedEvents.size(); i++) + { + g_err << i << ". "; + switch(expectedEvents[i]) + { + case Insert: + g_err << "Insert" << endl; + break; + case Update: + g_err << "Update" << endl; + break; + case Delete: + g_err << "Delete" << endl; + break; + default: + abort(); + } + } + + Vector receivedEvents; + + int rc = collectEvents(ndb, calc, tab, receivedEvents, idCol, updateCol, + &eventBeforeRecAttr, + &eventAfterRecAttr); + if (rc == NDBT_OK) + { + rc = verifyEvents(receivedEvents, + expectedEvents, + records); + } + + if (ndb->dropEventOperation(pOp) != 0) + { + g_err << "Drop Event Operation failed : " << ndb->getNdbError() << endl; + return NDBT_FAILED; + } + + return rc; +}; + +enum PreRefreshOps +{ + PR_NONE, + PR_INSERT, + PR_INSERTDELETE, + PR_DELETE +}; + +struct RefreshScenario +{ + const char* name; + bool preExist; + PreRefreshOps preRefreshOps; +}; + +static RefreshScenario refreshTests[] = { + { "No row, No pre-ops", false, PR_NONE }, + { "No row, Insert pre-op", false, PR_INSERT }, + { "No row, Insert-Del pre-op", false, PR_INSERTDELETE }, + { "Row exists, No pre-ops", true, PR_NONE }, + { "Row exists, Delete pre-op", true, PR_DELETE } +}; + +enum OpTypes +{ + READ_C, + READ_S, + READ_E, + INSERT, + UPDATE, + WRITE, + DELETE, + LAST +}; + +const char* opTypeNames[] = +{ + "READ_C", + "READ_S", + "READ_E", + "INSERT", + "UPDATE", + "WRITE", + "DELETE" +}; + + +int +runRefreshLocking(NDBT_Context* ctx, NDBT_Step* step) +{ + /* Check that refresh in various situations has the + * locks we expect it to + * Scenario combinations : + * Now row pre-existing | Row pre-existing + * Trans1 : Refresh | Insert-Refresh | Insert-Delete-Refresh + * Delete-Refresh + * Trans2 : Read [Committed|Shared|Exclusive] | Insert | Update + * Write | Delete + * + * Expectations : Read committed always non-blocking + * Read committed sees pre-existing row + * All other trans2 operations deadlock + */ + + Ndb* ndb = GETNDB(step); + Uint32 numScenarios = sizeof(refreshTests) / sizeof(refreshTests[0]); + HugoTransactions hugoTrans(*ctx->getTab()); + + for (Uint32 s = 0; s < numScenarios; s++) + { + RefreshScenario& scenario = refreshTests[s]; + + if (scenario.preExist) + { + /* Create pre-existing tuple */ + if (hugoTrans.loadTable(ndb, 1) != 0) + { + g_err << "Pre-exist failed : " << hugoTrans.getNdbError() << endl; + return NDBT_FAILED; + } + } + + if (hugoTrans.startTransaction(ndb) != 0) + { + g_err << "Start trans failed : " << hugoTrans.getNdbError() << endl; + return NDBT_FAILED; + } + + g_err << "Scenario : " << scenario.name << endl; + + /* Do pre-refresh ops */ + switch (scenario.preRefreshOps) + { + case PR_NONE: + break; + case PR_INSERT: + case PR_INSERTDELETE: + if (hugoTrans.pkInsertRecord(ndb, 0) != 0) + { + g_err << "Pre insert failed : " << hugoTrans.getNdbError() << endl; + return NDBT_FAILED; + } + + if (scenario.preRefreshOps == PR_INSERT) + break; + case PR_DELETE: + if (hugoTrans.pkDeleteRecord(ndb, 0) != 0) + { + g_err << "Pre delete failed : " << hugoTrans.getNdbError() << endl; + return NDBT_FAILED; + } + break; + } + + /* Then refresh */ + if (hugoTrans.pkRefreshRecord(ndb, 0) != 0) + { + g_err << "Refresh failed : " << hugoTrans.getNdbError() << endl; + return NDBT_FAILED; + } + + /* Now execute */ + if (hugoTrans.execute_NoCommit(ndb) != 0) + { + g_err << "Execute failed : " << hugoTrans.getNdbError() << endl; + return NDBT_FAILED; + } + + { + /* Now try ops from another transaction */ + HugoOperations hugoOps(*ctx->getTab()); + Uint32 ot = READ_C; + + while (ot < LAST) + { + if (hugoOps.startTransaction(ndb) != 0) + { + g_err << "Start trans2 failed : " << hugoOps.getNdbError() << endl; + return NDBT_FAILED; + } + + g_err << "Operation type : " << opTypeNames[ot] << endl; + int res = 0; + switch (ot) + { + case READ_C: + res = hugoOps.pkReadRecord(ndb,0,1,NdbOperation::LM_CommittedRead); + break; + case READ_S: + res = hugoOps.pkReadRecord(ndb,0,1,NdbOperation::LM_Read); + break; + case READ_E: + res = hugoOps.pkReadRecord(ndb,0,1,NdbOperation::LM_Exclusive); + break; + case INSERT: + res = hugoOps.pkInsertRecord(ndb, 0); + break; + case UPDATE: + res = hugoOps.pkUpdateRecord(ndb, 0); + break; + case WRITE: + res = hugoOps.pkWriteRecord(ndb, 0); + break; + case DELETE: + res = hugoOps.pkDeleteRecord(ndb, 0); + break; + case LAST: + abort(); + } + + hugoOps.execute_Commit(ndb); + + if ((ot == READ_C) && (scenario.preExist)) + { + if (hugoOps.getNdbError().code == 0) + { + g_err << "Read committed succeeded" << endl; + } + else + { + g_err << "UNEXPECTED : Read committed failed. " << hugoOps.getNdbError() << endl; + return NDBT_FAILED; + } + } + else + { + if (hugoOps.getNdbError().code == 0) + { + g_err << opTypeNames[ot] << " succeeded, should not have" << endl; + return NDBT_FAILED; + } + } + + hugoOps.closeTransaction(ndb); + + ot = ot + 1; + } + + } + + /* Close refresh transaction */ + hugoTrans.closeTransaction(ndb); + + if (scenario.preExist) + { + /* Cleanup pre-existing before next iteration */ + if (hugoTrans.pkDelRecords(ndb, 0) != 0) + { + g_err << "Delete pre existing failed : " << hugoTrans.getNdbError() << endl; + return NDBT_FAILED; + } + } + } + + return NDBT_OK; +} + + NDBT_TESTSUITE(testBasic); TESTCASE("PkInsert", "Verify that we can insert and delete from this table using PK" @@ -2746,6 +3551,12 @@ TESTCASE("UnlockUpdateBatch", STEP(runPkRead); FINALIZER(runClearTable); } +TESTCASE("RefreshTuple", + "Test refreshTuple() operation properties"){ + INITIALIZER(initSubscription); + INITIALIZER(runRefreshTuple); + FINALIZER(removeSubscription); +} TESTCASE("Bug54986", "") { INITIALIZER(runBug54986); @@ -2773,6 +3584,11 @@ TESTCASE("899", "") STEP(runTest899); FINALIZER(runEnd899); } +TESTCASE("RefreshLocking", + "Test Refresh locking properties") +{ + INITIALIZER(runRefreshLocking); +} NDBT_TESTSUITE_END(testBasic); #if 0 === modified file 'storage/ndb/test/ndbapi/testIndex.cpp' --- a/storage/ndb/test/ndbapi/testIndex.cpp 2011-04-28 07:47:53 +0000 +++ b/storage/ndb/test/ndbapi/testIndex.cpp 2011-05-25 13:19:02 +0000 @@ -1744,6 +1744,73 @@ runMixed2(NDBT_Context* ctx, NDBT_Step* return NDBT_FAILED; } +#define check(b, e) \ + if (!(b)) { g_err << "ERR: " << step->getName() << " failed on line " << __LINE__ << ": " << e.getNdbError() << endl; return NDBT_FAILED; } + +int runRefreshTupleAbort(NDBT_Context* ctx, NDBT_Step* step){ + int records = ctx->getNumRecords(); + int loops = ctx->getNumLoops(); + + Ndb* ndb = GETNDB(step); + + const NdbDictionary::Table& tab = *ctx->getTab(); + + for (int i=0; i < tab.getNoOfColumns(); i++) + { + if (tab.getColumn(i)->getStorageType() == NDB_STORAGETYPE_DISK) + { + g_err << "Table has disk column(s) skipping." << endl; + return NDBT_OK; + } + } + + + g_err << "Loading table." << endl; + HugoTransactions hugoTrans(*ctx->getTab()); + check(hugoTrans.loadTable(ndb, records) == 0, hugoTrans); + + HugoOperations hugoOps(*ctx->getTab()); + + /* Check refresh, abort sequence with an ordered index + * Previously this gave bugs due to corruption of the + * tuple version + */ + while (loops--) + { + Uint32 numRefresh = 2 + rand() % 10; + + g_err << "Refresh, rollback * " << numRefresh << endl; + + while (--numRefresh) + { + /* Refresh, rollback */ + check(hugoOps.startTransaction(ndb) == 0, hugoOps); + check(hugoOps.pkRefreshRecord(ndb, 0, records, 0) == 0, hugoOps); + check(hugoOps.execute_NoCommit(ndb) == 0, hugoOps); + check(hugoOps.execute_Rollback(ndb) == 0, hugoOps); + check(hugoOps.closeTransaction(ndb) == 0, hugoOps); + } + + g_err << "Refresh, commit" << endl; + /* Refresh, commit */ + check(hugoOps.startTransaction(ndb) == 0, hugoOps); + check(hugoOps.pkRefreshRecord(ndb, 0, records, 0) == 0, hugoOps); + check(hugoOps.execute_NoCommit(ndb) == 0, hugoOps); + check(hugoOps.execute_Commit(ndb) == 0, hugoOps); + check(hugoOps.closeTransaction(ndb) == 0, hugoOps); + + g_err << "Update, commit" << endl; + /* Update */ + check(hugoOps.startTransaction(ndb) == 0, hugoOps); + check(hugoOps.pkUpdateRecord(ndb, 0, records, 2 + loops) == 0, hugoOps); + check(hugoOps.execute_NoCommit(ndb) == 0, hugoOps); + check(hugoOps.execute_Commit(ndb) == 0, hugoOps); + check(hugoOps.closeTransaction(ndb) == 0, hugoOps); + } + + return NDBT_OK; +} + int runBuildDuring(NDBT_Context* ctx, NDBT_Step* step){ @@ -3619,6 +3686,16 @@ TESTCASE("Bug60851", "") INITIALIZER(runBug60851); FINALIZER(createPkIndex_Drop); } +TESTCASE("RefreshWithOrderedIndex", + "Refresh tuples with ordered index(es)") +{ + TC_PROPERTY("OrderedIndex", 1); + TC_PROPERTY("LoggedIndexes", Uint32(0)); + INITIALIZER(createPkIndex); + INITIALIZER(runRefreshTupleAbort); + FINALIZER(createPkIndex_Drop); + FINALIZER(runClearTable); +} NDBT_TESTSUITE_END(testIndex); int main(int argc, const char** argv){ === modified file 'storage/ndb/test/run-test/daily-devel-tests.txt' --- a/storage/ndb/test/run-test/daily-devel-tests.txt 2011-04-08 11:06:53 +0000 +++ b/storage/ndb/test/run-test/daily-devel-tests.txt 2011-05-25 13:19:02 +0000 @@ -129,3 +129,16 @@ max-time: 1800 cmd: testDict args: -n SchemaTrans -l 1 +# Refresh tuple +max-time: 300 +cmd: testBasic +args: -n RefreshTuple T6 D1 + +max-time: 300 +cmd: testIndex +args: -n RefreshWithOrderedIndex T2 D2 + +max-time: 300 +cmd: testBasic +args: -n RefreshLocking D1 + === modified file 'storage/ndb/test/src/HugoOperations.cpp' --- a/storage/ndb/test/src/HugoOperations.cpp 2011-04-07 07:22:49 +0000 +++ b/storage/ndb/test/src/HugoOperations.cpp 2011-05-25 13:19:02 +0000 @@ -567,6 +567,47 @@ int HugoOperations::pkDeleteRecord(Ndb* return NDBT_OK; } +int HugoOperations::pkRefreshRecord(Ndb* pNdb, + int recordNo, + int numRecords, + int anyValueInfo){ + + char buffer[NDB_MAX_TUPLE_SIZE]; + const NdbDictionary::Table * pTab = + pNdb->getDictionary()->getTable(tab.getName()); + + if (pTab == 0) + { + return NDBT_FAILED; + } + + const NdbRecord * record = pTab->getDefaultRecord(); + NdbOperation::OperationOptions opts; + opts.optionsPresent = NdbOperation::OperationOptions::OO_ANYVALUE; + for(int r=0; r < numRecords; r++) + { + bzero(buffer, sizeof(buffer)); + if (calc.equalForRow((Uint8*)buffer, record, r + recordNo)) + { + return NDBT_FAILED; + } + + opts.anyValue = anyValueInfo? + (anyValueInfo << 16) | (r+recordNo) : + 0; + + const NdbOperation* pOp = pTrans->refreshTuple(record, buffer, + &opts, sizeof(opts)); + if (pOp == NULL) + { + ERR(pTrans->getNdbError()); + setNdbError(pTrans->getNdbError()); + return NDBT_FAILED; + } + } + return NDBT_OK; +} + int HugoOperations::execute_Commit(Ndb* pNdb, AbortOption eao){ === modified file 'storage/ndb/test/src/HugoTransactions.cpp' --- a/storage/ndb/test/src/HugoTransactions.cpp 2011-02-02 00:40:07 +0000 +++ b/storage/ndb/test/src/HugoTransactions.cpp 2011-05-25 13:19:02 +0000 @@ -1502,6 +1502,79 @@ HugoTransactions::pkDelRecords(Ndb* pNdb } int +HugoTransactions::pkRefreshRecords(Ndb* pNdb, + int startFrom, + int count, + int batch) +{ + int r = 0; + int retryAttempt = 0; + + g_info << "|- Refreshing records..." << startFrom << "-" << (startFrom+count) + << " (batch=" << batch << ")" << endl; + + while (r < count) + { + if(r + batch > count) + batch = count - r; + + if (retryAttempt >= m_retryMax) + { + g_info << "ERROR: has retried this operation " << retryAttempt + << " times, failing!" << endl; + return NDBT_FAILED; + } + + pTrans = pNdb->startTransaction(); + if (pTrans == NULL) + { + const NdbError err = pNdb->getNdbError(); + + if (err.status == NdbError::TemporaryError){ + ERR(err); + NdbSleep_MilliSleep(50); + retryAttempt++; + continue; + } + ERR(err); + return NDBT_FAILED; + } + + if (pkRefreshRecord(pNdb, r, batch) != NDBT_OK) + { + ERR(pTrans->getNdbError()); + closeTransaction(pNdb); + return NDBT_FAILED; + } + + if (pTrans->execute(Commit, AbortOnError) == -1) + { + const NdbError err = pTrans->getNdbError(); + + switch(err.status){ + case NdbError::TemporaryError: + ERR(err); + closeTransaction(pNdb); + NdbSleep_MilliSleep(50); + retryAttempt++; + continue; + break; + + default: + ERR(err); + closeTransaction(pNdb); + return NDBT_FAILED; + } + } + + closeTransaction(pNdb); + r += batch; // Read next record + } + + return NDBT_OK; +} + +int HugoTransactions::pkReadUnlockRecords(Ndb* pNdb, int records, int batch, === modified file 'storage/ndb/test/tools/hugoPkUpdate.cpp' --- a/storage/ndb/test/tools/hugoPkUpdate.cpp 2011-02-02 00:40:07 +0000 +++ b/storage/ndb/test/tools/hugoPkUpdate.cpp 2011-05-25 13:19:02 +0000 @@ -43,6 +43,8 @@ struct ThrOutput { NDBT_Stats latency; }; +static int _refresh = 0; + int main(int argc, const char** argv){ ndb_init(); @@ -63,7 +65,9 @@ int main(int argc, const char** argv){ // { "batch", 'b', arg_integer, &_batch, "batch value", "batch" }, { "records", 'r', arg_integer, &_records, "Number of records", "records" }, { "usage", '?', arg_flag, &_help, "Print help", "" }, - { "database", 'd', arg_string, &db, "Database", "" } + { "database", 'd', arg_string, &db, "Database", "" }, + { "refresh", 0, arg_flag, &_refresh, "refresh record rather than update them", "" } + }; int num_args = sizeof(args) / sizeof(args[0]); int optind = 0; @@ -135,7 +139,10 @@ int main(int argc, const char** argv){ ths.stop(); if (ths.get_err()) + { + ths.disconnect(); NDBT_ProgramExit(NDBT_FAILED); + } if (_stats) { NDBT_Stats latency; @@ -160,6 +167,8 @@ int main(int argc, const char** argv){ i++; } + ths.disconnect(); + return NDBT_ProgramExit(NDBT_OK); } @@ -177,9 +186,19 @@ static void hugoPkUpdate(NDBT_Thread& th hugoTrans.setThrInfo(ths.get_count(), thr.get_thread_no()); int ret; - ret = hugoTrans.pkUpdateRecords(thr.get_ndb(), - input->records, - input->batch); + if (_refresh == 0) + { + ret = hugoTrans.pkUpdateRecords(thr.get_ndb(), + input->records, + input->batch); + } + else + { + ret = hugoTrans.pkRefreshRecords(thr.get_ndb(), + 0, + input->records, + input->batch); + } if (ret != 0) thr.set_err(ret); } --===============7821843858136016303== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/frazer.clement@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: frazer.clement@stripped\ # 9mm9q5r4m74wsiix # target_branch: file:///home/frazer/bzr/mysql-5.1-telco-7.0/ # testament_sha1: dc50af8a242d1ac4ec4f4560f2941ed92530148c # timestamp: 2011-05-25 14:19:07 +0100 # base_revision_id: jonas@stripped # # Begin bundle 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 --===============7821843858136016303==--