//-< CURSOR.H >------------------------------------------------------*--------* // FastDB Version 1.0 (c) 1999 GARRET * ? * // (Main Memory Database Management System) * /\| * // * / \ * // Created: 20-Nov-98 K.A. Knizhnik * / [] \ * // Last update: 10-Dec-98 K.A. Knizhnik * GARRET * //-------------------------------------------------------------------*--------* // Table cursor //-------------------------------------------------------------------*--------* #ifndef __CURSOR_H__ #define __CURSOR_H__ class dbOrderByNode; class FASTDB_DLL_ENTRY dbSelection { public: enum { quantum = 1024 }; class segment { public: segment* prev; segment* next; size_t nRows; oid_t rows[quantum]; segment(segment* after) { prev = after; next = NULL; nRows = 0; } }; segment* first; segment* last; segment* curr; size_t nRows; size_t pos; segment* createNewSegment(segment* after); void add(oid_t oid) { if (last == NULL) { first = last = createNewSegment(NULL); } else if (last->nRows == quantum) { last = last->next = createNewSegment(last); } last->rows[last->nRows++] = oid; nRows += 1; } void sort(dbDatabase* db, dbOrderByNode* order); static int compare(dbRecord* a, dbRecord* b, dbOrderByNode* order); void toArray(oid_t* oids); dbSelection() { nRows = 0; pos = 0; first = curr = last = NULL; } void reverse(); void reset(); }; enum dbCursorType { dbCursorViewOnly, dbCursorForUpdate }; /** * Base class for all cursors */ class FASTDB_DLL_ENTRY dbAnyCursor : public dbL2List { friend class dbAnyContainer; friend class dbDatabase; friend class dbHashTable; friend class dbTtreeNode; friend class dbSubSql; friend class dbStatement; friend class dbServer; friend class dbCLI; public: /** * Get number of selected records * @return number of selected records */ int getNumberOfRecords() { return selection.nRows; } /** * Remove current record */ void remove(); /** * Checks whether selection is empty * @return true if there is no current record */ bool isEmpty() { return currId == 0; } /** * Checks whether limit for number of selected reacord is reached * @return true if limit is reached */ bool isLimitReached() { return selection.nRows >= limit; } /** * Extract OIDs of selected recrods in array * @param arr if arr is not null, then this array is used as destination (it should * be at least selection.nRows long)
* If arr is null, then new array is created by new oid_t[] and returned by this method * @return if arr is not null, then arr, otherwise array created by this method */ oid_t* toArrayOfOid(oid_t* arr); /** * Execute query. * @param query selection criteria * @param aType cursor type: dbCursorForUpdate, dbCursorViewOnly * @param paramStruct pointer to structure with parameters. If you want to create reentrant precompiled query, i.e. * query which can be used concurrently by different threadsm you should avoid to use static variables in * such query, and instead of it place paramters into some structure, specify in query relative offsets to the parameters, * fill local structure and pass pointer to it to select method. * @return number of selected records */ int select(dbQuery& query, dbCursorType aType, void* paramStruct = NULL) { type = aType; reset(); paramBase = paramStruct; db->select(this, query); paramBase = NULL; if (gotoFirst() && prefetch) { fetch(); } return selection.nRows; } /** * Execute query with default cursor type. * @param query selection criteria * @param paramStruct pointer to structure with parameters. * @return number of selected records */ int select(dbQuery& query, void* paramStruct = NULL) { return select(query, defaultType, paramStruct); } /** * Execute query. * @param condition selection criteria * @param aType cursor type: dbCursorForUpdate, dbCursorViewOnly * @param paramStruct pointer to structure with parameters. * @return number of selected records */ int select(char const* condition, dbCursorType aType, void* paramStruct = NULL) { dbQuery query(condition); return select(query, aType, paramStruct); } /** * Execute query with default cursor type. * @param condition selection criteria * @param paramStruct pointer to structure with parameters. * @return number of selected records */ int select(char const* condition, void* paramStruct = NULL) { return select(condition, defaultType, paramStruct); } /** * Select all records from the table * @param aType cursor type: dbCursorForUpdate, dbCursorViewOnly * @return number of selected records */ int select(dbCursorType aType) { type = aType; reset(); db->select(this); if (gotoFirst() && prefetch) { fetch(); } return selection.nRows; } /** * Select all records from the table with default cursor type * @return number of selected records */ int select() { return select(defaultType); } /** * Select all records from the table with specfied value of the key * @param key name of the key field * @param value searched value of the key * @return number of selected records */ int selectByKey(char const* key, void const* value); /** * Select all records from the table with specfied range of the key values * @param key name of the key field * @param minValue inclusive low bound for key values, if NULL then there is no low bound * @param maxValue inclusive high bound for key values, if NULL then there is no high bound * @return number of selected records */ int selectByKeyRange(char const* key, void const* minValue, void const* maxValue); /** * Update current record. You should changed value of current record before and then call * update method to save changes to the database */ void update() { assert(type == dbCursorForUpdate && currId != 0); updateInProgress = true; db->update(currId, table, record); updateInProgress = false; } /** * Remove all records in the table */ void removeAll() { assert(db != NULL); db->deleteTable(table); reset(); } /** * Remove all selected records */ void removeAllSelected(); /** * Specify maximal number of records to be selected */ void setSelectionLimit(size_t lim) { limit = lim; } /** * Remove selection limit */ void unsetSelectionLimit() { limit = dbDefaultSelectionLimit; } /** * Set prefetch mode. By default, current record is fetch as soon as it is becomes current. * But sometimesyou need only OIDs of selected records. In this case setting prefetchMode to false can help. * @param mode if false then current record is not fetched. You should explicitly call fetch * method if you want to fetch it. */ void setPrefetchMode(bool mode) { prefetch = mode; } /** * Reset cursor */ void reset(); /** * Check whether current record is the last one in the selection * @return true if next() method will return NULL */ bool isLast(); /** * Check whether current record is the first one in the selection * @return true if prev() method will return NULL */ bool isFirst(); /** * Freeze cursor. This method makes it possible to save current state of cursor, close transaction to allow * other threads to proceed, and then later restore state of the cursor using unfreeze method and continue * traversal through selected records. */ void freeze(); /** * Unfreeze cursor. This method starts new transaction and restore state of the cursor */ void unfreeze(); /** * Skip specified number of records * @param n if positive then skip n records forward, if negative then skip -n * records backward * @return true if specified number of records was successfully skipped, false if * there is no next (n > 0) or previous (n < 0) record in the selction. */ bool skip(int n); /** * Position cursor on the record with the specified OID * @param oid object identifier of record * @return poistion of the record in the selection or -1 if record with such OID is not in selection */ int seek(oid_t oid); /** * Get table for which cursor is opened */ dbTableDescriptor* getTable() { return table; } /** * Set table for the cursor * @param aTable table which records will be iterated */ void setTable(dbTableDescriptor* aTable) { table = aTable; db = aTable->db; } /** * Set destination for selected record * rec - buffer to which fields of current record will be fetched */ void setRecord(void* rec) { record = (byte*)rec; } /** * Get pointer to the location where fields of the current record are fetched * @return pointer to the memory location set by cursor constructor or setRecord method */ void* getRecord() { return record; } protected: dbDatabase* db; dbTableDescriptor* table; dbCursorType type; dbCursorType defaultType; dbSelection selection; bool allRecords; oid_t firstId; oid_t lastId; oid_t currId; byte* record; size_t limit; int4* bitmap; // bitmap to avoid duplicates size_t bitmapSize; bool eliminateDuplicates; bool prefetch; bool removed; // current record was removed bool updateInProgress; void* paramBase; void checkForDuplicates(); bool isMarked(oid_t oid) { return bitmap != NULL && (bitmap[oid >> 5] & (1 << (oid & 31))) != 0; } void mark(oid_t oid) { if (bitmap != NULL) { bitmap[oid >> 5] |= 1 << (oid & 31); } } bool add(oid_t oid) { if (selection.nRows < limit) { if (eliminateDuplicates) { if (bitmap[oid >> 5] & (1 << (oid & 31))) { return true; } bitmap[oid >> 5] |= 1 << (oid & 31); } selection.add(oid); return selection.nRows < limit; } return false; } bool gotoNext(); bool gotoPrev(); bool gotoFirst(); bool gotoLast(); void setCurrent(dbAnyReference const& ref); void fetch() { assert(!(db->currIndex[currId] & (dbInternalObjectMarker|dbFreeHandleMarker))); table->columns->fetchRecordFields(record, (byte*)db->getRow(currId)); } void adjustReferences(size_t base, size_t size, long shift) { if (currId != 0) { table->columns->adjustReferences(record, base, size, shift); } } dbAnyCursor(dbTableDescriptor& aTable, dbCursorType aType, byte* rec) : table(&aTable),type(aType),defaultType(aType), allRecords(false),currId(0),record(rec) { db = aTable.db; limit = dbDefaultSelectionLimit; updateInProgress = false; prefetch = true; removed = false; bitmap = NULL; bitmapSize = 0; eliminateDuplicates = false; paramBase = NULL; } public: dbAnyCursor() : table(NULL),type(dbCursorViewOnly),defaultType(dbCursorViewOnly), allRecords(false),currId(0),record(NULL) { limit = dbDefaultSelectionLimit; updateInProgress = false; prefetch = false; removed = false; bitmap = NULL; bitmapSize = 0; eliminateDuplicates = false; db = NULL; paramBase = NULL; } ~dbAnyCursor(); }; /** * Cursor template parameterized by table class */ template class dbCursor : public dbAnyCursor { protected: T record; public: /** * Cursor constructor * @param type cursor type (dbCursorViewOnly by default) */ dbCursor(dbCursorType type = dbCursorViewOnly) : dbAnyCursor(T::dbDescriptor, type, (byte*)&record) {} /** * Cursor constructor with explicit specification of database. * This cursor should be used for unassigned tables. * @param aDB database in which table lokkup is performed * @param type cursor type (dbCursorViewOnly by default) */ dbCursor(dbDatabase* aDb, dbCursorType type = dbCursorViewOnly) : dbAnyCursor(T::dbDescriptor, type, (byte*)&record) { db = aDb; dbTableDescriptor* theTable = db->lookupTable(table); if (theTable != NULL) { table = theTable; } } /** * Get pointer to the current record * @return pointer to the current record or NULL if there is no current record */ T* get() { return currId == 0 ? (T*)NULL : &record; } /** * Get next record * @return pointer to the next record or NULL if there is no next record */ T* next() { if (gotoNext()) { fetch(); return &record; } return NULL; } /** * Get previous record * @return pointer to the previous record or NULL if there is no previous record */ T* prev() { if (gotoPrev()) { fetch(); return &record; } return NULL; } /** * Get pointer to the first record * @return pointer to the first record or NULL if no records were selected */ T* first() { if (gotoFirst()) { fetch(); return &record; } return NULL; } /** * Get pointer to the last record * @return pointer to the last record or NULL if no records were selected */ T* last() { if (gotoLast()) { fetch(); return &record; } return NULL; } /** * Position cursor on the record with the specified OID * @param oid object identifier of record * @return poistion of the record in the selection or -1 if record with such OID is not in selection */ int seek(dbReference const& ref) { return dbAnyCursor::seek(ref.getOid()); } /** * Overloaded operator for accessing components of the current record * @return pointer to the current record */ T* operator ->() { assert(currId != 0); return &record; } /** * Select record by reference * @param ref reference to the record * @return pointer to the referenced record */ T* at(dbReference const& ref) { setCurrent(ref); return &record; } /** * Convert selection to array of reference * @param arr [OUT] array of refeences in which references to selected recrods will be placed */ void toArray(dbArray< dbReference >& arr) { arr.resize(selection.nRows); toArrayOfOid((oid_t*)arr.base()); } /** * Get current object idenitifer * @param reference to the current record */ dbReference currentId() { return dbReference(currId); } /** * Method nextAvailable allows to iterate through the records in uniform way even when some records * are removed. For example: *
     * if (cursor.select(q) > 0) { 
     *     do { 
     *         if (x) { 
     *             cursor.remove();
     *         } else { 
     *             cursor.update();
     *         }
     *     } while (cursor.nextAvaiable());
     *  }
     *
* @return pointer to the current record */ T* nextAvailable() { if (!removed) { return next(); } else { removed = false; return get(); } } }; class dbParallelQueryContext { public: dbDatabase* const db; dbCompiledQuery* const query; oid_t firstRow; dbTable* table; dbAnyCursor* cursor; dbSelection selection[dbMaxParallelSearchThreads]; void search(int i); dbParallelQueryContext(dbDatabase* aDb, dbTable* aTable, dbCompiledQuery* aQuery, dbAnyCursor* aCursor) : db(aDb), query(aQuery), firstRow(aTable->firstRow), table(aTable), cursor(aCursor) {} }; extern char* strupper(char* s); extern char* strlower(char* s); #endif