diff --git a/Makefile b/Makefile index 3c4bf15..c08b1c2 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CXX = g++ CXXFLAGS = -std=c++17 -I./src -I./test -SRC = $(wildcard src/app/*.cpp src/file/*.cpp src/tx/*.cpp src/tx/LogRecord/*.cpp src/logging/*.cpp src/buffer/*.cpp src/record/*.cpp src/scan/*.cpp src/meta/*.cpp src/indexing/*.cpp src/plan/*.cpp src/parse/*.cpp) +SRC = $(wildcard src/app/*.cpp src/file/*.cpp src/tx/*.cpp src/tx/LogRecord/*.cpp src/logging/*.cpp src/buffer/*.cpp src/record/*.cpp src/scan/*.cpp src/meta/*.cpp src/indexing/*.cpp src/plan/*.cpp src/parse/*.cpp src/interface/*.cpp) TESTS = $(wildcard test/*.cpp) OUT = build/test_runner diff --git a/src/app/SampleDB.cpp b/src/app/SampleDB.cpp index 5aa5dff..2b3194b 100644 --- a/src/app/SampleDB.cpp +++ b/src/app/SampleDB.cpp @@ -22,7 +22,7 @@ namespace app { _mdm = std::make_unique(isNew, *txPtr); auto qp = std::make_unique(_mdm.get()); - auto up = std::make_unique(_mdm.get()); + auto up = std::make_unique(_mdm.get()); _planner = std::make_unique(std::move(qp), std::move(up)); txPtr->commit(); } @@ -50,6 +50,10 @@ namespace app { return *_bm; } + meta::MetaDataMgr& SampleDB::getMetaDataManager() { + return *_mdm; + } + plan::Planner& SampleDB::getPlanner() { return *_planner; } diff --git a/src/app/SampleDB.h b/src/app/SampleDB.h index aedf2a6..122318d 100644 --- a/src/app/SampleDB.h +++ b/src/app/SampleDB.h @@ -4,6 +4,7 @@ #include #include #include "file/FileMgr.h" +#include "indexing/IndexUpdatePlanner.h" #include "logging/LogMgr.h" #include "buffer/BufferMgr.h" #include "tx/Transaction.h" @@ -11,7 +12,6 @@ #include "plan/Planner.h" #include "plan/BasicQueryPlanner.h" #include "plan/UpdatePlanner.h" -#include "plan/BasicUpdatePlanner.h" namespace app { class SampleDB { @@ -24,6 +24,7 @@ namespace app { file::FileMgr& fileMgr(); logging::LogMgr& logMgr(); buffer::BufferMgr& bufferMgr(); + meta::MetaDataMgr& getMetaDataManager(); plan::Planner& getPlanner(); std::unique_ptr newTransaction(); private: diff --git a/src/indexing/BTreePage.cpp b/src/indexing/BTreePage.cpp index f87b0bb..926c9c7 100644 --- a/src/indexing/BTreePage.cpp +++ b/src/indexing/BTreePage.cpp @@ -89,7 +89,7 @@ namespace indexing { // Method called only from BTreeLeaf record::RID BTreePage::getDataRid(int slot) const { - return record::RID(getInt(slot, "block"), getInt(slot, "slot")); + return record::RID(getInt(slot, "block"), getInt(slot, "id")); } void BTreePage::insertLeaf(int slot, const scan::Constant& dataval, const record::RID& datarid) { diff --git a/src/interface/Connection.cpp b/src/interface/Connection.cpp new file mode 100644 index 0000000..a64d57d --- /dev/null +++ b/src/interface/Connection.cpp @@ -0,0 +1,45 @@ +#include "interface/Connection.h" + +namespace interface { + Connection::Connection(std::unique_ptr db) + : _db(std::move(db)), _transaction(_db->newTransaction()), _planner(_db->getPlanner()) {} + + std::unique_ptr Connection::createStatement() { + try { + auto ptr = std::make_unique(this, _planner); + return ptr; + } catch (const std::exception& e) { + throw std::runtime_error("Failed to create statement: " + std::string(e.what())); + } + } + + void Connection::close() { + try { + commit(); + } catch (const std::exception& e) { + throw std::runtime_error("Failed to close connection: " + std::string(e.what())); + } + } + + void Connection::commit() { + try { + _transaction->commit(); + _transaction = _db->newTransaction(); + } catch (const std::exception& e) { + throw std::runtime_error("Failed to commit transaction: " + std::string(e.what())); + } + } + + void Connection::rollback() { + try { + _transaction->rollback(); + _transaction = _db->newTransaction(); + } catch (const std::exception& e) { + throw std::runtime_error("Failed to rollback transaction: " + std::string(e.what())); + } + } + + tx::Transaction& Connection::getTransaction() { + return *_transaction; + } +} diff --git a/src/interface/Connection.h b/src/interface/Connection.h new file mode 100644 index 0000000..5224d2f --- /dev/null +++ b/src/interface/Connection.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include "app/SampleDB.h" +#include "plan/Planner.h" +#include "interface/Statement.h" +#include "tx/Transaction.h" + +namespace interface { + class Statement; + + class Connection { + public: + Connection(std::unique_ptr db); + std::unique_ptr createStatement(); + void close(); + void commit(); + void rollback(); + tx::Transaction& getTransaction(); + private: + std::unique_ptr _db; + std::unique_ptr _transaction; + plan::Planner& _planner; + }; +} diff --git a/src/interface/Driver.cpp b/src/interface/Driver.cpp new file mode 100644 index 0000000..b12fd47 --- /dev/null +++ b/src/interface/Driver.cpp @@ -0,0 +1,10 @@ +#include "interface/Driver.h" + +namespace interface { + std::unique_ptr Driver::connect(const std::string& dbName) { + auto db = std::make_unique(dbName); + auto connection = std::make_unique(std::move(db)); + + return connection; + } +} diff --git a/src/interface/Driver.h b/src/interface/Driver.h new file mode 100644 index 0000000..0c7e091 --- /dev/null +++ b/src/interface/Driver.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +#include "app/SampleDB.h" +#include "interface/Connection.h" + +namespace interface { + class Driver { + public: + std::unique_ptr connect(const std::string& dbName); + }; +} diff --git a/src/interface/MetaData.cpp b/src/interface/MetaData.cpp new file mode 100644 index 0000000..ceaa1ad --- /dev/null +++ b/src/interface/MetaData.cpp @@ -0,0 +1,41 @@ +#include "MetaData.h" + +namespace interface { + MetaData::MetaData(const record::Schema& schema) : _schema(schema) {} + + int MetaData::getColumnCount() const { + try { + return _schema.fieldNames().size(); + } catch (const std::exception& e) { + throw std::runtime_error("Failed to get column count: " + std::string(e.what())); + } + } + + std::string MetaData::getColumnName(int column) const { + try { + return _schema.fieldNames().at(column - 1); + } catch (const std::exception& e) { + throw std::runtime_error("Failed to get column name: " + std::string(e.what())); + } + } + + int MetaData::getColumnType(int column) const { + try { + std::string fieldName = getColumnName(column); + return _schema.fieldType(fieldName); + } catch (const std::exception& e) { + throw std::runtime_error("Failed to get column type: " + std::string(e.what())); + } + } + + int MetaData::getColumnDisplaySize(int column) const { + try { + std::string fieldName = getColumnName(column); + int fieldType = _schema.fieldType(fieldName); + int fieldLength = (fieldType == record::Schema::INTEGER) ? 6 : _schema.fieldLength(fieldName); + return fieldName.length() > fieldLength ? fieldName.length() + 1: fieldLength + 1; + } catch (const std::exception& e) { + throw std::runtime_error("Failed to get column display size: " + std::string(e.what())); + } + } +} diff --git a/src/interface/MetaData.h b/src/interface/MetaData.h new file mode 100644 index 0000000..7f7aeef --- /dev/null +++ b/src/interface/MetaData.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +#include "record/Schema.h" + +namespace interface { + class MetaData { + public: + MetaData(const record::Schema& schema); + int getColumnCount() const; + std::string getColumnName(int column) const; + int getColumnType(int column) const; + int getColumnDisplaySize(int column) const; + private: + record::Schema _schema; + }; +} diff --git a/src/interface/ResultSet.cpp b/src/interface/ResultSet.cpp new file mode 100644 index 0000000..16a81d3 --- /dev/null +++ b/src/interface/ResultSet.cpp @@ -0,0 +1,52 @@ +#include "interface/ResultSet.h" + +namespace interface { + ResultSet::ResultSet(plan::Plan* plan, Connection* connection) : _scan(plan->open()), _schema(plan->schema()), _connection(connection) {} + + bool ResultSet::next() { + try { + return _scan->next(); + } catch (const std::exception& e) { + _connection->rollback(); + std::runtime_error("Failed to move to next record: " + std::string(e.what())); + return false; + } + } + + int ResultSet::getInt(std::string& fieldName) const { + try { + boost::algorithm::to_lower(fieldName); + return _scan->getInt(fieldName); + } catch (const std::exception& e) { + _connection->rollback(); + std::runtime_error("Failed to get integer value for field '" + fieldName + "': " + std::string(e.what())); + return -1; + } + } + + std::string ResultSet::getString(std::string& fieldName) const { + try { + boost::algorithm::to_lower(fieldName); + return _scan->getString(fieldName); + } catch (const std::exception& e) { + _connection->rollback(); + std::runtime_error("Failed to get string value for field '" + fieldName + "': " + std::string(e.what())); + return ""; + } + } + + MetaData ResultSet::getMetaData() const { + MetaData metaData(_schema); + return metaData; + } + + void ResultSet::close() { + try { + _scan->close(); + _connection->commit(); + } catch (const std::exception& e) { + _scan->close(); + _connection->commit(); + } + } +} diff --git a/src/interface/ResultSet.h b/src/interface/ResultSet.h new file mode 100644 index 0000000..3c62f88 --- /dev/null +++ b/src/interface/ResultSet.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +#include "Connection.h" +#include "MetaData.h" +#include "plan/Plan.h" +#include "scan/Scan.h" +#include "record/Schema.h" + +namespace interface { + class Connection; + + class ResultSet { + public: + ResultSet(plan::Plan* plan, Connection* connection); + bool next(); + int getInt(std::string& fieldName) const; + std::string getString(std::string& fieldName) const; + MetaData getMetaData() const; + void close(); + private: + std::shared_ptr _scan; + record::Schema _schema; + Connection* _connection; + }; +} diff --git a/src/interface/Statement.cpp b/src/interface/Statement.cpp new file mode 100644 index 0000000..58256d9 --- /dev/null +++ b/src/interface/Statement.cpp @@ -0,0 +1,34 @@ +#include "Statement.h" + +namespace interface { + Statement::Statement(Connection* connection, plan::Planner& planner) : _connection(connection), _planner(planner) {} + + ResultSet Statement::executeQuery(const std::string& query) { + try { + tx::Transaction& tx = _connection->getTransaction(); + auto plan = _planner.createQueryPlan(query, &tx); + ResultSet rs(plan.get(), _connection); + return rs; + } catch (const std::exception& e) { + _connection->rollback(); + throw std::runtime_error("Failed to execute query: " + std::string(e.what())); + } + } + + int Statement::executeUpdate(const std::string& cmd) { + try { + tx::Transaction& tx = _connection->getTransaction(); + int result = _planner.executeUpdate(cmd, &tx); + _connection->commit(); + return result; + } catch (const std::exception& e) { + _connection->rollback(); + std::runtime_error("Failed to execute update: " + std::string(e.what())); + return 0; + } + } + + void Statement::close() { + return; + } +} diff --git a/src/interface/Statement.h b/src/interface/Statement.h new file mode 100644 index 0000000..6c6122f --- /dev/null +++ b/src/interface/Statement.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include "Connection.h" +#include "interface/ResultSet.h" +#include "plan/Planner.h" + +namespace interface { + class Connection; + + class ResultSet; + + class Statement { + public: + Statement(Connection* connection, plan::Planner& planner); + ResultSet executeQuery(const std::string& query); + int executeUpdate(const std::string& cmd); + void close(); + private: + Connection* _connection; + plan::Planner& _planner; + }; +} diff --git a/test/indexRetrievalTest.cpp b/test/indexRetrievalTest.cpp new file mode 100644 index 0000000..fdfb810 --- /dev/null +++ b/test/indexRetrievalTest.cpp @@ -0,0 +1,142 @@ +#include "catch.hpp" + +#include +#include +#include +#include + +#include "app/SampleDB.h" +#include "interface/Connection.h" +#include "interface/Driver.h" +#include "interface/Statement.h" +#include "interface/ResultSet.h" +#include "plan/Plan.h" +#include "plan/TablePlan.h" +#include "plan/Planner.h" +#include "scan/Scan.h" +#include "tx/Transaction.h" + +namespace { + void createDB(const std::string& dbname) { + try { + auto driver = std::make_unique(); + auto conn = driver->connect(dbname); + auto stmt = conn->createStatement(); + + std::string s = + "create table student ( sid int, sname varchar ( 10 ), majorid int, gradyear int )"; + stmt->executeUpdate(s); + std::cout << "student table created" << std::endl; + + s = "create index majorid on student ( majorid )"; + stmt->executeUpdate(s); + std::cout << s << " created" << std::endl; + + s = "insert into student ( sid, sname, majorid, gradyear ) values "; + std::vector studvals = { + "( 1, 'joe', 10, 2021 )", "( 2, 'amy', 20, 2020 )", + "( 3, 'max', 10, 2022 )", "( 4, 'sue', 20, 2022 )", + "( 5, 'bob', 30, 2020 )", "( 6, 'kim', 20, 2020 )", + "( 7, 'art', 30, 2021 )", "( 8, 'pat', 20, 2019 )", + "( 9, 'lee', 10, 2021 )"}; + + for (const auto& val : studvals) { + stmt->executeUpdate(s + val); + } + std::cout << "student data inserted" << std::endl; + + s = "create table dept ( did int, dname varchar ( 8 ) )"; + stmt->executeUpdate(s); + std::cout << "dept table created" << std::endl; + + s = "insert into dept ( did, dname ) values "; + std::vector deptvals = {"( 10, 'compsci' )", "( 20, 'math' )", "( 30, 'drama' )"}; + + for (const auto& val : deptvals) { + stmt->executeUpdate(s + val); + } + std::cout << "dept data inserted" << std::endl; + + s = "create table section ( sectid int, courseid int, prof varchar ( 8 ), yearoffered int )"; + stmt->executeUpdate(s); + std::cout << "section table created" << std::endl; + + s = "insert into section ( sectid, courseid, prof, yearoffered ) values "; + std::vector sectvals = { + "( 13, 12, 'turing', 2018 )", "( 23, 12, 'turing', 2019 )", + "( 33, 32, 'newton', 2019 )", "( 43, 32, 'einstein', 2017 )", + "( 53, 62, 'brando', 2018 )"}; + + for (const auto& val : sectvals) { + stmt->executeUpdate(s + val); + } + std::cout << "sect data inserted" << std::endl; + + s = "create table enroll ( eId int, studentid int, sectionid int, grade varchar ( 2 ) )"; + stmt->executeUpdate(s); + std::cout << "enroll table created" << std::endl; + + s = "insert into enroll ( eid, studentid, sectionid, grade ) values "; + std::vector enrollvals = { + "( 14, 1, 13, 'A' )", "( 24, 1, 43, 'C' )", "( 34, 2, 43, 'B+' )", + "( 44, 4, 33, 'B' )", "( 54, 4, 53, 'A' )", "( 64, 6, 53, 'A' )"}; + + for (const auto& val : enrollvals) { + stmt->executeUpdate(s + val); + } + std::cout << "enroll data inserted" << std::endl; + } catch (std::exception& e) { + std::cout << e.what() << std::endl; + } + } + + TEST_CASE("IndexRetrievalTest/Main", "[IndexRetrievalTest]") { + std::string fileName = "indexretrievalTest"; + std::filesystem::remove_all(fileName); // Clean start + + createDB(fileName); + + app::SampleDB db(fileName); + auto transaction = db.newTransaction(); + auto& mdm = db.getMetaDataManager(); + + // open a scan on the data table + auto studentplan = std::static_pointer_cast( + std::make_shared(transaction.get(), "student", &mdm)); + auto studentscan = std::static_pointer_cast(studentplan->open()); + int rowcount = 0; + for (studentscan->beforeFirst(); studentscan->next(); ) ++rowcount; + REQUIRE(rowcount == 9); + + // open the index on majorid + std::map indexes = + mdm.getIndexInfo("student", *transaction); + REQUIRE(indexes.count("majorid") == 1); + + meta::IndexInfo ii = indexes["majorid"]; + auto idx = ii.open(); + + // retrieve all index records having a dataval of 20. + scan::Constant c(20); + idx->beforeFirst(c); + + std::vector ids; + while (idx->next()) { + record::RID datarid = idx->getDataRid(); + studentscan->moveToRid(datarid); + ids.push_back(studentscan->getInt("sid")); + } + + // close resources + idx->close(); + studentscan->close(); + transaction->commit(); + + // We expect these four students have majorid=20: amy, sue, kim, pat. + std::vector expected = {2, 4, 6, 8}; + std::sort(ids.begin(), ids.end()); + std::sort(expected.begin(), expected.end()); + + REQUIRE(ids == expected); + } +} diff --git a/test/indexUpdateTest.cpp b/test/indexUpdateTest.cpp new file mode 100644 index 0000000..2f412b7 --- /dev/null +++ b/test/indexUpdateTest.cpp @@ -0,0 +1,164 @@ +#include "catch.hpp" + +#include +#include +#include +#include +#include + +#include "app/SampleDB.h" +#include "interface/Connection.h" +#include "interface/Driver.h" +#include "interface/Statement.h" +#include "interface/ResultSet.h" +#include "plan/Plan.h" +#include "plan/TablePlan.h" +#include "plan/Planner.h" +#include "scan/Scan.h" +#include "tx/Transaction.h" + +namespace { + void createDB(const std::string& dbname) { + try { + auto driver = std::unique_ptr(); + auto conn = driver->connect(dbname); + auto stmt = conn->createStatement(); + + std::string s = "create table student ( sid int, sname varchar ( 10 ), majorid int, gradyear int )"; + stmt->executeUpdate(s); + std::cout << "student table created" << std::endl; + + s = "create index majorid on student ( majorid )"; + stmt->executeUpdate(s); + std::cout << s << " created" << std::endl; + + s = "insert into student ( sid, sname, majorid, gradyear ) values "; + std::vector studvals = { + "( 1, 'joe', 10, 2021 )", + "( 2, 'amy', 20, 2020 )", + "( 3, 'max', 10, 2022 )", + "( 4, 'sue', 20, 2022 )", + "( 5, 'bob', 30, 2020 )", + "( 6, 'kim', 20, 2020 )", + "( 7, 'art', 30, 2021 )", + "( 8, 'pat', 20, 2019 )", + "( 9, 'lee', 10, 2021 )", + }; + for (const auto& v : studvals) stmt->executeUpdate(s + v); + std::cout << "student data inserted" << std::endl; + + s = "create table dept ( did int, dname varchar ( 8 ) )"; + stmt->executeUpdate(s); + std::cout << "dept table created" << std::endl; + + s = "insert into dept ( did, dname ) values "; + std::vector deptvals = { + "( 10, 'compsci' )", + "( 20, 'math' )", + "( 30, 'drama' )", + }; + for (const auto& v : deptvals) stmt->executeUpdate(s + v); + std::cout << "dept data inserted" << std::endl; + + s = "create table section ( sectid int, courseid int, prof varchar ( 8 ), yearoffered int )"; + stmt->executeUpdate(s); + std::cout << "section table created" << std::endl; + + s = "insert into section ( sectid, courseid, prof, yearoffered ) values "; + std::vector sectvals = { + "( 13, 12, 'turing', 2018 )", + "( 23, 12, 'turing', 2019 )", + "( 33, 32, 'newton', 2019 )", + "( 43, 32, 'einstein', 2017 )", + "( 53, 62, 'brando', 2018 )", + }; + for (const auto& v : sectvals) stmt->executeUpdate(s + v); + + s = "create table enroll ( eId int, studentid int, sectionid int, grade varchar ( 2 ) )"; + stmt->executeUpdate(s); + + s = "insert into enroll ( eid, studentid, sectionid, grade ) values "; + std::vector enrollvals = { + "( 14, 1, 13, 'A' )", + "( 24, 1, 43, 'C' )", + "( 34, 2, 43, 'B+' )", + "( 44, 4, 33, 'B' )", + "( 54, 4, 53, 'A' )", + "( 64, 6, 53, 'A' )", + }; + for (const auto& v : enrollvals) stmt->executeUpdate(s + v); + std::cout << "enroll data inserted" << std::endl; + } catch (std::exception& e) { + std::cout << e.what() << std::endl; + throw; // surface failures to the test + } + } + + TEST_CASE("IndexUpdateTest/Main", "[IndexUpdateTest]") { + std::string fileName = "indexreupdateTest"; + std::filesystem::remove_all(fileName); // Clean start + + createDB(fileName); + + app::SampleDB db(fileName); + auto transaction = db.newTransaction(); + auto& mdm = db.getMetaDataManager(); + + // open a scan on the data table + auto studentplan = std::static_pointer_cast( + std::make_shared(transaction.get(), "student", &mdm)); + auto studentscan = std::static_pointer_cast(studentplan->open()); + + // create a map containing all indexes for student + std::map> indexes; + std::map idxinfo = + mdm.getIndexInfo("student", *transaction); + for (auto& [fldname, ii] : idxinfo) { + indexes[fldname] = ii.open(); + } + + SECTION("Task 1: insert 'sam' and index entries") { + studentscan->insert(); + studentscan->setInt("sid", 11); + studentscan->setString("sname", "sam"); + studentscan->setInt("gradyear", 2023); + studentscan->setInt("majorid", 30); + + record::RID samRid = studentscan->getRid(); + for (const auto& [fldname, _ii] : indexes) { + scan::Constant dataval = studentscan->getValue(fldname); + indexes[fldname]->insert(dataval, samRid); + } + + SECTION("Task 2: delete 'joe' and remove index entries") { + studentscan->beforeFirst(); + while (studentscan->next()) { + if (studentscan->getString("sname") == "joe") { + record::RID joeRid = studentscan->getRid(); + for (const auto& [fldname, _ii] : indexes) { + scan::Constant dataval = studentscan->getValue(fldname); + indexes[fldname]->remove(dataval, joeRid); + } + studentscan->remove(); + break; + } + } + + studentscan->beforeFirst(); + bool seenJoe = false; + bool seenSam = false; + while (studentscan->next()) { + const auto name = studentscan->getString("sname"); + if (name == "joe") seenJoe = true; + if (name == "sam") seenSam = true; + } + REQUIRE_FALSE(seenJoe); // joe deleted + REQUIRE(seenSam); // sam inserted + } + } + + studentscan->close(); + for (const auto& [_, idx] : indexes) idx->close(); + transaction->commit(); + } +}