╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮ │1072 try consume(.xit, admin_repo_opts, io, allocator, &repo, events_ref, &[_]EventWithId{.{│ │1073 .id = event_id_hex, │ │1074 .timestamp = @intCast(std.Io.Timestamp.now(io, .real).toSeconds()), │ │1069 - .author_email = owner.email, │ │1075 + .author_email = owner.event.email, │ │1076 .event = .{ .repo = .{ │ │1077 .user_id = &owner_user_id, │ │1078 .name = repo_name, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1087 // reading from xitdb │ │1088 // │ │1089 │ │1090 +// a struct field's fields share its container's map, so their names must not │ │1091 +// collide with the container's or two fields would silently share a key │ │1092 +fn validateFlattenedFields(comptime T: type) void { │ │1093 + comptime { │ │1094 + for (std.meta.fields(T)) |field| { │ │1095 + if (@typeInfo(field.type) != .@"struct") continue; │ │1096 + for (std.meta.fields(field.type)) |nested| { │ │1097 + if (@hasField(T, nested.name)) │ │1098 + @compileError("flattened field name collision in " ++ @typeName(T) ++ ": " ++ nested.name);│ │1099 + } │ │1100 + } │ │1101 + } │ │1102 +} │ │1103 + │ │1104 pub fn read( │ │1105 comptime T: type, │ │1106 comptime DB: type, │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────╮ │1112 │ │1113 switch (@typeInfo(T)) { │ │1114 .@"struct" => |struct_info| { │ │1115 + comptime validateFlattenedFields(T); │ │1116 inline for (struct_info.fields) |field| {│ │1117 switch (@typeInfo(field.type)) { │ │1118 .pointer => |pointer_info| { │ ╰───────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1148 else │ │1149 null; │ │1150 }, │ │1151 + // a struct field's own fields are read from the same map │ │1152 + .@"struct" => @field(event, field.name) = try read(field.type, DB, hash_kind, arena, map),│ │1153 else => @compileError("unsupported read field type: " ++ @typeName(field.type)), │ │1154 } │ │1155 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────╮ │1160 return event; │ │1161 } │ │1162 │ │1140 -// whether two events carry the same payload data (commit-derived fields│ │1141 -// aren't compared) │ │1163 +// whether two events carry the same data │ │1164 fn fieldsEqual(comptime T: type, a: T, b: T) bool { │ │1165 switch (@typeInfo(T)) { │ │1166 .@"struct" => |struct_info| { │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1167 inline for (struct_info.fields) |field| { │ │1146 - if (comptime derivedField(field.name)) continue; │ │1168 if (!fieldEqual(field.type, @field(a, field.name), @field(b, field.name))) return false;│ │1169 } │ │1170 }, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1255 ) !void { │ │1256 switch (@typeInfo(T)) { │ │1257 .@"struct" => |struct_info| { │ │1258 + comptime validateFlattenedFields(T); │ │1259 inline for (struct_info.fields) |field| { │ │1260 try upsertField(DB, hash_kind, map, field.name, field.type, @field(event, field.name));│ │1261 } │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────╮ │1333 _ = try map.remove(key); │ │1334 } │ │1335 }, │ │1336 + // a struct field's own fields are stored in the same map │ │1337 + .@"struct" => try upsert(Field, DB, hash_kind, map, value), │ │1338 else => @compileError("unsupported upsert field type: " ++ @typeName(Field)),│ │1339 } │ │1340 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────╮ │src/event/Issue.zig│ ╰───────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────╮ │ 8 description: []const u8, │ │ 9 tags: []const u8, // space-separated │ │10 status: Status = .open, │ │11 -author_email: ?[]const u8 = null, │ │12 -created_ts: u64 = 0, // the commit timestamp of the event that first created this issue │ │11 + │ │12 +// what the db stores: the event's data plus the commit-derived fields │ │13 +pub const Record = struct { │ │14 + event: Self, │ │15 + author_email: ?[]const u8 = null, │ │16 + created_ts: u64 = 0, // the commit timestamp of the event that first created this issue│ │17 +}; │ │18 + │ │19 +const Self = @This(); │ │20 │ │21 pub const Status = enum { │ │22 open, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────╮ │58 if (tag.len > tag_max_len) return false; │ │59 } │ │60 return true; │ │54 -} │ │55 - │ │56 -// the commit-derived fields must not appear in the JSON we output│ │57 -pub fn jsonStringify(self: @This(), jw: anytype) !void { │ │58 - try jw.beginObject(); │ │59 - inline for (std.meta.fields(@This())) |field| { │ │60 - if (comptime evt.derivedField(field.name)) continue; │ │61 - try jw.objectField(field.name); │ │62 - try jw.write(@field(self, field.name)); │ │63 - } │ │64 - try jw.endObject(); │ │61 } │ │62 │ │63 pub fn consume( │ ╰──────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯