Chapter 5 MySQL NoSQL Connector for JavaScript

Table of Contents

5.1 MySQL NoSQL Connector for JavaScript Overview
5.2 Installing the JavaScript Connector
5.3 Connector for JavaScript API Documentation
5.3.1 Batch
5.3.2 Context
5.3.3 Converter
5.3.4 Errors
5.3.5 Mynode
5.3.6 Session
5.3.7 SessionFactory
5.3.8 TableMapping and FieldMapping
5.3.9 TableMetadata
5.3.10 Transaction
5.4 Using the MySQL JavaScript Connector: Examples
5.4.1 Requirements for the Examples
5.4.2 Example: Finding Rows
5.4.3 Inserting Rows
5.4.4 Deleting Rows

This section provides information about the MySQL NoSQL Connector for JavaScript, a set of Node.js adapters for NDB Cluster and MySQL Server available beginning with NDB 7.3.1, which make it possible to write JavaScript applications for Node.js using MySQL data.

5.1 MySQL NoSQL Connector for JavaScript Overview

This connector differs in a number of key respects from most other MySQL Connectors and APIs. The interface is asynchronous, following the built-in Node.js event model. In addition, it employs a domain object model for data storage. Applications retrieve data in the form of fully-instantiated objects, rather than as rows and columns.

The MySQL Node.js adapter includes 2 drivers. The ndb driver accesses the NDB storage engine directly, using the NDB API (see Chapter 2, The NDB API). No MySQL Server is required for the ndb driver. The mysql driver uses a MySQL Server for its data source, and depends on the node-mysql Node.js module from https://github.com/felixge/node-mysql/. Regardless of the driver in use, no SQL statements are required; when using the Connector for JavaScript, Node.js applications employ data objects for all requests made to the database.

5.2 Installing the JavaScript Connector

This section covers basic installation and setup of the MySQL JavaScript Connector and its prerequites. The Connector requires both Node.js and NDB Cluster to be installed first; you can install these in either order. In addition, the mysql-js adapter requires the node-mysql driver. Building the Connector also requires that your system have a working C++ compiler such as gcc or Microsoft Visual Studio.

To install all of the prerequisites for the JavaScript Connector, including node-mysql, you should perform the following steps:

  1. Node.js.  If you do not already have Node.js installed on your system, you can obtain it from http://nodejs.org/download/. In addition to source code, prebuilt binaries and installers are available for a number of platforms. Many Linux distributions also have Node.js in their repositories (you may need to add an alternative repository in your package manager).

    NDB 7.3.1 requires Node.js version 0.7.9 or earlier, due to dependency on node-waf. NDB 7.3.2 and later use node-gyp (see https://npmjs.org/package/node-gyp), and should work with Node.js 0.8.0 and later.

    Regardless of the method by which you obtain Node.js, keep in mind that the architecture of the version you install must match that of the NDB Cluster binaries you intend to use; you cannot, for example, install the JavaScript Connector using 64-bit Node.js and 32-bit NDB Cluster. If you do not know the architecture of your existing Node.js installation, you can determine this by checking the value of global.process.arch.

  2. NDB Cluster.  If NDB Cluster, including all header and library files, is not already installed on the system, install it (see NDB Cluster Installation).

    As mentioned previously, you must make sure that the architecture (32-bit or 64-bit) is the same for both NDB Cluster and Node.js. You can check the architecture of an existing NDB Cluster installation in the output of ndb_mgm -V.

  3. node-mysql driver.  The mysql-js adapter also requires a working installation of the node-mysql driver from https://github.com/felixge/node-mysql/. You can install the driver using the Node.js npm install command; see the project web site for the recommended version and package identifier.

Once the requirements just listed are met, you can find the files needed to install the MySQL Connector for JavaScript in share/nodejs in the NDB Cluster installation directory. (If you installed NDB Cluster as an RPM, this is /usr/share/mysql/nodejs.) To use the Node.js npm tool to perform a best-guess installation without any user intervention, change to the share/nodejs directory, then use npm as shown here:

shell> npm install .

The final period (.) character is required. Note that you must run this command in share/node.js in the NDB Cluster installation directory.

You can test your installation using the supplied test program. This requires a running NDB Cluster, including a MySQL Server with a database named test. The mysql client executable must be in the path.

To run the test suite, change to the test directory, then execute command shown here:

shell> node driver

By default, all servers are run on the local machine using default ports; this can be changed by editing the file test/test_connection.js, which is generated by running the test suite. If this file is not already present (see Bug #16967624), you can copy share/nodejs/test/lib/test_connection_js to the test directory for this purpose.

If you istalled NDB Cluster to a nondefault location, you may need to export the LD_LIBRARY_PATH to enable the test suite. The test suite also requires that the test database be available on the MySQL server.

NDB 7.3.1 also provided an alternative build script in share/node.js/setup; this was removed in NDB 7.3.2 and later NDB Cluster 7.3 releases.

5.3 Connector for JavaScript API Documentation

This section contains prototype descriptions and other information for the MySQL Connector for JavaScript.

5.3.1 Batch

This class represents a batch of operations.

Batch extends Context
execute(Function(Object error) callback);

Execute this batch. When a batch is executed, all operations are executed; the callback for each operation is called when that operation is executed (operations are not performed in any particular order). The execute() function's callback is also called.

A batch is executed in the context of the session's current state: this is autocommit if a transaction has not been started; this also includes the default lock mode and the partition key.

clear();

Clear this batch without affecting the transaction state. After being cleared, the batch is still valid, but all operations previously defined are removed; this restores the batch to a clean state.

The callbacks for any operations that are defined for this batch are called with an error indicating that the batch has been cleared.

This function requires no arguments.

getSession();

Get the session from which this batch was created.

This function requires no arguments.

5.3.2 Context

Context is the supertype of Session and Batch. It contains functions that are executed immediately if called from a session, or when the batch is executed.

The Mynode implementation does have any concept of a user and does not define any such property.

find(Function constructor, Object keys, Function(Object error, Object instance[, ...]) callback[, ...]);

find(String tableName, Object keys, Function(Object error, Object instance[, ...]) callback[, ...]);

Find a specific instance based on a primary key or unique key value.

You can use either of two versions of this function. In the first version, the constructor parameter is the constructor function of a mapped domain object. Alternatively, you can use the tableName instead, in the second variant of the function.

For both versions of find(), the keys may be of any type. A key must uniquely identify a single row in the database. If keys is a simple type (number or string), then the parameter type must be the same type as or compatible with the primary key type of the mapped object. Otherwise, properties are taken from the parameter and matched against property names in the mapping. Primary key properties are used if all are present, and other properties ignored. If keys cannot be used identify the primary key, property names corresponding to unique key columns are used instead. If no complete primary or unique key properties are found, an error is reported. The returned object is loaded based on the mapping and the current values in the database.

For multi-column primary or unique keys, all key fields must be set.

load(Object instance, Function(Object error) callback);

Load a specific instance by matching its primary or unique key with a database row, without creating a new domain object. (This is unlike find(), which creates a new, mapped domain object.)

The instance must have its primary or unique key value or values set. The mapped values in the object are loaded based on the current values in the database. Unmapped properties in the object are not changed.

Primary key properties are used if all are present, and all other properties are ignored; otherwise, property names corresponding to unique key columns are used. If no complete primary or unique key properties can be found, an error is reported.

The callback function is called with the parameters provided when the operation has completed. The error is the Node.js Error object; see Section 5.3.4, “Errors”, for more information.

persist(Object instance, Function(Object error) callback);

persist(Function constructor, Object values, Function(Object error) callback);

persist(String tableName, Object values, Function(Object error) callback);

Insert an instance into the database, unless the instance already exists in the database, in which case an exception is reported to a callback function. Autogenerated values are present in the instance when the callback is executed.

The role of an instance to be persisted can be fulfilled in any of three ways: by an instance object; by a constructor, with parameters, for a mapped domain object; or by table name and values to be inserted.

In all three cases, the callback function is called with the parameters provided, if any, when the operation has completed. The error is the Node.js Error object; see Section 5.3.4, “Errors”, for more information.

remove(Object instance, Function(Object error) callback);

remove(Function constructor, Object keys, Function(Object error) callback);

remove(String tableName, Object keys, Function(Object error) callback);

Delete an instance of a class from the database by a primary or unique key.

There are three versions of remove(); these allow you to delete an instance by referring to the instance object, to a constructor function, or by name of the table. The instance object must contain key values that uniquely identify a single row in the database. Otherwise, if the keys supplied with the function constructor or table name is a simple type (Number or String), then the parameter type must be of either the same type as or a type compatible with the primary key type of the mapped object. If keys is not a simple type, properties are taken from the parameter and matched against property names in the mapping. Primary key properties are used if all are present, and other properties ignored. If keys does not identify the primary key, property names corresponding to unique key columnsare used instead. If no complete primary or unique key properties are found, an error is reported to the callback.

All three versions of remove() call the callback function with the parameters provided, if any, when the operation is complete. The error object is a Node.js Error; see Section 5.3.4, “Errors”, for error codes.

update(Object instance, Function(Object error) callback);

update(Function constructor, keys, values, Function(Object error) callback);

update(String tableName, keys, values, Function(Object error) callback);

Update an instance in the database with the supplied values without retrieving it. The primary key is used to determine which instance is updated. If the instance does not exist in the database, an exception is reported in the callback.

As with the methods previously shown for persisting instances in and removing them from the database, update() exists in three variations, which allow you to use the instance as an object, an object constructor with keys, or by tableName and keys.

Unique key fields of the keys object determine which instance is to be updated. The values object provides values to be updated. If the keys object contains all fields corresponding to the primary key, the primary key identifies the instance. If not, unique keys are chosen is a nondeterministic manner.

Note

update() cannot be used to change the primary key.

save(Object instance, Function(Object error) callback);

save(Function constructor, Object values, Function(Object error) callback);

save(String tableName, Object values, Function(Object error) callback);

Save an instance in the database without checking for its existence. If the instance already exists, it is updated (as if you had used update()); otherwise, it is created (as if persist() had been used). The instance id property is used to determine which instance should be saved. As with update(), persist(), and remove(), this method allows you to specify the instance using an object, object constructor, or table name.

All three versions of the save() method call the callback function with any parameters provided when the operation has been completed. The error is a Node.js Error object; see Section 5.3.4, “Errors”, for error codes and messages.

Boolean isBatch()

Context also exposes an isBatch() instance method, which returns true if this Context is a Batch, and false if it is a Session. isBatch() takes no arguments.

5.3.3 Converter

Converter classes convert between JavaScript types and MySQL types. If the user supplies a JavaScript converter, it used to read and write to the database.

Converters have several purposes, including the following:

  • To convert between MySQL DECIMAL types and a user's preferred JavaScript fixed-precision utility library

  • To convert between MySQL BIGINT types and a user's preferred JavaScript big number utility library

  • To serialize arbitrary application objects into character or binary columns

The ndb backend also uses converters to support SET and ENUM columns. (The mysql backend does not use these.)

A Converter class has the interface defined here:

function Converter() {}:

Converter.prototype = {
  "toDB"    : function(obj) {  },
  "fromDB"  : function(val) {  }
};

The Converter must implement the following two functions:

  1. toDB(obj): Convert an application object obj into a form that can be stored in the database.

  2. fromDB(val): Convert a value val read from the database into application object format.

Each function returns the result of the conversion.

Converter invocations are chained in the following ways:

  • When writing to the database, first the registered FieldConverter, if any, is invoked. Later, any registered TypeConverter is invoked.

  • When reading from the database, first the registered TypeConverter, if any, is invoked. Later, any registered FieldConverter is invoked.

5.3.4 Errors

The Errors object contains the error codes and message exposed by the MySQL Node.js adapters.

var Errors;

Errors = {
  /* Standard-defined classes, SQL-99 */
  "02000" : "No Data",

  // connection errors
  "08000" : "Connection error",
  "08001" : "Unable to connect to server",
  "08004" : "Connection refused",

  // data errors
  "22000" : "Data error",
  "22001" : "String too long",
  "22003" : "Numeric value out of range",
  "22008" : "Invalid datetime",

  // Constraint violations
  // 23000 includes both duplicate primary key and duplicate unique key
  "23000" : "Integrity Constraint Violation",

  // misc. errors
  "25000" : "Invalid Transaction State",
  "2C000" : "Invalid character set name",
  "42S02" : "Table not found",
  "IM001" : "Driver does not support this function",

  /* Implementation-defined classes (NDB) */
  "NDB00" : "Refer to ndb_error for details"
};

5.3.5 Mynode

This class is used to generate and obtain information about sessions (Session objects). To create an instance, use the Node.js require() function with the driver name, like this:

var nosql = require("mysql-js");

ConnectionProperties can be used to retrieve or set the connection properties for a given session. You can obtain a complete set of of default connection properties for a given adapter using the ConnectionProperties constructor, shown here, with the name of the adapter (a string) used as the value of nameOrProperties:

ConnectionProperties(nameOrProperties);

You can also create your own ConnectionProperties object by supplying a list of property names and values to a new ConnectionProperties object in place of the adapter name. Then you can use this object to set the connection properties for a new session, as shown here:

var NdbConnectionProperties = {
  "implementation" : "ndb",

  "ndb_connectstring" : "localhost:1186",
  "database"          : "test",
  "mysql_user"        : "root",

  "ndb_connect_retries" : 4,
  "ndb_connect_delay"   : 5,
  "ndb_connect_verbose" : 0,

  "linger_on_close_msec": 500,
  "use_ndb_async_api"   : false,

  "ndb_session_pool_min" : 4,
  "ndb_session_pool_max" : 100,
};

var sharePath = '/usr/local/mysql/share/nodejs';      // path to share/nodejs
var nosql = require(sharePath);       
var dbProperties = nosql.ConnectionProperties(NdbConnectionProperties);

It is also possible to obtain an object with the adapter's default connection properties, after which you can update a selected number of these properties, then use the modified object to set connection properties for the session, as shown here:

var sharePath = '/usr/local/mysql/share/nodejs';      // path to share/nodejs
var spi   = require(sharePath + "/Adapter/impl/SPI"); // under share/nodejs

var serviceProvider         = spi.getDBServiceProvider('ndb');
var NdbConnectionProperties = serviceProvider.getDefaultConnectionProperties();

NdbConnectionProperties.mysql_user = 'nodejs_user';
NdbConnectionProperties.database   = 'my_nodejs_db';

var dbProperties = nosql.ConnectionProperties(NdbConnectionProperties);

The ConnectionProperties object includes the following properties:

  • implementation: For Node.js applications using NDB Cluster, this is always ndb.

  • ndb_connectstring: NDB Cluster connection string used to connect to the management server.

  • database: Name of the MySQL database to use.

  • mysql_user: MySQL user name.

  • ndb_connect_retries: Number of times to retry a failed connection before timing out; use a number less than 0 for this to keep trying the connection without ever stopping.

  • ndb_connect_delay: Interval in seconds between connection retries.

  • ndb_connect_verbose: 1 or 0; 1 enables extra console output during connection.

  • linger_on_close_msec: When a client closes a DBConnectionPool, the underlying connection is kept open for this many milliseconds in case another client tries to reuse it.

  • use_ndb_async_api: If true, some operations are executed using asynchronous calls for improved concurrency. If false, the number of operations in transit is limited to one per worker thread.

  • ndb_session_pool_min: Minimum number of DBSession objects per NdbConnectionPool.

  • ndb_session_pool_max: Maximum number of DBSession objects per NdbConnectionPool.

    Each NdbConnectionPool maintains a pool of DBSession objects, along with their underlying Ndb objects. This parameter, together with ndb_session_pool_min, sets guidelines for the size of that pool.

The TableMapping constructor is also visible as a top-level function. You can get the mapping either by name, or by using an existing mapping:

TableMapping(tableName);

TableMapping(tableMapping);
openSession(properties, mappings, Function(err, Session) callback);

Connect to the data source and get a Session in the callback function. This is equivalent to calling connect() (see later in this section), and then calling getSession() on the SessionFactory that is returned in the callback function.

Note

Executing this method could result in connections being made to many other nodes on the network, waiting for them to become ready, and making multiple requests to them. You should avoid opening new sessions unnecessarily for this reason.

The implementation member of the properties object determines the implementation of the Session.

If mappings is undefined, null, or an empty array, no mappings are loaded or validated. In this case, any required mappings are loaded and validated when needed during execution. If mappings contains a string or a constructor function, the metadata for the table (or mapped table) is loaded from the database and validated against the requirements of the mapping.

Multiple tables and constructors may be passed to openSession() as elements in an array.

connect(properties, mappings, Function(err, SessionFactory) callback);

Connect to the data source to obtain a SessionFactory in the callback function. In order to obtain a Session, you must then call getSession() on this SessionFactory, whose implementation is determined by the implementation member of the properties object.

If mappings is undefined, null, or an empty array, no mappings are loaded or validated. In this case, any required mappings are loaded and validated when needed. If mappings contains a string or a constructor function, the metadata for the table (or mapped table) is loaded from the database and validated against the requirements of the mapping.

Multiple tables and constructors may be passed as elements in an array.

Array getOpenSessionFactories()

Get an array of all the SessionFactory objects that have been created by this module.

Note

The following functions are part of the public API but are not intended for application use. They form part of the contract between Mynode and SessionFactory.

  • Connection()

  • getConnectionKey()

  • getConnection()

  • newConnection()

  • deleteFactory()

5.3.6 Session

A session is the main user access path to the database. The Session class models such a session.

Session extends Context
getMapping(Object parameter, Function(Object err, Object mapping) callback);

Get the mappings for a table or class.

The parameter may be a table name, a mapped constructor function, or a domain object. This function returns a fully resolved TableMapping object.

Batch createBatch()

Creates a new, empty batch for collecting multiple operations to be executed together. In an application, you can invoke this function similarly to what is shown here:

var nosql = require("mysql-js");

var myBatch = nosql.createBatch();
Array listBatches():

Return an array whose elements consist of all current batches belonging to this session.

Transaction currentTransaction();

Get the current Transaction.

void close(Function(Object error) callback);

Close this session. Must be called when the session is no longer needed.

boolean isClosed();

Returns true if this session is closed.

void setLockMode(String lockMode);

Set the lock mode for read operations. This takes effect immediately and remains in effect until the session is closed or this method is called again. lockMode must be one of 'EXCLUSIVE', 'SHARED', OR 'NONE'.

Array listTables(databaseName, callback);

List all tables in database databaseName.

TableMetadata getTableMetadata(String databaseName, String tableName, callback);

Fetch metadata for table tableName in database databaseName.

5.3.7 SessionFactory

This class is used to generate and manage sessions. A Session provides a context for database transactions and operations. Each independent user should have its own session.

openSession(Object mappings, Function(Object error, Session session) callback);

Open a database session object. Table mappings are validated at the beginning of the session. Resources required for sessions are allocated in advance; if those resources are not available, the method returns an error in the callback.

Array getOpenSessions();

Get all open sessions that have been created by this SessionFactory.

close(Function(Error err));

Close the connection to the database. This ensures proper disconnection. The function passed in is called when the close operation is complete.

5.3.8 TableMapping and FieldMapping

A TableMapping describes the mapping of a domain object in the application to a table stored in the database. A default table mapping is one which maps each column in a table to a field of the same name.

TableMapping = {
  String table                  :  "" ,
  String database               :  "" ,
  boolean mapAllColumns         : true,
  Array fields                  : null
};

The table and data members are the names of the table and database, respectively. mapAllColumns, if true, creates a default FieldMapping for all columns not listed in fields, such that that all columns not explicitly mapped are given a default mapping to a field of the same name. fields holds an array of FieldMapping objects;this can also be a single FieldMapping.

A FieldMapping describes a single field in a domain object. There is no public constructor for this object; you can create a FieldMapping using TableMapping.mapField(), or you can use FieldMapping literals can be used directly in the TableMapping constructor.

FieldMapping = {
  String fieldName     :  "" ,
  String columnName    :  "" ,
  Boolean persistent    : true,
  Converter converter     : null
};

fieldName and columnName are the names of the field and the column where this field are stored, respectively, in the domain object. If persistent is true (the default), the field is stored in the database. converter specifies a Converter class, if any, to use with this field (defaults to null). };

The TableMapping constructor can take either the name of a table (possibly qualified with the database name) or a TableMapping literal.

TableMapping mapField(String fieldName, [String columnName], [Converter converter], [Boolean persistent])

Create a field mapping for a named field of a mapped object. The only mandatory parmeter is fieldName, which provides the name a field in a JavaScript application object. The remaining parameters are optional, and may appear in any order. The cyrrent TableMapping object is returned.

columnName specifies the name of the database column that maps to this object field. If omitted, columnName defaults to the same value as fieldName. A converter can be used to supply a Converter class that performs custom conversion between JavaScript and database data types. The default is null. persistent specifies whether the field is persisted to the database, and defaults to true.

Important

If persistent is false, then the columnName and converter parameters may not be used.

TableMapping applyToClass(Function constuctor)

Attach a TableMapping to a constructor for mapped objects. After this is done, any object created from the constructor will qualify as a mapped instance, which several forms of the relevant Session and Batch methods can be used.

For example, an application can construct an instance that is only partly complete, then use Session.load() to populate it with all mapped fields from the database. After the application modifies the instance, Session.save() saves it back. Similarly, Session.find() can take the mapped constructor, retrieve an object based on keys, and then use the constructor to create a fully-fledged domain object.

5.3.9 TableMetadata

A TableMetadata object represents a table. This is the object returned in the getTable() callback. indexes[0] represents the table's intrinsic primary key.

TableMetadata = {
  database         : ""    ,  // Database name
  name             : ""    ,  // Table Name
  columns          : {}    ,  // ordered array of ColumnMetadata objects
  indexes          : {}    ,  // array of IndexMetadata objects
  partitionKey     : {}    ,  // ordered array of column numbers in the partition key
};

ColumnMetadata object represents a table column.

ColumnMetadata = {
  /* Required Properties */
  name             : ""    ,  // column name
  columnNumber     : -1    ,  // position of column in table, and in columns array
  columnType       : ""    ,  // a ColumnTypes value
  isIntegral       : false ,  // true if column is some variety of INTEGER type
  isNullable       : false ,  // true if NULLABLE
  isInPrimaryKey   : false ,  // true if column is part of PK
  isInPartitionKey : false ,  // true if column is part of partition key
  columnSpace      : 0     ,  // buffer space required for encoded stored value
  defaultValue     : null  ,  // default value for column: null for default NULL;
                              // undefined for no default; or a type-appropriate
                              // value for column

  /* Optional Properties, depending on columnType */
  /* Group A: Numeric */
  isUnsigned       : false ,  //  true for UNSIGNED
  intSize          : null  ,  //  1,2,3,4, or 8 if column type is INT
  scale            : 0     ,  //  DECIMAL scale
  precision        : 0     ,  //  DECIMAL precision
  isAutoincrement  : false ,  //  true for AUTO_INCREMENT columns

  /* Group B: Non-numeric */
  length           : 0     ,  //  CHAR or VARCHAR length in characters
  isBinary         : false ,  //  true for BLOB/BINARY/VARBINARY
  charsetNumber    : 0     ,  //  internal number of charset
  charsetName      : ""    ,  //  name of charset
};

An IndexMetadata object represents a table index. The indexes array of TableMetadata contains one IndexMetadata object per table index.

NDB implements a primary key as both an ordered index and a unique index, and might be viewed through the NDB API adapter as two indexes, but through a MySQL adapter as a single index that is both unique and ordered. We tolerate this discrepancy and note that the implementation in Adapter/api must treat the two descriptions as equivalent.

IndexMetadata = {
  name             : ""    ,  // Index name; undefined for PK
  isPrimaryKey     : true  ,  // true for PK; otherwise undefined
  isUnique         : true  ,  // true or false
  isOrdered        : true  ,  // true or false; can scan if true
  columns          : null  ,  // an ordered array of column numbers
};

The ColumnMetaData object's columnType must be a valid ColumnTypes value, as shown in this object's definition here:

ColumnTypes =  [
  "TINYINT",
  "SMALLINT",
  "MEDIUMINT",
  "INT",
  "BIGINT",
  "FLOAT",
  "DOUBLE",
  "DECIMAL",
  "CHAR",
  "VARCHAR",
  "BLOB",
  "TEXT",
  "DATE",
  "TIME",
  "DATETIME",
  "YEAR",
  "TIMESTAMP",
  "BIT",
  "BINARY",
  "VARBINARY"
];

5.3.10 Transaction

A transaction is always either automatic or explicit. If it is automatic, (autocommit), every operation is performed as part of a new transaction that is automatically committed.

Beginning, committing, and rolling back a transaction

begin();

Begin a transaction. No arguments are required. If a transaction is already active, an exception is thrown.

commit(Function(Object error) callback);

Commit a transaction.

This method takes as its sole argument a callback function that returns an error object.

rollback(Function(Object error) callback);

Roll back a transaction. Errors are reported in the callback function.

Transaction information methods

Boolean isActive();

Determine whether or not a given transaction is currently active. Returns true if a transaction is active, and false otherwise.

isActive() requires no arguments.

setRollbackOnly();

Mark the transaction as rollback-only. Once this is done, commit() rolls back the transaction and throws an exception; rollback() rolls the transaction back, but does not throw an exception. To mark a transaction as rollback-only, call the setRollbackOnly() method, as shown here.

This method is one-way; a transaction marked as rollback-only cannot be unmarked. Invoking setRollbackOnly() while in autocommit mode throws an exception. This method requires no arguments.

boolean getRollbackOnly();

Determine whether a transaction has been marked as rollback-only. Returns true if the transaction has been so marked. setRollbackOnly() takes no arguments.

5.4 Using the MySQL JavaScript Connector: Examples

This section contains a number of examples performing basic database operations such as retrieving, inserting, or deleting rows from a table. The source for these files ca also be found in share/nodejs/samples, under the NDB Cluster installation directory.

5.4.1 Requirements for the Examples

The software requirements for running the examples found in the next few sections are as follows:

  • A working Node.js installation

  • Working installations of the ndb and mysql-js adapters

  • The mysql-js adapter also requires a working installation of the node-mysql driver from https://github.com/felixge/node-mysql/.

Section 5.2, “Installing the JavaScript Connector”, describes the installation process for all three of these requirements.

Sample database, table, and data.  All of the examples use a sample table named tweet, in the test database. This table is defined as in the following CREATE TABLE statement:

CREATE TABLE IF NOT EXISTS tweet  (
    id CHAR(36) NOT NULL PRIMARY KEY,
    author VARCHAR(20),
    message VARCHAR(140),
    date_created TIMESTAMP,

    KEY idx_btree_date_created (date_created),
    KEY idx_btree_author(author)
) 
ENGINE=NDB;

The tweet table can be created by running the included SQL script create.sql in the mysql client. You can do this by invoking mysql in your system shell, as shown here:

shell> mysql < create.sql

All of the examples also make use of two modules defined in the file lib.js, whose contents are reproduced here:

# FILE: lib.js

"use strict";

var udebug = unified_debug.getLogger("samples/lib.js");
var exec = require("child_process").exec;
var SQL = {};

/* Pseudo random UUID generator */

var randomUUID = function() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
    return v.toString(16);
    });
};

/* Tweet domain object model */

var Tweet = function(author, message) {
  this.id = randomUUID();
  this.date_created = new Date();
  this.author = author;
  this.message = message;
};

/* SQL DDL Utilities */

var runSQL = function(sqlPath, source, callback) {

  function childProcess(error, stdout, stderr) {
    udebug.log('harness runSQL process completed.');
    udebug.log(source + ' stdout: ' + stdout);
    udebug.log(source + ' stderr: ' + stderr);
    if (error !== null) {
      console.log(source + 'exec error: ' + error);
    } else {
      udebug.log(source + ' exec OK');
    }
    if(callback) {
      callback(error);
    }
  }

  var p = mysql_conn_properties;
  var cmd = 'mysql';
  if(p) {
    if(p.mysql_socket)     { cmd += " --socket=" + p.mysql_socket; }
    else if(p.mysql_port)  { cmd += " --port=" + p.mysql_port; }
    if(p.mysql_host)     { cmd += " -h " + p.mysql_host; }
    if(p.mysql_user)     { cmd += " -u " + p.mysql_user; }
    if(p.mysql_password) { cmd += " --password=" + p.mysql_password; }
  }
  cmd += ' <' + sqlPath;
  udebug.log('harness runSQL forking process...');
  var child = exec(cmd, childProcess);
};

SQL.create =  function(suite, callback) {
  var sqlPath = path.join(suite.path, 'create.sql');
  udebug.log_detail("createSQL path: " + sqlPath);
  runSQL(sqlPath, 'createSQL', callback);
};

SQL.drop = function(suite, callback) {
  var sqlPath = path.join(suite.path, 'drop.sql');
  udebug.log_detail("dropSQL path: " + sqlPath);
  runSQL(sqlPath, 'dropSQL', callback);
};


/* Exports from this module */
exports.SQL               = SQL;
exports.Tweet             = Tweet;

Finally, a module used for random data generation is included in the file ndb_loader/lib/RandomData.js, shown here:

# FILE: RandomData.js

var assert = require("assert");


function RandomIntGenerator(min, max) {
  assert(max > min);
  var range = max - min;
  this.next = function() {
    var x = Math.floor(Math.random() * range);
    return min + x;
  };
}


function SequentialIntGenerator(startSeq) {
  var seq = startSeq - 1;
  this.next = function() {
    seq += 1;
    return seq;
  };
}


function RandomFloatGenerator(min, max, prec, scale) {
  assert(max > min);
  this.next = function() {
    var x = Math.random();
    /* fixme! */
    return 100 * x;
  };
}


function RandomCharacterGenerator() {
  var intGenerator = new RandomIntGenerator(32, 126);
  this.next = function() {
    return String.fromCharCode(intGenerator.next());
  };
}


function RandomVarcharGenerator(length) {
  var lengthGenerator = new RandomIntGenerator(0, length),
      characterGenerator = new RandomCharacterGenerator();
  this.next = function() {
    var i = 0,
        str = "",
        len = lengthGenerator.next();
    for(; i < len ; i++) str += characterGenerator.next();
    return str;
  }
}


function RandomCharGenerator(length) {
  var characterGenerator = new RandomCharacterGenerator();
  this.next = function() {
    var i = 0,
        str = "";
    for(; i < length ; i++) str += characterGenerator.next();
    return str;
  };
}


function RandomDateGenerator() {
  var generator = new RandomIntGenerator(0, Date.now());
  this.next = function() {
    return new Date(generator.next());
  };
}


function RandomGeneratorForColumn(column) {
  var g = {},
      min, max, bits;

  switch(column.columnType.toLocaleUpperCase()) {
    case "TINYINT":
    case "SMALLINT":
    case "MEDIUMINT":
    case "INT":
    case "BIGINT":
      if(column.isInPrimaryKey) {
        g = new SequentialIntGenerator(0);
      }
      else {
        bits = column.intSize * 8;
        max = column.isUnsigned ? Math.pow(2,bits)-1 : Math.pow(2, bits-1);
        min = column.isUnsigned ?                  0 : 1 - max;
        g = new RandomIntGenerator(min, max);
      }
      break;
    case "FLOAT":
    case "DOUBLE":
    case "DECIMAL":
      g = new RandomFloatGenerator(0, 100000); // fixme
      break;
    case "CHAR":
      g = new RandomCharGenerator(column.length);
      break;
    case "VARCHAR":
      g = new RandomVarcharGenerator(column.length);
      break;
    case "TIMESTAMP":
      g = new RandomIntGenerator(0, Math.pow(2,32)-1);
      break;
    case "YEAR":  
      g = new RandomIntGenerator(1900, 2155);
      break;
    case "DATE":
    case "TIME":
    case "DATETIME":
      g = new RandomDateGenerator();
      break;
    case "BLOB":
    case "TEXT":
    case "BIT":
    case "BINARY":
    case "VARBINARY":
    default:
      throw("UNSUPPORTED COLUMN TYPE " + column.columnType);
      break;
  }

  return g;
}


function RandomRowGenerator(table) {
  var i = 0,
      generators = [];
  for(; i < table.columns.length ; i++) {
    generators[i] = RandomGeneratorForColumn(table.columns[i]);
  }

  this.newRow = function() {
    var n, col, row = {};
    for(n = 0; n < table.columns.length ; n++) {
      col = table.columns[n];
      row[col.name] = generators[n].next();
    }
    return row;
  };
}

exports.RandomRowGenerator = RandomRowGenerator;
exports.RandomGeneratorForColumn = RandomGeneratorForColumn;

5.4.2 Example: Finding Rows

# FILE: find.js

var nosql = require('..');
var lib = require('./lib.js');
var adapter = 'ndb';
global.mysql_conn_properties = {};

var user_args = [];

// *** program starts here ***

// analyze command line

var usageMessage =
  "Usage: node find key\n" +
  "          -h or --help: print this message\n" +
  "         -d or --debug: set the debug flag\n" +
  "  --mysql_socket=value: set the mysql socket\n" +
  "    --mysql_port=value: set the mysql port\n" +
  "    --mysql_host=value: set the mysql host\n" +
  "    --mysql_user=value: set the mysql user\n" +
  "--mysql_password=value: set the mysql password\n" +
  "              --detail: set the detail debug flag\n" +
  "   --adapter=<adapter>: run on the named adapter (e.g. ndb or mysql)\n"
  ;

// handle command line arguments
var i, exit, val, values;

for(i = 2; i < process.argv.length ; i++) {
  val = process.argv[i];
  switch (val) {
  case '--debug':
  case '-d':
    unified_debug.on();
    unified_debug.level_debug();
    break;
  case '--detail':
    unified_debug.on();
    unified_debug.level_detail();
    break;
  case '--help':
  case '-h':
    exit = true;
    break;
  default:
    values = val.split('=');
    if (values.length === 2) {
      switch (values[0]) {
      case '--adapter':
        adapter = values[1];
        break;
      case '--mysql_socket':
        mysql_conn_properties.mysql_socket = values[1];
        break;
      case '--mysql_port':
        mysql_conn_properties.mysql_port = values[1];
        break;
      case '--mysql_host':
        mysql_conn_properties.mysql_host = values[1];
        break;
      case '--mysql_user':
        mysql_conn_properties.mysql_user = values[1];
        break;
      case '--mysql_password':
        mysql_conn_properties.mysql_password = values[1];
        break;
      default:
        console.log('Invalid option ' + val);
        exit = true;
      }
    } else {
      user_args.push(val);
   }
  }
}

if (user_args.length !== 1) {
  console.log(usageMessage);
  process.exit(0);
};

if (exit) {
  console.log(usageMessage);
  process.exit(0);
}

console.log('Running find with adapter', adapter, user_args);
//create a database properties object

var dbProperties = nosql.ConnectionProperties(adapter);

// create a basic mapping
var annotations = new nosql.TableMapping('tweet').applyToClass(lib.Tweet);

//check results of find
var onFind = function(err, object) {
  console.log('onFind.');
  if (err) {
    console.log(err);
  } else {
    console.log('Found: ' + JSON.stringify(object));
  }
  process.exit(0);
};

// find an object
var onSession = function(err, session) {
  if (err) {
    console.log('Error onSession.');
    console.log(err);
    process.exit(0);
  } else {
    session.find(lib.Tweet, user_args[0], onFind);
  }
};

// connect to the database
nosql.openSession(dbProperties, annotations, onSession);

5.4.3 Inserting Rows

# FILE: insert.js


var nosql = require('..');
var lib = require('./lib.js');
var adapter = 'ndb';
global.mysql_conn_properties = {};

var user_args = [];
// *** program starts here ***

// analyze command line

var usageMessage =
  "Usage: node insert author message\n" +
  "          -h or --help: print this message\n" +
  "         -d or --debug: set the debug flag\n" +
  "  --mysql_socket=value: set the mysql socket\n" +
  "    --mysql_port=value: set the mysql port\n" +
  "    --mysql_host=value: set the mysql host\n" +
  "    --mysql_user=value: set the mysql user\n" +
  "--mysql_password=value: set the mysql password\n" +
  "              --detail: set the detail debug flag\n" +
  "   --adapter=<adapter>: run on the named adapter (e.g. ndb or mysql)\n"
  ;

// handle command line arguments
var i, exit, val, values;

for(i = 2; i < process.argv.length ; i++) {
  val = process.argv[i];
  switch (val) {
  case '--debug':
  case '-d':
    unified_debug.on();
    unified_debug.level_debug();
    break;
  case '--detail':
    unified_debug.on();
    unified_debug.level_detail();
    break;
  case '--help':
  case '-h':
    exit = true;
    break;
  default:
    values = val.split('=');
    if (values.length === 2) {
      switch (values[0]) {
      case '--adapter':
        adapter = values[1];
        break;
      case '--mysql_socket':
        mysql_conn_properties.mysql_socket = values[1];
        break;
      case '--mysql_port':
        mysql_conn_properties.mysql_port = values[1];
        break;
      case '--mysql_host':
        mysql_conn_properties.mysql_host = values[1];
        break;
      case '--mysql_user':
        mysql_conn_properties.mysql_user = values[1];
        break;
      case '--mysql_password':
        mysql_conn_properties.mysql_password = values[1];
        break;
      default:
        console.log('Invalid option ' + val);
        exit = true;
      }
    } else {
      user_args.push(val);
   }
  }
}

if (user_args.length !== 2) {
  console.log(usageMessage);
  process.exit(0);
};

if (exit) {
  console.log(usageMessage);
  process.exit(0);
}

console.log('Running insert with adapter', adapter, user_args);
//create a database properties object

var dbProperties = nosql.ConnectionProperties(adapter);

// create a basic mapping
var annotations = new nosql.TableMapping('tweet').applyToClass(lib.Tweet);

//check results of insert
var onInsert = function(err, object) {
  console.log('onInsert.');
  if (err) {
    console.log(err);
  } else {
    console.log('Inserted: ' + JSON.stringify(object));
  }
  process.exit(0);
};

// insert an object
var onSession = function(err, session) {
  if (err) {
    console.log('Error onSession.');
    console.log(err);
    process.exit(0);
  } else {
    var data = new lib.Tweet(user_args[0], user_args[1]);
    session.persist(data, onInsert, data);
  }
};

// connect to the database
nosql.openSession(dbProperties, annotations, onSession);

5.4.4 Deleting Rows

FILE: delete.js


var nosql = require('..');
var lib = require('./lib.js');
var adapter = 'ndb';
global.mysql_conn_properties = {};

var user_args = [];
// *** program starts here ***

// analyze command line

var usageMessage =
  "Usage: node delete message-id\n" +
  "          -h or --help: print this message\n" +
  "         -d or --debug: set the debug flag\n" +
  "  --mysql_socket=value: set the mysql socket\n" +
  "    --mysql_port=value: set the mysql port\n" +
  "    --mysql_host=value: set the mysql host\n" +
  "    --mysql_user=value: set the mysql user\n" +
  "--mysql_password=value: set the mysql password\n" +
  "              --detail: set the detail debug flag\n" +
  "   --adapter=<adapter>: run on the named adapter (e.g. ndb or mysql)\n"
  ;

// handle command line arguments
var i, exit, val, values;

for(i = 2; i < process.argv.length ; i++) {
  val = process.argv[i];
  switch (val) {
  case '--debug':
  case '-d':
    unified_debug.on();
    unified_debug.level_debug();
    break;
  case '--detail':
    unified_debug.on();
    unified_debug.level_detail();
    break;
  case '--help':
  case '-h':
    exit = true;
    break;
  default:
    values = val.split('=');
    if (values.length === 2) {
      switch (values[0]) {
      case '--adapter':
        adapter = values[1];
        break;
      case '--mysql_socket':
        mysql_conn_properties.mysql_socket = values[1];
        break;
      case '--mysql_port':
        mysql_conn_properties.mysql_port = values[1];
        break;
      case '--mysql_host':
        mysql_conn_properties.mysql_host = values[1];
        break;
      case '--mysql_user':
        mysql_conn_properties.mysql_user = values[1];
        break;
      case '--mysql_password':
        mysql_conn_properties.mysql_password = values[1];
        break;
      default:
        console.log('Invalid option ' + val);
        exit = true;
      }
    } else {
      user_args.push(val);
   }
  }
}

if (user_args.length !== 1) {
  console.log(usageMessage);
  process.exit(0);
};

if (exit) {
  console.log(usageMessage);
  process.exit(0);
}

console.log('Running delete with adapter', adapter, user_args);
//create a database properties object

var dbProperties = nosql.ConnectionProperties(adapter);

// create a basic mapping
var annotations = new nosql.TableMapping('tweet').applyToClass(lib.Tweet);

// check results of delete
var onDelete = function(err, object) {
  console.log('onDelete.');
  if (err) {
    console.log(err);
  } else {
    console.log('Deleted: ' + JSON.stringify(object));
  }
  process.exit(0);
};

// delete an object
var onSession = function(err, session) {
  if (err) {
    console.log('Error onSession.');
    console.log(err);
    process.exit(0);
  } else {
    var tweet = new lib.Tweet();
    tweet.id = user_args[0];
    session.remove(tweet, onDelete, user_args[0]);
  }
};

// connect to the database
nosql.openSession(dbProperties, annotations, onSession);