Zig Project: Database Engine
A database engine is a program that stores values under keys and finds them again later. This lesson builds a small one that keeps its records in a file.
Store values in memory
The store holds each value under its key. A lookup by key returns the stored value.
const std = @import("std");
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const alloc = arena.allocator();
var out = std.io.getStdOut().writer();
var store = std.StringHashMap([]const u8).init(alloc);
try store.put("name", "zig");
try store.put("level", "advanced");
try out.print("name={s}\n", .{store.get("name").?});
try out.print("level={s}\n", .{store.get("level").?});
try out.print("count={d}\n", .{store.count()});
}
Save records to a file
Saving writes one record per line in a fixed order. The file is created fresh on every save.
const std = @import("std");
pub fn main() !void {
var out = std.io.getStdOut().writer();
const Entry = struct { key: []const u8, value: []const u8 };
const entries = [_]Entry{ .{ .key = "name", .value = "zig" }, .{ .key = "level", .value = "advanced" } };
var file = try std.fs.cwd().createFile("db.txt", .{});
defer file.close();
var w = file.writer();
for (entries) |e| {
try w.print("{s}={s}\n", .{ e.key, e.value });
}
try out.print("saved {d} entries\n", .{entries.len});
}
Load records from a file
Loading reads the file, splits each line at the equals sign, and rebuilds the store. The program prints the values in a fixed order.
const std = @import("std");
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const alloc = arena.allocator();
var out = std.io.getStdOut().writer();
{
var file = try std.fs.cwd().createFile("db.txt", .{});
defer file.close();
var w = file.writer();
try w.print("name={s}\n", .{"zig"});
try w.print("level={s}\n", .{"advanced"});
}
const data = try std.fs.cwd().readFileAlloc(alloc, "db.txt", 4096);
var store = std.StringHashMap([]const u8).init(alloc);
var it = std.mem.splitScalar(u8, data, '\n');
while (it.next()) |line| {
if (line.len == 0) continue;
const eq = std.mem.indexOfScalar(u8, line, '=') orelse continue;
try store.put(line[0..eq], line[eq + 1 ..]);
}
try out.print("name={s}\n", .{store.get("name").?});
try out.print("level={s}\n", .{store.get("level").?});
try out.print("count={d}\n", .{store.count()});
}
Example
const std = @import("std");
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const alloc = arena.allocator();
var out = std.io.getStdOut().writer();
const Entry = struct { key: []const u8, value: []const u8 };
const entries = [_]Entry{ .{ .key = "alpha", .value = "one" }, .{ .key = "beta", .value = "two" }, .{ .key = "gamma", .value = "three" } };
{
var file = try std.fs.cwd().createFile("db.txt", .{});
defer file.close();
var w = file.writer();
for (entries) |e| {
try w.print("{s}={s}\n", .{ e.key, e.value });
}
}
const data = try std.fs.cwd().readFileAlloc(alloc, "db.txt", 4096);
var store = std.StringHashMap([]const u8).init(alloc);
var it = std.mem.splitScalar(u8, data, '\n');
while (it.next()) |line| {
if (line.len == 0) continue;
const eq = std.mem.indexOfScalar(u8, line, '=') orelse continue;
try store.put(line[0..eq], line[eq + 1 ..]);
}
try out.print("alpha={s}\n", .{store.get("alpha").?});
try out.print("beta={s}\n", .{store.get("beta").?});
try out.print("gamma={s}\n", .{store.get("gamma").?});
try out.print("loaded {d} entries\n", .{store.count()});
}
Key points
- Key-value store: a key-value store is a collection that pairs each key with one value.
- Persistence: persistence is the property of data surviving after a program ends, here by writing it to a file.
- Lookup: a lookup is the act of finding a value by its key.
- Record: a record is one key and one value written on one line of the file.
- Reload: a reload is the act of reading saved records back into memory.
- Overwrite: an overwrite is replacing the old file contents with new contents when saving again.
The program writes three records to a file. It then reads the file back into a fresh store. Finally it prints each value by key, in a fixed order, plus the loaded count.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
What does the store print for a key that was saved to the file and then reloaded?