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

Building Databases from C

This page documents the C API functions for building Matchy databases.

Overview

Building a database in C involves three steps:

  1. Create a builder with matchy_builder_new()
  2. Add entries with matchy_builder_add_*() functions
  3. Build and save with matchy_builder_build()
#include <matchy.h>

matchy_builder_t *builder = matchy_builder_new();

matchy_builder_add_ip(builder, "192.0.2.1/32", NULL);
matchy_builder_add_pattern(builder, "*.example.com", NULL);

matchy_error_t err = matchy_builder_build(builder, "database.mxy");
matchy_builder_free(builder);

Builder Functions

matchy_builder_new

matchy_builder_t *matchy_builder_new(void);

Creates a new database builder.

Returns: Builder handle, or NULL on error

Example:

matchy_builder_t *builder = matchy_builder_new();
if (!builder) {
    fprintf(stderr, "Failed to create builder\n");
    return 1;
}

Memory: Caller must free with matchy_builder_free()

matchy_builder_free

void matchy_builder_free(matchy_builder_t *builder);

Frees a builder and all its resources.

Parameters:

  • builder - Builder to free (may be NULL)

Example:

matchy_builder_free(builder);
builder = NULL;  // Good practice

Note: After calling this, the builder handle must not be used.

Adding Entries

matchy_builder_add_ip

matchy_error_t matchy_builder_add_ip(
    matchy_builder_t *builder,
    const char *ip_cidr,
    const char *data_json
);

Adds an IP address or CIDR range to the database.

Parameters:

  • builder - Builder handle
  • ip_cidr - IP address or CIDR (e.g., "192.0.2.1" or "10.0.0.0/8")
  • data_json - Associated data as JSON string, or NULL

Returns: MATCHY_SUCCESS or error code

Example:

// IP without data
err = matchy_builder_add_ip(builder, "8.8.8.8", NULL);

// IP with data
err = matchy_builder_add_ip(builder, "192.0.2.1/32",
    "{\"country\":\"US\",\"asn\":15169}");

// CIDR range
err = matchy_builder_add_ip(builder, "10.0.0.0/8",
    "{\"type\":\"private\"}");

if (err != MATCHY_SUCCESS) {
    fprintf(stderr, "Failed to add IP\n");
}

Valid formats:

  • IPv4: "192.0.2.1", "10.0.0.0/8"
  • IPv6: "2001:db8::1", "2001:db8::/32"

matchy_builder_add_pattern

matchy_error_t matchy_builder_add_pattern(
    matchy_builder_t *builder,
    const char *pattern,
    const char *data_json
);

Adds a glob pattern to the database.

Parameters:

  • builder - Builder handle
  • pattern - Glob pattern string
  • data_json - Associated data as JSON, or NULL

Returns: MATCHY_SUCCESS or error code

Example:

// Simple wildcard
err = matchy_builder_add_pattern(builder, "*.google.com", NULL);

// With data
err = matchy_builder_add_pattern(builder, "mail.*",
    "{\"category\":\"email\",\"priority\":10}");

// Character class
err = matchy_builder_add_pattern(builder, "test[123].com", NULL);

if (err != MATCHY_SUCCESS) {
    fprintf(stderr, "Invalid pattern\n");
}

Pattern syntax:

  • * - Matches any characters
  • ? - Matches single character
  • [abc] - Matches any of a, b, c
  • [!abc] - Matches anything except a, b, c

matchy_builder_add_exact

matchy_error_t matchy_builder_add_exact(
    matchy_builder_t *builder,
    const char *string,
    const char *data_json
);

Adds an exact string match to the database.

Parameters:

  • builder - Builder handle
  • string - Exact string to match
  • data_json - Associated data as JSON, or NULL

Returns: MATCHY_SUCCESS or error code

Example:

// Exact match
err = matchy_builder_add_exact(builder, "example.com", NULL);

// With data
err = matchy_builder_add_exact(builder, "api.example.com",
    "{\"endpoint\":\"api\",\"rate_limit\":1000}");

if (err != MATCHY_SUCCESS) {
    fprintf(stderr, "Failed to add string\n");
}

Note: Exact matches are faster than patterns. Use them when possible.

Building the Database

matchy_builder_build

matchy_error_t matchy_builder_build(
    matchy_builder_t *builder,
    const char *output_path
);

Builds the database and writes it to a file.

Parameters:

  • builder - Builder handle
  • output_path - Path where database file will be written

Returns: MATCHY_SUCCESS or error code

Example:

err = matchy_builder_build(builder, "database.mxy");
if (err != MATCHY_SUCCESS) {
    fprintf(stderr, "Build failed\n");
    return 1;
}

printf("Database written to database.mxy\n");

Notes:

  • File is created or overwritten
  • Build process compiles all entries into optimized format
  • Builder can be reused after building

Complete Example

#include <matchy.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    matchy_error_t err;
    
    // Create builder
    matchy_builder_t *builder = matchy_builder_new();
    if (!builder) {
        fprintf(stderr, "Failed to create builder\n");
        return 1;
    }
    
    // Add IP entries
    err = matchy_builder_add_ip(builder, "192.0.2.1/32",
        "{\"country\":\"US\"}");
    if (err != MATCHY_SUCCESS) {
        fprintf(stderr, "Failed to add IP\n");
        goto cleanup;
    }
    
    err = matchy_builder_add_ip(builder, "10.0.0.0/8",
        "{\"type\":\"private\"}");
    if (err != MATCHY_SUCCESS) {
        fprintf(stderr, "Failed to add CIDR\n");
        goto cleanup;
    }
    
    // Add patterns
    err = matchy_builder_add_pattern(builder, "*.google.com",
        "{\"category\":\"search\"}");
    if (err != MATCHY_SUCCESS) {
        fprintf(stderr, "Failed to add pattern\n");
        goto cleanup;
    }
    
    err = matchy_builder_add_pattern(builder, "mail.*",
        "{\"category\":\"email\"}");
    if (err != MATCHY_SUCCESS) {
        fprintf(stderr, "Failed to add pattern\n");
        goto cleanup;
    }
    
    // Add exact strings
    err = matchy_builder_add_exact(builder, "example.com", NULL);
    if (err != MATCHY_SUCCESS) {
        fprintf(stderr, "Failed to add exact string\n");
        goto cleanup;
    }
    
    // Build database
    err = matchy_builder_build(builder, "my_database.mxy");
    if (err != MATCHY_SUCCESS) {
        fprintf(stderr, "Build failed\n");
        goto cleanup;
    }
    
    printf("✓ Database built successfully: my_database.mxy\n");
    
cleanup:
    matchy_builder_free(builder);
    return (err == MATCHY_SUCCESS) ? 0 : 1;
}

Compilation:

gcc -o build_db build_db.c -lmatchy
./build_db

Data Format

JSON Data Structure

Data is passed as JSON strings:

{
  "key1": "string_value",
  "key2": 42,
  "key3": 3.14,
  "key4": true,
  "key5": ["array", "values"],
  "key6": {
    "nested": "object"
  }
}

Supported types:

  • Strings
  • Numbers (integers, floats)
  • Booleans (true/false)
  • Arrays
  • Objects (nested maps)
  • null

Example with Complex Data

const char *geo_data = 
    "{"
    "  \"country\": \"US\","
    "  \"city\": \"Mountain View\","
    "  \"coords\": {"
    "    \"lat\": 37.386,"
    "    \"lon\": -122.084"
    "  },"
    "  \"tags\": [\"datacenter\", \"cloud\"]"
    "}";

matchy_builder_add_ip(builder, "8.8.8.8", geo_data);

Error Handling

Error Codes

CodeConstantMeaning
0MATCHY_SUCCESSOperation succeeded
-1MATCHY_ERROR_FILE_NOT_FOUNDFile not found
-2MATCHY_ERROR_INVALID_FORMATInvalid format
-3MATCHY_ERROR_CORRUPT_DATAData corruption
-4MATCHY_ERROR_OUT_OF_MEMORYOut of memory
-5MATCHY_ERROR_INVALID_PARAMInvalid parameter
-6MATCHY_ERROR_IOI/O error

Checking Errors

err = matchy_builder_add_ip(builder, ip, data);
if (err != MATCHY_SUCCESS) {
    switch (err) {
        case MATCHY_ERROR_INVALID_PARAM:
            fprintf(stderr, "Invalid IP address: %s\n", ip);
            break;
        case MATCHY_ERROR_OUT_OF_MEMORY:
            fprintf(stderr, "Out of memory\n");
            break;
        default:
            fprintf(stderr, "Error: %d\n", err);
    }
}

Best Practices

1. Always Check Returns

if (matchy_builder_add_ip(builder, ip, data) != MATCHY_SUCCESS) {
    // Handle error
}

2. Use Cleanup Labels

matchy_builder_t *builder = NULL;
matchy_error_t err;

builder = matchy_builder_new();
if (!builder) goto cleanup;

err = matchy_builder_add_ip(builder, "192.0.2.1", NULL);
if (err != MATCHY_SUCCESS) goto cleanup;

// ... more operations ...

cleanup:
    if (builder) matchy_builder_free(builder);
    return err;

3. Validate Input

if (!ip || strlen(ip) == 0) {
    fprintf(stderr, "Empty IP address\n");
    return MATCHY_ERROR_INVALID_PARAM;
}

err = matchy_builder_add_ip(builder, ip, data);

4. Batch Operations

const char *ips[] = {
    "192.0.2.1",
    "10.0.0.1",
    "172.16.0.1",
    NULL
};

for (int i = 0; ips[i]; i++) {
    err = matchy_builder_add_ip(builder, ips[i], NULL);
    if (err != MATCHY_SUCCESS) {
        fprintf(stderr, "Failed to add IP %s\n", ips[i]);
        // Continue or abort based on requirements
    }
}

Thread Safety

Builders are NOT thread-safe. Do not call builder functions from multiple threads simultaneously.

// WRONG: Don't do this
#pragma omp parallel for
for (int i = 0; i < n; i++) {
    matchy_builder_add_ip(builder, ips[i], NULL);  // Data race!
}

// RIGHT: Use a single thread for building
for (int i = 0; i < n; i++) {
    matchy_builder_add_ip(builder, ips[i], NULL);
}

Performance Tips

1. Pre-allocate When Possible

If you know approximately how many entries you'll add, building is more efficient.

2. Order Doesn't Matter

Entries can be added in any order - the builder optimizes internally.

3. Reuse Builders

Builders can be reused after building:

matchy_builder_build(builder, "db1.mxy");
// Builder is still valid, can add more entries
matchy_builder_add_ip(builder, "1.2.3.4", NULL);
matchy_builder_build(builder, "db2.mxy");

4. Build Time

Building time depends on entry count:

  • 1,000 entries: ~10ms
  • 10,000 entries: ~50ms
  • 100,000 entries: ~500ms
  • 1,000,000 entries: ~5s

See Also