Skip to content

Commit

Permalink
Merge pull request #1628 from Expensify/tyler-named-sqresult-columns
Browse files Browse the repository at this point in the history
Allow retrieving value by name
  • Loading branch information
iwiznia authored Dec 20, 2023
2 parents b55a97d + 541004f commit 34deae2
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 9 deletions.
61 changes: 59 additions & 2 deletions libstuff/SQResult.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,63 @@
#include <libstuff/libstuff.h>
#include "SQResult.h"

SQResultRow::SQResultRow(SQResult& result, size_t count) : vector<string>(count), result(&result) {
}

SQResultRow::SQResultRow() : vector<string>(), result(nullptr) {
}

void SQResultRow::push_back(const string& s) {
vector<string>::push_back(s);
}

string& SQResultRow::operator[](const size_t& key) {
return vector<string>::operator[](key);
}
const string& SQResultRow::operator[](const size_t& key) const {
return vector<string>::operator[](key);
}

SQResultRow& SQResultRow::operator=(const SQResultRow& other) {
vector<string>::operator=(other);
result = other.result;
return *this;
}

string& SQResultRow::operator[](const string& key) {
if (result) {
for (size_t i = 0; i < result->headers.size(); i++) {

// If the headers have more entries than the row (they really shouldn't), break early instead of segfaulting.
if (i >= size()) {
break;
}

if (result->headers[i] == key) {
return (*this)[i];
}
}
}
throw out_of_range("No column named " + key);
}

const string& SQResultRow::operator[](const string& key) const {
if (result) {
for (size_t i = 0; i < result->headers.size(); i++) {

// If the headers have more entries than the row (they really shouldn't), break early instead of segfaulting.
if (i >= size()) {
break;
}

if (result->headers[i] == key) {
return (*this)[i];
}
}
}
throw out_of_range("No column named " + key);
}

string SQResult::serializeToJSON() const {
// Just output as a simple object
// **NOTE: This probably isn't super fast, but could be easily optimized
Expand Down Expand Up @@ -90,15 +147,15 @@ void SQResult::clear() {
rows.clear();
}

vector<string>& SQResult::operator[](size_t rowNum) {
SQResultRow& SQResult::operator[](size_t rowNum) {
try {
return rows.at(rowNum);
} catch (const out_of_range& e) {
STHROW("Out of range");
}
}

const vector<string>& SQResult::operator[](size_t rowNum) const {
const SQResultRow& SQResult::operator[](size_t rowNum) const {
try {
return rows.at(rowNum);
} catch (const out_of_range& e) {
Expand Down
22 changes: 19 additions & 3 deletions libstuff/SQResult.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,28 @@
#include <string>
#include <vector>
using namespace std;
class SQResult;

class SQResultRow : public vector<string> {
public:
SQResultRow();
SQResultRow(SQResult& result, size_t count = 0);
void push_back(const string& s);
string& operator[](const size_t& key);
const string& operator[](const size_t& key) const;
string& operator[](const string& key);
const string& operator[](const string& key) const;
SQResultRow& operator=(const SQResultRow& other);

private:
SQResult* result = nullptr;
};

class SQResult {
public:
// Attributes
vector<string> headers;
vector<vector<string>> rows;
vector<SQResultRow> rows;

// Accessors
bool empty() const;
Expand All @@ -18,8 +34,8 @@ class SQResult {
void clear();

// Operators
vector<string>& operator[](size_t rowNum);
const vector<string>& operator[](size_t rowNum) const;
SQResultRow& operator[](size_t rowNum);
const SQResultRow& operator[](size_t rowNum) const;

// Serializers
string serializeToJSON() const;
Expand Down
2 changes: 1 addition & 1 deletion libstuff/libstuff.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2592,7 +2592,7 @@ int SQuery(sqlite3* db, const char* e, const string& sql, SQResult& result, int6
}

if (error == SQLITE_ROW) {
result.rows.emplace_back(vector<string>(numColumns));
result.rows.emplace_back(SQResultRow(result, numColumns));
for (int i = 0; i < numColumns; i++) {
int colType = sqlite3_column_type(preparedStatement, i);
switch (colType) {
Expand Down
2 changes: 1 addition & 1 deletion sqlitecluster/SQLite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,7 @@ int SQLite::commit(const string& description, function<void()>* preCheckpointCal
sqlite3_file *pWal = 0;
sqlite3_int64 sz = 0;
sqlite3_file_control(_db, "main", SQLITE_FCNTL_JOURNAL_POINTER, &pWal);
if (pWal) {
if (pWal && pWal->pMethods) {
// This is not set for HC-tree DBs.
pWal->pMethods->xFileSize(pWal, &sz);
}
Expand Down
2 changes: 1 addition & 1 deletion test/lib/BedrockTester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ bool BedrockTester::readDB(const string& query, SQResult& result, bool online)
list<string> rows = SParseJSONArray(row0);
for (const string& rowStr : rows) {
list<string> vals = SParseJSONArray(rowStr);
vector<string> row;
SQResultRow row(result);
for (auto& v : vals) {
row.push_back(v);
}
Expand Down
48 changes: 47 additions & 1 deletion test/tests/LibStuffTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

#include <libstuff/libstuff.h>
#include <libstuff/SData.h>
#include <libstuff/SQResult.h>
#include <libstuff/SRandom.h>
#include <sqlitecluster/SQLite.h>
#include <test/lib/BedrockTester.h>

struct LibStuff : tpunit::TestFixture {
Expand All @@ -29,7 +31,9 @@ struct LibStuff : tpunit::TestFixture {
TEST(LibStuff::testHexConversion),
TEST(LibStuff::testBase32Conversion),
TEST(LibStuff::testContains),
TEST(LibStuff::testFirstOfMonth))
TEST(LibStuff::testFirstOfMonth),
TEST(LibStuff::SQResultTest)
)
{ }

void testEncryptDecrpyt() {
Expand Down Expand Up @@ -633,4 +637,46 @@ struct LibStuff : tpunit::TestFixture {
ASSERT_EQUAL(SFirstOfMonth(timeStamp4, -13), "2019-06-01");
ASSERT_EQUAL(SFirstOfMonth(timeStamp4, -25), "2018-06-01");
}

void SQResultTest() {
SQLite db(":memory:", 1000, 1000, 1);
db.beginTransaction(SQLite::TRANSACTION_TYPE::EXCLUSIVE);
db.write("CREATE TABLE testTable(id INTEGER PRIMARY KEY, name STRING, value STRING, created DATE);");
db.write ("INSERT INTO testTable VALUES(1, 'name1', 'value1', '2023-12-20');");
db.write ("INSERT INTO testTable VALUES(2, 'name2', 'value2', '2023-12-21');");
db.write ("INSERT INTO testTable VALUES(3, 'name3', 'value3', '2023-12-22');");
db.prepare();
db.commit();

db.beginTransaction(SQLite::TRANSACTION_TYPE::SHARED);
SQResult result;
db.read("SELECT name, value FROM testTable ORDER BY id;", result);
db.rollback();

// All our names make sense?
ASSERT_EQUAL(result[0]["name"], "name1");
ASSERT_EQUAL(result[1]["name"], "name2");
ASSERT_EQUAL(result[2]["name"], "name3");

// All our values make sense?
ASSERT_EQUAL(result[0]["value"], "value1");
ASSERT_EQUAL(result[1]["value"], "value2");
ASSERT_EQUAL(result[2]["value"], "value3");

// Validate our exception handling.
bool threw = false;
try {
string s = result[0]["notacolumn"];
} catch (const out_of_range& e) {
threw = true;
}
ASSERT_TRUE(threw);

// Test aliased names.
db.beginTransaction(SQLite::TRANSACTION_TYPE::SHARED);
result.clear();
db.read("SELECT name as coco, value FROM testTable ORDER BY id;", result);
db.rollback();
ASSERT_EQUAL(result[0]["coco"], "name1");
}
} __LibStuff;

0 comments on commit 34deae2

Please sign in to comment.