From 39e8697e735e580fe83e8ab303b0609e1c75bd83 Mon Sep 17 00:00:00 2001 From: hskl18 Date: Sun, 12 Jul 2026 02:16:09 -0700 Subject: [PATCH] Modernize C++ database build and tests --- .DS_Store | Bin 6148 -> 0 bytes .github/workflows/ci.yml | 32 ++++ .gitignore | 53 +++--- .idea/.gitignore | 8 - .idea/.name | 1 - .idea/misc.xml | 4 - .idea/vcs.xml | 6 - CMakeLists.txt | 101 +++++------ LICENSE | 21 +++ README.md | 110 +++++++++--- _tests/_test_files/basic_test.cpp | 165 ------------------ _tests/_test_files/test_batch1.cpp | 42 ----- _tests/_test_files/test_batch2.cpp | 44 ----- includes/.DS_Store | Bin 6148 -> 0 bytes includes/binary_files/file_record.cpp | 3 +- includes/binary_files/utilities.cpp | 4 +- includes/binary_files/utilities.h | 4 +- .../linked_list_functions.h | 3 +- includes/linked_list/queue/MyQueue.h | 6 +- includes/linked_list/stack/MyStack.h | 4 +- includes/map/bplustree.h | 8 +- includes/map/btree_array_functions.h | 4 +- includes/map/map.h | 4 +- includes/map/mmap.h | 4 +- includes/map/mpair.h | 10 +- includes/parser/parser.cpp | 10 +- includes/shunting_yard/token.h | 6 +- includes/sql/sql.cpp | 20 +-- includes/table/table.cpp | 33 +--- includes/table/table.h | 10 +- includes/tokenizer/ftokenize.h | 4 +- .../tokenizer/state_machine_functions.cpp | 7 +- includes/tokenizer/stokenize.cpp | 35 ++-- includes/tokenizer/stokenize.h | 6 +- tests/sql_tests.cpp | 136 +++++++++++++++ 35 files changed, 417 insertions(+), 491 deletions(-) delete mode 100644 .DS_Store create mode 100644 .github/workflows/ci.yml delete mode 100644 .idea/.gitignore delete mode 100644 .idea/.name delete mode 100644 .idea/misc.xml delete mode 100644 .idea/vcs.xml create mode 100644 LICENSE delete mode 100644 _tests/_test_files/basic_test.cpp delete mode 100644 _tests/_test_files/test_batch1.cpp delete mode 100644 _tests/_test_files/test_batch2.cpp delete mode 100644 includes/.DS_Store create mode 100644 tests/sql_tests.cpp diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 155e3a8bd1b8fcab2677b732826a3959ce248082..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK&ubGw6n>MYCTkILQ0PS%=rzQy5T$t8#`MsGmu}I6O3ZdcUEJ=J?8XQouxI^a zy!J2g-_h^QOvNRNrwWz#;LZ1D-g`65H^clOA~jm1AyJQrx=_YefZ{j8e%1|X`Ic>< zvVELWK?ADj6IA3jFu-efmr|NhNg4gJe#f~vOLCLI&zs{bbnx8@EZz_q*7}TcTDa_H zyn6Vz_+(jL`u?wy%RDQq{rzvMwbkCf)9D1AozC0%y;;W9v|5y->HIa96`_xCcWL&DttWb>o5%0eZ6{e zaM0Jor^EF+=swtc^z3BvA-^chXFF3;*xOd_dAx=%FgAGj7|)B`6zA}9UO*>wM29q{ z7r+5sP}*b)z9zG%iU - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 478dc82..22cf592 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,72 +1,49 @@ -cmake_minimum_required(VERSION 3.5 FATAL_ERROR) -project(project_name) +cmake_minimum_required(VERSION 3.20) + +project(SQLInCPP VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set(SQL_CORE_SOURCES + includes/binary_files/file_record.cpp + includes/binary_files/utilities.cpp + includes/parser/parser.cpp + includes/shunting_yard/shunting_yard.cpp + includes/sql/sql.cpp + includes/table/table.cpp + includes/tokenizer/ftokenize.cpp + includes/tokenizer/state_machine_functions.cpp + includes/tokenizer/stokenize.cpp +) + +add_library(sql_core STATIC ${SQL_CORE_SOURCES}) +target_include_directories(sql_core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") -#. . . . . . . . . . . . . . . . . . . . -include(GNUInstallDirs) if(MSVC) - add_compile_options(/W4) - add_link_options(/W4) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin) + target_compile_options(sql_core PRIVATE /W4) else() - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + target_compile_options(sql_core PRIVATE -Wall -Wextra -Wpedantic) endif() +add_executable(run_sql run_sql.cpp) +target_link_libraries(run_sql PRIVATE sql_core) -# place binaries and libraries according to GNU standards -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +include(CTest) +if(BUILD_TESTING) + add_executable(sql_tests tests/sql_tests.cpp) + target_link_libraries(sql_tests PRIVATE sql_core) + target_compile_definitions(sql_tests PRIVATE + SQL_TEST_FIXTURE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/batch" + ) + set(SQL_TEST_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/test-workspace") + file(MAKE_DIRECTORY "${SQL_TEST_WORKING_DIRECTORY}") -if(CMAKE_CXX_COMPILER_ID MATCHES GNU) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") + add_test(NAME sql_behavior COMMAND sql_tests) + set_tests_properties(sql_behavior PROPERTIES + WORKING_DIRECTORY "${SQL_TEST_WORKING_DIRECTORY}" + ) endif() -#. . . . . . . . . . . . . . . . . . . . - -# ------------------------------------ -include(FetchContent) -FetchContent_Declare( - googletest - URL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip -) - -# For Windows: Prevent overriding the parent project's compiler/linker settings -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(googletest) - -# GoogleTest requires at least C++11 -set(CMAKE_CXX_STANDARD 14) - -enable_testing() - - -# ------------------------------------ - -#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Grab all cpp files from includes folder -#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -set(INCLUDES_FOLDER includes) -FILE(GLOB_RECURSE SOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "${INCLUDES_FOLDER}/*.cpp" ) -#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -ADD_EXECUTABLE(run_sql - run_sql.cpp - ${SOURCE_FILES} -) -ADD_EXECUTABLE(basic_test - _tests/_test_files/basic_test.cpp - ${SOURCE_FILES} -) -ADD_EXECUTABLE(test_batch1 - _tests/_test_files/test_batch1.cpp - ${SOURCE_FILES} - ) - -ADD_EXECUTABLE(test_batch2 - _tests/_test_files/test_batch2.cpp - ${SOURCE_FILES} - ) -TARGET_LINK_LIBRARIES(run_sql gtest) -TARGET_LINK_LIBRARIES(basic_test gtest) -TARGET_LINK_LIBRARIES(test_batch1 gtest) -TARGET_LINK_LIBRARIES(test_batch2 gtest) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9933c67 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Hanbin Tan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index d1c2679..2f910ea 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,90 @@ -# SQL_InCPP -A small version of what is called a relational database management system (DBMS), -as well as a DSL (Domain Specific Language) with which a user can interact with your database. +# SQL in C++ +SQL in C++ is an educational relational database engine and command-line DSL implemented from first principles. +It parses a focused subset of SQL, persists records to binary files, indexes field values with B+ trees, and evaluates compound filters with a shunting-yard pipeline. -## Preview +![Command-line preview](image/preview.png) - ![preview.png](./image/preview.png) -## Reserved keywords +## What it supports -- create -- table -- fields -- insert -- into -- values -- select -- from -- where -- \* -- and, or -- !=, = , <, >, >=, <= +- `CREATE TABLE` with an ordered field list. +- `INSERT INTO` with quoted or unquoted values. +- `SELECT *` and projected field lists. +- `WHERE` filters using `=`, `!=`, `<`, `<=`, `>`, and `>=`. +- Compound `AND` and `OR` expressions with parentheses. +- Binary persistence across separate CLI sessions. +- Batch execution from the sample command files in `batch/`. -- create table fields [, ...] -- insert into values [, ...] -- select <\* | FIELD_NAME> [, ...] from where - [ ...] +This project is intentionally smaller than a production SQL database. +It does not implement joins, transactions, schemas, query planning, updates, or deletes. +Values are stored and ordered as strings, so numeric-looking values use lexical comparison semantics. -## Design +## Architecture -It can be found in https://github.com/hanbintan2003/SQL/blob/main/design.txt +| Component | Responsibility | +| --- | --- | +| `includes/tokenizer` | Converts command text into lexical tokens with a state machine. | +| `includes/parser` | Validates the supported grammar and builds a parse tree. | +| `includes/shunting_yard` | Converts compound filter expressions to postfix form. | +| `includes/map` | Implements B+ tree-backed maps and multimaps. | +| `includes/binary_files` | Reads and writes fixed-format binary records. | +| `includes/table` | Owns table persistence, indexes, projections, and selections. | +| `includes/sql` | Coordinates parsing and table operations through the public `SQL` interface. | -## Features +Additional implementation notes are available in [design.txt](design.txt). +The canonical repository is [hskl18/SQL](https://github.com/hskl18/SQL). -- it supports unlimited amount of whitespaces between the input statement -- it supports complex boolean query statements for selection -- it supports permanent storage where the created tables will be stored inside the binary files -- it has an interactive mode where the user can type into the command line and the input statement can be used to interact with the database +## Build + +You need a C++17 compiler and CMake 3.20 or newer. + +```bash +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build --parallel +``` + +The build has no downloaded test framework or other third-party dependency. + +## Test + +```bash +ctest --test-dir build --output-on-failure +``` + +The test executable verifies inserts, projections, compound filters, persistence, invalid input handling, and both repository batch fixtures. +Tests run in an isolated build directory, so they do not leave database files in the source tree. + +## Run the CLI + +```bash +./build/run_sql +``` + +Type `end` to exit and `cls` to clear the terminal. +The CLI writes each table to `.bin` and `
_fields.bin` in the current working directory. + +Example session: + +```text +SQL>> create table student fields first, last, major, age +SQL>> insert into student values Ada, Lovelace, Math, 36 +SQL>> insert into student values Grace, Hopper, CS, 30 +SQL>> select first, major from student where major = CS +SQL>> end +``` + +## Query grammar + +```text +create table
fields [, ...] +insert into
values [, ...] +select <* | field [, field ...]> from
+ [where + [ ...]] +``` + +Quote values that contain spaces, such as `"Mary Ann"`. + +## License + +This project is available under the [MIT License](LICENSE). diff --git a/_tests/_test_files/basic_test.cpp b/_tests/_test_files/basic_test.cpp deleted file mode 100644 index b9fd072..0000000 --- a/_tests/_test_files/basic_test.cpp +++ /dev/null @@ -1,165 +0,0 @@ -#include "gtest/gtest.h" -#include -#include - -//------------------------------------------------------------------------------ -// Files we are testing: -#include "../../includes/sql/sql.h" - -//------------------------------------------------------------------------------ - -using namespace std; - -//------------------------------------------------------------------------------ -// COPY BASIC_TEST INTO THIS FILE. -// AND THEN, -// DO NOT EDIT THIS FILE ANY FURTHER -//------------------------------------------------------------------------------ - -const vector command_list = { - /*00*/ "create table employee fields last, first, dep, salary, year", - /*01*/ "insert into employee values Blow, Joe, CS, 100000, 2018", - /*02*/ "insert into employee values Blow, JoAnn, Physics, 200000, 2016", - /*03*/ "insert into employee values Johnson, Jack, HR, 150000, 2014", - /*04*/ "insert into employee values Johnson, \"Jimmy\", Chemistry,140000, 2018", - - /*05*/ "create table student fields fname, lname, major, age", - /*06*/ "insert into student values Flo, Yao, Art, 20", - /*07*/ "insert into student values Bo, Yang, CS, 28", - /*08*/ "insert into student values \"Sammuel L.\", Jackson, CS, 40", - /*09*/ "insert into student values \"Billy\", Jackson, Math, 27", - /*10*/ "insert into student values \"Mary Ann\", Davis, Math, 30", - - /*11*/ "select * from employee", - /*12*/ "select last, first, age from employee", - /*13*/ "select last from employee", - /*14*/ "select * from employee where last = Johnson", - /*15*/ "select * from employee where last=Blow and major=\"JoAnn\"", - - /*16*/ "select * from student", - /*17*/ "select * from student where (major=CS or major=Art)", - /*18*/ "select * from student where lname>J", - /*19*/ "select * from student where lname>J and (major=CS or major=Art)"}; - -const int MAKE_TABLE_COMMANDS = 11; -const int SELECT_COMMANDS = 20; - -bool sql_basic(bool debug = false){ - remove("student.bin"); - remove("student_fields.bin"); - remove("employee.bin"); - remove("employee_fields.bin"); - - SQL sql; - Table t; - // create the tables - cout << "> " << command_list[0] << endl; - sql.command(command_list[0]); - cout << "table is created" << endl - << endl; - // insert to the tables - for (int i = 0; i < MAKE_TABLE_COMMANDS; i++) - { - cout << "> " << command_list[i] << endl; - t = sql.command(command_list[i]); - } - - cout << endl - << endl; - - // select the tables - for (int i = MAKE_TABLE_COMMANDS; i < command_list.size(); i++){ - cout << "> " << command_list[i] << endl; - t = sql.command(command_list[i]); - if (debug) cout << t << endl; - cout << "records selected: " << sql.select_recnos() << endl - << endl; - } - remove("student.bin"); - remove("student_fields.bin"); - remove("employee.bin"); - remove("employee_fields.bin"); - - return true; -} - -// Lord help me! -const bool debug = true; - -TEST(SQL_BASIC, SQLBasic) -{ - bool success = sql_basic(debug); - EXPECT_EQ(success, true); -} - -int main(int argc, char** argv){ - ::testing::InitGoogleTest(&argc, argv); - std::cout << "\n\n----------running basic_test.cpp---------\n\n" - << std::endl; - - return RUN_ALL_TESTS(); -} - -/* -----------running basic_test.cpp--------- - - -[==========] Running 2 tests from 1 test suite. -[----------] Global test environment set-up. -[----------] 2 tests from SQL_BASIC -[ RUN ] SQL_BASIC.SQLBasic -FAILED: create table: table name cannot be empty -FAILED: create table: table name cannot be empty -> create table employee fields last, first, dep, salary, year -table is created - -> create table employee fields last, first, dep, salary, year -FAILED: create table: employee table already exists -> insert into employee values Blow, Joe, CS, 100000, 2018 -> insert into employee values Blow, JoAnn, Physics, 200000, 2016 -> insert into employee values Johnson, Jack, HR, 150000, 2014 -> insert into employee values Johnson, "Jimmy", Chemistry,140000, 2018 -> create table student fields fname, lname, major, age -> insert into student values Flo, Yao, Art, 20 -> insert into student values Bo, Yang, CS, 28 -> insert into student values "Sammuel L.", Jackson, CS, 40 -> insert into student values "Billy", Jackson, Math, 27 -> insert into student values "Mary Ann", Davis, Math, 30 - - -> select * from employee -records selected: 0 1 2 3 - -> select last, first, age from employee -records selected: 0 1 2 3 - -> select last from employee -records selected: 0 1 2 3 - -> select * from employee where last = Johnson -records selected: 2 3 - -> select * from employee where last=Blow and major="JoAnn" -records selected: - -> select * from student -records selected: 0 1 2 3 4 - -> select * from student where (major=CS or major=Art) -records selected: 0 1 2 - -> select * from student where lname>J -records selected: 2 3 1 0 - -> select * from student where lname>J and (major=CS or major=Art) -records selected: 0 1 2 - -[ OK ] SQL_BASIC.SQLBasic (21 ms) -[ RUN ] SQL_BASIC.SQLBatch -[ OK ] SQL_BASIC.SQLBatch (79 ms) -[----------] 2 tests from SQL_BASIC (100 ms total) - -[----------] Global test environment tear-down -[==========] 2 tests from 1 test suite ran. (101 ms total) -[ PASSED ] 2 tests. -*/ \ No newline at end of file diff --git a/_tests/_test_files/test_batch1.cpp b/_tests/_test_files/test_batch1.cpp deleted file mode 100644 index 6e9eb99..0000000 --- a/_tests/_test_files/test_batch1.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "gtest/gtest.h" -#include -#include - -//------------------------------------------------------------------------------ -// Files we are testing: -#include "../../includes/sql/sql.h" -//------------------------------------------------------------------------------ - -using namespace std; - -bool sql_batch(bool debug = false){ - remove("employeeSample01.bin"); - remove("employeeSample01_fields.bin"); - remove("studentSample01.bin"); - remove("studentSample01_fields.bin"); - - SQL sql; - string s = "_!sample01.txt"; - sql.batch(s.c_str()); - - remove("employeeSample01.bin"); - remove("employeeSample01_fields.bin"); - remove("studentSample01.bin"); - remove("studentSample01_fields.bin"); - return true; -} - -// Lord help me! -const bool debug = false; - -TEST(SQL_BASIC, SQLBatch){ - bool success = sql_batch(debug); - EXPECT_EQ(success, true); -} - -int main(int argc, char** argv){ - ::testing::InitGoogleTest(&argc, argv); - std::cout << "\n\n----------running test_batch1.cpp---------\n\n" - << std::endl; - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/_tests/_test_files/test_batch2.cpp b/_tests/_test_files/test_batch2.cpp deleted file mode 100644 index 0ec8314..0000000 --- a/_tests/_test_files/test_batch2.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "gtest/gtest.h" -#include -#include - -//------------------------------------------------------------------------------ -// Files we are testing: -#include "../../includes/sql/sql.h" -//------------------------------------------------------------------------------ - -using namespace std; - -bool sql_batch(bool debug = false) -{ - remove("studentSelect02.bin"); - remove("studentSelect02_fields.bin"); - remove("employeeSample02.bin"); - remove("employeeSample02_fields.bin"); - SQL sql; - string s = "_!sample02.txt"; - sql.batch(s.c_str()); - - remove("studentSelect02.bin"); - remove("studentSelect02_fields.bin"); - remove("employeeSample02.bin"); - remove("employeeSample02_fields.bin"); - return true; -} - -// Lord help me! -const bool debug = false; - -TEST(SQL_BASIC, SQLBatch) -{ - bool success = sql_batch(debug); - EXPECT_EQ(success, true); -} - -int main(int argc, char** argv) -{ - ::testing::InitGoogleTest(&argc, argv); - std::cout << "\n\n----------running test_batch2.cpp---------\n\n" - << std::endl; - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/includes/.DS_Store b/includes/.DS_Store deleted file mode 100644 index 29acbac45ae0be2967fec2e0d3b8004c7567eb29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKO>fgM7=FEWYeSjT1Cw?^io~^o1*xEkODN;Ol^{3(DhUZ&v@GtDlxnLg^$h=k zzrdAW!ry`CwLK|K*^U(;zmom<<9+<`IJRRV5~FGC5%r0vi@?~vh3F5)^&Hl0%akxr zs5T0^pfP=-l(IE%TVNHi3jA#f@ZP;m5q4}&A=US9LgkB0evC31!G{lsrwI3dDMN~= zhh5IRQ%WetzUN#?RfF}t!D`VNdYY_yiMtK29h#`|lxVwX_v)v6{dMoC_)-1aC!G8&0{$m$!Q*!iW}$($LTub{{DRKJsb{P z&+`@ocmC+`aNrIf4;KrkyLa!wlb7Su>@1gGpaV?c>{YgF@CsO`8)o=9DDq6^$ME77 zT2S$vvW8;3dUeINp>c@sC8K23Q>|d#(}AZ{-$ssAxx8i_>kM>dZG#G!@21<^V4t=t ztAJJD-%x@)PSQ*m%tH2!Bpl@)g5j8NOsX$E?=7}LR9p$d^ z^9?RFYB~w?_zWb5;SXz<;HH zXdeYfBTUKMt!tCxyVgbcfWXFnOO1+x$Xv%N!B_D_records.emplace_back(str,n); @@ -50,4 +50,3 @@ vector FileRecord::get_records() { return ans; //return the record } - diff --git a/includes/binary_files/utilities.cpp b/includes/binary_files/utilities.cpp index 6ce4e8e..f12aedf 100644 --- a/includes/binary_files/utilities.cpp +++ b/includes/binary_files/utilities.cpp @@ -11,7 +11,7 @@ bool file_exists(const char filename[]){ } // Open a file for reading and writing -void open_fileRW(fstream& f, const char filename[]) throw(char*){ +void open_fileRW(fstream& f, const char filename[]){ f.open(filename, ios::in | ios::out | ios::binary); if(!f.is_open()){ throw "File could not be opened"; @@ -25,4 +25,4 @@ void open_fileW(fstream& f, const char filename[]){ throw "File could not be opened"; } } -// Open a file for reading \ No newline at end of file +// Open a file for reading diff --git a/includes/binary_files/utilities.h b/includes/binary_files/utilities.h index 4dc7960..b177b6b 100644 --- a/includes/binary_files/utilities.h +++ b/includes/binary_files/utilities.h @@ -5,7 +5,7 @@ using namespace std; bool file_exists(const char filename[]); -void open_fileRW(fstream& f, const char filename[]) throw(char*); +void open_fileRW(fstream& f, const char filename[]); void open_fileW(fstream& f, const char filename[]); -#endif // UTILITIES_H \ No newline at end of file +#endif // UTILITIES_H diff --git a/includes/linked_list/linked_list_functions/linked_list_functions.h b/includes/linked_list/linked_list_functions/linked_list_functions.h index 1d9dc90..39c0336 100644 --- a/includes/linked_list/linked_list_functions/linked_list_functions.h +++ b/includes/linked_list/linked_list_functions/linked_list_functions.h @@ -202,6 +202,7 @@ T _delete_node(node*& head, node* delete_this) if (delete_this == lastNode) { node* prevNode = lastNode->_prev; + if (prevNode != nullptr) prevNode->_next = nullptr; delete delete_this; return val; } @@ -273,4 +274,4 @@ node* _last_node(node* head) return _last_node(head->_next); } -#endif // LINKED_LIST_FUNCTIONS_H \ No newline at end of file +#endif // LINKED_LIST_FUNCTIONS_H diff --git a/includes/linked_list/queue/MyQueue.h b/includes/linked_list/queue/MyQueue.h index e5d683a..465f41c 100644 --- a/includes/linked_list/queue/MyQueue.h +++ b/includes/linked_list/queue/MyQueue.h @@ -46,7 +46,7 @@ class Queue{ return *this; } // friend operator: it++ - friend Iterator operator++(Iterator& it, int unused){ + friend Iterator operator++(Iterator& it, int){ Iterator hold = it; it._ptr = it._ptr->_next; return hold; @@ -75,7 +75,7 @@ class Queue{ } const T& operator[](int i) const{return (*this)[i];} Queue& operator+=(const vector& items){ - for (int i = 0; i < items.size(); i++){ + for (std::size_t i = 0; i < items.size(); i++){ this->push(items[i]); } return *this; @@ -224,4 +224,4 @@ ostream& operator<<(ostream& outs, const Queue& printMe){ return outs; } -#endif // MYQUEUE_H \ No newline at end of file +#endif // MYQUEUE_H diff --git a/includes/linked_list/stack/MyStack.h b/includes/linked_list/stack/MyStack.h index 193300a..d6c432b 100644 --- a/includes/linked_list/stack/MyStack.h +++ b/includes/linked_list/stack/MyStack.h @@ -50,7 +50,7 @@ class Stack{ return *this; } // friend operator: it++ - friend Iterator operator++(Iterator& it, int unused){ + friend Iterator operator++(Iterator& it, int){ Iterator hold = it; it._ptr = it._ptr->_next; return hold; @@ -180,4 +180,4 @@ ostream& operator<<(ostream& outs, const Stack& printMe){ return outs; } -#endif // MYSTACK_H \ No newline at end of file +#endif // MYSTACK_H diff --git a/includes/map/bplustree.h b/includes/map/bplustree.h index 9746e56..70cf938 100644 --- a/includes/map/bplustree.h +++ b/includes/map/bplustree.h @@ -34,7 +34,7 @@ class BPlusTree{ } // Postfix ++ (it++) - Iterator operator ++ (int un_used){ + Iterator operator ++ (int){ Iterator ans = *this; if(this->key_ptr == this->it->data_count-1){ this->it=this->it->next; @@ -209,7 +209,7 @@ void BPlusTree::copy_tree(const BPlusTree &source, BPlusTree *&last_node) this->next= nullptr; this->child_count = source.child_count; - for (int i = 0; i < this->child_count; ++i) { + for (std::size_t i = 0; i < this->child_count; ++i) { this->subset[i]=new BPlusTree(); this->subset[i]->copy_tree(*source.subset[i], last_node); } @@ -546,8 +546,6 @@ void BPlusTree::transfer_from_right(std::size_t i){ template BPlusTree* BPlusTree::merge_with_next_subset(std::size_t i){ BPlusTree* left_subset = this->subset[i]; - BPlusTree* right_subset = this->subset[i + 1]; - Item item; delete_item(this->data, i, this->data_count, item); if (!left_subset->is_leaf()) attach_item(left_subset->data, left_subset->data_count, item); @@ -613,4 +611,4 @@ bool BPlusTree::is_valid() const // TODO -#endif // BPLUSTREE_H \ No newline at end of file +#endif // BPLUSTREE_H diff --git a/includes/map/btree_array_functions.h b/includes/map/btree_array_functions.h index 54c60aa..8dc9c63 100644 --- a/includes/map/btree_array_functions.h +++ b/includes/map/btree_array_functions.h @@ -86,7 +86,7 @@ std::size_t index_of_maximal(T data[], std::size_t n){ template std::size_t first_ge(const T data[], std::size_t n, const T& entry){ - for (int i = 0; i < n; ++i) + for (std::size_t i = 0; i < n; ++i) if (!(data[i] < entry)) return i; return n; @@ -188,4 +188,4 @@ void print_array(const T data[], std::size_t n, std::size_t pos){ }//print array data -#endif // BTREE_ARRAY_FUNCTIONS_H \ No newline at end of file +#endif // BTREE_ARRAY_FUNCTIONS_H diff --git a/includes/map/map.h b/includes/map/map.h index 9ee43a8..ba48ca5 100644 --- a/includes/map/map.h +++ b/includes/map/map.h @@ -20,7 +20,7 @@ class Map{ : _it(it){} // OPERATORS - Iterator operator++(int unused){ + Iterator operator++(int){ Iterator hold = *this; this->_it++; return hold; @@ -182,4 +182,4 @@ template typename Map::Iterator Map::upper_bound(const K& key){ return Iterator(this->map.upper_bound(Pair(key))); } -#endif // MAP_H \ No newline at end of file +#endif // MAP_H diff --git a/includes/map/mmap.h b/includes/map/mmap.h index 872e414..883ca38 100644 --- a/includes/map/mmap.h +++ b/includes/map/mmap.h @@ -23,7 +23,7 @@ class MMap{ : _it(it){} // OPERATORS - Iterator operator++(int unused){ + Iterator operator++(int){ Iterator hold = *this; this->_it++; return hold; @@ -120,4 +120,4 @@ class MMap{ } }; -#endif // MMAP_H \ No newline at end of file +#endif // MMAP_H diff --git a/includes/map/mpair.h b/includes/map/mpair.h index d9f0750..3607782 100644 --- a/includes/map/mpair.h +++ b/includes/map/mpair.h @@ -18,7 +18,7 @@ struct MPair{ // Overloaded operators friend std::ostream& operator<<(std::ostream& outs, const MPair& print_me){ outs << print_me.key << ":"; - for(int i = 0; i < print_me.value_list.size(); ++i) outs << print_me.value_list[i] << " "; + for(std::size_t i = 0; i < print_me.value_list.size(); ++i) outs << print_me.value_list[i] << " "; outs << endl; return outs; } @@ -36,7 +36,7 @@ struct MPair{ } friend MPair operator+(const MPair& lhs, const MPair& rhs){ MPair mpair(lhs.key, lhs.value_list); - for (int i = 0; i < rhs.value_list; ++i) + for (std::size_t i = 0; i < rhs.value_list.size(); ++i) mpair.value_list.push_back(rhs.value_list[i]); return mpair; } @@ -48,7 +48,7 @@ struct MPair{ template std::ostream& operator<<(std::ostream& outs, const std::vector& vec){ - for (int i = 0; i < vec.size(); ++i) outs << vec[i] << " "; + for (std::size_t i = 0; i < vec.size(); ++i) outs << vec[i] << " "; outs << endl; return outs; } @@ -61,8 +61,8 @@ std::vector& operator+=(std::vector& vec, const Item& value){ template std::vector& operator+=(std::vector& lhs, const std::vector& rhs) { - for (int i = 0; i < rhs.size(); ++i) lhs.push_back(rhs[i]); + for (std::size_t i = 0; i < rhs.size(); ++i) lhs.push_back(rhs[i]); return lhs; } -#endif // MPAIR_H \ No newline at end of file +#endif // MPAIR_H diff --git a/includes/parser/parser.cpp b/includes/parser/parser.cpp index 0034441..a0aada2 100644 --- a/includes/parser/parser.cpp +++ b/includes/parser/parser.cpp @@ -40,7 +40,7 @@ MMap Parser::parse_tree(){ bool Parser::parse(){ // Check if the number of types matches the number of input tokens - if (this->types.size() != this->input.size()) return false; + if (this->types.empty() || this->types.size() != this->input.size()) return false; int state = 0; Ptype type = this->types[0]; @@ -75,7 +75,7 @@ bool Parser::parse(){ // Update the state based on the state table state = this->table[0][type]; - for (int i = 1; i < this->input.size(); ++i){ + for (std::size_t i = 1; i < this->input.size(); ++i){ type = this->types[i]; int prev_state = state; state = this->table[prev_state][type]; @@ -162,7 +162,7 @@ void Parser::tokenize(){ // reserved keywords bool did_push = false; if (token.type() == TOKEN_ALPHA){ - int prev_size = this->types.size(); + std::size_t prev_size = this->types.size(); if (token.token_str() == "create") this->types.push_back(CREATE); if (token.token_str() == "table") this->types.push_back(TABLE); if (token.token_str() == "fields") this->types.push_back(FIELDS); @@ -196,9 +196,9 @@ void Parser::tokenize(){ // process "" here, basically concat vector clean_input; vector clean_type; - for (int i = 0; i < this->input.size(); ++i){ + for (std::size_t i = 0; i < this->input.size(); ++i){ if (this->input[i].token_str() == "\""){ - int j = i + 1; + std::size_t j = i + 1; string s = ""; while (this->input[j].type() == TOKEN_ALPHA){ if (j != i + 1) s += " "; diff --git a/includes/shunting_yard/token.h b/includes/shunting_yard/token.h index 641f276..a748790 100644 --- a/includes/shunting_yard/token.h +++ b/includes/shunting_yard/token.h @@ -13,10 +13,10 @@ class Token { public: Token() = default; - ~Token() = default; + virtual ~Token() = default; virtual TokenType token_type() const{ return TOKEN_TOKENSTR;} virtual string token_string() const{ return "";} - virtual void print(ostream& outs) const{} + virtual void print(ostream&) const{} virtual int precedence() const{ return -1;} friend ostream& operator<<(ostream& outs, const Token& token){ token.print(outs); @@ -28,4 +28,4 @@ class Token } }; -#endif \ No newline at end of file +#endif diff --git a/includes/sql/sql.cpp b/includes/sql/sql.cpp index 3512c8d..e71b3a5 100644 --- a/includes/sql/sql.cpp +++ b/includes/sql/sql.cpp @@ -4,6 +4,7 @@ SQL::SQL(){ this->_table=Table(); this->_parser=Parser(); + this->_error=false; } // Process commands from a file SQL::SQL(const char* file): SQL(){this->batch(file);} @@ -15,7 +16,8 @@ Table SQL::command(const string& cmd){ MMap parsed_tree = this->_parser.parse_tree(); this->_ptree = parsed_tree; - if (_ptree.empty())return _table; + this->_error = _ptree.empty(); + if (this->_error) return _table; string table_name = parsed_tree["table_name"][0]; string command = parsed_tree["command"][0]; @@ -48,22 +50,20 @@ Table SQL::command(const string& cmd){ void SQL::batch(const char* file){ ifstream f; - string temp = file; - string input_file_path = "../../batch/" + temp; - - f.open(input_file_path.c_str()); + f.open(file); if (f.fail()){ + this->_error = true; cout << "No file named " << file << " exists." << endl; return; } + this->_error = false; cout << "------------------------------Batch Begins------------------------------" << endl; - while (!f.eof()){ - string str; - getline(f, str); + string str; + while (getline(f, str)){ - if(str[0] != '/' && !str.empty()){ + if(!str.empty() && str[0] != '/'){ cout << "command:" << str << endl; this->_table = this->command(str); @@ -77,4 +77,4 @@ void SQL::batch(const char* file){ f.close(); cout << "------------------------------DONE------------------------------" << endl; -} \ No newline at end of file +} diff --git a/includes/table/table.cpp b/includes/table/table.cpp index 10937e5..9f3beb2 100644 --- a/includes/table/table.cpp +++ b/includes/table/table.cpp @@ -42,7 +42,7 @@ Table::Table(const string& table_name, const vector& field_names) : Tabl recordIndices.clear(); // Initialize fieldNameMap and cache - for (int i = 0; i < fieldNames.size(); ++i) { + for (std::size_t i = 0; i < fieldNames.size(); ++i) { fieldNameMap[fieldNames[i]] = i; cache[fieldNames[i]] = MMap(); } @@ -76,7 +76,7 @@ Table::Table(const string& table_name) : Table() { selectedFields = field_names; f.close(); - for (int i = 0; i < fieldNames.size(); ++i) { + for (std::size_t i = 0; i < fieldNames.size(); ++i) { fieldNameMap[fieldNames[i]] = i; } @@ -96,7 +96,7 @@ Table::Table(const string& table_name) : Table() { // Put the ith_entry into cache vector ith_entry = r.get_records_string(); - for (int ith_entry_walker = 0; ith_entry_walker < ith_entry.size(); ++ith_entry_walker) { + for (std::size_t ith_entry_walker = 0; ith_entry_walker < ith_entry.size(); ++ith_entry_walker) { string field_value = ith_entry[ith_entry_walker]; long index = i; cache[fieldNames[ith_entry_walker]].insert(field_value, index); @@ -139,7 +139,7 @@ string Table::insert_into(const vector& field_values) { f.close(); // Insert the record into the cache - for (int i = 0; i < field_values.size(); ++i) { + for (std::size_t i = 0; i < field_values.size(); ++i) { // Extract the field value and field name const string& field_value = field_values[i]; string field_name = fieldNames[i]; @@ -166,7 +166,7 @@ ostream& operator<<(ostream& outs, const Table& print_me){ // for field values int eIndex = print_me.fieldNames.size(); - for (int i = 0; i < print_me.printQueue.size(); ++i){ + for (std::size_t i = 0; i < print_me.printQueue.size(); ++i){ string field_value = print_me.printQueue[i]; if (i % eIndex == 0){ outs << endl; @@ -253,7 +253,7 @@ Table Table::select(const vector& selected_fields, const string& field_n if (byte == 0) break; vector entry = r.get_records_string(); - for (int i = 0; i < entry.size(); ++i) + for (std::size_t i = 0; i < entry.size(); ++i) if (contains(temp.selectedFields, fieldNames[i])) { temp.printQueue += entry[i]; @@ -277,22 +277,7 @@ Table Table::select(const vector& selected_fields, const vector& if (!infix.empty()){ typename Queue::Iterator it; for (it = infix.begin(); it != infix.end(); ++it){ - // Determine the token type and delete the appropriate dynamic cast - TokenType tokenType = (*it)->token_type(); - - if (tokenType == TOKEN_TOKENSTR) { - delete dynamic_cast(*it); - } else if (tokenType == TOKEN_RIGHTPAREN) { - delete dynamic_cast(*it); - } else if (tokenType == TOKEN_LEFTPAREN) { - delete dynamic_cast(*it); - } else if (tokenType == TOKEN_LOGICAL) { - delete dynamic_cast(*it); - } else if (tokenType == TOKEN_RELATIONAL) { - delete dynamic_cast(*it); - } else { - // Do nothing for other token types - } + delete *it; } infix.clear(); } @@ -326,7 +311,7 @@ Table Table::select(const vector& selected_fields, const Queue& if (!expression.empty()){temp.recordIndices = Eval(expression);} else{ recordIndices.clear(); - for (int i = 0; i < numRecords; ++i){ + for (long i = 0; i < numRecords; ++i){ temp.recordIndices.push_back(i); } } @@ -341,7 +326,7 @@ Table Table::select(const vector& selected_fields, const Queue& if (byte == 0) break; vector entry = r.get_records_string(); - for (int i = 0; i < entry.size(); ++i) + for (std::size_t i = 0; i < entry.size(); ++i) if (contains(temp.fieldNames, fieldNames[i])) temp.printQueue += entry[i]; } f.close(); diff --git a/includes/table/table.h b/includes/table/table.h index 7e60a00..d41545b 100644 --- a/includes/table/table.h +++ b/includes/table/table.h @@ -33,7 +33,7 @@ using namespace std; template inline bool contains(const vector& v, const T& target){ - for (int i = 0; i < v.size(); ++i)if (v[i] == target) return true; + for (std::size_t i = 0; i < v.size(); ++i)if (v[i] == target) return true; return false; } @@ -43,7 +43,7 @@ inline void my_sort(vector& a, vector& b){ if (a.size() != b.size()) return; vector> to_sort; - for (int i = 0; i < a.size(); ++i){to_sort.push_back(make_pair(a[i], b[i]));} + for (std::size_t i = 0; i < a.size(); ++i){to_sort.push_back(make_pair(a[i], b[i]));} sort(to_sort.begin(), to_sort.end(), comparitor); a.clear(); @@ -117,7 +117,7 @@ class Table{ // Get the title of the table inline string title() const { return tableName; } // Get the fields of the table - inline vector get_fields() { return selectedFields; } + inline vector get_fields() const { return selectedFields; } // Get the number of records in the table inline long record_count() const { return select_recnos().size(); } private: @@ -176,7 +176,7 @@ class Table{ vector permutated = sField; // search for invalids vector invalid_indices; - for (int i = 0; i < permutated.size(); ++i){ + for (std::size_t i = 0; i < permutated.size(); ++i){ string field_name = permutated[i]; if (!fieldNameMap.contains(field_name)) invalid_indices.push_back(i); } @@ -200,4 +200,4 @@ class Table{ }; -#endif // TABLE_H \ No newline at end of file +#endif // TABLE_H diff --git a/includes/tokenizer/ftokenize.h b/includes/tokenizer/ftokenize.h index c3c647c..b9d01b4 100644 --- a/includes/tokenizer/ftokenize.h +++ b/includes/tokenizer/ftokenize.h @@ -11,7 +11,7 @@ class FTokenizer{ public: // Maximum number of characters in a block - const int MAX_BLOCK = MAX_BUFFER; + static constexpr std::size_t MAX_BLOCK = MAX_BUFFER; // Constructor FTokenizer(char* fname); // Extract one token (very similar to the way cin >> works) @@ -39,4 +39,4 @@ class FTokenizer{ }; -#endif // FTOKENIZE_H \ No newline at end of file +#endif // FTOKENIZE_H diff --git a/includes/tokenizer/state_machine_functions.cpp b/includes/tokenizer/state_machine_functions.cpp index e3f7986..f0c09f2 100644 --- a/includes/tokenizer/state_machine_functions.cpp +++ b/includes/tokenizer/state_machine_functions.cpp @@ -33,8 +33,9 @@ void mark_cells(int row, int _table[][MAX_COLUMNS], int from, int to, int state) //Mark columns represented by the string columns[] for this row void mark_cells(int row, int _table[][MAX_COLUMNS], const char columns[], int state){ - for(int i = 0; i < strlen(columns); i++){ - _table[row][columns[i]] = state; + for(std::size_t i = 0; i < strlen(columns); i++){ + const auto column = static_cast(columns[i]); + _table[row][column] = state; } } @@ -62,4 +63,4 @@ void show_string(char s[], int _pos){ std::cout << " "; } std::cout << "^" << std::endl; -} \ No newline at end of file +} diff --git a/includes/tokenizer/stokenize.cpp b/includes/tokenizer/stokenize.cpp index bfb81a9..eb4b0c6 100644 --- a/includes/tokenizer/stokenize.cpp +++ b/includes/tokenizer/stokenize.cpp @@ -3,12 +3,12 @@ STokenizer::STokenizer() { _buffer = ""; _pos = 0; - make_table(_table); + make_table(); } STokenizer::STokenizer(const char str[]) { set_string(str); - make_table(_table); + make_table(); } void STokenizer::set_string(const char str[]) { @@ -24,7 +24,7 @@ bool STokenizer::more() { return !done(); } -void STokenizer::make_table(int table[][MAX_COLUMNS]) { +void STokenizer::make_table() { init_table(_table); mark_fail(_table, 0); for (int i = 1; i <= 7; ++i) mark_success(_table, i); @@ -66,36 +66,29 @@ STRING_TOKEN_TYPES STokenizer::token_type(int state) const { bool STokenizer::get_token(int start_state, SToken& token) { if (done()) return false; - int start = _pos; - int hold = start; - char t = _buffer[_pos]; - - if (!(t <= 255 && t >= 0)) { - token = SToken("", TOKEN_UNKNOWN); - _pos++; - return true; - } + std::size_t start = _pos; + std::size_t hold = start; + unsigned char t = static_cast(_buffer[_pos]); int state = _table[start_state][t]; if (state >= 4 && state <= 7) { - token = SToken(string(1, t), token_type(state)); + token = SToken(string(1, static_cast(t)), token_type(state)); _pos++; return true; } if (state == -1) { - token = SToken(string(1, t), TOKEN_UNKNOWN); + token = SToken(string(1, static_cast(t)), TOKEN_UNKNOWN); _pos++; return true; } - int size = _buffer.length(); + std::size_t size = _buffer.length(); start++; while (start < size) { - t = _buffer[start]; - if (!(t <= 255 && t >= 0)) break; + t = static_cast(_buffer[start]); int next_state = _table[state][t]; if (state == 1) { @@ -110,12 +103,13 @@ bool STokenizer::get_token(int start_state, SToken& token) { continue; } if (t == '.' && start + 1 < size) { - char next_token = _buffer[start + 1]; + unsigned char next_token = + static_cast(_buffer[start + 1]); while (start < size) { int next_next_state = _table[state][next_token]; if (next_next_state != 2) break; start++; - next_token = _buffer[start]; + next_token = static_cast(_buffer[start]); } break; } @@ -143,7 +137,6 @@ bool STokenizer::get_token(int start_state, SToken& token) { } STokenizer &operator>>(STokenizer &s, SToken &t) { - int state = 0; s.get_token(0, t); return s; -} \ No newline at end of file +} diff --git a/includes/tokenizer/stokenize.h b/includes/tokenizer/stokenize.h index 4f2bbc9..5963fc8 100644 --- a/includes/tokenizer/stokenize.h +++ b/includes/tokenizer/stokenize.h @@ -12,10 +12,10 @@ class STokenizer { private: string _buffer; - int _pos{}; + std::size_t _pos{}; int _table[MAX_ROWS][MAX_COLUMNS]{}; - void make_table(int table[][MAX_COLUMNS]); + void make_table(); STRING_TOKEN_TYPES token_type(int state) const; bool get_token(int start_state, SToken& token); @@ -29,4 +29,4 @@ class STokenizer { friend STokenizer& operator>>(STokenizer& s, SToken& t); }; -#endif // STOKENIZE_H \ No newline at end of file +#endif // STOKENIZE_H diff --git a/tests/sql_tests.cpp b/tests/sql_tests.cpp new file mode 100644 index 0000000..be248d6 --- /dev/null +++ b/tests/sql_tests.cpp @@ -0,0 +1,136 @@ +#include "includes/sql/sql.h" + +#include +#include +#include +#include +#include + +namespace { + +void expect_equal(long actual, long expected, const std::string& message) { + if (actual != expected) { + throw std::runtime_error( + message + ": expected " + std::to_string(expected) + + ", received " + std::to_string(actual) + ); + } +} + +void expect_true(bool value, const std::string& message) { + if (!value) { + throw std::runtime_error(message); + } +} + +void expect_records( + const std::vector& actual, + const std::vector& expected, + const std::string& message +) { + if (actual != expected) { + throw std::runtime_error(message); + } +} + +void remove_table(const std::string& name) { + std::filesystem::remove(name + ".bin"); + std::filesystem::remove(name + "_fields.bin"); +} + +void test_batch_executes_repository_fixture() { + remove_table("employeeSample01"); + remove_table("studentSample01"); + + SQL sql; + const std::string fixture = + std::string(SQL_TEST_FIXTURE_DIR) + "/_!sample01.txt"; + sql.batch(fixture.c_str()); + + const Table employees = sql.command("select * from employeeSample01"); + expect_equal(employees.record_count(), 6, "sample batch employee count"); + + const Table students = sql.command( + "select * from studentSample01 where lname = \"Del Rio\"" + ); + expect_records(students.select_recnos(), {3}, "quoted batch selection"); + + remove_table("employeeSample01"); + remove_table("studentSample01"); +} + +void test_commands_persist_and_filter_records() { + remove_table("people_test"); + + SQL sql; + sql.command("create table people_test fields first, last, major, age"); + sql.command("insert into people_test values Ada, Lovelace, Math, 36"); + sql.command("insert into people_test values Grace, Hopper, CS, 30"); + sql.command("insert into people_test values Edsger, Dijkstra, CS, 40"); + sql.command("insert into people_test values Barbara, Liskov, CS, 24"); + + const Table all = sql.command("select * from people_test"); + expect_equal(all.record_count(), 4, "inserted record count"); + + const Table filtered = sql.command( + "select first, major from people_test where major = CS and age < 35" + ); + expect_records(filtered.select_recnos(), {1, 3}, "compound selection"); + expect_true( + filtered.get_fields() == std::vector({"first", "major"}), + "selected fields should be preserved" + ); + + SQL reopened; + const Table persisted = reopened.command( + "select * from people_test where age >= 36" + ); + expect_records(persisted.select_recnos(), {0, 2}, "persisted selection"); + + reopened.command("drop table people_test"); + expect_true(reopened.is_error(), "invalid commands should report an error"); + + remove_table("people_test"); +} + +void test_second_batch_fixture_and_missing_file_errors() { + remove_table("employeeSample02"); + remove_table("studentSelect02"); + + SQL sql; + const std::string fixture = + std::string(SQL_TEST_FIXTURE_DIR) + "/_!sample02.txt"; + sql.batch(fixture.c_str()); + + const Table employees = sql.command("select * from employeeSample02"); + expect_equal(employees.record_count(), 16, "second batch employee count"); + + const Table students = sql.command( + "select * from studentSelect02 where major = CS and age < 25" + ); + expect_records(students.select_recnos(), {0, 7}, "second batch filter"); + + SQL missing; + missing.batch("missing-fixture.sql"); + expect_true(missing.is_error(), "missing batch files should report an error"); + + remove_table("employeeSample02"); + remove_table("studentSelect02"); +} + +} // namespace + +int main() { + try { + test_commands_persist_and_filter_records(); + std::cout << "PASS: command persistence and filtering" << std::endl; + test_batch_executes_repository_fixture(); + std::cout << "PASS: repository batch fixture" << std::endl; + test_second_batch_fixture_and_missing_file_errors(); + std::cout << "PASS: second batch fixture and errors" << std::endl; + return 0; + } catch (const std::exception& error) { + std::cerr << "FAIL: " << error.what() << std::endl; + return 1; + } +}