diff --git a/src/index.zig b/src/index.zig new file mode 100644 index 0000000..9960974 --- /dev/null +++ b/src/index.zig @@ -0,0 +1,155 @@ +const std = @import("std"); +const dbmod = @import("db.zig"); +const row = @import("row.zig"); + +const Allocator = std.mem.Allocator; +const Db = dbmod.Db; +const Value = row.Value; + +/// Keyspace layout shared by every table and index living inside a single +/// Db instance: +/// "R\x00" table "\x00" orderedPk -> encoded Row +/// "I\x00" table "\x00" field "\x00" orderedValue orderedPk -> encoded pk Value +/// "J\x00" table "\x00" indexName "\x00" orderedValue1 .. orderedValueN orderedPk -> encoded pk Value +/// "M\x00INDEXES\x00" table -> encoded list of indexed field names +/// "M\x00CINDEXES\x00" table -> encoded list of composite index specs +/// "M\x00UNIQUE\x00" table -> encoded list of UNIQUE-constrained field names +/// "M\x00FKEYS\x00" table -> encoded list of FOREIGN KEY specs (field, refTable, refField) +/// "M\x00CHECKS\x00" table -> encoded list of CHECK constraint specs (name, expr source) +/// "M\x00REFBY\x00" table -> encoded list of reverse FK specs (childTable, childField, refField), +/// one entry per OTHER table's FOREIGN KEY that names this table+refField +/// +/// All namespaces share one flat LSM keyspace (there is only one Db), so the +/// leading tag byte plus NUL separators keep tables, single-field indexes, +/// composite indexes, and metadata from ever colliding or shadowing each +/// other during a scan. A composite index entry's value columns are just N +/// single-column `row.appendOrdered` encodings concatenated in the index's +/// declared field order ahead of the primary key's - each encoding is +/// already self-delimiting (fixed width for none/boolean/int/float, a +/// 0x00 0x00 terminator for text/bytes with 0x00 bytes escaped), so the +/// concatenation compares byte-for-byte in exactly the same order as +/// comparing the value tuples component by component. That is what makes a +/// prefix scan over the leading K of N components ("leftmost prefix" rule, +/// same as a real multi-column B-tree index) a correct equality lookup on +/// just those K columns, unconstrained on the rest. +pub const rowTag: u8 = 'R'; +pub const indexTag: u8 = 'I'; +pub const compositeIndexTag: u8 = 'J'; +pub const metaTag: u8 = 'M'; +const sep: u8 = 0x00; + +fn appendSegment(out: *std.ArrayList(u8), segment: []const u8) !void { + try out.appendSlice(segment); + try out.append(sep); +} + +pub fn rowPrefix(allocator: Allocator, table: []const u8) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(rowTag); + try appendSegment(&out, table); + return out.toOwnedSlice(); +} + +pub fn rowKey(allocator: Allocator, table: []const u8, pk: Value) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(rowTag); + try appendSegment(&out, table); + try row.appendOrdered(&out, pk); + return out.toOwnedSlice(); +} + +pub fn indexPrefix(allocator: Allocator, table: []const u8, field: []const u8) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(indexTag); + try appendSegment(&out, table); + try appendSegment(&out, field); + return out.toOwnedSlice(); +} + +pub fn indexEntryKey(allocator: Allocator, table: []const u8, field: []const u8, value: Value, pk: Value) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(indexTag); + try appendSegment(&out, table); + try appendSegment(&out, field); + try row.appendOrdered(&out, value); + try row.appendOrdered(&out, pk); + return out.toOwnedSlice(); +} + +pub fn indexValueBound(allocator: Allocator, table: []const u8, field: []const u8, value: Value) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(indexTag); + try appendSegment(&out, table); + try appendSegment(&out, field); + try row.appendOrdered(&out, value); + return out.toOwnedSlice(); +} + +fn metaKey(allocator: Allocator, table: []const u8) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(metaTag); + try appendSegment(&out, "INDEXES"); + try out.appendSlice(table); + return out.toOwnedSlice(); +} + +fn compositeMetaKey(allocator: Allocator, table: []const u8) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(metaTag); + try appendSegment(&out, "CINDEXES"); + try out.appendSlice(table); + return out.toOwnedSlice(); +} + +fn uniqueMetaKey(allocator: Allocator, table: []const u8) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(metaTag); + try appendSegment(&out, "UNIQUE"); + try out.appendSlice(table); + return out.toOwnedSlice(); +} + +fn foreignKeyMetaKey(allocator: Allocator, table: []const u8) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(metaTag); + try appendSegment(&out, "FKEYS"); + try out.appendSlice(table); + return out.toOwnedSlice(); +} + +fn checkMetaKey(allocator: Allocator, table: []const u8) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(metaTag); + try appendSegment(&out, "CHECKS"); + try out.appendSlice(table); + return out.toOwnedSlice(); +} + +fn referencedByMetaKey(allocator: Allocator, table: []const u8) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(metaTag); + try appendSegment(&out, "REFBY"); + try out.appendSlice(table); + return out.toOwnedSlice(); +} + +pub fn compositeIndexPrefix(allocator: Allocator, table: []const u8, indexName: []const u8) ![]u8 { + var out = std.ArrayList(u8).init(allocator); + errdefer out.deinit(); + try out.append(compositeIndexTag); + try appendSegment(&out, table); + try appendSegment(&out, indexName); + return out.toOwnedSlice(); +} +