MarkLogic 10 integrates JavaScript as a first-class server-side programming language. You can call a JavaScript program from an App Server, and that program has server-side access to the MarkLogic built-in functions. This chapter describes the JavaScript implementation in MarkLogic and includes the following sections:
MarkLogic Server integrates the Google V8 JavaScript engine (https://code.google.com/p/v8/), a high-performance open source C++ implementation of JavaScript.
MarkLogic embeds version 6.7 of the Google V8 JavaScript engine.
This version of V8 offers some of the newer EcmaScript 2015 (formerly known as EcmaScript 6) features. Some EcmaScript 15 features are:
EcmaScript 2015 generators use the function*
syntax. For a description of EcmaScript 6 generators, see documentation for implementation of generators such as https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function* and http://wiki.ecmascript.org/doku.php?id=harmony:generators. For generators, MarkLogic only supports the Generator.prototype.next()
method (which the for ... of
loop uses), not the Generator.prototype.return()
and Generator.prototype.throw()
methods.
The following is a simple JavaScript generator example to run in MarkLogic:
function* gen(limit){ for (let i = 0; i < limit; i++) yield xdmp.eval('xs.dateTime(new Date())');} const result=[]; for (const i of gen(10)){ result.push(i);} result; /* returns ten different dateTime values (because they are each run in a separate eval) */
JavaScript as a programming language has become extremely popular, and it is familiar to a huge number of developers. Over the last several years, JavaScript has expanded its footprint from the browser to other programming environments like Node.js. MarkLogic Server-Side JavaScript expands that familiarity one level deeper into the database server level. This allows you to combine the power of programming at the database level with the familiarity of JavaScript.
With JavaScript running within MarkLogic, you can do processing of your data right at the server level, without having to transfer the data to a middle tier. In MarkLogic, you have always been able to do this with XQuery. In MarkLogic 8, you can do that same processing using JavaScript, for which most organizations have a lot of experienced programmers.
MarkLogic has many XQuery functions that return date values, using W3C standard XML dates and durations. These functions are all available in Server-Side JavaScript, and their values are returned in the XML types.
For the return value from any of these functions, you can call toObject()
and the date values are converted into JavaScript UTC dates. This way you can use the powerful XML date and duration functions if you want to, and you can combine that with any JavaScript handling of dates that you might prefer (or that you might already have JavaScript code to handle). For reference material on JavaScript Date functions, see any JavaScript reference (for example, Mozilla). For the MarkLogic Server-Side JavaScript date functions, see http://docs.marklogic.com/js/fn/dates.
Consider the following example:
const results = new Array(); const cdt = fn.currentDateTime(); results.push(cdt); const utc = cdt.toObject(); results.push(utc); results; => ["2015-01-05T15:36:17.804712-08:00", "2015-01-05T23:36:17.804"]
In the above example, notice that the output from the cdt
variable (the first item in the results
array) retains the timezone information inherent in XML dateTime values. The output from the utc
variable (the second item in the results
array) no longer has the timezone shift, as it is now a UTC value.
Similarly, you can use any of the UTC methods on MarkLogic-based dates that are converted to objects, For example, the following returns the UTC month:
fn.currentDateTime().toObject() .getMonth(); // note that the month is 0-based, so January is 0
The following returns the number of milliseconds since January 1, 1970:
const utc = fn.currentDateTime().toObject(); Date.parse(utc); // => 1420502304000 (will be different for different times)
The flexibility to use JavaScript date functions when needed and XML/XQuery date functions when needed provides flexibility in how you use dates in Server-Side JavaScript.
In Server-Side JavaScript, you have full access to all of the rich datatypes in MarkLogic, including the numeric datatypes. In general, Server-Side JavaScript maps numeric datatypes in MarkLogic to a JavaScript Number
. There are a few cases, however, where MarkLogic wraps the number in a MarkLogic numeric type instead of returning a JavaScript Number
. Those cases are:
Number
, then MarkLogic wraps it in a numeric datatype (xs.decimal
, for example).xs.integer
, xs:double
, or xs:float
.Query Console, which ships on port 8000 on default installations of MarkLogic, allows you to evaluate JavaScript using Server-Side JavaScript, making it is very easy to try out examples. For example, the following are each hello world examples that you can run in Query Console by entering the following (with JavaScript selected as the Query Type):
"hello world" fn.concat("hello ", "world")
Both return the string hello world
. For details about Query Console, see the Query Console User Guide.
When you put a JavaScript module under an App Server root with a sjs
file extension, you can evaluate that module via HTTP from the App Server. For example, if you have an HTTP App Server with a root /space/appserver
, and the port set to 1234, then you can save the following file as /space/appserver/my-js.sjs
:
xdmp.setResponseContentType("text/plain"); "hello"
Evaluating this module in a browser pointed to http://localhost:1234/my-js.sjs
(or substituting your hostname for localhost if your browser is on a different machine) returns the string hello
.
You cannot serve up a Server-Side JavaScript module with a .js
file extension (application/javascript
mimetype) directly from the App Server; directly served modules need a .sjs
extension (application/vnd.marklogic-javascript
mimetype). You can import JavaScript libraries with either extension, however, as described in Importing JavaScript Modules Into JavaScript Programs.
If you want to have a single program that does some scripting of tasks, where one task relies on updates from the previous tasks, you can create a JavaScript module that uses xdmp.invoke or xdmp.invokeFunction, where the calls to xdmp.invoke or xdmp.invokeFunction have options to make their contents evaluate in a separate transaction.
The module being invoked using xdmp.invoke may either be JavaScript or XQuery. The module is considered to be JavaScript if the module path ends with a file extension configured for the MIME type application/vnd.marklogic-javascript
or application/vnd.marklogic-js-module
in MarkLogic's Mimetypes configuration. Otherwise, it is assumed to be XQuery.
Invoking a function is programming-language specific. The XQuery version of xdmp:invoke-function can only be used to invoke XQuery functions. The Server-Side JavaScript version of this function (xdmp.invokeFunction) can only be used to invoke JavaScript functions.
The next example invokes a module using external variables, and executes in a separate transaction.
Assume you have a module in the modules database with a URI "http://example.com/application/log.sjs" containing the following code:
xdmp.log(myvar)
Then you can call this module using xdmp.invoke as follows:
xdmp.invoke("log.sjs", {myvar: "log this"}, { modules : xdmp.modulesDatabase(), root : "http://example.com/application/", isolation : "different-transaction" }); => Invokes a JavaScript module from the modules database with the URI http://example.com/application/log.sjs. The invoked module will then be executed, logging the message sent in the external variable to the log.
Each App Server thread runs its own isolate of the V8 JavaScript engine. Objects from one isolate cannot be used in another. This means that V8 data structures such as function objects cannot be shared across App Server threads.
For example, if you cache a function object in a server field in one thread, and then try to access it from another thread (such as from code executed under xdmp.spawn), the function will not be valid. A Server-Side JavaScript function is only valid in the thread in which it is created.
If you are accustomed to working with XQuery or you are developing in both XQuery and Server-Side JavaScript, you should be aware that the semantics of exception handling are not the same in the two languages.
MarkLogic implements the standard exception handling semantics for JavaScript: JavaScript statements in a try block are not rolled back if they complete before a caught exception is raised. In XQuery, all expressions evaluated in a try block are rolled back, even if the exception is caught.
For example, in the following code, the call to xdmp.documentSetMetadata data throws an XDMP-CONFLICTINGUPDATES
exception because it tries to update the document metadata twice in the same transaction. The exception is trapped by the try-catch. The initial document insert succeeds because it was evaluated before the exception occurs.
'use strict'; declareUpdate(); try{ xdmp.documentInsert("doc.json", {content :"value"}, {metadata:{a:1, b:2}}) xdmp.documentSetMetadata("doc.json", {c:3}) } catch(err) { err.toString(); }
The equivalent XQuery code would not insert "doc.json". For more details, see try/catch Expression in the XQuery and XSLT Reference Guide.
You can call into Server-Side JavaScript code from XQuery, and vice versa.
For example, you can use a library module such as the XQuery triggers library (trgr
) from Server-Side JavaScript, whether or not the documentation explicitly calls it out. For details, see Using XQuery Functions and Variables in JavaScript.
You can also eval or invoke code blocks in either language. Use xdmp.xqueryEval to evaluate a block of XQuery from Server-Side JavaScript. Use xdmp.invoke to invoke either XQuery or Server-Side JavaScript from Server-Side JavaScript.
Similarly, you can use xdmp:javascript-eval to evaluate Server-Side JavaScript from XQuery, and xdmp:invoke to invoke either XQuery or Server-Side JavaScript from XQuery.