diff --git a/.DS_Store b/.DS_Store
deleted file mode 100644
index 155e3a8..0000000
Binary files a/.DS_Store and /dev/null differ
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..3f8982b
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,32 @@
+name: CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+
+permissions:
+ contents: read
+
+jobs:
+ build-and-test:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest]
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+
+ - name: Configure
+ run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON
+
+ - name: Build
+ run: cmake --build build --config Release --parallel
+
+ - name: Test
+ run: ctest --test-dir build -C Release --output-on-failure
diff --git a/.gitignore b/.gitignore
index 315768e..bffac08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,34 +1,35 @@
-# Prerequisites
-*.d
+# Build output
+/build/
+/cmake-build-*/
+CMakeCache.txt
+CMakeFiles/
+CTestTestfile.cmake
+cmake_install.cmake
+Makefile
+Testing/
-# Compiled Object files
-*.slo
+# Compiled artifacts
+*.a
+*.app
+*.d
+*.dll
+*.dylib
+*.exe
+*.gch
+*.lai
+*.lib
*.lo
*.o
*.obj
-
-# Precompiled Headers
-*.gch
+*.out
*.pch
-
-# Compiled Dynamic libraries
+*.slo
*.so
-*.dylib
-*.dll
-
-# Fortran module files
-*.mod
-*.smod
-# Compiled Static libraries
-*.lai
-*.la
-*.a
-*.lib
-
-# Executables
-*.exe
-*.out
-*.app
+# Database files created by the CLI and tests
+*.bin
-cmake-build-debug
+# Editors and operating systems
+.DS_Store
+.idea/
+.vscode/
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 13566b8..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index 0db59e0..0000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-project_name
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 79b3c94..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ 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
+
- 
-## 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 29acbac..0000000
Binary files a/includes/.DS_Store and /dev/null differ
diff --git a/includes/binary_files/file_record.cpp b/includes/binary_files/file_record.cpp
index 4741678..d2e4be0 100644
--- a/includes/binary_files/file_record.cpp
+++ b/includes/binary_files/file_record.cpp
@@ -1,7 +1,7 @@
#include "file_record.h"
//Constructors
-FileRecord::FileRecord(string s){}
+FileRecord::FileRecord(string){}
FileRecord::FileRecord(char *str){
int n = strlen(str);
this->_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;
+ }
+}