//-< DATABASE.H >----------------------------------------------------*--------*
// GigaBASE                  Version 1.0         (c) 1999  GARRET    *     ?  *
// (Post Relational Database Management System)                      *   /\|  *
//                                                                   *  /  \  *
//                          Created:     20-Nov-98    K.A. Knizhnik  * / [] \ *
//                          Last update: 14-Feb-99    K.A. Knizhnik  * GARRET *
//-------------------------------------------------------------------*--------*
// Database management
//-------------------------------------------------------------------*--------*

#ifndef __DATABASE_H__
#define __DATABASE_H__

#include "class.h"
#include "reference.h"
#include "file.h"
#include "pagepool.h"

BEGIN_GIGABASE_NAMESPACE

const size_t dbDefaultInitIndexSize = 512*1024;
const size_t dbDefaultExtensionQuantum = 4*1024*1024;

const size_t dbFileExtensionQuantum = 0; 

const unsigned dbMaxParallelSearchThreads = 64;

enum dbHandleFlags {
    dbPageObjectFlag = 0x1,
    dbModifiedFlag   = 0x2,
    dbFreeHandleFlag = 0x4,
#if 1 // for compatibility reasons
    dbVisitedFlag    = 0x4,
    dbFlagsMask      = 0x7,
    dbFlagsBits      = 3
#else
    dbVisitedFlag    = 0x8,
    dbFlagsMask      = 0xF,
    dbFlagsBits      = 4
#endif
};

const size_t dbAllocationQuantumBits = 6;
const size_t dbAllocationQuantum = 1 << dbAllocationQuantumBits;
const size_t dbPageBits = 13;
const size_t dbPageSize = 1 << dbPageBits;
const size_t dbIdsPerPage = dbPageSize / sizeof(oid_t);
const size_t dbHandlesPerPage = dbPageSize / sizeof(offs_t);
const size_t dbBitmapSegmentBits = dbPageBits + 3 + dbAllocationQuantumBits;
const size_t dbBitmapSegmentSize = 1 << dbBitmapSegmentBits;
const size_t dbBitmapPages = 1 << (dbDatabaseOffsetBits-dbBitmapSegmentBits);
const size_t dbDirtyPageBitmapSize = 1 << (dbDatabaseOidBits-dbPageBits-3);
const size_t dbDefaultSelectionLimit = 2000000000;

const int    dbBMsearchThreshold = 512;

const char_t dbMatchAnyOneChar = '_';
const char_t dbMatchAnySubstring = '%';

const int    dbMaxFileSegments = 64;

enum dbPredefinedIds {
    dbInvalidId,
    dbMetaTableId,
    dbBitmapId,
    dbFirstUserId = dbBitmapId + dbBitmapPages
};

enum dbLockType { 
    dbNoLock,
    dbSharedLock,
    dbUpdateLock,
    dbExclusiveLock
};

class dbHeader {
  public:
    int4   curr;  // current root
    int4   dirty; // database was not closed normally
    int4   initialized; // database is initilaized
    struct {
	offs_t size;            // database file size
	offs_t index;           // offset to object index
	offs_t shadowIndex;     // offset to shadow index
	oid_t  indexSize;       // size of object index
	oid_t  shadowIndexSize; // size of object index
	oid_t  indexUsed;       // used part of the index
	oid_t  freeList;        // L1 list of free descriptors
	oid_t  bitmapEnd;       // index of last allocated bitmap page
    } root[2];

    bool isInitialized() {
	return initialized == 1
	    && (dirty == 1 || dirty == 0)
	    && (curr == 1 || curr == 0)
	    && root[curr].size > root[curr].index
	    && root[curr].size > root[curr].shadowIndex
	    && root[curr].size > root[curr].indexSize*sizeof(offs_t)
 	                       + root[curr].shadowIndexSize*sizeof(offs_t)
	    && root[curr].indexSize >= root[curr].indexUsed
	    && root[curr].indexUsed >= dbFirstUserId
	    && root[curr].bitmapEnd > dbBitmapId;
    }
};

class dbSynthesizedAttribute;
class dbInheritedAttribute;
class dbDatabaseThreadContext;

class dbMonitor {
  public:
    dbLockType accLock; 

    dbDatabaseThreadContext* firstPending;
    dbDatabaseThreadContext* lastPending;

    int        nLockUpgrades;

    int        nReaders;
    int        nWriters;
    int        backupInProgress;

    void wait(dbLockType type, dbMutex& mutex, dbDatabaseThreadContext* ctx);

    dbMonitor() { 
	firstPending = lastPending = NULL;
	accLock = dbNoLock;
	backupInProgress = 0;
	nReaders = nWriters = 0;
	nLockUpgrades = 0;
    }
};



class dbAnyCursor;
class dbQuery;
class dbExprNode;
class dbSearchContext;


class GIGABASE_DLL_ENTRY dbDatabase {
    friend class dbSelection;
    friend class dbAnyCursor;
    friend class dbHashTable;
    friend class dbQuery;
    friend class dbBtree;
    friend class dbBtreePage;
    friend class dbBtreeLeafPage;
    friend class dbInheritedAttribute;
    friend class dbParallelQueryContext;
    friend class dbServer;

    friend class dbBlob;
    friend class dbBlobIterator;
    friend class dbBlobReadIterator;
    friend class dbBlobWriteIterator;
    friend class dbAnyContainer;

    friend class dbGetTie;
    friend class dbPutTie;

  public:
    bool open(char_t const* databaseName, time_t transactionCommitDelay = 0);
    void close();

    void commit();
    void precommit();
    void rollback();

    void attach();
    void detach();

    void lock(dbLockType type = dbExclusiveLock) { beginTransaction(type); }

    bool backup(char_t const* backupFileName);
    bool restore(char_t const* backupFileName, char_t const* databaseFileName);

    int  getVersion();

    void assign(dbTableDescriptor& desc) {
	assert(((void)"Table is not yet assigned to the database",
		desc.tableId == 0));
	desc.db = this;
	desc.fixedDatabase = true;
    }

    dbTableDescriptor* lookupTable(dbTableDescriptor* desc);

    void setConcurrency(unsigned nThreads);

    long getAllocatedSize() { return allocatedSize; }

    enum dbErrorClass {
	NoError, 
	QueryError,
	ArithmeticError,
	IndexOutOfRangeError,
	DatabaseOpenError,
	FileError,
	OutOfMemoryError,
	Deadlock,
	NullReferenceError
    };


    virtual void scheduleBackup(char_t const* fileName, time_t periodSec);

    virtual void handleError(dbErrorClass error, char const* msg = NULL,
			     int arg = 0);

    enum dbAccessType {
	dbReadOnly  = 0,
	dbAllAccess = 1
    };
    const dbAccessType accessType;
    const size_t extensionQuantum;
    const size_t initIndexSize;

    static unsigned dbParallelScanThreshold;

    void insertRecord(dbTableDescriptor* table, dbAnyReference* ref,
		      void const* record);

    offs_t used();

#ifndef NO_MEMBER_TEMPLATES
    template<class T>
    dbReference<T> insert(T const& record) {
        dbReference<T> ref;
        insertRecord(lookupTable(&T::dbDescriptor), &ref, &record);
        return ref;
    }
#endif

    dbDatabase(dbAccessType type = dbAllAccess,
	       size_t poolSize = 0, // autodetect size of available memory
	       size_t dbExtensionQuantum = dbDefaultExtensionQuantum,
	       size_t dbInitIndexSize = dbDefaultInitIndexSize,
	       int nThreads = 1
	       // Do not specify the last parameter - it is only for checking
	       // that application and GigaBASE library were built with the
	       // same compiler options (-DNO_PTHREADS is critical)
	       // Mismached parameters should cause linker error
#ifdef NO_PTHREADS
	       , bool usePthreads = false
#endif
	       );

    virtual ~dbDatabase();

    static void cleanup();
    
  protected:
    dbThreadContext<dbDatabaseThreadContext> threadContext;

    dbHeader* header;           // base address of database file mapping
    int4*     dirtyPagesMap;    // bitmap of changed pages in current index
    unsigned  parThreads;
    bool      modified;

    int       curr;             // copy of header->root, used to allow read access to the database 
                                // during transaction commit

    volatile int commitInProgress;
    volatile int concurrentTransId;

    size_t    currRBitmapPage;  //current bitmap page for allocating records
    size_t    currRBitmapOffs;  //offset in current bitmap page for allocating
                                //unaligned records
    size_t    currPBitmapPage;  //current bitmap page for allocating page objects
    size_t    currPBitmapOffs;  //offset in current bitmap page for allocating
                                //page objects

    struct dbLocation { 
	offs_t      pos;
	size_t      size;
	dbLocation* next;
    };
    dbLocation* reservedChain;
    
    size_t    committedIndexSize;
    size_t    currIndexSize;

    oid_t     updatedRecordId;

    dbFile*                   file;
    dbMutex                   mutex;
    dbSemaphore               writeSem;
    dbSemaphore               readSem;
    dbSemaphore               upgradeSem;
    dbEvent                   backupCompletedEvent;
    dbMonitor                 monitor;
    dbPagePool                pool;
    dbTableDescriptor*        tables;

    int*                      bitmapPageAvailableSpace;
    bool                      opened;

    long                      allocatedSize;

    int                       forceCommitCount;
    time_t                    commitDelay;     
    time_t                    commitTimeout;
    time_t                    commitTimerStarted;
    
    dbMutex                   commitThreadSyncMutex;
    dbMutex                   delayedCommitStartTimerMutex;
    dbMutex                   delayedCommitStopTimerMutex;
    dbEvent                   commitThreadSyncEvent;   
    // object used to notify delayed commit thread to schdule delayed commit
    dbEvent                   delayedCommitStartTimerEvent; 
    // object used by delaued commit thread to wait for sepcified timeout
    dbEvent                   delayedCommitStopTimerEvent; 
    dbDatabaseThreadContext*  delayedCommitContext;     // save context of delayed transaction

    dbMutex                   backupMutex;    
    dbEvent                   backupInitEvent;
    char_t*                   backupFileName;
    time_t                    backupPeriod;

    dbThread                  backupThread;
    dbThread                  commitThread;

    void delayedCommit();
    void backupScheduler();

    static void thread_proc delayedCommitProc(void* arg) { 
	((dbDatabase*)arg)->delayedCommit();
    }

    static void thread_proc backupSchedulerProc(void* arg) { 
	((dbDatabase*)arg)->backupScheduler();
    }

    void commit(dbDatabaseThreadContext* ctx);


    offs_t getPos(oid_t oid) {
	byte* p = pool.get(header->root[1-curr].index
			   + oid / dbHandlesPerPage * dbPageSize);
	offs_t pos = *((offs_t*)p + oid % dbHandlesPerPage);
	pool.unfix(p);
	return pos;
    }

    void setPos(oid_t oid, offs_t pos) {
	byte* p = pool.put(header->root[1-curr].index
			   + oid / dbHandlesPerPage * dbPageSize);
	*((offs_t*)p + oid % dbHandlesPerPage) = pos;
	pool.unfix(p);
    }

    dbRecord* getRow(dbGetTie& tie, oid_t oid) {
	offs_t pos = getPos(oid);
	assert(!(pos & (dbFreeHandleFlag|dbPageObjectFlag)));
	tie.set(pool, pos & ~dbFlagsMask);
	return (dbRecord*)tie.get();
    }

    void getHeader(dbRecord& rec, oid_t oid) {
	offs_t pos = getPos(oid);
	int offs = (int)pos & (dbPageSize-1);
	byte* p = pool.get(pos - offs);
	rec = *(dbRecord*)(p + (offs & ~dbFlagsMask));
	pool.unfix(p);
    }

    byte* put(oid_t oid) {
	offs_t pos = getPos(oid);
	int offs = (int)pos & (dbPageSize-1);
	return pool.put(pos-offs) + (offs & ~dbFlagsMask);
    }

    byte* get(oid_t oid) {
	offs_t pos = getPos(oid);
	int offs = (int)pos & (dbPageSize-1);
	return pool.get(pos-offs) + (offs & ~dbFlagsMask);
    }

    dbRecord* putRow(dbPutTie& tie, oid_t oid, size_t newSize);
    dbRecord* putRow(dbPutTie& tie, oid_t oid);
    byte* put(dbPutTie& tie, oid_t oid);

    void restoreTablesConsistency();

    void applyIndex(dbFieldDescriptor* field, dbSearchContext& sc);

    bool isIndexApplicable(dbAnyCursor* cursor, dbExprNode* expr,
			   dbFieldDescriptor* &indexedField, bool forAll);

    bool isIndexApplicableToExpr(dbSearchContext& sc, dbExprNode* expr);

    bool followInverseReference(dbExprNode* expr, dbExprNode* andExpr,
				dbAnyCursor* cursor, oid_t iref);

    bool existsInverseReference(dbExprNode* expr, int nExistsClauses);

    static void _fastcall execute(dbExprNode* expr,
				  dbInheritedAttribute& iattr,
				  dbSynthesizedAttribute& sattr);
    bool evaluate(dbExprNode* expr, oid_t oid, dbTableDescriptor* table, dbAnyCursor* cursor);

    void select(dbAnyCursor* cursor);
    void select(dbAnyCursor* cursor, dbQuery& query);
    void traverse(dbAnyCursor* cursor, dbQuery& query);

    void update(oid_t oid, dbTableDescriptor* table, void const* record);
    void remove(dbTableDescriptor* table, oid_t oid);

    offs_t allocate(size_t size, oid_t oid = 0);
    void free(offs_t pos, size_t size);
    void extend(offs_t size);
    void cloneBitmap(offs_t pos, size_t size);

    oid_t allocateId();
    void freeId(oid_t oid);

    void updateCursors(oid_t oid);

    oid_t allocatePage() {
	oid_t oid = allocateId();
	setPos(oid, allocate(dbPageSize) | dbPageObjectFlag | dbModifiedFlag);
	return oid;
    }
    void freePage(oid_t oid);

    oid_t allocateRow(oid_t tableId, size_t size,
		      dbTableDescriptor* desc = NULL);
    void freeRow(oid_t tableId, oid_t oid, dbTableDescriptor* desc = NULL);

    static void deleteCompiledQuery(dbExprNode* tree);

    void beginTransaction(dbLockType type);
    void endTransaction(dbDatabaseThreadContext* ctx);

    void initializeMetaTable();
    bool loadScheme();

    bool completeDescriptorsInitialization();

    void reformatTable(oid_t tableId, dbTableDescriptor* desc);
    void addIndices(dbTableDescriptor* desc);
    oid_t addNewTable(dbTableDescriptor* desc);

    void updateTableDescriptor(dbTableDescriptor* desc,
			       oid_t tableId, dbTable* table);


    void removeInverseReferences(dbTableDescriptor* desc, oid_t oid);

    void insertInverseReference(dbFieldDescriptor* desc, oid_t reverseId,
				oid_t targetId);
    void removeInverseReference(dbFieldDescriptor* desc,
				oid_t reverseId, oid_t targetId);


    void deleteTable(dbTableDescriptor* desc);
    void dropTable(dbTableDescriptor* desc);

    void createIndex(dbFieldDescriptor* fd);
    void createHashTable(dbFieldDescriptor* fd);
    void dropIndex(dbFieldDescriptor* fd);
    void dropHashTable(dbFieldDescriptor* fd);

    void linkTable(dbTableDescriptor* table, oid_t tableId);
    void unlinkTable(dbTableDescriptor* table);

    bool wasReserved(offs_t pos, size_t size);
    void reserveLocation(dbLocation& location, offs_t pos, size_t size);
    void commitLocation();

    dbTableDescriptor* findTable(char_t const* name);
    dbTableDescriptor* findTableByName(char_t const* name);
};

template<class T>
dbReference<T> insert(T const& record) {
    dbReference<T> ref;
    T::dbDescriptor.getDatabase()->insertRecord(&T::dbDescriptor, &ref, &record);
    return ref;
}

#ifdef NO_MEMBER_TEMPLATES
template<class T>
dbReference<T> insert(dbDatabase& db, T const& record) {
    dbReference<T> ref;
    db.insertRecord(db.lookupTable(&T::dbDescriptor), &ref, &record);
    return ref;
}
#endif

class dbSearchContext {
  public:
    dbDatabase*  db;
    dbExprNode*  condition;
    dbAnyCursor* cursor;
    char_t*      firstKey;
    int          firstKeyInclusion;
    char_t*      lastKey;
    int          lastKeyInclusion;
    int          offs;
    int          probes;
    bool         tmpKeys; // temporary keys were created for using index with LIKE operation
    union {
	bool    b;
	int1    i1;
	int2    i2;
	int4    i4;
	int8    i8;
	real4   f4;
	real8   f8;
	oid_t   oid;
	void*   raw;
	char_t* s;
    } literal[2];
};

END_GIGABASE_NAMESPACE

#endif

