╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭─────────────────────────────────────╮ │ 9 issue: struct { │ │10 title: []const u8, │ │11 description: []const u8, │ │12 - tags: []const []const u8,│ │12 + tags: []const u8, │ │13 }, │ │14 }; │ │15 │ ╰─────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │152 │ │153 switch (@typeInfo(Field)) { │ │154 .pointer => |pointer_info| { │ │155 - if (pointer_info.size != .slice) { │ │156 - @compileError("upsert only supports slices, not other pointer types");│ │157 - } │ │158 - │ │159 - if (pointer_info.child == u8) { │ │155 + if (pointer_info.size == .slice and pointer_info.child == u8) { │ │156 try upsertBytes(DB, hash_kind, map, key, value); │ │157 } else { │ │162 - try upsertArray(DB, hash_kind, map, key, Field, value); │ │158 + @compileError("unsupported upsert field type: " ++ @typeName(Field)); │ │159 } │ │160 }, │ │161 .array => |array_info| { │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │162 if (array_info.child == u8) { │ │163 try upsertBytes(DB, hash_kind, map, key, &value); │ │164 } else { │ │169 - try upsertArray(DB, hash_kind, map, key, Field, value); │ │165 + @compileError("unsupported upsert field type: " ++ @typeName(Field));│ │166 } │ │167 }, │ │168 .int => |int_info| switch (int_info.signedness) { │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │170 .signed => try upsertInt(DB, hash_kind, map, key, value), │ │171 }, │ │172 else => @compileError("unsupported upsert field type: " ++ @typeName(Field)), │ │177 - } │ │178 -} │ │179 - │ │180 -fn upsertArray( │ │181 - comptime DB: type, │ │182 - comptime hash_kind: hash.HashKind, │ │183 - map: DB.HashMap(.read_write), │ │184 - key: hash.HashInt(hash_kind), │ │185 - comptime Value: type, │ │186 - value: Value, │ │187 -) !void { │ │188 - var existing_cursor_maybe = try map.getCursor(key); │ │189 - if (existing_cursor_maybe) |*existing_cursor| { │ │190 - if (try valueEqual(DB, hash_kind, Value, value, existing_cursor)) { │ │191 - return; │ │192 - } │ │193 - } │ │194 - │ │195 - var value_cursor = try map.putCursor(key); │ │196 - try value_cursor.write(.{ .slot = null }); │ │197 - try upsertValue(DB, hash_kind, Value, value, &value_cursor); │ │198 -} │ │199 - │ │200 -fn upsertValue( │ │201 - comptime DB: type, │ │202 - comptime hash_kind: hash.HashKind, │ │203 - comptime Value: type, │ │204 - value: Value, │ │205 - cursor: *DB.Cursor(.read_write), │ │206 -) !void { │ │207 - switch (@typeInfo(Value)) { │ │208 - .pointer => |pointer_info| { │ │209 - if (pointer_info.size == .slice and pointer_info.child == u8) { │ │210 - try cursor.write(.{ .bytes = value }); │ │211 - } else if (pointer_info.size == .slice) { │ │212 - const list = try DB.ArrayList(.read_write).init(cursor.*); │ │213 - for (value) |item| { │ │214 - var item_cursor = try list.appendCursor(); │ │215 - try upsertValue(DB, hash_kind, pointer_info.child, item, &item_cursor);│ │216 - } │ │217 - } else { │ │218 - @compileError("unsupported upsert value type: " ++ @typeName(Value)); │ │219 - } │ │220 - }, │ │221 - .array => |array_info| { │ │222 - const list = try DB.ArrayList(.read_write).init(cursor.*); │ │223 - for (value) |item| { │ │224 - var item_cursor = try list.appendCursor(); │ │225 - try upsertValue(DB, hash_kind, array_info.child, item, &item_cursor); │ │226 - } │ │227 - }, │ │228 - .int => |int_info| switch (int_info.signedness) { │ │229 - .unsigned => try cursor.write(.{ .uint = value }), │ │230 - .signed => try cursor.write(.{ .int = value }), │ │231 - }, │ │232 - else => @compileError("unsupported upsert value type: " ++ @typeName(Value)), │ │173 } │ │174 } │ │175 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │224 } │ │225 │ │226 try map.put(key, .{ .int = value }); │ │287 -} │ │288 - │ │289 -fn valueEqual( │ │290 - comptime DB: type, │ │291 - comptime hash_kind: hash.HashKind, │ │292 - comptime Value: type, │ │293 - value: Value, │ │294 - cursor: *DB.Cursor(.read_only), │ │295 -) !bool { │ │296 - switch (@typeInfo(Value)) { │ │297 - .pointer => |pointer_info| { │ │298 - if (pointer_info.size == .slice and pointer_info.child == u8) { │ │299 - return try bytesEqual(DB, cursor, value); │ │300 - } else if (pointer_info.size == .slice) { │ │301 - return try arrayEqual(DB, hash_kind, pointer_info.child, value, cursor); │ │302 - } else { │ │303 - @compileError("unsupported upsert value type: " ++ @typeName(Value)); │ │304 - } │ │305 - }, │ │306 - .array => |array_info| return try arrayEqual(DB, hash_kind, array_info.child, value[0..], cursor),│ │307 - .int => |int_info| switch (int_info.signedness) { │ │308 - .unsigned => return (try cursor.readUint()) == value, │ │309 - .signed => return (try cursor.readInt()) == value, │ │310 - }, │ │311 - else => @compileError("unsupported upsert value type: " ++ @typeName(Value)), │ │312 - } │ │227 } │ │228 │ │315 -fn arrayEqual( │ │316 - comptime DB: type, │ │317 - comptime hash_kind: hash.HashKind, │ │318 - comptime Item: type, │ │319 - values: []const Item, │ │320 - cursor: *DB.Cursor(.read_only), │ │321 -) !bool { │ │322 - const list = try DB.ArrayList(.read_only).init(cursor.*); │ │323 - if (try list.count() != values.len) { │ │324 - return false; │ │325 - } │ │326 - │ │327 - for (values, 0..) |value, i| { │ │328 - var item_cursor = try list.getCursor(@intCast(i)) orelse return false; │ │329 - if (!try valueEqual(DB, hash_kind, Item, value, &item_cursor)) { │ │330 - return false; │ │331 - } │ │332 - } │ │333 - │ │334 - return true; │ │335 -} │ │336 - │ │229 fn bytesEqual( │ │230 comptime DB: type, │ │231 cursor: *DB.Cursor(.read_only), │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/test/event.zig│ ╰──────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │47 .issue = .{ │ │48 .title = "Login form clears password on validation error", │ │49 .description = "Submitting an invalid email address resets the password field. Preserve the field value and show an inline validation message.",│ │50 - .tags = &[_][]const u8{ "bug", "priority-high", "ui" }, │ │50 + .tags = "bug\x00priority-high\x00ui", │ │51 }, │ │52 }, │ │53 }, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │58 .issue = .{ │ │59 .title = "Login form clears password on validation error", │ │60 .description = "Submitting an invalid email address resets the password field and removes typed input. Preserve the field value and show an inline validation message.",│ │61 - .tags = &[_][]const u8{ "bug", "priority-low", "ui" }, │ │61 + .tags = "bug\x00priority-low\x00ui", │ │62 }, │ │63 }, │ │64 }, │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │68 .issue = .{ │ │69 .title = "Search results ignore archived project filter", │ │70 .description = "Filtering search results to active projects still returns issues from archived projects. Apply the archived flag before ranking results.",│ │71 - .tags = &[_][]const u8{ "bug", "search", "backend" }, │ │71 + .tags = "bug\x00search\x00backend", │ │72 }, │ │73 }, │ │74 }, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │78 .issue = .{ │ │79 .title = "Issue list does not persist selected sort order", │ │80 .description = "Changing the issue list sort order is lost after refresh. Store the selected sort field and direction with the user's view preferences.",│ │81 - .tags = &[_][]const u8{ "enhancement", "frontend", "preferences" }, │ │81 + .tags = "enhancement\x00frontend\x00preferences", │ │82 }, │ │83 }, │ │84 }, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │194 │ │195 // make sure the issue's tags were correctly edited │ │196 const tags_cursor = try first_issue.getCursor(hash.hashInt(repo_opts.hash, "tags")) orelse return error.NotFound;│ │197 - const tags = try Repo.DB.ArrayList(.read_only).init(tags_cursor); │ │198 - try std.testing.expectEqual(events_to_consume[1].data.issue.tags.len, try tags.count()); │ │199 - │ │200 - for (events_to_consume[1].data.issue.tags, 0..) |expected_tag, i| { │ │201 - const tag_cursor = try tags.getCursor(@intCast(i)) orelse return error.NotFound; │ │202 - const tag_value = try tag_cursor.readBytesAlloc(allocator, null); │ │203 - defer allocator.free(tag_value); │ │204 - try std.testing.expectEqualStrings(expected_tag, tag_value); │ │205 - } │ │197 + const tags_value = try tags_cursor.readBytesAlloc(allocator, null); │ │198 + defer allocator.free(tags_value); │ │199 + try std.testing.expectEqualStrings(events_to_consume[1].data.issue.tags, tags_value); │ │200 } │ │201 } │ │202 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯