//-< 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 <code>arr</code> is not null, then this array is used as destination (it should
* be at least selection.nRows long)<BR>
* If <code>arr</code> is null, then new array is created by new oid_t[] and returned by this method
* @return if <code>arr</code> is not null, then <code>arr</code>, otherwise array created by this method
*/
oid_t* toArrayOfOid(oid_t* arr);
/**
* Execute query.
* @param query selection criteria
* @param aType cursor type: <code>dbCursorForUpdate, dbCursorViewOnly</code>
* @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: <code>dbCursorForUpdate, dbCursorViewOnly</code>
* @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: <code>dbCursorForUpdate, dbCursorViewOnly</code>
* @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 <code>NULL</code> then there is no low bound
* @param maxValue inclusive high bound for key values, if <code>NULL</code> 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 <code>false</code> then current record is not fetched. You should explicitly call <code>fetch</code>
* 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 <code>NULL</code>
*/
bool isLast();
/**
* Check whether current record is the first one in the selection
* @return true if prev() method will return <code>NULL</code>
*/
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 <code>n</code> records forward, if negative then skip <code>-n</code>
* records backward
* @return <code>true</code> if specified number of records was successfully skipped, <code>false</code> if
* there is no next (<code>n > 0</code>) or previous (<code>n < 0</code>) 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 T>
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 <code>NULL</code> if there is no current record
*/
T* get() {
return currId == 0 ? (T*)NULL : &record;
}
/**
* Get next record
* @return pointer to the next record or <code>NULL</code> 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 <code>NULL</code> 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 <code>NULL</code> 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 <code>NULL</code> 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<T> 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<T> 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<T> >& arr) {
arr.resize(selection.nRows);
toArrayOfOid((oid_t*)arr.base());
}
/**
* Get current object idenitifer
* @param reference to the current record
*/
dbReference<T> currentId() {
return dbReference<T>(currId);
}
/**
* Method nextAvailable allows to iterate through the records in uniform way even when some records
* are removed. For example:
* <PRE>
* if (cursor.select(q) > 0) {
* do {
* if (x) {
* cursor.remove();
* } else {
* cursor.update();
* }
* } while (cursor.nextAvaiable());
* }
*</PRE>
* @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
syntax highlighted by Code2HTML, v. 0.9.1