Functions - aemadrid/orientdb GitHub Wiki
<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
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.
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
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.
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);
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.
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.
return db.query("select name from ouser");
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.
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.
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() );
}
}
}
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 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
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 |
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 |
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 |