Using Node.js with Caché
Using cache.node NoSQL Methods
[Home] [Back] [Next]
InterSystems: The power behind what matters   
Class Reference   
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 Caché.
Consider the following global node:
   ^Customer(1)="Jane K. White"
Note:
By convention, global names are prefixed with the ‘^’ character in Caché. 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 Caché Database
Before any other methods can be called, the cache.node module must be loaded, an instance of the Caché object created and the target Caché database opened before any data (or Caché 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 Caché. First, there is the option of API level connectivity to a local Caché instance and, second, network based connectivity to either a local or remote Caché instance.
Connecting Via the Caché API
Connecting to a local instance of Caché via its API offers high performance integration between Node.js and Caché.
   mydata.open(parameters[, function(error, result){}]);
where:
Example:
   mydata.open({  path:"/cache20151/mgr",
                  username: "_SYSTEM",
                  password: "SYS",
                  namespace: "USER"
               }
               [, function(error, result){}]
   );
The Caché Principal Device
The Caché 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 Caché Via the Network
Network based connectivity to Caché can be used to connect to either a local or remote instance of Caché. This method is particularly useful for connecting to Caché 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 Caché 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 Caché Connectivity
The cache.node module will allow multiple instances of the Caché class to be created per hosting Node.js process if (and only if) TCP based connectivity to Caché is used.
Example 1: Creating two instances of the Caché 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 Caché 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 Caché 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 Caché 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 Caché Database
The following method will gracefully close a previously opened Caché 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 Caché Serialization Lock
A Caché 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 Caché operations (invoked through cache.node) to ensure that only one command at a time is submitted to the associated Caché 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 Caché. 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 Caché 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 Caché 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, Caché.
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 Caché. 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 Caché 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 Caché 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 Caché database is not open:
   Node.js Adaptor for Cache: Version: 1.1.112 (CM)
If the Caché database is open:
   Node.js Adaptor for Cache: Version: 1.1.112 (CM); Cache Version: 2016.3 build 168
Invoking a Caché Function: function()
Functions contained within the Caché 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 Caché 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 Caché 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
   }