A full-stack SQL query interface built on top of NITCBase — a custom relational database engine written in C++. MiniSQL extends the original engine with new SQL capabilities and wraps it with a modern web interface, allowing users to run SQL queries through a browser instead of a command line.
NITCBase is a teaching RDBMS built from scratch in C++. It simulates a disk-based relational database with its own buffer manager, cache layer, B+ tree indexing, and a SQL-like command interface. MiniSQL extends this engine and builds a complete web interface on top of it.
The project has three layers:
- C++ Engine — the core database engine (extended NITCBase)
- Node.js Bridge — connects the C++ engine to the web
- React Frontend — the browser-based SQL interface
React Frontend (Vite + Tailwind)
↕ HTTP (axios)
Node.js Bridge (Express)
↕ stdin / stdout
C++ Engine (NITCBase/MiniSQL)
↕ read / write
Binary Disk File (Disk/disk)
Disk_Class — raw disk read/write
Buffer — StaticBuffer + BlockBuffer (in-memory block pool)
Cache — OpenRelTable, RelCacheTable, AttrCacheTable
BlockAccess — linear search, insert record, project
BPlusTree — index structure for fast attribute lookups
Schema — DDL: create/delete/rename relations and attributes
Algebra — DML: insert, select, project, join, print
Frontend — thin dispatcher to Algebra and Schema
FrontendInterface + RegexHandler — CLI layer, regex-based SQL parser
The original NITCBase had 4 SELECT variants — all wrote results INTO a new relation on disk. We added:
SELECT * FROM relname;
SELECT col1, col2 FROM relname;
SELECT * FROM relname WHERE attr op value;
SELECT col1, col2 FROM relname WHERE attr op value;Output is pipe-delimited with a header row and row count — parsed by the Node bridge into JSON.
PRINT BPLUSTREE relname.attr;Prints the B+ tree level by level using BFS traversal, showing internal and leaf nodes at each level.
Algebra/Algebra.h/Algebra.cpp—print()overloads +printBPlusTree()Frontend/Frontend.h/Frontend.cpp— thin dispatchersFrontendInterface/RegexHandler.h— new regex patternsFrontendInterface/FrontendInterface.cpp— handler implementations
| Layer | Technology |
|---|---|
| Database Engine | C++ (NITCBase) |
| Bridge Server | Node.js + Express |
| Frontend | React + Vite + Tailwind CSS |
| HTTP Client | Axios |
| Icons | Lucide React |
- g++ (C++17 or later)
- Node.js v18 or later
- npm
- readline library (for the C++ engine CLI)
MINI_SQL/
├── backend/
│ ├── Codebase/
│ │ ├── Disk/ ← binary disk file
│ │ ├── Files/ ← input/output files
│ │ ├── mycodebase/ ← C++ engine source + binary
│ │ └── XFS_Interface/ ← disk formatting tool
│ ├── index.js ← Node.js bridge server
│ └── package.json
└── frontend/ ← Vite React app
└── ├── src/
│ ├── app/
│ │ ├── App.jsx
│ │ └── components/
│ │ └── BPlusVisualizer.jsx
│ └── styles/
│ └── main.jsx
├── package.json
└── vite.config.js
cd backend/Codebase/XFS_Interface
./xfs_interfaceInside XFS shell:
fdisk
exit
cd backend/Codebase/mycodebase
makeThis produces the codebase binary.
cd backend
node index.jsYou should see:
Server on port 3001
Spawning engine...
✅ Engine ready
cd frontend
npm install
npm run devOpen http://localhost:5173 in your browser.
Important: Always use
node index.js, notnodemon. Nodemon watches for file changes and will restart the server when the engine writes to the disk file, breaking active queries.
CREATE TABLE relname(attr1 STR, attr2 NUM);
DROP TABLE relname;- Attribute types:
STR(string, max 16 chars) orNUM(number) - Max 18 user relations on disk
- Max 10 relations open at once
SELECT * FROM relname;
SELECT col1, col2 FROM relname;
SELECT * FROM relname WHERE attr op value;
SELECT col1, col2 FROM relname WHERE attr op value;Operators: < <= > >= = !=
SELECT * FROM source INTO target;
SELECT col1, col2 FROM source INTO target;
SELECT * FROM source INTO target WHERE attr op value;
SELECT col1, col2 FROM source INTO target WHERE attr op value;Saves result into a new relation on disk. Use SELECT * FROM target; to view it.
INSERT INTO relname VALUES(val1, val2);Values must match the relation's attribute types and order. String values don't need quotes.
SELECT * FROM rel1 JOIN rel2 INTO target WHERE rel1.attr = rel2.attr;
SELECT col1, col2 FROM rel1 JOIN rel2 INTO target WHERE rel1.attr = rel2.attr;Equi-join only. Result saved to disk — use SELECT * FROM target; to view.
OPEN TABLE relname;
CLOSE TABLE relname;Relations are auto-opened before queries. Manual open/close is available but not usually needed.
CREATE INDEX ON relname.attr;
DROP INDEX ON relname.attr;
PRINT BPLUSTREE relname.attr;Index must exist before printing. Output shows the tree level by level.
ALTER TABLE RENAME relname TO newname;
ALTER TABLE RENAME COLUMN relname oldattr TO newattr;Renames a relation or attribute. Does not modify data.
Start with a clean database (run fdisk in XFS Interface if needed). Run each block and verify the expected output.
CREATE TABLE Students(name STR, cgpa NUM);Expected: ✅ Relation Students created successfully · status bar shows 1/18
CREATE TABLE Students(name STR, cgpa NUM);Expected: ❌ Relation already exists
INSERT INTO Students VALUES(Alice, 9);Expected: ✅ Inserted successfully
INSERT INTO Students VALUES(Bob, 8);INSERT INTO Students VALUES(Charlie, 7);INSERT INTO Students VALUES(Dave, 9);Expected: ✅ all three success
SELECT * FROM Students;Expected: table with 4 rows — Alice, Bob, Charlie, Dave
SELECT name FROM Students;Expected: table with only name column, 4 rows
SELECT * FROM Students WHERE cgpa = 9;Expected: Alice and Dave only
SELECT * FROM Students WHERE cgpa > 7;Expected: Alice, Bob, Dave
SELECT name FROM Students WHERE cgpa > 8;Expected: only name column — Alice and Dave
SELECT * FROM Students INTO TopStudents WHERE cgpa = 9;Expected: ✅ Selected successfully · status bar shows 2/18
SELECT * FROM TopStudents;Expected: Alice and Dave
CREATE INDEX ON Students.cgpa;Expected: ✅ success
PRINT BPLUSTREE Students.cgpa;Expected: B+ Tree Structure block showing LEVEL 0, LEVEL 1 etc.
DROP INDEX ON Students.cgpa;Expected: ✅ success
ALTER TABLE RENAME Students TO Pupils;Expected: ✅ success
SELECT * FROM Pupils;Expected: same 4 rows
ALTER TABLE RENAME COLUMN Pupils cgpa TO gpa;Expected: ✅ success
SELECT * FROM Pupils;Expected: columns now show name and gpa
CREATE TABLE Courses(name STR, grade NUM);INSERT INTO Courses VALUES(Alice, 10);INSERT INTO Courses VALUES(Bob, 9);SELECT * FROM Pupils JOIN Courses INTO JoinResult WHERE Pupils.name = Courses.name;SELECT * FROM JoinResult;Expected: table with Alice and Bob rows merged
Paste all at once in the editor:
CREATE TABLE T1(x NUM);
INSERT INTO T1 VALUES(1);
INSERT INTO T1 VALUES(2);
SELECT * FROM T1;Expected: 4 labeled results shown — create success, two inserts, then table with 2 rows
DROP TABLE TopStudents;
DROP TABLE JoinResult;
DROP TABLE Courses;
DROP TABLE T1;
DROP TABLE Pupils;Expected: ✅ all success · status bar back to 0/18
- Click History button — should show all queries run so far
- Hard refresh
Ctrl+Shift+R - Click History again — queries should still be there
The C++ engine spawns once when the Node server starts and stays alive. Commands are written to its stdin and responses read from stdout. This preserves state (open relations, buffer) between queries.
NITCBase only writes data to disk when the process exits (EXIT command). After every write operation (CREATE TABLE, INSERT, DROP TABLE, ALTER TABLE, CREATE/DROP INDEX), the bridge automatically sends EXIT, waits for the engine to restart, then continues. This is invisible to the user.
NITCBase requires OPEN TABLE before querying. The bridge parses every query, extracts relation names, and auto-opens them. When the 10-relation limit is reached, the least recently used relation is closed (LRU eviction).
All commands are serialized through a queue — only one command runs at a time, preventing concurrent stdin writes from corrupting responses.
Multiple semicolon-separated commands in the editor run sequentially, with flush/respawn handled between write operations automatically.
- Dark-themed UI — professional dark interface with syntax-aware monospace editor
- Live status bar — shows engine online/offline, open relation count, total relations on disk
- Query history — persists across page refresh using localStorage, capped at 20 entries
- Multi-query — paste multiple commands separated by semicolons and run all at once
- B+ Tree output —
PRINT BPLUSTREErenders in a styled monospace block - B+ Tree visualizer — interactive demo in the User Manual showing how B+ tree splits and grows
- User Manual — complete syntax reference built into the UI
- Save button — manually triggers disk flush (EXIT + respawn)
- Single-user only — all users share the same disk instance. Multi-user support would require per-session disk isolation, which is not supported by the current NITCBase architecture.
- No UPDATE or DELETE — NITCBase does not support single-record update or delete. Deletion works at the block level only.
- No ORDER BY, LIMIT, COUNT, GROUP BY — not implemented in the engine.
- No multi-table JOIN — only two-relation equi-joins supported.
- String values without quotes — the engine does not support quoted strings; write values directly.
- Disk resets on format — running
fdisk + formatin XFS Interface wipes all data.
NITCBase simulates a disk as a single binary file (Disk/disk). The disk is divided into fixed-size blocks (2048 bytes each). A buffer pool keeps recently accessed blocks in memory. Relations and attributes are stored in two system catalogs — RELATIONCAT and ATTRIBUTECAT — which are always open.
When a relation is opened, its metadata is loaded into the relation cache and attribute cache. Queries use BlockAccess to scan records linearly or via B+ tree index. All changes stay in the buffer until EXIT is called, which triggers the StaticBuffer destructor to flush dirty blocks to disk.
Built as a personal project to gain hands-on experience with systems programming, database internals, and full-stack development.