Skip to content

Commit 1a3ff8f

Browse files
authored
Merge pull request #9 from marselester/api-refactor
Refactor lookup() and within()
2 parents 7de0b17 + 60cd0ab commit 1a3ff8f

10 files changed

Lines changed: 384 additions & 393 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Here is how you can decode `City.city` and `City.country` fields.
5454
```zig
5555
// This gets us ~34% of performance gains, i.e., ~859K lookups per second.
5656
const fields = maxminddb.Fields.from(maxminddb.geolite2.City, &.{ "city", "country" });
57-
const city = try db.lookup(allocator, maxminddb.geolite2.City, &ip, .{ .only = fields });
57+
const city = try db.lookup(allocator, maxminddb.geolite2.City, ip, .{ .only = fields });
5858
```
5959

6060
Alternatively, define your own struct.
@@ -69,7 +69,7 @@ const MyCity = struct {
6969
} = .{},
7070
};
7171
72-
const city = try db.lookup(allocator, MyCity, &ip, .{});
72+
const city = try db.lookup(allocator, MyCity, ip, .{});
7373
```
7474

7575
Decoding `MyCity` increases throughput by up to 60% (639,848 vs 1,025,477 lookups per second).

examples/benchmark.zig

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,15 @@ pub fn main() !void {
5050
std.crypto.random.bytes(&ip_bytes);
5151
const ip = std.net.Address.initIp4(ip_bytes, 0);
5252

53-
_ = db.lookup(arena_allocator, maxminddb.geolite2.City, &ip, .{}) catch |err| {
54-
switch (err) {
55-
maxminddb.Error.AddressNotFound => {
56-
not_found_count += 1;
57-
continue;
58-
},
59-
else => {
60-
std.debug.print("! Lookup error for IP {any}: {any}\n", .{ ip, err });
61-
lookup_errors += 1;
62-
continue;
63-
},
64-
}
53+
const result = db.lookup(arena_allocator, maxminddb.geolite2.City, ip, .{}) catch |err| {
54+
std.debug.print("! Lookup error for IP {any}: {any}\n", .{ ip, err });
55+
lookup_errors += 1;
56+
continue;
6557
};
58+
if (result == null) {
59+
not_found_count += 1;
60+
continue;
61+
}
6662
_ = arena.reset(std.heap.ArenaAllocator.ResetMode.retain_capacity);
6763
}
6864

examples/lookup.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ pub fn main() !void {
1616
// Note, for better performance use arena allocator and reset it after calling lookup().
1717
// You won't need to call city.deinit() in that case.
1818
const ip = try std.net.Address.parseIp("89.160.20.128", 0);
19-
const city = try db.lookup(allocator, maxminddb.geoip2.City, &ip, .{});
19+
const city = try db.lookup(allocator, maxminddb.geoip2.City, ip, .{}) orelse return;
2020
defer city.deinit();
2121

22-
var it = city.country.names.?.iterator();
22+
var it = city.value.country.names.?.iterator();
2323
while (it.next()) |kv| {
2424
std.debug.print("{s} = {s}\n", .{ kv.key_ptr.*, kv.value_ptr.* });
2525
}

examples/within.zig

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,35 +19,33 @@ pub fn main() !void {
1919
var it = try db.within(allocator, maxminddb.geolite2.City, network, .{});
2020
defer it.deinit();
2121

22-
// Note, for better performance use arena allocator and reset it after calling it.next().
23-
// You won't need to call item.record.deinit() in that case.
22+
// The iterator owns the values; each next() call invalidates the previous item.
2423
var n: usize = 0;
25-
while (try it.next(allocator)) |item| {
26-
defer item.record.deinit();
24+
while (try it.next()) |item| {
2725

28-
const continent = item.record.continent.code;
29-
const country = item.record.country.iso_code;
26+
const continent = item.value.continent.code;
27+
const country = item.value.country.iso_code;
3028
var city: []const u8 = "";
31-
if (item.record.city.names) |city_names| {
29+
if (item.value.city.names) |city_names| {
3230
city = city_names.get("en") orelse "";
3331
}
3432

3533
if (city.len != 0) {
3634
std.debug.print("{f} {s}-{s}-{s}\n", .{
37-
item.net,
35+
item.network,
3836
continent,
3937
country,
4038
city,
4139
});
4240
} else if (country.len != 0) {
4341
std.debug.print("{f} {s}-{s}\n", .{
44-
item.net,
42+
item.network,
4543
continent,
4644
country,
4745
});
4846
} else if (continent.len != 0) {
4947
std.debug.print("{f} {s}\n", .{
50-
item.net,
48+
item.network,
5149
continent,
5250
});
5351
}

src/decoder.zig

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const DataField = struct {
4949

5050
/// Fields is a bitmask for selecting which top-level struct fields to decode.
5151
/// It also provides struct introspection helpers that skip underscore-prefixed
52-
/// internal fields (e.g., _arena): count, index, and entries.
52+
/// internal fields: count, index, and entries.
5353
pub const Fields = struct {
5454
mask: u64 = 0,
5555

@@ -143,7 +143,7 @@ pub const Decoder = struct {
143143
// This means that strings such as geolite2.City.postal.code are backed by the src's array,
144144
// so the caller should create a copy of the record when the src is freed (when the database is closed).
145145
//
146-
// When fields is set, only top-level fields whose bit is set are decoded; others are skipped.
146+
// When fields provided, only top-level fields whose bit is set are decoded; others are skipped.
147147
pub fn decodeRecord(
148148
self: *Decoder,
149149
allocator: std.mem.Allocator,
@@ -156,7 +156,7 @@ pub const Decoder = struct {
156156

157157
fn decodeStruct(
158158
self: *Decoder,
159-
parent_allocator: std.mem.Allocator,
159+
allocator: std.mem.Allocator,
160160
T: type,
161161
data_field: DataField,
162162
fields: ?Fields,
@@ -165,22 +165,9 @@ pub const Decoder = struct {
165165
return DecodeError.ExpectedStructType;
166166
}
167167

168-
// The decoded record (e.g., geolite2.City) must be initialized with an allocator,
169-
// so the caller could free the memory when the record is no longer needed.
170-
// Record's inner structs will use the same allocator.
171-
//
172168
// Note, all the record's fields must be defined, i.e., .{ .some_field = undefined }
173169
// could contain garbage if the field wasn't found in the database and therefore not decoded.
174-
var record: T = undefined;
175-
var allocator = parent_allocator;
176-
if (@hasDecl(T, "init")) {
177-
record = T.init(allocator);
178-
allocator = record._arena.allocator();
179-
} else {
180-
record = .{};
181-
}
182-
// Free the record if decoding has failed.
183-
errdefer if (@hasDecl(T, "init")) record.deinit();
170+
var record: T = .{};
184171

185172
// Maps use the size in the control byte (and any following bytes) to indicate
186173
// the number of key/value pairs in the map, not the size of the payload in bytes.
@@ -195,7 +182,7 @@ pub const Decoder = struct {
195182

196183
var found = false;
197184
inline for (std.meta.fields(T)) |f| {
198-
// Skip struct fields whose name starts with an underscore, e.g., _arena.
185+
// Skip struct fields whose name starts with an underscore.
199186
if (f.name[0] == '_') {
200187
continue;
201188
}
@@ -283,7 +270,7 @@ pub const Decoder = struct {
283270

284271
return switch (T) {
285272
// String or Bytes
286-
[]const u8 => if (field.type == .String or field.type == .Bytes) self.decodeBytes(field.size) else DecodeError.ExpectedStringOrBytes,
273+
[]const u8 => if (field.type == FieldType.String or field.type == FieldType.Bytes) self.decodeBytes(field.size) else DecodeError.ExpectedStringOrBytes,
287274
// Double
288275
f64 => if (field.type == FieldType.Double) try self.decodeDouble(field.size) else DecodeError.ExpectedDouble,
289276
// Uint16
@@ -361,7 +348,7 @@ pub const Decoder = struct {
361348
}
362349

363350
// Decode Map into a struct, e.g., geolite2.City.continent.
364-
// Nested structs are always fully decoded (no field mask).
351+
// Nested structs are always fully decoded (no Fields mask).
365352
return try self.decodeStruct(allocator, T, field, null);
366353
},
367354
};

src/geoip2.zig

Lines changed: 21 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@ pub const Names = std.StringArrayHashMap([]const u8);
88
/// It can be used for geolocation at the country-level for analytics, content customization,
99
/// or compliance use cases in territories that are not disputed.
1010
pub const Country = struct {
11-
continent: Self.Continent,
12-
country: Self.Country,
13-
registered_country: Self.Country,
14-
represented_country: Self.RepresentedCountry,
15-
traits: Self.Traits,
16-
17-
_arena: std.heap.ArenaAllocator,
11+
continent: Self.Continent = .{},
12+
country: Self.Country = .{},
13+
registered_country: Self.Country = .{},
14+
represented_country: Self.RepresentedCountry = .{},
15+
traits: Self.Traits = .{},
1816

1917
const Self = @This();
2018
pub const Continent = struct {
@@ -38,42 +36,22 @@ pub const Country = struct {
3836
pub const Traits = struct {
3937
is_anycast: bool = false,
4038
};
41-
42-
pub fn init(allocator: std.mem.Allocator) Self {
43-
const arena = std.heap.ArenaAllocator.init(allocator);
44-
45-
return .{
46-
.continent = .{},
47-
.country = .{},
48-
.registered_country = .{},
49-
.represented_country = .{},
50-
.traits = .{},
51-
52-
._arena = arena,
53-
};
54-
}
55-
56-
pub fn deinit(self: *const Self) void {
57-
self._arena.deinit();
58-
}
5939
};
6040

6141
/// City represents a record in the GeoIP2-City database, for example,
6242
/// https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoIP2-City-Test.json.
6343
///
6444
/// It can be used for geolocation down to the city or postal code for analytics and content customization.
6545
pub const City = struct {
66-
city: Self.City,
67-
continent: Country.Continent,
68-
country: Country.Country,
69-
location: Self.Location,
70-
postal: Self.Postal,
71-
registered_country: Country.Country,
72-
represented_country: Country.RepresentedCountry,
46+
city: Self.City = .{},
47+
continent: Country.Continent = .{},
48+
country: Country.Country = .{},
49+
location: Self.Location = .{},
50+
postal: Self.Postal = .{},
51+
registered_country: Country.Country = .{},
52+
represented_country: Country.RepresentedCountry = .{},
7353
subdivisions: ?std.ArrayList(Self.Subdivision) = null,
74-
traits: Country.Traits,
75-
76-
_arena: std.heap.ArenaAllocator,
54+
traits: Country.Traits = .{},
7755

7856
const Self = @This();
7957
pub const City = struct {
@@ -95,45 +73,22 @@ pub const City = struct {
9573
iso_code: []const u8 = "",
9674
names: ?Names = null,
9775
};
98-
99-
pub fn init(allocator: std.mem.Allocator) Self {
100-
const arena = std.heap.ArenaAllocator.init(allocator);
101-
102-
return .{
103-
.city = .{},
104-
.continent = .{},
105-
.country = .{},
106-
.location = .{},
107-
.postal = .{},
108-
.registered_country = .{},
109-
.represented_country = .{},
110-
.traits = .{},
111-
112-
._arena = arena,
113-
};
114-
}
115-
116-
pub fn deinit(self: *const Self) void {
117-
self._arena.deinit();
118-
}
11976
};
12077

12178
/// Enterprise represents a record in the GeoIP2-Enterprise database, for example,
12279
/// https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoIP2-Enterprise-Test.json.
12380
/// Determine geolocation data such as country, region, state, city, ZIP/postal code,
12481
/// and additional intelligence such as confidence factors, ISP, domain, and connection type.
12582
pub const Enterprise = struct {
126-
city: Self.City,
127-
continent: Self.Continent,
128-
country: Self.Country,
129-
location: Self.Location,
130-
postal: Self.Postal,
131-
registered_country: Self.Country,
132-
represented_country: Self.RepresentedCountry,
83+
city: Self.City = .{},
84+
continent: Self.Continent = .{},
85+
country: Self.Country = .{},
86+
location: Self.Location = .{},
87+
postal: Self.Postal = .{},
88+
registered_country: Self.Country = .{},
89+
represented_country: Self.RepresentedCountry = .{},
13390
subdivisions: ?std.ArrayList(Self.Subdivision) = null,
134-
traits: Self.Traits,
135-
136-
_arena: std.heap.ArenaAllocator,
91+
traits: Self.Traits = .{},
13792

13893
const Self = @This();
13994
pub const City = struct {
@@ -191,27 +146,6 @@ pub const Enterprise = struct {
191146
static_ip_score: f64 = 0,
192147
user_type: []const u8 = "",
193148
};
194-
195-
pub fn init(allocator: std.mem.Allocator) Self {
196-
const arena = std.heap.ArenaAllocator.init(allocator);
197-
198-
return .{
199-
.city = .{},
200-
.continent = .{},
201-
.country = .{},
202-
.location = .{},
203-
.postal = .{},
204-
.registered_country = .{},
205-
.represented_country = .{},
206-
.traits = .{},
207-
208-
._arena = arena,
209-
};
210-
}
211-
212-
pub fn deinit(self: *const Self) void {
213-
self._arena.deinit();
214-
}
215149
};
216150

217151
/// ISP represents a record in the GeoIP2-ISP database, for example,

0 commit comments

Comments
 (0)