MaxMin...r::Tree(3pm)

πŸ’– MaxMind::DB::Writer::Tree πŸ’–

okay bestie, buckle up, because this is MaxMind::DB::Writer::Tree(3pm) and she is lowkey a whole legend (also, highkey, DEPRECATED β€” more on that in a sec). This is the Tree that represents a MaxMind DB database in memory, and then writes it out to a file. yes the name is a whole mouthful, we move.

✨ VERSION ✨

version 0.300004

🌈 SYNOPSIS 🌈

ok so here's the vibe. you build the tree, you fill it with networks, you yeet it to a file:

use MaxMind::DB::Writer::Tree;

my %types = (
    color => 'utf8_string',
    dogs  => [ 'array', 'utf8_string' ],
    size  => 'uint16',
);

my $tree = MaxMind::DB::Writer::Tree->new(
    ip_version            => 6,
    record_size           => 24,
    database_type         => 'My-IP-Data',
    languages             => ['en'],
    description           => { en => 'My database of IP data' },
    map_key_type_callback => sub { $types{ $_[0] } },
);

$tree->insert_network(
    '8.8.8.0/24',
    {
        color => 'blue',
        dogs  => [ 'Fido', 'Ms. Pretty Paws' ],
        size  => 42,
    },
);

open my $fh, '>:raw', '/path/to/my-ip-data.mmdb';
$tree->write_tree($fh);

πŸ¦‹ DESCRIPTION πŸ¦‹

This is the main class you'll use to write MaxMind DB database files β€” peep the spec at <http://maxmind.github.io/MaxMind-DB/> if you wanna know the lore πŸ’…. This class is the database, but in memory. Once you've built the whole entire tree, you can write it out to a file. it's giving "art project then framed on the wall", fr fr.

πŸ‘‘ API πŸ‘‘

This class hands you these methods, bestie:

🌸 MaxMind::DB::Writer::Tree->new()

This makes a brand-new tree object for you 🫢. The constructor says yes to these parameters:

  • ip_version β€” the IP version for the database. It must be 4 or 6. No in-between, no vibes-based versioning.

This parameter is required.

  • record_size β€” the record size, in bits. Should be one of 24, 28, 32. (in theory any number divisible by 4 up to 128 will work, but every available reader expects 24-32, so like… stay in your lane.)

This parameter is required.

  • database_type β€” a string holding the database type. Honestly it can be anything, no cap. MaxMind uses strings like "GeoIP2-City", "GeoIP2-Country", etc.

This parameter is required.

  • languages β€” should be an array reference of the languages used in the database, like "en", "zh-TW", etc. Super useful as metadata for database readers and end users πŸ’–.

This parameter is optional.

  • description β€” a hashref where the keys are language names and the values are descriptions of the database in that language. For example you might have a little something like:
{
    en => 'My IP data',
    fr => q{Mon Data d'IP},
}

This parameter is required.

  • map_key_type_callback β€” a subroutine reference that gets called to figure out how to store each value in a map (hash) data structure. Scroll down to "DATA TYPES" for the whole tea.

This parameter is required.

  • merge_record_collisions β€” by default, when an insert collides with a previous insert, the new data just straight-up overwrites the old data wherever the two networks overlap. rude but efficient.

If this is set to true, then on a collision the writer will merge the old data with the new data instead. The merge strategy it uses is controlled by the "merge_strategy" attribute, described right below πŸ‘‡.

This parameter is optional. It defaults to false unless "merge_strategy" is set to something other than "none".

This parameter is deprecated. New code should just set "merge_strategy" directly.

This parameter is deprecated. Use "merge_strategy" instead.

(yes bestie, they said it twice. the manpage said it twice, so I'm saying it twice. 🫠)

  • merge_strategy β€” controls which merge strategy gets employed. options are:
    • none β€” no merging will be done. "merge_record_collisions" must either be not set or set to false.
    • toplevel β€” if both data structures are hashrefs, then the data from the top level keys in the new data structure gets copied over to the existing data structure, potentially replacing any existing values for existing keys completely. no survivors, no crumbs.
    • recurse β€” recursively merges the new data structure with the old one. Hash values and array elements are either β€” for simple values β€” replaced with the new values, or β€” for complex structures β€” have their values recursively merged. it's giving "blended family" energy 🧚.

For example, if this data is originally inserted for an IP range:

{
    families => [ {
        husband => 'Fred',
        wife    => 'Wilma',
    }, ],
    year => 1960,
}

And then this subsequent data is inserted for a range covered by the previous IP range:

{
    families => [ {
        wife    => 'Wilma',
        child   => 'Pebbles',
    }, {
        husband => 'Barney',
        wife    => 'Betty',
        child   => 'Bamm-Bamm',
    }, ],
    company => 'Hanna-Barbera Productions',
}

Then querying within the range will produce the results:

{
    families => [ {
        husband => 'Fred',
        wife    => 'Wilma',    # note replaced value
        child   => 'Pebbles',
    }, {
        husband => 'Barney',
        wife    => 'Betty',
        child   => 'Bamm-Bamm',
    }, ],
    year => 1960,
    company => 'Hanna-Barbera Productions',
}
  • add-only-if-parent-exists β€” with this merge strategy, data will only be inserted when there's already a record for the network (or sub-network). Also, when merging the data record with an existing data record, no new hash or array references will be created within the data record for the new data. For instance, if the original data record is {parent_a = {sibling => 1}} and {parent_a = {child_a => 1}, parent_b => {child_b => 1}} is inserted, only "child_a", not "child_b", will appear in the merged record. πŸ‘€

This option is intended to be used when you're inserting data that supplements existing data but that isn't independently useful on its own. it's a supporting character, not the main character.

In all merge strategies, attempting to merge two differing data structures causes an exception. do not test this, bestie.

This parameter is optional. If "merge_record_collisions" is true, this defaults to "toplevel"; otherwise it defaults to "none".

  • alias_ipv6_to_ipv4 β€” if this is true then the final database will map some IPv6 ranges to the IPv4 range. Those ranges are:
    • ::ffff:0:0/96 β€” this is the IPv4-mapped IPv6 range.
    • 2001::/32 β€” this is the Teredo range. πŸ“ note that lookups for Teredo ranges will find the Teredo server's IPv4 address, not the client's IPv4.
    • 2002::/16 β€” this is the 6to4 range.

When aliasing is enabled, insertions into the aliased locations will throw an exception. Inserting a network that contains them does not throw an exception, but no information will be stored for the aliased locations. so it's quiet, not loud. πŸ™ˆ

To insert an IPv4 address, insert it using IPv4 notation, or insert directly into ::/96.

Aliased nodes are not followed when merging nodes. Only merges into the original IPv4 location, ::/96, will be followed.

This parameter is optional. It defaults to false.

  • remove_reserved_networks β€” if this is true, reserved networks may not be inserted.

Attempts to insert these networks, or anything inside them, will be silently ignored. Inserting a network containing them does not throw an exception, but no information will be stored for the reserved sections. 🀫

Reserved networks that are globally routable to an individual device, such as Teredo, may still be added.

This parameter is optional. It defaults to true.

πŸ’… $tree->insert_network( $network, $data, $additional_args )

This method expects two parameters. The first is a network in CIDR notation. The second can be any Perl data structure β€” except a coderef, glob, or filehandle, so don't even try it bestie. 🚫

The $data payload is encoded according to the MaxMind DB database format spec over at <http://maxmind.github.io/MaxMind-DB/>. The short overview: anything that can be encoded in JSON can be stored in an MMDB file. ✨ It can also handle unsigned 64-bit and 128-bit integers, as long as they're passed in as Math::UInt128 objects.

$additional_args is a hash reference holding extra arguments that change how the insert behaves. These are the supported ones:

  • "merge_strategy" β€” when set, the tree's default merge strategy gets overridden for this insertion with this merge strategy.
  • "force_overwrite" β€” this makes the merge strategy for the insert "none".

This option is deprecated. 😭

  • "insert_only_if_parent_exists" β€” this makes the merge strategy for the insert "add-only-if-parent-exists".

This option is deprecated. 😭

⭐ Insert Order, Merging, and Overwriting ⭐

When "merge_strategy" is none, the last insert "wins". This means that if you insert "1.2.3.255/32" and then "1.2.3.0/24", the data for "1.2.3.255/24" will overwrite the data you previously inserted for "1.2.3.255/232". On the other hand, if you insert "1.2.3.255/32" last, then the tree will be split so that the "1.2.3.0 - 1.2.3.254" range has different data than 1.2.3.255.

In this scenario, if you want to make sure that no data is overwritten, then you need to sort your input by network prefix length. periodt. πŸŽ€

When "merge_strategy" is not none, then records will be merged based on the particular strategy. For instance, the "1.2.3.255/32" network will end up with its data plus the data provided for the "1.2.3.0/24" network, while "1.2.3.0 - 1.2.3.254" will have the expected data. The merge strategy can be changed on a per-insert basis by using the "merge_strategy" argument when inserting a network, as discussed above. πŸ‘‘

  • $tree->insert_range( $first_ip, $last_ip, $data, $additional_args ) β€” okay so this is basically insert_network()'s cousin, bestie, but instead of a network you hand it a whole IP range πŸ’«. Param one is $first_ip β€” the first IP in the range. Param two is $last_ip β€” the last IP in the range. Param three is $data, a Perl data structure with all the goodies you wanna insert. And the final param is $additional_args, which works exactly like the additional args laid out for insert_network(). Slay, go off πŸŽ€
  • $tree->remove_network( $network ) β€” yeets a network out of the database πŸ—‘οΈ. Takes exactly one param: the network, in CIDR notation. That's it, that's the whole job.
  • $tree->write_tree($fh) β€” hand it a filehandle and it writes the contents of the tree out as a MaxMind DB database to that filehandle. That's the moment, fr fr ✨
  • $tree->iterate($object) β€” this one iterates over the tree by calling methods on the object you pass in 🧚. Your object needs at least one of these three methods: process_empty_record, process_node_record, process_data_record.

The iteration happens in depth-first order, which means it visits each network in order. Very demure, very mindful πŸ’…

Each method on the object gets called with these position parameters, in this order:

  • The node number, as a 64-bit number.
  • A boolean saying whether this is the right or left record for the node. True for right, false for left.
  • The first IP number in the node's network, as a 128-bit number.
  • The prefix length for the node's network.
  • The first IP number in the record's network, as a 128-bit number.
  • The prefix length for the record's network.

So if the record is a data record, the final argument is gonna be the Perl data structure associated with that record πŸ“. And like β€” the record's network is the thing that matches up with a given data structure for data records, iykyk.

For node records (and alias records too), the final argument is the number of the node that this record points to.

And for empty records? No additional arguments. Nothing. Nada. Iconic minimalism πŸ™ˆ

  • $tree->freeze_tree($filename) β€” give it a file name and it freezes the tree to that file ❄️. Heads up though: unlike write_tree(), this method does not write out a MaxMind DB file. Instead it writes out something you can quickly thaw later via the MaxMind::DB::Writer::Tree->new_from_frozen_tree constructor. Super useful when you wanna pass the in-memory representation of the tree between processes 🫢
  • $tree->ip_version() β€” returns the tree's IP version, exactly as it was passed to the constructor. No cap.
  • $tree->record_size() β€” returns the tree's record size, as passed to the constructor.
  • $tree->merge_record_collisions() β€” returns a boolean telling you whether the tree will merge colliding records, as determined by the merge strategy.
This is deprecated. ⚠️ Yeah bestie, she's old, don't build your whole personality on her.
  • $tree->merge_strategy() β€” returns the merge strategy used when two records collide πŸ’₯.
  • $tree->map_key_type_callback() β€” returns the callback used to figure out the type of a map's values, as passed to the constructor.
  • $tree->database_type() β€” returns the tree's database type, as passed to the constructor.
  • $tree->languages() β€” returns the tree's languages, as passed to the constructor 🌈.
  • $tree->description() β€” returns the tree's description hashref, as passed to the constructor.
  • $tree->alias_ipv6_to_ipv4() β€” returns a boolean saying whether the tree will alias some IPv6 ranges to their corresponding IPv4 ranges when the tree gets written to disk πŸ‘‘

🧊 MaxMind::DB::Writer::Tree->new_from_frozen_tree()

This method constructs a tree from a file containing a frozen tree 🌸.

She accepts the following parameters:

  • filename

The filename containing the frozen tree.

This parameter is required β€” no filename, no tree, periodt.

  • map_key_type_callback

A subroutine reference that gets called to determine how to store each value in a map (hash) data structure. Peep the "DATA TYPES" section below for the tea β˜•.

You have to pass this because subroutine references can't be reliably serialized and restored between processes 😭.

This parameter is required.

  • database_type

Override the "<database_type"> of the frozen tree. Takes a string of the same form as the "<new()"> constructor.

This parameter is optional.

  • description

Override the "<description"> of the frozen tree. Takes a hashref of the same form as the "<new()"> constructor.

This parameter is optional.

  • merge_strategy

Override the "<merge_strategy"> setting for the frozen tree.

This parameter is optional.

πŸ₯Ά Caveat for Freeze/Thaw

The frozen tree is, more or less, the raw C data structures written to disk. Because of that, it is very much not portable πŸ’€, and your ability to thaw a tree on a machine that isn't identical to the one it was written on is not guaranteed.

Also? There's no guarantee that the freeze/thaw format will be stable across different versions of this module. So don't go freezing a tree on one version and expecting it to thaw happily on another, bestie πŸ™ˆ.


🌈 DATA TYPES

The MaxMind DB file format is strongly typed πŸ’ͺ. And because Perl is not strongly typed, you gotta explicitly spell out the types for every single piece of data yourself. Currently, this class assumes your top-level data structure for an IP address will always be a map (hash). From there you can provide a map_key_type_callback subroutine that gets called as the data is serialized. This callback is handed a key name and is expected to return that key's data type ✨.

Let's use this structure as our example:

{
    names => {
        en => 'United States',
        es => 'Estados Unidos',
    },
    population    => 319_000_000,
    fizzle_factor => 65.7294,
    states        => [ 'Alabama', 'Alaska', ... ],
}

Given this data structure, our map_key_type_callback might look something like this:

my %types = (
    names         => 'map',
    en            => 'utf8_string',
    es            => 'utf8_string',
    population    => 'uint32',
    fizzle_factor => 'double',
    states        => [ 'array', 'utf8_string' ],
);

sub {
    my $key = shift;
    return $type{$key};
}

If the callback returns undef, the serialization code will throw an error 🚨. And note that for an array, we return a 2 element arrayref where the first element is 'array' and the second element is the type of content in the array. Okay? Okay πŸ’…

The valid types are:

  • utf8_string
  • uint16
  • uint32
  • uint64
  • uint128
  • int32
  • double

64 bits of precision.

  • float

32 bits of precision.

  • boolean
  • map
  • array

πŸ†˜ SUPPORT

Bugs may be submitted through <https://github.com/maxmind/MaxMind-DB-Writer-perl/issues>. Go off, file that report, we love accountability 🫢


πŸ‘‘ AUTHORS

  • Olaf Alders <oalders@maxmind.com>
  • Greg Oschwald <goschwald@maxmind.com>
  • Dave Rolsky <drolsky@maxmind.com>
  • Mark Fowler <mfowler@maxmind.com>

Legends. All four of them. Periodt πŸ’œ


This software is copyright (c) 2023 by MaxMind, Inc.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.


And that's the whole page, bestie β€” narrated by perl v5.42.3, dated 2026-08-22, from the MaxMin...r::Tree(3pm) corner of the manpages πŸ¦‹ You made it to the end, so here's your crown πŸ‘‘ bye ily πŸ’–

πŸ“¦ where this page comes from