Functions - aemadrid/orientdb GitHub Wiki

Functions

<wiki:toc max_depth="2" />

Since 1.2.0 OrientDB supports Functions. A Function is an executable unit of code that can takes parameters and returns a result. Using the Functions you can do Functional programming where logic and data are all together in a central place. Functions are similar to the Stored Procedures of RDBMS.

These the features of OrientDB Functions:

  • are persistent
  • can be written in Javascript, but Ruby, Scala, Java and other languages are coming
  • can be executed via Java, REST and Studio
  • can call each other
  • supports recursion
  • have automatic mapping of parameters by position and name
  • plugins can inject new objects to being used by functions

Create your first function

To start using Functions the simplest way is using the Studio. Open the database and go to the "Functions" panel. Then write as name "sum", add 2 parameters named "a" and "b" and now write the following code in the text area:

    return a + b;

Then click on the "Save" button. Well, your function has been saved and will appear on the left between the available functions. Now let's go to test it. On the bottom you will find your brand new function with 2 empty boxes. That's the place where to insert run-time parameter. Write 3 and 5 as parameters and click "Execute" to see the result. "8.0" will appear in the output box below.

Where my functions are saved?

Functions are saved in the database using the OFunction class and the following properties:

  • name, as the name of the function
  • code, as the code to execute
  • parameters, as an optional EMBEDDEDLIST of String containing the parameter names if any
  • idempotent, tells if the function is idempotent, namely if it changes the database. Read-only functions are idempotent. This is needed to avoid calling non-idempotent functions using the HTTP GET method

Concurrent editing

Since OrientDB uses 1 record per function, the MVCC mechanism is used against concurrent record updates. This means that concurrent developers can work on functions all together and in case anyone update the function you're saving an error is thrown.

Usage

Usage via Java API

Using OrientDB's functions from Java is straightforward. First get the reference to the Function Manager, get the right function and execute it passing the parameters if any. In this example parameters are passed by position:

    ODatabaseDocumentTx db = new ODatabaseDocumentTx("local:/tmp/db");
    db.open("admin", "admin");
    OFunction sum = db.getMetadata().getFunctionLibrary().getFunction("sum");
    Number result = sum.execute(3, 5);

You can execute functions passing parameters by name:

    Map<String,Object> params = new HashMap<String,Object>();
    params.put("a", 3);
    params.put("b", 5);
    Number result = sum.execute(params);

Usage via HTTP REST

Each function is exposed as a REST service allowing the receiving of parameters. parameters are passed by position.

Below how to execute the "sum" function created before:

    http://localhost:2480/function/demo/sum/3/5

This will return a HTTP 202 OK with text:

    8.0

You can call with HTTP GET method only functions declared as "idempotent". Use HTTP POST to call any functions.

For more information look at HTTP REST protocol. To know how to write server-side function for web applications look at Server-Side functions.

Access to the databases from Functions

OrientDB always binds special variable to use OrientDB services from inside the functions. The most important are:

  • db, that is the current document database instance
  • gdb, that is the current graph database instance

You can call methods against those objects to interact with the database.

Execute a query

    return db.query("select name from ouser");

Execute a query with external parameters

Create the new function with name "getyUserRoles" with the parameter "user". Then write this code:

    return db.query("select roles from ouser where name = ?", name );

The name parameter is bound as variable in Javascript! You can use to build your query.

Write your own repository classes

Functions are the perfect place where to write the logic your application access to the database. You could adopt a DDD approach letting to the function to work as Repositories or DAO.

This mechanism it's very powerful allowing to avoid re-deploying an application if database/query change. Furthermore each function is published and reachable via HTTP REST protocol allowing the automatic creation of a RESTful service.

Example

Below an example of functions to build a repository for OUser records.

function user_getAll(){

    return db.query("select from ouser" );

}

function user_getByName( name ){

    return db.query("select from ouser where name = ?", name );

}

function user_getAdmin(){

    return user_getByName("admin");

}

function user_create( name, role ){

    var role = db.query("select from ORole where name = ?", roleName); 
    if( role == null ){
      response.send(404, "Role name not found", "text/plain", "Error: role name not found" );
    } else {
      
      db.begin();
      try{
        var result = db.save({ "@class" : "OUser", name : "Luca", password : "Luc4", roles : role});
        db.commit();
        return result;
      }catch ( err ){
        db.rollback();
        response.send(500, "Error on creating new user", "text/plain", err.toString() );
      }
    }

}

Recursive calls

Create the new function with name "factorial" with the parameter "n". Then write this code:

    if (num === 0)
      return 1;
    else
      return num * factorial( num - 1 );

This function calls it self to find the factorial number for <num> as parameter. The result is 3628800.0.

Server-Side functions

Server-Side functions can be used as Servlet replacement. To know how to call a Server-Side function look at Usage via HTTP REST. When are called via HTTP REST protocol, OrientDB embeds two more variable:

  • request, as the HTTP request and implemented by OHttpRequestWrapper class
  • response, as the HTTP request response implemented by OHttpResponseWrapper class
  • util, as an utility class with helper functions to use inside the functions. It's implemented by OFunctionUtilWrapper class

Request object

Refer to this object as "request". Example:

      var params = request.getParameters();
Method signature Description Return type
getContent() Returns the request's content String
getUser() Gets the request's user name String
getContentType() Returns the request's content type String
getHttpVersion() Return the request's HTTP version String
getHttpMethod() Return the request's HTTP method called String
getIfMatch() Return the request's IF-MATCH header String
isMultipart() Returns if the requests has multipart boolean
getArguments() Returns the request's arguments passed in REST form. Example: /2012/10/26 String[]
getArgument(<position>) Returns the request's argument by position, or null if not found String
getParameters() Returns the request's parameters String
getParameter(<name>) Returns the request's parameter by name or null if not found String
hasParameters(<name>*) Returns the number of parameters found between those passed Integer
getSessionId() Returns the session-id String
getURL() Returns the request's URL String

Response object

Refer to this object as "response". Example:

    var roles = db.query("select from ORole where name = ?", roleName); 
    if( roles == null || roles.length == 0 ){
      response.send(404, "Role name not found", "text/plain", "Error: role name not found" );
    } else {
      
      db.begin();
      try{
        var result = db.save({ "@class" : "OUser", name : "Luca", password : "Luc4", "roles" : roles});
        db.commit();
        return result;
      }catch ( err ){
        db.rollback();
        response.send(500, "Error on creating new user", "text/plain", err.toString() );
      }
    }
Method signature Description Return type
getHeader() Returns the response's additional headers String
setHeader(String header) Sets the response's additional headers to send back. To specify multiple headers use the line breaks Request object
getContentType() Returns the response's content type. If null will be automatically detected String
setContentType(String contentType) Sets the response's content type. If null will be automatically detected Request object
getCharacterSet() Returns the response's character set used String
setCharacterSet(String characterSet) Sets the response's character set Request object
getHttpVersion() String
writeStatus(int httpCode, String reason) Sets the response's status as HTTP code and reason Request object
writeStatus(int httpCode, String reason) Sets the response's status as HTTP code and reason Request object
writeHeaders(String contentType) Sets the response's headers using the keep-alive Request object
writeHeaders(String contentType, boolean keepAlive) Sets the response's headers specifying when using the keep-alive or not Request object
writeLine(String content) Writes a line in the response. A line feed will be appended at the end of the content Request object
writeContent(String content) Writes content directly to the response Request object
writeRecords(List<OIdentifiable> records) Writes records as response. The records are serialized in JSON format Request object
writeRecords( List<OIdentifiable> records, String fetchPlan) Writes records as response specifying a fetch-plan to serialize nested records. The records are serialized in JSON format Request object
writeRecord(ORecord record) Writes a record as response. The record is serialized in JSON format Request object
writeRecord(ORecord record, String fetchPlan) Writes a record as response. The record is serialized in JSON format Request object
send(int code, String reason, String contentType, Object content) Sends the complete HTTP response in one call Request object
send(int code, String reason, String contentType, Object content, String headers) Sends the complete HTTP response in one call specifying additional headers. Keep-alive is set Request object
send(int code, String reason, String contentType, Object content, String headers, boolean keepAlive) Sends the complete HTTP response in one call specifying additional headers Request object
sendStream(int code, String reason, String contentType, InputStream content, long size) Sends the complete HTTP response in one call specifying a stream as content Request object
flush() Flushes the content to the TCP/IP socket Request object

Util object

Refer to this object as "util". Example:

    if( util.exists(year) ){
      print("\nYes, the year was passed!");
    }
Method signature Description Return type
exists(<variable>*) Returns trues if any of the passed variables are defined. In JS, fr example, a variable is defined if it's not null and not equals to "undefined" Boolean
⚠️ **GitHub.com Fallback** ⚠️