Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Data Types Reference

Matchy databases store arbitrary data with each entry using the DataValue type system.

Overview

DataValue is a Rust enum supporting these types:

  • Bool: Boolean values
  • Uint16: 16-bit unsigned integers
  • Uint32: 32-bit unsigned integers
  • Uint64: 64-bit unsigned integers
  • Uint128: 128-bit unsigned integers
  • Int32: 32-bit signed integers
  • Float: 32-bit floating point
  • Double: 64-bit floating point
  • String: UTF-8 text
  • Bytes: Arbitrary binary data
  • Array: Ordered list of values
  • Map: Key-value mappings
  • Timestamp: Unix epoch seconds (compact storage for ISO 8601 timestamps)

See Data Types for conceptual overview.

DataValue Enum

#![allow(unused)]
fn main() {
pub enum DataValue {
    Pointer(u32),
    String(String),
    Double(f64),
    Bytes(Vec<u8>),
    Uint16(u16),
    Uint32(u32),
    Map(HashMap<String, DataValue>),
    Int32(i32),
    Uint64(u64),
    Uint128(u128),
    Array(Vec<DataValue>),
    Bool(bool),
    Float(f32),
    Timestamp(i64),  // Unix epoch seconds
}
}

Creating Values

Direct Construction

#![allow(unused)]
fn main() {
use matchy::DataValue;

let bool_val = DataValue::Bool(true);
let int_val = DataValue::Uint32(42);
let str_val = DataValue::String("hello".to_string());
}

From JSON

#![allow(unused)]
fn main() {
let val: DataValue = serde_json::from_value(serde_json::json!(42))?;
let val: DataValue = serde_json::from_value(serde_json::json!("text"))?;
let val: DataValue = serde_json::from_value(serde_json::json!(true))?;
}

Working with Maps

Maps are the most common data structure:

#![allow(unused)]
fn main() {
use std::collections::HashMap;
use matchy::DataValue;

let mut data = HashMap::new();
data.insert("country".to_string(), DataValue::String("US".to_string()));
data.insert("asn".to_string(), DataValue::Uint32(15169));
data.insert("lat".to_string(), DataValue::Double(37.751));
data.insert("lon".to_string(), DataValue::Double(-97.822));
}

Working with Arrays

#![allow(unused)]
fn main() {
let tags = DataValue::Array(vec![
    DataValue::String("cdn".to_string()),
    DataValue::String("cloud".to_string()),
]);

data.insert("tags".to_string(), tags);
}

Working with Timestamps

Timestamps store Unix epoch seconds compactly (8 bytes vs 27-byte ISO 8601 strings):

#![allow(unused)]
fn main() {
use matchy::DataValue;

let first_seen = DataValue::Timestamp(1727891071);
data.insert("first_seen".to_string(), first_seen);
}

ISO 8601 strings in JSON input are automatically parsed into Timestamps during deserialization:

{
  "entry": "1.2.3.4",
  "first_seen": "2025-10-02T18:44:31Z"
}

When serialized back to JSON, Timestamps render as ISO 8601 strings for readability.

Nested Structures

#![allow(unused)]
fn main() {
let mut location = HashMap::new();
location.insert("city".to_string(), DataValue::String("Mountain View".to_string()));
location.insert("country".to_string(), DataValue::String("US".to_string()));

data.insert("location".to_string(), DataValue::Map(location));
}

Type Conversion

Extracting Values

#![allow(unused)]
fn main() {
match value {
    DataValue::String(s) => println!("String: {}", s),
    DataValue::Uint32(n) => println!("Number: {}", n),
    DataValue::Map(m) => {
        for (k, v) in m {
            println!("{}: {:?}", k, v);
        }
    }
    _ => println!("Other type"),
}
}

Helper Functions

#![allow(unused)]
fn main() {
fn get_string(val: &DataValue) -> Option<&str> {
    match val {
        DataValue::String(s) => Some(s),
        _ => None,
    }
}

fn get_u32(val: &DataValue) -> Option<u32> {
    match val {
        DataValue::Uint32(n) => Some(*n),
        _ => None,
    }
}
}

Complete Example

use matchy::{DatabaseBuilder, DataValue, MatchMode};
use std::collections::HashMap;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut builder = DatabaseBuilder::new(MatchMode::CaseInsensitive);
    
    // IP with rich data
    let mut ip_data = HashMap::new();
    ip_data.insert("country".to_string(), DataValue::String("US".to_string()));
    ip_data.insert("asn".to_string(), DataValue::Uint32(15169));
    ip_data.insert("tags".to_string(), DataValue::Array(vec![
        DataValue::String("datacenter".to_string()),
        DataValue::String("cloud".to_string()),
    ]));
    
    builder.add_entry("8.8.8.8", ip_data)?;
    
    // Pattern with metadata
    let mut pattern_data = HashMap::new();
    pattern_data.insert("category".to_string(), DataValue::String("search".to_string()));
    pattern_data.insert("priority".to_string(), DataValue::Uint16(100));
    
    builder.add_entry("*.google.com", pattern_data)?;
    
    let db_bytes = builder.build()?;
    std::fs::write("database.mxy", &db_bytes)?;
    
    Ok(())
}

Binary Format

DataValue types are serialized to the MMDB binary format:

DataValueMMDB TypeNotes
Boolboolean1 bit
Uint16uint162 bytes
Uint32uint324 bytes
Uint64uint648 bytes
Uint128uint12816 bytes
Int32int324 bytes
FloatfloatIEEE 754
DoubledoubleIEEE 754
Stringutf8_stringLength-prefixed
BytesbytesLength-prefixed
ArrayarrayRecursive
MapmapKey-value pairs
Timestampext 1288 bytes, Matchy extension

See Binary Format for encoding details.

Size Limits

  • Strings: Up to about 16.8 MB per encoded string
  • Bytes: Up to about 16.8 MB per encoded byte array
  • Arrays: Up to about 16.8 million encoded elements
  • Maps: Up to about 16.8 million encoded key-value pairs
  • Nesting: Validation rejects total nesting deeper than 64 levels

Performance

Data types have different serialization costs:

TypeCostNotes
Bool, integersO(1)Fixed size
Float, DoubleO(1)Fixed size
StringO(n)Length-dependent
BytesO(n)Length-dependent
ArrayO(n × m)n = length, m = element cost
MapO(n × m)n = entries, m = value cost

Prefer smaller types when possible:

  • Use Uint16 instead of Uint32 if values fit
  • Use Int32 instead of Double for integers
  • Avoid deep nesting

Serialization Example

#![allow(unused)]
fn main() {
use matchy::{Database, QueryResult, DataValue};

let db = Database::from("database.mxy").open()?;

if let Some(QueryResult::Ip { data: DataValue::Map(data), .. }) = db.lookup("8.8.8.8")? {
    // Extract specific fields
    if let Some(DataValue::String(country)) = data.get("country") {
        println!("Country: {}", country);
    }
    
    if let Some(DataValue::Uint32(asn)) = data.get("asn") {
        println!("ASN: {}", asn);
    }
    
    if let Some(DataValue::Array(tags)) = data.get("tags") {
        println!("Tags:");
        for tag in tags {
            if let DataValue::String(s) = tag {
                println!("  - {}", s);
            }
        }
    }
}
}

JSON Conversion

DataValue maps naturally to JSON:

#![allow(unused)]
fn main() {
use serde_json::json;

// DataValue to JSON (conceptual)
fn to_json(val: &DataValue) -> serde_json::Value {
    match val {
        DataValue::Bool(b) => json!(b),
        DataValue::Uint32(n) => json!(n),
        DataValue::String(s) => json!(s),
        DataValue::Array(arr) => {
            json!(arr.iter().map(to_json).collect::<Vec<_>>())
        }
        DataValue::Map(map) => {
            let obj: serde_json::Map<String, serde_json::Value> = 
                map.iter().map(|(k, v)| (k.clone(), to_json(v))).collect();
            json!(obj)
        }
        _ => json!(null),
    }
}
}

See Also