feat: add removeEntry and dropAllEntries to index
This commit is contained in:
1 parent
133f4efa0a
commit
cfd2fd1f5d
1 file changed
+162
+162
@@ -948,3 +948,165 @@ pub fn addEntry(db: *Db, allocator: Allocator, table: []const u8, field: []const
|
||||
try db.put(key, pkBytes);
|
||||
}
|
||||
|
||||
pub fn removeEntry(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);
|
||||
try db.delete(key);
|
||||
}
|
||||
|
||||
/// Deletes every index entry for `table.field`, used when dropping an index.
|
||||
pub fn dropAllEntries(db: *Db, allocator: Allocator, table: []const u8, field: []const u8) !void {
|
||||
const prefix = try indexPrefix(allocator, table, field);
|
||||
defer allocator.free(prefix);
|
||||
|
||||
var keys = std.ArrayList([]u8).init(allocator);
|
||||
defer {
|
||||
for (keys.items) |k| allocator.free(k);
|
||||
keys.deinit();
|
||||
}
|
||||
|
||||
var it = try db.scanPrefix(allocator, prefix);
|
||||
defer it.deinit();
|
||||
while (try it.next()) |entry| {
|
||||
try keys.append(try allocator.dupe(u8, entry.key));
|
||||
}
|
||||
for (keys.items) |k| try db.delete(k);
|
||||
}
|
||||
|
||||
/// Adds one composite index entry mapping the full `values` tuple -> `pk`
|
||||
/// under `table`'s index `indexName`. Same stored-value convention as
|
||||
/// `addEntry`: the plain (non order-preserving) pk encoding.
|
||||
pub fn addCompositeEntry(db: *Db, allocator: Allocator, table: []const u8, indexName: []const u8, values: []const Value, pk: Value) !void {
|
||||
const key = try compositeIndexEntryKey(allocator, table, indexName, values, pk);
|
||||
defer allocator.free(key);
|
||||
const pkBytes = try row.encodeValue(allocator, pk);
|
||||
defer allocator.free(pkBytes);
|
||||
try db.put(key, pkBytes);
|
||||
}
|
||||
|
||||
pub fn removeCompositeEntry(db: *Db, allocator: Allocator, table: []const u8, indexName: []const u8, values: []const Value, pk: Value) !void {
|
||||
const key = try compositeIndexEntryKey(allocator, table, indexName, values, pk);
|
||||
defer allocator.free(key);
|
||||
try db.delete(key);
|
||||
}
|
||||
|
||||
/// Deletes every entry for a composite index, used when dropping it.
|
||||
pub fn dropAllCompositeEntries(db: *Db, allocator: Allocator, table: []const u8, indexName: []const u8) !void {
|
||||
const prefix = try compositeIndexPrefix(allocator, table, indexName);
|
||||
defer allocator.free(prefix);
|
||||
|
||||
var keys = std.ArrayList([]u8).init(allocator);
|
||||
defer {
|
||||
for (keys.items) |k| allocator.free(k);
|
||||
keys.deinit();
|
||||
}
|
||||
|
||||
var it = try db.scanPrefix(allocator, prefix);
|
||||
defer it.deinit();
|
||||
while (try it.next()) |entry| {
|
||||
try keys.append(try allocator.dupe(u8, entry.key));
|
||||
}
|
||||
for (keys.items) |k| try db.delete(k);
|
||||
}
|
||||
|
||||
/// Lazy cursor over a leftmost-prefix equality lookup on a composite index:
|
||||
/// `valuesPrefix` may cover all of the index's columns or just a leading
|
||||
/// subset of them. Streaming for the same reason as `EqCursor`.
|
||||
pub const CompositeEqCursor = struct {
|
||||
it: dbmod.RangeIterator,
|
||||
|
||||
pub fn next(self: *CompositeEqCursor, allocator: Allocator) !?Value {
|
||||
const entry = try self.it.next() orelse return null;
|
||||
return try row.decodeValue(allocator, entry.value);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *CompositeEqCursor) void {
|
||||
self.it.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
pub fn scanCompositeEqCursor(db: *Db, allocator: Allocator, table: []const u8, indexName: []const u8, valuesPrefix: []const Value) !CompositeEqCursor {
|
||||
const prefix = try compositeIndexValueBound(allocator, table, indexName, valuesPrefix);
|
||||
defer allocator.free(prefix);
|
||||
return .{ .it = try db.scanPrefix(allocator, prefix) };
|
||||
}
|
||||
|
||||
/// Eager counterpart of `scanCompositeEqCursor`, all matching primary keys.
|
||||
pub fn scanCompositeEq(db: *Db, allocator: Allocator, table: []const u8, indexName: []const u8, valuesPrefix: []const Value) ![]Value {
|
||||
var cursor = try scanCompositeEqCursor(db, allocator, table, indexName, valuesPrefix);
|
||||
defer cursor.deinit();
|
||||
|
||||
var results = std.ArrayList(Value).init(allocator);
|
||||
errdefer {
|
||||
for (results.items) |v| v.free(allocator);
|
||||
results.deinit();
|
||||
}
|
||||
while (try cursor.next(allocator)) |pk| try results.append(pk);
|
||||
return results.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Lazy cursor over a range lookup on the column immediately following a
|
||||
/// leftmost-prefix of equality columns on a composite index: `eqValues`
|
||||
/// pins the leading columns exactly like `CompositeEqCursor`, `low`/`high`
|
||||
/// bound the next column. Columns beyond that one are left unconstrained by
|
||||
/// the scan, same leftmost-prefix rule as the equality-only cursor.
|
||||
pub const CompositeRangeCursor = struct {
|
||||
it: dbmod.RangeIterator,
|
||||
|
||||
pub fn next(self: *CompositeRangeCursor, allocator: Allocator) !?Value {
|
||||
const entry = try self.it.next() orelse return null;
|
||||
return try row.decodeValue(allocator, entry.value);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *CompositeRangeCursor) void {
|
||||
self.it.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
pub fn scanCompositeRangeCursor(
|
||||
db: *Db,
|
||||
allocator: Allocator,
|
||||
table: []const u8,
|
||||
indexName: []const u8,
|
||||
eqValues: []const Value,
|
||||
low: ?Bound,
|
||||
high: ?Bound,
|
||||
) !CompositeRangeCursor {
|
||||
const prefix = try compositeIndexValueBound(allocator, table, indexName, eqValues);
|
||||
defer allocator.free(prefix);
|
||||
|
||||
var startBuf: ?[]u8 = null;
|
||||
defer if (startBuf) |b| allocator.free(b);
|
||||
var endBuf: ?[]u8 = null;
|
||||
defer if (endBuf) |b| allocator.free(b);
|
||||
|
||||
// Same inclusive/exclusive gap trick as `scanRangeCursor`: a real entry
|
||||
// key is always `compositeIndexRangeBound(eqValues, value) ++ orderedPk`,
|
||||
// a strict byte-extension of the bare per-value bound (pk encodings are
|
||||
// never empty), so a key with value == threshold always sorts after
|
||||
// `compositeIndexRangeBound(eqValues, threshold)` itself.
|
||||
const start = if (low) |b| blk: {
|
||||
const bound = try compositeIndexRangeBound(allocator, table, indexName, eqValues, b.value);
|
||||
if (b.inclusive) {
|
||||
startBuf = bound;
|
||||
} else {
|
||||
defer allocator.free(bound);
|
||||
startBuf = try prefixUpperBound(allocator, bound) orelse try allocator.dupe(u8, bound);
|
||||
}
|
||||
break :blk startBuf.?;
|
||||
} else prefix;
|
||||
|
||||
const end = if (high) |b| blk: {
|
||||
const bound = try compositeIndexRangeBound(allocator, table, indexName, eqValues, b.value);
|
||||
if (b.inclusive) {
|
||||
defer allocator.free(bound);
|
||||
endBuf = try prefixUpperBound(allocator, bound) orelse try allocator.dupe(u8, bound);
|
||||
} else {
|
||||
endBuf = bound;
|
||||
}
|
||||
break :blk endBuf.?;
|
||||
} else blk: {
|
||||
endBuf = try prefixUpperBound(allocator, prefix) orelse try allocator.dupe(u8, prefix);
|
||||
break :blk endBuf.?;
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user