docs.intersystems.com
Home  /  Application Development: Language Bindings and Gateways  /  Using Node.js with InterSystems IRIS  /  Using cache.node NoSQL Methods


Using Node.js with InterSystems IRIS
Using cache.node NoSQL Methods
[Back]  [Next] 
InterSystems: The power behind what matters   
Search:  


This chapter introduces some basic concepts and describes how to connect cache.node to the database. The following topics are discussed:
Comparing Globals and JSON Objects
In this section we will look at the basic relationship between JSON objects in the Node.js environment and InterSystems IRIS.
Consider the following global node:
   ^Customer(1)="Jane K. White"
Note:
By convention, global names are prefixed with the ‘^’ character in InterSystems IRIS. However, this convention need not be followed in the corresponding JSON representation.
The equivalent JSON construct will be:
   {global: "Customer", subscripts: [1], data: "Jane K. White"}
Adding further nodes to this data construct:
globals:
   ^Customer(1)="Jane K. White"
   ^Customer(1, "Address", 1)="London"
   ^Customer(1, "Address", 2)="UK"
   ^Customer(1, "DateOfRegistration")="1 May 2010"
JSON:
   {  global: "Customer",
      subscripts: [1],
      data: "Jane K. White"
   }
   {  global: "Customer",
      subscripts: [1, "Address", 1],
      data: "London"
   }
   {  global: "Customer",
   subscripts: [1, "Address", 2],
   data: "UK"
   }
   {  global: "Customer",
   subscripts: [1, "DateOfRegistration"],
   data: "1 May 2010"
   }
cache.node Methods: (Synchronous vs. Asynchronous)
All methods provided by cache.node can be invoked either synchronously or asynchronously. While synchronous operation is conceptually easier to grasp and can be useful for debugging, the expectation in the Node.js environment is that all operations should be implemented asynchronously with a completion event raised by means of a callback function. For this reason, most of the examples given in this guide are coded to run asynchronously. To run them synchronously simply omit the callback function from the arguments list.
Consider the method to determine the version of the cache.node module in use:
Synchronous operation:
   var result = mydata.version();
Asynchronous operation:
   mydata.version(
      function(error, result) {
         if (error) { // error
         }
         else { // success
         }
      }
   );
The standard convention for reporting errors in Node.js is implemented in cache.node. If an operation is successful, error will be false. If an error occurs then error will be true and the result object will contain the details according to the following JSON construct:
   {
      "ErrorMessage": [error message text],
      "ErrorCode": [error code],
      "ok": [true|false]
   }
For synchronous operation, you should check for the existence of these properties in the result object to determine whether or not an error has occurred.
Opening and Closing the InterSystems IRIS Database
Before any other methods can be called, the cache.node module must be loaded, an instance of the InterSystems IRIS object created and the target InterSystems IRIS database opened before any data (or InterSystems IRIS functionality) can be accessed.
Loading the Cache.node Module
If the cache.node module has been installed in the correct location for your Node.js installation, the following line will successfully load it from the default location:
   var cachedb = require('cache');
If the module is installed in some other location the full path should be specified. For example:
   var cachedb = require('/opt/cm/node0120/build/default/cache');
Creating an Instance
The next task is to create an instance of the cache.node object.
   var mydata = new cachedb.Cache();
There are two ways to connect to InterSystems IRIS. First, there is the option of API level connectivity to a local InterSystems IRIS instance and, second, network based connectivity to either a local or remote InterSystems IRIS instance.
Connecting Via the InterSystems IRIS API
Connecting to a local instance of InterSystems IRIS via its API offers high performance integration between Node.js and InterSystems IRIS.
   mydata.open(parameters[, function(error, result){}]);
where:
Example:
   mydata.open({  path:"/cache20151/mgr",
                  username: "_SYSTEM",
                  password: "SYS",
                  namespace: "USER"
               }
               [, function(error, result){}]
   );
The InterSystems IRIS Principal Device
The InterSystems IRIS principal input and output device for the cache.node session can be specified by defining the input_device and output_device properties respectively.
Example (using the standard input/output device):
   mydata.open({  path: '/opt/cache20151/mgr',
                  username: "_system",
                  password: "SYS",
                  namespace: "user",
                  input_device: "stdin",
                  output_device: "stdout"}
               [, function(error, result){}]
   );
The default is to use the NULL device for cache.node sessions.
Connecting to InterSystems IRIS Via the Network
Network based connectivity to InterSystems IRIS can be used to connect to either a local or remote instance of InterSystems IRIS. This method is particularly useful for connecting to InterSystems IRIS systems installed on platforms for which Node.js is not natively available.
Also, if Node.js is hosting a public facing web application then network based connectivity to InterSystems IRIS will provide the basis for a secure architecture in which the database is physically separated from the web server tier:
   mydata.open(parameters[, function(error, result){}]);
where:
Example:
   mydata.open({  ip_address: "127.0.0.1",
                  tcp_port: 56773,
                  username: "_SYSTEM",
                  password: "SYS",
                  namespace: "USER"
               }
               [, function(error, result){}]
   );
Multiple Instances of InterSystems IRIS Connectivity
The cache.node module will allow multiple instances of the InterSystems IRIS class to be created per hosting Node.js process if (and only if) TCP based connectivity to InterSystems IRIS is used.
Example 1: Creating two instances of the InterSystems IRIS class
   var cachedb = require('cache');
   var user = new cachedb.Cache();
   var samples = new cachedb.Cache();

   user.open({ ip_address: "127.0.0.1",
               tcp_port: 1972,
               username: "_SYSTEM",
               password: "SYS",
               namespace: "USER",
            }
   );

   samples.open({ ip_address: "127.0.0.1",
                  tcp_port: 1972,
                  username: "_SYSTEM",
                  password: "SYS",
                  namespace: "SAMPLES",
               }
   );

   console.log("'user' instance in namespace: " + user.get_namespace());
   console.log("'samples' instance in namespace: " + samples.get_namespace());

   user.close();
   samples.close();
If InterSystems IRIS API based connectivity is used, it is still the case that only one instance can be created in a single Node.js process. This is because the InterSystems IRIS executable to which the hosting Node.js process binds is inherently single threaded. However, it is possible to create one connection using API based connectivity together with one or more TCP based connections.
Example 2: Creating two instances of the InterSystems IRIS class (one API and one TCP)
   var cachedb = require('cache');
   var user = new cachedb.Cache();
   var samples = new cachedb.Cache();

   // API based connection to Cache
   user.open({ path:"/cache20152/mgr",
               username: "_SYSTEM",
               password: "SYS",
               namespace: "USER",
            }
   );

   // TCP based connection to Cache
   samples.open({ ip_address: "127.0.0.1",
                  tcp_port: 1972,
                  username: "_SYSTEM",
                  password: "SYS",
                  namespace: "SAMPLES",
               }
   );

   console.log("'user' instance in namespace: " + user.get_namespace());
   console.log("'samples' instance in namespace: " + samples.get_namespace());

   user.close();
   samples.close();
Closing Access to the InterSystems IRIS Database
The following method will gracefully close a previously opened InterSystems IRIS database.
   mydata.close([function(error, result){}]);
Optional open() Settings
Specifying Character Encoding
The default character encoding used in cache.node is UTF-8. Alternatively, 16-bit Unicode (UTF-16) can be used by defining this character encoding in the open() method.
Example:
   mydata.open({  path:"/cache20151/mgr",
                  username: "_SYSTEM",
                  password: "SYS",
                  namespace: "USER",
                  charset: "UTF-16"
               }
                  [, function(error, result){}]
   );
Disabling the InterSystems IRIS Serialization Lock
An InterSystems IRIS process is inherently single threaded. Events fired asynchronously in the Node.js environment depend on the use of multiple threads to complete tasks concurrently within the context of a single process. For this reason, a serialization lock is applied to all InterSystems IRIS operations (invoked through cache.node) to ensure that only one command at a time is submitted to the associated InterSystems IRIS process.
However, locking is expensive. As a performance enhancement, the serialization lock may be disabled in the open() method if it can be ascertained that the Node.js application will only submit one task at a time to InterSystems IRIS. The lock should not be disabled if cache.node methods are used asynchronously. Even if synchronous mode is used throughout (with respect to the cache.node methods), it must be ascertained that calls to InterSystems IRIS fired synchronously cannot overlap as a result of events firing asynchronously elsewhere in the application. If in doubt, the lock should remain enabled. If an application crash occurs with the lock disabled then it should be re-enabled as a first step in diagnosing the problem.
Example: Disabling the serialization lock
   mydata.open({  path:"/cache20151/mgr",
                  username: "_SYSTEM",
                  password: "SYS",
                  namespace: "USER",
                  lock: 0
               }
               [, function(error, result){}]
   );
Enabling Debug Mode
A trace facility is included in the cache.node module to facilitate easier debugging when problems occur. The trace will record all calls to the InterSystems IRIS Call-in API, the input arguments used and the result returned. If TCP based connectivity is used the trace will record all request buffers submitted to, and response buffers received from, InterSystems IRIS.
The trace facility is enabled by defining the debug property in the open() method.
Example 1: Write a trace to the console (i.e. stdout)
   mydata.open({  path:"/cache20151/mgr",
                  username: "_SYSTEM",
                  password: "SYS",
                  namespace: "USER",
                  debug: 1
               }
               [, function(error, result){}]
   );
Example 2: Write a trace to a file (debug.log)
   mydata.open({  path:"/cache20151/mgr",
                  username: "_SYSTEM",
                  password: "SYS",
                  namespace: "USER",
                  debug: "debug.log"
               }
               [, function(error, result){}]
   );
Example trace:
Node.js Code:
   var cachedb = require('cache');
   var mydata = new cachedb.Cache();
   mydata.open({  path:"/opt/cache20151/mgr",
                  username: "_SYSTEM",
                  password: "SYS",
                  namespace: "USER"
                  debug: "debug.log",
               });
   mydata.set("Person", "1", "Jane K. White");
   var data = mydata.get("Person ", 1));
   mydata.close();
This will produce the following trace (or similar):
       >>> 000007FEF9E39340==fopen(debug.log, "a")
           (Debug trace file opened) 
   >>> cache_open 
       >>> 0000000180000000==sys_dso_load(
          /opt/cache20151/bin/libcache.so) 
       >>> 0==CacheSetDir(/opt/cache20151/mgr) 
       >>> 0==CacheSecureStartA(0000000000126CC0(_SYSTEM),
           0000000000116CA0(SYS),
           000000000010EC90(Node.JS), 56, 15,
           0000000000106C80(//./nul), 000000000012ECD0(//./nul)) 
   >>> cache_set 
       >>> 0==CachePushGlobal(6, Person) 
       >>> 00000000269E7010==CacheExStrNew(000000002682D770, 2) 
       >>> 0==CachePushExStr(000000002682D770) 
           >>> 1 
       >>> 00000000269E7010==CacheExStrNew(000000002682D788, 11) 
       >>> 0==CachePushExStr(000000002682D788) 
           >>> Jane K. White 
       >>> 0==CacheGlobalSet(1) 
       >>> 0==CacheExStrKill(000000002682D770) 
       >>> 0==CacheExStrKill(000000002682D788) 
 
   >>> cache_get 
       >>> 0==CachePushGlobal(6, Person) 
       >>> 00000000269E7010==CacheExStrNew(000000002682D770, 2) 
       >>> 0==CachePushExStr(000000002682D770) 
           >>> 1 
       >>> 0==CacheGlobalGet(1, 0) 
       >>> 0==CachePopExStr(000000000013F410) 
       >>> 0==CacheExStrKill(000000000013F410) 
           >>> Jane K. White 
       >>> 0==CacheExStrKill(000000002682D770) 
 
   >>> cache_close 
       >>> 0==CacheEnd() 
Specifying an Interrupt Signal Handler
The cache.node module includes an optional signal handler to facilitate the clean interrupt of Node.js processes connected to InterSystems IRIS. Ideally, instead of relying on this facility, applications should include a suitable termination signal handler in the JavaScript code.
For example: to respond to the SIGINT signal (Control-C) in JavaScript code:
   process.on( 'SIGINT', function() {
      // clean up and close down gracefully
   });
In the absence of such an application level signal handler, the cache.node module can be instructed to register its own termination signal handler. Use the stop_signal property in the open() method to specify that cache.node should terminate execution on receiving signal SIGINT or SIGTERM (or both).
For example, to instruct cache.node to close the Node.js process on receiving either a SIGINT or SIGTERM signal:
   user.open({path: "/cache20163/mgr",
              username: "_SYSTEM",
              password: "SYS",
              namespace: "USER",
              stop_signal: "SIGINT,SIGTERM"
             }
   );
The signal handlers implemented in the cache.node module do no more than simply close down connectivity to InterSystems IRIS and halt the hosting Node.js process.
Utility Functions
Pausing a Node.js Operation: sleep()
When troubleshooting Node.js applications, particularly those making extensive use of asynchronous completion techniques, it is often convenient to pause a function. The cache.node module contains a sleep() method to pause the current thread of execution for a period of time (in milliseconds) defined as the argument.
Example: (sleep for 5 seconds)
   mydata.sleep(5000);
Note:
This facility should only be used for troubleshooting as Node.js is architected according to the principle that no operation should block the main thread of execution.
Getting Version Information: version() and about()
The version() and about() methods return basic version information about the cache.node module in use and the associated InterSystems IRIS database (if open).
Synchronous:
   var result = mydata.version();
Or:
   var result = mydata.about();
Asynchronous:
   mydata.version(function(error, result){});
Or:
   mydata.about(function(error, result){});
Example:
   mydata.version(
      function(error, result) {
         if (error) { // error (see result.ErrorMessage and result.ErrorCode)
         }
         else { // success
         }
      }
   );
Result:
If the InterSystems IRIS database is not open:
   Node.js Adaptor for Cache: Version: 1.1.112 (CM)
If the InterSystems IRIS database is open:
   Node.js Adaptor for Cache: Version: 1.1.112 (CM); Cache Version: 2016.3 build 168
Invoking an InterSystems IRIS Function: function()
Functions contained within the InterSystems IRIS environment can be called directly via the function() method.
Synchronous:
   var result = mydata.function(cache_function);
Asynchronous:
   var result = mydata.function(cache_function, function(error, result){});
The Math routine
The examples in this section will be based on calls to the following InterSystems IRIS routine:
   Math           ; Math Functions
                  ;
   Add(X,Y)       ; Add two numbers
                  Quit (X * Y)
                  ;
   Multiply(X,Y)  ; Add two numbers
                  Quit (X * Y)
                  ;
This is a simple InterSystems IRIS routine called Math containing functions to perform basic mathematical functions (Add and Multiply).
Example 1 (Synchronous/Non-JSON)
   result = mydata.function("Add^Math", 3, 4);
result: 7
Example 2: (Synchronous/JSON)
   result = mydata.function({function: "Add^Math", arguments: [3, 4]},
result:
   {
      "function": "Add^Math",
      "arguments": [3, 4],
      "result": 7
   }
Example 3: (Asynchronous/JSON)
   mydata.lock(
      {function: "Add^Math", arguments: [3, 4]},
      function(error, result) {
         if (error) { // error (see result.ErrorMessage and result.ErrorCode) 
         }
         else { // success 
         }
      }
   );
result:
   {
      "ok": 1
      "function": " Add^Math ",
           "arguments": [3, 4],
           "result": 7
   }