From 075f26f903484e303e6709d7a3933fc0a2b60e57 Mon Sep 17 00:00:00 2001 From: Todd Jeffreys Date: Sat, 5 Oct 2024 20:11:04 -0600 Subject: [PATCH] Improve Create + add tests --- vstudio/wbtrv32/wbtrv32.cpp | 60 ++++++--- vstudio/wbtrv32/wbtrv32.h | 8 ++ vstudio/wbtrv32/wbtrv32_test.cc | 226 ++++++++++++++++++++++++++++++++ 3 files changed, 276 insertions(+), 18 deletions(-) diff --git a/vstudio/wbtrv32/wbtrv32.cpp b/vstudio/wbtrv32/wbtrv32.cpp index 6181540..123559b 100644 --- a/vstudio/wbtrv32/wbtrv32.cpp +++ b/vstudio/wbtrv32/wbtrv32.cpp @@ -453,14 +453,6 @@ static BtrieveError Stop(const BtrieveCommand &command) { return BtrieveError::Success; } -typedef struct _tagACSCREATEDATA { - char header; // should be 0xAC - char name[8]; // not necessarily null terminated - char acs[256]; // the table itself -} ACSCREATEDATA, *LPACSCREATEDATA; - -static_assert(sizeof(ACSCREATEDATA) == 265); - static BtrieveError Create(BtrieveCommand &command) { const char *lpszFileName = reinterpret_cast(command.lpKeyBuffer); @@ -489,19 +481,42 @@ static BtrieveError Create(BtrieveCommand &command) { } } + std::vector clientProvidedAcs; + uint8_t numberOfAcs = 0; + // find and categorize all the key data before populating everything + for (uint16_t i = 0; i < lpFileSpec->numberOfKeys; ++i, ++lpKeySpec) { + check_acs: + if (lpKeySpec->attributes & NumberedACS) { + numberOfAcs = max(numberOfAcs, lpKeySpec->acsNumber + 1); + } + + if (lpKeySpec->attributes & SegmentedKey) { + ++lpKeySpec; + goto check_acs; + } + } + // at this point we are pointing at the ACS data, so store it now + wbtrv32::LPACSCREATEDATA lpAcsCreateData = + reinterpret_cast(lpKeySpec); + while (numberOfAcs--) { + if (lpAcsCreateData->header != 0xAC) { + return BtrieveError::InvalidACS; + } + + clientProvidedAcs.push_back(lpAcsCreateData++); + } + + lpKeySpec = reinterpret_cast(lpFileSpec + 1); std::vector keys; - for (uint16_t i = 0; i < lpFileSpec->numberOfKeys; ++i) { + for (uint16_t i = 0; i < lpFileSpec->numberOfKeys; ++i, ++lpKeySpec) { char acsName[9]; std::vector acs; + std::vector keyDefinitions; + process_key: if (lpKeySpec->attributes & NumberedACS) { - LPACSCREATEDATA lpAcsCreateData = reinterpret_cast( - reinterpret_cast(command.lpDataBuffer) + - *command.lpdwDataBufferLength - sizeof(ACSCREATEDATA)); - - if (lpAcsCreateData->header != 0xAC) { - return BtrieveError::InvalidACS; - } + wbtrv32::LPACSCREATEDATA lpAcsCreateData = + clientProvidedAcs[lpKeySpec->acsNumber]; memcpy(acsName, lpAcsCreateData->name, 8); acsName[8] = 0; @@ -517,9 +532,18 @@ static BtrieveError Create(BtrieveCommand &command) { i, static_cast(lpKeySpec->length), static_cast(lpKeySpec->position) - 1, static_cast(lpKeySpec->extendedDataType), - lpKeySpec->attributes, false, 0, 0, lpKeySpec->nullValue, acsName, acs); + lpKeySpec->attributes, lpKeySpec->attributes & SegmentedKey, + lpKeySpec->attributes & SegmentedKey ? i : 0, 0, lpKeySpec->nullValue, + acsName, acs); + + keyDefinitions.push_back(keyDefinition); + + if (lpKeySpec->attributes & SegmentedKey) { + ++lpKeySpec; + goto process_key; + } - keys.push_back(Key(&keyDefinition, 1)); + keys.push_back(Key(keyDefinitions.data(), keyDefinitions.size())); } RecordType recordType = RecordType::Fixed; diff --git a/vstudio/wbtrv32/wbtrv32.h b/vstudio/wbtrv32/wbtrv32.h index b23462e..938f7ed 100644 --- a/vstudio/wbtrv32/wbtrv32.h +++ b/vstudio/wbtrv32/wbtrv32.h @@ -31,10 +31,18 @@ typedef struct _tagKEYSPEC { uint8_t number; uint8_t acsNumber; } KEYSPEC, *LPKEYSPEC; + +typedef struct _tagACSCREATEDATA { + uint8_t header; // should be 0xAC + char name[8]; // not necessarily null terminated + char acs[256]; // the table itself +} ACSCREATEDATA, *LPACSCREATEDATA; + #pragma pack(pop) static_assert(sizeof(FILESPEC) == 16); static_assert(sizeof(KEYSPEC) == 16); +static_assert(sizeof(ACSCREATEDATA) == 265); void processAttach(); diff --git a/vstudio/wbtrv32/wbtrv32_test.cc b/vstudio/wbtrv32/wbtrv32_test.cc index 143ba99..d9f0810 100644 --- a/vstudio/wbtrv32/wbtrv32_test.cc +++ b/vstudio/wbtrv32/wbtrv32_test.cc @@ -1,11 +1,14 @@ #include "wbtrv32.h" +#include #include #include "../../btrieve/AttributeMask.h" +#include "../../btrieve/BtrieveDriver.h" #include "../../btrieve/ErrorCode.h" #include "../../btrieve/KeyDataType.h" #include "../../btrieve/OpenMode.h" +#include "../../btrieve/SqliteDatabase.h" #include "../../btrieve/TestBase.h" #include "btrieve/OperationCode.h" #include "gtest/gtest.h" @@ -1391,3 +1394,226 @@ TEST_F(wbtrv32Test, StopClosesAllDatabases) { &dwDataBufferLength, nullptr, 0, 0), btrieve::BtrieveError::FileNotOpen); } + +TEST_F(wbtrv32Test, CreateSingleKey) { + unsigned char buffer[1024]; + auto mbbsEmuDb = tempPath->getTempPath(); + std::filesystem::path path(mbbsEmuDb); + path /= L"test.dat"; + + memset(buffer, 0, sizeof(buffer)); + + wbtrv32::LPFILESPEC lpFileSpec = + reinterpret_cast(buffer); + + lpFileSpec->pageSize = 4096; + lpFileSpec->numberOfKeys = 1; + lpFileSpec->logicalFixedRecordLength = 128; + lpFileSpec->fileVersion = 0x60; + lpFileSpec->physicalPageSize = 4096 / 512; + lpFileSpec->fileFlags = 0; // not variable + + wbtrv32::LPKEYSPEC lpKeySpec = + reinterpret_cast(lpFileSpec + 1); + lpKeySpec->position = 3; + lpKeySpec->length = 4; + lpKeySpec->attributes = UseExtendedDataType | Duplicates; + lpKeySpec->extendedDataType = btrieve::KeyDataType::Integer; + + DWORD dwDataBufferLength = + reinterpret_cast(lpKeySpec + 1) - buffer; + ASSERT_EQ(btrcall(btrieve::OperationCode::Create, nullptr, buffer, + &dwDataBufferLength, + const_cast(reinterpret_cast( + toStdString(path.c_str()).c_str())), + -1, 0), + btrieve::BtrieveError::Success); + + path = mbbsEmuDb; + path /= "test.db"; + ASSERT_NE(GetFileAttributesW(path.c_str()), 0xFFFFFFFF); + + btrieve::BtrieveDriver driver(new btrieve::SqliteDatabase()); + ASSERT_EQ(driver.open(path.c_str()), btrieve::BtrieveError::Success); + + ASSERT_EQ(driver.getRecordCount(), 0); + ASSERT_EQ(driver.isVariableLengthRecords(), false); + ASSERT_EQ(driver.getKeys().size(), 1); + + EXPECT_EQ(driver.getKeys()[0].getPrimarySegment(), + btrieve::KeyDefinition(0, 4, 2, btrieve::KeyDataType::Integer, + Duplicates | UseExtendedDataType, false, 0, + 0, 0, "", std::vector())); +} + +TEST_F(wbtrv32Test, CreateSingleKeyWithAcs) { + unsigned char buffer[1024]; + auto mbbsEmuDb = tempPath->getTempPath(); + std::filesystem::path path(mbbsEmuDb); + path /= L"test.dat"; + + memset(buffer, 0, sizeof(buffer)); + + wbtrv32::LPFILESPEC lpFileSpec = + reinterpret_cast(buffer); + + lpFileSpec->pageSize = 4096; + lpFileSpec->numberOfKeys = 1; + lpFileSpec->logicalFixedRecordLength = 128; + lpFileSpec->fileVersion = 0x60; + lpFileSpec->physicalPageSize = 4096 / 512; + lpFileSpec->fileFlags = 1; // variable + + wbtrv32::LPKEYSPEC lpKeySpec = + reinterpret_cast(lpFileSpec + 1); + lpKeySpec->position = 3; + lpKeySpec->length = 16; + lpKeySpec->attributes = UseExtendedDataType | Duplicates | NumberedACS; + lpKeySpec->extendedDataType = btrieve::KeyDataType::Zstring; + + wbtrv32::LPACSCREATEDATA lpAcsCreateData = + reinterpret_cast(lpKeySpec + 1); + lpAcsCreateData->header = 0xAC; + strcpy(lpAcsCreateData->name, "ALLCAPS"); + for (int i = 0; i < ARRAYSIZE(lpAcsCreateData->acs); ++i) { + lpAcsCreateData->acs[i] = toupper(i); + } + + DWORD dwDataBufferLength = + reinterpret_cast(lpAcsCreateData + 1) - buffer; + ASSERT_EQ(btrcall(btrieve::OperationCode::Create, nullptr, buffer, + &dwDataBufferLength, + const_cast(reinterpret_cast( + toStdString(path.c_str()).c_str())), + -1, 0), + btrieve::BtrieveError::Success); + + path = mbbsEmuDb; + path /= "test.db"; + ASSERT_NE(GetFileAttributesW(path.c_str()), 0xFFFFFFFF); + + btrieve::BtrieveDriver driver(new btrieve::SqliteDatabase()); + ASSERT_EQ(driver.open(path.c_str()), btrieve::BtrieveError::Success); + + ASSERT_EQ(driver.getRecordCount(), 0); + ASSERT_EQ(driver.isVariableLengthRecords(), true); + ASSERT_EQ(driver.getKeys().size(), 1); + + std::vector acsVector(ACS_LENGTH); + memcpy(acsVector.data(), lpAcsCreateData->acs, ACS_LENGTH); + + EXPECT_EQ( + driver.getKeys()[0].getPrimarySegment(), + btrieve::KeyDefinition(0, 16, 2, btrieve::KeyDataType::Zstring, + Duplicates | UseExtendedDataType | NumberedACS, + false, 0, 0, 0, "ALLCAPS", acsVector)); +} + +TEST_F(wbtrv32Test, CreateMultipleKeysWithAcs) { + unsigned char buffer[1024]; + auto mbbsEmuDb = tempPath->getTempPath(); + std::filesystem::path path(mbbsEmuDb); + path /= L"test.dat"; + + memset(buffer, 0, sizeof(buffer)); + + wbtrv32::LPFILESPEC lpFileSpec = + reinterpret_cast(buffer); + + lpFileSpec->pageSize = 4096; + lpFileSpec->numberOfKeys = 3; + lpFileSpec->logicalFixedRecordLength = 128; + lpFileSpec->fileVersion = 0x60; + lpFileSpec->physicalPageSize = 4096 / 512; + lpFileSpec->fileFlags = 1; // variable + + // first key with an acs + wbtrv32::LPKEYSPEC lpKeySpec = + reinterpret_cast(lpFileSpec + 1); + lpKeySpec->number = 0; + lpKeySpec->position = 3; + lpKeySpec->length = 16; + lpKeySpec->attributes = UseExtendedDataType | Duplicates | NumberedACS; + lpKeySpec->extendedDataType = btrieve::KeyDataType::Zstring; + + // second key, which will have two segments + ++lpKeySpec; + lpKeySpec->number = 1; + lpKeySpec->position = 21; + lpKeySpec->length = 4; + lpKeySpec->attributes = UseExtendedDataType | SegmentedKey; + lpKeySpec->extendedDataType = btrieve::KeyDataType::Integer; + + ++lpKeySpec; + lpKeySpec->number = 1; + lpKeySpec->position = 25; + lpKeySpec->length = 8; + lpKeySpec->attributes = UseExtendedDataType; + lpKeySpec->extendedDataType = btrieve::KeyDataType::Float; + + // third key with a second acs + ++lpKeySpec; + lpKeySpec->number = 2; + lpKeySpec->position = 31; + lpKeySpec->length = 16; + lpKeySpec->attributes = UseExtendedDataType | NumberedACS; + lpKeySpec->extendedDataType = btrieve::KeyDataType::Zstring; + lpKeySpec->acsNumber = 1; + + // first acs + wbtrv32::LPACSCREATEDATA lpAcsCreateData1 = + reinterpret_cast(lpKeySpec + 1); + lpAcsCreateData1->header = 0xAC; + strcpy(lpAcsCreateData1->name, "ALLCAPS"); + for (int i = 0; i < ARRAYSIZE(lpAcsCreateData1->acs); ++i) { + lpAcsCreateData1->acs[i] = toupper(i); + } + + // second acs + wbtrv32::LPACSCREATEDATA lpAcsCreateData2 = lpAcsCreateData1 + 1; + lpAcsCreateData2->header = 0xAC; + strcpy(lpAcsCreateData2->name, "LOWER"); + for (int i = 0; i < ARRAYSIZE(lpAcsCreateData2->acs); ++i) { + lpAcsCreateData2->acs[i] = tolower(i); + } + + DWORD dwDataBufferLength = + reinterpret_cast(lpAcsCreateData2 + 1) - buffer; + ASSERT_EQ(btrcall(btrieve::OperationCode::Create, nullptr, buffer, + &dwDataBufferLength, + const_cast(reinterpret_cast( + toStdString(path.c_str()).c_str())), + -1, 0), + btrieve::BtrieveError::Success); + + path = mbbsEmuDb; + path /= "test.db"; + ASSERT_NE(GetFileAttributesW(path.c_str()), 0xFFFFFFFF); + + btrieve::BtrieveDriver driver(new btrieve::SqliteDatabase()); + ASSERT_EQ(driver.open(path.c_str()), btrieve::BtrieveError::Success); + + ASSERT_EQ(driver.getRecordCount(), 0); + ASSERT_EQ(driver.isVariableLengthRecords(), true); + ASSERT_EQ(driver.getKeys().size(), 3); + + std::vector acsVector(ACS_LENGTH); + memcpy(acsVector.data(), lpAcsCreateData1->acs, ACS_LENGTH); + + EXPECT_EQ( + driver.getKeys()[0].getPrimarySegment(), + btrieve::KeyDefinition(0, 16, 2, btrieve::KeyDataType::Zstring, + Duplicates | UseExtendedDataType | NumberedACS, + false, 0, 0, 0, "ALLCAPS", acsVector)); + + EXPECT_EQ(driver.getKeys()[1].isComposite(), true); + EXPECT_EQ(driver.getKeys()[1].getSegments().size(), 2); + EXPECT_EQ(driver.getKeys()[1].getNumber(), 1); + EXPECT_EQ(driver.getKeys()[1].getLength(), 12); + + memcpy(acsVector.data(), lpAcsCreateData2->acs, ACS_LENGTH); + EXPECT_EQ(driver.getKeys()[2].getPrimarySegment(), + btrieve::KeyDefinition(2, 16, 30, btrieve::KeyDataType::Zstring, + UseExtendedDataType | NumberedACS, false, 0, + 0, 0, "LOWER", acsVector)); +}