Table of Contents
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.
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.
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:
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.
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.
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.
This section contains prototype descriptions and other information for the MySQL Connector for JavaScript.
This class represents a batch of operations.
Batch extends Context
execute(Function(Objecterror)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.
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(Functionconstructor, Objectkeys, Function(Objecterror, Objectinstance[, ...])callback[, ...]); find(StringtableName, Objectkeys, Function(Objecterror, Objectinstance[, ...])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(Objectinstance, Function(Objecterror)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(Objectinstance, Function(Objecterror)callback); persist(Functionconstructor, Objectvalues, Function(Objecterror)callback); persist(StringtableName, Objectvalues, Function(Objecterror)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(Objectinstance, Function(Objecterror)callback); remove(Functionconstructor, Objectkeys, Function(Objecterror)callback); remove(StringtableName, Objectkeys, Function(Objecterror)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(Objectinstance, Function(Objecterror)callback); update(Functionconstructor,keys,values, Function(Objecterror)callback); update(StringtableName,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.
update() cannot be used to change the
primary key.
save(Objectinstance, Function(Objecterror)callback); save(Functionconstructor, Objectvalues, Function(Objecterror)callback); save(StringtableName, Objectvalues, Function(Objecterror)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.
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:
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:
toDB(:
Convert an application object obj)obj
into a form that can be stored in the database.
fromDB(:
Convert a value val)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.
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"
};
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.
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.
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()
A session is the main user access path to the database. The
Session class models such a session.
Session extends Context
getMapping(Objectparameter, Function(Objecterr, Objectmapping)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(Objecterror)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(StringdatabaseName, StringtableName,callback);
Fetch metadata for table tableName in
database databaseName.
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(Objectmappings, Function(Objecterror, Sessionsession)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.
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(StringfieldName, [StringcolumnName], [Converterconverter], [Booleanpersistent])
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.
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.
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" ];
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.
begin();
Begin a transaction. No arguments are required. If a transaction is already active, an exception is thrown.
commit(Function(Objecterror)callback);
Commit a transaction.
This method takes as its sole argument a
callback function that returns an
error object.
rollback(Function(Objecterror)callback);
Roll back a transaction. Errors are reported in the
callback function.
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.
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.
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;
# 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);
# 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);
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);