Skip to content
This repository

Use ElasticSearch as a NoSQL database in Perl

Fetching latest commit…

Cannot retrieve the latest commit at this time

README.pod

You need a recent version of Java installed, then download the current stable release of ElasticSearch from http://www.elasticsearch.org/download/. For instance:

    curl -L -O https://github.com/downloads/elasticsearch/elasticsearch/elasticsearch-0.19.11.tar.gz
    tar -xzf elasticsearch-0.19.11.tar.gz

Note: The minimum required version of ElasticSearch is 0.19.11.

Use your favourite CPAN installer:

    cpanm Elastic::Model

See "TEST SUITE" in Elastic::Manual for how to run the full test suite against a local ElasticSearch cluster.

    cd elasticsearch-0.19.11/
    ./bin/elasticsearch -f      # -f starts the server in the foreground

You now have a running ElasticSearch cluster with one node. You can test that it is running with:

    curl http://localhost:9200/?pretty=true

See http://www.elasticsearch.org/tutorials/ and http://www.elasticsearch.org/guide/reference/setup/ for more information about installing and configuring ElasticSearch.

First set up a simple model. The model handles the relationship between your classes and ElasticSearch.

    package MyApp;

    use Elastic::Model;

    has_namespace 'myapp' => {
        user    => 'MyApp::User'
    };

    no Elastic::Model;

    1;

Your model must define at least one namespace, which tells Elastic::Model which type (like a table in a DB) should be handled by which of your classes. So the above declaration says:

"For all indices which belong to namespace myapp, objects of class MyApp::User will be stored under the type user in ElasticSearch."

    package MyApp::User;

    use Elastic::Doc;

    use DateTime();

    has 'name' => (
        is  => 'rw',
        isa => 'Str',
    );

    has 'email' => (
        is  => 'rw',
        isa => 'Str',
    );

    has 'created' => (
        is      => 'ro',
        isa     => 'DateTime',
        default => sub { DateTime->now }
    );

    no Elastic::Doc;

    1;

This simple Moose class only changes "use Moose;" to "use Elastic::Doc;". At the moment we're not configuring anything else. Thanks to Moose's introspection, we have enough information to setup everything we need.

    #!/bin/env perl

    use strict;
    use warnings;
    use MyApp();

    my $model  = MyApp->new();

This creates an instance of your model, with a connection to a local ElasticSearch cluster. The last line is the equivalent of:

    use ElasticSearch();
    my $es     = ElasticSearch->new( servers => 'localhost:9200' );
    my $model  = MyApp->new( es => $es );

Before we get started, we need to create an index (like a database in a relational DB) in ElasticSearch.

    $model->namespace('myapp')->index->create();

This has created index myapp, which contains type user (where a type is like a table in a database). It has also configured the type's mapping (which is like the schema or column definition).

Our index is now ready to use.

Before we can save or retrieve objects/documents from ElasticSearch, we need a domain:

A domain is like a database handle. It allows us to talk to a particular index or alias. (An alias is like a shortcut which points at one or more indices.)

    $domain = $model->domain('myapp');

See Elastic::Manual::Scaling for more about how to use aliases.

Normally, you would create an object with:

    my $user = MyApp::User->new(...)

but to use all of the magic of Elastic::Model, you must create your object via the $domain object:

    my $user    = $domain->new_doc(
        user => {                           # $type => \%args_to_new
            id    => 1,                     # auto-generated if not provided
            name  => 'Clinton',
            email => 'clint@domain.com',
        }
    );

    $user->save;                            # save to ElasticSearch

Now, we can retrieve the user object from ElasticSearch, using the type and id:

    $user = $domain->get( user => 1 );

    say $user->name;
    # Clinton

    $user->email( 'clinton@domain.com' );

Elastic::Model keeps track of what attributes have been changed, plus their original value:

    say $user->has_changed;
    # 1

    say $user->has_changed('email');
    # 1

    say $user->old_value('email');
    # 'clint@domain.com';

The UID (unique ID) of the object tracks (amongst other things) the current version number. ElasticSearch uses this version number to avoid overwriting changes that have been made by another process (see Optimistic Currency Control).

    say $user->uid->version;
    # 1

The version number is incremented each time a changed object is saved.

    $user->save;

    say $user->uid->version;
    # 2

    say $user->has_changed;
    # 0

By default, everything in ElasticSearch is indexed and searchable. You can search across one index or many indices, one type or many types.

In order to query the objects stored in ElasticSearch, you need a view. Views are reusable, so you might create views like $recent_users, $approved_comments etc.

You can create a view from your $domain object, in which case the view will be limited to just that domain:

    $view = $domain->view;              # limited to index 'myapp';

To create a view which queries multiple domains, you could do:

    $view = $model->view->domain(['index_1', 'alias_2']);

Or to query all domains known to your model:

    $view = $model->view;

When setting an attribute on a view, a cloned instance of the old view is returned, meaning that you can use one view to derive another:

    $all   = $domain->view;                         # all types in $domain
    $users = $all->type('user');                    # type 'user' in index $domain
    $clint = $users->queryb({name => 'clinton'});   # users whose name is 'clinton'

Queries can be specified using the standard ElasticSearch query DSL or with the more Perlish more compact ElasticSearch::SearchBuilder syntax.

Standard query DSL:

    $search = $view->query( { text =>  { name    => 'clinton' }})
                   ->filter({ range => { created => { gt => '2012-01-01' }}});

SearchBuilder syntax:

    $search = $view->queryb(  { name    => 'clinton'             })
                   ->filterb( { created => { gt => '2012-01-01' }});

Once you have defined your view, you call a search method (eg search()) which performs the search and returns a Results object.

    my $results = $search->search;
    say "Total results found: ".$results->total;

    while (my $doc = $results->next_doc) {
        say $doc->name
    }

Views can also be used to return highlighted results, and facets, which provide aggregated results, much like GROUP-BY functions in SQL, for instance, the most popular terms, or the number of posting per day.

Something went wrong with that request. Please try again.