Use Cases for MRuby in Database Contexts | ArangoDB 2012
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
Get the latest tutorials, blog posts and news: