539 lines
19 KiB
C++

/*
Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef NdbBlob_H
#define NdbBlob_H
#include <ndb_types.h>
#include "NdbDictionary.hpp"
#include "NdbTransaction.hpp"
#include "NdbError.hpp"
class Ndb;
class NdbTransaction;
class NdbOperation;
class NdbRecAttr;
class NdbTableImpl;
class NdbColumnImpl;
class NdbEventOperationImpl;
/**
* @class NdbBlob
* @brief Blob handle
*
* Blob data is stored in 2 places:
*
* - "header" and "inline bytes" stored in the blob attribute
* - "blob parts" stored in a separate table NDB$BLOB_<tid>_<cid>
*
* Inline and part sizes can be set via NdbDictionary::Column methods
* when the table is created.
*
* NdbBlob is a blob handle. To access blob data, the handle must be
* created using NdbOperation::getBlobHandle in operation prepare phase.
* The handle has following states:
*
* - prepared: before the operation is executed
* - active: after execute or next result but before transaction commit
* - closed: after blob handle is closed or after transaction commit
* - invalid: after rollback or transaction close
*
* NdbBlob supports 3 styles of data access:
*
* - in prepare phase, NdbBlob methods getValue and setValue are used to
* prepare a read or write of a blob value of known size
*
* - in prepare phase, setActiveHook is used to define a routine which
* is invoked as soon as the handle becomes active
*
* - in active phase, readData and writeData are used to read or write
* blob data of arbitrary size
*
* The styles can be applied in combination (in above order).
*
* Blob operations take effect at next transaction execute. In some
* cases NdbBlob is forced to do implicit executes. To avoid this,
* operate on complete blob parts.
*
* Use NdbTransaction::executePendingBlobOps to flush your reads and
* writes. It avoids execute penalty if nothing is pending. It is not
* needed after execute (obviously) or after next scan result.
*
* NdbBlob also supports reading post or pre blob data from events. The
* handle can be read after next event on main table has been retrieved.
* The data is available immediately. See NdbEventOperation.
*
* Non-void NdbBlob methods return -1 on error and 0 on success. Output
* parameters are used when necessary.
*
* Usage notes for different operation types:
*
* - insertTuple must be followed by a setValue() call for every non
* nullable blob in the row.
*
* - readTuple or scan readTuples with lock mode LM_CommittedRead is
* temporarily upgraded to lock mode LM_Read if any blob attributes
* are accessed (to guarantee consistent view). After the Blob
* handle is closed, the LM_Read lock is removed on the next
* execute() call.
*
* - readTuple (with any lock mode) can only read blob value
*
* - updateTuple can either overwrite existing value with setValue or
* update it in active phase
*
* - writeTuple always overwrites blob value and must use setValue if
* blob attribute is non-nullable
*
* - deleteTuple creates implicit non-accessible blob handles
*
* - scan readTuples (any lock mode) can use its blob handles only
* to read blob value
*
* - scan readTuples with lock mode LM_Exclusive can update row and blob
* value using updateCurrentTuple, where the operation returned must
* create its own blob handles explicitly
*
* - scan readTuples with lock mode LM_Exclusive can delete row (and
* therefore blob values) using deleteCurrentTuple, which creates
* implicit non-accessible blob handles
*
* - the operation returned by lockCurrentTuple cannot update blob value
*
* Bugs / limitations:
*
* - too many pending blob ops can blow up i/o buffers
*
* - table and its blob part tables are not created atomically
*/
#ifndef DOXYGEN_SHOULD_SKIP_INTERNAL
/**
* - there is no support for an asynchronous interface
*/
#endif
class NdbBlob {
public:
/**
* State.
*/
enum State {
Idle = 0,
Prepared = 1,
Active = 2,
Closed = 3,
Invalid = 9
};
/**
* Get the state of a NdbBlob object.
*/
State getState();
/**
* Returns -1 for normal statement based blob and 0/1 for event
* operation post/pre data blob. Always succeeds.
*/
void getVersion(int& version);
#ifndef DOXYGEN_SHOULD_SKIP_INTERNAL
/**
* Blob head V1 is 8 bytes:
* 8 bytes blob length - native endian (of ndb apis)
*
* Blob head V2 is 16 bytes:
* 2 bytes head+inline length bytes (MEDIUM_VAR) - little-endian
* 2 bytes reserved (zero)
* 4 bytes NDB$PKID for blob events - little-endian
* 8 bytes blob length - litte-endian
*
* Following struct is for packing/unpacking the fields. It must
* not be C-cast to/from the head+inline attribute value.
*/
struct Head {
Uint16 varsize; // length of head+inline minus the 2 length bytes
Uint16 reserved; // must be 0 wl3717_todo checksum?
Uint32 pkid; // connects part and row with same PK within tx
Uint64 length; // blob length
//
Uint32 headsize; // for convenience, number of bytes in head
Head() :
varsize(0), reserved(0), pkid(0), length(0), headsize(0) {}
};
static void packBlobHead(const Head& head, char* buf, int blobVersion);
static void unpackBlobHead(Head& head, const char* buf, int blobVersion);
#endif
/**
* Prepare to read blob value. The value is available after execute.
* Use getNull() to check for NULL and getLength() to get the real length
* and to check for truncation. Sets current read/write position to
* after the data read.
*/
int getValue(void* data, Uint32 bytes);
/**
* Prepare to insert or update blob value. An existing longer blob
* value will be truncated. The data buffer must remain valid until
* execute. Sets current read/write position to after the data. Set
* data to null pointer (0) to create a NULL value.
*/
int setValue(const void* data, Uint32 bytes);
/**
* Callback for setActiveHook(). Invoked immediately when the prepared
* operation has been executed (but not committed). Any getValue() or
* setValue() is done first. The blob handle is active so readData or
* writeData() etc can be used to manipulate blob value. A user-defined
* argument is passed along. Returns non-zero on error.
*/
typedef int ActiveHook(NdbBlob* me, void* arg);
/**
* Define callback for blob handle activation. The queue of prepared
* operations will be executed in no commit mode up to this point and
* then the callback is invoked.
*/
int setActiveHook(ActiveHook* activeHook, void* arg);
#ifndef DOXYGEN_SHOULD_SKIP_DEPRECATED
int getDefined(int& isNull);
int getNull(bool& isNull);
#endif
/**
* Return -1, 0, 1 if blob is undefined, non-null, or null. For
* non-event blob, undefined causes a state error.
*/
int getNull(int& isNull);
/**
* Set blob to NULL.
*/
int setNull();
/**
* Get current length in bytes. Use getNull to distinguish between
* length 0 blob and NULL blob.
*/
int getLength(Uint64& length);
/**
* Truncate blob to given length. Has no effect if the length is
* larger than current length.
*/
int truncate(Uint64 length = 0);
/**
* Get current read/write position.
*/
int getPos(Uint64& pos);
/**
* Set read/write position. Must be between 0 and current length.
* "Sparse blobs" are not supported.
*/
int setPos(Uint64 pos);
/**
* Read at current position and set new position to first byte after
* the data read. A read past blob end returns actual number of bytes
* read in the in/out bytes parameter.
*/
int readData(void* data, Uint32& bytes);
/**
* Write at current position and set new position to first byte after
* the data written. A write past blob end extends the blob value.
*/
int writeData(const void* data, Uint32 bytes);
/**
* Return the blob column.
*/
const NdbDictionary::Column* getColumn();
/**
* Get blob parts table name. Useful only to test programs.
*/
static int getBlobTableName(char* btname, Ndb* anNdb, const char* tableName, const char* columnName);
/**
* Get blob event name. The blob event is created if the main event
* monitors the blob column. The name includes main event name.
*/
static int getBlobEventName(char* bename, Ndb* anNdb, const char* eventName, const char* columnName);
/**
* Return error object. The error may be blob specific or may be
* copied from a failed implicit operation.
*
* The error code is copied back to the operation unless the operation
* already has a non-zero error code.
*/
const NdbError& getNdbError() const;
/**
* Get a pointer to the operation which this Blob Handle
* was initially created as part of.
* Note that this could be a scan operation.
* Note that the pointer returned is a const pointer.
*/
const NdbOperation* getNdbOperation() const;
/**
* Return info about all blobs in this operation.
*
* Get first blob in list.
*/
NdbBlob* blobsFirstBlob();
/**
* Return info about all blobs in this operation.
*
* Get next blob in list. Initialize with blobsFirstBlob().
*/
NdbBlob* blobsNextBlob();
/**
* Close the BlobHandle
*
* The BlobHandle can be closed to release internal
* resources before transaction commit / abort time.
*
* The close method can only be called when the Blob is in
* Active state.
*
* If execPendingBlobOps = true then pending Blob operations
* will be flushed before the Blob handle is closed.
* If execPendingBlobOps = false then the Blob handle must
* have no pending read or write operations.
*
* Read operations and locks
*
* Where a Blob handle is created on a read operation using
* lockmode LM_Read or LM_Exclusive, the read operation can
* only be unlocked after all Blob handles created on the
* operation are closed.
*
* Where a row containing Blobs has been read with lockmode
* LM_CommittedRead, the lockmode is automatically upgraded to
* LM_Read to ensure consistency.
* In this case, when all the BlobHandles for the row have been
* close()d, an unlock operation for the row is automatically
* issued by the close() call, adding a pending 'write' operation
* to the Blob.
* After the next execute() call, the upgraded lock is released.
*/
int close(bool execPendingBlobOps = true);
private:
#ifndef DOXYGEN_SHOULD_SKIP_INTERNAL
friend class Ndb;
friend class NdbTransaction;
friend class NdbOperation;
friend class NdbScanOperation;
friend class NdbDictionaryImpl;
friend class NdbResultSet; // atNextResult
friend class NdbEventBuffer;
friend class NdbEventOperationImpl;
friend class NdbReceiver;
#endif
int theBlobVersion;
/*
* Disk data does not yet support Var* attrs. In both V1 and V2,
* if the primary table blob attr is specified as disk attr then:
* - the primary table blob attr remains a memory attr
* - the blob parts "DATA" attr becomes a disk attr
* - the blob parts "DATA" attr is fixed size
* Use following flag. It is always set for V1.
*/
bool theFixedDataFlag;
Uint32 theHeadSize;
Uint32 theVarsizeBytes;
// state
State theState;
// True if theNdbOp is using NdbRecord, false if NdbRecAttr.
bool theNdbRecordFlag;
void setState(State newState);
// quick and dirty support for events (consider subclassing)
int theEventBlobVersion; // -1=data op 0=post event 1=pre event
// define blob table
static void getBlobTableName(char* btname, const NdbTableImpl* t, const NdbColumnImpl* c);
static int getBlobTable(NdbTableImpl& bt, const NdbTableImpl* t, const NdbColumnImpl* c, struct NdbError& error);
static void getBlobEventName(char* bename, const NdbEventImpl* e, const NdbColumnImpl* c);
static void getBlobEvent(NdbEventImpl& be, const NdbEventImpl* e, const NdbColumnImpl* c);
// compute blob table column number for faster access
enum {
BtColumnPk = 0, /* V1 only */
BtColumnDist = 1, /* if stripe size != 0 */
BtColumnPart = 2,
BtColumnPkid = 3, /* V2 only */
BtColumnData = 4
};
int theBtColumnNo[5];
// ndb api stuff
Ndb* theNdb;
NdbTransaction* theNdbCon;
NdbOperation* theNdbOp;
NdbEventOperationImpl* theEventOp;
NdbEventOperationImpl* theBlobEventOp;
NdbRecAttr* theBlobEventPkRecAttr;
NdbRecAttr* theBlobEventDistRecAttr;
NdbRecAttr* theBlobEventPartRecAttr;
NdbRecAttr* theBlobEventPkidRecAttr;
NdbRecAttr* theBlobEventDataRecAttr;
const NdbTableImpl* theTable;
const NdbTableImpl* theAccessTable;
const NdbTableImpl* theBlobTable;
const NdbColumnImpl* theColumn;
unsigned char theFillChar;
// sizes
Uint32 theInlineSize;
Uint32 thePartSize;
Uint32 theStripeSize;
// getValue/setValue
bool theGetFlag;
char* theGetBuf;
bool theSetFlag;
bool theSetValueInPreExecFlag;
const char* theSetBuf;
Uint32 theGetSetBytes;
// pending ops
Uint8 thePendingBlobOps;
// activation callback
ActiveHook* theActiveHook;
void* theActiveHookArg;
// buffers
struct Buf {
char* data;
unsigned size;
unsigned maxsize;
Buf();
~Buf();
void alloc(unsigned n);
void release();
void zerorest();
void copyfrom(const Buf& src);
};
Buf theKeyBuf;
Buf theAccessKeyBuf;
Buf thePackKeyBuf;
Buf theHeadInlineBuf;
Buf theHeadInlineCopyBuf; // for writeTuple
Buf thePartBuf;
Uint16 thePartLen;
Buf theBlobEventDataBuf;
Uint32 theBlobEventDistValue;
Uint32 theBlobEventPartValue;
Uint32 theBlobEventPkidValue;
Head theHead;
char* theInlineData;
NdbRecAttr* theHeadInlineRecAttr;
NdbOperation* theHeadInlineReadOp;
bool theHeadInlineUpdateFlag;
// partition id for data events
bool userDefinedPartitioning;
Uint32 noPartitionId() { return ~(Uint32)0; }
Uint32 thePartitionId;
NdbRecAttr* thePartitionIdRecAttr;
// length and read/write position
int theNullFlag;
Uint64 theLength;
Uint64 thePos;
// errors
NdbError theError;
// for keeping in lists
NdbBlob* theNext;
// initialization
NdbBlob(Ndb*);
void init();
void release();
// classify operations
bool isTableOp();
bool isIndexOp();
bool isKeyOp();
bool isReadOp();
bool isInsertOp();
bool isUpdateOp();
bool isWriteOp();
bool isDeleteOp();
bool isScanOp();
bool isReadOnlyOp();
bool isTakeOverOp();
// computations
Uint32 getPartNumber(Uint64 pos);
Uint32 getPartOffset(Uint64 pos);
Uint32 getPartCount();
Uint32 getDistKey(Uint32 part);
// pack / unpack
int packKeyValue(const NdbTableImpl* aTable, const Buf& srcBuf);
int unpackKeyValue(const NdbTableImpl* aTable, Buf& dstBuf);
int copyKeyFromRow(const NdbRecord *record, const char *row,
Buf& packedBuf, Buf& unpackedBuf);
Uint32 getHeadInlineSize() { return theHeadSize + theInlineSize; }
void prepareSetHeadInlineValue();
void getNullOrEmptyBlobHeadDataPtr(const char * & data, Uint32 & byteSize);
// getters and setters
void packBlobHead();
void unpackBlobHead();
int getTableKeyValue(NdbOperation* anOp);
int setTableKeyValue(NdbOperation* anOp);
int setAccessKeyValue(NdbOperation* anOp);
int setDistKeyValue(NdbOperation* anOp, Uint32 part);
int setPartKeyValue(NdbOperation* anOp, Uint32 part);
int setPartPkidValue(NdbOperation* anOp, Uint32 pkid);
int getPartDataValue(NdbOperation* anOp, char* buf, Uint16* aLenLoc);
int setPartDataValue(NdbOperation* anOp, const char* buf, const Uint16& aLen);
int getHeadInlineValue(NdbOperation* anOp);
void getHeadFromRecAttr();
int setHeadInlineValue(NdbOperation* anOp);
void setHeadPartitionId(NdbOperation* anOp);
void setPartPartitionId(NdbOperation* anOp);
// data operations
int readDataPrivate(char* buf, Uint32& bytes);
int writeDataPrivate(const char* buf, Uint32 bytes);
int readParts(char* buf, Uint32 part, Uint32 count);
int readPart(char* buf, Uint32 part, Uint16& len);
int readTableParts(char* buf, Uint32 part, Uint32 count);
int readTablePart(char* buf, Uint32 part, Uint16& len);
int readEventParts(char* buf, Uint32 part, Uint32 count);
int readEventPart(char* buf, Uint32 part, Uint16& len);
int insertParts(const char* buf, Uint32 part, Uint32 count);
int insertPart(const char* buf, Uint32 part, const Uint16& len);
int updateParts(const char* buf, Uint32 part, Uint32 count);
int updatePart(const char* buf, Uint32 part, const Uint16& len);
int deletePartsThrottled(Uint32 part, Uint32 count);
int deleteParts(Uint32 part, Uint32 count);
int deletePartsUnknown(Uint32 part);
// pending ops
int executePendingBlobReads();
int executePendingBlobWrites();
// callbacks
int invokeActiveHook();
// blob handle maintenance
int atPrepare(NdbTransaction* aCon, NdbOperation* anOp, const NdbColumnImpl* aColumn);
int atPrepareNdbRecord(NdbTransaction* aCon, NdbOperation* anOp,
const NdbColumnImpl* aColumn,
const NdbRecord *key_record, const char *key_row);
int atPrepareNdbRecordTakeover(NdbTransaction* aCon, NdbOperation* anOp,
const NdbColumnImpl* aColumn,
const char *keyinfo, Uint32 keyinfo_bytes);
int atPrepareNdbRecordScan(NdbTransaction* aCon, NdbOperation* anOp,
const NdbColumnImpl* aColumn);
int atPrepareCommon(NdbTransaction* aCon, NdbOperation* anOp,
const NdbColumnImpl* aColumn);
int atPrepare(NdbEventOperationImpl* anOp, NdbEventOperationImpl* aBlobOp, const NdbColumnImpl* aColumn, int version);
int prepareColumn();
int preExecute(NdbTransaction::ExecType anExecType, bool& batch);
int postExecute(NdbTransaction::ExecType anExecType);
int preCommit();
int atNextResult();
int atNextResultNdbRecord(const char *keyinfo, Uint32 keyinfo_bytes);
int atNextResultCommon();
int atNextEvent();
// errors
void setErrorCode(int anErrorCode, bool invalidFlag = false);
void setErrorCode(NdbOperation* anOp, bool invalidFlag = false);
void setErrorCode(NdbEventOperationImpl* anOp, bool invalidFlag = false);
// list stuff
void next(NdbBlob* obj) { theNext= obj;}
NdbBlob* next() { return theNext;}
friend struct Ndb_free_list_t<NdbBlob>;
NdbBlob(const NdbBlob&); // Not impl.
NdbBlob&operator=(const NdbBlob&);
};
#endif