When building a custom file-based database from scratch in raw C, you quickly hit a low-level hardware constraint: operating systems and storage controller drivers cannot natively delete a single line of text from the middle of a physical file on a disk drive.
To create a complete CRUD (Create, Read, Update, Delete) system for my contact book application, I had to implement a classic systems programming technique: the Temporary File Architectural Pattern.
This guide explains how database engines manipulate raw disk files to perform safe, non-destructive row deletions without corrupting adjacent data blocks.
The Architectural Limitation of Disk Storage
Unlike volatile RAM, where bytes can be modified dynamically at any specific memory address offset, data written to disk sectors is contiguous. If you open a text file stream in standard modify mode, you can overwrite existing bytes, but you cannot "squeeze" a line out and expect the remaining data to automatically shift upward to close the gap.
To execute a deletion, we have to fake it using a dual-stream filtering pipeline.
The Strategy: Read, Filter, and Rebuild
Instead of attempting to alter the original database file (contacts.txt) directly, the architecture relies on creating an isolated clone (temp.txt) that only inherits the data we want to keep.
The execution steps are straightforward:
- Open the primary database file in Read (
"r") mode. - Open a temporary storage file in Write (
"w") mode. - Stream the primary file line-by-line into a stack-allocated character buffer.
- Use a pattern matcher to inspect the string. If it matches the deletion query, the CPU drops the buffer. If it doesn't match, the stream writes the buffer directly into the temporary file.
- Close both stream pipelines, wipe out the old file, and rename the temporary clone to become the new primary database.
Technical Implementation
Here is the robust abstraction layer handling the file stream operations safely:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int deleteContactRecord(const char *target_name) {
// Open dual pipelines to the storage controller
FILE *source_db = fopen("contacts.txt", "r");
FILE *temp_db = fopen("temp.txt", "w");
if (source_db == NULL || temp_db == NULL) {
printf("System Error: Unable to initialize file database streams.\n");
return 1;
}
char line_buffer[200];
int deletion_flag = 0;
// Stream lines into RAM row-by-row
while (fgets(line_buffer, sizeof(line_buffer), source_db) != NULL) {
// If the deletion query string is NOT found in this row, preserve it
if (strstr(line_buffer, target_name) == NULL) {
fprintf(temp_db, "%s", line_buffer);
} else {
deletion_flag = 1; // Match found; skip writing to filter it out
}
}
// Force system flush and close connections
fclose(source_db);
fclose(temp_db);
// Commit file table updates permanently
if (deletion_flag == 1) {
remove("contacts.txt"); // Erase the old database state
rename("temp.txt", "contacts.txt"); // Promote temp file to primary
printf("Record mapping to '%s' successfully purged.\n", target_name);
} else {
remove("temp.txt"); // Dissolve the empty utility file
printf("Record not found. State preserved.\n");
}
return 0;
}
Why This Matters for Systems Engineering
This exact "read-filter-write" transaction pattern is the basics of database persistence.
With the CRUD engine of this app complete, the next architectural milestone is shifting from sequential file parsing to O(1) memory lookups by designing a custom Hash Table data structure from scratch.
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.