/*++ Copyright (c) 1998 Microsoft Corporation Module Name: database.cpp Abstract: SIS Groveler Jet-Blue database front-end Authors: Cedric Krumbein, 1998 Environment: User Mode Revision History: --*/ #include "all.hxx" /*****************************************************************************/ /*************** SGDatabase class static value initializations ***************/ /*****************************************************************************/ DWORD SGDatabase::numInstances = 0; JET_INSTANCE SGDatabase::instance = 0; BOOL SGDatabase::jetInitialized = FALSE; TCHAR * SGDatabase::logDir = NULL; /*****************************************************************************/ /****************** SGDatabase class private static methods ******************/ /*****************************************************************************/ BOOL SGDatabase::set_log_drive(const _TCHAR *drive_name) { int drive_name_len = _tcslen(drive_name); int cs_dir_path_len = _tcslen(CS_DIR_PATH); ASSERT(NULL == logDir); logDir = new TCHAR[drive_name_len + cs_dir_path_len + 1 - 1]; ASSERT(NULL != logDir); _tcsncpy(SGDatabase::logDir, drive_name, drive_name_len - 1); _tcscpy(&SGDatabase::logDir[drive_name_len-1], CS_DIR_PATH); return TRUE; } BOOL SGDatabase::InitializeEngine() { DWORD_PTR maxVerPages; DWORD_PTR minCacheSize; DWORD_PTR newCacheSize; DWORDLONG cacheSize; DWORD circularLog; MEMORYSTATUSEX memStatus; SYSTEM_INFO sysInfo; JET_ERR jetErr; ASSERT(!jetInitialized); ASSERT(logDir); if (!SetCurrentDirectory(logDir)) { DPRINTF((_T("SGDatabase::InitializeEngine: can't cd to \"%s\", %ld\n"), logDir, GetLastError())); return FALSE; } circularLog = 1; jetErr = JetSetSystemParameter(&instance, 0, JET_paramCircularLog, circularLog, NULL); if (jetErr != JET_errSuccess) { DPRINTF((_T("(2) JetSetSystemParameter: jetErr=%ld\n"), jetErr)); return FALSE; } // // Set the maximum cache size used by the database engine to min(4% phys mem, 6M). // jetErr = JetGetSystemParameter(instance, 0, JET_paramCacheSizeMin, &minCacheSize, NULL, 0); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetGetSystemParameter: jetErr=%ld\n"), jetErr)); TerminateEngine(); return FALSE; } memStatus.dwLength = sizeof memStatus; GlobalMemoryStatusEx(&memStatus); // get total physical memory GetSystemInfo(&sysInfo); // get page size cacheSize = memStatus.ullTotalPhys / 25; // 4% newCacheSize = (DWORD) min(cacheSize, MAX_DATABASE_CACHE_SIZE); newCacheSize = newCacheSize / sysInfo.dwPageSize; if (newCacheSize < minCacheSize) newCacheSize = minCacheSize; jetErr = JetSetSystemParameter(&instance, 0, JET_paramCacheSizeMax, newCacheSize, NULL); if (jetErr != JET_errSuccess) { DPRINTF((_T("(3) JetSetSystemParameter: jetErr=%ld\n"), jetErr)); return FALSE; } // // Set Version Cache size // jetErr = JetGetSystemParameter(instance, 0, JET_paramMaxVerPages, &maxVerPages, NULL, 0); if (jetErr != JET_errSuccess) { DPRINTF((_T("(2) JetGetSystemParameter: jetErr=%ld\n"), jetErr)); TerminateEngine(); return FALSE; } if (maxVerPages >= MIN_VER_PAGES) { DPRINTF((_T("JetGetSystemParameter(instance=%lu): MaxVerPages=%lu\n"), instance, maxVerPages)); } else { maxVerPages = MIN_VER_PAGES; jetErr = JetSetSystemParameter(&instance, 0, JET_paramMaxVerPages, maxVerPages, NULL); if (jetErr != JET_errSuccess) { DPRINTF((_T("(4) JetSetSystemParameter: jetErr=%ld\n"), jetErr)); TerminateEngine(); return FALSE; } DPRINTF((_T("JetSetSystemParameter(instance=%lu, MaxVerPages)=%lu\n"), instance, maxVerPages)); } // // Initialize Jet // jetErr = JetInit(&instance); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetInit: jetErr=%ld\n"), jetErr)); return FALSE; } jetInitialized = TRUE; DPRINTF((_T("JetInit: instance=%lu\n"), instance)); return TRUE; } /*****************************************************************************/ BOOL SGDatabase::TerminateEngine() { JET_ERR jetErr; BOOL rc; ASSERT(jetInitialized); jetErr = JetTerm(instance); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetTerm: jetErr=%ld\n"), jetErr)); rc = FALSE; } else { rc = TRUE; // Delete no longer needed jet files. if (logDir) { WIN32_FIND_DATA findData; HANDLE fHandle; BOOL success; TFileName fName, delName; delName.assign(logDir); delName.append(_T("\\")); delName.append(DATABASE_DELETE_RES_FILE_NAME); fHandle = FindFirstFile(delName.name, &findData); if (fHandle != INVALID_HANDLE_VALUE) { do { if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { success = GetParentName(delName.name, &fName); ASSERT(success); // internal error if failed fName.append(_T("\\")); fName.append(findData.cFileName); if (!DeleteFile(fName.name)) { DPRINTF((_T("SGDatabase::Close: can't delete \"%s\", %d\n"), delName.name, GetLastError())); } } } while (FindNextFile(fHandle, &findData)); success = FindClose(fHandle); ASSERT(success); fHandle = NULL; } } } jetInitialized = FALSE; DPRINTF((_T("JetTerm\n"))); return rc; } /*****************************************************************************/ /********************** SGDatabase class private methods *********************/ /*****************************************************************************/ BOOL SGDatabase::CreateTable( const CHAR *tblName, DWORD numColumns, ColumnSpec **columnSpecs, JET_COLUMNID *columnIDs, JET_TABLEID *tblID) { JET_COLUMNDEF columnDef; JET_COLUMNID colIDcount; JET_ERR jetErr; ColumnSpec *columnSpec; DWORD i, j; ASSERT(sesID != ~0); ASSERT(dbID != ~0); ASSERT(numColumns <= MAX_COLUMNS); jetErr = JetCreateTable(sesID, dbID, tblName, TABLE_PAGES, TABLE_DENSITY, tblID); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetCreateTable: jetErr=%ld\n"), jetErr)); return FALSE; } DPRINTF((_T("JetCreateTable: tblID=%lu colIDs={"), *tblID)); columnDef.cbStruct = sizeof(JET_COLUMNDEF); columnDef.wCountry = COUNTRY_CODE; columnDef.langid = LANG_ID; columnDef.cp = CODE_PAGE; columnDef.wCollate = COLLATE; colIDcount = 1; for (i = 0; i < numColumns; i++) { columnSpec = columnSpecs[i]; columnDef.columnid = colIDcount; columnDef.coltyp = columnSpec->coltyp; columnDef.cbMax = columnSpec->size; columnDef.grbit = columnSpec->grbit; jetErr = JetAddColumn(sesID, *tblID, columnSpec->name, &columnDef, NULL, 0, &columnIDs[i]); if (jetErr != JET_errSuccess) { DPRINTF((_T("\nJetAddColumn: jetErr=%ld\n"), jetErr)); return FALSE; } DPRINTF((_T(" %lu"), columnIDs[i])); if (i+1 < numColumns && colIDcount == columnIDs[i]) { ColIDCollision: colIDcount++; for (j = 0; j < i; j++) if (colIDcount == columnIDs[j]) goto ColIDCollision; } } DPRINTF((_T(" }\n"))); return TRUE; } /*****************************************************************************/ BOOL SGDatabase::CreateIndex( JET_TABLEID tblID, const CHAR *keyName, DWORD numKeys, ColumnSpec **keyColumnSpecs) { JET_ERR jetErr; CHAR indexStr[MAX_PATH]; ColumnSpec *keyColumnSpec; DWORD indexStrLen, i; ASSERT(sesID != ~0); ASSERT(numKeys <= MAX_KEYS); indexStrLen = 0; for (i = 0; i < numKeys; i++) { keyColumnSpec = keyColumnSpecs[i]; indexStr[indexStrLen++] = '+'; strcpy(indexStr + indexStrLen, keyColumnSpec->name); indexStrLen += strlen(keyColumnSpec->name) + 1; } indexStr[indexStrLen++] = '\0'; jetErr = JetCreateIndex(sesID, tblID, keyName, 0, indexStr, indexStrLen, TABLE_DENSITY); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetCreateIndex: jetErr=%ld\n"), jetErr)); return FALSE; } return TRUE; } /*****************************************************************************/ BOOL SGDatabase::OpenTable( const CHAR *tblName, DWORD numColumns, ColumnSpec **columnSpecs, JET_COLUMNID *columnIDs, JET_TABLEID *tblID) { JET_COLUMNDEF columnDef; JET_ERR jetErr; ColumnSpec *columnSpec; DWORD i; ASSERT(sesID != ~0); ASSERT(dbID != ~0); ASSERT(numColumns <= MAX_COLUMNS); jetErr = JetOpenTable(sesID, dbID, tblName, NULL, 0, 0, tblID); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetOpenTable: jetErr=%ld\n"), jetErr)); return FALSE; } DPRINTF((_T("JetOpenTable: tblID=%lu colIDs={"), *tblID)); for (i = 0; i < numColumns; i++) { columnSpec = columnSpecs[i]; jetErr = JetGetTableColumnInfo(sesID, *tblID, columnSpec->name, &columnDef, sizeof(JET_COLUMNDEF), JET_ColInfo); if (jetErr != JET_errSuccess) { DPRINTF((_T("\nJetGetTableColumnInfo: jetErr=%ld\n"), jetErr)); return FALSE; } columnIDs[i] = columnDef.columnid; DPRINTF((_T(" %lu"), columnIDs[i])); } DPRINTF((_T(" }\n"))); return TRUE; } /*****************************************************************************/ BOOL SGDatabase::CloseTable(JET_TABLEID tblID) { JET_ERR jetErr; ASSERT(sesID != ~0); ASSERT(tblID != ~0); jetErr = JetCloseTable(sesID, tblID); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetCloseTable: jetErr=%ld\n"), jetErr)); return FALSE; } return TRUE; } /*****************************************************************************/ LONG SGDatabase::PositionCursor( JET_TABLEID tblID, const CHAR *keyName, const VOID *entry, DWORD numKeys, ColumnSpec **keyColumnSpecs) const { JET_COLTYP coltyp; JET_ERR jetErr; ColumnSpec *keyColumnSpec; const BYTE *dataPtr[MAX_KEYS]; DWORD cbData[MAX_KEYS], i; ASSERT(sesID != ~0); ASSERT(numKeys <= MAX_KEYS); jetErr = JetSetCurrentIndex(sesID, tblID, keyName); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetSetCurrentIndex: jetErr=%ld\n"), jetErr)); return -1; } for (i = 0; i < numKeys; i++) { keyColumnSpec = keyColumnSpecs[i]; coltyp = keyColumnSpec->coltyp; dataPtr[i] = (const BYTE *)entry + keyColumnSpec->offset; if (coltyp == JET_coltypBinary) { dataPtr[i] = *(BYTE **)dataPtr[i]; ASSERT(dataPtr[i] != NULL); cbData[i] = (_tcslen((const TCHAR *)dataPtr[i]) + 1) * sizeof(TCHAR); } else cbData[i] = keyColumnSpec->size; jetErr = JetMakeKey(sesID, tblID, dataPtr[i], cbData[i], i == 0 ? JET_bitNewKey : 0); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetMakeKey: jetErr=%ld\n"), jetErr)); return -1; } } jetErr = JetSeek(sesID, tblID, JET_bitSeekEQ); if (jetErr != JET_errSuccess) { if (jetErr == JET_errRecordNotFound) return 0; DPRINTF((_T("JetSeek: jetErr=%ld\n"), jetErr)); return -1; } for (i = 0; i < numKeys; i++) { jetErr = JetMakeKey(sesID, tblID, dataPtr[i], cbData[i], i == 0 ? JET_bitNewKey : 0); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetMakeKey: jetErr=%ld\n"), jetErr)); return -1; } } jetErr = JetSetIndexRange(sesID, tblID, JET_bitRangeUpperLimit | JET_bitRangeInclusive); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetSetIndexRange: jetErr=%ld\n"), jetErr)); return -1; } return 1; } /*****************************************************************************/ LONG SGDatabase::PositionCursorFirst( JET_TABLEID tblID, const CHAR *keyName) const { JET_ERR jetErr; ASSERT(sesID != ~0); jetErr = JetSetCurrentIndex(sesID, tblID, keyName); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetSetCurrentIndex: jetErr=%ld\n"), jetErr)); return -1; } jetErr = JetMove(sesID, tblID, JET_MoveFirst, 0); if (jetErr != JET_errSuccess) { if (jetErr == JET_errNoCurrentRecord) return 0; DPRINTF((_T("JetMove: jetErr=%ld\n"), jetErr)); return -1; } return 1; } /*****************************************************************************/ LONG SGDatabase::PositionCursorNext(JET_TABLEID tblID) const { JET_ERR jetErr; ASSERT(sesID != ~0); jetErr = JetMove(sesID, tblID, JET_MoveNext, 0); if (jetErr != JET_errSuccess) { if (jetErr == JET_errNoCurrentRecord) return 0; DPRINTF((_T("JetMove: jetErr=%ld\n"), jetErr)); return -1; } return 1; } /*****************************************************************************/ LONG SGDatabase::PositionCursorLast( JET_TABLEID tblID, const CHAR *keyName) const { JET_ERR jetErr; ASSERT(sesID != ~0); jetErr = JetSetCurrentIndex(sesID, tblID, keyName); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetSetCurrentIndex: jetErr=%ld\n"), jetErr)); return -1; } jetErr = JetMove(sesID, tblID, JET_MoveLast, 0); if (jetErr != JET_errSuccess) { if (jetErr == JET_errNoCurrentRecord) return 0; DPRINTF((_T("JetMove: jetErr=%ld\n"), jetErr)); return -1; } return 1; } /*****************************************************************************/ BOOL SGDatabase::PutData( JET_TABLEID tblID, const VOID *entry, DWORD numColumns, ColumnSpec **columnSpecs, const JET_COLUMNID *columnIDs) { JET_COLTYP coltyp; JET_ERR jetErr; ColumnSpec *columnSpec; const BYTE *dataPtr; DWORD cbData, i; ASSERT(sesID != ~0); ASSERT(numColumns <= MAX_COLUMNS); jetErr = JetPrepareUpdate(sesID, tblID, JET_prepInsert); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetPrepareUpdate: jetErr=%ld\n"), jetErr)); return FALSE; } for (i = 0; i < numColumns; i++) { columnSpec = columnSpecs[i]; coltyp = columnSpec->coltyp; if (columnSpec->grbit != JET_bitColumnAutoincrement) { dataPtr = (const BYTE *)entry + columnSpec->offset; if (coltyp == JET_coltypBinary || coltyp == JET_coltypLongBinary) { dataPtr = *(BYTE **)dataPtr; cbData = dataPtr != NULL ? (_tcslen((const TCHAR *)dataPtr) + 1) * sizeof(TCHAR) : 0; } else cbData = columnSpec->size; // May want to convert to JetSetColumns jetErr = JetSetColumn(sesID, tblID, columnIDs[i], dataPtr, cbData, 0, NULL); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetSetColumn: jetErr=%ld\n"), jetErr)); return FALSE; } } } jetErr = JetUpdate(sesID, tblID, NULL, 0, NULL); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetUpdate: jetErr=%ld\n"), jetErr)); return FALSE; } return TRUE; } /*****************************************************************************/ BOOL SGDatabase::RetrieveData( JET_TABLEID tblID, VOID *entry, DWORD numColumns, ColumnSpec **columnSpecs, const JET_COLUMNID *columnIDs, DWORD includeMask) const { JET_COLTYP coltyp; JET_ERR jetErr; ColumnSpec *columnSpec; BYTE *dataPtr; DWORD cbData, cbActual, i; BOOL varCol; ASSERT(sesID != ~0); ASSERT(numColumns <= MAX_COLUMNS); // May want to convert to JetRetrieveColumns for (i = 0; i < numColumns; i++) if ((includeMask & (1U << i)) != 0) { columnSpec = columnSpecs[i]; coltyp = columnSpec->coltyp; varCol = coltyp == JET_coltypBinary || coltyp == JET_coltypLongBinary; dataPtr = (BYTE *)entry + columnSpec->offset; if (varCol) dataPtr = *(BYTE **)dataPtr; if (dataPtr != NULL) { jetErr = JetRetrieveColumn(sesID, tblID, columnIDs[i], dataPtr, columnSpec->size, &cbActual, 0, NULL); if (jetErr == JET_errSuccess) cbData = varCol ? (_tcslen((TCHAR *)dataPtr) + 1) * sizeof(TCHAR) : columnSpec->size; else if (varCol && jetErr == JET_wrnColumnNull) { *(TCHAR *)dataPtr = _T('\0'); cbData = 0; } else { DPRINTF((_T("JetRetrieveColumn: jetErr=%ld\n"), jetErr)); return FALSE; } if (cbActual != cbData) { DPRINTF((_T("JetRetrieveColumn: cbActual=%lu!=%lu\n"), cbActual, cbData)); return FALSE; } } } return TRUE; } /*****************************************************************************/ LONG SGDatabase::Delete(JET_TABLEID tblID) { JET_ERR jetErr; LONG count, status; count = 0; ASSERT(sesID != ~0); while (TRUE) { jetErr = JetDelete(sesID, tblID); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetDelete: jetErr=%ld\n"), jetErr)); return -1; } count++; status = PositionCursorNext(tblID); if (status < 0) return status; if (status == 0) return count; } } /*****************************************************************************/ LONG SGDatabase::Count( JET_TABLEID tblID, const CHAR *keyName) const { JET_ERR jetErr; LONG count, status; count = 0; status = PositionCursorFirst(tblID, keyName); if (status < 0) return status; if (status == 0) return 0; ASSERT(sesID != ~0); jetErr = JetIndexRecordCount(sesID, tblID, (ULONG *) &count, MAXLONG); if (jetErr != JET_errSuccess) { if (jetErr == JET_errNoCurrentRecord) return 0; DPRINTF((_T("JetIndexRecordCount: jetErr=%ld\n"), jetErr)); return -1; } return count; } /*****************************************************************************/ /********************** SGDatabase class public methods **********************/ /*****************************************************************************/ SGDatabase::SGDatabase() { fileName = NULL; sesID = tableID = queueID = stackID = listID = ~0U; dbID = ~0U; numTableEntries = numQueueEntries = numStackEntries = numListEntries = 0; numUncommittedTableEntries = numUncommittedQueueEntries = numUncommittedStackEntries = numUncommittedListEntries = 0; inTransaction = FALSE; if (!jetInitialized) InitializeEngine(); numInstances++; } /*****************************************************************************/ SGDatabase::~SGDatabase() { Close(); ASSERT(fileName == NULL); ASSERT(sesID == ~0U); ASSERT(dbID == ~0U); ASSERT(tableID == ~0U); ASSERT(queueID == ~0U); ASSERT(stackID == ~0U); ASSERT(listID == ~0U); ASSERT(numTableEntries == 0); ASSERT(numQueueEntries == 0); ASSERT(numStackEntries == 0); ASSERT(numListEntries == 0); ASSERT(numUncommittedTableEntries == 0); ASSERT(numUncommittedQueueEntries == 0); ASSERT(numUncommittedStackEntries == 0); ASSERT(numUncommittedListEntries == 0); ASSERT(!inTransaction); if (--numInstances == 0 && jetInitialized) { TerminateEngine(); } } /*****************************************************************************/ BOOL SGDatabase::Create(const TCHAR *dbName) { CHAR szConnect[MAX_PATH]; DWORD strLen1, strLen2; JET_ERR jetErr; ASSERT(fileName == NULL); ASSERT(sesID == ~0U); ASSERT(dbID == ~0U); ASSERT(tableID == ~0U); ASSERT(queueID == ~0U); ASSERT(stackID == ~0U); ASSERT(listID == ~0U); ASSERT(numTableEntries == 0); ASSERT(numQueueEntries == 0); ASSERT(numStackEntries == 0); ASSERT(numListEntries == 0); ASSERT(numUncommittedTableEntries == 0); ASSERT(numUncommittedQueueEntries == 0); ASSERT(numUncommittedStackEntries == 0); ASSERT(numUncommittedListEntries == 0); ASSERT(!inTransaction); if (!jetInitialized && !InitializeEngine()) return FALSE; ASSERT(jetInitialized); jetErr = JetBeginSession(instance, &sesID, USERNAME, PASSWORD); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetBeginSession: jetErr=%ld\n"), jetErr)); Close(); return FALSE; } DPRINTF((_T("JetBeginSession: sesID=%lu\n"), sesID)); ASSERT(fileName == NULL); strLen1 = _tcslen(dbName); fileName = new CHAR[strLen1+1]; ASSERT(fileName != NULL); #ifdef _UNICODE strLen2 = sprintf(fileName, "%S", dbName); #else strLen2 = sprintf(fileName, "%s", dbName); #endif ASSERT(strLen1 == strLen2); sprintf(szConnect, ";COUNTRY=%u;LANGID=0x%04x;CP=%u", COUNTRY_CODE, LANG_ID, CODE_PAGE); // // Create the database // jetErr = JetCreateDatabase(sesID, fileName, szConnect, &dbID, 0); if (jetErr == JET_errSuccess) { DPRINTF((_T("JetCreateDatabase(\"%s\"): dbID=%lu\n"),dbName, dbID)); } else { if (jetErr != JET_errDatabaseDuplicate) { DPRINTF((_T("JetCreateDatabase(\"%s\"): jetErr=%ld\n"), dbName, jetErr)); Close(); return FALSE; } if (!DeleteFile(dbName)) { DPRINTF((_T("JetCreateDatabase: \"%s\" already exists and can't be deleted: %lu\n"), dbName, GetLastError())); Close(); return FALSE; } jetErr = JetCreateDatabase(sesID, fileName, szConnect, &dbID, 0); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetCreateDatabase: deleted old \"%s\"; jetErr=%ld\n"), dbName, jetErr)); Close(); return FALSE; } DPRINTF((_T("JetCreateDatabase: deleted old \"%s\"; new dbID=%lu\n"), dbName, dbID)); } if (!CreateTable(TABLE_NAME, TABLE_NCOLS, tableColumnSpecs, tableColumnIDs, &tableID)) { Close(); return FALSE; } if (!CreateIndex(tableID, TABLE_KEY_NAME_FILE_ID, TABLE_KEY_NCOLS_FILE_ID, tableKeyFileID) || !CreateIndex(tableID, TABLE_KEY_NAME_ATTR, TABLE_KEY_NCOLS_ATTR, tableKeyAttr) || !CreateIndex(tableID, TABLE_KEY_NAME_CSID, TABLE_KEY_NCOLS_CSID, tableKeyCSID)) { Close(); return FALSE; } if (!CreateTable(QUEUE_NAME, QUEUE_NCOLS, queueColumnSpecs, queueColumnIDs, &queueID)) { Close(); return FALSE; } if (!CreateIndex(queueID, QUEUE_KEY_NAME_READY_TIME, QUEUE_KEY_NCOLS_READY_TIME, queueKeyReadyTime) || !CreateIndex(queueID, QUEUE_KEY_NAME_FILE_ID, QUEUE_KEY_NCOLS_FILE_ID, queueKeyFileID) || !CreateIndex(queueID, QUEUE_KEY_NAME_ORDER, QUEUE_KEY_NCOLS_ORDER, queueKeyOrder)) { Close(); return FALSE; } if (!CreateTable(STACK_NAME, STACK_NCOLS, stackColumnSpecs, stackColumnIDs, &stackID)) { Close(); return FALSE; } if (!CreateIndex(stackID, STACK_KEY_NAME_FILE_ID, STACK_KEY_NCOLS_FILE_ID, stackKeyFileID) || !CreateIndex(stackID, STACK_KEY_NAME_ORDER, STACK_KEY_NCOLS_ORDER, stackKeyOrder)) { Close(); return FALSE; } if (!CreateTable(LIST_NAME, LIST_NCOLS, listColumnSpecs, listColumnIDs, &listID)) { Close(); return FALSE; } if (!CreateIndex(listID, LIST_KEY_NAME_NAME, LIST_KEY_NCOLS_NAME, listKeyName)) { Close(); return FALSE; } return TRUE; } /*****************************************************************************/ BOOL SGDatabase::Open(const TCHAR *dbName, BOOL is_log_drive) { SGNativeStackEntry stackEntry; JET_ERR jetErr; DWORD strLen1; #ifdef _UNICODE DWORD strLen2; #endif LONG status; ASSERT(sesID == ~0U); ASSERT(dbID == ~0U); ASSERT(tableID == ~0U); ASSERT(queueID == ~0U); ASSERT(stackID == ~0U); ASSERT(listID == ~0U); ASSERT(numTableEntries == 0); ASSERT(numQueueEntries == 0); ASSERT(numStackEntries == 0); ASSERT(numListEntries == 0); ASSERT(numUncommittedTableEntries == 0); ASSERT(numUncommittedQueueEntries == 0); ASSERT(numUncommittedStackEntries == 0); ASSERT(numUncommittedListEntries == 0); ASSERT(!inTransaction); // If this isn't the log drive, delete any log files that may exist // from a previous run. This is an abnormal condition that can arise // when the log drive is changing because of problems detected during // a previous startup. if (!is_log_drive) { WIN32_FIND_DATA findData; HANDLE fHandle; BOOL success; TFileName fName, delName; delName.assign(logDir); delName.append(_T("\\")); delName.append(DATABASE_DELETE_LOG_FILE_NAME); fHandle = FindFirstFile(delName.name, &findData); if (fHandle != INVALID_HANDLE_VALUE) { do { if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { success = GetParentName(delName.name, &fName); ASSERT(success); // internal error if failed fName.append(_T("\\")); fName.append(findData.cFileName); if (!DeleteFile(fName.name)) { DPRINTF((_T("SGDatabase::Open: can't delete \"%s\", %d\n"), delName.name, GetLastError())); } } } while (FindNextFile(fHandle, &findData)); success = FindClose(fHandle); ASSERT(success); fHandle = NULL; } } if (!jetInitialized && !InitializeEngine()) { Close(); return FALSE; } ASSERT(jetInitialized); jetErr = JetBeginSession(instance, &sesID, USERNAME, PASSWORD); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetBeginSession: jetErr=%ld\n"), jetErr)); Close(); return FALSE; } DPRINTF((_T("JetBeginSession: sesID=%lu\n"), sesID)); ASSERT(fileName == NULL); strLen1 = _tcslen(dbName); fileName = new CHAR[strLen1 + 1]; ASSERT(fileName != NULL); #ifdef _UNICODE strLen2 = sprintf(fileName, "%S", dbName); #else strLen2 = sprintf(fileName, "%s", dbName); #endif ASSERT(strLen1 == strLen2); // // Open the database // jetErr = JetAttachDatabase(sesID, fileName, 0); if (jetErr != JET_errSuccess && jetErr != JET_wrnDatabaseAttached) { if (jetErr == JET_errFileNotFound) { DPRINTF((_T("JetAttachDatabase: \"%s\" not found\n"), dbName)); } else { DPRINTF((_T("JetAttachDatabase(\"%s\"): jetErr=%ld\n"), dbName, jetErr)); } Close(); return FALSE; } jetErr = JetOpenDatabase(sesID, fileName, NULL, &dbID, 0); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetOpenDatabase(\"%s\"): jetErr=%ld\n"), dbName, jetErr)); Close(); return FALSE; } DPRINTF((_T("JetOpenDatabase(\"%s\"): dbID=%lu\n"), dbName, dbID)); if (!OpenTable(TABLE_NAME, TABLE_NCOLS, tableColumnSpecs, tableColumnIDs, &tableID)) { Close(); return FALSE; } if (!OpenTable(QUEUE_NAME, QUEUE_NCOLS, queueColumnSpecs, queueColumnIDs, &queueID)) { Close(); return FALSE; } if (!OpenTable(STACK_NAME, STACK_NCOLS, stackColumnSpecs, stackColumnIDs, &stackID)) { Close(); return FALSE; } if (!OpenTable(LIST_NAME, LIST_NCOLS, listColumnSpecs, listColumnIDs, &listID)) { Close(); return FALSE; } if ((numTableEntries = Count(tableID, TABLE_KEY_NAME_FILE_ID)) < 0 || (numQueueEntries = Count(queueID, QUEUE_KEY_NAME_READY_TIME)) < 0 || (numStackEntries = Count(stackID, STACK_KEY_NAME_FILE_ID)) < 0 || (numListEntries = Count(listID, LIST_KEY_NAME_NAME)) < 0) { Close(); return FALSE; } return TRUE; } /*****************************************************************************/ BOOL SGDatabase::Close() { JET_ERR jetErr; int strLen; BOOL success = TRUE; if (inTransaction) { success = CommitTransaction(); inTransaction = FALSE; } ASSERT(numUncommittedTableEntries == 0); ASSERT(numUncommittedQueueEntries == 0); ASSERT(numUncommittedStackEntries == 0); ASSERT(numUncommittedListEntries == 0); if (tableID != ~0U) { if (!CloseTable(tableID)) success = FALSE; tableID = ~0U; } if (queueID != ~0U) { if (!CloseTable(queueID)) success = FALSE; queueID = ~0U; } if (stackID != ~0U) { if (!CloseTable(stackID)) success = FALSE; stackID = ~0U; } if (listID != ~0U) { if (!CloseTable(listID)) success = FALSE; listID = ~0U; } if (dbID != ~0U) { ASSERT(fileName != NULL); ASSERT(sesID != ~0U); jetErr = JetCloseDatabase(sesID, dbID, 0); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetCloseDatabase: jetErr=%ld\n"), jetErr)); success = FALSE; } jetErr = JetDetachDatabase(sesID, fileName); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetDetachDatabase: jetErr=%ld\n"), jetErr)); success = FALSE; } dbID = ~0U; } if (sesID != ~0U) { jetErr = JetEndSession(sesID, 0); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetEndSession: jetErr=%ld\n"), jetErr)); success = FALSE; } sesID = ~0U; } if (fileName != NULL) { delete[] fileName; fileName = NULL; } numTableEntries = numQueueEntries = numStackEntries = numListEntries = 0; return success; } /*****************************************************************************/ BOOL SGDatabase::BeginTransaction() { JET_ERR jetErr; ASSERT(!inTransaction); ASSERT(numUncommittedTableEntries == 0); ASSERT(numUncommittedQueueEntries == 0); ASSERT(numUncommittedStackEntries == 0); ASSERT(numUncommittedListEntries == 0); if (sesID == ~0U) return -1; jetErr = JetBeginTransaction(sesID); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetBeginTransaction: jetErr=%ld\n"), jetErr)); return FALSE; } inTransaction = TRUE; return TRUE; } /*****************************************************************************/ BOOL SGDatabase::CommitTransaction() { JET_ERR jetErr; ASSERT(inTransaction); if (sesID == ~0U) return -1; jetErr = JetCommitTransaction(sesID, 0); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetCommitTransaction: jetErr=%ld\n"), jetErr)); return FALSE; } numTableEntries += numUncommittedTableEntries; numQueueEntries += numUncommittedQueueEntries; numStackEntries += numUncommittedStackEntries; numListEntries += numUncommittedListEntries; numUncommittedTableEntries = 0; numUncommittedQueueEntries = 0; numUncommittedStackEntries = 0; numUncommittedListEntries = 0; inTransaction = FALSE; return TRUE; } /*****************************************************************************/ BOOL SGDatabase::AbortTransaction() { JET_ERR jetErr; ASSERT(inTransaction); inTransaction = FALSE; if (sesID == ~0U) return -1; jetErr = JetRollback(sesID, 0); if (jetErr != JET_errSuccess) { DPRINTF((_T("JetRollback: jetErr=%ld\n"), jetErr)); return FALSE; } numUncommittedTableEntries = 0; numUncommittedQueueEntries = 0; numUncommittedStackEntries = 0; numUncommittedListEntries = 0; return TRUE; } /******************************* Table methods *******************************/ LONG SGDatabase::TablePut(const SGNativeTableEntry *entry) { BOOL alreadyInTransaction = inTransaction; ASSERT(entry != NULL); if (sesID == ~0U || dbID == ~0U || tableID == ~0U) return -1; if (!inTransaction && !BeginTransaction()) return -1; ASSERT(inTransaction); if (!PutData(tableID, entry, TABLE_NCOLS, tableColumnSpecs, tableColumnIDs)) { if (!alreadyInTransaction) AbortTransaction(); return -1; } numUncommittedTableEntries++; if (!alreadyInTransaction && !CommitTransaction()) return -1; return 1; } /*****************************************************************************/ LONG SGDatabase::TableGetFirstByFileID(SGNativeTableEntry *entry) const { LONG status; ASSERT(entry != NULL); if (sesID == ~0U || dbID == ~0U || tableID == ~0U) return -1; status = PositionCursor(tableID, TABLE_KEY_NAME_FILE_ID, entry, TABLE_KEY_NCOLS_FILE_ID, tableKeyFileID); if (status <= 0) return status; return RetrieveData(tableID, entry, TABLE_NCOLS, tableColumnSpecs, tableColumnIDs, TABLE_EXCLUDE_FILE_ID_MASK ) ? 1 : -1; } /*****************************************************************************/ LONG SGDatabase::TableGetFirstByAttr(SGNativeTableEntry *entry) const { LONG status; ASSERT(entry != NULL); if (sesID == ~0U || dbID == ~0U || tableID == ~0U) return -1; status = PositionCursor(tableID, TABLE_KEY_NAME_ATTR, entry, TABLE_KEY_NCOLS_ATTR, tableKeyAttr); if (status <= 0) return status; return RetrieveData(tableID, entry, TABLE_NCOLS, tableColumnSpecs, tableColumnIDs, TABLE_EXCLUDE_ATTR_MASK) ? 1 : -1; } /*****************************************************************************/ LONG SGDatabase::TableGetFirstByCSIndex(SGNativeTableEntry *entry) const { LONG status; ASSERT(entry != NULL); if (sesID == ~0U || dbID == ~0U || tableID == ~0U) return -1; status = PositionCursor(tableID, TABLE_KEY_NAME_CSID, entry, TABLE_KEY_NCOLS_CSID, tableKeyCSID); if (status <= 0) return status; return RetrieveData(tableID, entry, TABLE_NCOLS, tableColumnSpecs, tableColumnIDs, TABLE_EXCLUDE_CS_INDEX_MASK) ? 1 : -1; } /*****************************************************************************/ LONG SGDatabase::TableGetNext(SGNativeTableEntry *entry) const { LONG status; ASSERT(entry != NULL); if (sesID == ~0U || dbID == ~0U || tableID == ~0U) return -1; status = PositionCursorNext(tableID); if (status <= 0) return status; return RetrieveData(tableID, entry, TABLE_NCOLS, tableColumnSpecs, tableColumnIDs, GET_ALL_MASK) ? 1 : -1; } /*****************************************************************************/ LONG SGDatabase::TableDeleteByFileID(DWORDLONG fileID) { SGNativeTableEntry entry; LONG status; BOOL alreadyInTransaction = inTransaction; if (sesID == ~0U || dbID == ~0U || tableID == ~0U) return -1; entry.fileID = fileID; status = PositionCursor(tableID, TABLE_KEY_NAME_FILE_ID, &entry, TABLE_KEY_NCOLS_FILE_ID, tableKeyFileID); if (status <= 0) return status; if (!inTransaction && !BeginTransaction()) return -1; ASSERT(inTransaction); status = Delete(tableID); if (status < 0) { if (!alreadyInTransaction) AbortTransaction(); return -1; } numUncommittedTableEntries -= status; if (!alreadyInTransaction && !CommitTransaction()) return -1; return status; } /*****************************************************************************/ LONG SGDatabase::TableDeleteByCSIndex(const CSID *csIndex) { SGNativeTableEntry entry; LONG status; BOOL alreadyInTransaction = inTransaction; ASSERT(csIndex != NULL); if (sesID == ~0U || dbID == ~0U || tableID == ~0U) return -1; entry.csIndex = *csIndex; status = PositionCursor(tableID, TABLE_KEY_NAME_CSID, &entry, TABLE_KEY_NCOLS_CSID, tableKeyCSID); if (status <= 0) return status; if (!inTransaction && !BeginTransaction()) return -1; ASSERT(inTransaction); status = Delete(tableID); if (status < 0) { if (!alreadyInTransaction) AbortTransaction(); return -1; } numUncommittedTableEntries -= status; if (!alreadyInTransaction && !CommitTransaction()) return -1; return status; } /*****************************************************************************/ LONG SGDatabase::TableCount() const { LONG numEntries; if (sesID == ~0U || dbID == ~0U || tableID == ~0U) return -1; numEntries = numTableEntries + numUncommittedTableEntries; ASSERT(numEntries >= 0); ASSERT(Count(tableID, TABLE_KEY_NAME_FILE_ID) == numEntries); return numEntries; } /******************************* Queue methods *******************************/ LONG SGDatabase::QueuePut(SGNativeQueueEntry *entry) { BOOL alreadyInTransaction = inTransaction; ASSERT(entry != NULL); if (sesID == ~0U || dbID == ~0U || queueID == ~0U) return -1; if (!inTransaction && !BeginTransaction()) return -1; ASSERT(inTransaction); if (!PutData(queueID, entry, QUEUE_NCOLS, queueColumnSpecs, queueColumnIDs)) { if (!alreadyInTransaction) AbortTransaction(); return -1; } numUncommittedQueueEntries++; if (!alreadyInTransaction && !CommitTransaction()) return -1; return 1; } /*****************************************************************************/ LONG SGDatabase::QueueGetFirst(SGNativeQueueEntry *entry) const { LONG status; ASSERT(entry != NULL); if (sesID == ~0U || dbID == ~0U || queueID == ~0U) return -1; status = PositionCursorFirst(queueID, QUEUE_KEY_NAME_READY_TIME); if (status <= 0) return status; return RetrieveData(queueID, entry, QUEUE_NCOLS, queueColumnSpecs, queueColumnIDs, GET_ALL_MASK) ? 1 : -1; } /*****************************************************************************/ LONG SGDatabase::QueueGetFirstByFileID(SGNativeQueueEntry *entry) const { LONG status; ASSERT(entry != NULL); if (sesID == ~0U || dbID == ~0U || queueID == ~0U) return -1; status = PositionCursor(queueID, QUEUE_KEY_NAME_FILE_ID, entry, QUEUE_KEY_NCOLS_FILE_ID, queueKeyFileID); if (status <= 0) return status; return RetrieveData(queueID, entry, QUEUE_NCOLS, queueColumnSpecs, queueColumnIDs, QUEUE_EXCLUDE_FILE_ID_MASK) ? 1 : -1; } /*****************************************************************************/ LONG SGDatabase::QueueGetNext(SGNativeQueueEntry *entry) const { LONG status; ASSERT(entry != NULL); if (sesID == ~0U || dbID == ~0U || queueID == ~0U) return -1; status = PositionCursorNext(queueID); if (status <= 0) return status; return RetrieveData(queueID, entry, QUEUE_NCOLS, queueColumnSpecs, queueColumnIDs, GET_ALL_MASK) ? 1 : -1; } /*****************************************************************************/ LONG SGDatabase::QueueDelete(DWORD order) { SGNativeQueueEntry entry; LONG status; BOOL alreadyInTransaction = inTransaction; ASSERT(sesID != ~0U); ASSERT(dbID != ~0U); ASSERT(queueID != ~0U); entry.order = order; status = PositionCursor(queueID, QUEUE_KEY_NAME_ORDER, &entry, QUEUE_KEY_NCOLS_ORDER, queueKeyOrder); if (status <= 0) return status; if (!inTransaction && !BeginTransaction()) return -1; ASSERT(inTransaction); status = Delete(queueID); ASSERT(status <= 1); if (status < 0) { if (!alreadyInTransaction) AbortTransaction(); return -1; } numUncommittedQueueEntries -= status; if (!alreadyInTransaction && !CommitTransaction()) return -1; return status; } /*****************************************************************************/ LONG SGDatabase::QueueDeleteByFileID(DWORDLONG fileID) { SGNativeQueueEntry entry; LONG status; BOOL alreadyInTransaction = inTransaction; if (sesID == ~0U || dbID == ~0U || queueID == ~0U) return -1; entry.fileID = fileID; status = PositionCursor(queueID, QUEUE_KEY_NAME_FILE_ID, &entry, QUEUE_KEY_NCOLS_FILE_ID, queueKeyFileID); if (status <= 0) return status; if (!inTransaction && !BeginTransaction()) return -1; ASSERT(inTransaction); status = Delete(queueID); if (status < 0) { if (!alreadyInTransaction) AbortTransaction(); return -1; } numUncommittedQueueEntries -= status; if (!alreadyInTransaction && !CommitTransaction()) return -1; return status; } /*****************************************************************************/ LONG SGDatabase::QueueCount() const { LONG numEntries; if (sesID == ~0U || dbID == ~0U || queueID == ~0U) return -1; numEntries = numQueueEntries + numUncommittedQueueEntries; ASSERT(numEntries >= 0); ASSERT(Count(queueID, QUEUE_KEY_NAME_READY_TIME) == numEntries); return numEntries; } /******************************* Stack methods *******************************/ LONG SGDatabase::StackPut(DWORDLONG fileID, BOOL done) { SGNativeStackEntry entry; LONG status; BOOL alreadyInTransaction = inTransaction; if (sesID == ~0U || dbID == ~0U || stackID == ~0U) return -1; if (done) entry.order = 0; else { status = PositionCursorLast(stackID, STACK_KEY_NAME_ORDER); if (status < 0) return -1; if (status == 0) entry.order = 1; else { if (!RetrieveData(stackID, &entry, STACK_NCOLS, stackColumnSpecs, stackColumnIDs, STACK_GET_ORDER_ONLY_MASK)) return -1; entry.order++; } } entry.fileID = fileID; if (!inTransaction && !BeginTransaction()) return -1; ASSERT(inTransaction); if (!PutData(stackID, &entry, STACK_NCOLS, stackColumnSpecs, stackColumnIDs)) { if (!alreadyInTransaction) AbortTransaction(); return -1; } numUncommittedStackEntries++; if (!alreadyInTransaction && !CommitTransaction()) return -1; return 1; } /*****************************************************************************/ LONG SGDatabase::StackGetTop(SGNativeStackEntry *entry) const { LONG status; ASSERT(entry != NULL); if (sesID == ~0U || dbID == ~0U || stackID == ~0U) return -1; status = PositionCursorLast(stackID, STACK_KEY_NAME_ORDER); if (status <= 0) return status; status = RetrieveData(stackID, entry, STACK_NCOLS, stackColumnSpecs, stackColumnIDs, GET_ALL_MASK); if (status < 0) return status; ASSERT(status == 1); return entry->order == 0 ? 0 : 1; } /*****************************************************************************/ LONG SGDatabase::StackGetFirstByFileID(SGNativeStackEntry *entry) const { LONG status; ASSERT(entry != NULL); if (sesID == ~0U || dbID == ~0U || stackID == ~0U) return -1; status = PositionCursor(stackID, STACK_KEY_NAME_FILE_ID, entry, STACK_KEY_NCOLS_FILE_ID, stackKeyFileID); if (status <= 0) return status; return RetrieveData(stackID, entry, STACK_NCOLS, stackColumnSpecs, stackColumnIDs, STACK_EXCLUDE_FILE_ID_MASK) ? 1 : -1; } /*****************************************************************************/ LONG SGDatabase::StackGetNext(SGNativeStackEntry *entry) const { LONG status; ASSERT(entry != NULL); if (sesID == ~0U || dbID == ~0U || stackID == ~0U) return -1; status = PositionCursorNext(stackID); if (status <= 0) return status; return RetrieveData(stackID, entry, STACK_NCOLS, stackColumnSpecs, stackColumnIDs, GET_ALL_MASK) ? 1 : -1; } /*****************************************************************************/ LONG SGDatabase::StackDelete(DWORD order) { SGNativeStackEntry entry; LONG status; BOOL alreadyInTransaction = inTransaction; if (sesID == ~0U || dbID == ~0U || stackID == ~0U) return -1; entry.order = order; status = PositionCursor(stackID, STACK_KEY_NAME_ORDER, &entry, STACK_KEY_NCOLS_ORDER, stackKeyOrder); if (status <= 0) return status; if (!inTransaction && !BeginTransaction()) return -1; ASSERT(inTransaction); status = Delete(stackID); ASSERT(order == 0 || status <= 1); if (status < 0) { if (!alreadyInTransaction) AbortTransaction(); return -1; } numUncommittedStackEntries -= status; if (!alreadyInTransaction && !CommitTransaction()) return -1; return status; } /*****************************************************************************/ LONG SGDatabase::StackDeleteByFileID(DWORDLONG fileID) { SGNativeStackEntry entry; LONG status; BOOL alreadyInTransaction = inTransaction; if (sesID == ~0U || dbID == ~0U || stackID == ~0U) return -1; entry.fileID = fileID; status = PositionCursor(stackID, STACK_KEY_NAME_FILE_ID, &entry, STACK_KEY_NCOLS_FILE_ID, stackKeyFileID); if (status <= 0) return status; if (!inTransaction && !BeginTransaction()) return -1; ASSERT(inTransaction); status = Delete(stackID); if (status < 0) { if (!alreadyInTransaction) AbortTransaction(); return -1; } numUncommittedStackEntries -= status; if (!alreadyInTransaction && !CommitTransaction()) return -1; return status; } /*****************************************************************************/ LONG SGDatabase::StackCount() const { LONG numEntries; if (sesID == ~0U || dbID == ~0U || stackID == ~0U) return -1; numEntries = numStackEntries + numUncommittedStackEntries; ASSERT(numEntries >= 0); ASSERT(Count(stackID, STACK_KEY_NAME_ORDER) == numEntries); return numEntries; } /******************************* List methods ********************************/ LONG SGDatabase::ListWrite(const SGNativeListEntry *entry) { LONG status; BOOL alreadyInTransaction = inTransaction; ASSERT(entry != NULL); ASSERT(entry->name != NULL); if (sesID == ~0U || dbID == ~0U || listID == ~0U) return -1; // May want to overwrite the entry directly instead of deleting and inserting if (!inTransaction && !BeginTransaction()) return -1; ASSERT(inTransaction); status = ListDelete(entry->name); ASSERT(status <= 1); if (status < 0 || !PutData(listID, entry, LIST_NCOLS, listColumnSpecs, listColumnIDs)) { if (!alreadyInTransaction) AbortTransaction(); return -1; } if (status == 0) numUncommittedListEntries++; if (!alreadyInTransaction && !CommitTransaction()) return -1; return 1; } /*****************************************************************************/ LONG SGDatabase::ListRead(SGNativeListEntry *entry) const { LONG status; ASSERT(entry != NULL); ASSERT(entry->name != NULL); if (sesID == ~0U || dbID == ~0U || listID == ~0U) return -1; status = PositionCursor(listID, LIST_KEY_NAME_NAME, entry, LIST_KEY_NCOLS_NAME, listKeyName); if (status <= 0) return status; return RetrieveData(listID, entry, LIST_NCOLS, listColumnSpecs, listColumnIDs, LIST_EXCLUDE_NAME_MASK) ? 1 : -1; } /*****************************************************************************/ LONG SGDatabase::ListDelete(const TCHAR *name) { SGNativeListEntry entry; LONG status; BOOL alreadyInTransaction = inTransaction; ASSERT(name != NULL); if (sesID == ~0U || dbID == ~0U || listID == ~0U) return -1; entry.name = name; entry.value = NULL; status = PositionCursor(listID, LIST_KEY_NAME_NAME, &entry, LIST_KEY_NCOLS_NAME, listKeyName); if (status <= 0) return status; if (!inTransaction && !BeginTransaction()) return -1; ASSERT(inTransaction); status = Delete(listID); if (status < 0) { if (!alreadyInTransaction) AbortTransaction(); return -1; } return status; } /*****************************************************************************/ LONG SGDatabase::ListCount() const { LONG numEntries; if (sesID == ~0U || dbID == ~0U || listID == ~0U) return -1; numEntries = numListEntries + numUncommittedListEntries; ASSERT(numEntries >= 0); ASSERT(Count(listID, LIST_KEY_NAME_NAME) == numEntries); return numEntries; }