├── src ├── ql │ ├── ql_defs.h │ ├── ql.h │ ├── ql_test.cpp │ ├── ql_manager.h │ ├── ql_node.h │ ├── ql_node.cpp │ └── ql_manager.cpp ├── ix │ ├── ix.h │ ├── ix_scan.h │ ├── ix_scan.cpp │ ├── ix_manager.h │ ├── ix_index_handle.h │ ├── ix_defs.h │ ├── ix_manager.cpp │ ├── ix_test.cpp │ └── ix_index_handle.cpp ├── pf │ ├── pf.h │ ├── pf_manager.h │ ├── pf_defs.h │ ├── pf_pager.h │ ├── pf_manager.cpp │ ├── pf_pager.cpp │ └── pf_test.cpp ├── rm │ ├── rm.h │ ├── rm_scan.h │ ├── rm_manager.h │ ├── rm_scan.cpp │ ├── rm_defs.h │ ├── bitmap.h │ ├── rm_manager.cpp │ ├── bitmap_test.cpp │ ├── rm_file_handle.h │ ├── rm_file_handle.cpp │ └── rm_test.cpp ├── sm │ ├── sm.h │ ├── sm_defs.h │ ├── sm_manager.h │ ├── sm_test.cpp │ ├── sm_meta.h │ └── sm_manager.cpp ├── parser │ ├── ast.cpp │ ├── parser.h │ ├── parser_defs.h │ ├── parser_test.cpp │ ├── lex.l │ ├── ast_printer.h │ ├── ast.h │ └── yacc.y ├── rawcli.cpp ├── record_printer.h ├── defs.h ├── CMakeLists.txt ├── redbase.cpp ├── error.h └── interp.h ├── fig ├── demo.gif └── arch.svg ├── .clang-format ├── .gitignore ├── LICENSE ├── CMakeLists.txt ├── README.md └── test_e2e.py /src/ql/ql_defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | -------------------------------------------------------------------------------- /fig/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/li-plus/redbase-cpp/HEAD/fig/demo.gif -------------------------------------------------------------------------------- /src/ix/ix.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ix/ix_manager.h" 4 | #include "ix/ix_scan.h" 5 | -------------------------------------------------------------------------------- /src/ql/ql.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ql/ql_defs.h" 4 | #include "ql/ql_manager.h" 5 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IndentWidth: 4 3 | ColumnLimit: 120 4 | AlwaysBreakTemplateDeclarations: Yes 5 | -------------------------------------------------------------------------------- /src/pf/pf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pf/pf_defs.h" 4 | #include "pf/pf_manager.h" 5 | #include "pf/pf_pager.h" 6 | -------------------------------------------------------------------------------- /src/rm/rm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "rm/rm_defs.h" 4 | #include "rm/rm_manager.h" 5 | #include "rm/rm_scan.h" 6 | -------------------------------------------------------------------------------- /src/sm/sm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "sm/sm_defs.h" 4 | #include "sm/sm_manager.h" 5 | #include "sm/sm_meta.h" 6 | -------------------------------------------------------------------------------- /src/parser/ast.cpp: -------------------------------------------------------------------------------- 1 | #include "parser/ast.h" 2 | 3 | namespace ast { 4 | 5 | std::shared_ptr parse_tree; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/parser/parser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "parser/ast.h" 4 | #include "parser/ast_printer.h" 5 | #include "parser/parser_defs.h" 6 | -------------------------------------------------------------------------------- /src/sm/sm_defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | #include 5 | 6 | static const std::string DB_META_NAME = "db.meta"; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ide 2 | .idea/ 3 | .vscode/ 4 | 5 | # macos 6 | .DS_Store 7 | 8 | # build 9 | /build/ 10 | /cmake-build-debug/ 11 | *.out 12 | 13 | # bison & flex 14 | lex.yy.cpp 15 | yacc.tab.h 16 | yacc.tab.cpp 17 | 18 | # python 19 | __pycache__/ 20 | -------------------------------------------------------------------------------- /src/parser/parser_defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | 5 | int yyparse(); 6 | 7 | typedef struct yy_buffer_state *YY_BUFFER_STATE; 8 | 9 | YY_BUFFER_STATE yy_scan_string(const char *str); 10 | 11 | void yy_delete_buffer(YY_BUFFER_STATE buffer); 12 | -------------------------------------------------------------------------------- /src/rm/rm_scan.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "rm/rm_defs.h" 4 | 5 | class RmFileHandle; 6 | 7 | class RmScan : public RecScan { 8 | public: 9 | RmScan(const RmFileHandle *fh); 10 | 11 | void next() override; 12 | 13 | bool is_end() const override { return _rid.page_no == RM_NO_PAGE; } 14 | 15 | Rid rid() const override { return _rid; } 16 | 17 | private: 18 | const RmFileHandle *_fh; 19 | Rid _rid; 20 | }; 21 | -------------------------------------------------------------------------------- /src/rm/rm_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "rm/bitmap.h" 4 | #include "rm/rm_defs.h" 5 | #include "rm/rm_file_handle.h" 6 | 7 | class RmManager { 8 | public: 9 | static void create_file(const std::string &filename, int record_size); 10 | 11 | static void destroy_file(const std::string &filename); 12 | 13 | static std::unique_ptr open_file(const std::string &filename); 14 | 15 | static void close_file(const RmFileHandle *fh); 16 | }; 17 | -------------------------------------------------------------------------------- /src/ix/ix_scan.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ix/ix_defs.h" 4 | 5 | class IxIndexHandle; 6 | 7 | class IxScan : public RecScan { 8 | public: 9 | IxScan(const IxIndexHandle *ih, const Iid &lower, const Iid &upper) : _ih(ih), _iid(lower), _end(upper) {} 10 | 11 | void next() override; 12 | 13 | bool is_end() const override { return _iid == _end; } 14 | 15 | Rid rid() const override; 16 | 17 | const Iid &iid() const { return _iid; } 18 | 19 | private: 20 | const IxIndexHandle *_ih; 21 | Iid _iid; 22 | Iid _end; 23 | }; 24 | -------------------------------------------------------------------------------- /src/ix/ix_scan.cpp: -------------------------------------------------------------------------------- 1 | #include "ix/ix_scan.h" 2 | #include "ix/ix_index_handle.h" 3 | #include 4 | 5 | void IxScan::next() { 6 | assert(!is_end()); 7 | IxNodeHandle node = _ih->fetch_node(_iid.page_no); 8 | assert(node.hdr->is_leaf); 9 | assert(_iid.slot_no < node.hdr->num_key); 10 | // increment slot no 11 | _iid.slot_no++; 12 | if (_iid.page_no != _ih->hdr.last_leaf && _iid.slot_no == node.hdr->num_key) { 13 | // go to next leaf 14 | _iid.slot_no = 0; 15 | _iid.page_no = node.hdr->next_leaf; 16 | } 17 | } 18 | 19 | Rid IxScan::rid() const { return _ih->get_rid(_iid); } 20 | -------------------------------------------------------------------------------- /src/pf/pf_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "error.h" 4 | #include "pf/pf_pager.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class PfManager { 12 | public: 13 | static PfPager pager; 14 | 15 | static bool is_file(const std::string &path); 16 | 17 | static void create_file(const std::string &path); 18 | 19 | static void destroy_file(const std::string &path); 20 | 21 | static int open_file(const std::string &path); 22 | 23 | static void close_file(int fd); 24 | 25 | private: 26 | static std::unordered_map _path2fd; 27 | static std::unordered_map _fd2path; 28 | }; 29 | -------------------------------------------------------------------------------- /src/rm/rm_scan.cpp: -------------------------------------------------------------------------------- 1 | #include "rm/rm_scan.h" 2 | #include "rm/rm_file_handle.h" 3 | #include 4 | 5 | RmScan::RmScan(const RmFileHandle *fh) : _fh(fh) { 6 | _rid = Rid(RM_FIRST_RECORD_PAGE, -1); 7 | next(); 8 | } 9 | 10 | void RmScan::next() { 11 | assert(!is_end()); 12 | while (_rid.page_no < _fh->hdr.num_pages) { 13 | RmPageHandle ph = _fh->fetch_page(_rid.page_no); 14 | _rid.slot_no = Bitmap::next_bit(true, ph.bitmap, _fh->hdr.num_records_per_page, _rid.slot_no); 15 | if (_rid.slot_no < _fh->hdr.num_records_per_page) { 16 | return; 17 | } 18 | _rid.slot_no = -1; 19 | _rid.page_no++; 20 | } 21 | // next record not found 22 | _rid.page_no = RM_NO_PAGE; 23 | } 24 | -------------------------------------------------------------------------------- /src/ix/ix_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ix/ix_defs.h" 4 | #include "ix/ix_index_handle.h" 5 | #include 6 | #include 7 | 8 | class IxManager { 9 | public: 10 | static std::string get_index_name(const std::string &filename, int index_no) { 11 | return filename + '.' + std::to_string(index_no) + ".idx"; 12 | } 13 | 14 | static bool exists(const std::string &filename, int index_no); 15 | 16 | static void create_index(const std::string &filename, int index_no, ColType col_type, int col_len); 17 | 18 | static void destroy_index(const std::string &filename, int index_no); 19 | 20 | static std::unique_ptr open_index(const std::string &filename, int index_no); 21 | 22 | static void close_index(const IxIndexHandle *ih); 23 | }; 24 | -------------------------------------------------------------------------------- /src/rm/rm_defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | #include "pf/pf.h" 5 | 6 | constexpr int RM_NO_PAGE = -1; 7 | constexpr int RM_FILE_HDR_PAGE = 0; 8 | constexpr int RM_FIRST_RECORD_PAGE = 1; 9 | constexpr int RM_MAX_RECORD_SIZE = 512; 10 | 11 | struct RmFileHdr { 12 | int record_size; 13 | int num_pages; 14 | int num_records_per_page; 15 | int first_free; 16 | int bitmap_size; 17 | }; 18 | 19 | struct RmPageHdr { 20 | int next_free; 21 | int num_records; 22 | }; 23 | 24 | struct RmRecord { 25 | uint8_t *data; 26 | int size; 27 | 28 | RmRecord(const RmRecord &other) = delete; 29 | 30 | RmRecord &operator=(const RmRecord &other) = delete; 31 | 32 | RmRecord(int size_) { 33 | size = size_; 34 | data = new uint8_t[size_]; 35 | } 36 | 37 | ~RmRecord() { delete[] data; } 38 | }; 39 | -------------------------------------------------------------------------------- /src/rawcli.cpp: -------------------------------------------------------------------------------- 1 | #include "interp.h" 2 | 3 | int main(int argc, char **argv) { 4 | if (argc != 2) { 5 | std::cerr << "Usage: " << argv[0] << " " << std::endl; 6 | exit(1); 7 | } 8 | try { 9 | std::string db_name = argv[1]; 10 | if (!SmManager::is_dir(db_name)) { 11 | SmManager::create_db(db_name); 12 | } 13 | SmManager::open_db(db_name); 14 | 15 | while (true) { 16 | if (yyparse() == 0) { 17 | if (ast::parse_tree == nullptr) { 18 | break; 19 | } 20 | try { 21 | Interp::interp_sql(ast::parse_tree); 22 | } catch (RedBaseError &e) { 23 | std::cerr << e.what() << std::endl; 24 | } 25 | } 26 | } 27 | SmManager::close_db(); 28 | } catch (RedBaseError &e) { 29 | std::cerr << e.what() << std::endl; 30 | exit(1); 31 | } 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /src/pf/pf_defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | #include 5 | #include 6 | 7 | static constexpr int PAGE_SIZE = 4096; 8 | static constexpr int NUM_CACHE_PAGES = 65536; 9 | 10 | struct PageId { 11 | int fd; 12 | int page_no; 13 | 14 | PageId() = default; 15 | PageId(int fd_, int page_no_) : fd(fd_), page_no(page_no_) {} 16 | 17 | friend bool operator==(const PageId &x, const PageId &y) { return x.fd == y.fd && x.page_no == y.page_no; } 18 | friend bool operator!=(const PageId &x, const PageId &y) { return !(x == y); } 19 | 20 | friend std::ostream &operator<<(std::ostream &os, const PageId &self) { 21 | return os << "PageId(fd=" << self.fd << ", page_no=" << self.page_no << ")"; 22 | } 23 | }; 24 | 25 | namespace std { 26 | template <> 27 | struct hash { 28 | size_t operator()(const PageId &pid) const noexcept { return (pid.fd << 16) | pid.page_no; } 29 | }; 30 | } // namespace std 31 | 32 | struct Page { 33 | PageId id; 34 | uint8_t *buf; 35 | bool is_dirty; 36 | 37 | void mark_dirty() { is_dirty = true; } 38 | }; 39 | -------------------------------------------------------------------------------- /src/record_printer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class RecordPrinter { 9 | public: 10 | RecordPrinter(size_t num_cols_) : num_cols(num_cols_) { assert(num_cols_ > 0); } 11 | 12 | void print_separator() const { 13 | for (size_t i = 0; i < num_cols; i++) { 14 | std::cout << '+' << std::string(COL_WIDTH + 2, '-'); 15 | } 16 | std::cout << "+\n"; 17 | } 18 | 19 | void print_record(const std::vector &rec_str) const { 20 | assert(rec_str.size() == num_cols); 21 | for (auto col : rec_str) { 22 | if (col.size() > COL_WIDTH) { 23 | col = col.substr(0, COL_WIDTH - 3) + "..."; 24 | } 25 | std::cout << "| " << std::setw(COL_WIDTH) << col << ' '; 26 | } 27 | std::cout << "|\n"; 28 | } 29 | 30 | static void print_record_count(size_t num_rec) { std::cout << "Total record(s): " << num_rec << '\n'; } 31 | 32 | private: 33 | static constexpr size_t COL_WIDTH = 16; 34 | size_t num_cols; 35 | }; 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jiahao Li 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/rm/bitmap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class Bitmap { 7 | public: 8 | static constexpr int WIDTH = 8; 9 | static constexpr unsigned HIGHEST_BIT = 0x80u; 10 | 11 | static void init(uint8_t *bm, int size) { memset(bm, 0, size); } 12 | 13 | static void set(uint8_t *bm, int pos) { bm[get_bucket(pos)] |= get_bit(pos); } 14 | 15 | static void reset(uint8_t *bm, int pos) { bm[get_bucket(pos)] &= (uint8_t)~get_bit(pos); } 16 | 17 | static bool test(const uint8_t *bm, int pos) { return (bm[get_bucket(pos)] & get_bit(pos)) != 0; } 18 | 19 | static int next_bit(bool bit, const uint8_t *bm, int max_n, int curr) { 20 | for (int i = curr + 1; i < max_n; i++) { 21 | if (test(bm, i) == bit) { 22 | return i; 23 | } 24 | } 25 | return max_n; 26 | } 27 | 28 | static int first_bit(bool bit, const uint8_t *bm, int max_n) { return next_bit(bit, bm, max_n, -1); } 29 | 30 | private: 31 | static int get_bucket(int pos) { return pos / WIDTH; } 32 | 33 | static uint8_t get_bit(int pos) { return HIGHEST_BIT >> (uint8_t)(pos % WIDTH); } 34 | }; 35 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(RedBase) 3 | 4 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 5 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 6 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 7 | 8 | set(CMAKE_CXX_STANDARD 14) 9 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -Wall -pedantic-errors") 10 | 11 | if (NOT CMAKE_BUILD_TYPE) 12 | set(CMAKE_BUILD_TYPE Release) 13 | endif () 14 | 15 | # GoogleTest 16 | option(REDBASE_ENABLE_TEST "" ON) 17 | if (REDBASE_ENABLE_TEST) 18 | enable_testing() 19 | 20 | include(FetchContent) 21 | FetchContent_Declare( 22 | googletest 23 | URL https://github.com/google/googletest/archive/refs/heads/main.zip 24 | ) 25 | # For Windows: Prevent overriding the parent project's compiler/linker settings 26 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 27 | # Do not install GTest when `make install` 28 | set(INSTALL_GTEST OFF) 29 | FetchContent_MakeAvailable(googletest) 30 | 31 | include(GoogleTest) 32 | endif () 33 | 34 | add_subdirectory(src) 35 | 36 | # clang-format 37 | file(GLOB_RECURSE SOURCES src/*.cpp src/*.h) 38 | add_custom_target(format COMMAND clang-format -i ${SOURCES}) 39 | -------------------------------------------------------------------------------- /src/parser/parser_test.cpp: -------------------------------------------------------------------------------- 1 | #include "parser/parser.h" 2 | #include 3 | 4 | TEST(parser, basic) { 5 | std::vector sqls = { 6 | "show tables;", 7 | "desc tb;", 8 | "create table tb (a int, b float, c char(4));", 9 | "drop table tb;", 10 | "create index tb(a);", 11 | "drop index tb(b);", 12 | "insert into tb values (1, 3.14, 'pi');", 13 | "delete from tb where a = 1;", 14 | "update tb set a = 1, b = 2.2, c = 'xyz' where x = 2 and y < 1.1 and z > 'abc';", 15 | "select * from tb;", 16 | "select * from tb where x <> 2 and y >= 3. and z <= '123' and b < tb.a;", 17 | "select x.a, y.b from x, y where x.a = y.b and c = d;", 18 | "exit;", 19 | "help;", 20 | "", 21 | }; 22 | for (auto &sql : sqls) { 23 | std::cout << sql << std::endl; 24 | YY_BUFFER_STATE buf = yy_scan_string(sql.c_str()); 25 | EXPECT_EQ(yyparse(), 0); 26 | if (ast::parse_tree != nullptr) { 27 | ast::TreePrinter::print(ast::parse_tree); 28 | yy_delete_buffer(buf); 29 | std::cout << std::endl; 30 | } else { 31 | std::cout << "exit/EOF" << std::endl; 32 | } 33 | } 34 | ast::parse_tree.reset(); 35 | } 36 | -------------------------------------------------------------------------------- /src/rm/rm_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "rm/rm_manager.h" 2 | 3 | void RmManager::create_file(const std::string &filename, int record_size) { 4 | if (record_size < 1 || record_size > RM_MAX_RECORD_SIZE) { 5 | throw InvalidRecordSizeError(record_size); 6 | } 7 | PfManager::create_file(filename); 8 | int fd = PfManager::open_file(filename); 9 | 10 | RmFileHdr hdr{}; 11 | hdr.record_size = record_size; 12 | hdr.num_pages = 1; 13 | hdr.first_free = RM_NO_PAGE; 14 | // We have: sizeof(RmPageHdr) + (n + 7) / 8 + n * record_size <= PAGE_SIZE 15 | hdr.num_records_per_page = 16 | (Bitmap::WIDTH * (PAGE_SIZE - 1 - (int)sizeof(RmPageHdr)) + 1) / (1 + record_size * Bitmap::WIDTH); 17 | hdr.bitmap_size = (hdr.num_records_per_page + Bitmap::WIDTH - 1) / Bitmap::WIDTH; 18 | PfPager::write_page(fd, RM_FILE_HDR_PAGE, (uint8_t *)&hdr, sizeof(hdr)); 19 | PfManager::close_file(fd); 20 | } 21 | 22 | void RmManager::destroy_file(const std::string &filename) { PfManager::destroy_file(filename); } 23 | 24 | std::unique_ptr RmManager::open_file(const std::string &filename) { 25 | int fd = PfManager::open_file(filename); 26 | return std::make_unique(fd); 27 | } 28 | 29 | void RmManager::close_file(const RmFileHandle *fh) { 30 | PfPager::write_page(fh->fd, RM_FILE_HDR_PAGE, (uint8_t *)&fh->hdr, sizeof(fh->hdr)); 31 | PfManager::close_file(fh->fd); 32 | } 33 | -------------------------------------------------------------------------------- /src/rm/bitmap_test.cpp: -------------------------------------------------------------------------------- 1 | #include "rm/bitmap.h" 2 | #include 3 | #include 4 | 5 | constexpr int MAX_N = 4096; 6 | 7 | void check_equal(uint8_t *bm, std::bitset &mock) { 8 | for (int i = 0; i < MAX_N; i++) { 9 | EXPECT_EQ(Bitmap::test(bm, i), mock.test(i)); 10 | } 11 | int first_zero = Bitmap::first_bit(false, bm, MAX_N); 12 | if (first_zero < MAX_N) { 13 | EXPECT_FALSE(Bitmap::test(bm, first_zero)); 14 | } 15 | for (int i = 0; i < first_zero; i++) { 16 | EXPECT_TRUE(Bitmap::test(bm, i)); 17 | } 18 | int first_one = Bitmap::first_bit(true, bm, MAX_N); 19 | if (first_one < MAX_N) { 20 | EXPECT_TRUE(Bitmap::test(bm, first_one)); 21 | } 22 | for (int i = 0; i < first_one; i++) { 23 | EXPECT_FALSE(Bitmap::test(bm, i)); 24 | } 25 | } 26 | 27 | TEST(Bitmap, basic) { 28 | srand((unsigned)time(nullptr)); 29 | 30 | uint8_t bm[MAX_N / Bitmap::WIDTH]; 31 | Bitmap::init(bm, MAX_N / Bitmap::WIDTH); 32 | std::bitset mock; 33 | 34 | for (int round = 0; round < 10000; round++) { 35 | int choice = rand() % 2; 36 | int pos = rand() % MAX_N; 37 | if (choice == 0) { 38 | Bitmap::set(bm, pos); 39 | mock.set(pos); 40 | } else { 41 | Bitmap::reset(bm, pos); 42 | mock.reset(pos); 43 | } 44 | check_equal(bm, mock); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | template ::value, T>::type> 7 | static std::ostream &operator<<(std::ostream &os, const T &enum_val) { 8 | return os << (int)enum_val; 9 | } 10 | 11 | template ::value, T>::type> 12 | static std::istream &operator>>(std::istream &is, T &enum_val) { 13 | int int_val; 14 | is >> int_val; 15 | enum_val = static_cast(int_val); 16 | return is; 17 | } 18 | 19 | struct Rid { 20 | int page_no; 21 | int slot_no; 22 | 23 | Rid() = default; 24 | Rid(int page_no_, int slot_no_) : page_no(page_no_), slot_no(slot_no_) {} 25 | 26 | friend bool operator==(const Rid &x, const Rid &y) { return x.page_no == y.page_no && x.slot_no == y.slot_no; } 27 | friend bool operator!=(const Rid &x, const Rid &y) { return !(x == y); } 28 | }; 29 | 30 | enum ColType { TYPE_INT, TYPE_FLOAT, TYPE_STRING }; 31 | 32 | static inline std::string coltype2str(ColType type) { 33 | static std::map m = {{TYPE_INT, "INT"}, {TYPE_FLOAT, "FLOAT"}, {TYPE_STRING, "STRING"}}; 34 | return m.at(type); 35 | } 36 | 37 | class RecScan { 38 | public: 39 | virtual ~RecScan() = default; 40 | 41 | virtual void next() = 0; 42 | 43 | virtual bool is_end() const = 0; 44 | 45 | virtual Rid rid() const = 0; 46 | }; 47 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}) 2 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) 3 | 4 | find_package(BISON REQUIRED) 5 | find_package(FLEX REQUIRED) 6 | 7 | bison_target(yacc parser/yacc.y ${CMAKE_CURRENT_BINARY_DIR}/yacc.tab.cpp 8 | DEFINES_FILE ${CMAKE_CURRENT_BINARY_DIR}/yacc.tab.h) 9 | flex_target(lex parser/lex.l ${CMAKE_CURRENT_BINARY_DIR}/lex.yy.cpp) 10 | add_flex_bison_dependency(lex yacc) 11 | 12 | add_library(redbase-cpp STATIC 13 | pf/pf_manager.cpp pf/pf_pager.cpp 14 | rm/rm_manager.cpp rm/rm_scan.cpp rm/rm_file_handle.cpp 15 | ix/ix_manager.cpp ix/ix_index_handle.cpp ix/ix_scan.cpp 16 | sm/sm_manager.cpp 17 | ql/ql_manager.cpp ql/ql_node.cpp 18 | parser/ast.cpp ${BISON_yacc_OUTPUT_SOURCE} ${FLEX_lex_OUTPUTS}) 19 | 20 | add_executable(rawcli rawcli.cpp) 21 | target_link_libraries(rawcli redbase-cpp) 22 | 23 | add_executable(redbase redbase.cpp) 24 | target_link_libraries(redbase redbase-cpp readline) 25 | 26 | if (REDBASE_ENABLE_TEST) 27 | file(GLOB_RECURSE REDBASE_TEST_FILES *_test.cpp) 28 | foreach (REDBASE_TEST_FILE ${REDBASE_TEST_FILES}) 29 | get_filename_component(REDBASE_TEST_NAME ${REDBASE_TEST_FILE} NAME_WE) 30 | add_executable(${REDBASE_TEST_NAME} ${REDBASE_TEST_FILE}) 31 | target_link_libraries(${REDBASE_TEST_NAME} redbase-cpp gtest_main) 32 | gtest_discover_tests(${REDBASE_TEST_NAME}) 33 | endforeach () 34 | endif () 35 | -------------------------------------------------------------------------------- /src/rm/rm_file_handle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "rm/bitmap.h" 4 | #include "rm/rm_defs.h" 5 | #include 6 | 7 | struct RmPageHandle { 8 | RmPageHdr *hdr; 9 | uint8_t *bitmap; 10 | uint8_t *slots; 11 | Page *page; 12 | const RmFileHdr *fhdr; 13 | 14 | RmPageHandle(const RmFileHdr *fhdr_, Page *page_) : page(page_), fhdr(fhdr_) { 15 | hdr = (RmPageHdr *)page->buf; 16 | bitmap = page->buf + sizeof(RmPageHdr); 17 | slots = bitmap + fhdr->bitmap_size; 18 | } 19 | 20 | uint8_t *get_slot(int slot_no) const { return slots + slot_no * fhdr->record_size; } 21 | }; 22 | 23 | class RmFileHandle { 24 | friend class RmScan; 25 | 26 | public: 27 | RmFileHdr hdr; 28 | int fd; 29 | 30 | RmFileHandle(int fd_) { 31 | fd = fd_; 32 | PfPager::read_page(fd, RM_FILE_HDR_PAGE, (uint8_t *)&hdr, sizeof(hdr)); 33 | } 34 | 35 | RmFileHandle(const RmFileHandle &other) = delete; 36 | 37 | RmFileHandle &operator=(const RmFileHandle &other) = delete; 38 | 39 | bool is_record(const Rid &rid) const; 40 | 41 | std::unique_ptr get_record(const Rid &rid) const; 42 | 43 | Rid insert_record(uint8_t *buf); 44 | 45 | void delete_record(const Rid &rid); 46 | 47 | void update_record(const Rid &rid, uint8_t *buf); 48 | 49 | private: 50 | RmPageHandle fetch_page(int page_no) const; 51 | 52 | RmPageHandle create_page(); 53 | 54 | void release_page(RmPageHandle &ph); 55 | }; 56 | -------------------------------------------------------------------------------- /src/sm/sm_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ix/ix.h" 4 | #include "rm/rm.h" 5 | #include "sm/sm_defs.h" 6 | #include "sm/sm_meta.h" 7 | 8 | struct ColDef { 9 | std::string name; // Column name 10 | ColType type; // Type of column 11 | int len; // Length of column 12 | 13 | ColDef() = default; 14 | ColDef(std::string name_, ColType type_, int len_) : name(std::move(name_)), type(type_), len(len_) {} 15 | }; 16 | 17 | class SmManager { 18 | public: 19 | static DbMeta db; 20 | static std::map> fhs; 21 | static std::map> ihs; 22 | 23 | // Database management 24 | static bool is_dir(const std::string &db_name); 25 | 26 | static void create_db(const std::string &db_name); 27 | 28 | static void drop_db(const std::string &db_name); 29 | 30 | static void open_db(const std::string &db_name); 31 | 32 | static void close_db(); 33 | 34 | // Table management 35 | static void show_tables(); 36 | 37 | static void desc_table(const std::string &tab_name); 38 | 39 | static void create_table(const std::string &tab_name, const std::vector &col_defs); 40 | 41 | static void drop_table(const std::string &tab_name); 42 | 43 | // Index management 44 | static void create_index(const std::string &tab_name, const std::string &col_name); 45 | 46 | static void drop_index(const std::string &tab_name, const std::string &col_name); 47 | }; 48 | -------------------------------------------------------------------------------- /src/pf/pf_pager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "error.h" 4 | #include "pf/pf_defs.h" 5 | #include 6 | #include 7 | 8 | class PfPager { 9 | public: 10 | PfPager(); 11 | ~PfPager(); 12 | 13 | PfPager &operator=(const PfPager &other) = delete; 14 | 15 | static void read_page(int fd, int page_no, uint8_t *buf, int num_bytes); 16 | static void write_page(int fd, int page_no, const uint8_t *buf, int num_bytes); 17 | 18 | Page *create_page(int fd, int page_no); 19 | 20 | Page *fetch_page(int fd, int page_no); 21 | 22 | void flush_file(int fd); 23 | void flush_page(Page *page); 24 | void flush_all(); 25 | 26 | bool in_cache(const PageId &page_id) const { return _busy_map.find(page_id) != _busy_map.end(); } 27 | 28 | const std::list &busy_list() const { return _busy_pages; } 29 | const std::unordered_map::iterator> &busy_map() const { return _busy_map; } 30 | const std::list &free_list() const { return _free_pages; } 31 | 32 | private: 33 | static void force_page(Page *page); 34 | 35 | // Get the page from memory corresponding to the disk page. 36 | // If the page is not in memory, allocate a page and read the disk. 37 | template 38 | Page *get_page(int fd, int page_no); 39 | 40 | void access(Page *page); 41 | 42 | private: 43 | uint8_t _cache[NUM_CACHE_PAGES * PAGE_SIZE]; 44 | Page _pages[NUM_CACHE_PAGES]; 45 | std::unordered_map::iterator> _busy_map; 46 | std::list _busy_pages; 47 | std::list _free_pages; 48 | }; 49 | -------------------------------------------------------------------------------- /src/sm/sm_test.cpp: -------------------------------------------------------------------------------- 1 | #include "sm/sm.h" 2 | #include 3 | 4 | TEST(sm, basic) { 5 | std::string db = "db"; 6 | std::string tab1 = "tab1"; 7 | std::string tab2 = "tab2"; 8 | if (SmManager::is_dir(db)) { 9 | SmManager::drop_db(db); 10 | } 11 | // Cannot use a database that does not exist 12 | EXPECT_THROW(SmManager::open_db(db), DatabaseNotFoundError); 13 | 14 | // Create database 15 | SmManager::create_db(db); 16 | // Cannot re-create database 17 | EXPECT_THROW(SmManager::create_db(db), DatabaseExistsError); 18 | 19 | // Open database 20 | SmManager::open_db(db); 21 | std::vector col_defs = {ColDef("a", TYPE_INT, 4), ColDef("b", TYPE_FLOAT, 4), 22 | ColDef("c", TYPE_STRING, 256)}; 23 | // Create table 1 24 | SmManager::create_table(tab1, col_defs); 25 | // Cannot re-create table 26 | EXPECT_THROW(SmManager::create_table(tab1, col_defs), TableExistsError); 27 | 28 | // Create table 2 29 | SmManager::create_table(tab2, col_defs); 30 | // Create index for table 1 31 | SmManager::create_index(tab1, "a"); 32 | SmManager::create_index(tab1, "c"); 33 | // Cannot re-create index 34 | EXPECT_THROW(SmManager::create_index(tab1, "a"), IndexExistsError); 35 | 36 | // Create index for table 2 37 | SmManager::create_index(tab2, "b"); 38 | // Drop index of table 1 39 | SmManager::drop_index(tab1, "a"); 40 | // Cannot drop index that does not exist 41 | EXPECT_THROW(SmManager::drop_index(tab1, "b"), IndexNotFoundError); 42 | 43 | // Drop index 44 | SmManager::drop_table(tab1); 45 | // Cannot drop table that does not exist 46 | EXPECT_THROW(SmManager::drop_table(tab1), TableNotFoundError); 47 | 48 | // Clean up 49 | SmManager::close_db(); 50 | SmManager::drop_db(db); 51 | } -------------------------------------------------------------------------------- /src/ix/ix_index_handle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ix/ix_defs.h" 4 | 5 | static const bool binary_search = false; 6 | 7 | int ix_compare(const uint8_t *a, const uint8_t *b, ColType type, int col_len); 8 | 9 | struct IxNodeHandle { 10 | IxPageHdr *hdr; 11 | uint8_t *keys; 12 | Rid *rids; 13 | Page *page; 14 | const IxFileHdr *ihdr; 15 | 16 | IxNodeHandle() = default; 17 | 18 | IxNodeHandle(const IxFileHdr *ihdr_, Page *page_); 19 | 20 | uint8_t *get_key(int key_idx) const { return keys + key_idx * ihdr->col_len; } 21 | 22 | Rid *get_rid(int rid_idx) const { return &rids[rid_idx]; } 23 | 24 | int lower_bound(const uint8_t *target) const; 25 | int upper_bound(const uint8_t *key) const; 26 | 27 | void insert_keys(int pos, const uint8_t *key, int n); 28 | void insert_key(int pos, const uint8_t *key); 29 | void erase_key(int pos); 30 | 31 | void insert_rids(int pos, const Rid *rid, int n); 32 | void insert_rid(int pos, const Rid &rid); 33 | void erase_rid(int pos); 34 | 35 | int find_child(const IxNodeHandle &child) const; 36 | }; 37 | 38 | class IxIndexHandle { 39 | friend class IxTest; 40 | friend class IxScan; 41 | 42 | public: 43 | int fd; 44 | IxFileHdr hdr; 45 | 46 | IxIndexHandle(int fd_); 47 | 48 | void insert_entry(const uint8_t *key, const Rid &rid); 49 | 50 | void delete_entry(const uint8_t *key, const Rid &rid); 51 | 52 | Rid get_rid(const Iid &iid) const; 53 | 54 | Iid lower_bound(const uint8_t *key) const; 55 | 56 | Iid upper_bound(const uint8_t *key) const; 57 | 58 | Iid leaf_end() const; 59 | 60 | Iid leaf_begin() const; 61 | 62 | private: 63 | IxNodeHandle fetch_node(int page_no) const; 64 | 65 | IxNodeHandle create_node(); 66 | 67 | void maintain_parent(const IxNodeHandle &node); 68 | 69 | void erase_leaf(IxNodeHandle &leaf); 70 | 71 | void release_node(IxNodeHandle &node); 72 | 73 | void maintain_child(IxNodeHandle &node, int child_idx); 74 | }; 75 | -------------------------------------------------------------------------------- /src/pf/pf_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "pf/pf_manager.h" 2 | 3 | std::unordered_map PfManager::_path2fd; 4 | std::unordered_map PfManager::_fd2path; 5 | PfPager PfManager::pager; 6 | 7 | bool PfManager::is_file(const std::string &path) { 8 | struct stat st; 9 | return stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode); 10 | } 11 | 12 | void PfManager::create_file(const std::string &path) { 13 | if (is_file(path)) { 14 | throw FileExistsError(path); 15 | } 16 | int fd = open(path.c_str(), O_CREAT, S_IRUSR | S_IWUSR); 17 | if (fd < 0) { 18 | throw UnixError(); 19 | } 20 | if (close(fd) != 0) { 21 | throw UnixError(); 22 | } 23 | } 24 | 25 | void PfManager::destroy_file(const std::string &path) { 26 | if (!is_file(path)) { 27 | throw FileNotFoundError(path); 28 | } 29 | // If file is open, cannot destroy file 30 | if (_path2fd.count(path)) { 31 | throw FileNotClosedError(path); 32 | } 33 | // Remove file from disk 34 | if (unlink(path.c_str()) != 0) { 35 | throw UnixError(); 36 | } 37 | } 38 | 39 | int PfManager::open_file(const std::string &path) { 40 | if (!is_file(path)) { 41 | throw FileNotFoundError(path); 42 | } 43 | if (_path2fd.count(path)) { 44 | // File is already open 45 | throw FileNotClosedError(path); 46 | } 47 | // Open file and return the file descriptor 48 | int fd = open(path.c_str(), O_RDWR); 49 | if (fd < 0) { 50 | throw UnixError(); 51 | } 52 | // Memorize the opened unix file descriptor 53 | _path2fd[path] = fd; 54 | _fd2path[fd] = path; 55 | return fd; 56 | } 57 | 58 | void PfManager::close_file(int fd) { 59 | auto pos = _fd2path.find(fd); 60 | if (pos == _fd2path.end()) { 61 | throw FileNotOpenError(fd); 62 | } 63 | pager.flush_file(fd); 64 | const std::string &filename = pos->second; 65 | _path2fd.erase(filename); 66 | _fd2path.erase(pos); 67 | if (close(fd) != 0) { 68 | throw UnixError(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/ix/ix_defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "defs.h" 4 | #include "pf/pf.h" 5 | 6 | struct IxFileHdr { 7 | int first_free; 8 | int num_pages; // number of disk pages 9 | int root_page; // root page no 10 | ColType col_type; 11 | int col_len; 12 | int btree_order; // number of children per page 13 | int key_offset; // offset of key array 14 | int rid_offset; // offset of rid array (children array) 15 | int first_leaf; 16 | int last_leaf; 17 | 18 | IxFileHdr() = default; 19 | IxFileHdr(int first_free_, int num_pages_, int root_page_, ColType col_type_, int col_len_, int btree_order_, 20 | int key_offset_, int rid_offset_, int first_leaf_, int last_leaf_) 21 | : first_free(first_free_), num_pages(num_pages_), root_page(root_page_), col_type(col_type_), col_len(col_len_), 22 | btree_order(btree_order_), key_offset(key_offset_), rid_offset(rid_offset_), first_leaf(first_leaf_), 23 | last_leaf(last_leaf_) {} 24 | }; 25 | 26 | struct IxPageHdr { 27 | int next_free; 28 | int parent; 29 | int num_key; // number of current keys (always equals to #child - 1) 30 | int num_child; // number of current children 31 | bool is_leaf; 32 | int prev_leaf; // previous leaf node, effective only when is_leaf is true 33 | int next_leaf; // next leaf node, effective only when is_leaf is true 34 | 35 | IxPageHdr() = default; 36 | IxPageHdr(int next_free_, int parent_, int num_key_, int num_child_, bool is_leaf_, int prev_leaf_, int next_leaf_) 37 | : next_free(next_free_), parent(parent_), num_key(num_key_), num_child(num_child_), is_leaf(is_leaf_), 38 | prev_leaf(prev_leaf_), next_leaf(next_leaf_) {} 39 | }; 40 | 41 | struct Iid { 42 | int page_no; 43 | int slot_no; 44 | 45 | Iid() = default; 46 | Iid(int page_no_, int slot_no_) : page_no(page_no_), slot_no(slot_no_) {} 47 | 48 | friend bool operator==(const Iid &x, const Iid &y) { return x.page_no == y.page_no && x.slot_no == y.slot_no; } 49 | friend bool operator!=(const Iid &x, const Iid &y) { return !(x == y); } 50 | }; 51 | 52 | constexpr int IX_NO_PAGE = -1; 53 | constexpr int IX_FILE_HDR_PAGE = 0; 54 | constexpr int IX_LEAF_HEADER_PAGE = 1; 55 | constexpr int IX_INIT_ROOT_PAGE = 2; 56 | constexpr int IX_INIT_NUM_PAGES = 3; 57 | constexpr int IX_MAX_COL_LEN = 512; 58 | -------------------------------------------------------------------------------- /src/redbase.cpp: -------------------------------------------------------------------------------- 1 | #include "interp.h" 2 | #include 3 | #include 4 | #include 5 | 6 | static bool should_exit = false; 7 | 8 | void sigint_handler(int signo) { should_exit = true; } 9 | 10 | int main(int argc, char **argv) { 11 | if (argc != 2) { 12 | std::cerr << "Usage: " << argv[0] << " " << std::endl; 13 | exit(1); 14 | } 15 | signal(SIGINT, sigint_handler); 16 | try { 17 | std::cout << "\n" 18 | " ██████╗ ███████╗██████╗ ██████╗ █████╗ ███████╗███████╗\n" 19 | " ██╔══██╗██╔════╝██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔════╝\n" 20 | " ██████╔╝█████╗ ██║ ██║██████╔╝███████║███████╗█████╗ \n" 21 | " ██╔══██╗██╔══╝ ██║ ██║██╔══██╗██╔══██║╚════██║██╔══╝ \n" 22 | " ██║ ██║███████╗██████╔╝██████╔╝██║ ██║███████║███████╗\n" 23 | " ╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚══════╝\n" 24 | "\n" 25 | "Type 'help;' for help.\n" 26 | "\n"; 27 | // Database name is passed by args 28 | std::string db_name = argv[1]; 29 | if (!SmManager::is_dir(db_name)) { 30 | // Database not found, create a new one 31 | SmManager::create_db(db_name); 32 | } 33 | // Open database 34 | SmManager::open_db(db_name); 35 | 36 | // Wait for user input 37 | while (!should_exit) { 38 | char *line_read = readline("redbase> "); 39 | if (line_read == nullptr) { 40 | // EOF encountered 41 | break; 42 | } 43 | std::string line = line_read; 44 | free(line_read); 45 | 46 | if (should_exit) { 47 | break; 48 | } 49 | 50 | if (!line.empty()) { 51 | add_history(line.c_str()); 52 | YY_BUFFER_STATE buf = yy_scan_string(line.c_str()); 53 | if (yyparse() == 0) { 54 | if (ast::parse_tree != nullptr) { 55 | try { 56 | Interp::interp_sql(ast::parse_tree); 57 | } catch (RedBaseError &e) { 58 | std::cerr << e.what() << std::endl; 59 | } 60 | } else { 61 | should_exit = true; 62 | } 63 | } 64 | yy_delete_buffer(buf); 65 | } 66 | } 67 | SmManager::close_db(); 68 | std::cout << "Bye" << std::endl; 69 | } catch (RedBaseError &e) { 70 | std::cerr << e.what() << std::endl; 71 | exit(1); 72 | } 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /src/ix/ix_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "ix/ix_manager.h" 2 | #include 3 | 4 | bool IxManager::exists(const std::string &filename, int index_no) { 5 | auto ix_name = get_index_name(filename, index_no); 6 | return PfManager::is_file(ix_name); 7 | } 8 | 9 | void IxManager::create_index(const std::string &filename, int index_no, ColType col_type, int col_len) { 10 | std::string ix_name = get_index_name(filename, index_no); 11 | assert(index_no >= 0); 12 | // Create index file 13 | PfManager::create_file(ix_name); 14 | // Open index file 15 | int fd = PfManager::open_file(ix_name); 16 | // Create file header and write to file 17 | // Theoretically we have: |page_hdr| + (|attr| + |rid|) * n <= PAGE_SIZE 18 | // but we reserve one slot for convenient inserting and deleting, i.e. 19 | // |page_hdr| + (|attr| + |rid|) * (n + 1) <= PAGE_SIZE 20 | if (col_len > IX_MAX_COL_LEN) { 21 | throw InvalidColLengthError(col_len); 22 | } 23 | int btree_order = (int)((PAGE_SIZE - sizeof(IxPageHdr)) / (col_len + sizeof(Rid)) - 1); 24 | assert(btree_order > 2); 25 | int key_offset = sizeof(IxPageHdr); 26 | int rid_offset = key_offset + (btree_order + 1) * col_len; 27 | 28 | IxFileHdr fhdr(IX_NO_PAGE, IX_INIT_NUM_PAGES, IX_INIT_ROOT_PAGE, col_type, col_len, btree_order, key_offset, 29 | rid_offset, IX_INIT_ROOT_PAGE, IX_INIT_ROOT_PAGE); 30 | static uint8_t page_buf[PAGE_SIZE]; 31 | PfPager::write_page(fd, IX_FILE_HDR_PAGE, (const uint8_t *)&fhdr, sizeof(fhdr)); 32 | // Create leaf list header page and write to file 33 | { 34 | auto phdr = (IxPageHdr *)page_buf; 35 | *phdr = IxPageHdr(IX_NO_PAGE, IX_NO_PAGE, 0, 0, true, IX_INIT_ROOT_PAGE, IX_INIT_ROOT_PAGE); 36 | PfPager::write_page(fd, IX_LEAF_HEADER_PAGE, page_buf, PAGE_SIZE); 37 | } 38 | // Create root node and write to file 39 | { 40 | auto phdr = (IxPageHdr *)page_buf; 41 | *phdr = IxPageHdr(IX_NO_PAGE, IX_NO_PAGE, 0, 0, true, IX_LEAF_HEADER_PAGE, IX_LEAF_HEADER_PAGE); 42 | // Must write PAGE_SIZE here in case of future fetch_node() 43 | PfPager::write_page(fd, IX_INIT_ROOT_PAGE, page_buf, PAGE_SIZE); 44 | } 45 | // Close index file 46 | PfManager::close_file(fd); 47 | } 48 | 49 | void IxManager::destroy_index(const std::string &filename, int index_no) { 50 | std::string ix_name = get_index_name(filename, index_no); 51 | PfManager::destroy_file(ix_name); 52 | } 53 | 54 | std::unique_ptr IxManager::open_index(const std::string &filename, int index_no) { 55 | std::string ix_name = get_index_name(filename, index_no); 56 | int fd = PfManager::open_file(ix_name); 57 | return std::make_unique(fd); 58 | } 59 | 60 | void IxManager::close_index(const IxIndexHandle *ih) { 61 | PfPager::write_page(ih->fd, IX_FILE_HDR_PAGE, (const uint8_t *)&ih->hdr, sizeof(ih->hdr)); 62 | PfManager::close_file(ih->fd); 63 | } 64 | -------------------------------------------------------------------------------- /src/ql/ql_test.cpp: -------------------------------------------------------------------------------- 1 | #include "interp.h" 2 | #include "ql/ql.h" 3 | #include 4 | 5 | void exec_sql(const std::string &sql) { 6 | std::cout << sql << std::endl; 7 | YY_BUFFER_STATE buffer = yy_scan_string(sql.c_str()); 8 | EXPECT_EQ(yyparse(), 0); 9 | EXPECT_TRUE(ast::parse_tree != nullptr); 10 | yy_delete_buffer(buffer); 11 | Interp::interp_sql(ast::parse_tree); 12 | } 13 | 14 | TEST(ql, basic) { 15 | const std::string db_name = "db"; 16 | if (SmManager::is_dir(db_name)) { 17 | SmManager::drop_db(db_name); 18 | } 19 | SmManager::create_db(db_name); 20 | SmManager::open_db(db_name); 21 | 22 | exec_sql("create table tb1(s int, a int, b float, c char(16));"); 23 | exec_sql("create table tb2(x int, y float, z char(16), s int);"); 24 | exec_sql("create index tb1(s);"); 25 | 26 | exec_sql("show tables;"); 27 | exec_sql("desc tb1;"); 28 | 29 | exec_sql("select * from tb1;"); 30 | exec_sql("insert into tb1 values (0, 1, 1.1, 'abc');"); 31 | exec_sql("insert into tb1 values (2, 2, 2.2, 'def');"); 32 | exec_sql("insert into tb1 values (5, 3, 2.2, 'xyz');"); 33 | exec_sql("insert into tb1 values (4, 4, 2.2, '0123456789abcdef');"); 34 | exec_sql("insert into tb1 values (2, 5, -1.11, '');"); 35 | 36 | exec_sql("insert into tb2 values (1, 2., '123', 0);"); 37 | exec_sql("insert into tb2 values (2, 3., '456', 1);"); 38 | exec_sql("insert into tb2 values (3, 1., '789', 2);"); 39 | 40 | exec_sql("select * from tb1, tb2;"); 41 | exec_sql("select * from tb1, tb2 where tb1.s = tb2.s;"); 42 | 43 | exec_sql("create index tb2(s);"); 44 | exec_sql("select * from tb1, tb2;"); 45 | exec_sql("select * from tb1, tb2 where tb1.s = tb2.s;"); 46 | exec_sql("drop index tb2(s);"); 47 | 48 | EXPECT_THROW(exec_sql("insert into tb1 values (0, 1, 2., 100);"), IncompatibleTypeError); 49 | EXPECT_THROW(exec_sql("insert into tb1 values (0, 1, 2., 'abc', 1);"), InvalidValueCountError); 50 | EXPECT_THROW(exec_sql("insert into oops values (1, 2);"), TableNotFoundError); 51 | EXPECT_THROW(exec_sql("create table tb1 (a int, b float);"), TableExistsError); 52 | EXPECT_THROW(exec_sql("create index tb1(oops);"), ColumnNotFoundError); 53 | EXPECT_THROW(exec_sql("create index tb1(s);"), IndexExistsError); 54 | EXPECT_THROW(exec_sql("drop index tb1(a);"), IndexNotFoundError); 55 | EXPECT_THROW(exec_sql("insert into tb1 values (0, 1, 2., '0123456789abcdefg');"), StringOverflowError); 56 | EXPECT_THROW(exec_sql("select x from tb1;"), ColumnNotFoundError); 57 | EXPECT_THROW(exec_sql("select s from tb1, tb2;"), AmbiguousColumnError); 58 | EXPECT_THROW(exec_sql("select * from tb1, tb2 where s = 1;"), AmbiguousColumnError); 59 | EXPECT_THROW(exec_sql("select * from tb1 where s = 'oops';"), IncompatibleTypeError); 60 | 61 | exec_sql("select * from tb1;"); 62 | exec_sql("select * from tb2;"); 63 | exec_sql("select * from tb1, tb2;"); 64 | SmManager::close_db(); 65 | } 66 | -------------------------------------------------------------------------------- /src/ql/ql_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ql/ql_defs.h" 4 | #include "rm/rm.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | struct TabCol { 12 | std::string tab_name; 13 | std::string col_name; 14 | 15 | TabCol() = default; 16 | TabCol(std::string tab_name_, std::string col_name_) 17 | : tab_name(std::move(tab_name_)), col_name(std::move(col_name_)) {} 18 | 19 | friend bool operator<(const TabCol &x, const TabCol &y) { 20 | return std::make_pair(x.tab_name, x.col_name) < std::make_pair(y.tab_name, y.col_name); 21 | } 22 | }; 23 | 24 | struct Value { 25 | ColType type; // type of value 26 | union { 27 | int int_val; // int value 28 | float float_val; // float value 29 | }; 30 | std::string str_val; // string value 31 | 32 | std::shared_ptr raw; // raw record buffer 33 | 34 | void set_int(int int_val_) { 35 | type = TYPE_INT; 36 | int_val = int_val_; 37 | } 38 | 39 | void set_float(float float_val_) { 40 | type = TYPE_FLOAT; 41 | float_val = float_val_; 42 | } 43 | 44 | void set_str(std::string str_val_) { 45 | type = TYPE_STRING; 46 | str_val = std::move(str_val_); 47 | } 48 | 49 | void init_raw(int len) { 50 | assert(raw == nullptr); 51 | raw = std::make_shared(len); 52 | if (type == TYPE_INT) { 53 | assert(len == sizeof(int)); 54 | *(int *)(raw->data) = int_val; 55 | } else if (type == TYPE_FLOAT) { 56 | assert(len == sizeof(float)); 57 | *(float *)(raw->data) = float_val; 58 | } else if (type == TYPE_STRING) { 59 | if (len < (int)str_val.size()) { 60 | throw StringOverflowError(); 61 | } 62 | memset(raw->data, 0, len); 63 | memcpy(raw->data, str_val.c_str(), str_val.size()); 64 | } 65 | } 66 | }; 67 | 68 | enum CompOp { OP_EQ, OP_NE, OP_LT, OP_GT, OP_LE, OP_GE }; 69 | 70 | struct Condition { 71 | TabCol lhs_col; // left-hand side column 72 | CompOp op; // comparison operator 73 | bool is_rhs_val; // true if right-hand side is a value (not a column) 74 | TabCol rhs_col; // right-hand side column 75 | Value rhs_val; // right-hand side value 76 | }; 77 | 78 | struct SetClause { 79 | TabCol lhs; 80 | Value rhs; 81 | 82 | SetClause() = default; 83 | SetClause(TabCol lhs_, Value rhs_) : lhs(std::move(lhs_)), rhs(std::move(rhs_)) {} 84 | }; 85 | 86 | class QlManager { 87 | public: 88 | static void insert_into(const std::string &tab_name, std::vector values); 89 | 90 | static void delete_from(const std::string &tab_name, std::vector conds); 91 | 92 | static void update_set(const std::string &tab_name, std::vector set_clauses, 93 | std::vector conds); 94 | 95 | static void select_from(std::vector sel_cols, const std::vector &tab_names, 96 | std::vector conds); 97 | }; 98 | -------------------------------------------------------------------------------- /src/parser/lex.l: -------------------------------------------------------------------------------- 1 | /* keywords are case insensitive */ 2 | %option caseless 3 | /* we don't need yywrap() function */ 4 | %option noyywrap 5 | /* we don't need yyunput() function */ 6 | %option nounput 7 | /* we don't need input() function */ 8 | %option noinput 9 | /* enable location */ 10 | %option bison-bridge 11 | %option bison-locations 12 | 13 | %{ 14 | #include "parser/ast.h" 15 | #include "yacc.tab.h" 16 | #include 17 | 18 | // automatically update location 19 | #define YY_USER_ACTION \ 20 | yylloc->first_line = yylloc->last_line; \ 21 | yylloc->first_column = yylloc->last_column; \ 22 | for (int i = 0; yytext[i] != '\0'; i++) { \ 23 | if(yytext[i] == '\n') { \ 24 | yylloc->last_line++; \ 25 | yylloc->last_column = 1; \ 26 | } else { \ 27 | yylloc->last_column++; \ 28 | } \ 29 | } 30 | 31 | %} 32 | 33 | alpha [a-zA-Z] 34 | digit [0-9] 35 | white_space [ \t]+ 36 | new_line "\r"|"\n"|"\r\n" 37 | sign "+"|"-" 38 | identifier {alpha}(_|{alpha}|{digit})* 39 | value_int {sign}?{digit}+ 40 | value_float {sign}?{digit}+\.({digit}+)? 41 | value_string '[^']*' 42 | single_op ";"|"("|")"|","|"*"|"="|">"|"<"|"." 43 | 44 | %x STATE_COMMENT 45 | 46 | %% 47 | /* block comment */ 48 | "/*" { BEGIN(STATE_COMMENT); } 49 | "*/" { BEGIN(INITIAL); } 50 | [^*] { /* ignore the text of the comment */ } 51 | \* { /* ignore *'s that aren't part of */ } 52 | /* single line comment */ 53 | "--".* { /* ignore single line comment */ } 54 | /* white space and new line */ 55 | {white_space} { /* ignore white space */ } 56 | {new_line} { /* ignore new line */ } 57 | /* keywords */ 58 | "SHOW" { return SHOW; } 59 | "TABLES" { return TABLES; } 60 | "CREATE" { return CREATE; } 61 | "TABLE" { return TABLE; } 62 | "DROP" { return DROP; } 63 | "DESC" { return DESC; } 64 | "INSERT" { return INSERT; } 65 | "INTO" { return INTO; } 66 | "VALUES" { return VALUES; } 67 | "DELETE" { return DELETE; } 68 | "FROM" { return FROM; } 69 | "WHERE" { return WHERE; } 70 | "UPDATE" { return UPDATE; } 71 | "SET" { return SET; } 72 | "SELECT" { return SELECT; } 73 | "INT" { return INT; } 74 | "CHAR" { return CHAR; } 75 | "FLOAT" { return FLOAT; } 76 | "INDEX" { return INDEX; } 77 | "AND" { return AND; } 78 | "EXIT" { return EXIT; } 79 | "HELP" { return HELP; } 80 | /* operators */ 81 | ">=" { return GEQ; } 82 | "<=" { return LEQ; } 83 | "<>" { return NEQ; } 84 | {single_op} { return yytext[0]; } 85 | /* id */ 86 | {identifier} { 87 | yylval->sv_str = yytext; 88 | return IDENTIFIER; 89 | } 90 | /* literals */ 91 | {value_int} { 92 | yylval->sv_int = atoi(yytext); 93 | return VALUE_INT; 94 | } 95 | {value_float} { 96 | yylval->sv_float = atof(yytext); 97 | return VALUE_FLOAT; 98 | } 99 | {value_string} { 100 | yylval->sv_str = std::string(yytext + 1, strlen(yytext) - 2); 101 | return VALUE_STRING; 102 | } 103 | /* EOF */ 104 | <> { return T_EOF; } 105 | /* unexpected char */ 106 | . { std::cerr << "Lexer Error: unexpected character " << yytext[0] << std::endl; } 107 | %% 108 | -------------------------------------------------------------------------------- /src/rm/rm_file_handle.cpp: -------------------------------------------------------------------------------- 1 | #include "rm/rm_file_handle.h" 2 | #include 3 | 4 | bool RmFileHandle::is_record(const Rid &rid) const { 5 | RmPageHandle ph = fetch_page(rid.page_no); 6 | return Bitmap::test(ph.bitmap, rid.slot_no); 7 | } 8 | 9 | std::unique_ptr RmFileHandle::get_record(const Rid &rid) const { 10 | auto record = std::make_unique(hdr.record_size); 11 | RmPageHandle ph = fetch_page(rid.page_no); 12 | if (!Bitmap::test(ph.bitmap, rid.slot_no)) { 13 | throw RecordNotFoundError(rid.page_no, rid.slot_no); 14 | } 15 | uint8_t *slot = ph.get_slot(rid.slot_no); 16 | memcpy(record->data, slot, hdr.record_size); 17 | record->size = hdr.record_size; 18 | return record; 19 | } 20 | 21 | Rid RmFileHandle::insert_record(uint8_t *buf) { 22 | RmPageHandle ph = create_page(); 23 | // get slot number 24 | int slot_no = Bitmap::first_bit(false, ph.bitmap, hdr.num_records_per_page); 25 | assert(slot_no < hdr.num_records_per_page); 26 | // update bitmap 27 | Bitmap::set(ph.bitmap, slot_no); 28 | // update page header 29 | ph.page->mark_dirty(); 30 | ph.hdr->num_records++; 31 | if (ph.hdr->num_records == hdr.num_records_per_page) { 32 | // page is full 33 | hdr.first_free = ph.hdr->next_free; 34 | } 35 | // copy record data into slot 36 | uint8_t *slot = ph.get_slot(slot_no); 37 | memcpy(slot, buf, hdr.record_size); 38 | Rid rid(ph.page->id.page_no, slot_no); 39 | return rid; 40 | } 41 | 42 | void RmFileHandle::delete_record(const Rid &rid) { 43 | RmPageHandle ph = fetch_page(rid.page_no); 44 | if (!Bitmap::test(ph.bitmap, rid.slot_no)) { 45 | throw RecordNotFoundError(rid.page_no, rid.slot_no); 46 | } 47 | ph.page->mark_dirty(); 48 | if (ph.hdr->num_records == hdr.num_records_per_page) { 49 | // originally full, now available for new record 50 | release_page(ph); 51 | } 52 | Bitmap::reset(ph.bitmap, rid.slot_no); 53 | ph.hdr->num_records--; 54 | } 55 | 56 | void RmFileHandle::update_record(const Rid &rid, uint8_t *buf) { 57 | RmPageHandle ph = fetch_page(rid.page_no); 58 | if (!Bitmap::test(ph.bitmap, rid.slot_no)) { 59 | throw RecordNotFoundError(rid.page_no, rid.slot_no); 60 | } 61 | ph.page->mark_dirty(); 62 | uint8_t *slot = ph.get_slot(rid.slot_no); 63 | memcpy(slot, buf, hdr.record_size); 64 | } 65 | 66 | RmPageHandle RmFileHandle::fetch_page(int page_no) const { 67 | assert(page_no < hdr.num_pages); 68 | Page *page = PfManager::pager.fetch_page(fd, page_no); 69 | RmPageHandle ph(&hdr, page); 70 | return ph; 71 | } 72 | 73 | RmPageHandle RmFileHandle::create_page() { 74 | if (hdr.first_free == RM_NO_PAGE) { 75 | // No free pages. Need to allocate a new page. 76 | Page *page = PfManager::pager.create_page(fd, hdr.num_pages); 77 | // Init page handle 78 | RmPageHandle ph = RmPageHandle(&hdr, page); 79 | ph.hdr->num_records = 0; 80 | ph.hdr->next_free = RM_NO_PAGE; 81 | Bitmap::init(ph.bitmap, hdr.bitmap_size); 82 | // Update file header 83 | hdr.num_pages++; 84 | hdr.first_free = page->id.page_no; 85 | return ph; 86 | } else { 87 | // Fetch the first free page. 88 | RmPageHandle ph = fetch_page(hdr.first_free); 89 | return ph; 90 | } 91 | } 92 | 93 | void RmFileHandle::release_page(RmPageHandle &ph) { 94 | ph.hdr->next_free = hdr.first_free; 95 | hdr.first_free = ph.page->id.page_no; 96 | } 97 | -------------------------------------------------------------------------------- /src/sm/sm_meta.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "error.h" 4 | #include "sm/sm_defs.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | struct ColMeta { 12 | std::string tab_name; 13 | std::string name; 14 | ColType type; 15 | int len; 16 | int offset; 17 | bool index; 18 | 19 | ColMeta() = default; 20 | ColMeta(std::string tab_name_, std::string name_, ColType type_, int len_, int offset_, bool index_) 21 | : tab_name(std::move(tab_name_)), name(std::move(name_)), type(type_), len(len_), offset(offset_), 22 | index(index_) {} 23 | 24 | friend std::ostream &operator<<(std::ostream &os, const ColMeta &col) { 25 | return os << col.tab_name << ' ' << col.name << ' ' << col.type << ' ' << col.len << ' ' << col.offset << ' ' 26 | << col.index; 27 | } 28 | 29 | friend std::istream &operator>>(std::istream &is, ColMeta &col) { 30 | return is >> col.tab_name >> col.name >> col.type >> col.len >> col.offset >> col.index; 31 | } 32 | }; 33 | 34 | struct TabMeta { 35 | std::string name; 36 | std::vector cols; 37 | 38 | bool is_col(const std::string &col_name) const { 39 | auto pos = std::find_if(cols.begin(), cols.end(), [&](const ColMeta &col) { return col.name == col_name; }); 40 | return pos != cols.end(); 41 | } 42 | 43 | std::vector::iterator get_col(const std::string &col_name) { 44 | auto pos = std::find_if(cols.begin(), cols.end(), [&](const ColMeta &col) { return col.name == col_name; }); 45 | if (pos == cols.end()) { 46 | throw ColumnNotFoundError(col_name); 47 | } 48 | return pos; 49 | } 50 | 51 | friend std::ostream &operator<<(std::ostream &os, const TabMeta &tab) { 52 | os << tab.name << '\n' << tab.cols.size() << '\n'; 53 | for (auto &col : tab.cols) { 54 | os << col << '\n'; 55 | } 56 | return os; 57 | } 58 | 59 | friend std::istream &operator>>(std::istream &is, TabMeta &tab) { 60 | size_t n; 61 | is >> tab.name >> n; 62 | for (size_t i = 0; i < n; i++) { 63 | ColMeta col; 64 | is >> col; 65 | tab.cols.push_back(col); 66 | } 67 | return is; 68 | } 69 | }; 70 | 71 | struct DbMeta { 72 | std::string name; 73 | std::map tabs; 74 | 75 | bool is_table(const std::string &tab_name) const { return tabs.find(tab_name) != tabs.end(); } 76 | 77 | TabMeta &get_table(const std::string &tab_name) { 78 | auto pos = tabs.find(tab_name); 79 | if (pos == tabs.end()) { 80 | throw TableNotFoundError(tab_name); 81 | } 82 | return pos->second; 83 | } 84 | 85 | friend std::ostream &operator<<(std::ostream &os, const DbMeta &db_meta) { 86 | os << db_meta.name << '\n' << db_meta.tabs.size() << '\n'; 87 | for (auto &entry : db_meta.tabs) { 88 | os << entry.second << '\n'; 89 | } 90 | return os; 91 | } 92 | 93 | friend std::istream &operator>>(std::istream &is, DbMeta &db_meta) { 94 | size_t n; 95 | is >> db_meta.name >> n; 96 | for (size_t i = 0; i < n; i++) { 97 | TabMeta tab; 98 | is >> tab; 99 | db_meta.tabs[tab.name] = tab; 100 | } 101 | return is; 102 | } 103 | }; 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RedBase-C++ 2 | 3 | ![Demo](fig/demo.gif) 4 | 5 | A simple relational database based on [Stanford CS346 RedBase](https://web.stanford.edu/class/cs346/2015/redbase.html), implemented in elegant modern C++14. 6 | 7 | ## Features 8 | 9 | + Indexing. We implement B+Tree index to accelerate single-table queries as well as table joins. 10 | + Data Persistence. All changes to data are persistent on disk if the shell exits normally. 11 | + SQL support. We support a subset of SQL, including basic DDL (`create table`, `drop table`, `create index`, `drop index`) and DML (`insert`, `delete`, `update`, `select`) statements. 12 | 13 | ## Quick Start 14 | 15 | Prepare a Linux / macOS machine, and clone this project to your local environment. 16 | 17 | ```sh 18 | git clone https://github.com/li-plus/redbase-cpp && cd redbase-cpp 19 | ``` 20 | 21 | Install dependencies of this project. We use CMake as the C++ build system. Flex & Bison are used to generate the SQL parser. `libreadline` helps us to build a user-friendly shell. 22 | 23 | ```sh 24 | sudo apt install cmake flex bison libreadline-dev 25 | ``` 26 | 27 | Then build the project and make optional unittest. 28 | 29 | ```sh 30 | mkdir -p build/ && cd build 31 | cmake .. 32 | make -j 33 | make test # optional 34 | ``` 35 | 36 | Now start the RedBase shell and have fun! 37 | 38 | ```sh 39 | ./bin/redbase mydb 40 | ``` 41 | 42 | This command creates a database named `mydb` for the first time, since it does not exist yet. On the next time, it will directly load the existing database `mydb`. To drop the database `mydb`, simply remove this folder by `rm -r mydb`. 43 | 44 | ## Demo 45 | 46 | Below is a quick demo of the supported main features. 47 | 48 | ```sql 49 | create table student (id int, name char(32), major char(32)); 50 | create index student (id); 51 | create table grade (course char(32), student_id int, score float); 52 | create index grade (student_id); 53 | 54 | show tables; 55 | desc student; 56 | 57 | insert into student values (1, 'Tom', 'Computer Science'); 58 | insert into student values (2, 'Jerry', 'Computer Science'); 59 | insert into student values (3, 'Jack', 'Electrical Engineering'); 60 | 61 | select * from student; 62 | update student set major = 'Electrical Engineering' where id = 2; 63 | select * from student; 64 | delete from student where name = 'Jack'; 65 | select * from student; 66 | 67 | insert into grade values ('Data Structure', 1, 90.0); 68 | insert into grade values ('Data Structure', 2, 95.0); 69 | insert into grade values ('Calculus', 2, 82.0); 70 | insert into grade values ('Calculus', 1, 88.5); 71 | 72 | select * from student, grade; 73 | select id, name, major, course, score from student, grade where student.id = grade.student_id; 74 | 75 | drop index student (id); 76 | desc student; 77 | 78 | drop table student; 79 | drop table grade; 80 | show tables; 81 | 82 | exit; 83 | ``` 84 | 85 | ## Architecture 86 | 87 | ![Architecture](fig/arch.svg) 88 | 89 | + **Paged File Module (PF)** enables higher-level modules to perform efficient file I/O in terms of pages. 90 | + **Record Management Module (RM)** manages the storage of unordered records. 91 | + **Indexing Module (IX)** manages persistent indexes over unordered data records stored in record files. 92 | + **System Management Module (SM)** handles the data definition language (DDL), including `create table`, `drop table`, `create index`, `drop index` statements. 93 | + **Query Language Module (QL)** handles the data manipulation language (DML), including `insert`, `delete`, `update`, `select` statements. 94 | + **SQL Parser** translates a raw SQL statement into an Abstract Syntax Tree (AST), which is further interpreted and executed by the controller. 95 | -------------------------------------------------------------------------------- /src/ql/ql_node.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ql/ql_manager.h" 4 | #include "sm/sm.h" 5 | 6 | class QlNode { 7 | public: 8 | virtual ~QlNode() = default; 9 | 10 | virtual size_t len() const = 0; 11 | virtual const std::vector &cols() const = 0; 12 | 13 | virtual void begin() = 0; 14 | virtual void next() = 0; 15 | virtual bool is_end() const = 0; 16 | 17 | virtual std::unique_ptr rec() const = 0; 18 | virtual void feed(const std::map &feed_dict) = 0; 19 | 20 | static std::vector::const_iterator get_col(const std::vector &rec_cols, const TabCol &target); 21 | 22 | static std::map rec2dict(const std::vector &cols, const RmRecord *rec); 23 | }; 24 | 25 | class QlNodeProj : public QlNode { 26 | public: 27 | QlNodeProj(std::unique_ptr prev, const std::vector &sel_cols); 28 | 29 | size_t len() const override { return _len; } 30 | const std::vector &cols() const override { return _cols; } 31 | 32 | void begin() override { _prev->begin(); } 33 | void next() override { 34 | assert(!_prev->is_end()); 35 | _prev->next(); 36 | } 37 | bool is_end() const override { return _prev->is_end(); } 38 | 39 | std::unique_ptr rec() const override; 40 | 41 | void feed(const std::map &feed_dict) override { 42 | throw InternalError("Cannot feed a projection node"); 43 | } 44 | 45 | private: 46 | std::unique_ptr _prev; 47 | std::vector _cols; 48 | size_t _len; 49 | std::vector _sel_idxs; 50 | }; 51 | 52 | class QlNodeTable : public QlNode { 53 | public: 54 | QlNodeTable(std::string tab_name, std::vector conds); 55 | 56 | void begin() override; 57 | void next() override; 58 | bool is_end() const override; 59 | 60 | size_t len() const override { return _len; } 61 | const std::vector &cols() const override { return _cols; } 62 | 63 | std::unique_ptr rec() const override { 64 | assert(!is_end()); 65 | return _fh->get_record(_rid); 66 | } 67 | 68 | void feed(const std::map &feed_dict) override; 69 | 70 | const Rid &rid() const { return _rid; } 71 | 72 | void check_runtime_conds(); 73 | 74 | static bool eval_cond(const std::vector &rec_cols, const Condition &cond, const RmRecord *rec); 75 | 76 | static bool eval_conds(const std::vector &rec_cols, const std::vector &conds, 77 | const RmRecord *rec); 78 | 79 | private: 80 | std::string _tab_name; 81 | std::vector _conds; 82 | RmFileHandle *_fh; 83 | std::vector _cols; 84 | size_t _len; 85 | std::vector _fed_conds; 86 | 87 | Rid _rid; 88 | std::unique_ptr _scan; 89 | }; 90 | 91 | class QlNodeJoin : public QlNode { 92 | public: 93 | QlNodeJoin(std::unique_ptr left, std::unique_ptr right); 94 | 95 | size_t len() const override { return _len; } 96 | 97 | const std::vector &cols() const override { return _cols; } 98 | 99 | void begin() override; 100 | 101 | void next() override; 102 | 103 | bool is_end() const override { return _left->is_end(); } 104 | 105 | std::unique_ptr rec() const override; 106 | 107 | void feed(const std::map &feed_dict) override; 108 | 109 | void feed_right(); 110 | 111 | private: 112 | std::unique_ptr _left; 113 | std::unique_ptr _right; 114 | size_t _len; 115 | std::vector _cols; 116 | 117 | std::map _prev_feed_dict; 118 | }; 119 | -------------------------------------------------------------------------------- /src/pf/pf_pager.cpp: -------------------------------------------------------------------------------- 1 | #include "pf/pf_pager.h" 2 | #include 3 | #include 4 | 5 | PfPager::PfPager() { 6 | for (size_t i = 0; i < NUM_CACHE_PAGES; i++) { 7 | _pages[i].buf = _cache + i * PAGE_SIZE; 8 | _pages[i].is_dirty = false; 9 | _free_pages.push_back(&_pages[i]); 10 | } 11 | } 12 | 13 | PfPager::~PfPager() { 14 | flush_all(); 15 | assert(_free_pages.size() == NUM_CACHE_PAGES); 16 | } 17 | 18 | void PfPager::read_page(int fd, int page_no, uint8_t *buf, int num_bytes) { 19 | lseek(fd, page_no * PAGE_SIZE, SEEK_SET); 20 | ssize_t bytes_read = read(fd, buf, num_bytes); 21 | if (bytes_read != num_bytes) { 22 | throw UnixError(); 23 | } 24 | } 25 | 26 | void PfPager::write_page(int fd, int page_no, const uint8_t *buf, int num_bytes) { 27 | lseek(fd, page_no * PAGE_SIZE, SEEK_SET); 28 | ssize_t bytes_write = write(fd, buf, num_bytes); 29 | if (bytes_write != num_bytes) { 30 | throw UnixError(); 31 | } 32 | } 33 | 34 | Page *PfPager::create_page(int fd, int page_no) { 35 | Page *page = get_page(fd, page_no); 36 | page->mark_dirty(); 37 | return page; 38 | } 39 | 40 | Page *PfPager::fetch_page(int fd, int page_no) { return get_page(fd, page_no); } 41 | 42 | void PfPager::flush_file(int fd) { 43 | auto it_page = _busy_pages.begin(); 44 | while (it_page != _busy_pages.end()) { 45 | auto prev_page = it_page; 46 | it_page++; 47 | if ((*prev_page)->id.fd == fd) { 48 | flush_page(*prev_page); 49 | } 50 | } 51 | } 52 | 53 | void PfPager::force_page(Page *page) { 54 | if (page->is_dirty) { 55 | write_page(page->id.fd, page->id.page_no, page->buf, PAGE_SIZE); 56 | page->is_dirty = false; 57 | } 58 | } 59 | 60 | template 61 | Page *PfPager::get_page(int fd, int page_no) { 62 | Page *page; 63 | PageId page_id(fd, page_no); 64 | auto map_it = _busy_map.find(page_id); 65 | if (map_it == _busy_map.end()) { 66 | // Page is not in memory (i.e. on disk). Allocate new cache page for it. 67 | if (_free_pages.empty()) { 68 | // Cache is full. Need to flush a page to disk. 69 | assert(!_busy_pages.empty()); 70 | force_page(_busy_pages.back()); 71 | _busy_map.erase(_busy_pages.back()->id); 72 | _busy_pages.splice(_busy_pages.begin(), _busy_pages, --_busy_pages.end()); 73 | } else { 74 | // Cache is not full. Allocate from free pages. 75 | _busy_pages.splice(_busy_pages.begin(), _free_pages, _free_pages.begin()); 76 | } 77 | _busy_map[page_id] = _busy_pages.begin(); 78 | page = _busy_pages.front(); 79 | page->id = page_id; 80 | page->is_dirty = false; 81 | if (EXISTS) { 82 | read_page(fd, page_no, page->buf, PAGE_SIZE); 83 | } 84 | } else { 85 | // Page is in memory 86 | page = *map_it->second; 87 | access(page); 88 | } 89 | return page; 90 | } 91 | 92 | void PfPager::access(Page *page) { 93 | assert(in_cache(page->id)); 94 | _busy_pages.splice(_busy_pages.begin(), _busy_pages, _busy_map[page->id]); 95 | } 96 | 97 | void PfPager::flush_page(Page *page) { 98 | assert(in_cache(page->id)); 99 | auto map_it = _busy_map.find(page->id); 100 | auto busy_it = map_it->second; 101 | force_page(page); 102 | _free_pages.splice(_free_pages.begin(), _busy_pages, busy_it); 103 | _busy_map.erase(map_it); 104 | assert(!in_cache(page->id)); 105 | } 106 | 107 | void PfPager::flush_all() { 108 | for (Page *page : _busy_pages) { 109 | force_page(page); 110 | } 111 | _free_pages.insert(_free_pages.end(), _busy_pages.begin(), _busy_pages.end()); 112 | _busy_pages.clear(); 113 | _busy_map.clear(); 114 | } 115 | -------------------------------------------------------------------------------- /src/error.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class RedBaseError : public std::exception { 8 | std::string _msg; 9 | 10 | public: 11 | RedBaseError(const std::string &msg) : _msg("Error: " + msg) {} 12 | 13 | const char *what() const noexcept override { return _msg.c_str(); } 14 | }; 15 | 16 | class InternalError : public RedBaseError { 17 | public: 18 | InternalError(const std::string &msg) : RedBaseError(msg) {} 19 | }; 20 | 21 | // PF errors 22 | class UnixError : public RedBaseError { 23 | public: 24 | UnixError() : RedBaseError(strerror(errno)) {} 25 | }; 26 | 27 | class FileNotOpenError : public RedBaseError { 28 | public: 29 | FileNotOpenError(int fd) : RedBaseError("Invalid file descriptor: " + std::to_string(fd)) {} 30 | }; 31 | 32 | class FileNotClosedError : public RedBaseError { 33 | public: 34 | FileNotClosedError(const std::string &filename) : RedBaseError("File is opened: " + filename) {} 35 | }; 36 | 37 | class FileExistsError : public RedBaseError { 38 | public: 39 | FileExistsError(const std::string &filename) : RedBaseError("File already exists: " + filename) {} 40 | }; 41 | 42 | class FileNotFoundError : public RedBaseError { 43 | public: 44 | FileNotFoundError(const std::string &filename) : RedBaseError("File not found: " + filename) {} 45 | }; 46 | 47 | // RM errors 48 | class RecordNotFoundError : public RedBaseError { 49 | public: 50 | RecordNotFoundError(int page_no, int slot_no) 51 | : RedBaseError("Record not found: (" + std::to_string(page_no) + "," + std::to_string(slot_no) + ")") {} 52 | }; 53 | 54 | class InvalidRecordSizeError : public RedBaseError { 55 | public: 56 | InvalidRecordSizeError(int record_size) : RedBaseError("Invalid record size: " + std::to_string(record_size)) {} 57 | }; 58 | 59 | // IX errors 60 | class InvalidColLengthError : public RedBaseError { 61 | public: 62 | InvalidColLengthError(int col_len) : RedBaseError("Invalid column length: " + std::to_string(col_len)) {} 63 | }; 64 | 65 | class IndexEntryNotFoundError : public RedBaseError { 66 | public: 67 | IndexEntryNotFoundError() : RedBaseError("Index entry not found") {} 68 | }; 69 | 70 | // SM errors 71 | class DatabaseNotFoundError : public RedBaseError { 72 | public: 73 | DatabaseNotFoundError(const std::string &db_name) : RedBaseError("Database not found: " + db_name) {} 74 | }; 75 | 76 | class DatabaseExistsError : public RedBaseError { 77 | public: 78 | DatabaseExistsError(const std::string &db_name) : RedBaseError("Database already exists: " + db_name) {} 79 | }; 80 | 81 | class TableNotFoundError : public RedBaseError { 82 | public: 83 | TableNotFoundError(const std::string &tab_name) : RedBaseError("Table not found: " + tab_name) {} 84 | }; 85 | 86 | class TableExistsError : public RedBaseError { 87 | public: 88 | TableExistsError(const std::string &tab_name) : RedBaseError("Table already exists: " + tab_name) {} 89 | }; 90 | 91 | class ColumnNotFoundError : public RedBaseError { 92 | public: 93 | ColumnNotFoundError(const std::string &col_name) : RedBaseError("Column not found: " + col_name) {} 94 | }; 95 | 96 | class IndexNotFoundError : public RedBaseError { 97 | public: 98 | IndexNotFoundError(const std::string &tab_name, const std::string &col_name) 99 | : RedBaseError("Index not found: " + tab_name + '.' + col_name) {} 100 | }; 101 | 102 | class IndexExistsError : public RedBaseError { 103 | public: 104 | IndexExistsError(const std::string &tab_name, const std::string &col_name) 105 | : RedBaseError("Index already exists: " + tab_name + '.' + col_name) {} 106 | }; 107 | 108 | // QL errors 109 | class InvalidValueCountError : public RedBaseError { 110 | public: 111 | InvalidValueCountError() : RedBaseError("Invalid value count") {} 112 | }; 113 | 114 | class StringOverflowError : public RedBaseError { 115 | public: 116 | StringOverflowError() : RedBaseError("String is too long") {} 117 | }; 118 | 119 | class IncompatibleTypeError : public RedBaseError { 120 | public: 121 | IncompatibleTypeError(const std::string &lhs, const std::string &rhs) 122 | : RedBaseError("Incompatible type error: lhs " + lhs + ", rhs " + rhs) {} 123 | }; 124 | 125 | class AmbiguousColumnError : public RedBaseError { 126 | public: 127 | AmbiguousColumnError(const std::string &col_name) : RedBaseError("Ambiguous column: " + col_name) {} 128 | }; 129 | -------------------------------------------------------------------------------- /src/rm/rm_test.cpp: -------------------------------------------------------------------------------- 1 | #include "rm/rm.h" 2 | #include 3 | 4 | void rand_buf(int size, uint8_t *out_buf) { 5 | for (int i = 0; i < size; i++) { 6 | out_buf[i] = rand() & 0xff; 7 | } 8 | } 9 | 10 | struct rid_hash_t { 11 | size_t operator()(const Rid &rid) const { return (rid.page_no << 16) | rid.slot_no; } 12 | }; 13 | 14 | struct rid_equal_t { 15 | bool operator()(const Rid &x, const Rid &y) const { return x.page_no == y.page_no && x.slot_no == y.slot_no; } 16 | }; 17 | 18 | void check_equal(const RmFileHandle *fh, const std::unordered_map &mock) { 19 | // Test all records 20 | for (auto &entry : mock) { 21 | Rid rid = entry.first; 22 | auto mock_buf = (uint8_t *)entry.second.c_str(); 23 | auto rec = fh->get_record(rid); 24 | EXPECT_EQ(memcmp(mock_buf, rec->data, fh->hdr.record_size), 0); 25 | } 26 | // Randomly get record 27 | for (int i = 0; i < 10; i++) { 28 | Rid rid(1 + rand() % (fh->hdr.num_pages - 1), rand() % fh->hdr.num_records_per_page); 29 | bool mock_exist = mock.count(rid) > 0; 30 | bool rm_exist = fh->is_record(rid); 31 | EXPECT_EQ(rm_exist, mock_exist); 32 | } 33 | // Test RM scan 34 | size_t num_records = 0; 35 | for (RmScan scan(fh); !scan.is_end(); scan.next()) { 36 | EXPECT_GT(mock.count(scan.rid()), 0); 37 | auto rec = fh->get_record(scan.rid()); 38 | EXPECT_EQ(memcmp(rec->data, mock.at(scan.rid()).c_str(), fh->hdr.record_size), 0); 39 | num_records++; 40 | } 41 | EXPECT_EQ(num_records, mock.size()); 42 | } 43 | 44 | std::ostream &operator<<(std::ostream &os, const Rid &rid) { 45 | return os << '(' << rid.page_no << ", " << rid.slot_no << ')'; 46 | } 47 | 48 | TEST(rm, basic) { 49 | srand((unsigned)time(nullptr)); 50 | 51 | std::unordered_map mock; 52 | 53 | std::string filename = "abc.txt"; 54 | 55 | int record_size = 4 + rand() % 256; 56 | // test files 57 | { 58 | if (PfManager::is_file(filename)) { 59 | PfManager::destroy_file(filename); 60 | } 61 | RmManager::create_file(filename, record_size); 62 | auto fh = RmManager::open_file(filename); 63 | EXPECT_EQ(fh->hdr.record_size, record_size); 64 | EXPECT_EQ(fh->hdr.first_free, RM_NO_PAGE); 65 | EXPECT_EQ(fh->hdr.num_pages, 1); 66 | int max_bytes = 67 | fh->hdr.record_size * fh->hdr.num_records_per_page + fh->hdr.bitmap_size + (int)sizeof(RmPageHdr); 68 | EXPECT_LE(max_bytes, PAGE_SIZE); 69 | int rand_val = rand(); 70 | fh->hdr.num_pages = rand_val; 71 | RmManager::close_file(fh.get()); 72 | // reopen file 73 | fh = RmManager::open_file(filename); 74 | EXPECT_EQ(fh->hdr.num_pages, rand_val); 75 | RmManager::close_file(fh.get()); 76 | RmManager::destroy_file(filename); 77 | } 78 | // test pages 79 | RmManager::create_file(filename, record_size); 80 | auto fh = RmManager::open_file(filename); 81 | 82 | uint8_t write_buf[PAGE_SIZE]; 83 | size_t add_cnt = 0; 84 | size_t upd_cnt = 0; 85 | size_t del_cnt = 0; 86 | for (int round = 0; round < 10000; round++) { 87 | double insert_prob = 1. - mock.size() / 2500.; 88 | double dice = rand() * 1. / RAND_MAX; 89 | if (mock.empty() || dice < insert_prob) { 90 | rand_buf(fh->hdr.record_size, write_buf); 91 | Rid rid = fh->insert_record(write_buf); 92 | mock[rid] = std::string((char *)write_buf, fh->hdr.record_size); 93 | add_cnt++; 94 | // std::cout << "insert " << rid << '\n'; 95 | } else { 96 | // update or erase random rid 97 | int rid_idx = rand() % mock.size(); 98 | auto it = mock.begin(); 99 | for (int i = 0; i < rid_idx; i++) { 100 | it++; 101 | } 102 | auto rid = it->first; 103 | if (rand() % 2 == 0) { 104 | // update 105 | rand_buf(fh->hdr.record_size, write_buf); 106 | fh->update_record(rid, write_buf); 107 | mock[rid] = std::string((char *)write_buf, fh->hdr.record_size); 108 | upd_cnt++; 109 | // std::cout << "update " << rid << '\n'; 110 | } else { 111 | // erase 112 | fh->delete_record(rid); 113 | mock.erase(rid); 114 | del_cnt++; 115 | // std::cout << "delete " << rid << '\n'; 116 | } 117 | } 118 | // Randomly re-open file 119 | if (round % 500 == 0) { 120 | RmManager::close_file(fh.get()); 121 | fh = RmManager::open_file(filename); 122 | } 123 | check_equal(fh.get(), mock); 124 | } 125 | EXPECT_EQ(mock.size(), add_cnt - del_cnt); 126 | std::cout << "insert " << add_cnt << '\n' << "delete " << del_cnt << '\n' << "update " << upd_cnt << '\n'; 127 | // clean up 128 | RmManager::close_file(fh.get()); 129 | RmManager::destroy_file(filename); 130 | } -------------------------------------------------------------------------------- /src/parser/ast_printer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "parser/ast.h" 4 | #include 5 | #include 6 | #include 7 | 8 | namespace ast { 9 | 10 | class TreePrinter { 11 | public: 12 | static void print(const std::shared_ptr &node) { print_node(node, 0); } 13 | 14 | private: 15 | static std::string offset2string(int offset) { return std::string(offset, ' '); } 16 | 17 | template 18 | static void print_val(const T &val, int offset) { 19 | std::cout << offset2string(offset) << val << '\n'; 20 | } 21 | 22 | template 23 | static void print_val_list(const std::vector &vals, int offset) { 24 | std::cout << offset2string(offset) << "LIST\n"; 25 | offset += 2; 26 | for (auto &val : vals) { 27 | print_val(val, offset); 28 | } 29 | } 30 | 31 | static std::string type2str(SvType type) { 32 | static std::map m{ 33 | {SV_TYPE_INT, "INT"}, 34 | {SV_TYPE_FLOAT, "FLOAT"}, 35 | {SV_TYPE_STRING, "STRING"}, 36 | }; 37 | return m.at(type); 38 | } 39 | 40 | static std::string op2str(SvCompOp op) { 41 | static std::map m{ 42 | {SV_OP_EQ, "=="}, {SV_OP_NE, "!="}, {SV_OP_LT, "<"}, {SV_OP_GT, ">"}, {SV_OP_LE, "<="}, {SV_OP_GE, ">="}, 43 | }; 44 | return m.at(op); 45 | } 46 | 47 | template 48 | static void print_node_list(std::vector nodes, int offset) { 49 | std::cout << offset2string(offset); 50 | offset += 2; 51 | std::cout << "LIST\n"; 52 | for (auto &node : nodes) { 53 | print_node(node, offset); 54 | } 55 | } 56 | 57 | static void print_node(const std::shared_ptr &node, int offset) { 58 | std::cout << offset2string(offset); 59 | offset += 2; 60 | if (auto x = std::dynamic_pointer_cast(node)) { 61 | std::cout << "HELP\n"; 62 | } else if (auto x = std::dynamic_pointer_cast(node)) { 63 | std::cout << "SHOW_TABLES\n"; 64 | } else if (auto x = std::dynamic_pointer_cast(node)) { 65 | std::cout << "CREATE_TABLE\n"; 66 | print_val(x->tab_name, offset); 67 | print_node_list(x->fields, offset); 68 | } else if (auto x = std::dynamic_pointer_cast(node)) { 69 | std::cout << "DROP_TABLE\n"; 70 | print_val(x->tab_name, offset); 71 | } else if (auto x = std::dynamic_pointer_cast(node)) { 72 | std::cout << "DESC_TABLE\n"; 73 | print_val(x->tab_name, offset); 74 | } else if (auto x = std::dynamic_pointer_cast(node)) { 75 | std::cout << "CREATE_INDEX\n"; 76 | print_val(x->tab_name, offset); 77 | print_val(x->col_name, offset); 78 | } else if (auto x = std::dynamic_pointer_cast(node)) { 79 | std::cout << "DROP_INDEX\n"; 80 | print_val(x->tab_name, offset); 81 | print_val(x->col_name, offset); 82 | } else if (auto x = std::dynamic_pointer_cast(node)) { 83 | std::cout << "COL_DEF\n"; 84 | print_val(x->col_name, offset); 85 | print_node(x->type_len, offset); 86 | } else if (auto x = std::dynamic_pointer_cast(node)) { 87 | std::cout << "COL\n"; 88 | print_val(x->tab_name, offset); 89 | print_val(x->col_name, offset); 90 | } else if (auto x = std::dynamic_pointer_cast(node)) { 91 | std::cout << "TYPE_LEN\n"; 92 | print_val(type2str(x->type), offset); 93 | print_val(x->len, offset); 94 | } else if (auto x = std::dynamic_pointer_cast(node)) { 95 | std::cout << "INT_LIT\n"; 96 | print_val(x->val, offset); 97 | } else if (auto x = std::dynamic_pointer_cast(node)) { 98 | std::cout << "FLOAT_LIT\n"; 99 | print_val(x->val, offset); 100 | } else if (auto x = std::dynamic_pointer_cast(node)) { 101 | std::cout << "STRING_LIT\n"; 102 | print_val(x->val, offset); 103 | } else if (auto x = std::dynamic_pointer_cast(node)) { 104 | std::cout << "SET_CLAUSE\n"; 105 | print_val(x->col_name, offset); 106 | print_node(x->val, offset); 107 | } else if (auto x = std::dynamic_pointer_cast(node)) { 108 | std::cout << "BINARY_EXPR\n"; 109 | print_node(x->lhs, offset); 110 | print_val(op2str(x->op), offset); 111 | print_node(x->rhs, offset); 112 | } else if (auto x = std::dynamic_pointer_cast(node)) { 113 | std::cout << "INSERT\n"; 114 | print_val(x->tab_name, offset); 115 | print_node_list(x->vals, offset); 116 | } else if (auto x = std::dynamic_pointer_cast(node)) { 117 | std::cout << "DELETE\n"; 118 | print_val(x->tab_name, offset); 119 | print_node_list(x->conds, offset); 120 | } else if (auto x = std::dynamic_pointer_cast(node)) { 121 | std::cout << "UPDATE\n"; 122 | print_val(x->tab_name, offset); 123 | print_node_list(x->set_clauses, offset); 124 | print_node_list(x->conds, offset); 125 | } else if (auto x = std::dynamic_pointer_cast(node)) { 126 | std::cout << "SELECT\n"; 127 | print_node_list(x->cols, offset); 128 | print_val_list(x->tabs, offset); 129 | print_node_list(x->conds, offset); 130 | } else { 131 | assert(0); 132 | } 133 | } 134 | }; 135 | 136 | } // namespace ast 137 | -------------------------------------------------------------------------------- /src/parser/ast.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ast { 8 | 9 | enum SvType { SV_TYPE_INT, SV_TYPE_FLOAT, SV_TYPE_STRING }; 10 | 11 | enum SvCompOp { SV_OP_EQ, SV_OP_NE, SV_OP_LT, SV_OP_GT, SV_OP_LE, SV_OP_GE }; 12 | 13 | // Base class for tree nodes 14 | struct TreeNode { 15 | virtual ~TreeNode() = default; // enable polymorphism 16 | }; 17 | 18 | struct Help : public TreeNode {}; 19 | 20 | struct ShowTables : public TreeNode {}; 21 | 22 | struct TypeLen : public TreeNode { 23 | SvType type; 24 | int len; 25 | 26 | TypeLen(SvType type_, int len_) : type(type_), len(len_) {} 27 | }; 28 | 29 | struct Field : public TreeNode {}; 30 | 31 | struct ColDef : public Field { 32 | std::string col_name; 33 | std::shared_ptr type_len; 34 | 35 | ColDef(std::string col_name_, std::shared_ptr type_len_) 36 | : col_name(std::move(col_name_)), type_len(std::move(type_len_)) {} 37 | }; 38 | 39 | struct CreateTable : public TreeNode { 40 | std::string tab_name; 41 | std::vector> fields; 42 | 43 | CreateTable(std::string tab_name_, std::vector> fields_) 44 | : tab_name(std::move(tab_name_)), fields(std::move(fields_)) {} 45 | }; 46 | 47 | struct DropTable : public TreeNode { 48 | std::string tab_name; 49 | 50 | DropTable(std::string tab_name_) : tab_name(std::move(tab_name_)) {} 51 | }; 52 | 53 | struct DescTable : public TreeNode { 54 | std::string tab_name; 55 | 56 | DescTable(std::string tab_name_) : tab_name(std::move(tab_name_)) {} 57 | }; 58 | 59 | struct CreateIndex : public TreeNode { 60 | std::string tab_name; 61 | std::string col_name; 62 | 63 | CreateIndex(std::string tab_name_, std::string col_name_) 64 | : tab_name(std::move(tab_name_)), col_name(std::move(col_name_)) {} 65 | }; 66 | 67 | struct DropIndex : public TreeNode { 68 | std::string tab_name; 69 | std::string col_name; 70 | 71 | DropIndex(std::string tab_name_, std::string col_name_) 72 | : tab_name(std::move(tab_name_)), col_name(std::move(col_name_)) {} 73 | }; 74 | 75 | struct Expr : public TreeNode {}; 76 | 77 | struct Value : public Expr {}; 78 | 79 | struct IntLit : public Value { 80 | int val; 81 | 82 | IntLit(int val_) : val(val_) {} 83 | }; 84 | 85 | struct FloatLit : public Value { 86 | float val; 87 | 88 | FloatLit(float val_) : val(val_) {} 89 | }; 90 | 91 | struct StringLit : public Value { 92 | std::string val; 93 | 94 | StringLit(std::string val_) : val(std::move(val_)) {} 95 | }; 96 | 97 | struct Col : public Expr { 98 | std::string tab_name; 99 | std::string col_name; 100 | 101 | Col(std::string tab_name_, std::string col_name_) 102 | : tab_name(std::move(tab_name_)), col_name(std::move(col_name_)) {} 103 | }; 104 | 105 | struct SetClause : public TreeNode { 106 | std::string col_name; 107 | std::shared_ptr val; 108 | 109 | SetClause(std::string col_name_, std::shared_ptr val_) 110 | : col_name(std::move(col_name_)), val(std::move(val_)) {} 111 | }; 112 | 113 | struct BinaryExpr : public TreeNode { 114 | std::shared_ptr lhs; 115 | SvCompOp op; 116 | std::shared_ptr rhs; 117 | 118 | BinaryExpr(std::shared_ptr lhs_, SvCompOp op_, std::shared_ptr rhs_) 119 | : lhs(std::move(lhs_)), op(op_), rhs(std::move(rhs_)) {} 120 | }; 121 | 122 | struct InsertStmt : public TreeNode { 123 | std::string tab_name; 124 | std::vector> vals; 125 | 126 | InsertStmt(std::string tab_name_, std::vector> vals_) 127 | : tab_name(std::move(tab_name_)), vals(std::move(vals_)) {} 128 | }; 129 | 130 | struct DeleteStmt : public TreeNode { 131 | std::string tab_name; 132 | std::vector> conds; 133 | 134 | DeleteStmt(std::string tab_name_, std::vector> conds_) 135 | : tab_name(std::move(tab_name_)), conds(std::move(conds_)) {} 136 | }; 137 | 138 | struct UpdateStmt : public TreeNode { 139 | std::string tab_name; 140 | std::vector> set_clauses; 141 | std::vector> conds; 142 | 143 | UpdateStmt(std::string tab_name_, std::vector> set_clauses_, 144 | std::vector> conds_) 145 | : tab_name(std::move(tab_name_)), set_clauses(std::move(set_clauses_)), conds(std::move(conds_)) {} 146 | }; 147 | 148 | struct SelectStmt : public TreeNode { 149 | std::vector> cols; 150 | std::vector tabs; 151 | std::vector> conds; 152 | 153 | SelectStmt(std::vector> cols_, std::vector tabs_, 154 | std::vector> conds_) 155 | : cols(std::move(cols_)), tabs(std::move(tabs_)), conds(std::move(conds_)) {} 156 | }; 157 | 158 | // Semantic value 159 | struct SemValue { 160 | int sv_int; 161 | float sv_float; 162 | std::string sv_str; 163 | std::vector sv_strs; 164 | 165 | std::shared_ptr sv_node; 166 | 167 | SvCompOp sv_comp_op; 168 | 169 | std::shared_ptr sv_type_len; 170 | 171 | std::shared_ptr sv_field; 172 | std::vector> sv_fields; 173 | 174 | std::shared_ptr sv_expr; 175 | 176 | std::shared_ptr sv_val; 177 | std::vector> sv_vals; 178 | 179 | std::shared_ptr sv_col; 180 | std::vector> sv_cols; 181 | 182 | std::shared_ptr sv_set_clause; 183 | std::vector> sv_set_clauses; 184 | 185 | std::shared_ptr sv_cond; 186 | std::vector> sv_conds; 187 | }; 188 | 189 | extern std::shared_ptr parse_tree; 190 | 191 | } // namespace ast 192 | 193 | #define YYSTYPE ast::SemValue 194 | -------------------------------------------------------------------------------- /src/interp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "parser/parser.h" 4 | #include "ql/ql.h" 5 | #include "sm/sm.h" 6 | #include 7 | 8 | class Interp { 9 | public: 10 | static void interp_sql(const std::shared_ptr &root) { 11 | if (auto x = std::dynamic_pointer_cast(root)) { 12 | std::cout 13 | << "Supported SQL syntax:\n" 14 | " command ;\n" 15 | "command:\n" 16 | " CREATE TABLE table_name (column_name type [, column_name type ...])\n" 17 | " DROP TABLE table_name\n" 18 | " CREATE INDEX table_name (column_name)\n" 19 | " DROP INDEX table_name (column_name)\n" 20 | " INSERT INTO table_name VALUES (value [, value ...])\n" 21 | " DELETE FROM table_name [WHERE where_clause]\n" 22 | " UPDATE table_name SET column_name = value [, column_name = value ...] [WHERE where_clause]\n" 23 | " SELECT selector FROM table_name [WHERE where_clause]\n" 24 | "type:\n" 25 | " {INT | FLOAT | CHAR(n)}\n" 26 | "where_clause:\n" 27 | " condition [AND condition ...]\n" 28 | "condition:\n" 29 | " column op {column | value}\n" 30 | "column:\n" 31 | " [table_name.]column_name\n" 32 | "op:\n" 33 | " {= | <> | < | > | <= | >=}\n" 34 | "selector:\n" 35 | " {* | column [, column ...]}\n"; 36 | } else if (auto x = std::dynamic_pointer_cast(root)) { 37 | SmManager::show_tables(); 38 | } else if (auto x = std::dynamic_pointer_cast(root)) { 39 | SmManager::desc_table(x->tab_name); 40 | } else if (auto x = std::dynamic_pointer_cast(root)) { 41 | std::vector col_defs; 42 | for (auto &field : x->fields) { 43 | if (auto sv_col_def = std::dynamic_pointer_cast(field)) { 44 | ColDef col_def(sv_col_def->col_name, interp_sv_type(sv_col_def->type_len->type), 45 | sv_col_def->type_len->len); 46 | col_defs.push_back(col_def); 47 | } else { 48 | throw InternalError("Unexpected field type"); 49 | } 50 | } 51 | SmManager::create_table(x->tab_name, col_defs); 52 | } else if (auto x = std::dynamic_pointer_cast(root)) { 53 | SmManager::drop_table(x->tab_name); 54 | } else if (auto x = std::dynamic_pointer_cast(root)) { 55 | SmManager::create_index(x->tab_name, x->col_name); 56 | } else if (auto x = std::dynamic_pointer_cast(root)) { 57 | SmManager::drop_index(x->tab_name, x->col_name); 58 | } else if (auto x = std::dynamic_pointer_cast(root)) { 59 | std::vector values; 60 | for (auto &sv_val : x->vals) { 61 | values.push_back(interp_sv_value(sv_val)); 62 | } 63 | QlManager::insert_into(x->tab_name, values); 64 | } else if (auto x = std::dynamic_pointer_cast(root)) { 65 | std::vector conds = interp_where_clause(x->conds); 66 | QlManager::delete_from(x->tab_name, conds); 67 | } else if (auto x = std::dynamic_pointer_cast(root)) { 68 | std::vector conds = interp_where_clause(x->conds); 69 | std::vector set_clauses; 70 | for (auto &sv_set_clause : x->set_clauses) { 71 | SetClause set_clause(TabCol("", sv_set_clause->col_name), interp_sv_value(sv_set_clause->val)); 72 | set_clauses.push_back(set_clause); 73 | } 74 | QlManager::update_set(x->tab_name, set_clauses, conds); 75 | } else if (auto x = std::dynamic_pointer_cast(root)) { 76 | std::vector conds = interp_where_clause(x->conds); 77 | std::vector sel_cols; 78 | for (auto &sv_sel_col : x->cols) { 79 | TabCol sel_col(sv_sel_col->tab_name, sv_sel_col->col_name); 80 | sel_cols.push_back(sel_col); 81 | } 82 | QlManager::select_from(sel_cols, x->tabs, conds); 83 | } else { 84 | throw InternalError("Unexpected AST root"); 85 | } 86 | } 87 | 88 | private: 89 | static ColType interp_sv_type(ast::SvType sv_type) { 90 | static std::map m = { 91 | {ast::SV_TYPE_INT, TYPE_INT}, {ast::SV_TYPE_FLOAT, TYPE_FLOAT}, {ast::SV_TYPE_STRING, TYPE_STRING}}; 92 | return m.at(sv_type); 93 | } 94 | 95 | static CompOp interp_sv_comp_op(ast::SvCompOp op) { 96 | static std::map m = { 97 | {ast::SV_OP_EQ, OP_EQ}, {ast::SV_OP_NE, OP_NE}, {ast::SV_OP_LT, OP_LT}, 98 | {ast::SV_OP_GT, OP_GT}, {ast::SV_OP_LE, OP_LE}, {ast::SV_OP_GE, OP_GE}, 99 | }; 100 | return m.at(op); 101 | } 102 | 103 | static Value interp_sv_value(const std::shared_ptr &sv_val) { 104 | Value val; 105 | if (auto int_lit = std::dynamic_pointer_cast(sv_val)) { 106 | val.set_int(int_lit->val); 107 | } else if (auto float_lit = std::dynamic_pointer_cast(sv_val)) { 108 | val.set_float(float_lit->val); 109 | } else if (auto str_lit = std::dynamic_pointer_cast(sv_val)) { 110 | val.set_str(str_lit->val); 111 | } else { 112 | throw InternalError("Unexpected sv value type"); 113 | } 114 | return val; 115 | } 116 | 117 | static std::vector interp_where_clause(const std::vector> &sv_conds) { 118 | std::vector conds; 119 | for (auto &expr : sv_conds) { 120 | Condition cond; 121 | cond.lhs_col = TabCol(expr->lhs->tab_name, expr->lhs->col_name); 122 | cond.op = interp_sv_comp_op(expr->op); 123 | if (auto rhs_val = std::dynamic_pointer_cast(expr->rhs)) { 124 | cond.is_rhs_val = true; 125 | cond.rhs_val = interp_sv_value(rhs_val); 126 | } else if (auto rhs_col = std::dynamic_pointer_cast(expr->rhs)) { 127 | cond.is_rhs_val = false; 128 | cond.rhs_col = TabCol(rhs_col->tab_name, rhs_col->col_name); 129 | } 130 | conds.push_back(cond); 131 | } 132 | return conds; 133 | } 134 | }; 135 | -------------------------------------------------------------------------------- /src/parser/yacc.y: -------------------------------------------------------------------------------- 1 | %{ 2 | #include "parser/ast.h" 3 | #include "yacc.tab.h" 4 | #include 5 | #include 6 | 7 | int yylex(YYSTYPE *yylval, YYLTYPE *yylloc); 8 | 9 | void yyerror(YYLTYPE *locp, const char* s) { 10 | std::cerr << "Parser Error at line " << locp->first_line << " column " << locp->first_column << ": " << s << std::endl; 11 | } 12 | 13 | using namespace ast; 14 | %} 15 | 16 | // request a pure (reentrant) parser 17 | %define api.pure full 18 | // enable location in error handler 19 | %locations 20 | // enable verbose syntax error message 21 | %define parse.error verbose 22 | 23 | // keywords 24 | %token SHOW TABLES CREATE TABLE DROP DESC INSERT INTO VALUES DELETE FROM 25 | WHERE UPDATE SET SELECT INT CHAR FLOAT INDEX AND EXIT HELP 26 | // non-keywords 27 | %token LEQ NEQ GEQ T_EOF 28 | 29 | // type-specific tokens 30 | %token IDENTIFIER VALUE_STRING 31 | %token VALUE_INT 32 | %token VALUE_FLOAT 33 | 34 | // specify types for non-terminal symbol 35 | %type stmt dbStmt ddl dml 36 | %type field 37 | %type fieldList 38 | %type type 39 | %type op 40 | %type expr 41 | %type value 42 | %type valueList 43 | %type tbName colName 44 | %type tableList 45 | %type col 46 | %type colList selector 47 | %type setClause 48 | %type setClauses 49 | %type condition 50 | %type whereClause optWhereClause 51 | 52 | %% 53 | start: 54 | stmt ';' 55 | { 56 | parse_tree = $1; 57 | YYACCEPT; 58 | } 59 | | HELP 60 | { 61 | parse_tree = std::make_shared(); 62 | YYACCEPT; 63 | } 64 | | EXIT 65 | { 66 | parse_tree = nullptr; 67 | YYACCEPT; 68 | } 69 | | T_EOF 70 | { 71 | parse_tree = nullptr; 72 | YYACCEPT; 73 | } 74 | ; 75 | 76 | stmt: 77 | dbStmt 78 | | ddl 79 | | dml 80 | ; 81 | 82 | dbStmt: 83 | SHOW TABLES 84 | { 85 | $$ = std::make_shared(); 86 | } 87 | ; 88 | 89 | ddl: 90 | CREATE TABLE tbName '(' fieldList ')' 91 | { 92 | $$ = std::make_shared($3, $5); 93 | } 94 | | DROP TABLE tbName 95 | { 96 | $$ = std::make_shared($3); 97 | } 98 | | DESC tbName 99 | { 100 | $$ = std::make_shared($2); 101 | } 102 | | CREATE INDEX tbName '(' colName ')' 103 | { 104 | $$ = std::make_shared($3, $5); 105 | } 106 | | DROP INDEX tbName '(' colName ')' 107 | { 108 | $$ = std::make_shared($3, $5); 109 | } 110 | ; 111 | 112 | dml: 113 | INSERT INTO tbName VALUES '(' valueList ')' 114 | { 115 | $$ = std::make_shared($3, $6); 116 | } 117 | | DELETE FROM tbName optWhereClause 118 | { 119 | $$ = std::make_shared($3, $4); 120 | } 121 | | UPDATE tbName SET setClauses optWhereClause 122 | { 123 | $$ = std::make_shared($2, $4, $5); 124 | } 125 | | SELECT selector FROM tableList optWhereClause 126 | { 127 | $$ = std::make_shared($2, $4, $5); 128 | } 129 | ; 130 | 131 | fieldList: 132 | field 133 | { 134 | $$ = std::vector>{$1}; 135 | } 136 | | fieldList ',' field 137 | { 138 | $$.push_back($3); 139 | } 140 | ; 141 | 142 | field: 143 | colName type 144 | { 145 | $$ = std::make_shared($1, $2); 146 | } 147 | ; 148 | 149 | type: 150 | INT 151 | { 152 | $$ = std::make_shared(SV_TYPE_INT, sizeof(int)); 153 | } 154 | | CHAR '(' VALUE_INT ')' 155 | { 156 | $$ = std::make_shared(SV_TYPE_STRING, $3); 157 | } 158 | | FLOAT 159 | { 160 | $$ = std::make_shared(SV_TYPE_FLOAT, sizeof(float)); 161 | } 162 | ; 163 | 164 | valueList: 165 | value 166 | { 167 | $$ = std::vector>{$1}; 168 | } 169 | | valueList ',' value 170 | { 171 | $$.push_back($3); 172 | } 173 | ; 174 | 175 | value: 176 | VALUE_INT 177 | { 178 | $$ = std::make_shared($1); 179 | } 180 | | VALUE_FLOAT 181 | { 182 | $$ = std::make_shared($1); 183 | } 184 | | VALUE_STRING 185 | { 186 | $$ = std::make_shared($1); 187 | } 188 | ; 189 | 190 | condition: 191 | col op expr 192 | { 193 | $$ = std::make_shared($1, $2, $3); 194 | } 195 | ; 196 | 197 | optWhereClause: 198 | /* epsilon */ { /* ignore*/ } 199 | | WHERE whereClause 200 | { 201 | $$ = $2; 202 | } 203 | ; 204 | 205 | whereClause: 206 | condition 207 | { 208 | $$ = std::vector>{$1}; 209 | } 210 | | whereClause AND condition 211 | { 212 | $$.push_back($3); 213 | } 214 | ; 215 | 216 | col: 217 | tbName '.' colName 218 | { 219 | $$ = std::make_shared($1, $3); 220 | } 221 | | colName 222 | { 223 | $$ = std::make_shared("", $1); 224 | } 225 | ; 226 | 227 | colList: 228 | col 229 | { 230 | $$ = std::vector>{$1}; 231 | } 232 | | colList ',' col 233 | { 234 | $$.push_back($3); 235 | } 236 | ; 237 | 238 | op: 239 | '=' 240 | { 241 | $$ = SV_OP_EQ; 242 | } 243 | | '<' 244 | { 245 | $$ = SV_OP_LT; 246 | } 247 | | '>' 248 | { 249 | $$ = SV_OP_GT; 250 | } 251 | | NEQ 252 | { 253 | $$ = SV_OP_NE; 254 | } 255 | | LEQ 256 | { 257 | $$ = SV_OP_LE; 258 | } 259 | | GEQ 260 | { 261 | $$ = SV_OP_GE; 262 | } 263 | ; 264 | 265 | expr: 266 | value 267 | { 268 | $$ = std::static_pointer_cast($1); 269 | } 270 | | col 271 | { 272 | $$ = std::static_pointer_cast($1); 273 | } 274 | ; 275 | 276 | setClauses: 277 | setClause 278 | { 279 | $$ = std::vector>{$1}; 280 | } 281 | | setClauses ',' setClause 282 | { 283 | $$.push_back($3); 284 | } 285 | ; 286 | 287 | setClause: 288 | colName '=' value 289 | { 290 | $$ = std::make_shared($1, $3); 291 | } 292 | ; 293 | 294 | selector: 295 | '*' 296 | { 297 | $$ = {}; 298 | } 299 | | colList 300 | ; 301 | 302 | tableList: 303 | tbName 304 | { 305 | $$ = std::vector{$1}; 306 | } 307 | | tableList ',' tbName 308 | { 309 | $$.push_back($3); 310 | } 311 | ; 312 | 313 | tbName: IDENTIFIER; 314 | 315 | colName: IDENTIFIER; 316 | %% 317 | -------------------------------------------------------------------------------- /src/sm/sm_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "sm/sm_manager.h" 2 | #include "ix/ix.h" 3 | #include "record_printer.h" 4 | #include "rm/rm.h" 5 | #include 6 | #include 7 | #include 8 | 9 | DbMeta SmManager::db; 10 | std::map> SmManager::fhs; 11 | std::map> SmManager::ihs; 12 | 13 | bool SmManager::is_dir(const std::string &db_name) { 14 | struct stat st; 15 | return stat(db_name.c_str(), &st) == 0 && S_ISDIR(st.st_mode); 16 | } 17 | 18 | void SmManager::create_db(const std::string &db_name) { 19 | if (is_dir(db_name)) { 20 | throw DatabaseExistsError(db_name); 21 | } 22 | // Create a subdirectory for the database 23 | std::string cmd = "mkdir " + db_name; 24 | if (system(cmd.c_str()) < 0) { 25 | throw UnixError(); 26 | } 27 | if (chdir(db_name.c_str()) < 0) { 28 | throw UnixError(); 29 | } 30 | // Create the system catalogs 31 | DbMeta new_db; 32 | new_db.name = db_name; 33 | std::ofstream ofs(DB_META_NAME); 34 | ofs << new_db; 35 | // cd back to root dir 36 | if (chdir("..") < 0) { 37 | throw UnixError(); 38 | } 39 | } 40 | 41 | void SmManager::drop_db(const std::string &db_name) { 42 | if (!is_dir(db_name)) { 43 | throw DatabaseNotFoundError(db_name); 44 | } 45 | std::string cmd = "rm -r " + db_name; 46 | if (system(cmd.c_str()) < 0) { 47 | throw UnixError(); 48 | } 49 | } 50 | 51 | void SmManager::open_db(const std::string &db_name) { 52 | if (!is_dir(db_name)) { 53 | throw DatabaseNotFoundError(db_name); 54 | } 55 | // cd to database dir 56 | if (chdir(db_name.c_str()) < 0) { 57 | throw UnixError(); 58 | } 59 | // Load meta 60 | std::ifstream ifs(DB_META_NAME); 61 | ifs >> db; 62 | // Open all record files & index files 63 | for (auto &entry : db.tabs) { 64 | auto &tab = entry.second; 65 | fhs[tab.name] = RmManager::open_file(tab.name); 66 | for (size_t i = 0; i < tab.cols.size(); i++) { 67 | auto &col = tab.cols[i]; 68 | if (col.index) { 69 | auto index_name = IxManager::get_index_name(tab.name, i); 70 | assert(ihs.count(index_name) == 0); 71 | ihs[index_name] = IxManager::open_index(tab.name, i); 72 | } 73 | } 74 | } 75 | } 76 | 77 | void SmManager::close_db() { 78 | // Dump meta 79 | std::ofstream ofs(DB_META_NAME); 80 | ofs << db; 81 | db.name.clear(); 82 | db.tabs.clear(); 83 | // Close all record files 84 | for (auto &entry : fhs) { 85 | RmManager::close_file(entry.second.get()); 86 | } 87 | fhs.clear(); 88 | // Close all index files 89 | for (auto &entry : ihs) { 90 | IxManager::close_index(entry.second.get()); 91 | } 92 | ihs.clear(); 93 | if (chdir("..") < 0) { 94 | throw UnixError(); 95 | } 96 | } 97 | 98 | void SmManager::show_tables() { 99 | RecordPrinter printer(1); 100 | printer.print_separator(); 101 | printer.print_record({"Tables"}); 102 | printer.print_separator(); 103 | for (auto &entry : db.tabs) { 104 | auto &tab = entry.second; 105 | printer.print_record({tab.name}); 106 | } 107 | printer.print_separator(); 108 | } 109 | 110 | void SmManager::desc_table(const std::string &tab_name) { 111 | TabMeta &tab = db.get_table(tab_name); 112 | 113 | std::vector captions = {"Field", "Type", "Index"}; 114 | RecordPrinter printer(captions.size()); 115 | // Print header 116 | printer.print_separator(); 117 | printer.print_record(captions); 118 | printer.print_separator(); 119 | // Print fields 120 | for (auto &col : tab.cols) { 121 | std::vector field_info = {col.name, coltype2str(col.type), col.index ? "YES" : "NO"}; 122 | printer.print_record(field_info); 123 | } 124 | // Print footer 125 | printer.print_separator(); 126 | } 127 | 128 | void SmManager::create_table(const std::string &tab_name, const std::vector &col_defs) { 129 | if (db.is_table(tab_name)) { 130 | throw TableExistsError(tab_name); 131 | } 132 | // Create table meta 133 | int curr_offset = 0; 134 | TabMeta tab; 135 | tab.name = tab_name; 136 | for (auto &col_def : col_defs) { 137 | ColMeta col(tab_name, col_def.name, col_def.type, col_def.len, curr_offset, false); 138 | curr_offset += col_def.len; 139 | tab.cols.push_back(col); 140 | } 141 | // Create & open record file 142 | int record_size = curr_offset; 143 | RmManager::create_file(tab_name, record_size); 144 | db.tabs[tab_name] = tab; 145 | fhs[tab_name] = RmManager::open_file(tab_name); 146 | } 147 | 148 | void SmManager::drop_table(const std::string &tab_name) { 149 | // Find table index in db meta 150 | TabMeta &tab = db.get_table(tab_name); 151 | // Close & destroy record file 152 | RmManager::close_file(fhs.at(tab_name).get()); 153 | RmManager::destroy_file(tab_name); 154 | // Close & destroy index file 155 | for (auto &col : tab.cols) { 156 | if (col.index) { 157 | SmManager::drop_index(tab_name, col.name); 158 | } 159 | } 160 | db.tabs.erase(tab_name); 161 | fhs.erase(tab_name); 162 | } 163 | 164 | void SmManager::create_index(const std::string &tab_name, const std::string &col_name) { 165 | TabMeta &tab = db.get_table(tab_name); 166 | auto col = tab.get_col(col_name); 167 | if (col->index) { 168 | throw IndexExistsError(tab_name, col_name); 169 | } 170 | // Create index file 171 | int col_idx = col - tab.cols.begin(); 172 | IxManager::create_index(tab_name, col_idx, col->type, col->len); 173 | // Open index file 174 | auto ih = IxManager::open_index(tab_name, col_idx); 175 | // Get record file handle 176 | auto fh = fhs.at(tab_name).get(); 177 | // Index all records into index 178 | for (RmScan rm_scan(fh); !rm_scan.is_end(); rm_scan.next()) { 179 | auto rec = fh->get_record(rm_scan.rid()); 180 | const uint8_t *key = rec->data + col->offset; 181 | ih->insert_entry(key, rm_scan.rid()); 182 | } 183 | // Store index handle 184 | auto index_name = IxManager::get_index_name(tab_name, col_idx); 185 | assert(ihs.count(index_name) == 0); 186 | ihs[index_name] = std::move(ih); 187 | // Mark column index as created 188 | col->index = true; 189 | } 190 | 191 | void SmManager::drop_index(const std::string &tab_name, const std::string &col_name) { 192 | TabMeta &tab = db.tabs[tab_name]; 193 | auto col = tab.get_col(col_name); 194 | if (!col->index) { 195 | throw IndexNotFoundError(tab_name, col_name); 196 | } 197 | int col_idx = col - tab.cols.begin(); 198 | auto index_name = IxManager::get_index_name(tab_name, col_idx); 199 | IxManager::close_index(ihs.at(index_name).get()); 200 | IxManager::destroy_index(tab_name, col_idx); 201 | ihs.erase(index_name); 202 | col->index = false; 203 | } 204 | -------------------------------------------------------------------------------- /src/pf/pf_test.cpp: -------------------------------------------------------------------------------- 1 | #include "pf/pf.h" 2 | #include 3 | 4 | TEST(PfManagerTest, basic) { 5 | std::string path1 = "a.txt"; 6 | if (PfManager::is_file(path1)) { 7 | PfManager::destroy_file(path1); 8 | } 9 | std::string path2 = "b.txt"; 10 | if (PfManager::is_file(path2)) { 11 | PfManager::destroy_file(path2); 12 | } 13 | 14 | // create file 15 | PfManager::create_file(path1); 16 | EXPECT_THROW(PfManager::create_file(path1), FileExistsError); 17 | 18 | // is file 19 | EXPECT_TRUE(PfManager::is_file(path1)); 20 | EXPECT_FALSE(PfManager::is_file(path2)); 21 | 22 | // open file 23 | int fd = PfManager::open_file(path1); 24 | EXPECT_THROW(PfManager::open_file(path2), FileNotFoundError); 25 | EXPECT_THROW(PfManager::open_file(path1), FileNotClosedError); 26 | 27 | // close file 28 | EXPECT_THROW(PfManager::close_file(fd + 1), FileNotOpenError); 29 | PfManager::close_file(fd); 30 | EXPECT_THROW(PfManager::close_file(fd), FileNotOpenError); 31 | 32 | // destroy file 33 | EXPECT_THROW(PfManager::destroy_file(path2), FileNotFoundError); 34 | fd = PfManager::open_file(path1); 35 | EXPECT_THROW(PfManager::destroy_file(path1), FileNotClosedError); 36 | PfManager::close_file(fd); 37 | PfManager::destroy_file(path1); 38 | EXPECT_THROW(PfManager::destroy_file(path1), FileNotFoundError); 39 | } 40 | 41 | static void check_pages(const std::list &busy_page_ids) { 42 | EXPECT_EQ(PfManager::pager.free_list().size(), NUM_CACHE_PAGES - busy_page_ids.size()); 43 | EXPECT_EQ(PfManager::pager.busy_list().size(), busy_page_ids.size()); 44 | auto busy_it = PfManager::pager.busy_list().begin(); 45 | for (const auto &pid : busy_page_ids) { 46 | EXPECT_EQ(pid, (*busy_it)->id); 47 | EXPECT_TRUE(PfManager::pager.in_cache(pid)); 48 | EXPECT_EQ(PfManager::pager.busy_map().at(pid), busy_it); 49 | busy_it++; 50 | } 51 | } 52 | 53 | TEST(PfPagerTest, lru) { 54 | srand((unsigned)time(nullptr)); 55 | 56 | std::vector paths{"0.txt", "1.txt", "2.txt", "3.txt"}; 57 | std::vector fds; 58 | for (const auto &path : paths) { 59 | if (PfManager::is_file(path)) { 60 | PfManager::destroy_file(path); 61 | } 62 | PfManager::create_file(path); 63 | int fd = PfManager::open_file(path); 64 | fds.emplace_back(fd); 65 | } 66 | 67 | // create pages 68 | std::list busy_page_ids; 69 | for (int i = 0; i < NUM_CACHE_PAGES; i++) { 70 | if (rand() % 100 == 0) { 71 | check_pages(busy_page_ids); 72 | } 73 | for (int fd : fds) { 74 | PfManager::pager.create_page(fd, i); 75 | busy_page_ids.push_front(PageId(fd, i)); 76 | if (busy_page_ids.size() > NUM_CACHE_PAGES) { 77 | busy_page_ids.pop_back(); 78 | } 79 | } 80 | } 81 | check_pages(busy_page_ids); 82 | 83 | // access pages 84 | auto busy_it = busy_page_ids.begin(); 85 | while (busy_it != busy_page_ids.end()) { 86 | if (rand() % 100 == 0) { 87 | check_pages(busy_page_ids); 88 | } 89 | PfManager::pager.fetch_page(busy_it->fd, busy_it->page_no); 90 | auto curr_it = busy_it++; 91 | busy_page_ids.push_front(*curr_it); 92 | busy_page_ids.erase(curr_it); 93 | } 94 | check_pages(busy_page_ids); 95 | 96 | for (int fd : fds) { 97 | PfManager::close_file(fd); 98 | } 99 | for (const auto &path : paths) { 100 | PfManager::destroy_file(path); 101 | } 102 | } 103 | 104 | static constexpr int MAX_FILES = 16; 105 | static constexpr int MAX_PAGES = 128; 106 | 107 | class MockPager { 108 | public: 109 | uint8_t *get_page(int fd, int page_no) { return &data[fd][page_no * PAGE_SIZE]; } 110 | 111 | void check_disk(int fd, int page_no) { 112 | EXPECT_TRUE(!PfManager::pager.in_cache({fd, page_no})); 113 | uint8_t buf[PAGE_SIZE]; 114 | PfPager::read_page(fd, page_no, buf, PAGE_SIZE); 115 | uint8_t *mock_buf = get_page(fd, page_no); 116 | EXPECT_EQ(memcmp(buf, mock_buf, PAGE_SIZE), 0); 117 | } 118 | 119 | void check_cache(int fd, int page_no) { 120 | EXPECT_TRUE(PfManager::pager.in_cache({fd, page_no})); 121 | Page *page = PfManager::pager.fetch_page(fd, page_no); 122 | uint8_t *mock_buf = get_page(fd, page_no); 123 | EXPECT_EQ(memcmp(page->buf, mock_buf, PAGE_SIZE), 0); 124 | } 125 | 126 | std::unordered_map> data; // fd -> buffer 127 | }; 128 | 129 | void rand_buf(int size, uint8_t *buf) { 130 | for (int i = 0; i < size; i++) { 131 | int rand_ch = rand() & 0xff; 132 | buf[i] = rand_ch; 133 | } 134 | } 135 | 136 | TEST(PfPagerTest, readwrite) { 137 | MockPager mock; 138 | 139 | // create files 140 | std::vector filenames; 141 | std::vector fds; 142 | for (int i = 0; i < MAX_FILES; i++) { 143 | std::string filename = std::to_string(i) + ".txt"; 144 | if (PfManager::is_file(filename)) { 145 | PfManager::destroy_file(filename); 146 | } 147 | PfManager::create_file(filename); 148 | int fd = PfManager::open_file(filename); 149 | filenames.emplace_back(std::move(filename)); 150 | fds.emplace_back(fd); 151 | mock.data[fd] = {}; 152 | } 153 | 154 | std::unordered_map is_created; 155 | is_created.reserve(MAX_FILES * MAX_PAGES); 156 | for (int fd : fds) { 157 | for (int i = 0; i < MAX_PAGES; i++) { 158 | is_created[PageId(fd, i)] = false; 159 | } 160 | } 161 | 162 | // random access 163 | for (int i = 0; i < 100000; i++) { 164 | int file_idx = rand() % MAX_FILES; 165 | int fd = fds[file_idx]; 166 | int page_no = rand() % MAX_PAGES; 167 | PageId page_id(fd, page_no); 168 | uint8_t *mock_buf = mock.get_page(fd, page_no); 169 | 170 | if (!is_created[page_id]) { 171 | // create page 172 | Page *page = PfManager::pager.create_page(fd, page_no); 173 | rand_buf(PAGE_SIZE, page->buf); 174 | memcpy(mock_buf, page->buf, PAGE_SIZE); 175 | } 176 | 177 | // get page 178 | Page *page = PfManager::pager.fetch_page(fd, page_no); 179 | EXPECT_EQ(memcmp(page->buf, mock_buf, PAGE_SIZE), 0); 180 | // check equal in cache 181 | mock.check_cache(fd, page_no); 182 | 183 | // modify 184 | rand_buf(PAGE_SIZE, page->buf); 185 | memcpy(mock_buf, page->buf, PAGE_SIZE); 186 | page->mark_dirty(); 187 | 188 | // flush 189 | if (rand() % 20 == 0) { 190 | PfManager::pager.flush_page(page); 191 | mock.check_disk(fd, page_no); 192 | } 193 | // flush the entire file 194 | if (rand() % 200 == 0) { 195 | PfManager::pager.flush_file(fd); 196 | } 197 | // re-open file 198 | if (rand() % 200 == 0) { 199 | PfManager::close_file(fd); 200 | int new_fd = PfManager::open_file(filenames[file_idx]); 201 | if (new_fd != fd) { 202 | fds[file_idx] = new_fd; 203 | mock.data[new_fd] = mock.data[fd]; 204 | mock.data.erase(fd); 205 | } 206 | } 207 | } 208 | 209 | // flush and check disk 210 | PfManager::pager.flush_all(); 211 | for (int fd : fds) { 212 | for (int page_no = 0; page_no < MAX_PAGES; page_no++) { 213 | mock.check_disk(fd, page_no); 214 | } 215 | } 216 | 217 | // close and destroy files 218 | for (int fd : fds) { 219 | PfManager::close_file(fd); 220 | } 221 | for (const auto &path : filenames) { 222 | PfManager::destroy_file(path); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/ix/ix_test.cpp: -------------------------------------------------------------------------------- 1 | #include "ix/ix.h" 2 | #include 3 | 4 | class IxTest : public ::testing::Test { 5 | public: 6 | void check_tree(const IxIndexHandle *ih, int root_page) { 7 | IxNodeHandle node = ih->fetch_node(root_page); 8 | if (node.hdr->is_leaf) { 9 | return; 10 | } 11 | for (int i = 0; i < node.hdr->num_child; i++) { 12 | IxNodeHandle child = ih->fetch_node(node.get_rid(i)->page_no); 13 | // check parent 14 | EXPECT_EQ(child.hdr->parent, root_page); 15 | // check last key 16 | EXPECT_EQ(memcmp(node.get_key(i), child.get_key(child.hdr->num_key - 1), ih->hdr.col_len), 0); 17 | check_tree(ih, node.get_rid(i)->page_no); 18 | } 19 | } 20 | 21 | void check_leaf(const IxIndexHandle *ih) { 22 | // check leaf list 23 | int leaf_no = ih->hdr.first_leaf; 24 | while (leaf_no != IX_LEAF_HEADER_PAGE) { 25 | IxNodeHandle curr = ih->fetch_node(leaf_no); 26 | IxNodeHandle prev = ih->fetch_node(curr.hdr->prev_leaf); 27 | IxNodeHandle next = ih->fetch_node(curr.hdr->next_leaf); 28 | // Ensure prev->next == curr && next->prev == curr 29 | EXPECT_EQ(prev.hdr->next_leaf, leaf_no); 30 | EXPECT_EQ(next.hdr->prev_leaf, leaf_no); 31 | leaf_no = curr.hdr->next_leaf; 32 | } 33 | } 34 | 35 | void check_equal(const IxIndexHandle *ih, const std::multimap &mock) { 36 | check_tree(ih, ih->hdr.root_page); 37 | check_leaf(ih); 38 | for (auto &entry : mock) { 39 | int mock_key = entry.first; 40 | // test lower bound 41 | { 42 | auto mock_lower = mock.lower_bound(mock_key); 43 | Iid iid = ih->lower_bound((const uint8_t *)&mock_key); 44 | Rid rid = ih->get_rid(iid); 45 | EXPECT_EQ(rid, mock_lower->second); 46 | } 47 | // test upper bound 48 | { 49 | auto mock_upper = mock.upper_bound(mock_key); 50 | Iid iid = ih->upper_bound((const uint8_t *)&mock_key); 51 | if (mock_upper == mock.end()) { 52 | EXPECT_EQ(iid, ih->leaf_end()); 53 | } else { 54 | Rid rid = ih->get_rid(iid); 55 | EXPECT_EQ(rid, mock_upper->second); 56 | } 57 | } 58 | } 59 | // test scan 60 | IxScan scan(ih, ih->leaf_begin(), ih->leaf_end()); 61 | auto it = mock.begin(); 62 | while (!scan.is_end() && it != mock.end()) { 63 | Rid mock_rid = it->second; 64 | Rid rid = scan.rid(); 65 | EXPECT_EQ(rid, mock_rid); 66 | it++; 67 | scan.next(); 68 | } 69 | EXPECT_TRUE(scan.is_end()); 70 | EXPECT_EQ(it, mock.end()); 71 | } 72 | 73 | void print_btree(IxIndexHandle &ih, int root_page, int offset) { 74 | IxNodeHandle node = ih.fetch_node(root_page); 75 | for (int i = node.hdr->num_child - 1; i > -1; i--) { 76 | // print key 77 | std::cout << std::string(offset, ' ') << *(int *)node.get_key(i) << std::endl; 78 | // print child 79 | if (!node.hdr->is_leaf) { 80 | print_btree(ih, node.get_rid(i)->page_no, offset + 4); 81 | } 82 | } 83 | } 84 | 85 | void test_ix_insert_delete(int order, int round) { 86 | std::string filename = "abc"; 87 | int index_no = 0; 88 | if (IxManager::exists(filename, index_no)) { 89 | IxManager::destroy_index(filename, index_no); 90 | } 91 | IxManager::create_index(filename, index_no, TYPE_INT, sizeof(int)); 92 | auto ih = IxManager::open_index(filename, index_no); 93 | if (order > 2 && order <= ih->hdr.btree_order) { 94 | ih->hdr.btree_order = order; 95 | } 96 | std::multimap mock; 97 | for (int i = 0; i < round; i++) { 98 | int rand_key = rand() % round; 99 | Rid rand_val(rand(), rand()); 100 | ih->insert_entry((const uint8_t *)&rand_key, rand_val); 101 | mock.insert(std::make_pair(rand_key, rand_val)); 102 | if (round % 500 == 0) { 103 | IxManager::close_index(ih.get()); 104 | ih = IxManager::open_index(filename, index_no); 105 | } 106 | } 107 | std::cout << "Insert " << round << std::endl; 108 | // print_btree(ih, ih.hdr.root_page, 0); 109 | check_equal(ih.get(), mock); 110 | for (int i = 0; i < round; i++) { 111 | auto it = mock.begin(); 112 | int key = it->first; 113 | Rid rid = it->second; 114 | ih->delete_entry((const uint8_t *)&key, rid); 115 | mock.erase(it); 116 | if (round % 500 == 0) { 117 | IxManager::close_index(ih.get()); 118 | ih = IxManager::open_index(filename, index_no); 119 | } 120 | } 121 | std::cout << "delete " << round << std::endl; 122 | check_equal(ih.get(), mock); 123 | IxManager::close_index(ih.get()); 124 | IxManager::destroy_index(filename, index_no); 125 | } 126 | 127 | void test_ix(int order, int round) { 128 | std::string filename = "abc"; 129 | int index_no = 0; 130 | if (IxManager::exists(filename, index_no)) { 131 | IxManager::destroy_index(filename, index_no); 132 | } 133 | IxManager::create_index(filename, index_no, TYPE_INT, sizeof(int)); 134 | auto ih = IxManager::open_index(filename, index_no); 135 | if (order >= 2 && order <= ih->hdr.btree_order) { 136 | ih->hdr.btree_order = order; 137 | } 138 | int add_cnt = 0; 139 | int del_cnt = 0; 140 | std::multimap mock; 141 | for (int i = 0; i < round; i++) { 142 | double dice = rand() * 1. / RAND_MAX; 143 | double insert_prob = 1. - mock.size() / (0.5 * round); 144 | if (mock.empty() || dice < insert_prob) { 145 | // Insert 146 | int rand_key = rand() % round; 147 | Rid rand_val(rand(), rand()); 148 | ih->insert_entry((const uint8_t *)&rand_key, rand_val); 149 | mock.insert(std::make_pair(rand_key, rand_val)); 150 | add_cnt++; 151 | } else { 152 | // Delete 153 | int rand_idx = rand() % mock.size(); 154 | auto it = mock.begin(); 155 | for (int k = 0; k < rand_idx; k++) { 156 | it++; 157 | } 158 | int key = it->first; 159 | Rid rid = it->second; 160 | ih->delete_entry((const uint8_t *)&key, rid); 161 | mock.erase(it); 162 | del_cnt++; 163 | } 164 | // Randomly re-open file 165 | if (round % 500 == 0) { 166 | IxManager::close_index(ih.get()); 167 | ih = IxManager::open_index(filename, index_no); 168 | } 169 | } 170 | // print_btree(ih, ih.hdr.root_page, 0); 171 | std::cout << "Insert " << add_cnt << '\n' << "Delete " << del_cnt << '\n'; 172 | while (!mock.empty()) { 173 | int rand_idx = rand() % mock.size(); 174 | auto it = mock.begin(); 175 | for (int k = 0; k < rand_idx; k++) { 176 | it++; 177 | } 178 | int key = it->first; 179 | Rid rid = it->second; 180 | ih->delete_entry((const uint8_t *)&key, rid); 181 | mock.erase(it); 182 | // Randomly re-open file 183 | if (round % 500 == 0) { 184 | IxManager::close_index(ih.get()); 185 | ih = IxManager::open_index(filename, index_no); 186 | } 187 | } 188 | check_equal(ih.get(), mock); 189 | IxManager::close_index(ih.get()); 190 | IxManager::destroy_index(filename, index_no); 191 | } 192 | }; 193 | 194 | TEST_F(IxTest, basic) { 195 | srand((unsigned)time(nullptr)); 196 | // init 197 | test_ix_insert_delete(3, 1000); 198 | test_ix(4, 1000); 199 | test_ix(-1, 100000); 200 | } 201 | -------------------------------------------------------------------------------- /src/ql/ql_node.cpp: -------------------------------------------------------------------------------- 1 | #include "ql/ql_node.h" 2 | 3 | std::vector::const_iterator QlNode::get_col(const std::vector &rec_cols, const TabCol &target) { 4 | auto pos = std::find_if(rec_cols.begin(), rec_cols.end(), [&](const ColMeta &col) { 5 | return col.tab_name == target.tab_name && col.name == target.col_name; 6 | }); 7 | if (pos == rec_cols.end()) { 8 | throw ColumnNotFoundError(target.tab_name + '.' + target.col_name); 9 | } 10 | return pos; 11 | } 12 | 13 | std::map QlNode::rec2dict(const std::vector &cols, const RmRecord *rec) { 14 | std::map rec_dict; 15 | for (auto &col : cols) { 16 | TabCol key(col.tab_name, col.name); 17 | Value val; 18 | uint8_t *val_buf = rec->data + col.offset; 19 | if (col.type == TYPE_INT) { 20 | val.set_int(*(int *)val_buf); 21 | } else if (col.type == TYPE_FLOAT) { 22 | val.set_float(*(float *)val_buf); 23 | } else if (col.type == TYPE_STRING) { 24 | std::string str_val((char *)val_buf, col.len); 25 | str_val.resize(strlen(str_val.c_str())); 26 | val.set_str(str_val); 27 | } 28 | assert(rec_dict.count(key) == 0); 29 | val.init_raw(col.len); 30 | rec_dict[key] = val; 31 | } 32 | return rec_dict; 33 | } 34 | 35 | QlNodeProj::QlNodeProj(std::unique_ptr prev, const std::vector &sel_cols) { 36 | _prev = std::move(prev); 37 | 38 | size_t curr_offset = 0; 39 | auto &prev_cols = _prev->cols(); 40 | for (auto &sel_col : sel_cols) { 41 | auto pos = get_col(prev_cols, sel_col); 42 | _sel_idxs.push_back(pos - prev_cols.begin()); 43 | auto col = *pos; 44 | col.offset = curr_offset; 45 | curr_offset += col.len; 46 | _cols.push_back(col); 47 | } 48 | _len = curr_offset; 49 | } 50 | 51 | std::unique_ptr QlNodeProj::rec() const { 52 | assert(!is_end()); 53 | auto &prev_cols = _prev->cols(); 54 | auto prev_rec = _prev->rec(); 55 | auto &proj_cols = _cols; 56 | auto proj_rec = std::make_unique(_len); 57 | for (size_t proj_idx = 0; proj_idx < proj_cols.size(); proj_idx++) { 58 | size_t prev_idx = _sel_idxs[proj_idx]; 59 | auto &prev_col = prev_cols[prev_idx]; 60 | auto &proj_col = proj_cols[proj_idx]; 61 | memcpy(proj_rec->data + proj_col.offset, prev_rec->data + prev_col.offset, proj_col.len); 62 | } 63 | return proj_rec; 64 | } 65 | 66 | QlNodeTable::QlNodeTable(std::string tab_name, std::vector conds) { 67 | _tab_name = std::move(tab_name); 68 | _conds = std::move(conds); 69 | TabMeta &tab = SmManager::db.get_table(_tab_name); 70 | _fh = SmManager::fhs.at(_tab_name).get(); 71 | _cols = tab.cols; 72 | _len = _cols.back().offset + _cols.back().len; 73 | static std::map swap_op = { 74 | {OP_EQ, OP_EQ}, {OP_NE, OP_NE}, {OP_LT, OP_GT}, {OP_GT, OP_LT}, {OP_LE, OP_GE}, {OP_GE, OP_LE}, 75 | }; 76 | 77 | for (auto &cond : _conds) { 78 | if (cond.lhs_col.tab_name != _tab_name) { 79 | // lhs is on other table, now rhs must be on this table 80 | assert(!cond.is_rhs_val && cond.rhs_col.tab_name == _tab_name); 81 | // swap lhs and rhs 82 | std::swap(cond.lhs_col, cond.rhs_col); 83 | cond.op = swap_op.at(cond.op); 84 | } 85 | } 86 | _fed_conds = _conds; 87 | } 88 | 89 | void QlNodeTable::begin() { 90 | check_runtime_conds(); 91 | 92 | int index_no = -1; 93 | 94 | TabMeta &tab = SmManager::db.get_table(_tab_name); 95 | for (auto &cond : _fed_conds) { 96 | if (cond.is_rhs_val && cond.op != OP_NE) { 97 | // If rhs is value and op is not "!=", find if lhs has index 98 | auto lhs_col = tab.get_col(cond.lhs_col.col_name); 99 | if (lhs_col->index) { 100 | // This column has index, use it 101 | index_no = lhs_col - tab.cols.begin(); 102 | break; 103 | } 104 | } 105 | } 106 | 107 | if (index_no == -1) { 108 | // no index is available, scan record file 109 | _scan = std::make_unique(_fh); 110 | } else { 111 | // index is available, scan index 112 | auto ih = SmManager::ihs.at(IxManager::get_index_name(_tab_name, index_no)).get(); 113 | Iid lower = ih->leaf_begin(); 114 | Iid upper = ih->leaf_end(); 115 | auto &index_col = _cols[index_no]; 116 | for (auto &cond : _fed_conds) { 117 | if (cond.is_rhs_val && cond.op != OP_NE && cond.lhs_col.col_name == index_col.name) { 118 | uint8_t *rhs_key = cond.rhs_val.raw->data; 119 | if (cond.op == OP_EQ) { 120 | lower = ih->lower_bound(rhs_key); 121 | upper = ih->upper_bound(rhs_key); 122 | } else if (cond.op == OP_LT) { 123 | upper = ih->lower_bound(rhs_key); 124 | } else if (cond.op == OP_GT) { 125 | lower = ih->upper_bound(rhs_key); 126 | } else if (cond.op == OP_LE) { 127 | upper = ih->upper_bound(rhs_key); 128 | } else if (cond.op == OP_GE) { 129 | lower = ih->lower_bound(rhs_key); 130 | } else { 131 | throw InternalError("Unexpected op type"); 132 | } 133 | break; // TODO: maintain an interval 134 | } 135 | } 136 | _scan = std::make_unique(ih, lower, upper); 137 | } 138 | // Get the first record 139 | while (!_scan->is_end()) { 140 | _rid = _scan->rid(); 141 | auto rec = _fh->get_record(_rid); 142 | if (eval_conds(_cols, _fed_conds, rec.get())) { 143 | break; 144 | } 145 | _scan->next(); 146 | } 147 | } 148 | 149 | void QlNodeTable::next() { 150 | check_runtime_conds(); 151 | assert(!is_end()); 152 | for (_scan->next(); !_scan->is_end(); _scan->next()) { 153 | _rid = _scan->rid(); 154 | auto rec = _fh->get_record(_rid); 155 | if (eval_conds(_cols, _fed_conds, rec.get())) { 156 | break; 157 | } 158 | } 159 | } 160 | 161 | bool QlNodeTable::is_end() const { return _scan->is_end(); } 162 | 163 | void QlNodeTable::feed(const std::map &feed_dict) { 164 | _fed_conds = _conds; 165 | for (auto &cond : _fed_conds) { 166 | if (!cond.is_rhs_val && cond.rhs_col.tab_name != _tab_name) { 167 | // need to feed rhs col 168 | cond.is_rhs_val = true; 169 | cond.rhs_val = feed_dict.at(cond.rhs_col); 170 | } 171 | } 172 | check_runtime_conds(); 173 | } 174 | 175 | void QlNodeTable::check_runtime_conds() { 176 | for (auto &cond : _fed_conds) { 177 | assert(cond.lhs_col.tab_name == _tab_name); 178 | if (!cond.is_rhs_val) { 179 | assert(cond.rhs_col.tab_name == _tab_name); 180 | } 181 | } 182 | } 183 | 184 | bool QlNodeTable::eval_cond(const std::vector &rec_cols, const Condition &cond, const RmRecord *rec) { 185 | auto lhs_col = get_col(rec_cols, cond.lhs_col); 186 | uint8_t *lhs = rec->data + lhs_col->offset; 187 | uint8_t *rhs; 188 | ColType rhs_type; 189 | if (cond.is_rhs_val) { 190 | rhs_type = cond.rhs_val.type; 191 | rhs = cond.rhs_val.raw->data; 192 | } else { 193 | // rhs is a column 194 | auto rhs_col = get_col(rec_cols, cond.rhs_col); 195 | rhs_type = rhs_col->type; 196 | rhs = rec->data + rhs_col->offset; 197 | } 198 | assert(rhs_type == lhs_col->type); // TODO convert to common type 199 | int cmp = ix_compare(lhs, rhs, rhs_type, lhs_col->len); 200 | if (cond.op == OP_EQ) { 201 | return cmp == 0; 202 | } else if (cond.op == OP_NE) { 203 | return cmp != 0; 204 | } else if (cond.op == OP_LT) { 205 | return cmp < 0; 206 | } else if (cond.op == OP_GT) { 207 | return cmp > 0; 208 | } else if (cond.op == OP_LE) { 209 | return cmp <= 0; 210 | } else if (cond.op == OP_GE) { 211 | return cmp >= 0; 212 | } else { 213 | throw InternalError("Unexpected op type"); 214 | } 215 | } 216 | 217 | bool QlNodeTable::eval_conds(const std::vector &rec_cols, const std::vector &conds, 218 | const RmRecord *rec) { 219 | return std::all_of(conds.begin(), conds.end(), 220 | [&](const Condition &cond) { return eval_cond(rec_cols, cond, rec); }); 221 | } 222 | 223 | QlNodeJoin::QlNodeJoin(std::unique_ptr left, std::unique_ptr right) { 224 | _left = std::move(left); 225 | _right = std::move(right); 226 | _len = _left->len() + _right->len(); 227 | _cols = _left->cols(); 228 | auto right_cols = _right->cols(); 229 | for (auto &col : right_cols) { 230 | col.offset += _left->len(); 231 | } 232 | _cols.insert(_cols.end(), right_cols.begin(), right_cols.end()); 233 | } 234 | 235 | void QlNodeJoin::begin() { 236 | _left->begin(); 237 | if (_left->is_end()) { 238 | return; 239 | } 240 | feed_right(); 241 | _right->begin(); 242 | while (_right->is_end()) { 243 | _left->next(); 244 | if (_left->is_end()) { 245 | break; 246 | } 247 | feed_right(); 248 | _right->begin(); 249 | } 250 | } 251 | 252 | void QlNodeJoin::next() { 253 | assert(!is_end()); 254 | _right->next(); 255 | while (_right->is_end()) { 256 | _left->next(); 257 | if (_left->is_end()) { 258 | break; 259 | } 260 | feed_right(); 261 | _right->begin(); 262 | } 263 | } 264 | 265 | std::unique_ptr QlNodeJoin::rec() const { 266 | assert(!is_end()); 267 | auto record = std::make_unique(_len); 268 | memcpy(record->data, _left->rec()->data, _left->len()); 269 | memcpy(record->data + _left->len(), _right->rec()->data, _right->len()); 270 | return record; 271 | } 272 | 273 | void QlNodeJoin::feed(const std::map &feed_dict) { 274 | _prev_feed_dict = feed_dict; 275 | _left->feed(feed_dict); 276 | } 277 | 278 | void QlNodeJoin::feed_right() { 279 | auto left_dict = rec2dict(_left->cols(), _left->rec().get()); 280 | auto feed_dict = _prev_feed_dict; 281 | feed_dict.insert(left_dict.begin(), left_dict.end()); 282 | _right->feed(feed_dict); 283 | } 284 | -------------------------------------------------------------------------------- /test_e2e.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import re 4 | import sqlite3 5 | import string 6 | import time 7 | from shutil import rmtree 8 | 9 | TYPE_INT = 1 10 | TYPE_FLOAT = 2 11 | TYPE_STR = 3 12 | 13 | 14 | def parse_select(out_msg): 15 | sep_cnt = 0 16 | table = [] 17 | lines = out_msg.split('\n') 18 | lines = [line.strip() for line in lines if line.strip()] 19 | 20 | int_patt = re.compile(r'^[+-]?\d+$') 21 | float_patt = re.compile(r'^[+-]?\d+\.\d*$') 22 | 23 | for line in lines: 24 | if line.startswith('+'): 25 | sep_cnt = (sep_cnt + 1) % 3 26 | if sep_cnt == 0: 27 | yield table 28 | table = [] 29 | elif sep_cnt == 2: 30 | # ignore leading & trailing empty strings 31 | row = line.split('|')[1:-1] 32 | record = [] 33 | for col in row: 34 | col = col.strip() 35 | if int_patt.match(col): 36 | col = int(col) 37 | elif float_patt.match(col): 38 | col = round(float(col), 2) 39 | else: 40 | col = str(col) 41 | record.append(col) 42 | table.append(tuple(record)) 43 | 44 | 45 | def rand_int(max_val=0xffff): 46 | return random.randint(0, max_val) 47 | 48 | 49 | def rand_float(): 50 | return random.randint(0, 0xffff) / 100 51 | 52 | 53 | def rand_str(): 54 | str_len = random.randint(1, 16) 55 | char_set = string.ascii_lowercase + string.ascii_uppercase 56 | s = ''.join(random.choices(char_set, k=str_len)) 57 | return f'\'{s}\'' 58 | 59 | 60 | def rand_insert(tab_name, col_types, max_int=0xffff): 61 | values = [] 62 | for col_type in col_types: 63 | if col_type == TYPE_INT: 64 | val = str(rand_int(max_int)) 65 | elif col_type == TYPE_FLOAT: 66 | val = str(rand_float()) 67 | elif col_type == TYPE_STR: 68 | val = rand_str() 69 | else: 70 | assert False 71 | values.append(val) 72 | values = ', '.join(values) 73 | sql = f'insert into {tab_name} values ({values});' 74 | return sql 75 | 76 | 77 | def rand_eq(): 78 | col = random.choice(['a', 'b', 'c']) 79 | if col == 'a': 80 | val = rand_int() 81 | elif col == 'b': 82 | val = rand_float() 83 | else: 84 | val = rand_str() 85 | return f'{col} = {val}' 86 | 87 | 88 | def rand_delete(): 89 | cond = rand_eq() 90 | sql = f'delete from tb where {cond};' 91 | return sql 92 | 93 | 94 | def rand_update(): 95 | cond = rand_eq() 96 | set_clause = rand_eq() 97 | sql = f'update tb set {set_clause} where {cond};' 98 | return sql 99 | 100 | 101 | def check_equal(sqls, restart=True): 102 | with open('in.sql', 'w') as f: 103 | f.write('\n'.join(sqls)) 104 | 105 | redbase_db_name = 'db' 106 | mock_db_name = 'mock.db' 107 | 108 | if restart: 109 | # drop db if it already exists 110 | if os.path.isdir(redbase_db_name): 111 | rmtree(redbase_db_name) 112 | if os.path.isfile(mock_db_name): 113 | os.remove(mock_db_name) 114 | 115 | query_sqls = [] 116 | 117 | # run sqlite3 118 | start = time.time() 119 | ans = [] 120 | conn = sqlite3.connect(mock_db_name) 121 | c = conn.cursor() 122 | for sql in sqls: 123 | if sql.startswith('create index') or sql.startswith('drop index'): 124 | continue 125 | c.execute(sql) 126 | if sql.startswith('select'): 127 | query_sqls.append(sql) 128 | table = c.fetchall() 129 | ans.append(table) 130 | conn.commit() 131 | conn.close() 132 | print(f'SQLite3 spent {time.time() - start:.4f}s') 133 | 134 | # run redbase 135 | start = time.time() 136 | os.system(f'./build/bin/rawcli {redbase_db_name} < in.sql > rb.out 2>&1') 137 | print(f'Redbase spent {time.time() - start:.4f}s') 138 | 139 | # check output 140 | with open('rb.out') as f: 141 | out_msg = f.read() 142 | 143 | out = list(parse_select(out_msg)) 144 | assert len(out) == len(ans) == len(query_sqls) 145 | for i, (query_sql, out_tb, ans_tb) in enumerate(zip(query_sqls, out, ans)): 146 | assert len(out_tb) == len(ans_tb) 147 | out_tb = sorted(out_tb) 148 | ans_tb = sorted(ans_tb) 149 | assert out_tb == ans_tb 150 | print(f'Test #{i:02d}: PASSED {query_sql}') 151 | 152 | 153 | def test_single(): 154 | # build table 155 | sqls = ['create table tb (a int, b float, c char(16));', 156 | 'create index tb(a);'] 157 | 158 | col_types = [TYPE_INT, TYPE_FLOAT, TYPE_STR] 159 | for _ in range(10000): 160 | sql = rand_insert('tb', col_types) 161 | sqls.append(sql) 162 | 163 | sqls.append('create index tb(b);') 164 | 165 | # random update / delete / insert 166 | for _ in range(1000): 167 | choice = random.randint(0, 2) 168 | if choice == 0: 169 | sql = rand_insert('tb', col_types) 170 | elif choice == 1: 171 | sql = rand_delete() 172 | elif choice == 2: 173 | sql = rand_update() 174 | else: 175 | assert False 176 | sqls.append(sql) 177 | 178 | sqls += [ 179 | 'select * from tb;', 180 | 'select * from tb where a > 10000;', 181 | 'select * from tb where a <= 20000;', 182 | 'select * from tb where a < 10000 and a > 20000;', 183 | 'select * from tb where b >= 100. and a < 30000;', 184 | 'select * from tb where a <> 100 and a <> 200 and b <> 50.00;', 185 | 'select * from tb where c > \'m\';', 186 | 'select * from tb where c < \'hello world\';', 187 | ] 188 | 189 | check_equal(sqls) 190 | 191 | 192 | def test_multi(): 193 | sqls = ['create table tb1 (s int, a int, b float, c char(16));', 194 | 'create index tb1(a);', 195 | 'create table tb2 (x float, y int, z char(32), s int);', 196 | 'create index tb2(y);', 197 | 'create table tb3 (m int, n float);'] 198 | 199 | tb1_types = [TYPE_INT, TYPE_INT, TYPE_FLOAT, TYPE_STR] 200 | for _ in range(100): 201 | sql = rand_insert('tb1', tb1_types, max_int=100) 202 | sqls.append(sql) 203 | 204 | tb2_types = [TYPE_FLOAT, TYPE_INT, TYPE_STR, TYPE_INT] 205 | for _ in range(100): 206 | sql = rand_insert('tb2', tb2_types, max_int=100) 207 | sqls.append(sql) 208 | 209 | tb3_types = [TYPE_INT, TYPE_FLOAT] 210 | for _ in range(10): 211 | sql = rand_insert('tb3', tb3_types, max_int=100) 212 | sqls.append(sql) 213 | 214 | # query 215 | sqls += [ 216 | 'select * from tb1;', 217 | 'select * from tb2;', 218 | 'select * from tb3;', 219 | 'select * from tb1, tb2;', 220 | 'select * from tb3, tb2;', 221 | 'select * from tb1, tb2, tb3;', 222 | 'select * from tb1, tb2, tb3 where tb1.s = tb2.s;', 223 | 'select * from tb1, tb2 where a > 40000 and y < 20000;', 224 | 'select * from tb2, tb3 where tb2.s = m;', 225 | ] 226 | 227 | check_equal(sqls) 228 | 229 | 230 | def test_index_join(): 231 | sqls = ['create table tb1 (s int, a int, b float, c char(16));', 232 | 'create index tb1(a);', 233 | 'create table tb2 (x float, y int, z char(32), s int);', 234 | 'create index tb2(y);', 235 | 'create table tb3 (m int, n int);', 236 | 'create index tb3(m);'] 237 | 238 | tb1_types = [TYPE_INT, TYPE_INT, TYPE_FLOAT, TYPE_STR] 239 | for _ in range(10000): 240 | sql = rand_insert('tb1', tb1_types) 241 | sqls.append(sql) 242 | 243 | tb2_types = [TYPE_FLOAT, TYPE_INT, TYPE_STR, TYPE_INT] 244 | for _ in range(10000): 245 | sql = rand_insert('tb2', tb2_types) 246 | sqls.append(sql) 247 | 248 | tb3_types = [TYPE_INT, TYPE_INT] 249 | for _ in range(10000): 250 | sql = rand_insert('tb3', tb3_types) 251 | sqls.append(sql) 252 | 253 | # query 254 | sqls += [ 255 | 'select * from tb1, tb2 where a = y;', 256 | 'select * from tb1, tb2 where y = a;', 257 | 'select * from tb1, tb2, tb3 where a = y and a = m;', 258 | 'select * from tb1, tb2, tb3 where a = y and m = y;', 259 | ] 260 | 261 | check_equal(sqls) 262 | 263 | 264 | def test_basic(): 265 | ddl = [ 266 | "create table tb(s int, a int, b float, c char(16));", 267 | "create index tb(s);", 268 | "create table tb2(x int, y float, z char(16), s int);", 269 | "create table tb3(m int, n int);" 270 | ] 271 | 272 | dml = [ 273 | # single table 274 | "select * from tb;", 275 | "insert into tb values (0, 1, 1., 'abc');", 276 | "insert into tb values (2, 2, 2., 'def');", 277 | "insert into tb values (5, 3, 2., 'xyz');", 278 | "insert into tb values (4, 4, 2., '0123456789abcdef');", 279 | "insert into tb values (2, 5, -100., 'oops');", 280 | "insert into tb values (-100, 6, 3., '');", 281 | "select * from tb;", 282 | "select * from tb where a = 3;", 283 | "select * from tb where b > -100.;", 284 | "select * from tb where a < 2;", 285 | "select * from tb where b <> 1.;", 286 | "select * from tb where c = 'abc';", 287 | "select * from tb where c <= 'def';", 288 | "select * from tb where c >= 'def';", 289 | "select * from tb where c >= 'def' and a < 3;", 290 | "select * from tb where s < a;", 291 | "select * from tb where a = s;", 292 | "select * from tb where s > a;", 293 | "update tb set a = 996 where a = 3;", 294 | "select * from tb;", 295 | "update tb set b = 997., c = 'icu' where c = 'xyz';", 296 | "select * from tb;", 297 | "delete from tb where a = 996;", 298 | "select * from tb;", 299 | "select s from tb;", 300 | "select a, s from tb;", 301 | "select a, s, b, c, b, a from tb;", 302 | # join 303 | "insert into tb2 values (1, 2., 'abc', 0);", 304 | "insert into tb2 values (2, 3., 'def', 1);", 305 | "insert into tb2 values (3, 1., 'ghi', 2);", 306 | "select * from tb;", 307 | "select * from tb2;", 308 | "select * from tb, tb2;", 309 | "select * from tb2, tb;", 310 | "insert into tb3 values (1, 11);", 311 | "insert into tb3 values (3, 33);", 312 | "insert into tb3 values (5, 55);", 313 | "select * from tb, tb2, tb3;", 314 | # join with selector and conditions 315 | "select * from tb, tb2 where a = x;", 316 | "select * from tb, tb2 where a = 2 and x = 1;", 317 | "select * from tb, tb2 where a > x and a > 3 and x <= 2;", 318 | "select * from tb, tb2 where a <> x and a > 3 and x <= 2;", 319 | "select * from tb, tb2 where x <= a and a > 3 and x <= 2;", 320 | "select * from tb, tb2 where tb.s = tb2.s;", 321 | "select * from tb, tb2, tb3 where tb.s = tb2.s and tb.a = tb3.m;", 322 | "select * from tb, tb2, tb3 where tb.s = tb3.m and tb2.x = tb3.m;", 323 | "select * from tb, tb2, tb3 where tb.s = tb2.s and tb2.x = tb3.m;", 324 | "select * from tb, tb2, tb3 where tb.s = tb2.s and tb2.x = tb3.m \ 325 | and a > 1 and y >= 1.0 and n > 20 and a <> tb.s and x <> tb2.s and m <> n;", 326 | "select tb.s, y, tb2.s, c from tb, tb2 where tb.s = tb2.s;" 327 | ] 328 | check_equal(ddl + dml) 329 | 330 | # test persistent storage 331 | check_equal(dml, restart=False) 332 | 333 | 334 | if __name__ == '__main__': 335 | test_basic() 336 | test_single() 337 | test_multi() 338 | test_index_join() 339 | -------------------------------------------------------------------------------- /src/ql/ql_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "ql/ql_manager.h" 2 | #include "ix/ix.h" 3 | #include "ql/ql_node.h" 4 | #include "record_printer.h" 5 | #include "sm/sm.h" 6 | 7 | static TabCol check_column(const std::vector &all_cols, TabCol target) { 8 | if (target.tab_name.empty()) { 9 | // Table name not specified, infer table name from column name 10 | std::string tab_name; 11 | for (auto &col : all_cols) { 12 | if (col.name == target.col_name) { 13 | if (!tab_name.empty()) { 14 | throw AmbiguousColumnError(target.col_name); 15 | } 16 | tab_name = col.tab_name; 17 | } 18 | } 19 | if (tab_name.empty()) { 20 | throw ColumnNotFoundError(target.col_name); 21 | } 22 | target.tab_name = tab_name; 23 | } else { 24 | // Make sure target column exists 25 | if (!(SmManager::db.is_table(target.tab_name) && 26 | SmManager::db.get_table(target.tab_name).is_col(target.col_name))) { 27 | throw ColumnNotFoundError(target.tab_name + '.' + target.col_name); 28 | } 29 | } 30 | return target; 31 | } 32 | 33 | static std::vector get_all_cols(const std::vector &tab_names) { 34 | std::vector all_cols; 35 | for (auto &sel_tab_name : tab_names) { 36 | const auto &sel_tab_cols = SmManager::db.get_table(sel_tab_name).cols; 37 | all_cols.insert(all_cols.end(), sel_tab_cols.begin(), sel_tab_cols.end()); 38 | } 39 | return all_cols; 40 | } 41 | 42 | static std::vector check_where_clause(const std::vector &tab_names, 43 | const std::vector &conds) { 44 | auto all_cols = get_all_cols(tab_names); 45 | // Get raw values in where clause 46 | std::vector res_conds = conds; 47 | for (auto &cond : res_conds) { 48 | // Infer table name from column name 49 | cond.lhs_col = check_column(all_cols, cond.lhs_col); 50 | if (!cond.is_rhs_val) { 51 | cond.rhs_col = check_column(all_cols, cond.rhs_col); 52 | } 53 | TabMeta &lhs_tab = SmManager::db.get_table(cond.lhs_col.tab_name); 54 | auto lhs_col = lhs_tab.get_col(cond.lhs_col.col_name); 55 | ColType lhs_type = lhs_col->type; 56 | ColType rhs_type; 57 | if (cond.is_rhs_val) { 58 | cond.rhs_val.init_raw(lhs_col->len); 59 | rhs_type = cond.rhs_val.type; 60 | } else { 61 | TabMeta &rhs_tab = SmManager::db.get_table(cond.rhs_col.tab_name); 62 | auto rhs_col = rhs_tab.get_col(cond.rhs_col.col_name); 63 | rhs_type = rhs_col->type; 64 | } 65 | if (lhs_type != rhs_type) { 66 | throw IncompatibleTypeError(coltype2str(lhs_type), coltype2str(rhs_type)); 67 | } 68 | } 69 | return res_conds; 70 | } 71 | 72 | void QlManager::insert_into(const std::string &tab_name, std::vector values) { 73 | TabMeta tab = SmManager::db.get_table(tab_name); 74 | if (values.size() != tab.cols.size()) { 75 | throw InvalidValueCountError(); 76 | } 77 | // Get record file handle 78 | auto fh = SmManager::fhs.at(tab_name).get(); 79 | // Make record buffer 80 | RmRecord rec(fh->hdr.record_size); 81 | for (size_t i = 0; i < values.size(); i++) { 82 | auto &col = tab.cols[i]; 83 | auto &val = values[i]; 84 | if (col.type != val.type) { 85 | throw IncompatibleTypeError(coltype2str(col.type), coltype2str(val.type)); 86 | } 87 | val.init_raw(col.len); 88 | memcpy(rec.data + col.offset, val.raw->data, col.len); 89 | } 90 | // Insert into record file 91 | Rid rid = fh->insert_record(rec.data); 92 | // Insert into index 93 | for (size_t i = 0; i < tab.cols.size(); i++) { 94 | auto &col = tab.cols[i]; 95 | if (col.index) { 96 | auto ih = SmManager::ihs.at(IxManager::get_index_name(tab_name, i)).get(); 97 | ih->insert_entry(rec.data + col.offset, rid); 98 | } 99 | } 100 | } 101 | 102 | void QlManager::delete_from(const std::string &tab_name, std::vector conds) { 103 | TabMeta &tab = SmManager::db.get_table(tab_name); 104 | // Parse where clause 105 | conds = check_where_clause({tab_name}, conds); 106 | // Get all RID to delete 107 | std::vector rids; 108 | QlNodeTable table_scan(tab_name, conds); 109 | for (table_scan.begin(); !table_scan.is_end(); table_scan.next()) { 110 | rids.push_back(table_scan.rid()); 111 | } 112 | // Get record file 113 | auto fh = SmManager::fhs.at(tab_name).get(); 114 | // Get all index files 115 | std::vector ihs(tab.cols.size(), nullptr); 116 | for (size_t col_i = 0; col_i < tab.cols.size(); col_i++) { 117 | if (tab.cols[col_i].index) { 118 | ihs[col_i] = SmManager::ihs.at(IxManager::get_index_name(tab_name, col_i)).get(); 119 | } 120 | } 121 | // Delete each rid from record file and index file 122 | for (auto &rid : rids) { 123 | auto rec = fh->get_record(rid); 124 | // Delete from index file 125 | for (size_t col_i = 0; col_i < tab.cols.size(); col_i++) { 126 | if (ihs[col_i] != nullptr) { 127 | ihs[col_i]->delete_entry(rec->data + tab.cols[col_i].offset, rid); 128 | } 129 | } 130 | // Delete from record file 131 | fh->delete_record(rid); 132 | } 133 | } 134 | 135 | void QlManager::update_set(const std::string &tab_name, std::vector set_clauses, 136 | std::vector conds) { 137 | TabMeta &tab = SmManager::db.get_table(tab_name); 138 | // Parse where clause 139 | conds = check_where_clause({tab_name}, conds); 140 | // Get raw values in set clause 141 | for (auto &set_clause : set_clauses) { 142 | auto lhs_col = tab.get_col(set_clause.lhs.col_name); 143 | if (lhs_col->type != set_clause.rhs.type) { 144 | throw IncompatibleTypeError(coltype2str(lhs_col->type), coltype2str(set_clause.rhs.type)); 145 | } 146 | set_clause.rhs.init_raw(lhs_col->len); 147 | } 148 | // Get all RID to update 149 | std::vector rids; 150 | QlNodeTable table_scan(tab_name, conds); 151 | for (table_scan.begin(); !table_scan.is_end(); table_scan.next()) { 152 | rids.push_back(table_scan.rid()); 153 | } 154 | // Get record file 155 | auto fh = SmManager::fhs.at(tab_name).get(); 156 | // Get all necessary index files 157 | std::vector ihs(tab.cols.size(), nullptr); 158 | for (auto &set_clause : set_clauses) { 159 | auto lhs_col = tab.get_col(set_clause.lhs.col_name); 160 | if (lhs_col->index) { 161 | size_t lhs_col_idx = lhs_col - tab.cols.begin(); 162 | if (ihs[lhs_col_idx] == nullptr) { 163 | ihs[lhs_col_idx] = SmManager::ihs.at(IxManager::get_index_name(tab_name, lhs_col_idx)).get(); 164 | } 165 | } 166 | } 167 | // Update each rid from record file and index file 168 | for (auto &rid : rids) { 169 | auto rec = fh->get_record(rid); 170 | // Remove old entry from index 171 | for (size_t i = 0; i < tab.cols.size(); i++) { 172 | if (ihs[i] != nullptr) { 173 | ihs[i]->delete_entry(rec->data + tab.cols[i].offset, rid); 174 | } 175 | } 176 | // Update record in record file 177 | for (auto &set_clause : set_clauses) { 178 | auto lhs_col = tab.get_col(set_clause.lhs.col_name); 179 | memcpy(rec->data + lhs_col->offset, set_clause.rhs.raw->data, lhs_col->len); 180 | } 181 | fh->update_record(rid, rec->data); 182 | // Insert new entry into index 183 | for (size_t i = 0; i < tab.cols.size(); i++) { 184 | if (ihs[i] != nullptr) { 185 | ihs[i]->insert_entry(rec->data + tab.cols[i].offset, rid); 186 | } 187 | } 188 | } 189 | } 190 | 191 | static std::vector pop_conds(std::vector &conds, const std::vector &tab_names) { 192 | auto has_tab = [&](const std::string &tab_name) { 193 | return std::find(tab_names.begin(), tab_names.end(), tab_name) != tab_names.end(); 194 | }; 195 | std::vector solved_conds; 196 | auto it = conds.begin(); 197 | while (it != conds.end()) { 198 | if (has_tab(it->lhs_col.tab_name) && (it->is_rhs_val || has_tab(it->rhs_col.tab_name))) { 199 | solved_conds.emplace_back(std::move(*it)); 200 | it = conds.erase(it); 201 | } else { 202 | it++; 203 | } 204 | } 205 | return solved_conds; 206 | } 207 | 208 | void QlManager::select_from(std::vector sel_cols, const std::vector &tab_names, 209 | std::vector conds) { 210 | // Parse selector 211 | auto all_cols = get_all_cols(tab_names); 212 | if (sel_cols.empty()) { 213 | // select all columns 214 | for (auto &col : all_cols) { 215 | TabCol sel_col(col.tab_name, col.name); 216 | sel_cols.push_back(sel_col); 217 | } 218 | } else { 219 | // infer table name from column name 220 | for (auto &sel_col : sel_cols) { 221 | sel_col = check_column(all_cols, sel_col); 222 | } 223 | } 224 | // Parse where clause 225 | conds = check_where_clause(tab_names, conds); 226 | // Scan table 227 | std::vector> tab_nodes(tab_names.size()); 228 | for (size_t i = 0; i < tab_names.size(); i++) { 229 | auto curr_conds = pop_conds(conds, {tab_names.begin(), tab_names.begin() + i + 1}); 230 | tab_nodes[i] = std::make_unique(tab_names[i], curr_conds); 231 | } 232 | assert(conds.empty()); 233 | std::unique_ptr query_plan = std::move(tab_nodes.back()); 234 | for (size_t i = tab_names.size() - 2; i != (size_t)-1; i--) { 235 | query_plan = std::make_unique(std::move(tab_nodes[i]), std::move(query_plan)); 236 | } 237 | query_plan = std::make_unique(std::move(query_plan), sel_cols); 238 | // Column titles 239 | std::vector captions; 240 | captions.reserve(sel_cols.size()); 241 | for (auto &sel_col : sel_cols) { 242 | captions.push_back(sel_col.col_name); 243 | } 244 | // Print header 245 | RecordPrinter rec_printer(sel_cols.size()); 246 | rec_printer.print_separator(); 247 | rec_printer.print_record(captions); 248 | rec_printer.print_separator(); 249 | // Print records 250 | size_t num_rec = 0; 251 | for (query_plan->begin(); !query_plan->is_end(); query_plan->next()) { 252 | auto rec = query_plan->rec(); 253 | std::vector columns; 254 | for (auto &col : query_plan->cols()) { 255 | std::string col_str; 256 | uint8_t *rec_buf = rec->data + col.offset; 257 | if (col.type == TYPE_INT) { 258 | col_str = std::to_string(*(int *)rec_buf); 259 | } else if (col.type == TYPE_FLOAT) { 260 | col_str = std::to_string(*(float *)rec_buf); 261 | } else if (col.type == TYPE_STRING) { 262 | col_str = std::string((char *)rec_buf, col.len); 263 | col_str.resize(strlen(col_str.c_str())); 264 | } 265 | columns.push_back(col_str); 266 | } 267 | rec_printer.print_record(columns); 268 | num_rec++; 269 | } 270 | // Print footer 271 | rec_printer.print_separator(); 272 | // Print record count 273 | RecordPrinter::print_record_count(num_rec); 274 | } 275 | -------------------------------------------------------------------------------- /src/ix/ix_index_handle.cpp: -------------------------------------------------------------------------------- 1 | #include "ix/ix_index_handle.h" 2 | #include "ix/ix_scan.h" 3 | #include 4 | 5 | int ix_compare(const uint8_t *a, const uint8_t *b, ColType type, int col_len) { 6 | switch (type) { 7 | case TYPE_INT: { 8 | int ia = *(int *)a; 9 | int ib = *(int *)b; 10 | return (ia < ib) ? -1 : ((ia > ib) ? 1 : 0); 11 | } 12 | case TYPE_FLOAT: { 13 | float fa = *(float *)a; 14 | float fb = *(float *)b; 15 | return (fa < fb) ? -1 : ((fa > fb) ? 1 : 0); 16 | } 17 | case TYPE_STRING: 18 | return memcmp(a, b, col_len); 19 | default: 20 | throw InternalError("Unexpected data type"); 21 | } 22 | } 23 | 24 | IxNodeHandle::IxNodeHandle(const IxFileHdr *ihdr_, Page *page_) { 25 | ihdr = ihdr_; 26 | page = page_; 27 | hdr = (IxPageHdr *)page->buf; 28 | keys = page->buf + ihdr->key_offset; 29 | rids = (Rid *)(page->buf + ihdr->rid_offset); 30 | } 31 | 32 | int IxNodeHandle::lower_bound(const uint8_t *target) const { 33 | if (binary_search) { 34 | int lo = 0, hi = hdr->num_key; 35 | while (lo < hi) { 36 | int mid = (lo + hi) / 2; 37 | uint8_t *key_addr = get_key(mid); 38 | if (ix_compare(target, key_addr, ihdr->col_type, ihdr->col_len) <= 0) { 39 | hi = mid; 40 | } else { 41 | lo = mid + 1; 42 | } 43 | } 44 | return lo; 45 | } else { 46 | int key_idx = 0; 47 | while (key_idx < hdr->num_key) { 48 | uint8_t *key_addr = get_key(key_idx); 49 | if (ix_compare(target, key_addr, ihdr->col_type, ihdr->col_len) <= 0) { 50 | break; 51 | } 52 | key_idx++; 53 | } 54 | return key_idx; 55 | } 56 | } 57 | 58 | int IxNodeHandle::upper_bound(const uint8_t *key) const { 59 | if (binary_search) { 60 | int lo = 0, hi = hdr->num_key; 61 | while (lo < hi) { 62 | int mid = (lo + hi) / 2; 63 | uint8_t *key_slot = get_key(mid); 64 | if (ix_compare(key, key_slot, ihdr->col_type, ihdr->col_len) < 0) { 65 | hi = mid; 66 | } else { 67 | lo = mid + 1; 68 | } 69 | } 70 | return lo; 71 | } else { 72 | int key_idx = 0; 73 | while (key_idx < hdr->num_key) { 74 | uint8_t *key_addr = get_key(key_idx); 75 | if (ix_compare(key, key_addr, ihdr->col_type, ihdr->col_len) < 0) { 76 | break; 77 | } 78 | key_idx++; 79 | } 80 | return key_idx; 81 | } 82 | } 83 | 84 | void IxNodeHandle::insert_keys(int pos, const uint8_t *key, int n) { 85 | uint8_t *key_slot = get_key(pos); 86 | memmove(key_slot + n * ihdr->col_len, key_slot, (hdr->num_key - pos) * ihdr->col_len); 87 | memcpy(key_slot, key, n * ihdr->col_len); 88 | hdr->num_key += n; 89 | } 90 | 91 | void IxNodeHandle::insert_key(int pos, const uint8_t *key) { insert_keys(pos, key, 1); } 92 | 93 | void IxNodeHandle::erase_key(int pos) { 94 | uint8_t *key = get_key(pos); 95 | memmove(key, key + ihdr->col_len, (hdr->num_key - pos - 1) * ihdr->col_len); 96 | hdr->num_key--; 97 | } 98 | 99 | void IxNodeHandle::insert_rids(int pos, const Rid *rid, int n) { 100 | Rid *rid_slot = get_rid(pos); 101 | memmove(rid_slot + n, rid_slot, (hdr->num_child - pos) * sizeof(Rid)); 102 | memcpy(rid_slot, rid, n * sizeof(Rid)); 103 | hdr->num_child += n; 104 | } 105 | 106 | void IxNodeHandle::insert_rid(int pos, const Rid &rid) { insert_rids(pos, &rid, 1); } 107 | 108 | void IxNodeHandle::erase_rid(int pos) { 109 | Rid *rid = get_rid(pos); 110 | memmove(rid, rid + 1, (hdr->num_child - pos - 1) * sizeof(Rid)); 111 | hdr->num_child--; 112 | } 113 | 114 | int IxNodeHandle::find_child(const IxNodeHandle &child) const { 115 | int rank; 116 | for (rank = 0; rank < hdr->num_child; rank++) { 117 | if (get_rid(rank)->page_no == child.page->id.page_no) { 118 | break; 119 | } 120 | } 121 | assert(rank < hdr->num_child); 122 | return rank; 123 | } 124 | 125 | IxIndexHandle::IxIndexHandle(int fd_) { 126 | fd = fd_; 127 | PfPager::read_page(fd, IX_FILE_HDR_PAGE, (uint8_t *)&hdr, sizeof(hdr)); 128 | } 129 | 130 | void IxIndexHandle::insert_entry(const uint8_t *key, const Rid &rid) { 131 | Iid iid = upper_bound(key); 132 | IxNodeHandle node = fetch_node(iid.page_no); 133 | node.page->mark_dirty(); 134 | // We need to insert at iid.slot_no 135 | node.insert_key(iid.slot_no, key); 136 | node.insert_rid(iid.slot_no, rid); 137 | // Maintain parent's max key 138 | if (iid.page_no == hdr.last_leaf && iid.slot_no == node.hdr->num_key - 1) { 139 | // Max key updated 140 | maintain_parent(node); 141 | } 142 | // Solve overflow 143 | while (node.hdr->num_child > hdr.btree_order) { 144 | // If leaf node is overflowed, we need to split it 145 | if (node.hdr->parent == IX_NO_PAGE) { 146 | // If current page is root node, allocate new root 147 | IxNodeHandle root = create_node(); 148 | *root.hdr = IxPageHdr(IX_NO_PAGE, IX_NO_PAGE, 0, 0, false, IX_NO_PAGE, IX_NO_PAGE); 149 | // Insert current node's key & rid 150 | Rid curr_rid(node.page->id.page_no, -1); 151 | root.insert_rid(0, curr_rid); 152 | root.insert_key(0, node.get_key(node.hdr->num_key - 1)); 153 | // update current node's parent 154 | node.hdr->parent = root.page->id.page_no; 155 | // update global root page 156 | hdr.root_page = root.page->id.page_no; 157 | } 158 | // Allocate brother node 159 | IxNodeHandle bro = create_node(); 160 | *bro.hdr = IxPageHdr(IX_NO_PAGE, 161 | node.hdr->parent, // They have the same parent 162 | 0, 0, 163 | node.hdr->is_leaf, // Brother node is leaf only if current node is leaf. 164 | IX_NO_PAGE, IX_NO_PAGE); 165 | if (bro.hdr->is_leaf) { 166 | // maintain brother node's leaf pointer 167 | bro.hdr->next_leaf = node.hdr->next_leaf; 168 | bro.hdr->prev_leaf = node.page->id.page_no; 169 | // Let original next node's prev = brother node 170 | IxNodeHandle next = fetch_node(node.hdr->next_leaf); 171 | next.page->mark_dirty(); 172 | next.hdr->prev_leaf = bro.page->id.page_no; 173 | // curr's next = brother node 174 | node.hdr->next_leaf = bro.page->id.page_no; 175 | } 176 | // Split at middle position 177 | int split_idx = node.hdr->num_child / 2; 178 | // Keys in [0, split_idx) stay in current node, [split_idx, curr_keys) go to brother node 179 | int num_transfer = node.hdr->num_key - split_idx; 180 | bro.insert_keys(0, node.get_key(split_idx), num_transfer); 181 | bro.insert_rids(0, node.get_rid(split_idx), num_transfer); 182 | node.hdr->num_key = split_idx; 183 | node.hdr->num_child = split_idx; 184 | // Update children's parent 185 | for (int child_idx = 0; child_idx < bro.hdr->num_child; child_idx++) { 186 | maintain_child(bro, child_idx); 187 | } 188 | // Copy the last key up to its parent 189 | uint8_t *popup_key = node.get_key(split_idx - 1); 190 | // Load parent node 191 | IxNodeHandle parent = fetch_node(node.hdr->parent); 192 | parent.page->mark_dirty(); 193 | // Find the rank of current node in its parent 194 | int child_idx = parent.find_child(node); 195 | // Insert popup key into parent 196 | parent.insert_key(child_idx, popup_key); 197 | Rid bro_rid(bro.page->id.page_no, -1); 198 | parent.insert_rid(child_idx + 1, bro_rid); 199 | // Update global last_leaf if needed 200 | if (hdr.last_leaf == node.page->id.page_no) { 201 | hdr.last_leaf = bro.page->id.page_no; 202 | } 203 | // Go to its parent 204 | node = parent; 205 | } 206 | } 207 | 208 | void IxIndexHandle::delete_entry(const uint8_t *key, const Rid &rid) { 209 | Iid lower = lower_bound(key); 210 | Iid upper = upper_bound(key); 211 | for (IxScan scan(this, lower, upper); !scan.is_end(); scan.next()) { 212 | // load btree node 213 | IxNodeHandle node = fetch_node(scan.iid().page_no); 214 | assert(node.hdr->is_leaf); 215 | Rid *curr_rid = node.get_rid(scan.iid().slot_no); 216 | if (*curr_rid != rid) { 217 | continue; 218 | } 219 | // Found the entry with the given rid, delete it 220 | node.page->mark_dirty(); 221 | node.erase_key(scan.iid().slot_no); 222 | node.erase_rid(scan.iid().slot_no); 223 | // Update its parent's key to the node's new last key 224 | maintain_parent(node); 225 | // Solve underflow 226 | while (node.hdr->num_child < (hdr.btree_order + 1) / 2) { 227 | if (node.hdr->parent == IX_NO_PAGE) { 228 | // If current node is root node, underflow is permitted 229 | if (!node.hdr->is_leaf && node.hdr->num_key <= 1) { 230 | // If root node is not leaf and it is empty, delete the root 231 | int new_root_page = node.get_rid(0)->page_no; 232 | // Load new root and set its parent to NO_PAGE 233 | IxNodeHandle new_root = fetch_node(new_root_page); 234 | new_root.page->mark_dirty(); 235 | new_root.hdr->parent = IX_NO_PAGE; 236 | // Update global root 237 | hdr.root_page = new_root_page; 238 | // Free current page 239 | release_node(node); 240 | } 241 | break; 242 | } 243 | // Load parent node 244 | IxNodeHandle parent = fetch_node(node.hdr->parent); 245 | parent.page->mark_dirty(); 246 | // Find the rank of this child in its parent 247 | int child_idx = parent.find_child(node); 248 | if (0 < child_idx) { 249 | // current node has left brother, load it 250 | IxNodeHandle bro = fetch_node(parent.get_rid(child_idx - 1)->page_no); 251 | if (bro.hdr->num_child > (hdr.btree_order + 1) / 2) { 252 | // If left brother is rich, borrow one node from it 253 | bro.page->mark_dirty(); 254 | node.insert_key(0, bro.get_key(bro.hdr->num_key - 1)); 255 | node.insert_rid(0, *bro.get_rid(bro.hdr->num_child - 1)); 256 | bro.erase_key(bro.hdr->num_key - 1); 257 | bro.erase_rid(bro.hdr->num_child - 1); 258 | // Maintain parent's key as the node's max key 259 | maintain_parent(bro); 260 | // Maintain first child's parent 261 | maintain_child(node, 0); 262 | // underflow is solved 263 | break; 264 | } 265 | } 266 | if (child_idx + 1 < parent.hdr->num_child) { 267 | // current node has right brother, load it 268 | IxNodeHandle bro = fetch_node(parent.get_rid(child_idx + 1)->page_no); 269 | if (bro.hdr->num_child > (hdr.btree_order + 1) / 2) { 270 | // If right brother is rich, borrow one node from it 271 | bro.page->mark_dirty(); 272 | node.insert_key(node.hdr->num_key, bro.get_key(0)); 273 | node.insert_rid(node.hdr->num_child, *bro.get_rid(0)); 274 | bro.erase_key(0); 275 | bro.erase_rid(0); 276 | // Maintain parent's key as the node's max key 277 | maintain_parent(node); 278 | // Maintain last child's parent 279 | maintain_child(node, node.hdr->num_child - 1); 280 | // Underflow is solved 281 | break; 282 | } 283 | } 284 | // neither brothers is rich, need to merge 285 | if (0 < child_idx) { 286 | // merge with left brother, transfer all children of current node to left brother 287 | IxNodeHandle bro = fetch_node(parent.get_rid(child_idx - 1)->page_no); 288 | bro.page->mark_dirty(); 289 | bro.insert_keys(bro.hdr->num_key, node.get_key(0), node.hdr->num_key); 290 | bro.insert_rids(bro.hdr->num_child, node.get_rid(0), node.hdr->num_child); 291 | // Maintain left brother's children 292 | for (int i = bro.hdr->num_child - node.hdr->num_child; i < bro.hdr->num_child; i++) { 293 | maintain_child(bro, i); 294 | } 295 | parent.erase_key(child_idx); 296 | parent.erase_rid(child_idx); 297 | maintain_parent(bro); 298 | // Maintain leaf list 299 | if (node.hdr->is_leaf) { 300 | erase_leaf(node); 301 | } 302 | // Update global last-leaf 303 | if (hdr.last_leaf == node.page->id.page_no) { 304 | hdr.last_leaf = bro.page->id.page_no; 305 | } 306 | // Free current page 307 | release_node(node); 308 | } else { 309 | assert(child_idx + 1 < parent.hdr->num_child); 310 | // merge with right brother, transfer all children of right brother to current node 311 | IxNodeHandle bro = fetch_node(parent.get_rid(child_idx + 1)->page_no); 312 | bro.page->mark_dirty(); 313 | // Transfer all right brother's valid rid to current node 314 | node.insert_rids(node.hdr->num_child, bro.get_rid(0), bro.hdr->num_child); 315 | node.insert_keys(node.hdr->num_key, bro.get_key(0), bro.hdr->num_key); 316 | // Maintain current node's children 317 | for (int i = node.hdr->num_child - bro.hdr->num_child; i < node.hdr->num_child; i++) { 318 | maintain_child(node, i); 319 | } 320 | parent.erase_rid(child_idx + 1); 321 | parent.erase_key(child_idx); 322 | // Maintain parent's key as the node's max key 323 | maintain_parent(node); 324 | // Maintain leaf list 325 | if (bro.hdr->is_leaf) { 326 | erase_leaf(bro); 327 | } 328 | // Update global last leaf 329 | if (hdr.last_leaf == bro.page->id.page_no) { 330 | hdr.last_leaf = node.page->id.page_no; 331 | } 332 | // Free right brother page 333 | release_node(bro); 334 | } 335 | node = parent; 336 | } 337 | return; 338 | } 339 | throw IndexEntryNotFoundError(); 340 | } 341 | 342 | Rid IxIndexHandle::get_rid(const Iid &iid) const { 343 | IxNodeHandle node = fetch_node(iid.page_no); 344 | if (iid.slot_no >= node.hdr->num_child) { 345 | throw IndexEntryNotFoundError(); 346 | } 347 | return *node.get_rid(iid.slot_no); 348 | } 349 | 350 | Iid IxIndexHandle::lower_bound(const uint8_t *key) const { 351 | IxNodeHandle node = fetch_node(hdr.root_page); 352 | // Travel through inner nodes 353 | while (!node.hdr->is_leaf) { 354 | int key_idx = node.lower_bound(key); 355 | if (key_idx >= node.hdr->num_key) { 356 | return leaf_end(); 357 | } 358 | Rid *child = node.get_rid(key_idx); 359 | node = fetch_node(child->page_no); 360 | } 361 | // Now we come to a leaf node, we do a sequential search 362 | int key_idx = node.lower_bound(key); 363 | Iid iid(node.page->id.page_no, key_idx); 364 | return iid; 365 | } 366 | 367 | Iid IxIndexHandle::upper_bound(const uint8_t *key) const { 368 | IxNodeHandle node = fetch_node(hdr.root_page); 369 | // Travel through inner nodes 370 | while (!node.hdr->is_leaf) { 371 | int key_idx = node.upper_bound(key); 372 | if (key_idx >= node.hdr->num_key) { 373 | return leaf_end(); 374 | } 375 | Rid *child = node.get_rid(key_idx); 376 | node = fetch_node(child->page_no); 377 | } 378 | // Now we come to a leaf node, we do a sequential search 379 | int key_idx = node.upper_bound(key); 380 | Iid iid(node.page->id.page_no, key_idx); 381 | return iid; 382 | } 383 | 384 | Iid IxIndexHandle::leaf_end() const { 385 | IxNodeHandle node = fetch_node(hdr.last_leaf); 386 | Iid iid(hdr.last_leaf, node.hdr->num_key); 387 | return iid; 388 | } 389 | 390 | Iid IxIndexHandle::leaf_begin() const { 391 | Iid iid(hdr.first_leaf, 0); 392 | return iid; 393 | } 394 | 395 | IxNodeHandle IxIndexHandle::create_node() { 396 | Page *page; 397 | IxNodeHandle node; 398 | if (hdr.first_free == IX_NO_PAGE) { 399 | page = PfManager::pager.create_page(fd, hdr.num_pages); 400 | hdr.num_pages++; 401 | node = IxNodeHandle(&hdr, page); 402 | } else { 403 | page = PfManager::pager.fetch_page(fd, hdr.first_free); 404 | node = IxNodeHandle(&hdr, page); 405 | hdr.first_free = node.hdr->next_free; 406 | } 407 | page->mark_dirty(); 408 | return node; 409 | } 410 | 411 | IxNodeHandle IxIndexHandle::fetch_node(int page_no) const { 412 | assert(page_no < hdr.num_pages); 413 | Page *page = PfManager::pager.fetch_page(fd, page_no); 414 | IxNodeHandle node(&hdr, page); 415 | return node; 416 | } 417 | 418 | void IxIndexHandle::maintain_parent(const IxNodeHandle &node) { 419 | IxNodeHandle curr = node; 420 | while (curr.hdr->parent != IX_NO_PAGE) { 421 | // Load its parent 422 | IxNodeHandle parent = fetch_node(curr.hdr->parent); 423 | int rank = parent.find_child(curr); 424 | uint8_t *parent_key = parent.get_key(rank); 425 | uint8_t *child_max_key = curr.get_key(curr.hdr->num_key - 1); 426 | if (memcmp(parent_key, child_max_key, hdr.col_len) == 0) { 427 | break; 428 | } 429 | parent.page->mark_dirty(); 430 | memcpy(parent_key, child_max_key, hdr.col_len); 431 | curr = parent; 432 | } 433 | } 434 | 435 | void IxIndexHandle::erase_leaf(IxNodeHandle &leaf) { 436 | assert(leaf.hdr->is_leaf); 437 | IxNodeHandle prev = fetch_node(leaf.hdr->prev_leaf); 438 | prev.page->mark_dirty(); 439 | prev.hdr->next_leaf = leaf.hdr->next_leaf; 440 | 441 | IxNodeHandle next = fetch_node(leaf.hdr->next_leaf); 442 | next.page->mark_dirty(); 443 | next.hdr->prev_leaf = leaf.hdr->prev_leaf; 444 | } 445 | 446 | void IxIndexHandle::release_node(IxNodeHandle &node) { 447 | node.hdr->next_free = hdr.first_free; 448 | hdr.first_free = node.page->id.page_no; 449 | } 450 | 451 | void IxIndexHandle::maintain_child(IxNodeHandle &node, int child_idx) { 452 | if (!node.hdr->is_leaf) { 453 | // Current node is inner node, load its child and set its parent to current node 454 | int child_page_no = node.get_rid(child_idx)->page_no; 455 | IxNodeHandle child = fetch_node(child_page_no); 456 | child.page->mark_dirty(); 457 | child.hdr->parent = node.page->id.page_no; 458 | } 459 | } 460 | -------------------------------------------------------------------------------- /fig/arch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
System Management
(SM)
System Management...
Record Management
(RM)
Record Management...
Access Record File
Access Record File
Paged File
(PF)
Paged File...
Indexing
(IX)
Indexing...
Access Index File
Access Index File
Query Language
(QL)
Query Language...
Create/Drop
 Index
Create/Drop...
Scan Index
Scan Index
Scan Record
Scan Record
SQL Parser
SQL Parser
User Input
User Input
SQL
SQL
DML
DML
Create/Drop Table
Create/Drop Table
DDL
DDL
Access Table Meta
Access Table Meta
Viewer does not support full SVG 1.1
--------------------------------------------------------------------------------