/* * (c) Copyright, Real-Time Innovations, 2002-2026. * All rights reserved. * No duplications, whole or partial, manual or electronic, may be made * without express written permission. Any such copies, or * revisions thereof, must display this notice unaltered. * This code contains trade secrets of Real-Time Innovations, Inc. */ #ifndef reda_database_h #define reda_database_h #include "osapi/osapi_type.h" #include "reda/reda_worker.h" #include "reda/reda_fastBuffer.h" #include "reda/reda_orderedDataType.h" #include "reda/reda_table.h" #ifdef __cplusplus extern "C" { #endif /* Doxygen file & module documentation ==================================*/ /*e \file \brief Defines the REDADatabase */ /*e \defgroup REDADatabaseClass REDADatabase \ingroup REDAModule @brief structure that aggregates REDATables and provides easier table management. The Database provides an additional level of safety in case Tables are added and removed. When using the Database, the application should not use direct pointers to Tables but should use the safe table-references the Database provides. \image html Database_Public.jpg "Overview of REDADatabase" More specifically, in case a Database is used, the Database supersedes all the methods on the Table as follows: \verbatim REDADatabase_createTable supersedes REDATable_new REDADatabase_bindCursor supersedes REDATable_bindCursor REDADatabase_cleanup supersedes REDATable_cleanup AND REDATable_delete \endverbatim An application can have one or more database and a database can hold any number of tables of different types. Typically, the life-cycle of a database is as follows. \code //create a new database database = REDADatabase_new(....) //add tables to the database if (!REDADatabase_createTable(database, tableReference,...)) { // table creation failed } //bind cursors to the table REDACursor cursor; if (!REDADatabase_bindCursor(database, &cursor, tableReference, worker)) { // binding a cursor is not possible } //now the cursor can be used SAFELY to read and modify //the table; safety is guaranteed even if the table itself //is subsequently removed and cleaned-up //regularly you need to give the database a chance to //reclaim resources REDADatabase_cleanup(database, NULL, worker); //This will reclaim resources on all tables, and even //delete the tables themselves if it's safe to do so //etc...create/use/remove tables and cleanup regularly //Eventually...when the database itself needs to be deleted //(deleting a database is fundamentally unsafe: it's the // responsability of the application to make sure that // essentially nobody will use any resources associated // with the database) REDADatabase_cleanup(database, &canBeDeleted, worker); if (canBeDeleted) { REDADatabase_delete(database, worker); } \endcode In addition the Database provides support for the common use-case where several workers need to access a Table efficiently through pre-allocated Cursors. \image html DatabaseWithCursorPerWorker.jpg "Using the CursorPerWorker facility" In this case, the methods createCursorPerWorker, deleteCursorPerWorker and assertAndStartCursor supersede the methods bindCursor and Cursor_start. A bound cursor will be kept in worker-specific storage, so that the Worker can easily reuse a previoudly bound Cursor. The prototypical use-case is as follows: \code //add a tables to the database if (!REDADatabase_createTable(database, &tableReference,...)) { // table creation failed } //provide a cursor to all current & future workers cursorPerWorker = REDADatabase_createCursorPerWorker(database, &tableReference); //When a cursor needs to access a table, it does the following //This call is very efficient unless the worker has never used this //cursorPerWorker, in which case a new cursor is automatically created //and bound to the Table if (REDACursorPerWorker_assertAndStartCursor(cursorPerWorker, &cursor, worker)) { //access the table using the cursor //goto....assert....lock.... REDACursor_finish(cursor); } //eventually, the CursorPerWorker needs to be destroyed REDADatabase_destroyCursorPerWorker(database, cursorPerWorker); //now: nobody is allowed to access this cursorPerWorker! \endcode @see REDADatabase_new REDADatabase_createTable REDADatabase_createCursorPerWorker */ /* forward declare to appease the compiler. */ struct REDADatabase; struct REDACursorPerWorker; struct REDAWeakReference; struct REDACursor; /* ----------------------------------------------------------------- */ /*e \ingroup REDADatabaseClass @brief Structure to configure detailed Database properties */ struct REDADatabaseProperty { /*e hint for memory management of table: resources are allocated in chunks that have enough space to store tableAllocationChunk tables */ struct REDAFastBufferPoolGrowthProperty growth; /*e maximum skiplist level for all tables in the database */ RTI_UINT8 skiplistLevelMax; /*e initial number of total records across all tables for which resources are allocated at the database level; the resources will grow as needed by doubling */ int totalRecordCountInitial; /*e Initial number of weak references. Each database has a collection of weak references to records of the tables it contains. This paramater specifies the initial size of the collection. The actual initial value used may be larger than the one specified here if the specified initial value would not allow the number of weak references to grow to the specified maximum. */ int weakReferenceCountInitial; /*e * Maximum number of weak references. * * Each database has a collection of weak references to records of the * tables it contains. This paramater specifies the maximum size of the * collection. The special constant * REDA_DATABASE_WEAKREF_COUNT_MAX_UNLIMITED can be used to indicate that * the collection should grow indefinitely as needed. */ int weakReferenceCountMax; }; /*e * \ingroup REDADatabaseClass * @brief Used as the maximum level for a skiplist in a database. * * Seems like there would be little penalty if we were to use * REDA_SKIPLIST_LEVEL_MAX_UPPER_LIMIT as the probability of higher * level nodes is negligible whenever sizing arrays we are * using REDA_SKIPLIST_FORWARD_POINTERS_MAX anyway. However this can save in * the number of fast-buffer pools needed to manage the different * level nodes. Experimentally seven works well up to 1000 nodes. 2^7 * is 128 so there would be about 8 iterations. We have tables but * still this may be too limiting. A better scheme may be to re-use * the same fast-buffer pools at several levels, e.g. size it to * accommodate the largest level in the set and install teh same pool * for all. Good pool sizes to minimize overhead may be: 1, 2, 3, 4, * 6, 9, 16, 32. Eight pools cover all levels and the average number of * pointers wasted per node is about 1/32!! * * 1*p(l=5) + 2*p(l=7) + 1*p(l=8) + 6*p(l=10)+5*p(l=11)+... + 15*p(l=17)+... * * = (1/32-1/64) + 2/128-1/256 + 6/1024-1/2048-1/4096... + 15/2^17 - 1/2^18 * -... * ~< 1/64 + 1/64 + 1/128 + 1/8128 * ~= 1/32 */ #define REDA_DATABASE_SKIPLIST_LEVEL_MAX (7) /*e \ingroup REDADatabaseClass @brief Database initial total record count. */ #define REDA_DATABASE_TOTAL_RECORD_COUNT_INITIAL (128) /*e * \ingroup REDADatabaseClass * @brief Database initial weak reference count. * * This initial value will be adjusted upwards as necessary to allow the * number of weak references to grow to the maximum. */ #define REDA_DATABASE_WEAKREF_COUNT_INITIAL (1) /*e * \ingroup REDADatabaseClass * @brief Database unlimited weak reference count. * * This value indicates that the number of weak references can grow without * limit. In can be used to indicate that there is not a maximum number of weak * references. */ #define REDA_DATABASE_WEAKREF_COUNT_MAX_UNLIMITED (-1) /*e \ingroup REDADatabaseClass @brief Default property of database. */ #define REDA_DATABASE_PROPERTY_DEFAULT \ { REDA_FAST_BUFFER_POOL_GROWTH_PROPERTY_DEFAULT, \ REDA_DATABASE_SKIPLIST_LEVEL_MAX, \ REDA_DATABASE_TOTAL_RECORD_COUNT_INITIAL, \ REDA_DATABASE_WEAKREF_COUNT_INITIAL, \ REDA_DATABASE_WEAKREF_COUNT_MAX_UNLIMITED } /* ----------------------------------------------------------------- */ /*e \ingroup REDADatabaseClass @brief Create new database to hold tables. @post On success, a valid empty database is returned @param workerFactory \b In. This manager must stay in existence as long as the Database is in existence. @param adminEA \b In. The EA the database will use for administrative purposes. The level of this EA must be such that the EA can be entered from within any table- or record-level EAs. @param property \b In. Contains optional detailed configuration properties for the database. If NULL is passed, defaults are used. The structure can be deleted after the creation of the database returns. @param worker \b In. @return Pointer to the database on success. NULL on failure. @mtsafety safe. @see REDADatabase_createTable REDAWorkerFactory_new */ extern REDADllExport struct REDADatabase *REDADatabase_new( struct REDAWorkerFactory *workerFactory, struct REDAExclusiveArea *adminEA, const struct REDADatabaseProperty *property, struct REDAWorker *worker); /* ----------------------------------------------------------------- */ /*e \ingroup REDADatabaseClass @brief Get the property of a database. @param property \b In. After this method returns, this will contains detailed configuration properties for the database. @param worker \b In. @mtsafety safe. */ extern REDADllExport void REDADatabase_getProperty( struct REDADatabase *me, struct REDADatabaseProperty *property, struct REDAWorker *worker); /* ----------------------------------------------------------------- */ /*e \ingroup REDADatabaseClass @brief get the WorkerFactory that this database uses. @pre database instantiated @see REDADatabase_new */ extern REDADllExport struct REDAWorkerFactory *REDADatabase_getWorkerFactory( struct REDADatabase *me); /* ----------------------------------------------------------------- */ /*e \ingroup REDADatabaseClass @brief Deletes a REDADatabase. It is the responsability of the application that uses the database to ensure that, after the call to REDADatabase_delete is made, no other methods are invoked on the database and no weak-references to records in tables of the database are used. @pre A valid (undeleted) Database for which the databaseCanBeDeleted parameter returned on invoking REDADatabase_cleanup is RTI_TRUE; this means that all user tables in the Database have been removed and are cleaned up completely. @post The database is deleted. @param worker \b In. The worker that does the delete; can be any worker @mtsafety UNSAFE (it's the responsability of the caller that only one thread calls this at most once). @see REDADatabase_new REDADatabase_cleanup */ extern REDADllExport RTIBool REDADatabase_delete(struct REDADatabase *me, struct REDAWorker *worker); /* ----------------------------------------------------------------- */ /*e \ingroup REDADatabaseClass @brief Cleaning up will invoke the cleanup method on all tables in the database and on the internal structures. This reclaims unused resource in the database and its tables in a thread-safe manner: any thread can call cleanup at any time. @pre A valid (undeleted) Database @post All database resources that can be safely deleted have been freed to the heap or returned to their pools @param databaseCanBeDeleted \b Out. Means the database is currently in a state where it could be deleted (all tables have been removed and cleanup has been allowed to run sufficiently for a complete cleanup) Optional parameter. @param worker \b In. The worker that does the cleanup @mtsafety safe: any worker can call this; workers could call it concurrently although that is not a useful use-case; typically one reaper-thread will occasionally perform clean-up @see REDADatabase_delete */ extern REDADllExport RTIBool REDADatabase_cleanup( struct REDADatabase *me, RTIBool *databaseCanBeDeleted, struct REDAWorker *worker); /* ----------------------------------------------------------------- */ /*e \ingroup REDADatabaseClass @brief Create a new table in the database. @param tableReference \b Out. On success, this will contain a reference to the new table @param tableName \b In. An optional name of the table. Maximum length is REDA_TABLE_NAME_MAX_LENGTH. Can be NULL. @param keyType \b in. Defines the type of keys used to identify the records in the the table. Must have been initialized with REDAOrderedDataType_define. The relevant contents will be copied into the table; this structure can be deleted after the Table has been created. @param readOnlyAreaType \b in. Defines the type of data in the read-only area of the records. Must have been initialized with REDAOrderedDataType_define or can be NULL in case the Table should not have records with a readOnlyArea. The relevant contents will be copied into the table; this structure can be deleted after the Table has been created. @param readWriteAreaType \b in. Defines the type of data in the read-write area of the records. Must have been initialized with REDAOrderedDataType_define OR can be NULL in case the Table should not have records with a readWriteArea. The relevant contents will be copied into the table; this structure can be deleted after the Table has been created. @param hashDefinition \b in. Defines the hashing that will be used on the key. See the documentation for REDAHashedSkiplist and REDAHash for more information. The relevant contents will be copied into the table; this structure can be deleted after the Table has been created. In case this parameter is NULL, no hashing will be used. @param tableEA \b in. The table-level exclusive area. It is necessary that a worker can enter the adminEA of the table, the adminEA of the weak-reference manager and the record EA's from within the tableEA. @param tableFinalizeFnc \b In. A function to be notified that the table is physically removed from memory and nobody is able to access it anymore. See REDATableFinalizeFunction for details. It is called in the context of REDADatabase_cleanup. This parameter may be NULL if no finalization function is desired. @param tableFinalizeParam \b In. Parameter to pass to tableFinalizeFnc. @param recordFinalizeFnc \b in. Used to notify application when a record is about the be physically removed and nobody can access its contents. See REDARecordFinalizeFunction for details. It is called in the context of REDADatabase_cleanup. May be NULL to indicate there is no notification is needed. @param recordFinalizeParam \b in. Additional parameter to pass to recordFinalizeFnc. @param growthPropertyIn. Directive for how the table should grow. @param tableUserData \b in. Table provides storage space for one pointer, which can be retrieved safely through REDACursor_getTableUserData. The table only provides safe read-only access; this pointer or the structure it points cannot be modified without additional protection on top of the Table. @param worker \b in. Worker used for table-creation only. @return RTI_TRUE on success, RTI_FALSE on failure @mtsafety safe */ extern REDADllExport RTIBool REDADatabase_createTable( struct REDADatabase *me, struct REDAWeakReference *tableReference, const char *tableName, struct REDAOrderedDataType *keyType, struct REDAOrderedDataType *readOnlyAreaType, struct REDAOrderedDataType *readWriteAreaType, const struct REDAHashDefinition *hashDefinition, struct REDAExclusiveArea *tableEA, REDATableFinalizeFunction tableFinalizeFnc, void *tableFinalizeParam, REDATableRecordFinalizeFunction recordFinalizeFnc, void *recordFinalizeParam, const struct REDAFastBufferPoolGrowthProperty *growthPropertyIn, void *tableUserData, struct REDAWorker *worker); /* ----------------------------------------------------------------- */ /*e \ingroup REDADatabaseClass @brief Bind a cursor to a previously created table in the database and a worker. REDACursor is the mechanism that is used to interact with a table. Once bound, the cursor is exclusively attached to the table and must be called only by the thread whose worker the cursor is bound to. REDADatabase will ensure that a table is not deleted while there are any cursors bound to it and makes, therefore, the use of cursors thread-safe even in the present of other threads that remove tables. @pre REDACursor not currently bound to any table @post The REDACursor is now bound to a table and a worker. @param database \b In. @param cursor \b InOut. A cursor that is not bound to any tables. This paramater must point to valid storage and cannot be NULL. @param tableReference In. Reference to a previously-created table in the database (see REDADatabase_createTable) @param worker In. The identify of "thread" that will be accessing the table using the cursor. @return RTI_TRUE if successful or RTI_FALSE otherwise. @mtsafety safe. @see REDADatabase_bindCursor REDACursor REDADatabase_createTable */ extern REDADllExport RTIBool REDADatabase_bindCursor( struct REDADatabase *me, struct REDACursor *cursor, struct REDAWeakReference *tableReference, struct REDAWorker *worker); /* ----------------------------------------------------------------- */ /*e \ingroup REDADatabaseClass @param tableReference \b In. Reference to the table to which the cursor-per-worker will be bound as the workers need them @return cursor-per-worker created for the table. NULL on failure. @see REDACursorPerWorker_assertAndStartCursor REDADatabase_destroyCursorPerWorker */ extern REDADllExport struct REDACursorPerWorker * REDADatabase_createCursorPerWorker( struct REDADatabase *me, struct REDAWeakReference *tableReference); /* ----------------------------------------------------------------- */ /*e \ingroup REDADatabaseClass @param cursorPerWorker \b InOut.The cursorPerWorker that will be destroyed. May be NULL, in which case noop. @param worker \b In. The worker of the calling thread, cannot be NULL. @see REDACursorPerWorker_assertAndStartCursor REDADatabase_createCursorPerWorker */ extern REDADllExport void REDADatabase_destroyCursorPerWorker( struct REDADatabase *me, struct REDACursorPerWorker *cursorPerWorker, struct REDAWorker *worker); /*e \defgroup REDACursorPerWorkerClass REDACursorPerWorker \ingroup REDADatabaseClass @brief A factory for the cursors that are bound to a table in the database. */ /* ----------------------------------------------------------------- */ /*e \ingroup REDACursorPerWorkerClass @brief Return a cursor bound to the worker (and to the table defined when the CursorPerWorker was created). This call can be slow if the worker has never asserted a cursor before from this CursorPerWorker. In this case it involves memory allocation and binding a Cursor to a table. However, in all other cases, this call is very fast: it takes an already bound cursor out of worker-specific storage. @param cursorPerWorker \b In. @param cursorPtr \b Out. The cursor bound to this worker will be returned here @param failReasonPtr \b Out. Optional, in case you want to find why this failed. @param worker \b In. The worker that should make this call: the cursor will be bound to this worker and this worker's worker- specific storage will be used to store the bound cursor @return RTI_TRUE on success, RTI_FALSE on failure @see REDADatabase_createCursorPerWorker REDADatabase_destroyCursorPerWorker */ extern REDADllExport RTIBool REDACursorPerWorker_assertAndStartCursorFnc( struct REDACursorPerWorker *me, struct REDACursor **cursorPtr, int *failReasonPtr, struct REDAWorker *worker); /* ----------------------------------------------------------------- */ /*e \ingroup REDACursorPerWorkerClass @brief Return a cursor bound to the worker (and to the table defined when the CursorPerWorker was created). This call can be slow if the worker has never asserted a cursor before from this CursorPerWorker. In this case it involves memory allocation and binding a Cursor to a table. However, in all other cases, this call is very fast: it takes an already bound cursor out of worker-specific storage. @behavior May be macroed for performance @param cursorPerWorker \b In. @param cursorPtr \b Out. The cursor bound to this worker will be returned here @param worker \b In. The worker that should make this call: the cursor will be bound to this worker and this worker's worker- specific storage will be used to store the bound cursor @return content of cursorPtr as a convenience */ extern REDADllExport struct REDACursor *REDACursorPerWorker_assertCursor( struct REDACursorPerWorker *me, struct REDACursor **cursorPtr, struct REDAWorker *worker); /*e \ingroup REDACursorPerWorkerClass @brief Get, bind, and start a cursor to a table in a lazy manner. A recurring usage pattern with multiple cursors in a function is as follows:
  1. Get and start cursor A on the database
  2. Get and start cursor B on the database
  3. Do stuff in table A
  4. Do stuff in table B
  5. finish B
  6. finish A
All the while, we must pop out and finish every started cursor on exception. As a good coding practice, concept of "stack of cursors" you pop out of is useful. @behavior May be macroed for performance @pre Preconditions for REDADatabase_getCursorLazy and REDACursor_startSafe jointly apply. @post post conditions for REDADatabase_getCursorLazy and REDACursor_startSafe jointly apply. @param cursorPtr \b Out. Point to the cursor in question. API is somewhat cumbersome for macroization. @param failReasonPtr \b Out. See failStatus param for REDACursor_startSafe. @param safeCursorStack \b InOut. an array of pointers to cursors all in one of the "active" states. In other words \c cursors[0] is of type \c (REDACursor *). On succesful \e cursor will be added to the array, that is, cursors[*current]= cursor. @param currentIndex \b Inout. Pointer to an integer containing the number of pointer to cursors currently in the \e cursors array. On succesful return *current will be incremeted to account for the new pointer to cursor in the array. @param tableReference \b In. Reference to a previously-created table in the database. @param workerStorageId \b In. Storage id that is either new to the worker, or lese has been used to store a cursor bound to that table and worker. @param worker \b In. The identify of "thread" that will use the cursor to access the table. @return Cursor the cursor living inside worker-specific storage. On success, it will be be bound to that table and worker. @see REDACursor_finishSafe */ RTIBool REDACursorPerWorker_assertAndStartCursorSafe( struct REDACursorPerWorker *me, struct REDACursor **cursorPtr, int *failReasonPtr, struct REDACursor *safeCursorStack, int *currentIndex, struct REDAWorker *worker); /*e \ingroup REDADatabaseClass * @brief Find table by table name in database. * * @param tableReference \b Out. On success, this will contain a reference to * the new table. Can be NULL if you do not need to obtain weak reference to * table. * * @param tableName \b In. Name of the table to be searched. * * @param worker \b In. Worker used for finding table only. * * @return RTI_TRUE on success, RTI_FALSE on failure. */ extern REDADllExport RTIBool REDADatabase_findTable( struct REDADatabase *me, struct REDAWeakReference *tableReference, const char *tableName, struct REDAWorker *worker); /*e \ingroup REDADatabaseClass * @brief Print out the name of all tables in database. * * @param database \b In. The database. * * @param worker \b In. Worker used for listing table only. * * @return RTI_TRUE on success, RTI_FALSE on failure. */ extern REDADllExport RTIBool REDADatabase_listTables(struct REDADatabase *me, struct REDAWorker *worker); /*e \ingroup REDADatabaseClass * @brief Print out the records for a table in database. * * @param database \b In. The database. * @param tableReferenceIn \b In. If not null, WR used to look up the table * to print. * @param tableNameIn \b In. If not null, table name used to look up the table * to print. * @param useCompactPrintFormat \b In. Determines if tue compat print format * is used. * @param printAdminArea \b In. Determines if the admin area is printed. * @param worker \b In. Worker used for listing table only. * * @return RTI_TRUE on success, RTI_FALSE on failure. */ extern REDADllExport RTIBool REDADatabase_listRecords( struct REDADatabase *database, const struct REDAWeakReference *tableReference, const char *tableName, RTIBool useCompactPrintFormat, RTIBool printAdminArea, struct REDAWorker *worker); /*e \ingroup REDADatabaseClass @brief Creates a new exclusiveArea The EA will be created with "UNNAMED_EA" name. It is mainly used for testing. @post exclusiveArea is a properly initialized exclusiveArea with referenceCount 0 @param database \b InOut. this @param level \b In. The level of this EA; for deadlock-prevention. This level must be > 0. @param worker \b In. @return NULL on failure. Pointer to the newly created exclusive area on success. @mtsafety safe @see REDADatabase_destroyExclusiveArea */ extern REDADllExport struct REDAExclusiveArea *REDADatabase_createExclusiveArea( struct REDADatabase *database, int level, struct REDAWorker *worker); /*e \ingroup REDADatabaseClass * * @brief Creates a new exclusiveArea * * @post exclusiveArea is a properly initialized exclusiveArea with * referenceCount 0 * * @param database \b InOut. this * @param level \b In. The level of this EA; for deadlock-prevention. This * level must be > 0. * @param name \b In. The name of the new EA. * @param worker \b In. * * @return NULL on failure. Pointer to the newly created exclusive area on * success. * * @mtsafety safe * * @see REDADatabase_destroyExclusiveArea */ extern REDADllExport struct REDAExclusiveArea * REDADatabase_createExclusiveAreaEx( struct REDADatabase *database, int level, const char *name, struct REDAWorker *worker); /*e \ingroup REDADatabaseClass * * Destroy an EA created by REDADatabase_createExclusiveArea. * * @param database \b InOut. The REDADatabase object. * @param ea \b InOut. The ExclusiveArea that should be destroyed. * @param worker \b In. The Worker. * * @mtsafety Safe with regards to the manager. The EA must be destroyed only * once. A destroyed EA cannot be used for asserting a new record and both * operations cannot be called concurrently. * The physical destruction of the EA will be done in the database clean up. * * @see REDADatabase_createExclusiveArea */ extern REDADllExport void REDADatabase_destroyExclusiveArea( struct REDADatabase *database, struct REDAExclusiveArea *ea, struct REDAWorker *worker); /*e \ingroup REDADatabaseClass Remove a table without requiring a cursor. @param database \b InOut. this @param tableReference \b InOut. Weak reference to the table to be removed @param worker \b In. */ extern REDADllExport RTIBool REDADatabase_removeTable( struct REDADatabase *database, struct REDAWeakReference *tableReference, struct REDAWorker *worker); #ifdef __cplusplus } /* extern "C" */ #endif /* __cplusplus */ #include "reda/reda_database_impl.h" #endif /* reda_database_h */