Skip to content

xsawyerx/melian-perl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NAME

Melian - Perl client to the Melian cache

VERSION

version 0.007

SYNOPSIS

use Melian;

#-----------------------------------------------
# OO INTERFACE (easy, name-based)
#-----------------------------------------------
my $melian = Melian->new(
    'dsn' => 'unix:///tmp/melian.sock',
);

my $row = $melian->fetch_by_string_from( 'cats', 'name', 'Pixel' );

# Using IDs directly (faster)
my $row2 = $melian->fetch_by_string( 1, 1, 'Pixel' );

#-----------------------------------------------
# FUNCTIONAL INTERFACE (fastest)
#-----------------------------------------------
use Melian qw(fetch_by_string_with);

my $conn = Melian->create_connection(
    'dsn' => 'unix:///tmp/melian.sock',
);

# Must use IDs in functional mode
my $row3 = fetch_by_string_with( $conn, 1, 1, 'Pixel' );

DESCRIPTION

Melian is a tiny, fast, no-nonsense Perl client for the Melian cache server.

Melian (the server) keeps full table snapshots in memory. Lookups are done entirely inside the server and returned as compact binary payloads. Think of it as a super-fast read-only lookup service.

This module gives you two ways to talk to Melian:

Object-Oriented Mode (easy)

Use table names and column names:

$melian->fetch_by_string_from( 'cats', 'name', 'Pixel' );

Behind the scenes, the module:

  • Looks up the table ID in the schema
  • Looks up the column ID in the schema
  • Builds the binary request to the server
  • Reads the reply and decodes the binary row payload

This is the most convenient way. It is a little slower, because there is some name lookup and method dispatch each time.

Functional Mode (fastest)

Use this when you want raw speed:

my $conn = Melian->create_connection(...);
my $row = fetch_by_string_with( $conn, $table_id, $column_id, $key );

This avoids:

  • Method calls
  • Object construction
  • Lookup of table IDs and column IDs

It is simply: "write a request to the socket, read a response."

If you're chasing microseconds, this is the mode for you.

SCHEMA

Melian needs a schema so it knows which table IDs and column IDs correspond to which names. A schema looks something like:

people#0|60|id#0:int

Or:

people#0|60|id#0:int,cats#1|45|id#0:int;name#1:string

The format is simple:

  • table_name#table_id (multiple tables separated by ,)
  • |refresh_period_in_seconds
  • |column_name#column_id:column_type (multiple columns separated by ;)

You do NOT need to write this schema unless you want to. If you do not supply one, Melian will request it automatically from the server at startup.

If you provide a schema, it should match the schema set for the Melian server.

Accessing table and column IDs

Once the client is constructed:

my $schema = $melian->schema();

Each table entry contains:

{
    id      => 1,
    name    => "cats",
    period  => 45,
    indexes => [
        { id => 0, column => "id",   type => "int"    },
        { id => 1, column => "name", type => "string" },
    ],
}

If you use the functional API, you probably want to store them in constants:

use constant {
    'CAT_ID_TABLE'    => 1,
    'CAT_ID_COLUMN'   => 0, # integer column
    'CAT_NAME_COLUMN' => 1, # string column
};

This saves name lookups on every request.

BINARY PAYLOADS

Fetch calls return a hashref keyed by column name with native values by default:

{
    id   => 42,
    name => "Pixel",
}

If you need field types, use the *_with_fields methods to get:

{
    id   => { type => Melian::VALUE_INT64, value => 42 },
    name => { type => Melian::VALUE_BYTES, value => "Pixel" },
}

Type mappings:

  • VALUE_NULL -> undef
  • VALUE_INT64 -> Perl integer
  • VALUE_FLOAT64 -> Perl number
  • VALUE_DECIMAL -> ASCII string
  • VALUE_BOOL -> Perl boolean (0/1)
  • VALUE_BYTES -> raw bytes; enable decode_utf8 to decode to UTF-8 strings

If decode_utf8 is enabled and a BYTES field contains invalid UTF-8, decoding throws an exception.

METHODS

new(...)

my $melian = Melian->new(
    'dsn'         => 'unix:///tmp/melian.sock',
    'timeout'     => 1, # Only relevant for TCP/IP
    'schema_spec' => 'people#0|60|id#0:int',
);

Creates a new client and automatically loads the schema.

You may specify:

  • schema — already-parsed schema hashref

      my $melian = Melian->new(
          'schema' => {
              'id'      => 1,
              'name'    => 'cats',
              'period'  => 45,
              'indexes' => [
                  { 'id' => 0, 'column' => "id",   'type' => 'int'    },
                  { 'id' => 1, 'column' => "name", 'type' => 'string' },
              ],
          }
          ...
      );
    

    You would normally either provide a spec, a file, or nothing (to let Melian fetch it from the server).

  • schema_spec — inline schema description

      my $melian = Melian->new(
          'schema_spec' => 'cats#0|45|id#0:int;name#1:string',
          ...
      );
    
  • schema_file — path to JSON schema file

      my $melian = Melian->new(
          'schema_file' => '/etc/melian/schema.json',
          ...
      );
    
  • decode_utf8 — decode BYTES values as UTF-8 strings (default false)

  • nothing — Melian will ask the server for the schema

      my $melian = Melian->new(...);
    

connect()

$melian->connect();

Opens the underlying socket. Called automatically by new().

disconnect()

$melian->disconnect();

Closes the socket. Called automatically when instance goes out of scope, so you don't need to think about this.

fetch_raw($table_id, $column_id, $key_bytes)

my $encoded_data = $melian->fetch_raw( 0, 0, pack 'V', 20 );
my $encoded_data = $melian->fetch_raw( 0, 1, 'Pixel' );

Fetches a raw binary payload. Does NOT decode it.

You probably don't want to use this. See fetch_by_int(), fetch_by_int_from(), fetch_by_string(), and fetch_by_string_from() instead.

fetch_raw_from($table_name, $column_name, $key_bytes)

my $encoded_data = $melian->fetch_raw_from( 'cats', 'id', pack 'V', 20 );
my $encoded_data = $melian->fetch_raw_from( 'cats', 'name', 'Pixel' );

Same as above, but uses names instead of IDs.

You probably don't want to use this. See fetch_by_int(), fetch_by_int_from(), fetch_by_string(), and fetch_by_string_from() instead.

fetch_by_string($table_id, $column_id, $string_key)

my $hashref = $melian->fetch_by_string( 0, 1, 'Pixel' );

Fetches a binary row from the server and decodes into a Perl hashref with native values.

fetch_by_string_with_fields($table_id, $column_id, $string_key)

Same as fetch_by_string, but returns {type, value} pairs.

fetch_by_string_from($table_name, $column_name, $string_key)

my $hashref = $melian->fetch_by_string( 'cats', 'name', 'Pixel' );

Name-based version. Slightly slower than using IDs.

fetch_by_string_from_with_fields($table_name, $column_name, $string_key)

Name-based version that returns {type, value} pairs.

fetch_by_int($table_id, $column_id, $int)

my $hashref = $melian->fetch_by_int( 0, 0, 5 );

Same as fetch_by_string, but for integer-based column searches.

fetch_by_int_with_fields($table_id, $column_id, $int)

Same as fetch_by_int, but returns {type, value} pairs.

fetch_by_int_from($table_name, $column_name, $int)

my $hashref = $melian->fetch_by_int_from( 'cats', 'id', 5 );

Name-based version. Slightly slower than using IDs.

fetch_by_int_from_with_fields($table_name, $column_name, $int)

Name-based version that returns {type, value} pairs.

FUNCTIONS

These functions form the high-speed functional interface. They require a connection hashref returned by create_connection().

create_connection(%args)

my $conn = Melian->create_connection(%same_args_as_new);

Returns a connection hashref with a socket and options (including decode_utf8). Same options as new(), but no object is created.

fetch_raw_with($conn, $table_id, $column_id, $key_bytes)

my $encoded_data = fetch_raw_with( $conn, 0, 0, pack 'V', 20 );
my $encoded_data = fetch_raw_with( $conn, 0, 1, 'Pixel' );

Similar to fetch_raw() but uses the connection object you get back from create_connection().

You probably don't want to use this. See fetch_by_int_with() and fetch_by_string_with() instead.

fetch_by_string_with($conn, $table_id, $column_id, $string_key)

my $hashref = fetch_by_string_with( $conn, 0, 1, 'Pixel' );

Behaves like the corresponding OO method but skips object overhead and schema lookup.

fetch_by_string_with_fields($conn, $table_id, $column_id, $string_key)

Behaves like fetch_by_string_with, but returns {type, value} pairs.

fetch_by_int_with($conn, $table_id, $column_id, $int)

my $hashref = fetch_by_int_with( $conn, 0, 0, 5 );

Behaves like the corresponding OO method but skips object overhead and schema lookup.

fetch_by_int_with_fields($conn, $table_id, $column_id, $int)

Behaves like fetch_by_int_with, but returns {type, value} pairs.

table_of($schema, $table_name)

my $table_data = table_of( $schema, 'cats' );
my $table_id   = $table_data->{'id'};

Fetches the table information from the schema.

column_id_of($table_data, $column_name)

my $table_data = table_of( $schema, 'cats' );
my $column_id  = column_id_of( $table_data, 'name' );

Fetches the ID of a column from a given table metadata.

load_schema_from_describe($conn)

my $schema = load_schema_from_describe($conn);

This helps you retrieve the schema if you're using the functional interface. You can then use this schema to determine table and column IDs.

PERFORMANCE NOTES

  • OO mode is convenient but has overhead from name lookups and method calls.
  • ID-based OO mode is faster because it skips name lookups.
  • Functional mode is the fastest and is roughly equivalent to calling syswrite and sysread directly in Perl.
  • If you care about performance, use table and column IDs with the functional interface.

AUTHORS

  • Sawyer X
  • Gonzalo Diethelm

COPYRIGHT AND LICENSE

This software is Copyright (c) 2025 by Sawyer X, Gonzalo Diethelm.

This is free software, licensed under:

The MIT (X11) License

About

Perl client to the Melian cache

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages