feat: add CompositeIndexSpec and free to index
This commit is contained in:
1 parent
fff7ffeb78
commit
133f4efa0a
1 file changed
+158
+158
@@ -790,3 +790,161 @@ fn appendLenPrefixed(out: *std.ArrayList(u8), value: []const u8) !void {
|
||||
try out.appendSlice(value);
|
||||
}
|
||||
|
||||
/// One multi-column ("composite") index: a caller-chosen name (used as the
|
||||
/// keyspace discriminator, so it must be a `validIdentifier` like a table or
|
||||
/// field name) plus the ordered list of fields it indexes. Field order is
|
||||
/// significant - it defines both the on-disk key layout and which leading
|
||||
/// ("leftmost") prefix of columns a query can use for a narrowed lookup.
|
||||
pub const CompositeIndexSpec = struct {
|
||||
name: []u8,
|
||||
fields: [][]u8,
|
||||
|
||||
fn free(self: *const CompositeIndexSpec, allocator: Allocator) void {
|
||||
allocator.free(self.name);
|
||||
for (self.fields) |f| allocator.free(f);
|
||||
allocator.free(self.fields);
|
||||
}
|
||||
};
|
||||
|
||||
/// Persisted set of composite indexes on a table, the multi-column
|
||||
/// counterpart to `IndexSet`. Stored under a separate metadata key
|
||||
/// ("CINDEXES" vs "INDEXES") so single-field and composite indexes never
|
||||
/// share or shadow each other's schema record.
|
||||
pub const CompositeIndexSet = struct {
|
||||
allocator: Allocator,
|
||||
specs: std.ArrayList(CompositeIndexSpec),
|
||||
|
||||
pub fn load(allocator: Allocator, db: *Db, table: []const u8) !CompositeIndexSet {
|
||||
const key = try compositeMetaKey(allocator, table);
|
||||
defer allocator.free(key);
|
||||
|
||||
var set = CompositeIndexSet{ .allocator = allocator, .specs = std.ArrayList(CompositeIndexSpec).init(allocator) };
|
||||
errdefer set.deinit();
|
||||
|
||||
const raw = try db.get(allocator, key) orelse return set;
|
||||
defer allocator.free(raw);
|
||||
|
||||
var pos: usize = 0;
|
||||
if (pos + 4 > raw.len) return error.Malformed;
|
||||
const specCount = std.mem.readInt(u32, raw[pos..][0..4], .little);
|
||||
pos += 4;
|
||||
var i: u32 = 0;
|
||||
while (i < specCount) : (i += 1) {
|
||||
if (pos + 4 > raw.len) return error.Malformed;
|
||||
const nameLen = std.mem.readInt(u32, raw[pos..][0..4], .little);
|
||||
pos += 4;
|
||||
if (pos + nameLen > raw.len) return error.Malformed;
|
||||
const name = try allocator.dupe(u8, raw[pos .. pos + nameLen]);
|
||||
errdefer allocator.free(name);
|
||||
pos += nameLen;
|
||||
|
||||
if (pos + 4 > raw.len) return error.Malformed;
|
||||
const fieldCount = std.mem.readInt(u32, raw[pos..][0..4], .little);
|
||||
pos += 4;
|
||||
const fields = try allocator.alloc([]u8, fieldCount);
|
||||
var filled: usize = 0;
|
||||
errdefer {
|
||||
for (fields[0..filled]) |f| allocator.free(f);
|
||||
allocator.free(fields);
|
||||
}
|
||||
var j: u32 = 0;
|
||||
while (j < fieldCount) : (j += 1) {
|
||||
if (pos + 4 > raw.len) return error.Malformed;
|
||||
const len = std.mem.readInt(u32, raw[pos..][0..4], .little);
|
||||
pos += 4;
|
||||
if (pos + len > raw.len) return error.Malformed;
|
||||
fields[j] = try allocator.dupe(u8, raw[pos .. pos + len]);
|
||||
filled += 1;
|
||||
pos += len;
|
||||
}
|
||||
try set.specs.append(.{ .name = name, .fields = fields });
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
pub fn save(self: *const CompositeIndexSet, db: *Db, table: []const u8) !void {
|
||||
const key = try compositeMetaKey(self.allocator, table);
|
||||
defer self.allocator.free(key);
|
||||
|
||||
var out = std.ArrayList(u8).init(self.allocator);
|
||||
defer out.deinit();
|
||||
var countBuf: [4]u8 = undefined;
|
||||
std.mem.writeInt(u32, &countBuf, @intCast(self.specs.items.len), .little);
|
||||
try out.appendSlice(&countBuf);
|
||||
for (self.specs.items) |spec| {
|
||||
var lenBuf: [4]u8 = undefined;
|
||||
std.mem.writeInt(u32, &lenBuf, @intCast(spec.name.len), .little);
|
||||
try out.appendSlice(&lenBuf);
|
||||
try out.appendSlice(spec.name);
|
||||
|
||||
std.mem.writeInt(u32, &lenBuf, @intCast(spec.fields.len), .little);
|
||||
try out.appendSlice(&lenBuf);
|
||||
for (spec.fields) |f| {
|
||||
std.mem.writeInt(u32, &lenBuf, @intCast(f.len), .little);
|
||||
try out.appendSlice(&lenBuf);
|
||||
try out.appendSlice(f);
|
||||
}
|
||||
}
|
||||
try db.put(key, out.items);
|
||||
}
|
||||
|
||||
pub fn find(self: *const CompositeIndexSet, name: []const u8) ?*const CompositeIndexSpec {
|
||||
for (self.specs.items) |*s| {
|
||||
if (std.mem.eql(u8, s.name, name)) return s;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn has(self: *const CompositeIndexSet, name: []const u8) bool {
|
||||
return self.find(name) != null;
|
||||
}
|
||||
|
||||
pub fn add(self: *CompositeIndexSet, name: []const u8, fields: []const []const u8) !void {
|
||||
if (self.has(name)) return;
|
||||
const nameCopy = try self.allocator.dupe(u8, name);
|
||||
errdefer self.allocator.free(nameCopy);
|
||||
|
||||
const fieldsCopy = try self.allocator.alloc([]u8, fields.len);
|
||||
var filled: usize = 0;
|
||||
errdefer {
|
||||
for (fieldsCopy[0..filled]) |f| self.allocator.free(f);
|
||||
self.allocator.free(fieldsCopy);
|
||||
}
|
||||
for (fields, 0..) |f, i| {
|
||||
fieldsCopy[i] = try self.allocator.dupe(u8, f);
|
||||
filled += 1;
|
||||
}
|
||||
try self.specs.append(.{ .name = nameCopy, .fields = fieldsCopy });
|
||||
}
|
||||
|
||||
/// Returns true if a composite index by that name was present and has
|
||||
/// been removed.
|
||||
pub fn remove(self: *CompositeIndexSet, name: []const u8) bool {
|
||||
for (self.specs.items, 0..) |*s, i| {
|
||||
if (std.mem.eql(u8, s.name, name)) {
|
||||
s.free(self.allocator);
|
||||
_ = self.specs.orderedRemove(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *CompositeIndexSet) void {
|
||||
for (self.specs.items) |*s| s.free(self.allocator);
|
||||
self.specs.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
/// Adds one index entry mapping `value` -> `pk` under `table.field`. The
|
||||
/// entry's stored value is the plain (non order-preserving) encoding of the
|
||||
/// primary key, so lookups can recover the exact original Value instead of
|
||||
/// re-parsing the escaped ordered key bytes.
|
||||
pub fn addEntry(db: *Db, allocator: Allocator, table: []const u8, field: []const u8, value: Value, pk: Value) !void {
|
||||
const key = try indexEntryKey(allocator, table, field, value, pk);
|
||||
defer allocator.free(key);
|
||||
const pkBytes = try row.encodeValue(allocator, pk);
|
||||
defer allocator.free(pkBytes);
|
||||
try db.put(key, pkBytes);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user