Skip to content

Commit

Permalink
Add support for Float fields
Browse files Browse the repository at this point in the history
  • Loading branch information
paladine committed Sep 28, 2024
1 parent a627cac commit 6af92b9
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 45 deletions.
70 changes: 69 additions & 1 deletion btrieve/BtrieveDriver_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1736,12 +1736,16 @@ static std::vector<char> upperACS() {
return ret;
}

static std::vector<uint8_t> createRecord(const char *username) {
static std::vector<uint8_t> createRecord(const char *username, float value1 = 0,
double value2 = 0) {
std::vector<uint8_t> record(ACS_RECORD_LENGTH);

memset(record.data(), 0xFF, record.size());
strcpy(reinterpret_cast<char *>(record.data() + 2), username);

*reinterpret_cast<float *>(record.data() + 64) = value1;
*reinterpret_cast<double *>(record.data() + 68) = value2;

return record;
}

Expand All @@ -1762,6 +1766,18 @@ static BtrieveDatabase createACSBtrieveDatabase() {

keys.push_back(Key(&keyDefinition, 1));

keyDefinition = KeyDefinition(1, 4, 64, KeyDataType::Float,
UseExtendedDataType | Duplicates, false, 0, 0,
0, "", std::vector<char>());

keys.push_back(Key(&keyDefinition, 1));

keyDefinition = KeyDefinition(2, 8, 68, KeyDataType::Float,
UseExtendedDataType | Duplicates, false, 0, 0,
0, "", std::vector<char>());

keys.push_back(Key(&keyDefinition, 1));

return BtrieveDatabase(keys, pageLength, pageCount, recordLength,
physicalRecordLength, recordCount, fileLength,
RecordType::Fixed, false, 0);
Expand Down Expand Up @@ -1824,6 +1840,58 @@ TEST_F(BtrieveDriverTest, ACSInsertDuplicateFails) {
std::make_pair(BtrieveError::DuplicateKeyValue, 0u));
}

TEST_F(BtrieveDriverTest, FloatSeekByKey) {
SqliteDatabase *database = new SqliteDatabase(SQLITE_OPEN_MEMORY);
BtrieveDriver driver(database);

auto recordLoader =
database->create(_TEXT("unused.db"), createACSBtrieveDatabase());
recordLoader->onRecordsComplete();

ASSERT_EQ(database->insertRecord(std::basic_string_view<uint8_t>(
createRecord("Sysop", 1.0f, 2.0).data(), ACS_RECORD_LENGTH)),
std::make_pair(BtrieveError::Success, 1u));

ASSERT_EQ(
database->insertRecord(std::basic_string_view<uint8_t>(
createRecord("Paladine", -1.0f, -2.0).data(), ACS_RECORD_LENGTH)),
std::make_pair(BtrieveError::Success, 2u));

float fkey = -1.0f;
std::basic_string_view<uint8_t> key(reinterpret_cast<const uint8_t *>(&fkey),
sizeof(fkey));

ASSERT_EQ(driver.performOperation(1, key, OperationCode::QueryEqual),
BtrieveError::Success);
auto data = driver.getRecord();
ASSERT_TRUE(data.first);
ASSERT_EQ(driver.getPosition(), 2);
ASSERT_EQ(data.second.getData().size(), ACS_RECORD_LENGTH);
ASSERT_EQ(memcmp(data.second.getData().data() + 2, "Paladine", 8), 0);
ASSERT_EQ(*reinterpret_cast<const float *>(data.second.getData().data() + 64),
-1.0f);
ASSERT_EQ(
*reinterpret_cast<const double *>(data.second.getData().data() + 68),
-2.0);

double dkey = 2.0f;
key = std::basic_string_view<uint8_t>(
reinterpret_cast<const uint8_t *>(&dkey), sizeof(dkey));

ASSERT_EQ(driver.performOperation(2, key, OperationCode::QueryEqual),
BtrieveError::Success);
data = driver.getRecord();
ASSERT_TRUE(data.first);
ASSERT_EQ(driver.getPosition(), 1);
ASSERT_EQ(data.second.getData().size(), ACS_RECORD_LENGTH);
ASSERT_EQ(memcmp(data.second.getData().data() + 2, "Sysop", 6), 0);
ASSERT_EQ(*reinterpret_cast<const float *>(data.second.getData().data() + 64),
1.0f);
ASSERT_EQ(
*reinterpret_cast<const double *>(data.second.getData().data() + 68),
2.0);
}

static BtrieveDatabase createKeylessBtrieveDatabase() {
std::vector<Key> keys;
uint16_t pageLength = 512;
Expand Down
16 changes: 16 additions & 0 deletions btrieve/Key.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ std::string Key::getSqliteColumnSql() const {
} else if (type == KeyDataType::String || type == KeyDataType::Lstring ||
type == KeyDataType::Zstring || type == KeyDataType::OldAscii) {
sql << "TEXT";
} else if (type == KeyDataType::Float) {
sql << "REAL";
} else {
sql << "BLOB";
}
Expand Down Expand Up @@ -188,6 +190,20 @@ BindableValue Key::keyDataToSqliteObject(
case KeyDataType::Zstring:
case KeyDataType::OldAscii:
return BindableValue(extractNullTerminatedString(modifiedKeyData));
case KeyDataType::Float:
switch (getPrimarySegment().getLength()) {
case 4:
return BindableValue(static_cast<double>(
*reinterpret_cast<float *>(modifiedKeyData.data())));
case 8:
return BindableValue(
*reinterpret_cast<double *>(modifiedKeyData.data()));
default:
// should never happen since we verify on db creation
throw BtrieveException("Float key not 4/8 bytes");
break;
}
break;
default:
return BindableValue(modifiedKeyData);
}
Expand Down
5 changes: 5 additions & 0 deletions btrieve/KeyDefinition.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ class KeyDefinition {
throw BtrieveException("Key %d requires ACS, but none was provided",
number);
}

if (dataType_ == KeyDataType::Float && (length_ != 8 && length_ != 4)) {
throw BtrieveException(
"Key was specified as a float but isn't size 4/8 bytes");
}
}

KeyDefinition(const KeyDefinition &keyDefinition)
Expand Down
44 changes: 40 additions & 4 deletions btrieve/Key_test.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "AttributeMask.h"
#include "Key.h"
#include "Key.h"

#include "AttributeMask.h"
#include "gtest/gtest.h"

using namespace btrieve;
Expand Down Expand Up @@ -221,7 +222,8 @@ static std::vector<ParameterizedKeyDataType> createKeyDataTypeData() {
{KeyDataType::String}, {KeyDataType::Lstring},
{KeyDataType::Zstring}, {KeyDataType::OldAscii},
{KeyDataType::Integer}, {KeyDataType::Unsigned},
{KeyDataType::UnsignedBinary}, {KeyDataType::OldBinary}};
{KeyDataType::UnsignedBinary}, {KeyDataType::OldBinary},
{KeyDataType::Float}};
}

INSTANTIATE_TEST_CASE_P(Key, ParameterizedKeyDataTypeFixture,
Expand Down Expand Up @@ -322,4 +324,38 @@ createACSReplacementMultipleKey() {
INSTANTIATE_TEST_CASE_P(Key, ParameterizedACSReplacementMultipleKeyFixture,
::testing::ValuesIn(createACSReplacementMultipleKey()));

} // namespace
TEST(FloatKeys) {
KeyDefinition keyDefinition(0, sizeof(float), 0, KeyDataType::Float,
UseExtendedDataType, false, 0, 0, 0, "",
std::vector<char>());

Key key(&keyDefinition, 1);

float value = 2.435f;

auto actual = key
.keyDataToSqliteObject(std::basic_string_view<uint8_t>(
reinterpret_cast<uint8_t *>(&value), sizeof(value)))
.getDoubleValue();

EXPECT_EQ(actual, value);
}

TEST(DoubleKeys) {
KeyDefinition keyDefinition(0, sizeof(double), 0, KeyDataType::Float,
UseExtendedDataType, false, 0, 0, 0, "",
std::vector<char>());

Key key(&keyDefinition, 1);

double value = 2.435f;

auto actual = key
.keyDataToSqliteObject(std::basic_string_view<uint8_t>(
reinterpret_cast<uint8_t *>(&value), sizeof(value)))
.getDoubleValue();

EXPECT_EQ(actual, value);
}

} // namespace
5 changes: 3 additions & 2 deletions btrieve/Reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@

namespace btrieve {
class Reader {
public:
public:
virtual ~Reader() = default;

virtual bool read() = 0;
virtual int getInt32(unsigned int columnOrdinal) const = 0;
virtual int64_t getInt64(unsigned int columnOrdinal) const = 0;
virtual double getDouble(unsigned int columnOrdinal) const = 0;
virtual bool getBoolean(unsigned int columnOrdinal) const = 0;
virtual bool isDBNull(unsigned int columnOrdinal) const = 0;
virtual std::string getString(unsigned int columnOrdinal) const = 0;
virtual std::vector<uint8_t> getBlob(unsigned int columnOrdinal) const = 0;
virtual BindableValue getBindableValue(unsigned int columnOrdinal) const = 0;
};
} // namespace btrieve
} // namespace btrieve
#endif
27 changes: 13 additions & 14 deletions btrieve/SqliteDatabase.cc
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,14 @@ void SqliteDatabase::loadSqliteMetadata(std::string &acsName,
std::copy(readAcs.begin(), readAcs.end(), std::back_inserter(acs));
}

// reserved for future upgrade paths
/*
var version = reader.GetInt32(2);
if (version != CURRENT_VERSION){
UpgradeDatabaseFromVersion(version);
uint32_t version = reader->getInt32(2);
if (version != CURRENT_VERSION) {
upgradeDatabaseFromVersion(version);
}
*/
}

void SqliteDatabase::upgradeDatabaseFromVersion(uint32_t currentVersion) {
// no need for version upgrades - yet
}

void SqliteDatabase::loadSqliteKeys(const std::string &acsName,
Expand All @@ -171,23 +172,21 @@ void SqliteDatabase::loadSqliteKeys(const std::string &acsName,

keys.resize(numKeys);

SqlitePreparedStatement command(
database,
"SELECT number, segment, attributes, data_type, offset, length "
"FROM keys_t ORDER BY number, segment");
SqlitePreparedStatement command(database,
"SELECT number, segment, attributes, "
"data_type, offset, length, null_value "
"FROM keys_t ORDER BY number, segment");
std::unique_ptr<SqliteReader> reader = command.executeReader();

unsigned int segmentIndex = 0;
while (reader->read()) {
unsigned int number = reader->getInt32(0);

uint8_t nullValue = 0; // TODO why isn't this in database?

KeyDefinition keyDefinition(
number, reader->getInt32(5), reader->getInt32(4),
(KeyDataType)reader->getInt32(3), reader->getInt32(2),
reader->getBoolean(1), reader->getBoolean(1) ? number : 0, segmentIndex,
nullValue, acsName, acs);
reader->getInt32(6), acsName, acs);

keys[number].addSegment(keyDefinition);
}
Expand All @@ -214,7 +213,7 @@ std::unique_ptr<RecordLoader> SqliteDatabase::create(
keys = database.getKeys();

createSqliteMetadataTable(database);
createSqliteKeysTable(database);
(database);
createSqliteDataTable(database);
createSqliteDataIndices(database);
createSqliteTriggers(database);
Expand Down
2 changes: 2 additions & 0 deletions btrieve/SqliteDatabase.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ class SqliteDatabase : public SqlDatabase {

BtrieveError nextReader(Query *query, CursorDirection cursorDirection);

void upgradeDatabaseFromVersion(uint32_t currentVersion);

unsigned int openFlags;
mutable std::unordered_map<std::string, SqlitePreparedStatement>
preparedStatements;
Expand Down
52 changes: 28 additions & 24 deletions btrieve/SqliteReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace btrieve {
class SqliteReader : public Reader {
public:
public:
virtual ~SqliteReader() {}

virtual bool read() override {
Expand All @@ -29,6 +29,10 @@ class SqliteReader : public Reader {
return sqlite3_column_int64(statement, columnOrdinal);
}

virtual double getDouble(unsigned int columnOrdinal) const override {
return sqlite3_column_double(statement, columnOrdinal);
}

virtual bool getBoolean(unsigned int columnOrdinal) const override {
return getInt32(columnOrdinal) != 0;
}
Expand All @@ -48,8 +52,8 @@ class SqliteReader : public Reader {
}
}

virtual std::vector<uint8_t>
getBlob(unsigned int columnOrdinal) const override {
virtual std::vector<uint8_t> getBlob(
unsigned int columnOrdinal) const override {
int bytes = sqlite3_column_bytes(statement, columnOrdinal);
std::vector<uint8_t> ret(bytes);
if (bytes > 0) {
Expand All @@ -59,39 +63,39 @@ class SqliteReader : public Reader {
return ret;
}

virtual BindableValue
getBindableValue(unsigned int columnOrdinal) const override {
virtual BindableValue getBindableValue(
unsigned int columnOrdinal) const override {
int bytes;
const uint8_t *data;
int columnType = sqlite3_column_type(statement, columnOrdinal);

switch (columnType) {
case SQLITE_INTEGER:
return BindableValue(
static_cast<int64_t>(sqlite3_column_int64(statement, columnOrdinal)));
case SQLITE_FLOAT:
return BindableValue(sqlite3_column_double(statement, columnOrdinal));
case SQLITE_TEXT:
return BindableValue(reinterpret_cast<const char *>(
sqlite3_column_text(statement, columnOrdinal)));
case SQLITE_BLOB:
bytes = sqlite3_column_bytes(statement, columnOrdinal);
data = reinterpret_cast<const uint8_t *>(
sqlite3_column_blob(statement, columnOrdinal));
return BindableValue(std::basic_string_view<uint8_t>(data, bytes));
case SQLITE_NULL:
default:
return BindableValue();
case SQLITE_INTEGER:
return BindableValue(static_cast<int64_t>(
sqlite3_column_int64(statement, columnOrdinal)));
case SQLITE_FLOAT:
return BindableValue(sqlite3_column_double(statement, columnOrdinal));
case SQLITE_TEXT:
return BindableValue(reinterpret_cast<const char *>(
sqlite3_column_text(statement, columnOrdinal)));
case SQLITE_BLOB:
bytes = sqlite3_column_bytes(statement, columnOrdinal);
data = reinterpret_cast<const uint8_t *>(
sqlite3_column_blob(statement, columnOrdinal));
return BindableValue(std::basic_string_view<uint8_t>(data, bytes));
case SQLITE_NULL:
default:
return BindableValue();
}
}

private:
private:
friend class SqlitePreparedStatement;

SqliteReader(sqlite3_stmt *statement_) : statement(statement_){};
SqliteReader(sqlite3_stmt *statement_) : statement(statement_) {};

// can't use a shared_ptr here since sqlite3_stmt is an incomplete type
sqlite3_stmt *statement;
};
} // namespace btrieve
} // namespace btrieve
#endif

0 comments on commit 6af92b9

Please sign in to comment.