Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Here is how you can decode `City.city` and `City.country` fields.
```zig
// This gets us ~34% of performance gains, i.e., ~859K lookups per second.
const fields = maxminddb.Fields.from(maxminddb.geolite2.City, &.{ "city", "country" });
const city = try db.lookup(allocator, maxminddb.geolite2.City, &ip, .{ .only = fields });
const city = try db.lookup(allocator, maxminddb.geolite2.City, ip, .{ .only = fields });
```

Alternatively, define your own struct.
Expand All @@ -69,7 +69,7 @@ const MyCity = struct {
} = .{},
};

const city = try db.lookup(allocator, MyCity, &ip, .{});
const city = try db.lookup(allocator, MyCity, ip, .{});
```

Decoding `MyCity` increases throughput by up to 60% (639,848 vs 1,025,477 lookups per second).
Expand Down
20 changes: 8 additions & 12 deletions examples/benchmark.zig
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,15 @@ pub fn main() !void {
std.crypto.random.bytes(&ip_bytes);
const ip = std.net.Address.initIp4(ip_bytes, 0);

_ = db.lookup(arena_allocator, maxminddb.geolite2.City, &ip, .{}) catch |err| {
switch (err) {
maxminddb.Error.AddressNotFound => {
not_found_count += 1;
continue;
},
else => {
std.debug.print("! Lookup error for IP {any}: {any}\n", .{ ip, err });
lookup_errors += 1;
continue;
},
}
const result = db.lookup(arena_allocator, maxminddb.geolite2.City, ip, .{}) catch |err| {
std.debug.print("! Lookup error for IP {any}: {any}\n", .{ ip, err });
lookup_errors += 1;
continue;
};
if (result == null) {
not_found_count += 1;
continue;
}
_ = arena.reset(std.heap.ArenaAllocator.ResetMode.retain_capacity);
}

Expand Down
4 changes: 2 additions & 2 deletions examples/lookup.zig
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ pub fn main() !void {
// Note, for better performance use arena allocator and reset it after calling lookup().
// You won't need to call city.deinit() in that case.
const ip = try std.net.Address.parseIp("89.160.20.128", 0);
const city = try db.lookup(allocator, maxminddb.geoip2.City, &ip, .{});
const city = try db.lookup(allocator, maxminddb.geoip2.City, ip, .{}) orelse return;
defer city.deinit();

var it = city.country.names.?.iterator();
var it = city.value.country.names.?.iterator();
while (it.next()) |kv| {
std.debug.print("{s} = {s}\n", .{ kv.key_ptr.*, kv.value_ptr.* });
}
Expand Down
18 changes: 8 additions & 10 deletions examples/within.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,33 @@ pub fn main() !void {
var it = try db.within(allocator, maxminddb.geolite2.City, network, .{});
defer it.deinit();

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

const continent = item.record.continent.code;
const country = item.record.country.iso_code;
const continent = item.value.continent.code;
const country = item.value.country.iso_code;
var city: []const u8 = "";
if (item.record.city.names) |city_names| {
if (item.value.city.names) |city_names| {
city = city_names.get("en") orelse "";
}

if (city.len != 0) {
std.debug.print("{f} {s}-{s}-{s}\n", .{
item.net,
item.network,
continent,
country,
city,
});
} else if (country.len != 0) {
std.debug.print("{f} {s}-{s}\n", .{
item.net,
item.network,
continent,
country,
});
} else if (continent.len != 0) {
std.debug.print("{f} {s}\n", .{
item.net,
item.network,
continent,
});
}
Expand Down
27 changes: 7 additions & 20 deletions src/decoder.zig
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const DataField = struct {

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

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

fn decodeStruct(
self: *Decoder,
parent_allocator: std.mem.Allocator,
allocator: std.mem.Allocator,
T: type,
data_field: DataField,
fields: ?Fields,
Expand All @@ -165,22 +165,9 @@ pub const Decoder = struct {
return DecodeError.ExpectedStructType;
}

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

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

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

return switch (T) {
// String or Bytes
[]const u8 => if (field.type == .String or field.type == .Bytes) self.decodeBytes(field.size) else DecodeError.ExpectedStringOrBytes,
[]const u8 => if (field.type == FieldType.String or field.type == FieldType.Bytes) self.decodeBytes(field.size) else DecodeError.ExpectedStringOrBytes,
// Double
f64 => if (field.type == FieldType.Double) try self.decodeDouble(field.size) else DecodeError.ExpectedDouble,
// Uint16
Expand Down Expand Up @@ -361,7 +348,7 @@ pub const Decoder = struct {
}

// Decode Map into a struct, e.g., geolite2.City.continent.
// Nested structs are always fully decoded (no field mask).
// Nested structs are always fully decoded (no Fields mask).
return try self.decodeStruct(allocator, T, field, null);
},
};
Expand Down
108 changes: 21 additions & 87 deletions src/geoip2.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@ pub const Names = std.StringArrayHashMap([]const u8);
/// It can be used for geolocation at the country-level for analytics, content customization,
/// or compliance use cases in territories that are not disputed.
pub const Country = struct {
continent: Self.Continent,
country: Self.Country,
registered_country: Self.Country,
represented_country: Self.RepresentedCountry,
traits: Self.Traits,

_arena: std.heap.ArenaAllocator,
continent: Self.Continent = .{},
country: Self.Country = .{},
registered_country: Self.Country = .{},
represented_country: Self.RepresentedCountry = .{},
traits: Self.Traits = .{},

const Self = @This();
pub const Continent = struct {
Expand All @@ -38,42 +36,22 @@ pub const Country = struct {
pub const Traits = struct {
is_anycast: bool = false,
};

pub fn init(allocator: std.mem.Allocator) Self {
const arena = std.heap.ArenaAllocator.init(allocator);

return .{
.continent = .{},
.country = .{},
.registered_country = .{},
.represented_country = .{},
.traits = .{},

._arena = arena,
};
}

pub fn deinit(self: *const Self) void {
self._arena.deinit();
}
};

/// City represents a record in the GeoIP2-City database, for example,
/// https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoIP2-City-Test.json.
///
/// It can be used for geolocation down to the city or postal code for analytics and content customization.
pub const City = struct {
city: Self.City,
continent: Country.Continent,
country: Country.Country,
location: Self.Location,
postal: Self.Postal,
registered_country: Country.Country,
represented_country: Country.RepresentedCountry,
city: Self.City = .{},
continent: Country.Continent = .{},
country: Country.Country = .{},
location: Self.Location = .{},
postal: Self.Postal = .{},
registered_country: Country.Country = .{},
represented_country: Country.RepresentedCountry = .{},
subdivisions: ?std.ArrayList(Self.Subdivision) = null,
traits: Country.Traits,

_arena: std.heap.ArenaAllocator,
traits: Country.Traits = .{},

const Self = @This();
pub const City = struct {
Expand All @@ -95,45 +73,22 @@ pub const City = struct {
iso_code: []const u8 = "",
names: ?Names = null,
};

pub fn init(allocator: std.mem.Allocator) Self {
const arena = std.heap.ArenaAllocator.init(allocator);

return .{
.city = .{},
.continent = .{},
.country = .{},
.location = .{},
.postal = .{},
.registered_country = .{},
.represented_country = .{},
.traits = .{},

._arena = arena,
};
}

pub fn deinit(self: *const Self) void {
self._arena.deinit();
}
};

/// Enterprise represents a record in the GeoIP2-Enterprise database, for example,
/// https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoIP2-Enterprise-Test.json.
/// Determine geolocation data such as country, region, state, city, ZIP/postal code,
/// and additional intelligence such as confidence factors, ISP, domain, and connection type.
pub const Enterprise = struct {
city: Self.City,
continent: Self.Continent,
country: Self.Country,
location: Self.Location,
postal: Self.Postal,
registered_country: Self.Country,
represented_country: Self.RepresentedCountry,
city: Self.City = .{},
continent: Self.Continent = .{},
country: Self.Country = .{},
location: Self.Location = .{},
postal: Self.Postal = .{},
registered_country: Self.Country = .{},
represented_country: Self.RepresentedCountry = .{},
subdivisions: ?std.ArrayList(Self.Subdivision) = null,
traits: Self.Traits,

_arena: std.heap.ArenaAllocator,
traits: Self.Traits = .{},

const Self = @This();
pub const City = struct {
Expand Down Expand Up @@ -191,27 +146,6 @@ pub const Enterprise = struct {
static_ip_score: f64 = 0,
user_type: []const u8 = "",
};

pub fn init(allocator: std.mem.Allocator) Self {
const arena = std.heap.ArenaAllocator.init(allocator);

return .{
.city = .{},
.continent = .{},
.country = .{},
.location = .{},
.postal = .{},
.registered_country = .{},
.represented_country = .{},
.traits = .{},

._arena = arena,
};
}

pub fn deinit(self: *const Self) void {
self._arena.deinit();
}
};

/// ISP represents a record in the GeoIP2-ISP database, for example,
Expand Down
Loading