“Use-Cases for MRuby in a Database like ArangoDB” or “Can MRuby be the next PL/SQL?”

mruby Leave a comment

In the relational world, PL/SQL is used to store business/application logic inside the relational database. The same movement is currently happening in the NoSQL. Redis for example uses LUA script in its newest version (2.6), to allow developers to tweak Redis. There a lot of use-cases for a programming language in document-stores. Programming languages are used in various document stores like ArangoDB, CouchDB, MongoDB, or VoltDB. In my opinion in the following use-cases a suitable language like Ruby will be most handy.

Transactions: Quite a lot NoSQL databases, that implement transaction, use scripts to do so. While transaction in a RDBMS were long running processes, transactions in NoSQL database are short-running computations or modification. For example, Redis requires a complicated optimistic locking to simulate transactions. The same effect can much easier achieved with small script.

Business Logic: Document stores and also most graph databases store objects aka documents as JSON objects. One of the anti-patterns is to store an age field in these objects. It is very easy to compute the age from the birthday in the client. But even with such a simple example. there  are pit-falls. What is the correct time-zone? How to handle a “is 18-year check” if the birthday is the 29.Feb? Instead of handling these corner-cases in each different client, it is much easier to implement them once and for all in the server.

Permission: With complex queries, permission handling can be a performance disaster. One easy solution is to wrap the query in the a script and handle permission there.

Graph Traversal: In order to implement complex graph queries, one either needs ascii arts or scripts to describe the traversal.

Different databases implement these use-cases using different languages like Lua, Erlang, or JavaScript. With this blog I would like to promote Ruby as a possible candidate for such tasks.

Using MRuby within ArangoDB

ArangoDB, like CouchDB, has a HTTP interface and uses JSON for document exchange. The internal Http-Server of ArangoDB uses data structures for request and responses that loosely resambles the WEBrick interface of Ruby.

I tried to implement the age example with ArangoDB using MRuby. The first step is to define the HttpResponse and HttpRequest – as @tisba pointed out, it will be much more Ruby-like when using attribute_reader, see here. I will rewrite this as soon as this issue with MRuby has been solved.

module Arango
  class HttpRequest
    def body()
      return @body
    end
 
    def headers()
      return @headers
    end
 
    def parameters()
      return @parameters
    end
 
    def request_type()
      return @request_type
    end
 
    def suffix()
      return @suffix
    end
  end
end

and

module Arango
  class HttpResponse
    def content_type()
      return @content_type
    end
 
    def content_type=(type)
      @content_type = type
    end
 
    def body()
      return @body
    end
 
    def body=(text)
      @body = text.to_s
    end
    def status()
      return @status
    end
 
    def status=(code)
      @status = code.to_i
    end
  end
end

The AbstractServlet is also straight forward.

module Arango
  class AbstractServlet
    @@HTTP_OK                   = 200
    @@HTTP_CREATED              = 201
 
    def service(req, res)
      method = req.request_type
 
      if method == "GET"
        self.do_GET(req, res)
      elsif method == "PUT"
        self.do_PUT(req, res)
      elsif method == "POST"
        self.do_POST(req, res)
      elsif method == "DELETE"
        self.do_DELETE(req, res)
      elsif method == "HEAD"
        self.do_HEAD(req, res)
      else
        generate_unknown_method(req, res, method)
      end
    end
 
    def do_GET(req, res)
      res.status = @@HTTP_METHOD_NOT_ALLOWED
    end
 
    def do_PUT(req, res)
      res.status = @@HTTP_METHOD_NOT_ALLOWED
    end
 
    def do_POST(req, res)
      res.status = @@HTTP_METHOD_NOT_ALLOWED
    end
 
    def do_DELETE(req, res)
      res.status = @@HTTP_METHOD_NOT_ALLOWED
    end
 
    def do_HEAD(req, res)
      res.status = @@HTTP_METHOD_NOT_ALLOWED
    end
 
    def generate_unknown_method(req, res, method)
      res.status = @@HTTP_METHOD_NOT_ALLOWED
    end
  end
end

The missing piece is the C++/Ruby bridge. This bridge takes a C++ request objects, converts it into a Ruby object, calls the service method, takes the response object and converts it back to C++. The complete source code can be found at https://github.com/triAGENS/ArangoDB/tree/devel/arangod/MRServer.

Hallo World

The first script to try is always the “hallo world”:

class VersionHandler < Arango::AbstractServlet
 
  def do_GET request, response
    response.status = 200
    response.content_type = 'text/plain'
    response.body = 'Hallo, World!'
  end

Arango::HttpServer.mount “/_ruby/version”, VersionHandler

Using curl we now get:

> curl -v  http://localhost:8529/_ruby/version
> GET /_ruby/version HTTP/1.1
> User-Agent: curl/7.22.0
> Host: localhost:8529
> Accept: */*
>
< HTTP/1.1 200 OK
< connection: Keep-Alive
< content-type: text/plain
< server: triagens GmbH High-Performance HTTP Server
< content-length: 13
<
* Connection #0 to host localhost left intact
* Closing connection #0
Hallo, World!

Age Example

The age example is almost as simple – no error handling :-). Assume that the collection containing the users is called “users” and the birthday is in attribute of the same name. The call to get the user with id 123456 is “/_ruby/user/123456″.

class UserHandler < Arango::AbstractServlet
  def do_GET request, response
    uid = request.suffix[0]
    user = db.users.document(uid)
    user.age = age_of_user(user)
    response.status = 200
    response.content_type = 'application/json'
    response.body = to_json(user)
  end
end
 
Arango::HttpServer.mount "/_ruby/user", UserHandler

About Frank Celler

Frank is both entrepreneur and backend developer, developing mostly memory databases for two decades. He is the lead developer of ArangoDB and co-founder of triAGENS. Try to challenge Frank asking him questions on C, C++ and MRuby. Besides Frank organizes Cologne’s nosql group & nosql conferences.