Skip to content

Commit bd3f90a

Browse files
authored
Merge pull request #6 from marselester/rm-field-order-req
Remove the requirement for struct fields to match database field order
2 parents 52e505c + f864103 commit bd3f90a

1 file changed

Lines changed: 56 additions & 24 deletions

File tree

src/decoder.zig

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -88,39 +88,71 @@ pub const Decoder = struct {
8888
// Once we know the number of pairs, we can look at each pair in turn to determine
8989
// the size of the key and the key name, as well as the value's type and payload.
9090
const map_len = field.size;
91-
var map_key: ?[]const u8 = null;
9291
var field_count: usize = 0;
93-
inline for (std.meta.fields(T)) |f| next_field: {
94-
// Skip struct fields whose name starts with an underscore, e.g., _allocator.
95-
if (f.name[0] == '_') {
96-
break :next_field;
97-
}
92+
while (field_count < map_len) : (field_count += 1) {
93+
const map_key = try self.decodeValue(allocator, []const u8);
94+
95+
var found = false;
96+
inline for (std.meta.fields(T)) |f| {
97+
// Skip struct fields whose name starts with an underscore, e.g., _arena.
98+
if (f.name[0] == '_') {
99+
continue;
100+
}
98101

99-
// Don't decode more struct fields than the number of the map entries.
100-
if (field_count >= map_len) {
101-
break;
102+
if (std.mem.eql(u8, map_key, f.name)) {
103+
const map_value = try self.decodeValue(allocator, f.type);
104+
@field(record, f.name) = map_value;
105+
found = true;
106+
break;
107+
}
102108
}
103109

104-
// The map key is nulled to advance the map decoding to the next key.
105-
// Otherwise the key is used to decode the next struct field.
106-
if (map_key == null) {
107-
map_key = try self.decodeValue(allocator, []const u8);
108-
}
109-
// Struct fields must match the layout (names and order) in the database.
110-
// When the names don't match, the struct field is skipped.
111-
// This is usefull for optional fields, e.g., some db records have city.is_in_european_union flag.
112-
if (!std.mem.eql(u8, map_key.?, f.name)) {
113-
break :next_field;
110+
// If the field wasn't found in the struct, skip the value in the database.
111+
if (!found) {
112+
try self.skipValue();
114113
}
114+
}
115+
116+
return record;
117+
}
118+
119+
// Skips a value in the database without decoding it.
120+
// This is used when the database has fields that don't exist in the target struct.
121+
fn skipValue(self: *Decoder) !void {
122+
const field = self.decodeFieldSizeAndType();
123+
124+
if (field.type == FieldType.Pointer) {
125+
const next_offset = self.decodePointer(field.size);
126+
const prev_offset = self.offset;
115127

116-
const map_value = try self.decodeValue(allocator, f.type);
117-
@field(record, f.name) = map_value;
128+
self.offset = next_offset;
129+
try self.skipValue();
130+
self.offset = prev_offset;
118131

119-
field_count += 1;
120-
map_key = null;
132+
return;
121133
}
122134

123-
return record;
135+
switch (field.type) {
136+
// Bool has no payload, size is encoded in the control byte.
137+
.Bool => {},
138+
// Skip each array element.
139+
.Array => {
140+
for (0..field.size) |_| {
141+
try self.skipValue();
142+
}
143+
},
144+
// Skip each map key-value pair.
145+
.Map => {
146+
for (0..field.size) |_| {
147+
try self.skipValue();
148+
try self.skipValue();
149+
}
150+
},
151+
// For other types, just advance the offset.
152+
else => {
153+
self.offset += field.size;
154+
},
155+
}
124156
}
125157

126158
// Decodes a struct's field value which can be a built-in data type or another struct.

0 commit comments

Comments
 (0)