Skip to content

Commit ef6290f

Browse files
authored
Merge pull request #40 from acoustid/named-attributes
Add support for named attributes (strings)
2 parents 407d159 + a485ca0 commit ef6290f

8 files changed

+59
-25
lines changed

src/FileSegment.zig

+5-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub const Options = struct {
2020
allocator: std.mem.Allocator,
2121
dir: std.fs.Dir,
2222
info: SegmentInfo = .{},
23-
attributes: std.AutoHashMapUnmanaged(u64, u64) = .{},
23+
attributes: std.StringHashMapUnmanaged(u64) = .{},
2424
docs: std.AutoHashMapUnmanaged(u32, bool) = .{},
2525
index: std.ArrayListUnmanaged(u32) = .{},
2626
block_size: usize = 0,
@@ -40,6 +40,10 @@ pub fn init(allocator: std.mem.Allocator, options: Options) Self {
4040
}
4141

4242
pub fn deinit(self: *Self, delete_file: KeepOrDelete) void {
43+
var iter = self.attributes.iterator();
44+
while (iter.next()) |e| {
45+
self.allocator.free(e.key_ptr.*);
46+
}
4347
self.attributes.deinit(self.allocator);
4448
self.docs.deinit(self.allocator);
4549
self.index.deinit(self.allocator);

src/Index.zig

+15-4
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ pub fn getDocInfo(self: *Self, doc_id: u32) !?DocInfo {
501501

502502
pub const IndexInfo = struct {
503503
version: u64,
504-
attributes: std.AutoHashMapUnmanaged(u64, u64),
504+
attributes: std.StringHashMapUnmanaged(u64),
505505

506506
pub fn deinit(self: *IndexInfo, allocator: std.mem.Allocator) void {
507507
self.attributes.deinit(allocator);
@@ -512,16 +512,27 @@ pub fn getInfo(self: *Self, allocator: std.mem.Allocator) !IndexInfo {
512512
var snapshot = self.acquireSegments();
513513
defer self.releaseSegments(&snapshot); // FIXME this possibly deletes orphaned segments, do it in a separate thread
514514

515-
var attributes: std.AutoHashMapUnmanaged(u64, u64) = .{};
516-
errdefer attributes.deinit(allocator);
515+
var attributes: std.StringHashMapUnmanaged(u64) = .{};
516+
errdefer {
517+
var iter = attributes.iterator();
518+
while (iter.next()) |e| {
519+
allocator.free(e.key_ptr.*);
520+
}
521+
attributes.deinit(allocator);
522+
}
517523

518524
var version: u64 = 0;
519525
inline for (segment_lists) |n| {
520526
const segments = @field(snapshot, n);
521527
for (segments.value.nodes.items) |node| {
522528
var iter = node.value.attributes.iterator();
523529
while (iter.next()) |entry| {
524-
try attributes.put(allocator, entry.key_ptr.*, entry.value_ptr.*);
530+
const result = try attributes.getOrPut(allocator, entry.key_ptr.*);
531+
if (!result.found_existing) {
532+
errdefer attributes.removeByPtr(entry.key_ptr);
533+
result.key_ptr.* = try allocator.dupe(u8, entry.key_ptr.*);
534+
}
535+
result.value_ptr.* = entry.value_ptr.*;
525536
}
526537
std.debug.assert(node.value.info.version > version);
527538
version = node.value.info.version;

src/MemorySegment.zig

+8-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub const Options = struct {};
1919

2020
allocator: std.mem.Allocator,
2121
info: SegmentInfo = .{},
22-
attributes: std.AutoHashMapUnmanaged(u64, u64) = .{},
22+
attributes: std.StringHashMapUnmanaged(u64) = .{},
2323
docs: std.AutoHashMapUnmanaged(u32, bool) = .{},
2424
items: std.ArrayListUnmanaged(Item) = .{},
2525
frozen: bool = false,
@@ -34,6 +34,10 @@ pub fn init(allocator: std.mem.Allocator, opts: Options) Self {
3434
pub fn deinit(self: *Self, delete_file: KeepOrDelete) void {
3535
_ = delete_file;
3636

37+
var iter = self.attributes.iterator();
38+
while (iter.next()) |e| {
39+
self.allocator.free(e.key_ptr.*);
40+
}
3741
self.attributes.deinit(self.allocator);
3842
self.docs.deinit(self.allocator);
3943
self.items.deinit(self.allocator);
@@ -99,8 +103,10 @@ pub fn build(self: *Self, changes: []const Change) !void {
99103
}
100104
},
101105
.set_attribute => |op| {
102-
const result = self.attributes.getOrPutAssumeCapacity(op.key);
106+
const result = self.attributes.getOrPutAssumeCapacity(op.name);
103107
if (!result.found_existing) {
108+
errdefer self.attributes.removeByPtr(result.key_ptr);
109+
result.key_ptr.* = try self.allocator.dupe(u8, op.name);
104110
result.value_ptr.* = op.value;
105111
}
106112
},

src/change.zig

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub const Delete = struct {
1818
};
1919

2020
pub const SetAttribute = struct {
21-
key: u64,
21+
name: []const u8,
2222
value: u64,
2323

2424
pub fn msgpackFormat() msgpack.StructFormat {

src/filefmt.zig

+1-1
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ pub fn readSegmentFile(dir: fs.Dir, info: SegmentInfo, segment: *FileSegment) !v
421421

422422
if (header.has_attributes) {
423423
// FIXME nicer api in msgpack.zig
424-
var attributes = std.AutoHashMap(u64, u64).init(segment.allocator);
424+
var attributes = std.StringHashMap(u64).init(segment.allocator);
425425
defer attributes.deinit();
426426
try unpacker.readMapInto(&attributes);
427427
segment.attributes.deinit(segment.allocator);

src/segment_merger.zig

+12-3
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ const SharedPtr = @import("utils/shared_ptr.zig").SharedPtr;
77

88
pub const MergedSegmentInfo = struct {
99
info: SegmentInfo = .{},
10-
attributes: std.AutoHashMapUnmanaged(u64, u64) = .{},
10+
attributes: std.StringHashMapUnmanaged(u64) = .{},
1111
docs: std.AutoHashMapUnmanaged(u32, bool) = .{},
1212

1313
pub fn deinit(self: *MergedSegmentInfo, allocator: std.mem.Allocator) void {
14+
var iter = self.attributes.iterator();
15+
while (iter.next()) |e| {
16+
allocator.free(e.key_ptr.*);
17+
}
1418
self.attributes.deinit(allocator);
1519
self.docs.deinit(allocator);
1620
}
@@ -99,9 +103,14 @@ pub fn SegmentMerger(comptime Segment: type) type {
99103
const segment = source.reader.segment;
100104
var iter = segment.attributes.iterator();
101105
while (iter.next()) |entry| {
102-
const key = entry.key_ptr.*;
106+
const name = entry.key_ptr.*;
103107
const value = entry.value_ptr.*;
104-
self.segment.attributes.putAssumeCapacity(key, value);
108+
const result = self.segment.attributes.getOrPutAssumeCapacity(name);
109+
if (!result.found_existing) {
110+
errdefer self.segment.attributes.removeByPtr(result.key_ptr);
111+
result.key_ptr.* = try self.allocator.dupe(u8, name);
112+
}
113+
result.value_ptr.* = value;
105114
}
106115
}
107116

src/server.zig

+8-10
Original file line numberDiff line numberDiff line change
@@ -243,14 +243,14 @@ fn getRequestBody(comptime T: type, req: *httpz.Request, res: *httpz.Response) !
243243

244244
switch (content_type) {
245245
.json => {
246-
return json.parseFromSliceLeaky(T, req.arena, content, .{}) catch {
247-
try writeErrorResponse(400, error.InvalidContent, req, res);
246+
return json.parseFromSliceLeaky(T, req.arena, content, .{}) catch |err| {
247+
try writeErrorResponse(400, err, req, res);
248248
return null;
249249
};
250250
},
251251
.msgpack => {
252-
return msgpack.decodeFromSliceLeaky(T, req.arena, content) catch {
253-
try writeErrorResponse(400, error.InvalidContent, req, res);
252+
return msgpack.decodeFromSliceLeaky(T, req.arena, content) catch |err| {
253+
try writeErrorResponse(400, err, req, res);
254254
return null;
255255
};
256256
},
@@ -388,18 +388,16 @@ fn handleDeleteFingerprint(ctx: *Context, req: *httpz.Request, res: *httpz.Respo
388388
}
389389

390390
const Attributes = struct {
391-
attributes: std.AutoHashMapUnmanaged(u64, u64),
391+
attributes: std.StringHashMapUnmanaged(u64),
392392

393393
pub fn jsonStringify(self: Attributes, jws: anytype) !void {
394-
try jws.beginArray();
394+
try jws.beginObject();
395395
var iter = self.attributes.iterator();
396396
while (iter.next()) |entry| {
397-
try jws.beginArray();
398-
try jws.write(entry.key_ptr.*);
397+
try jws.objectField(entry.key_ptr.*);
399398
try jws.write(entry.value_ptr.*);
400-
try jws.endArray();
401399
}
402-
try jws.endArray();
400+
try jws.endObject();
403401
}
404402

405403
pub fn msgpackWrite(self: Attributes, packer: anytype) !void {

tests/test_index_api.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,19 @@ def test_get_index_not_found(client, index_name):
2323

2424
@pytest.mark.parametrize('fmt', ['json', 'msgpack'])
2525
def test_get_index(client, index_name, create_index, fmt):
26-
req = client.get(f'/{index_name}', headers=headers(fmt))
26+
req = client.post(f'/{index_name}/_update', json={
27+
'changes': [
28+
{'set_attribute': {'name': 'foo', 'value': 1234}},
29+
],
30+
})
2731
assert req.status_code == 200, req.content
2832

33+
req = client.get(f'/{index_name}', headers=headers(fmt))
34+
assert req.status_code == 200, req.content
2935
if fmt == 'json':
30-
expected = {'version': 0, 'attributes': []}
36+
expected = {'version': 1, 'attributes': {'foo': 1234}}
3137
else:
32-
expected = {'v': 0, 'a': {}}
38+
expected = {'v': 1, 'a': {'foo': 1234}}
3339
assert decode(fmt, req.content) == expected
3440

3541

0 commit comments

Comments
 (0)