Hotel System C++: Variable-Length File Organization with Multi-Index Retrieval
This project is a C++ hotel management system built as a file-organization engine rather than a DBMS-backed application. It persists Rooms, Guests, and Reservations using delimiter-based variable-length records, maintains in-memory primary/secondary indexes, and resolves records using physical file offsets.
The system intentionally exposes storage-engine internals that are typically hidden by database layers, making it an excellent educational reference for understanding how low-level data persistence works under the hood.
The system models core storage-engine concerns in a compact, production-quality educational implementation:
- Record serialization/deserialization with custom delimiter protocols
- Variable-length record storage avoiding fixed-width padding waste
- Logical deletion and free-space reuse via availability lists
- Primary and secondary indexing with binary search optimization
- Offset-based direct access for O(1) record retrieval after index lookup
Efficient retrieval and mutation of structured hotel data without SQL databases, ORM layers, or any external dependencies β pure C++ STL and file I/O only.
- β Persist all entities to disk in plain text files (human-readable format).
- β Support full CRUD operations: insert, search, update, and delete.
- β Keep lookups fast by maintaining in-memory indexes (primary + secondary).
- β Reuse deleted space efficiently using first-fit availability lists.
- β Keep index files and main data files synchronized across runtime and shutdown.
- β Demonstrate real storage-engine patterns used by actual database systems.
| Feature | Implementation Detail |
|---|---|
| π Variable-Length Records | | field delimiters + # record terminators; size computed at runtime |
| π Primary Indexing | id β offset mapping with binary search β O(log n) lookup |
| π·οΈ Secondary Indexing | Non-unique key support for room type, guest name, and nationality |
| ποΈ Logical Deletion | * marker preserves record boundaries; enables future space reuse |
| β»οΈ First-Fit Reuse | Avail-list tracks deleted slots for efficient reallocation |
| βοΈ In-Place Updates | Overwrite when new β€ old size; relocate (delete + rewrite) otherwise |
| πΎ Immediate Persistence | Indexes flushed after every operation β minimizes data-loss window |
| π° Reservation Pricing | Dynamic pricing based on room type and stay duration |
- Rooms:
room_type β room_id(filter by room class) - Guests:
full_name β personal_id(search by name, supports duplicates) - Guests:
nationality β personal_id(group/report by country)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β UI Layer (main.cpp) β
β Console menus & user interaction β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββ
β Data Collection Layer (DataCollector.h) β
β Input validation & workflow orchestration β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββΌβββββββββββββββββββ
β β β
βββββββββΌβββββββββββ βββββββΌβββββββββββ ββββββΌββββββββββββββββββ
β RAM/Index Layerβ β Storage Layer β β Free-Space Layer β
β (RAM_Manager.h) β β (File_Handler/)β β (Avail_list.h) β
β β β β β β
β β’ Sorted vectors β β β’ RoomHandler β β β’ Track deleted slot β
β β’ Binary search β β β’ GuestHandler β β offsets & sizes β
β β’ Index mutationsβ β β’ ReserveHndlr β β β’ First-fit alloc β
β β’Memoryβfile syncβ β β’ Serialize β β β’ Binary metadata β
β β β /Deserialize β β files β
ββββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββββββ
β β β
ββββββββββββββββββββΌβββββββββββββββββββ
β
ββββββββββΌβββββββββ
β Disk Files β
β (12 files) β
βββββββββββββββββββ
| Layer | Team Member | Responsibilities |
|---|---|---|
| UI Layer | Nora | Menu flow, user interaction, I/O presentation, navigation |
| Collection Layer | Mahmoud | Input validation, multi-entity workflows, business rule enforcement |
| RAM/Index Layer | Nada | In-memory sorted index vectors, binary search, memoryβfile synchronization |
| Storage Layer | Farouk | File I/O, record serialization/deserialization, offset management, logical deletion |
| Free-Space Layer | Farouk | Deleted-slot tracking, first-fit reuse algorithm, avail-list persistence |
| Configuration | Shared | Delimiters, file paths, constants (Constants.h) |
mermaid
flowchart TD
UI[main.cpp menus] --> COL[DataCollector operations]
COL --> RAM[RAM_Manager indexes]
COL --> RH[RoomHandler]
COL --> GH[GuestHandler]
COL --> RVH[ReserveHandler]
RH --> AV1[Avail_list room]
GH --> AV2[Avail_list guest]
RVH --> AV3[Avail_list reservation]
RH --> FILES[Main + Index Files]
GH --> FILES
RVH --> FILES
RAM --> IDX[Index Files on disk]
mermaid
sequenceDiagram
participant User
participant UI as main.cpp
participant Ops as DataCollector
participant RAM as RAM_Manager
participant H as Handlers
User->>UI: choose operation
UI->>Ops: invoke workflow
Ops->>RAM: search/validate via indexes
Ops->>H: read/write/delete by offset
H->>RAM: update indexes
H->>H: flush files
Console-based menu system that routes user selections into business operations. Handles all user-facing input/output presentation and navigation flow.
The orchestrator layer that:
- Validates all user inputs against business rules
- Ties together multi-entity workflows (e.g., reservation requires valid guest AND available room)
- Triggers the appropriate handlers after validation passes
- Manages cross-entity side effects (room status toggling on reservation create/cancel)
The memory subsystem responsible for:
- Maintaining sorted index vectors in RAM for all entity types
- Performing binary search on primary indexes (O(log n))
- Scanning secondary indexes for non-unique key lookups
- Mutating index entries on insert/update/delete
- Flushing index state to disk files and reloading on startup
The lowest-level persistence layer handling:
- Physical file read/write operations using
<fstream> - Record serialization (writing delimited fields) and deserialization (parsing)
- File offset calculation and seeking
- Logical deletion marking (
*prefix) - End-of-file positioning for append operations
The engine stores each entity in a set of coordinated files:
| File Type | Purpose | Count per Entity |
|---|---|---|
| Main Data File | Serialized variable-length records | 1 |
| Primary Index File | ID β offset mapping (sorted) |
1 |
| Secondary Index File(s) | key β ID mappings (non-unique) |
0β2 |
| Avail-List File | Free-space metadata (binary) | 1 |
Traditional approach: Scan entire file linearly β O(n)
Our approach: ID β [Binary Search Index] β Offset β [Direct Seek] β Record β O(log n) + O(1)
ID lookup goes through indexes to obtain the exact file byte position, then reads exactly one record from the main file β no scanning required.
Configured in Constants.h:
| File | Contents |
|---|---|
Room-main.txt |
Serialized room records |
Room-primary.txt |
Primary index: id|offset |
Room-secondary.txt |
Secondary index: room_type|room_id |
Avail-list-room.txt |
Binary free-space metadata |
| File | Contents |
|---|---|
Guest-main.txt |
Serialized guest records |
Guest-primary.txt |
Primary index: id|offset |
Guest-fullname.txt |
Secondary index: name|id |
Guest-nationality.txt |
Secondary index: nationality|id |
Avail-list-guest.txt |
Binary free-space metadata |
| File | Contents |
|---|---|
Reservation-main.txt |
Serialized reservation records |
Reservation-primary.txt |
Primary index: id|offset |
Avail-list-reserve.txt |
Binary free-space metadata |
Total: 12 files (9 core data/index files + 3 avail-list files for free-space management)
Records are written as concatenated text segments with delimiters. No padding, no fixed-width fields β length is derived dynamically from field content sizes plus separator overhead at runtime.
| Condition | Strategy | Description |
|---|---|---|
new_size β€ old_size |
In-place overwrite | Rewrite bytes at existing offset; no index offset change needed |
new_size > old_size |
Relocation | Mark old record as deleted (*), write new record at reused slot or EOF, update index with new offset |
This dual-strategy approach minimizes unnecessary file fragmentation while ensuring data integrity.
| Symbol | Meaning | Position |
|---|---|---|
| |
Field separator | Between fields within a record |
# |
Record terminator | End of each record |
* |
Deletion marker | First byte of a logically-deleted record |
// Writing a room record:
write(id); put('|');
write(room_type); put('|');
write(room_view); put('|');
write(room_status);put('#');
// Result: "42|double|garden-view|available#"The handlers manually serialize each field using fstream::write and put, and deserialize with getline(..., delimiter) β giving complete control over the binary/text format.
Delete does NOT physically remove bytes from the file (which would require shifting all subsequent data). Instead:
Step 1: Seek to record's byte offset in main file
Step 2: Overwrite first byte with '*' (deletion marker)
Step 3: Push (offset, size) tuple into entity's availability list
Step 4: Remove corresponding entries from ALL RAM index vectors (primary + secondary)
Step 5: Persist updated index files and avail-list metadata to disk
Benefits: O(1) deletion operation; deleted space becomes immediately reusable; no file compaction needed during runtime.
Offsets are generated by seeking to end-of-file:
file.seekp(0, ios::end);
offset = file.tellp(); // Current position = next write location- Primary index stores offsets as
short(16-bit, max ~32K unique positions) - Offsets represent byte positions within the main data file
User enters ID
β Binary search primary index vector in RAM [O(log n)]
β Retrieve stored offset value
β fseek() to that offset in main file [O(1)]
β Read exactly one record (until '#' found) [O(1)]
β Deserialize fields by splitting on '|'
β Return structured entity data
Deleted slots stored in avail-list can be reclaimed if a new/updated record fits:
New record needs N bytes
β Scan avail-list for first entry where size β₯ N [O(k), k = free slots]
β If found: reuse that offset (overwrite '*' with new data)
β If not found: append at EOF (new offset from tellp)
Format: id|offset
Example: 42|1056
Meaning: Entity with ID=42 is stored at byte offset 1056 in main file
- Implemented for all three entities (Room, Guest, Reservation)
- Stored in-memory as
vector<PrimaryIndex>β always kept sorted SearchPrimaryIndex()uses binary search β returns offset or-1- On every insert/update/delete: mutate RAM vector β flush to disk file
- Loaded from disk into RAM at program startup
Linear scan: O(n) comparisons β slow for large datasets
Binary search: O(log n) comparisons β e.g., 10,000 records β ~13 comparisons
Format: key|id
Example (Room): double|42
Example (Guest): Ahmed Mohamed Ali|10
Example (Guest): egypt|10
| Entity | Secondary Key(s) | Purpose | Uniqueness |
|---|---|---|---|
| Room | room_type |
Filter rooms by category (single/double/suit) | Non-unique (many rooms share a type) |
| Guest | full_name |
Search guests by name | Non-unique (duplicate names allowed) |
| Guest | nationality |
Group/report guests by country | Non-unique |
Input: secondary key value (e.g., "double")
β Linear scan of secondary index vector [O(n)]
β Collect ALL matching IDs (may be multiple) [m matches, m β€ n]
β For each matched ID:
β Binary search primary index [O(log n) each]
β Get offset β read record from main file [O(1) each]
Total: O(n + m Β· log n) β worst case O(n Β· log n) when m = n
Note: Future improvement would replace linear scan with binary/range search on sorted secondary keys.
βββββββββββββββ
β Room β
βββββββββββββββ
β room_id (PK)β
β room_type β
β room_view β
β room_status βββββββββββββββββββββββββ
ββββββββ¬βββββββ β
β β
β 1 β1
β β
ββββββββΌβββββββββββββββββββββββββββββββΌβββββββ
β Reservation β
ββββββββββββββββββββββββββββββββββββββββββββββ
β reservation_id (PK) β
β room_id (FK βββ Room) β
β guest_id (FK βββ Guest) β
β check_in β
β check_out β
β total_price β
ββββββββββββββββββββββββ¬ββββββββββββββββββββββ
β
β 1
β
ββββββββΌβββββββ
β Guest β
βββββββββββββββ
β personal_id ββββ(PK)
β full_name β
β nationality β
βββββββββββββββ
| Relationship | Cardinality | Constraint |
|---|---|---|
| Reservation β Room | Many-to-One | Room must exist; must be available at creation time |
| Reservation β Guest | Many-to-One | Guest must exist before reservation can be created |
| Room β Guest | Independent | No direct relationship; linked only through Reservation |
- Creating reservation: Sets
room.status = 'reserved' - Cancelling reservation: Logically deletes reservation record + sets
room.status = 'available'
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STARTUP β
β ββββββββ β
β 1. Program launches β
β 2. LoadAllIndexesFromDisk() called β
β 3. Each handler opens/creates its data + index files β
β 4. Avail-list files loaded into RAM vectors β
β 5. System ready for user input β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β RUNTIME OPERATIONS β
β βββββββββββββββββ β
β 6. User triggers CRUD operation via menu β
β 7. DataCollector validates input β
β 8. Handler mutates main file + RAM indexes β
β 9. Changes flushed IMMEDIATELY to disk (per-operation persist) β
β 10. Steps 6-9 repeat until user exits β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β SHUTDOWN β
β ββββββββ β
β 11. Destructors invoked for all handlers β
β 12. Final flush of all index vectors to disk files β
β 13. Final flush of all avail-lists to disk files β
β 14. All file streams closed cleanly β
β 15. Program terminates β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Aspect | RAM Layer (RAM_Manager) |
Storage Layer (Handlers) |
|---|---|---|
| Data Structure | Sorted vector<PrimaryIndex>, vector<SecondaryIndex> |
Raw file streams (fstream) |
| Operations | Binary search, vector insertion/deletion | seekg, seekp, read, write, getline |
| Search Method | Binary search on sorted vectors | Direct offset access (no searching) |
| Mutation | Insert/erase elements in vectors | Overwrite bytes at specific offsets |
| Persistence Trigger | Handler calls flush after mutation | Immediate write to file |
| Lifetime | Exists only during program execution | Persists across program runs |
User Request
β
DataCollector
β (uses RAM layer to validate/find)
RAM_Manager.search(index, key) β returns offset or -1
β (passes offset to storage layer)
Handler.readByOffset(offset) β returns parsed record
β
Result returned to user
Each entity follows the same architectural pattern: ID-based primary indexing + offset-based record access, with optional secondary indexes for non-ID query paths.
Primary Key: room_id (unique identity; auto-generated 1β100 on first run)
Secondary Key: room_type (supports filtering by class of room: single/double/suit)
| File | Format | Example |
|---|---|---|
| Main | id|room_type|room_view|room_status# |
42|double|garden-view|available# |
| Primary Index | id|offset |
42|1056 |
| Secondary Index | room_type|room_id |
double|42 |
When the system detects no existing room data, it automatically generates 100 rooms:
| ID Range | Room Type | Count |
|---|---|---|
| 1 β 40 | single |
40 rooms |
| 41 β 80 | double |
40 rooms |
| 81 β 100 | suit |
20 rooms |
View distribution is determined by ID ranges (garden-view vs sea-view).
Initial status for all auto-generated rooms: available.
Reservation Created Reservation Cancelled
βββββββββββββββββββββ ββββββββββββββββββββ
β β β β
βΌ β βΌ β
βββββββββββ set β βββββββββ set β
βavailableβββββββββββββββΌββββββΆβreservedβββββββββββββββΌβββΆ
βββββββββββ β βββββββββ β
β β
ββββββββββββββββββββββββββββββββ
| Query Type | Path | Complexity |
|---|---|---|
| By Room ID | Primary index binary search β offset β direct read | O(log n) |
| By Room Type | Secondary index scan β matching IDs β primary index lookups β reads | O(n + mΒ·log n) |
Primary Key: personal_id (user-provided, must be unique)
Secondary Keys: full_name, nationality (both non-unique)
| File | Format | Example |
|---|---|---|
| Main | id|full_name|nationality# |
10|Ahmed Mohamed Ali|egypt# |
| Primary Index | id|offset |
10|2080 |
| Name Secondary | name|id |
Ahmed Mohamed Ali|10 |
| Nationality Secondary | nationality|id |
egypt|10 |
| Index | Use Case | Multi-value Support |
|---|---|---|
| Name Index | Find guest(s) by name | β Yes β multiple guests can share a name (one-to-many entries) |
| Nationality Index | Report/group guests by country | β Yes β many guests per nationality |
| Rule | Enforcement |
|---|---|
| Duplicate personal ID | β Blocked β checked via primary index before insert |
| Duplicate full name | β Allowed β handled via one-to-many secondary index entries |
| Nationality = "Israel" | β Rejected β case-insensitive check blocks all variants ("israel", "ISRAEL", "IsRaEl", etc.) |
Primary Key: reservation_id (user-provided, must be unique)
No secondary indexes β reservations are always looked up by ID.
| File | Format | Example |
|---|---|---|
| Main | reservation_id|room_id|guest_id|check_in|check_out|total_price# |
1|42|10|2026-05-01|2026-05-04|2400.000000# |
| Primary Index | id|offset |
1|3200 |
Step 1: Validate reservation_id is unique (via primary index)
β
βΌ
Step 2: Validate guest_id exists (via guest primary index)
β
βΌ
Step 3: Validate room_id exists AND room_status == "available"
β
βΌ
Step 4: Validate date format (YYYY-MM-DD) and check_out > check_in
β
βΌ
Step 5: Compute total_price = days Γ rate(room_type)
β
βΌ
Step 6: PERSIST reservation record β Set room.status = "reserved"
β
βΌ
Step 7: On CANCEL: logically delete reservation β Set room.status = "available"
# βββ Active Records ββββββββββββββββββββββββββββββββββββββββββββββ
Room: 42|double|garden-view|available#
Guest: 10|Ahmed Mohamed Ali|egypt#
Reservation: 1|42|10|2026-05-01|2026-05-04|2400.000000#
# βββ Logically Deleted Record (notice leading *) βββββββββββββββββ
Deleted: *2|double|sea-view|reserved#
β
Deletion marker overwrites first byte 42 | double | garden-view | available #
βββ ββββββββββββββββββββββββββββββββββββββββ
id sep room_type sep room_view sep status terminator
| Entity | Field Count | Structure |
|---|---|---|
| Room-main | 4 fields | id | room_type | room_view | room_status # |
| Guest-main | 3 fields | id | full_name | nationality # |
| Reservation-main | 6 fields | reservation_id | room_id | guest_id | check_in | check_out | total_price # |
| Index Type | Structure | Stored As | Search Algorithm |
|---|---|---|---|
| Primary (all entities) | id | offset |
Sorted vector | Binary search β O(log n) |
| Room Secondary | room_type | room_id |
Sorted vector | Linear scan β O(n) |
| Guest Name Secondary | name | guest_id |
Sorted vector | Linear scan β O(n) |
| Guest Nationality Secondary | nationality | guest_id |
Sorted vector | Linear scan β O(n) |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. COLLECT INPUT β
β Gather all field values from user via console prompts β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 2. VALIDATE β
β β’ Check uniqueness (primary index) β
β β’ Check foreign keys exist (for reservations) β
β β’ Apply business rules (e.g., nationality block) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 3. COMPUTE SIZE β
β Calculate serialized byte length of the new record β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 4. ALLOCATE SPACE β
β β’ Scan avail-list for first-fit slot (size β₯ needed) β
β β’ OR append at EOF if no suitable slot exists β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 5. WRITE RECORD β
β Serialize and write to main file at allocated offset β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 6. UPDATE INDEXES β
β β’ Insert into primary index vector (sorted position) β
β β’ Insert into secondary index vector(s) if applicable β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 7. PERSIST β
β Flush updated indexes + avail-list (if slot was reused) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Input: entity ID
β
βΌ
Binary search RAM primary index vector
β
βββ Found β return offset
β
βββ Not found β return -1 (entity doesn't exist)
β
βΌ (if found)
fseek(main_file, offset)
β
βΌ
Read record until '#' encountered
β
βΌ
Split fields by '|' β construct entity object β return to user
Complexity: O(log n) + O(1) = O(log n)
Input: secondary key value (e.g., room_type="double")
β
βΌ
Linear scan secondary index vector
β
βΌ
Collect ALL matching IDs β [idβ, idβ, ..., idβ]
β
βΌ (for each matched ID)
Binary search primary index β get offset β read record
β
βΌ
Return collection of matching entity objects
Complexity: O(n) scan + m Γ O(log n) lookup = O(n + mΒ·log n)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. LOCATE OLD RECORD β
β Find current offset via primary index binary search β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 2. READ OLD DATA β
β Seek to offset β read current record β deserialize β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 3. SERIALIZE NEW DATA β
β Build new record string from updated field values β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 4. SIZE COMPARISON β
β β
β ββββββββββββββββββββββββββββββββββββββββββββ β
β β new_size <= old_size ? β β
β ββββββββββββββββββββββββββββββββββββββββββββ€ β
β β YES β IN-PLACE OVERWRITE β β
β β β’ Seek to existing offset β β
β β β’ Write new record bytes β β
β β β’ Patch secondary indexes if key changedβ β
β β β’ Offset stays the SAME β β
β ββββββββββββββββββββββββββββββββββββββββββββ€ β
β β NO β RELOCATION β β
β β β’ Mark old record with '*' (delete) β β
β β β’ Push old (offset,size) to avail-list β β
β β β’ Allocate new slot (reuse or append) β β
β β β’ Write new record at new offset β β
β β β’ Update primary index with NEW offset β β
β β β’ Rebuild secondary index entries β β
β ββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 5. PERSIST ALL CHANGES β
β Flush mutated indexes + avail-list to disk β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. RESOLVE OFFSET β
β Binary search primary index β get byte offset β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 2. MARK DELETED β
β Seek to offset β write '*' as first byte β
β Record still physically present but logically removed β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 3. REGISTER FREE SPACE β
β Push (offset, record_size) tuple into entity avail-list β
β This slot is now available for future first-fit reuse β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 4. CLEANUP PRIMARY INDEX β
β Remove entry from primary index vector β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 5. CLEANUP SECONDARY INDEXES β
β Remove ALL secondary index entries referencing this ID β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 6. PERSIST β
β Flush updated primary index file β
β Flush updated secondary index file(s) β
β Flush updated avail-list file β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Rule | Entity | Check | Failure Action |
|---|---|---|---|
| Unique ID | All | Primary index lookup | Block insert with error message |
| Guest exists | Reservation | Guest primary index lookup | Block β "Guest not found" |
| Room exists | Reservation | Room primary index lookup | Block β "Room not found" |
| Room available | Reservation | Check room_status == "available" |
Block β "Room already reserved" |
| Date format | Reservation | Regex/pattern match YYYY-MM-DD |
Block β "Invalid date format" |
| Date order | Reservation | check_out > check_in |
Block β "Check-out must be after check-in" |
| Nationality block | Guest | Case-insensitive compare to "Israel" |
Block β "Nationality not allowed" |
| Room Type | Daily Rate (EGP/Night) |
|---|---|
single |
500 |
double |
800 |
suit |
1,500 |
days = (check_out_date - check_in_date) converted to whole days
daily_rate = ROOM_PRICES[room_type]
total_price = days Γ daily_rate
| Scenario | Room Type | Check-In | Check-Out | Nights | Total Price |
|---|---|---|---|---|---|
| Weekend getaway | double | 2026-05-01 | 2026-05-04 | 3 | 3 Γ 800 = 2,400 |
| Business trip | single | 2026-06-10 | 2026-06-12 | 2 | 2 Γ 500 = 1,000 |
| Luxury stay | suit | 2026-07-01 | 2026-07-07 | 6 | 6 Γ 1,500 = 9,000 |
sequenceDiagram
participant User
participant UI as main.cpp
participant Ops as DataCollector
participant RAM as RAM_Manager
participant H as Handlers
User->>UI: choose operation
UI->>Ops: invoke workflow
Ops->>RAM: search/validate via indexes
Ops->>H: read/write/delete by offset
H->>RAM: update indexes
H->>H: flush files
| Decision | Rationale | Impact |
|---|---|---|
| Primary index as offset map | Eliminates need for full-file scans on lookup | Fast O(log n) retrieval; requires index maintenance overhead |
| Variable-length records | Avoids wasted space from fixed-width padding | More compact storage; more complex update logic (size comparison needed) |
| Logical deletion | Keeps writes simple; preserves record boundaries; enables space reuse | No O(n) byte-shifting on delete; eventual fragmentation without compaction |
| Separate secondary index files | Supports flexible non-ID queries without changing main-file format | Extra storage + sync complexity; powerful multi-path search capability |
| Immediate persistence after operations | Minimizes data-loss window between mutation and disk write | Slightly lower throughput; maximum durability guarantee |
| In-memory sorted vectors for indexes | Enables binary search; simple implementation | RAM usage scales linearly with dataset size |
| Operation | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Primary key lookup | O(log n) | O(1) extra | Binary search in RAM + single file seek |
| Secondary key lookup | O(n + mΒ·log n) | O(m) results | Linear scan + per-match primary lookup |
| Record fetch (by known offset) | O(1) | O(1) | Single fseek + read until # |
| Insert (append path) | O(1) amortized | O(1) | EOF write + index insertion |
| Insert (reuse path) | O(k) | O(1) | k = free slots scanned in avail-list |
| Update (in-place) | O(log n) | O(1) | Index lookup + overwrite |
| Update (relocation) | O(log n + k) | O(1) | Delete + insert combined |
| Delete | O(log n) | O(1) | Index lookup + mark + cleanup |
Legend: n = total records, m = number of secondary matches (m β€ n), k = free slots in avail-list
β Well-suited for: Small to medium datasets (up to ~10,000 records per entity), educational purposes, embedded systems, single-user desktop applications
| Limitation | Root Cause | Impact | Potential Fix |
|---|---|---|---|
| ID/Offset ceiling | short type (16-bit signed) |
Max ~32K unique records | Migrate to int or size_t |
| Secondary lookup bottleneck | Linear scan of secondary index | Degraded performance with large datasets | Implement binary/range search on sorted secondary keys |
| Full index rewrites | Entire index vector rewritten on every mutation | Unnecessary I/O for large indexes | Differential/delta updates |
| File fragmentation | Relocation leaves "holes" (deleted records) | Larger file sizes than data warrants | Periodic compaction utility |
| No concurrency support | Single-threaded design | Cannot handle simultaneous users | Add file locking or move to client-server architecture |
| Benefit | Explanation |
|---|---|
| Transparent file format | All data human-readable; debuggable with any text editor |
| Low conceptual overhead | No ORM magic, no query parser β everything explicit |
| Educational value | Exposes real database internals usually hidden by abstraction layers |
| Zero external dependencies | Pure C++17 standard library only β runs anywhere with a compiler |
| Predictable performance | No query optimizer vagaries β complexity is deterministic |
| Tradeoff | Mitigation |
|---|---|
| Manual consistency management | Careful ordering of operations in handler code |
| Duplicate logic across handlers | Shared utilities in common headers |
| Eventual fragmentation | Avail-list reuse mitigates; compaction planned for future |
| Platform-specific directory creation | Documented issue; portable replacement planned |
Some room update/delete code paths do not fully clean up stale room_type secondary index entries when a room's type changes. This can leave orphaned secondary index mappings pointing to non-existent or incorrect room types. Not critical for correctness (primary index remains authoritative), but could cause ghost entries in secondary searches.
This implementation intentionally avoids database abstractions to demonstrate foundational concepts:
| Database Concept | Our Analog | Learning Value |
|---|---|---|
| B-Tree index | Sorted vector + binary search | Understand why indexes are sorted |
| Row ID / Tuple identifier | File byte offset | Learn physical addressing |
| MVCC / soft delete | Logical deletion marker (*) |
See how deletes work internally |
| Free space map / FSM | Availability list | Understand space reuse mechanisms |
| WAL / write-ahead log | Immediate flush policy | Appreciate durability guarantees |
| Query planner execution | Manual index selection path | See how query optimization works |
These are the exact mechanisms that power PostgreSQL, MySQL, SQLite, and other production databases β now visible and understandable.
| Challenge | Solution Approach |
|---|---|
| Index/file consistency across CRUD | Every mutation updates both main file AND all affected indexes atomically (within single operation scope) |
| Safe variable-length updates | Size comparison before write; relocation fallback prevents data corruption |
| Cross-entity side effects | Reservation handler explicitly toggles room status; DataCollector coordinates the multi-step transaction |
| Deleted-space reuse with boundary safety | Avail-list stores (offset, size) tuples; first-fit ensures new record fits entirely within reclaimed slot |
| Date parsing and arithmetic | Custom date validation and day-difference calculation in workflow layer |
| Pricing integration | Room-type price lookup embedded in reservation creation flow |
| Nationality filtering | Explicit case-insensitive block list checked at insertion time |
- Widen numeric types: Replace
shortwithint/size_tfor IDs and offsets (remove 32K ceiling) - Optimize secondary indexes: Implement binary or range search instead of linear scan β reduces secondary lookup from O(n) to O(log n)
- Referential integrity guards: Prevent deleting a guest or room that has active reservations (currently allowed β could cause orphaned foreign keys)
- Compaction/defragmentation utility: Rebuild main files by copying only live records, resetting all offsets
- Vocabulary normalization: Standardize status terms (
reservedvsbookedvsoccupied) consistently across all modules - Portable filesystem API: Replace
system("if not exist ...")with C++17<filesystem>for cross-platform directory creation - Fix include paths: Convert absolute Windows paths in
main.cppto relative includes for out-of-the-box builds on all platforms
- CMake build configuration: Replace manual g++ commands with a proper build system
- Automated unit test suite: Test CRUD operations, edge cases, and consistency invariants
- Export/import functionality: JSON or CSV export for data portability
- Search history/recent queries: Cache frequently searched values
- Statistics dashboard: Show occupancy rates, revenue totals, guest nationality breakdowns
- File handling architecture and design patterns
- Persistent storage layer implementation
- Hard disk interaction and stream management
- Record writing/reading (serialization/deserialization)
- Offset management and addressing scheme
- File organization architecture decisions
- Low-level storage operations and bit-level concerns
- Availability list design and implementation
- RAM layer design and data structure selection
- Runtime memory handling strategies
- In-memory index/data structure implementation
- Sorted vector maintenance algorithms
- Data movement between memory and files
- Binary search implementation and optimization
- Index synchronization logic
- Data collection layer architecture
- Input acquisition and parsing workflows
- Data preparation before persistence
- Collector workflow logic and orchestration
- Business rule validation and enforcement
- Cross-entity coordination (reservation β room/guest)
- Error messaging and user guidance
- User interface design and layout
- User interaction flow and navigation
- Input/output presentation formatting
- Usability and interaction handling
- Console menu hierarchy and routing
- Display formatting and readability
- User experience polish
This project represents a practical, from-scratch storage engine implemented as a hotel management system. It demonstrates that the fundamental mechanisms underlying modern databases β persistence, indexing, record management, and lifecycle workflows β can be built directly on files with careful engineering.
| Aspect | What You Learn |
|---|---|
| Transparency | Every byte on disk is inspectable; no black boxes |
| Performance reasoning | Understand WHY operations have their complexity characteristics |
| Tradeoff awareness | See real engineering choices and their consequences |
| Systems thinking | Connect high-level operations to low-level file mechanics |
| Debugging skills | Diagnose issues at the file/hex level, not just application level |
Whether you're studying database internals, preparing for systems programming interviews, or building your own embedded data solution, this project provides a concrete reference implementation of storage-engine fundamentals.
| Operation | Primary Index Path | Secondary Index Path | Notes |
|---|---|---|---|
| Lookup by ID | O(log n) | β | Binary search in RAM primary index, then direct file read |
| Lookup by secondary key | β | O(n + mΒ·log n) | O(n) linear scan + O(log n) primary lookup per match; worst-case O(nΒ·log n) when m = n |
| Record fetch (after offset resolved) | O(1) | O(1) | Single fseek + sequential read until # terminator |
| Insert (append path) | O(log n) | O(log n) | Index insertion cost dominates; file write is O(1) |
| Insert (reuse path) | O(log n + k) | O(log n + k) | Plus O(k) avail-list scan for first-fit slot |
| Update (in-place) | O(log n) | β | Index lookup + overwrite at known offset |
| Update (relocation) | O(log n + k) | β | Delete (log n) + Insert (log n + k) combined |
| Delete | O(log n) | O(log n) | Index lookup + mark + index removal |
| Space reuse (first-fit) | O(k) | O(k) | k = number of free slots in avail-list vector |
* Index insertion into sorted vector is O(n) due to element shifting, but often cited as O(log n) for the search component alone.
Legend: n = total index entries, m = number of secondary matches (m β€ n), k = free slots in avail-list
Hotel-System-CPP/
β
βββ Final_Project/ # βββ Source Code βββ
β β
β βββ main.cpp # Entry point, UI menus, main() function
β βββ DataCollector.h # Input validation, workflow orchestration
β βββ RAM_Manager.h # In-memory index structures, search algorithms
β βββ Avail_list.h # Free-space tracking, first-fit allocation
β βββ Constants.h # Delimiters, filenames, paths, configuration
β β
β βββ File_Handler/ # βββ Entity-Specific Handlers βββ
β β βββ RoomHandler.h # Room CRUD, serialization, file I/O
β β βββ GuestHandler.h # Guest CRUD, serialization, file I/O
β β βββ ReserveHandler.h # Reservation CRUD, serialization, file I/O
β β
β βββ HotelSYS/Data/ # βββ Runtime Data Files (auto-generated) βββ
β β
β βββ Room/
β β βββ Room-main.txt # Room records (variable-length)
β β βββ Room-primary.txt # Primary index: idβoffset
β β βββ Room-secondary.txt # Secondary index: room_typeβid
β β βββ Avail-list-room.txt # Binary free-space metadata
β β
β βββ Guest/
β β βββ Guest-main.txt # Guest records
β β βββ Guest-primary.txt # Primary index: idβoffset
β β βββ Guest-fullname.txt # Secondary index: nameβid
β β βββ Guest-nationality.txt # Secondary index: nationalityβid
β β βββ Avail-list-guest.txt # Binary free-space metadata
β β
β βββ Reservation/
β βββ Reservation-main.txt # Reservation records
β βββ Reservation-primary.txt # Primary index: idβoffset
β βββ Avail-list-reserve.txt # Binary free-space metadata
β
βββ structure/ # βββ Documentation βββ
β βββ structure.txt # Additional structural notes
|
βββ .gitignore
βββ LICENSE
βββ README.md # This file
Total: 7 source files + 12 data files = 19 files
- β C++17 compatible compiler (GCC 7+, Clang 5+, MSVC 2017+)
- β Git (for cloning the repository)
- β Terminal/command prompt access
git clone https://github.com/Farouk0Elsayed/Hotel-System-CPP.git
cd Hotel-System-CPPKnown Issue (documented, fix planned β see Section 40):
Final_Project/main.cppcurrently contains absolute Windows paths in its#includedirectives. These will fail on any machine except the original developer's.
Solution β replace absolute paths with relative includes:
// β BEFORE (absolute Windows path β won't work on your machine):
#include "C:\\Users\\Farouk\\Documents\\Hotel-System-CPP\\Final_Project\\DataCollector.h"
// β
AFTER (relative path β works everywhere):
#include "DataCollector.h"Apply this fix to all #include directives in main.cpp. Alternatively, configure your IDE/build system's include search paths to contain ./Final_Project and ./Final_Project/File_Handler.
g++ -std=c++17 \
-I./Final_Project \
-I./Final_Project/File_Handler \
-o hotel ./Final_Project/main.cppNote: This assumes Step 2 is completed. This project uses a header-only implementation pattern where
main.cppis the sole translation unit β compiling it generates the complete executable.
./hotel # Linux/macOS
hotel.exe # WindowsYou'll be presented with a console menu system for managing rooms, guests, and reservations.
| Technology | Usage |
|---|---|
| C++17 | Core language standard |
| STL Containers | vector<T> for index storage, string for text handling |
| STL Algorithms | std::sort, std::lower_bound, std::find |
<fstream> |
ifstream, ofstream for all file I/O operations |
<iostream> |
Console input/output for UI layer |
| Concept | Where Applied |
|---|---|
| Variable-length records | All main data files |
| Delimiter-based serialization | | field separators, # record terminators |
| Logical deletion (soft delete) | * marker in first byte |
| Primary indexing | id β offset for all entities |
| Secondary indexing | Non-unique key lookups (room type, name, nationality) |
| Offset-based direct access | fseek + read by byte position |
| Availability lists (free-space management) | First-fit slot reuse |
| In-place update optimization | Size comparison before write decision |
| Concept | Implementation |
|---|---|
| Reservation lifecycle | Create β active β cancel β deleted |
| Room availability state machine | available β reserved transitions |
| Dynamic pricing | Rate lookup by room type Γ duration |
| Referential integrity (partial) | Foreign key validation on reservation creation |
| Business rule enforcement | Nationality blocking, uniqueness constraints |
| GitHub | Role |
|---|---|
| https://github.com/Farouk0Elsayed | Storage Systems Engineer |
| https://github.com/nada1102006 | Memory Systems Engineer |
| https://github.com/7okaa1 | Integration Engineer |
| https://github.com/NoraAlaa97 | UX/UI Engineer |
Built with dedication and precision by
π¨ Hotel System C++ β A Storage Engine, Not Just a Homework Assignment