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);
|
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