├── .gitignore ├── .jshintrc ├── README.md ├── binding.gyp ├── esql-3.70.debian.patch ├── esql-4.10.debian.patch ├── examples ├── ifx-insert.example.js ├── ifx.example.js ├── informix.example.js └── transaction.example.js ├── gyp └── preprocessor.sh ├── index.js ├── jsdoc-conf.json ├── lib ├── connection.js ├── context.js ├── cursor.js ├── informix.js ├── pool.js └── statement.js ├── package-lock.json ├── package.json ├── src ├── esqlc.ecpp ├── esqlc.h ├── ifx │ ├── ifx.cpp │ ├── ifx.h │ ├── types.h │ └── workers │ │ ├── connect.cpp │ │ ├── connect.h │ │ ├── cursorclose.cpp │ │ ├── cursorclose.h │ │ ├── disconnect.cpp │ │ ├── disconnect.h │ │ ├── fetch.cpp │ │ ├── fetch.h │ │ ├── stmtexec.cpp │ │ ├── stmtexec.h │ │ ├── stmtfree.cpp │ │ ├── stmtfree.h │ │ ├── stmtprepare.cpp │ │ └── stmtprepare.h └── module.cpp ├── test ├── data-types.test.js ├── ifx.test.js ├── informix.test.js ├── issues.test.js ├── lib.connection.test.js ├── lib.context.test.js ├── lib.cursor.test.js ├── lib.informix.test.js ├── lib.pool.test.js ├── lib.statement.test.js └── support │ ├── setup-db.sh │ └── test-db.sql └── wercker.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore 2 | # 3 | 4 | # node / npm 5 | build 6 | node_modules 7 | npm-debug.log 8 | 9 | # packaging 10 | informix-*.tgz 11 | 12 | # istanbul 13 | coverage 14 | 15 | # jsdoc generated docs 16 | docs 17 | 18 | # wercker 19 | _builds/ 20 | _cache/ 21 | _projects/ 22 | _steps/ 23 | _temp/ 24 | 25 | # temporary files 26 | tmp/ 27 | *.swp 28 | *~ 29 | 30 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6, 3 | "strict": true, 4 | "node": true, 5 | "mocha": true, 6 | "predef": [ 7 | "Promise" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-informix 2 | ============= 3 | 4 | [![Join the chat at https://gitter.im/nukedzn/node-informix](https://nukedzn.github.io/badges/gitter.svg)](https://gitter.im/nukedzn/node-informix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | 6 | [![npm version](https://img.shields.io/npm/v/informix.svg)](https://www.npmjs.com/package/informix) 7 | [![wercker status](https://app.wercker.com/status/6f7f1a8246881c3d98acec4875280c54/s/master "wercker status")](https://app.wercker.com/project/bykey/6f7f1a8246881c3d98acec4875280c54) 8 | [![codecov.io](https://codecov.io/github/nukedzn/node-informix/coverage.svg?branch=master)](https://codecov.io/github/nukedzn/node-informix?branch=master) 9 | [![Coverage Status](https://coveralls.io/repos/nukedzn/node-informix/badge.svg?branch=master&service=github)](https://coveralls.io/github/nukedzn/node-informix?branch=master) 10 | [![Dependency Status](https://david-dm.org/nukedzn/node-informix.svg)](https://david-dm.org/nukedzn/node-informix) 11 | [![devDependency Status](https://david-dm.org/nukedzn/node-informix/dev-status.svg)](https://david-dm.org/nukedzn/node-informix#info=devDependencies) 12 | 13 | A node.js native client for IBM Informix. 14 | 15 | 16 | ## Features 17 | 18 | * Developer friendly ES6 Promise based API 19 | * Transparent connections with lazy connect 20 | * Connection pooling 21 | * Prepared Statements 22 | * Transaction contexts 23 | * Linux, Mac OSX and Windows (experimental) compatibility 24 | 25 | 26 | 27 | ## Dependencies 28 | 29 | * [IBM Informix ESQL/C](http://www-03.ibm.com/software/products/en/esqlc) which 30 | can be installed using [IBM Informix CSDK](http://www-01.ibm.com/support/docview.wss?uid=swg27016673). 31 | 32 | 33 | ### Environment variables 34 | 35 | * [INFORMIXDIR](https://www-01.ibm.com/support/knowledgecenter/SSGU8G_12.1.0/com.ibm.sqlr.doc/ids_sqr_264.htm) - 36 | (e.g. `INFORMIXDIR=/opt/informix`) 37 | * [INFORMIXSERVER](https://www-01.ibm.com/support/knowledgecenter/SSGU8G_12.1.0/com.ibm.sqlr.doc/ids_sqr_266.htm) - 38 | (e.g. `INFORMIXSERVER=ol_informix1210`) 39 | * [INFORMIXSQLHOSTS](https://www-01.ibm.com/support/knowledgecenter/SSGU8G_12.1.0/com.ibm.sqlr.doc/ids_sqr_268.htm) 40 | * `PATH` to include `${INFORMIXDIR}/bin` - (e.g. `export PATH="${INFORMIXDIR}/bin:${PATH}"`) 41 | * `LD_LIBRARY_PATH` to include ESQL/C shared libraries - 42 | (e.g. `export LD_LIBRARY_PATH="${INFORMIXDIR}/lib:${INFORMIXDIR}/lib/esql:${LD_LIBRARY_PATH}"`) 43 | 44 | 45 | ### Debian/Ubuntu 46 | 47 | You'll need to patch `${INFORMIXDIR}/bin/esql` on Debian based systems. 48 | e.g. 49 | ``` bash 50 | $ cat esql-4.10.debian.patch | patch ${INFORMIXDIR}/bin/esql 51 | ``` 52 | 53 | 54 | 55 | ## Installation 56 | 57 | ``` bash 58 | $ npm install --save informix 59 | ``` 60 | 61 | 62 | 63 | ## Usage 64 | 65 | ```js 66 | var opts = { 67 | database : 'test@ol_informix1210', 68 | username : 'rockstar', 69 | password : 'secret' 70 | }; 71 | 72 | var informix = require( 'informix' )( opts ); 73 | ``` 74 | 75 | ```js 76 | var Informix = require( 'informix' ).Informix; 77 | var informix = new Informix( { database : 'test@ol_informix1210' } ); 78 | ``` 79 | 80 | ```js 81 | informix 82 | .query( "select tabname from systables where tabname like 'sys%auth';" ) 83 | .then( function ( cursor ) { 84 | return cursor.fetchAll( { close : true } ); 85 | } ) 86 | .then( function ( results ) { 87 | console.log( 'results:', results ); 88 | } ) 89 | .catch( function ( err ) { 90 | console.log( err ); 91 | } ); 92 | ``` 93 | 94 | ```js 95 | var ctx = informix.createContext(); 96 | 97 | ctx.begin() 98 | .then( function () { 99 | return ctx.query( "insert into tcustomers( fname, lname ) values( 'John', 'Smith' );" ); 100 | } ) 101 | .then( function ( cursor ) { 102 | console.log( 'id:', cursor.serial() ); 103 | return cursor.close(); 104 | } ) 105 | .then( function () { 106 | return ctx.commit(); 107 | } ) 108 | .then( function () { 109 | return ctx.end(); 110 | } ) 111 | .catch( function ( err ) { 112 | console.log( err ); 113 | } ); 114 | ``` 115 | 116 | 117 | 118 | ## API Documentation 119 | 120 | JSDoc generated API documentation can be found at [http://nukedzn.github.io/node-informix/docs/](http://nukedzn.github.io/node-informix/docs/). 121 | 122 | 123 | 124 | ## Contributing 125 | 126 | Contributions are welcome through GitHub pull requests ([using fork & pull model](https://help.github.com/articles/using-pull-requests/#fork--pull)). 127 | 128 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets' : [ 3 | { 4 | 'target_name' : 'esqlc', 5 | 'type' : 'none', 6 | 'conditions' : [ 7 | [ '(OS == "linux" or OS == "mac")', { 8 | 'actions' : [ { 9 | 'action_name' : 'esql-preprocess', 10 | 'inputs' : [ 11 | 'src/esqlc.ecpp' 12 | ], 13 | 'outputs' : [ 14 | '<(SHARED_INTERMEDIATE_DIR)/src/esqlc.cpp' 15 | ], 16 | 'action' : [ 'bash', './gyp/preprocessor.sh', '<(SHARED_INTERMEDIATE_DIR)/src' ] 17 | } ] 18 | } ], 19 | [ 'OS == "win"', { 20 | 'actions' : [ { 21 | 'action_name' : 'prepare', 22 | 'inputs' : [ 23 | 'src/esqlc.ecpp' 24 | ], 25 | 'outputs' : [ 26 | '<(INTERMEDIATE_DIR)/src/esqlc.ec' 27 | ], 28 | 'action' : [ 'copy', 'src/esqlc.ecpp', '<(INTERMEDIATE_DIR)/src/esqlc.ec' ] 29 | }, { 30 | 'action_name' : 'esql-preprocess', 31 | 'inputs' : [ 32 | '<(INTERMEDIATE_DIR)/src/esqlc.ec' 33 | ], 34 | 'outputs' : [ 35 | 'build/esqlc.c' 36 | ], 37 | 'action' : [ 'esql', '-thread', '-e', '<@(_inputs)' ] 38 | }, { 39 | 'action_name' : 'move', 40 | 'inputs' : [ 41 | 'build/esqlc.c' 42 | ], 43 | 'outputs' : [ 44 | '<(SHARED_INTERMEDIATE_DIR)/src/esqlc.cpp' 45 | ], 46 | 'action' : [ 'move', 'build/esqlc.c', '<(SHARED_INTERMEDIATE_DIR)/src/esqlc.cpp' ] 47 | } ] 48 | } ] 49 | ] 50 | }, 51 | { 52 | 'target_name' : 'ifx', 53 | 'dependencies' : [ 'esqlc' ], 54 | 'sources' : [ 55 | 'src/module.cpp', 56 | 'src/ifx/ifx.cpp', 57 | 'src/ifx/workers/connect.cpp', 58 | 'src/ifx/workers/disconnect.cpp', 59 | 'src/ifx/workers/stmtprepare.cpp', 60 | 'src/ifx/workers/stmtexec.cpp', 61 | 'src/ifx/workers/stmtfree.cpp', 62 | 'src/ifx/workers/fetch.cpp', 63 | 'src/ifx/workers/cursorclose.cpp', 64 | '<(SHARED_INTERMEDIATE_DIR)/src/esqlc.cpp', 65 | ], 66 | 'conditions' : [ 67 | [ '(OS == "linux" or OS == "mac")', { 68 | 'link_settings' : { 69 | 'libraries' : [ 70 | '-L$(INFORMIXDIR)/lib', 71 | '-L$(INFORMIXDIR)/lib/esql', 72 | ] 73 | } 74 | } ], 75 | [ 'OS == "mac"', { 76 | 'link_settings' : { 77 | 'libraries' : [ 78 | ' ?;' ) 44 | .then( function ( stmt ) { 45 | return stmt.exec( 0 ); 46 | } ) 47 | .then( function ( cursor ) { 48 | // Fetch all results and close cursor 49 | return cursor.fetchAll( { close : true } ); 50 | } ) 51 | .then( function ( results ) { 52 | console.log( '[stmt] results:', results ); 53 | } ) 54 | .catch( function ( err ) { 55 | console.log( err ); 56 | } ); 57 | 58 | -------------------------------------------------------------------------------- /examples/transaction.example.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * transaction.example.js 4 | * 5 | * Example of using transactions. 6 | * 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var Informix = require( '../' ).Informix; 12 | var informix = new Informix( { 13 | database : 'test@ol_informix1210', 14 | username : 'informix', 15 | password : 'informix' 16 | } ); 17 | 18 | 19 | // Error event listener 20 | informix.on( 'error', function ( err ) { 21 | console.log( '[event:error]', err ); 22 | } ); 23 | 24 | 25 | // Create a context to use with transactions 26 | var ctx = informix.createContext(); 27 | 28 | // Prepare statements 29 | var insert = ctx.prepare( 'insert into tcustomers( fname, lname ) values( ?, ? );' ); 30 | var select = ctx.prepare( 'select * from tcustomers where id = ?' ); 31 | 32 | var id; 33 | 34 | // Begin a transaction 35 | ctx.begin() 36 | .then( function () { 37 | // Insert data 38 | return insert 39 | .then( function ( stmt ) { 40 | return stmt.exec( [ 'John', 'Smith' ] ); 41 | } ); 42 | } ) 43 | .then( function ( cursor ) { 44 | id = cursor.serial(); 45 | cursor.close(); 46 | console.log( '[insert] id:', id ); 47 | 48 | // Rollback (or commit) the transaction 49 | return ctx.rollback(); 50 | //return ctx.commit(); 51 | } ) 52 | .then( function () { 53 | // Select data 54 | return select 55 | .then( function ( stmt ) { 56 | return stmt.exec( id ); 57 | } ); 58 | } ) 59 | .then( function ( cursor ) { 60 | return cursor.fetchAll( { close : true } ); 61 | } ) 62 | .then( function ( results ) { 63 | console.log( '[select] results:', results ); 64 | } ) 65 | .then( function () { 66 | // Free prepared statements 67 | return insert 68 | .then( function ( stmt ) { return stmt.free(); } ) 69 | .then( function () { 70 | return select 71 | .then( function ( stmt ) { return stmt.free(); } ); 72 | } ); 73 | } ) 74 | .then( function () { 75 | // End context 76 | return ctx.end(); 77 | } ) 78 | .catch( function ( err ) { 79 | console.log( err ); 80 | } ); 81 | 82 | -------------------------------------------------------------------------------- /gyp/preprocessor.sh: -------------------------------------------------------------------------------- 1 | #¡/bin/bash 2 | 3 | set -e 4 | 5 | cd src 6 | THREADLIB=POSIX esql -e -thread *.ecpp 7 | 8 | 9 | [[ -d $1 ]] || mkdir -p $1 10 | 11 | for f in *.C 12 | do 13 | mv $f "$1/$(basename $f .C).cpp" 14 | done 15 | 16 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | /** 5 | * A node.js native client for IBM Informix. 6 | * @module informix 7 | */ 8 | 9 | var Informix = require( './lib/informix' ); 10 | var Ifx = require( 'bindings' )( 'ifx' ).Ifx; 11 | 12 | 13 | /** 14 | * Create a new client by initialising a new {@link Informix} instance 15 | * 16 | * @param {object} opts - Options 17 | */ 18 | module.exports = function( opts ) { 19 | return new Informix( opts ); 20 | }; 21 | 22 | 23 | /** 24 | * IBM Informix client 25 | * 26 | * @type {Informix} 27 | */ 28 | module.exports.Informix = Informix; 29 | 30 | 31 | /** 32 | * Low level client binding 33 | * 34 | * @type {Ifx} 35 | */ 36 | module.exports.Ifx = Ifx; 37 | 38 | -------------------------------------------------------------------------------- /jsdoc-conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags" : { 3 | "allowUnknownTags" : true, 4 | "dictionaries" : [ "jsdoc", "closure" ] 5 | }, 6 | "source": { 7 | "include" : [ "index.js", "lib/" ], 8 | "includePattern" : ".+\\.js(doc)?$", 9 | "excludePattern" : "(^|\\/|\\\\)_" 10 | }, 11 | "plugins" : [], 12 | "templates" : { 13 | "cleverLinks" : false, 14 | "monospaceLinks" : false, 15 | "default" : { 16 | "includeDate" : false 17 | } 18 | }, 19 | "opts" : { 20 | "destination" : "./docs/", 21 | "encoding" : "utf8", 22 | "recurse" : true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/connection.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | var uuid = require( 'uuid' ); 5 | 6 | 7 | /** 8 | * Class representing a connection to a database 9 | * 10 | * @constructor 11 | * @param {Ifx} ifx - Native object instance 12 | * @param {Pool} [pool] - Connection pool associated with this connection 13 | * @param {object} [opts] - Constructor options 14 | */ 15 | var Connection = function ( ifx, pool, opts ) { 16 | 17 | var Pool = require( './pool' ); 18 | if (! (pool instanceof Pool) ) { 19 | opts = pool; 20 | pool = false; 21 | } 22 | 23 | 24 | // Privileged data 25 | this.$ = { 26 | id : uuid.v4(), 27 | index : -1, 28 | ifx : ifx, 29 | pool : pool 30 | }; 31 | 32 | this.options( opts ); 33 | 34 | }; 35 | 36 | 37 | /** 38 | * Acquire this connection from the pool 39 | * 40 | * @param {string} context - Context ID to be used when acquiring the connection 41 | * @return {Promise.} - A promise to a connection object or 42 | * an Error if rejected. 43 | */ 44 | Connection.prototype.acquire = function ( context ) { 45 | if (! this.$.pool ) { 46 | return Promise.resolve( this ); 47 | } 48 | 49 | return this.$.pool.acquire( context || this ); 50 | }; 51 | 52 | 53 | /** 54 | * Open a connection to a database 55 | * 56 | * @param {object} params - Connection parameters 57 | * @return {Promise.} - A promise to a connection object or an 58 | * Error if rejected. 59 | */ 60 | Connection.prototype.connect = function ( params ) { 61 | 62 | var self = this; 63 | 64 | // set INFORMIXSERVER environment variable if needed 65 | if ( params.database ) { 66 | 67 | var server = params.database.split( '@' )[ 1 ]; 68 | if ( server && ( process.env.INFORMIXSERVER !== server ) ) { 69 | process.env.INFORMIXSERVER = server; 70 | } 71 | 72 | } 73 | 74 | return new Promise( function ( resolve, reject ) { 75 | params.id = self.$.id; 76 | self.$.ifx.connect( params, function ( err, connid ) { 77 | if ( err ) { 78 | return reject( err ); 79 | } 80 | 81 | resolve( self ); 82 | } ); 83 | } ); 84 | 85 | }; 86 | 87 | 88 | /** 89 | * Return the connection ID 90 | * 91 | * @return {string} - ID generated for this connection. 92 | */ 93 | Connection.prototype.id = function () { 94 | return this.$.id; 95 | }; 96 | 97 | 98 | /** 99 | * Return the instance of the native binding used with this connection 100 | * 101 | * @return {Ifx} - Native object instance. 102 | */ 103 | Connection.prototype.ifx = function () { 104 | return this.$.ifx; 105 | }; 106 | 107 | 108 | /** 109 | * Return the connection pool index 110 | * 111 | * @return {integer} - Connection pool index associated with this connection. 112 | */ 113 | Connection.prototype.index = function () { 114 | return this.$.index; 115 | }; 116 | 117 | 118 | /** 119 | * Set options 120 | * 121 | * @param {object} opts - Options 122 | */ 123 | Connection.prototype.options = function ( opts ) { 124 | this.$.opts = opts || {}; 125 | 126 | if ( this.$.opts.index !== undefined ) { 127 | this.$.index = this.$.opts.index; 128 | } 129 | }; 130 | 131 | 132 | /** 133 | * Prepare a statement 134 | * 135 | * @param {string} sql - SQL statement to prepare 136 | * @param {object} [opts] - Options to be passed to the {@link Statement} constructor 137 | * 138 | * @return {Promise.} - A promise to a statement object or an 139 | * Error if rejected. 140 | */ 141 | Connection.prototype.prepare = function ( sql, opts ) { 142 | 143 | var Statement = require( './statement' ); 144 | var stmt = new Statement( this, opts ); 145 | return stmt.prepare( sql ); 146 | 147 | }; 148 | 149 | 150 | /** 151 | * Release this connection back to the pool 152 | * 153 | * @param {string} context - Context ID to be used when releasing the connection 154 | */ 155 | Connection.prototype.release = function ( context ) { 156 | if ( this.$.pool ) { 157 | this.$.pool.release( context || this ); 158 | } 159 | }; 160 | 161 | 162 | 163 | module.exports = Connection; 164 | 165 | -------------------------------------------------------------------------------- /lib/context.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | var uuid = require( 'uuid' ); 5 | 6 | var Statement = require( './statement' ); 7 | 8 | 9 | /** 10 | * Class representing a context which can be used to execute SQL statements in 11 | * chronological order and use transactions safely within the scope of the 12 | * context. 13 | * 14 | * @constructor 15 | * @param {Pool} - Connection pool to be used for this context 16 | */ 17 | var Context = function ( pool ) { 18 | 19 | // Privileged data 20 | this.$ = { 21 | conn : false, 22 | id : uuid.v4(), 23 | pool : pool, 24 | stmts : { 25 | cache : [], 26 | begin : false, 27 | commit : false, 28 | rollback : false 29 | }, 30 | transaction : false, 31 | }; 32 | 33 | 34 | // Reserve a connection for this context 35 | this.$.conn = this.$.pool.reserve( this.id() ); 36 | 37 | }; 38 | 39 | 40 | /** 41 | * Begin a transaction for this context 42 | * 43 | * @return {Promise.} - A promise which resolves after openning a 44 | * transaction or an Error if rejected. 45 | */ 46 | Context.prototype.begin = function () { 47 | 48 | var self = this; 49 | var stmt; 50 | 51 | if (! this.$.stmts.begin ) { 52 | stmt = new Statement( this.$.conn, { context : this.id(), id : 'begin' } ); 53 | stmt = this.$.stmts.begin = stmt.prepare( 'begin;' ); 54 | } else { 55 | stmt = this.$.stmts.begin; 56 | } 57 | 58 | return stmt 59 | .then( function ( stmt ) { 60 | self.$.transaction = true; 61 | return stmt.exec(); 62 | } ) 63 | .then( function ( cursor ) { 64 | return cursor.close(); 65 | } ); 66 | 67 | }; 68 | 69 | 70 | /** 71 | * Commit a transaction opened for this context 72 | * 73 | * @return {Promise.} - A promise which resolves after committing a 74 | * transaction or an Error if rejected. 75 | */ 76 | Context.prototype.commit = function () { 77 | 78 | var self = this; 79 | var stmt; 80 | 81 | if (! this.$.stmts.commit ) { 82 | stmt = new Statement( this.$.conn, { context : this.id(), id : 'commit' } ); 83 | stmt = this.$.stmts.commit = stmt.prepare( 'commit;' ); 84 | } else { 85 | stmt = this.$.stmts.commit; 86 | } 87 | 88 | return stmt 89 | .then( function ( stmt ) { 90 | self.$.transaction = false; 91 | return stmt.exec(); 92 | } ) 93 | .then( function ( cursor ) { 94 | return cursor.close(); 95 | } ); 96 | 97 | }; 98 | 99 | 100 | /** 101 | * Rollback any open transactions and end the context by releasing the connection 102 | * used back to the pool. 103 | * 104 | * @return {Promise} - A promise to a context ID string which 105 | * resolved after the context is ended or an Error if rejected. 106 | */ 107 | Context.prototype.end = function () { 108 | 109 | var self = this; 110 | var promise; 111 | 112 | if ( self.$.transaction ) { 113 | promise = self.rollback(); 114 | } else { 115 | promise = Promise.resolve(); 116 | } 117 | 118 | if ( self.$.stmts.begin ) { 119 | promise = promise 120 | .then( function () { 121 | return self.$.stmts.begin.then( function ( stmt ) { 122 | return stmt.free(); 123 | } ); 124 | } ); 125 | } 126 | 127 | if ( self.$.stmts.commit ) { 128 | promise = promise 129 | .then( function () { 130 | return self.$.stmts.commit.then( function ( stmt ) { 131 | return stmt.free(); 132 | } ); 133 | } ); 134 | } 135 | 136 | if ( self.$.stmts.rollback ) { 137 | promise = promise 138 | .then( function () { 139 | return self.$.stmts.rollback.then( function ( stmt ) { 140 | return stmt.free(); 141 | } ); 142 | } ); 143 | } 144 | 145 | self.$.stmts.cache.forEach( function( stmt ) { 146 | promise = promise 147 | .then( function() { 148 | return stmt 149 | .then( function( s ) { 150 | return s.free(); 151 | } ) 152 | .catch( function( err ) { 153 | if ( err.message === 'Invalid statement ID.' ) { return; } 154 | throw err; 155 | } ); 156 | } ); 157 | } ); 158 | 159 | 160 | return promise 161 | .then( function () { 162 | return self.$.pool.close( self.id() ); 163 | } ) 164 | .then( function () { 165 | return self.id(); 166 | } ); 167 | 168 | }; 169 | 170 | 171 | /** 172 | * Return the context ID 173 | * 174 | * @return {string} - Context ID. 175 | */ 176 | Context.prototype.id = function () { 177 | return this.$.id; 178 | }; 179 | 180 | 181 | /** 182 | * Prepare a statement which is only valid to be executed within this context 183 | * 184 | * @param {string} sql - SQL statement to prepare 185 | * @return {Promise.} - A promise to a statement object or an 186 | * Error if rejected. 187 | */ 188 | Context.prototype.prepare = function ( sql ) { 189 | var stmt = new Statement( this.$.conn, { context : this.id() } ); 190 | stmt = stmt.prepare( sql ); 191 | this.$.stmts.cache.push( stmt ); 192 | return stmt; 193 | }; 194 | 195 | 196 | /** 197 | * Run a query within this context 198 | * 199 | * @param {string} sql - SQL query to run 200 | * @return {Promise.} - A promise to a results cursor or an Error 201 | * if rejected. 202 | */ 203 | Context.prototype.query = function ( sql ) { 204 | var stmt = new Statement( this.$.conn, { context : this.id(), reusable : false } ); 205 | 206 | return stmt.prepare( sql ) 207 | .then( function ( stmt ) { 208 | return stmt.exec(); 209 | } ); 210 | }; 211 | 212 | 213 | /** 214 | * Rollback a transaction opened for this context 215 | * 216 | * @return {Promise.} - A promise which resolves after rolling-back a 217 | * transaction or an Error if rejected. 218 | */ 219 | Context.prototype.rollback = function () { 220 | 221 | var self = this; 222 | var stmt; 223 | 224 | if (! this.$.stmts.rollback ) { 225 | stmt = new Statement( this.$.conn, { context : this.id(), id : 'rollback' } ); 226 | stmt = this.$.stmts.rollback = stmt.prepare( 'rollback;' ); 227 | } else { 228 | stmt = this.$.stmts.rollback; 229 | } 230 | 231 | return stmt 232 | .then( function ( stmt ) { 233 | self.$.transaction = false; 234 | return stmt.exec(); 235 | } ) 236 | .then( function ( cursor ) { 237 | return cursor.close(); 238 | } ); 239 | 240 | }; 241 | 242 | 243 | 244 | module.exports = Context; 245 | 246 | -------------------------------------------------------------------------------- /lib/cursor.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | var uuid = require( 'uuid' ); 5 | 6 | 7 | /** 8 | * Class representing a cursor to a resultset 9 | * 10 | * @constructor 11 | * @param {Connection} conn - Connection object 12 | * @param {Statement} stmt - Statement object 13 | * @param {object} [opts] - Constructor options 14 | * @param {string} [opts.id] - Cursor id to use 15 | */ 16 | var Cursor = function ( conn, stmt, opts ) { 17 | 18 | // privileged data 19 | this.$ = { 20 | conn : conn, 21 | ifx : conn.ifx(), 22 | stmt : stmt 23 | }; 24 | 25 | this.options( opts ); 26 | 27 | }; 28 | 29 | 30 | /** 31 | * Return the context ID associated with this cursor 32 | * 33 | * @return {string} - Context ID 34 | */ 35 | Cursor.prototype.context = function () { 36 | return this.$.stmt.context(); 37 | }; 38 | 39 | 40 | /** 41 | * Return cursor ID 42 | * 43 | * @return {string} - ID generated for this cursor object. 44 | */ 45 | Cursor.prototype.id = function () { 46 | return this.$.id; 47 | }; 48 | 49 | 50 | /** 51 | * Close the results cursor 52 | * 53 | * @return {Promise.} - A promise to string which would contain 54 | * the ID of the closed cursor or an Error if rejected. 55 | */ 56 | Cursor.prototype.close = function () { 57 | 58 | var self = this; 59 | 60 | return self.$.conn.acquire( self.context() ) 61 | .then( function ( conn ) { 62 | return new Promise( function ( resolve, reject ) { 63 | self.$.ifx.close( self.$.id, function ( err, curid ) { 64 | if ( err ) { 65 | return reject( err ); 66 | } 67 | 68 | self.$.conn.release( self.context() ); 69 | 70 | if ( self.$.stmt && ( self.$.stmt.flags().reusable === false ) ) { 71 | self.$.stmt.free() 72 | .then( function ( stmtid ) { 73 | resolve( curid ); 74 | } ) 75 | .catch( reject ); 76 | } else { 77 | resolve( curid ); 78 | } 79 | } ); 80 | } ); 81 | } ) 82 | .catch( function ( err ) { 83 | self.$.conn.release( self.context() ); 84 | throw err; 85 | } ); 86 | 87 | }; 88 | 89 | 90 | /** 91 | * Fetch a result 92 | * 93 | * @return {Promise.} A promise to a results array (or null 94 | * if no more results) or an Error if rejected. 95 | */ 96 | Cursor.prototype.fetch = function () { 97 | 98 | var self = this; 99 | 100 | return self.$.conn.acquire( self.context() ) 101 | .then( function ( conn ) { 102 | return new Promise( function ( resolve, reject ) { 103 | self.$.ifx.fetch( self.$.id, function ( err, result ) { 104 | if ( err ) { 105 | return reject( err ); 106 | } 107 | 108 | self.$.conn.release( self.context() ); 109 | resolve( result ); 110 | } ); 111 | } ); 112 | } ) 113 | .catch( function ( err ) { 114 | self.$.conn.release( self.context() ); 115 | throw err; 116 | } ); 117 | 118 | }; 119 | 120 | 121 | /** 122 | * Fetch all results 123 | * 124 | * @param {object} [opts] - Options 125 | * @param {boolean} [opts.close=false] - Flag indicating to close the cursor after 126 | * fetching all results. 127 | * 128 | * @return {Promise.} A promise to an array of results or an 129 | * Error if rejected. 130 | */ 131 | Cursor.prototype.fetchAll = function ( opts ) { 132 | 133 | var self = this; 134 | 135 | if (! opts ) { opts = {}; } 136 | 137 | return self.$.conn.acquire( self.context() ) 138 | .then( function ( conn ) { 139 | return new Promise( function ( resolve, reject ) { 140 | var results = []; 141 | var fetcher = function ( err, result ) { 142 | if ( err ) { 143 | return reject( err ); 144 | } 145 | 146 | if ( result ) { 147 | results.push( result ); 148 | return self.$.ifx.fetch( self.$.id, fetcher ); 149 | } 150 | 151 | self.$.conn.release( self.context() ); 152 | resolve( results ); 153 | }; 154 | 155 | self.$.ifx.fetch( self.$.id, fetcher ); 156 | } ); 157 | } ) 158 | .catch( function ( err ) { 159 | self.$.conn.release( self.context() ); 160 | throw err; 161 | } ) 162 | .then( function ( results ) { 163 | if ( opts.close === true ) { 164 | return self.close() 165 | .then( function ( curid ) { 166 | return results; 167 | } ); 168 | } 169 | 170 | return results; 171 | } ); 172 | 173 | }; 174 | 175 | 176 | /** 177 | * Set options 178 | * 179 | * @param {object} opts - Options 180 | */ 181 | Cursor.prototype.options = function ( opts ) { 182 | this.$.opts = opts || {}; 183 | 184 | if ( (typeof this.$.opts.id) === 'string' ) { 185 | this.$.id = this.$.opts.id; 186 | } else { 187 | this.$.id = '_' + uuid.v4().replace( /\-/g, 's' ); 188 | } 189 | }; 190 | 191 | 192 | /** 193 | * Return the serial value generated after executing an insert statement. 194 | * 195 | * @return {number} - Generated serial value 196 | */ 197 | Cursor.prototype.serial = function () { 198 | return this.$.ifx.serial( this.$.id ); 199 | }; 200 | 201 | 202 | 203 | module.exports = Cursor; 204 | 205 | -------------------------------------------------------------------------------- /lib/informix.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | var EventEmitter = require( 'events' ).EventEmitter; 5 | 6 | var Context = require( './context' ); 7 | var Pool = require( './pool' ); 8 | 9 | 10 | /** 11 | * Class representing the IBM Informix client 12 | * 13 | * @constructor 14 | * @param {object} opts - Constructor options 15 | */ 16 | var Informix = function ( opts ) { 17 | 18 | EventEmitter.call( this ); 19 | 20 | // Privileged data 21 | this.$ = { 22 | pool : new Pool() 23 | }; 24 | 25 | this.options( opts ); 26 | 27 | }; 28 | 29 | Informix.prototype = Object.create( EventEmitter.prototype ); 30 | 31 | 32 | /** 33 | * Create a new context to execute SQL statements and use transactions safely 34 | * 35 | * @return {Context} - A context object. 36 | */ 37 | Informix.prototype.createContext = function () { 38 | return new Context( this.$.pool ); 39 | }; 40 | 41 | 42 | /** 43 | * Set options 44 | * 45 | * @param {object} opts - Options 46 | */ 47 | Informix.prototype.options = function ( opts ) { 48 | this.$.opts = opts || {}; 49 | 50 | var popts = { 51 | database : this.$.opts.database, 52 | username : this.$.opts.username, 53 | password : this.$.opts.password 54 | }; 55 | 56 | if ( this.$.opts.pool ) { 57 | for ( var key in this.$.opts.pool ) { 58 | popts[ key ] = this.$.opts.pool[ key ]; 59 | } 60 | } 61 | 62 | this.$.pool.options( popts ); 63 | }; 64 | 65 | 66 | /** 67 | * Prepare a statement 68 | * 69 | * @param {string} sql - SQL statement to prepare 70 | * @param {string} [name] - Statement name 71 | * @return {Promise.} - A promise to a statement object or an 72 | * Error if rejected. 73 | */ 74 | Informix.prototype.prepare = function ( sql, name ) { 75 | 76 | var self = this; 77 | var opts; 78 | 79 | if ( ( typeof name === 'string' ) && name.length > 0 ) { 80 | opts = { id : name }; 81 | } 82 | 83 | return self.$.pool.acquire() 84 | .then( function ( conn ) { 85 | self.$.pool.release( conn ); 86 | return conn.prepare( sql, opts ); 87 | } ) 88 | .catch( function ( err ) { 89 | self.emit( 'error', err ); 90 | throw err; 91 | } ); 92 | 93 | }; 94 | 95 | 96 | /** 97 | * Run a SQL query 98 | * 99 | * @param {string} sql - SQL query to run 100 | * @return {Promise.} - A promise to a results cursor or an Error 101 | * if rejected. 102 | */ 103 | Informix.prototype.query = function ( sql ) { 104 | 105 | var self = this; 106 | 107 | return self.$.pool.acquire() 108 | .then( function ( conn ) { 109 | self.$.pool.release( conn ); 110 | return conn.prepare( sql, { reusable : false } ); 111 | } ) 112 | .then( function ( stmt ) { 113 | return stmt.exec(); 114 | } ) 115 | .catch( function ( err ) { 116 | self.emit( 'error', err ); 117 | throw err; 118 | } ); 119 | 120 | }; 121 | 122 | 123 | 124 | module.exports = Informix; 125 | 126 | -------------------------------------------------------------------------------- /lib/pool.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | var debug = require( 'debug' )( 'informix:pool' ); 5 | var Ifx = require( 'bindings' )( 'ifx' ).Ifx; 6 | 7 | var Connection = require( './connection' ); 8 | 9 | 10 | /** 11 | * Class representing a connection pool 12 | * 13 | * @constructor 14 | * @param {object} [opts] - Constructor options 15 | */ 16 | var Pool = function ( opts ) { 17 | this.$reset( opts ); 18 | }; 19 | 20 | 21 | /** 22 | * Internal helper function to add a new connection to the pool 23 | * 24 | * @private 25 | * @return {Connection|undefined} - A connection object if successful or 26 | * undefined if unsuccessful. 27 | */ 28 | Pool.prototype.$add = function () { 29 | 30 | var conn; 31 | var index = ( this.$.pool.length ? this.$.pool.length : 0 ); 32 | 33 | if ( index < this.$.max ) { 34 | var params = { 35 | database : this.$.opts.database 36 | }; 37 | 38 | if ( this.$.opts.username ) { params.username = this.$.opts.username; } 39 | if ( this.$.opts.password ) { params.password = this.$.opts.password; } 40 | 41 | 42 | debug( '($add) adding new connection to the pool, index: %d', index ); 43 | conn = new Connection( this.$.ifx, this, { index : index } ); 44 | 45 | this.$.pool.push( conn ); 46 | this.$.promises.push( conn.connect( params ) ); 47 | this.$.resolvers.push( [] ); 48 | } 49 | 50 | 51 | return conn; 52 | 53 | }; 54 | 55 | 56 | /** 57 | * Internal helper function to get the next available connection 58 | * 59 | * @private 60 | * @return {Connection} - Connection object 61 | */ 62 | Pool.prototype.$next = function () { 63 | 64 | var self = this; 65 | var conn; 66 | 67 | if ( this.$.pool.length ) { 68 | 69 | // There should be at least one connection in the pool, try to filter out any 70 | // idle connections. 71 | var filter = function ( element, index, array ) { 72 | // There won't be any resolvers left when all the promises for this 73 | // connection are resolved. 74 | return ( self.$.resolvers[ index ].length === 0 ); 75 | }; 76 | 77 | conn = this.$.pool.find( filter ); 78 | 79 | } 80 | 81 | 82 | // Check whether there was an idle connection which could be used. 83 | if (! conn ) { 84 | 85 | // No idle connections found, try to add a new connection to the pool. 86 | conn = this.$add(); 87 | 88 | if (! conn ) { 89 | 90 | // No idle connections found and couldn't add a new connection to 91 | // the pool, re-use existing connections in a round robin fashion. 92 | debug( '($next) no idle connections, reusing %d', this.$.next ); 93 | conn = this.$.pool[ this.$.next ]; 94 | if ( ++this.$.next >= this.$.pool.length ) { 95 | this.$.next = 0; 96 | } 97 | 98 | } 99 | } 100 | 101 | 102 | return conn; 103 | 104 | }; 105 | 106 | 107 | /** 108 | * Internal helper function to return a promise for a connection 109 | * 110 | * @private 111 | * @param {Connection|string} c - Connection object or context ID string 112 | * @return {Promise.} - A promise to a connection object or 113 | * an Error if rejected. 114 | */ 115 | Pool.prototype.$promise = function ( c ) { 116 | 117 | var self = this; 118 | var promise; 119 | 120 | // Take a copy of the existing promise (first ever promise will be from connect()) 121 | // and replace it with a new promise for this connection/context which will be resolved 122 | // when released back to the pool. 123 | 124 | if ( c instanceof Connection ) { 125 | promise = this.$.promises[ c.index() ]; 126 | this.$.promises[ c.index() ] = new Promise( function ( resolve, reject ) { 127 | self.$.resolvers[ c.index() ].push( resolve ); 128 | } ); 129 | 130 | debug( '($promise)[ %d ] waiters: %d', c.index(), ( this.$.resolvers[ c.index() ].length - 1 ) ); 131 | } else { 132 | var ctx = this.$.contexts[ c ]; 133 | promise = ctx.promise; 134 | ctx.promise = new Promise( function ( resolve, reject ) { 135 | ctx.resolvers.push( resolve ); 136 | } ); 137 | 138 | debug( '($promise)[ %s ] waiters: %d', c, ( ctx.resolvers.length - 1 ) ); 139 | } 140 | 141 | 142 | // Return a promise chain which will wait for the last promise created for this 143 | // connection/context and then return a connection object. 144 | return promise 145 | .then( function ( conn ) { 146 | return conn; 147 | } ); 148 | 149 | }; 150 | 151 | 152 | /** 153 | * Internal helper function to reset the connection pool 154 | * 155 | * @private 156 | * @param {object} [opts] - Options 157 | */ 158 | Pool.prototype.$reset = function ( opts ) { 159 | 160 | // privileged data 161 | this.$ = { 162 | ifx : new Ifx(), 163 | min : 0, 164 | max : 5, 165 | next : 0, 166 | opts : {}, 167 | pool : [], 168 | contexts : {}, 169 | promises : [], 170 | resolvers : [] 171 | }; 172 | 173 | this.options( opts ); 174 | 175 | }; 176 | 177 | 178 | /** 179 | * Acquire a connection from the pool 180 | * 181 | * @param {Connection|string} [c] - A connection object or a context ID string 182 | * to indicate which connection to be aquired from the pool. 183 | * If this is null, a randon connection will be aquired from the pool. 184 | * 185 | * @return {Promise.} - A promise to a connection object or 186 | * an Error if rejected. 187 | */ 188 | Pool.prototype.acquire = function ( c ) { 189 | 190 | var self = this; 191 | 192 | if ( (typeof c) === 'string' ) { 193 | if (! this.$.contexts[ c ] ) { 194 | this.reserve( c ); 195 | } 196 | 197 | debug( '(acquire)[ %s ]', c ); 198 | return this.$promise( c ); 199 | } 200 | 201 | 202 | var conn; 203 | if ( c instanceof Connection ) { 204 | conn = c; 205 | } else { 206 | conn = this.$next(); 207 | } 208 | 209 | debug( '(acquire)[ %d ]', conn.index() ); 210 | return this.$promise( conn ); 211 | 212 | }; 213 | 214 | 215 | /** 216 | * Close a context and release the reserved connection back to the pool 217 | * 218 | * @param {string} context - Context ID 219 | * @return {Promise} - A promise to a context ID string which 220 | * resolves after the context is closed or an Error if rejected. 221 | */ 222 | Pool.prototype.close = function ( context ) { 223 | 224 | var self = this; 225 | var ctx = this.$.contexts[ context ]; 226 | 227 | debug( '(close)[ %s ] pending promises: %d', context, ctx.resolvers.length ); 228 | return ctx.promise 229 | .then( function ( conn ) { 230 | ctx.resolve( conn ); 231 | delete self.$.contexts[ context ]; 232 | 233 | return context; 234 | } ); 235 | 236 | }; 237 | 238 | 239 | /** 240 | * Set options 241 | * 242 | * @param {object} opts - Options 243 | */ 244 | Pool.prototype.options = function ( opts ) { 245 | this.$.opts = opts || {}; 246 | 247 | if ( this.$.opts.min ) { this.$.min = parseInt( this.$.opts.min ); } 248 | if ( this.$.opts.max ) { this.$.max = parseInt( this.$.opts.max ); } 249 | }; 250 | 251 | 252 | /** 253 | * Release a connection back to the pool 254 | * 255 | * @param {Connection|string} c - Connection object or a context ID 256 | */ 257 | Pool.prototype.release = function ( c ) { 258 | 259 | var conn, resolve; 260 | 261 | if ( c instanceof Connection ) { 262 | debug( '(release)[ %d ]', c.index() ); 263 | 264 | conn = c; 265 | resolve = this.$.resolvers[ c.index() ].shift(); 266 | } else { 267 | debug( '(release)[ %s ]', c ); 268 | 269 | var ctx = this.$.contexts[ c ]; 270 | conn = ctx.conn; 271 | resolve = ctx.resolvers.shift(); 272 | } 273 | 274 | 275 | // Release the connection by resolving the first available promise. There will 276 | // be at least one promise to resolve since we always return a new promise to 277 | // a connection. 278 | resolve( conn ); 279 | 280 | }; 281 | 282 | 283 | /** 284 | * Reserve a connection to be used with a new context 285 | * 286 | * @param {string} context - Context ID 287 | * @return {Connection} - A connection object reserved for this context. 288 | */ 289 | Pool.prototype.reserve = function ( context ) { 290 | 291 | if ( this.$.contexts[ context ] ) { 292 | throw new Error( 'A connection is already reserved for the given context.' ); 293 | } 294 | 295 | debug( '(reserve)[ %s ]', context ); 296 | 297 | var conn = this.$next(); 298 | var ctx = this.$.contexts[ context ] = { 299 | conn : conn, 300 | promise : this.$.promises[ conn.index() ], 301 | resolve : false, 302 | resolvers : [] 303 | }; 304 | 305 | this.$.promises[ conn.index() ] = new Promise( function ( resolve, reject ) { 306 | ctx.resolve = resolve; 307 | } ); 308 | 309 | 310 | return conn; 311 | 312 | }; 313 | 314 | 315 | 316 | module.exports = Pool; 317 | 318 | -------------------------------------------------------------------------------- /lib/statement.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | var crypto = require( 'crypto' ); 5 | 6 | 7 | /** 8 | * Class representing a prepared statement 9 | * 10 | * @constructor 11 | * @param {Connection} conn - Connection object 12 | * @param {object} [opts] - Constructor options 13 | * @param {boolean} [opts.reusable=true] - Flag to indicate whether this statement 14 | * should be reusable. 15 | */ 16 | var Statement = function ( conn, opts ) { 17 | 18 | // privileged data 19 | this.$ = { 20 | conn : conn, 21 | id : false, 22 | ifx : conn.ifx() 23 | }; 24 | 25 | this.options( opts ); 26 | 27 | }; 28 | 29 | 30 | /** 31 | * Return the context ID associated with this statement 32 | * 33 | * @return {string} - Context ID 34 | */ 35 | Statement.prototype.context = function () { 36 | return this.$.opts.context; 37 | }; 38 | 39 | 40 | /** 41 | * Execute the prepared statement 42 | * 43 | * @param {string|Array} [args] - Arguments to be used when executing the 44 | * prepared statement. 45 | * @param {object} [opts] - Cursor options. 46 | * 47 | * @return {Promise.} - A promise to a results cursor object or 48 | * an Error if rejected. 49 | */ 50 | Statement.prototype.exec = function ( args, opts ) { 51 | 52 | var self = this; 53 | if ( ( typeof args ) === 'object' && !Array.isArray( args ) ) { 54 | opts = args; 55 | args = undefined; 56 | } else { 57 | opts = opts || {}; 58 | } 59 | 60 | return self.$.conn.acquire( self.context() ) 61 | .then( function ( conn ) { 62 | return new Promise( function ( resolve, reject ) { 63 | var Cursor = require( './cursor' ); 64 | var cursor = new Cursor( self.$.conn, self, opts ); 65 | 66 | if ( args !== undefined ) { 67 | self.$.ifx.exec( conn.id(), self.$.id, cursor.id(), args, function ( err, curid ) { 68 | if ( err ) { 69 | return reject( err ); 70 | } 71 | 72 | self.$.conn.release( self.context() ); 73 | resolve( cursor ); 74 | } ); 75 | } else { 76 | self.$.ifx.exec( conn.id(), self.$.id, cursor.id(), function ( err, curid ) { 77 | if ( err ) { 78 | return reject( err ); 79 | } 80 | 81 | self.$.conn.release( self.context() ); 82 | resolve( cursor ); 83 | } ); 84 | } 85 | } ); 86 | } ) 87 | .catch( function ( err ) { 88 | self.$.conn.release( self.context() ); 89 | throw err; 90 | } ); 91 | 92 | }; 93 | 94 | 95 | /** 96 | * Return statement flags 97 | * 98 | * @return {object} flags - Statement flags 99 | * @return {boolean} flags.reusable - Flag to indicate whether this statement 100 | * should be reusable. 101 | */ 102 | Statement.prototype.flags = function () { 103 | 104 | var flags = { 105 | reusable : ( this.$.opts.reusable === false ? false : true ) 106 | }; 107 | 108 | return flags; 109 | 110 | }; 111 | 112 | 113 | /** 114 | * Free the prepared statement 115 | * 116 | * @return {Promise.} - A promise to a string which would 117 | * contain the statement ID freed or an Error if rejected. 118 | */ 119 | Statement.prototype.free = function () { 120 | 121 | var self = this; 122 | 123 | return self.$.conn.acquire( self.context() ) 124 | .then( function ( conn ) { 125 | return new Promise( function ( resolve, reject ) { 126 | self.$.ifx.free( conn.id(), self.$.id, function ( err, stmtid ) { 127 | if ( err ) { 128 | return reject( err ); 129 | } 130 | 131 | self.$.conn.release( self.context() ); 132 | resolve( stmtid ); 133 | } ); 134 | } ); 135 | } ) 136 | .catch( function ( err ) { 137 | self.$.conn.release( self.context() ); 138 | throw err; 139 | } ); 140 | 141 | }; 142 | 143 | 144 | /** 145 | * Set options 146 | * 147 | * @param {object} opts - Options 148 | */ 149 | Statement.prototype.options = function ( opts ) { 150 | this.$.opts = opts || {}; 151 | 152 | if ( (typeof this.$.opts.id) === 'string' ) { 153 | this.$.id = this.$.opts.id; 154 | } 155 | }; 156 | 157 | 158 | /** 159 | * Prepare a statement 160 | * 161 | * @param {string} sql - SQL statement to prepare 162 | * @return {Promise.} - A promise to a statement object or an 163 | * Error if rejected. 164 | */ 165 | Statement.prototype.prepare = function ( sql ) { 166 | 167 | var self = this; 168 | 169 | return self.$.conn.acquire( self.context() ) 170 | .then( function ( conn ) { 171 | return new Promise( function ( resolve, reject ) { 172 | if (! self.$.id ) { 173 | self.$.id = '_' + crypto.createHash( 'sha256' ).update( sql ).digest( 'hex' ); 174 | } 175 | 176 | self.$.ifx.prepare( conn.id(), self.$.id, sql, function ( err, stmtid ) { 177 | if ( err ) { 178 | return reject( err ); 179 | } 180 | 181 | self.$.conn.release( self.context() ); 182 | resolve( self ); 183 | } ); 184 | } ); 185 | } ) 186 | .catch( function ( err ) { 187 | self.$.conn.release( self.context() ); 188 | throw err; 189 | } ); 190 | 191 | }; 192 | 193 | 194 | 195 | module.exports = Statement; 196 | 197 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "informix", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "assertion-error": { 8 | "version": "1.1.0", 9 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 10 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 11 | "dev": true 12 | }, 13 | "bindings": { 14 | "version": "1.3.1", 15 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.1.tgz", 16 | "integrity": "sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew==" 17 | }, 18 | "chai": { 19 | "version": "4.2.0", 20 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", 21 | "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", 22 | "dev": true, 23 | "requires": { 24 | "assertion-error": "^1.1.0", 25 | "check-error": "^1.0.2", 26 | "deep-eql": "^3.0.1", 27 | "get-func-name": "^2.0.0", 28 | "pathval": "^1.1.0", 29 | "type-detect": "^4.0.5" 30 | } 31 | }, 32 | "check-error": { 33 | "version": "1.0.2", 34 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", 35 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", 36 | "dev": true 37 | }, 38 | "debug": { 39 | "version": "4.1.1", 40 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 41 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 42 | "requires": { 43 | "ms": "^2.1.1" 44 | } 45 | }, 46 | "deep-eql": { 47 | "version": "3.0.1", 48 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", 49 | "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", 50 | "dev": true, 51 | "requires": { 52 | "type-detect": "^4.0.0" 53 | } 54 | }, 55 | "formatio": { 56 | "version": "1.1.1", 57 | "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz", 58 | "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", 59 | "dev": true, 60 | "requires": { 61 | "samsam": "~1.1" 62 | } 63 | }, 64 | "get-func-name": { 65 | "version": "2.0.0", 66 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", 67 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", 68 | "dev": true 69 | }, 70 | "inherits": { 71 | "version": "2.0.1", 72 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", 73 | "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", 74 | "dev": true 75 | }, 76 | "lolex": { 77 | "version": "1.3.2", 78 | "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz", 79 | "integrity": "sha1-fD2mL/yzDw9agKJWbKJORdigHzE=", 80 | "dev": true 81 | }, 82 | "moment": { 83 | "version": "2.23.0", 84 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.23.0.tgz", 85 | "integrity": "sha512-3IE39bHVqFbWWaPOMHZF98Q9c3LDKGTmypMiTM2QygGXXElkFWIH7GxfmlwmY2vwa+wmNsoYZmG2iusf1ZjJoA==", 86 | "dev": true 87 | }, 88 | "ms": { 89 | "version": "2.1.1", 90 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 91 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 92 | }, 93 | "nan": { 94 | "version": "2.12.1", 95 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", 96 | "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==" 97 | }, 98 | "pathval": { 99 | "version": "1.1.0", 100 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", 101 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", 102 | "dev": true 103 | }, 104 | "samsam": { 105 | "version": "1.1.2", 106 | "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz", 107 | "integrity": "sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc=", 108 | "dev": true 109 | }, 110 | "sinon": { 111 | "version": "1.17.7", 112 | "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz", 113 | "integrity": "sha1-RUKk9JugxFwF6y6d2dID4rjv4L8=", 114 | "dev": true, 115 | "requires": { 116 | "formatio": "1.1.1", 117 | "lolex": "1.3.2", 118 | "samsam": "1.1.2", 119 | "util": ">=0.10.3 <1" 120 | } 121 | }, 122 | "type-detect": { 123 | "version": "4.0.8", 124 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", 125 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", 126 | "dev": true 127 | }, 128 | "util": { 129 | "version": "0.10.3", 130 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", 131 | "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", 132 | "dev": true, 133 | "requires": { 134 | "inherits": "2.0.1" 135 | } 136 | }, 137 | "uuid": { 138 | "version": "3.3.2", 139 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 140 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "informix", 3 | "version": "1.0.0", 4 | "description": "A node.js native client for IBM Informix", 5 | "main": "index.js", 6 | "gypfile": true, 7 | "scripts": { 8 | "codecov": "codecov", 9 | "coverage": "istanbul cover _mocha -- --reporter spec", 10 | "coverage:lcovonly": "istanbul cover _mocha --report lcovonly -- --reporter spec", 11 | "coveralls": "cat ./coverage/lcov.info | coveralls", 12 | "docs": "jsdoc -c jsdoc-conf.json ./README.md", 13 | "lint": "jshint index.js examples lib test", 14 | "precodecov": "npm run coverage:lcovonly", 15 | "precoveralls": "npm run coverage:lcovonly", 16 | "test": "mocha" 17 | }, 18 | "directories": { 19 | "example": "./examples", 20 | "lib": "./lib" 21 | }, 22 | "engines": { 23 | "node": ">= 0.12.0" 24 | }, 25 | "files": [ 26 | "gyp", 27 | "lib", 28 | "src", 29 | "binding.gyp", 30 | "index.js" 31 | ], 32 | "keywords": [ 33 | "informix" 34 | ], 35 | "author": "Uditha Atukorala ", 36 | "license": "ISC", 37 | "repository": "nukedzn/node-informix", 38 | "dependencies": { 39 | "bindings": "^1.3.1", 40 | "debug": "^4.1.1", 41 | "nan": "^2.12.1", 42 | "uuid": "^3.3.2" 43 | }, 44 | "devDependencies": { 45 | "chai": "^4.2.0", 46 | "moment": "^2.23.0", 47 | "sinon": "^1.17.4" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/esqlc.ecpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "esqlc.h" 6 | 7 | 8 | int32_t esqlc::connect( const char * connid, const char * database, const char * username, const char * password ) { 9 | 10 | EXEC SQL BEGIN DECLARE SECTION; 11 | 12 | const char * esql_db = database; 13 | const char * esql_connid = connid; 14 | 15 | #ifndef _WIN32 16 | const char * esql_user = username; 17 | const char * esql_pass = password; 18 | #else 19 | char * esql_user = 0; 20 | char * esql_pass = 0; 21 | #endif 22 | 23 | EXEC SQL END DECLARE SECTION; 24 | 25 | #ifdef _WIN32 26 | if ( username && std::strlen( username ) ) { 27 | esql_user = new char[ ( std::strlen( username ) + 1 ) ]; 28 | std::strncpy( esql_user, username, ( std::strlen( username ) + 1 ) ); 29 | } 30 | 31 | if ( password && std::strlen( password ) ) { 32 | esql_pass = new char[ ( std::strlen( password ) + 1 ) ]; 33 | std::strncpy( esql_pass, password, ( std::strlen( password ) + 1 ) ); 34 | } 35 | #endif 36 | 37 | if ( username && std::strlen( username ) && password && std::strlen( password ) ) { 38 | EXEC SQL connect to :esql_db as :esql_connid USER :esql_user USING :esql_pass 39 | WITH CONCURRENT TRANSACTION; 40 | } else if ( username && std::strlen( username ) ) { 41 | EXEC SQL connect to :esql_db as :esql_connid USER :esql_user 42 | WITH CONCURRENT TRANSACTION; 43 | } else { 44 | EXEC SQL connect to :esql_db as :esql_connid WITH CONCURRENT TRANSACTION; 45 | } 46 | 47 | 48 | #ifdef _WIN32 49 | if ( esql_user ) { delete [] esql_user; } 50 | if ( esql_pass ) { delete [] esql_pass; } 51 | #endif 52 | 53 | return SQLCODE; 54 | 55 | }; 56 | 57 | 58 | int32_t esqlc::prepare( const char * stmtid, const char * stmt, ifx_sqlda_t * &insqlda, ifx_sqlda_t * &outsqlda ) { 59 | 60 | EXEC SQL BEGIN DECLARE SECTION; 61 | 62 | const char * esql_stmtid = stmtid; 63 | const char * esql_stmt = stmt; 64 | 65 | EXEC SQL END DECLARE SECTION; 66 | 67 | 68 | EXEC SQL prepare :esql_stmtid from :esql_stmt; 69 | 70 | if ( SQLCODE == 0 ) { 71 | EXEC SQL describe input :esql_stmtid into insqlda; 72 | EXEC SQL describe output :esql_stmtid into outsqlda; 73 | } 74 | 75 | return SQLCODE; 76 | 77 | } 78 | 79 | 80 | int32_t esqlc::exec( const char * stmtid, ifx_sqlda_t * insqlda, int32_t * serial ) { 81 | 82 | EXEC SQL BEGIN DECLARE SECTION; 83 | 84 | const char * esql_stmtid = stmtid; 85 | 86 | EXEC SQL END DECLARE SECTION; 87 | 88 | 89 | if ( insqlda ) { 90 | EXEC SQL execute :esql_stmtid using descriptor insqlda; 91 | } else { 92 | EXEC SQL execute :esql_stmtid; 93 | } 94 | 95 | if ( SQLCODE == 0 && serial ) { 96 | std::memcpy( serial, &sqlca.sqlerrd[1], sizeof( *serial ) ); 97 | } 98 | 99 | return SQLCODE; 100 | 101 | } 102 | 103 | 104 | int32_t esqlc::exec( const char * stmtid, const char * curid, ifx_sqlda_t * insqlda ) { 105 | 106 | EXEC SQL BEGIN DECLARE SECTION; 107 | 108 | const char * esql_stmtid = stmtid; 109 | const char * esql_curid = curid; 110 | 111 | EXEC SQL END DECLARE SECTION; 112 | 113 | 114 | EXEC SQL declare :esql_curid cursor for :esql_stmtid; 115 | 116 | if ( SQLCODE == 0 ) { 117 | if ( insqlda ) { 118 | EXEC SQL open :esql_curid using descriptor insqlda; 119 | } else { 120 | EXEC SQL open :esql_curid; 121 | } 122 | } 123 | 124 | return SQLCODE; 125 | 126 | } 127 | 128 | 129 | int32_t esqlc::fetch( const char * curid, ifx_sqlda_t * outsqlda ) { 130 | 131 | EXEC SQL BEGIN DECLARE SECTION; 132 | 133 | const char * esql_curid = curid; 134 | 135 | EXEC SQL END DECLARE SECTION; 136 | 137 | 138 | EXEC SQL fetch :esql_curid using descriptor outsqlda; 139 | 140 | return SQLCODE; 141 | 142 | } 143 | 144 | 145 | int32_t esqlc::close( const char * curid ) { 146 | 147 | EXEC SQL BEGIN DECLARE SECTION; 148 | 149 | const char * esql_curid = curid; 150 | 151 | EXEC SQL END DECLARE SECTION; 152 | 153 | 154 | EXEC SQL close :esql_curid; 155 | EXEC SQL free :esql_curid; 156 | 157 | return SQLCODE; 158 | 159 | } 160 | 161 | 162 | int32_t esqlc::free( const char * stmtid ) { 163 | 164 | EXEC SQL BEGIN DECLARE SECTION; 165 | 166 | const char * esql_stmtid = stmtid; 167 | 168 | EXEC SQL END DECLARE SECTION; 169 | 170 | 171 | EXEC SQL free :esql_stmtid; 172 | 173 | return SQLCODE; 174 | 175 | } 176 | 177 | 178 | int32_t esqlc::disconnect( const char * connid ) { 179 | 180 | EXEC SQL BEGIN DECLARE SECTION; 181 | 182 | const char * esql_connid = connid; 183 | 184 | EXEC SQL END DECLARE SECTION; 185 | 186 | 187 | EXEC SQL disconnect :esql_connid; 188 | 189 | return SQLCODE; 190 | 191 | } 192 | 193 | 194 | int32_t esqlc::acquire( const char * connid ) { 195 | 196 | EXEC SQL BEGIN DECLARE SECTION; 197 | 198 | const char * esql_connid = connid; 199 | 200 | EXEC SQL END DECLARE SECTION; 201 | 202 | 203 | EXEC SQL set connection :esql_connid; 204 | 205 | return SQLCODE; 206 | 207 | } 208 | 209 | 210 | int32_t esqlc::release( const char * connid ) { 211 | 212 | EXEC SQL BEGIN DECLARE SECTION; 213 | 214 | const char * esql_connid = connid; 215 | 216 | EXEC SQL END DECLARE SECTION; 217 | 218 | 219 | EXEC SQL set connection :esql_connid dormant; 220 | 221 | return SQLCODE; 222 | 223 | } 224 | 225 | 226 | std::string esqlc::errmsg( int32_t code ) { 227 | 228 | char buffer[512]; 229 | int n, r, msg_len; 230 | 231 | n = snprintf( buffer, 64, "[%d] ", code ); 232 | r = rgetlmsg( code, ( buffer + n ), ( sizeof( buffer ) - ( n + 1 ) ), &msg_len ); 233 | 234 | if ( r == 0 ) { 235 | // rgetlmsg() returns a \n as well which we don't want 236 | buffer[n + (msg_len - 1)] = '\0'; 237 | } else { 238 | snprintf( ( buffer + n ), ( sizeof( buffer ) - ( n + 1 ) ), "(Failed to get error message, rgetlmsg() returned with %d)", r ); 239 | } 240 | 241 | return std::string( buffer ); 242 | 243 | }; 244 | 245 | 246 | const char * esqlc::sqlstate() { 247 | return SQLSTATE; 248 | } 249 | 250 | -------------------------------------------------------------------------------- /src/esqlc.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef ESQLC_H 3 | #define ESQLC_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | class esqlc { 12 | public: 13 | 14 | static int32_t connect( const char * connid, const char * database, const char * username = 0, const char * password = 0 ); 15 | static int32_t prepare( const char * stmtid, const char * stmtstr, ifx_sqlda_t * &insqlda, ifx_sqlda_t * &outsqlda ); 16 | static int32_t exec( const char * stmtid, ifx_sqlda_t * insqlda, int32_t * serial ); 17 | static int32_t exec( const char * stmtid, const char * curid, ifx_sqlda_t * insqlda ); 18 | static int32_t fetch( const char * curid, ifx_sqlda_t * outsqlda ); 19 | static int32_t close( const char * curid ); 20 | static int32_t free( const char * stmtid ); 21 | static int32_t disconnect( const char * connid ); 22 | 23 | static int32_t acquire( const char * connid ); 24 | static int32_t release( const char * connid ); 25 | 26 | static std::string errmsg( int32_t code ); 27 | static const char * sqlstate(); 28 | 29 | }; 30 | 31 | #endif /* !ESQLC_H */ 32 | 33 | -------------------------------------------------------------------------------- /src/ifx/ifx.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "ifx.h" 6 | #include "workers/connect.h" 7 | #include "workers/stmtprepare.h" 8 | #include "workers/stmtexec.h" 9 | #include "workers/stmtfree.h" 10 | #include "workers/fetch.h" 11 | #include "workers/cursorclose.h" 12 | #include "workers/disconnect.h" 13 | 14 | 15 | namespace ifx { 16 | 17 | // initialise static vars 18 | Nan::Persistent< v8::Function > Ifx::constructor; 19 | 20 | 21 | Ifx::Ifx() { 22 | // constructor 23 | } 24 | 25 | 26 | Ifx::~Ifx() { 27 | // destructor 28 | } 29 | 30 | 31 | void Ifx::init( v8::Local< v8::Object > exports ) { 32 | 33 | // stack-allocated handle scope 34 | Nan::HandleScope scope; 35 | 36 | // prepare constructor template 37 | v8::Local< v8::FunctionTemplate > tpl = Nan::New< v8::FunctionTemplate >( construct ); 38 | tpl->SetClassName( Nan::New( "Ifx" ).ToLocalChecked() ); 39 | tpl->InstanceTemplate()->SetInternalFieldCount( 1 ); 40 | 41 | // prototypes 42 | Nan::SetPrototypeMethod( tpl, "connect", connect ); 43 | Nan::SetPrototypeMethod( tpl, "prepare", prepare ); 44 | Nan::SetPrototypeMethod( tpl, "exec", exec ); 45 | Nan::SetPrototypeMethod( tpl, "fetch", fetch ); 46 | Nan::SetPrototypeMethod( tpl, "close", close ); 47 | Nan::SetPrototypeMethod( tpl, "free", free ); 48 | Nan::SetPrototypeMethod( tpl, "disconnect", disconnect ); 49 | 50 | Nan::SetPrototypeMethod( tpl, "serial", serial ); 51 | 52 | 53 | constructor.Reset( tpl->GetFunction() ); 54 | exports->Set( Nan::New( "Ifx" ).ToLocalChecked(), tpl->GetFunction() ); 55 | 56 | } 57 | 58 | 59 | void Ifx::construct( const Nan::FunctionCallbackInfo< v8::Value > &info ) { 60 | 61 | if (! info.IsConstructCall() ) { 62 | 63 | // invoked as `Ifx(...)`, convert to a constructor call 64 | const int argc = 1; 65 | v8::Local< v8::Value > argv[ argc ] = { info[0] }; 66 | v8::Local< v8::Function > c = Nan::New< v8::Function >( constructor ); 67 | return info.GetReturnValue().Set( Nan::NewInstance( c, argc, argv ).ToLocalChecked() ); 68 | 69 | } 70 | 71 | Ifx * obj = new Ifx(); 72 | obj->Wrap( info.This() ); 73 | info.GetReturnValue().Set( info.This() ); 74 | 75 | } 76 | 77 | 78 | void Ifx::connect( const Nan::FunctionCallbackInfo< v8::Value > &info ) { 79 | 80 | // basic validation 81 | if ( info.Length() != 2 ) { 82 | return Nan::ThrowError( "Invalid number of arguments." ); 83 | } 84 | 85 | if (! info[0]->IsObject() ) { 86 | return Nan::ThrowTypeError( "Connection parameters must be an object." ); 87 | } 88 | 89 | if (! info[1]->IsFunction() ) { 90 | return Nan::ThrowTypeError( "Callback must be a function." ); 91 | } 92 | 93 | 94 | v8::Local< v8::Object > params = info[0].As< v8::Object >(); 95 | 96 | // validate mandatory connection params 97 | if ( (! params->Has( Nan::New< v8::String >( "id" ).ToLocalChecked() ) ) 98 | || (! params->Has( Nan::New< v8::String >( "database" ).ToLocalChecked() ) ) ) { 99 | return Nan::ThrowTypeError( "Connection parameter 'id' and 'database' are mandatory." ); 100 | } 101 | 102 | 103 | // unwrap ourself 104 | Ifx * self = ObjectWrap::Unwrap< Ifx >( info.Holder() ); 105 | 106 | // check whether we already have a connection with the same ID 107 | Nan::Utf8String utf8connid( params->Get( Nan::New< v8::String >( "id" ).ToLocalChecked() ) ); 108 | ifx::conn_t * conn = self->_conns[ *utf8connid ]; 109 | if ( conn ) { 110 | return Nan::ThrowError( "A connection with the same ID already exists." ); 111 | } 112 | 113 | 114 | std::string username; 115 | std::string password; 116 | Nan::Utf8String utf8database( params->Get( Nan::New< v8::String >( "database" ).ToLocalChecked() ) ); 117 | Nan::Callback * cb = new Nan::Callback( info[1].As< v8::Function >() ); 118 | 119 | if ( params->Has( Nan::New< v8::String >( "username" ).ToLocalChecked() ) ) { 120 | Nan::Utf8String utf8user( params->Get( Nan::New< v8::String >( "username" ).ToLocalChecked() ) ); 121 | username = std::string( *utf8user ); 122 | } 123 | 124 | if ( params->Has( Nan::New< v8::String >( "password" ).ToLocalChecked() ) ) { 125 | Nan::Utf8String utf8pass( params->Get( Nan::New< v8::String >( "password" ).ToLocalChecked() ) ); 126 | password = std::string( *utf8pass ); 127 | } 128 | 129 | 130 | // prepare connection data structure 131 | conn = new ifx::conn_t(); 132 | conn->id = *utf8connid; 133 | conn->database = *utf8database; 134 | conn->username = username; 135 | conn->password = password; 136 | 137 | // update internal reference 138 | self->_conns[ conn->id ] = conn; 139 | 140 | // schedule async connection worker 141 | Nan::AsyncQueueWorker( new ifx::workers::Connect( conn, cb ) ); 142 | 143 | 144 | // return undefined 145 | info.GetReturnValue().Set( Nan::Undefined() ); 146 | 147 | } 148 | 149 | 150 | void Ifx::prepare( const Nan::FunctionCallbackInfo< v8::Value > &info ) { 151 | 152 | // basic validation 153 | if ( info.Length() != 4 ) { 154 | return Nan::ThrowError( "Invalid number of arguments." ); 155 | } 156 | 157 | if (! info[0]->IsString() ) { 158 | return Nan::ThrowTypeError( "Connection ID must be a string." ); 159 | } 160 | 161 | if (! info[1]->IsString() ) { 162 | return Nan::ThrowTypeError( "Statement ID must be a string." ); 163 | } 164 | 165 | if (! info[2]->IsString() ) { 166 | return Nan::ThrowTypeError( "Statement must be a string." ); 167 | } 168 | 169 | if (! info[3]->IsFunction() ) { 170 | return Nan::ThrowTypeError( "Callback must be a function." ); 171 | } 172 | 173 | 174 | // unwrap ourself 175 | Ifx * self = ObjectWrap::Unwrap< Ifx >( info.Holder() ); 176 | 177 | // grab JS arguments 178 | Nan::Utf8String utf8connid ( info[0] ); 179 | Nan::Utf8String utf8stmtid ( info[1] ); 180 | Nan::Utf8String utf8stmt ( info[2] ); 181 | Nan::Callback * cb = new Nan::Callback( info[3].As< v8::Function >() ); 182 | 183 | 184 | ifx::conn_t * conn = self->_conns[ *utf8connid ]; 185 | if (! conn ) { 186 | return Nan::ThrowError( "Invalid connection ID." ); 187 | } 188 | 189 | // check whether we already have a prepared statement with the same ID 190 | ifx::stmt_t * stmt = conn->stmts[ *utf8stmtid ]; 191 | if ( stmt ) { 192 | return Nan::ThrowError( "A Statement is already prepared with the same ID." ); 193 | } 194 | 195 | // prepare statement data structures 196 | stmt = new ifx::stmt_t(); 197 | stmt->id = *utf8stmtid; 198 | stmt->stmt = *utf8stmt; 199 | stmt->conn = conn; 200 | 201 | // update internal reference 202 | conn->stmts[ stmt->id ] = stmt; 203 | 204 | // schedule async worker 205 | Nan::AsyncQueueWorker( new ifx::workers::StmtPrepare( stmt, cb ) ); 206 | 207 | 208 | // return undefined 209 | info.GetReturnValue().Set( Nan::Undefined() ); 210 | 211 | } 212 | 213 | 214 | void Ifx::exec( const Nan::FunctionCallbackInfo< v8::Value > &info ) { 215 | 216 | // basic validation 217 | if ( info.Length() < 4 ) { 218 | return Nan::ThrowError( "Invalid number of arguments." ); 219 | } 220 | 221 | if (! info[0]->IsString() ) { 222 | return Nan::ThrowTypeError( "Connection ID must be a string." ); 223 | } 224 | 225 | if (! info[1]->IsString() ) { 226 | return Nan::ThrowTypeError( "Statement ID must be a string." ); 227 | } 228 | 229 | if (! info[2]->IsString() ) { 230 | return Nan::ThrowTypeError( "Cursor ID must be a string." ); 231 | } 232 | 233 | if (! info[info.Length() - 1]->IsFunction() ) { 234 | return Nan::ThrowTypeError( "Callback must be a function." ); 235 | } 236 | 237 | 238 | // unwrap ourself 239 | Ifx * self = ObjectWrap::Unwrap< Ifx >( info.Holder() ); 240 | 241 | Nan::Utf8String utf8connid( info[0] ); 242 | ifx::conn_t * conn = self->_conns[ *utf8connid ]; 243 | if (! conn ) { 244 | return Nan::ThrowError( "Invalid connection ID." ); 245 | } 246 | 247 | Nan::Utf8String utf8stmtid( info[1] ); 248 | ifx::stmt_t * stmt = conn->stmts[ *utf8stmtid ]; 249 | if (! stmt ) { 250 | return Nan::ThrowError( "Invalid statement ID." ); 251 | } 252 | 253 | /* 254 | * ESQL/C has this bad habit of reusing last closed cursor's input ifx_sqlda_t 255 | * (insqda) if the cursor is closed and freed. So we have to check input 256 | * arguments here rather than relying on ESQL/C. 257 | */ 258 | if ( ( stmt->insqlda && stmt->insqlda->sqld > 0 ) && info.Length() < 5 ) { 259 | return Nan::ThrowError( "This statment requires input arguments." ); 260 | } 261 | 262 | if ( (! stmt->insqlda ) && info.Length() > 4 ) { 263 | return Nan::ThrowError( "This statment does not expect any input arguments." ); 264 | } 265 | 266 | Nan::Utf8String utf8curid( info[2] ); 267 | if ( stmt->cursors[ *utf8curid ] || self->_cursors[ *utf8curid ] ) { 268 | return Nan::ThrowError( "A cursor with the same ID already exists." ); 269 | } 270 | 271 | 272 | // SQL descriptor area 273 | ifx::cursor_t * cursor = new ifx::cursor_t(); 274 | ifx_sqlda_t * insqlda = 0; 275 | 276 | if ( info.Length() > 4 ) { 277 | 278 | insqlda = new ifx_sqlda_t(); 279 | std::memset( insqlda, 0, sizeof( ifx_sqlda_t ) ); 280 | 281 | // FIXME: can we get away with only using CSTRINGTYPE data? 282 | 283 | if ( info[3]->IsArray() ) { 284 | 285 | char * arg; 286 | v8::Local< v8::Array > args = info[3].As< v8::Array >(); 287 | 288 | if ((int) stmt->insqlda->sqld != (int) args->Length()) { 289 | return Nan::ThrowError( "Too many or too few host variables given." ); 290 | } 291 | 292 | insqlda->sqld = args->Length(); 293 | insqlda->sqlvar = new ifx_sqlvar_t[ args->Length() ]; 294 | insqlda->desc_occ = 0; 295 | 296 | std::memset( insqlda->sqlvar, 0, ( sizeof( ifx_sqlvar_t ) * args->Length() ) ); 297 | 298 | ifx_sqlvar_t * sqlvar = stmt->insqlda->sqlvar; 299 | for ( uint32_t i = 0; i < args->Length(); i++, sqlvar++ ) { 300 | Nan::Utf8String utf8arg( args->Get( Nan::New< v8::Integer >( i ) ) ); 301 | size_t size = ( utf8arg.length() + 1 ); 302 | arg = new char[ size ]; 303 | 304 | std::strncpy( arg, *utf8arg, size ); 305 | cursor->args.push_back( arg ); 306 | 307 | if (sqlvar->sqltype == SQLTEXT) { 308 | ifx_loc_t *temp_loc = new ifx_loc_t(); 309 | std::memset( temp_loc, 0, sizeof( ifx_loc_t ) ); 310 | 311 | temp_loc->loc_loctype = LOCMEMORY; 312 | temp_loc->loc_type = SQLTEXT; 313 | temp_loc->loc_bufsize = (1024 * 1024); 314 | temp_loc->loc_size = size; 315 | temp_loc->loc_buffer = arg; 316 | 317 | insqlda->sqlvar[i].sqltype = CLOCATORTYPE; 318 | insqlda->sqlvar[i].sqllen = sizeof(ifx_loc_t); 319 | insqlda->sqlvar[i].sqldata = (char *) temp_loc; 320 | } else { 321 | insqlda->sqlvar[i].sqltype = CSTRINGTYPE; 322 | insqlda->sqlvar[i].sqllen = size; 323 | insqlda->sqlvar[i].sqldata = arg; 324 | } 325 | 326 | } 327 | 328 | } else { 329 | 330 | Nan::Utf8String utf8arg( info[3] ); 331 | size_t size = ( utf8arg.length() + 1 ); 332 | char * arg = new char[ size ]; 333 | 334 | std::strncpy( arg, *utf8arg, size ); 335 | cursor->args.push_back( arg ); 336 | 337 | insqlda->sqld = 1; 338 | insqlda->sqlvar = new ifx_sqlvar_t[1]; 339 | insqlda->desc_occ = 0; 340 | 341 | std::memset( insqlda->sqlvar, 0, sizeof( ifx_sqlvar_t[1] ) ); 342 | insqlda->sqlvar[0].sqltype = CSTRINGTYPE; 343 | insqlda->sqlvar[0].sqllen = size; 344 | insqlda->sqlvar[0].sqldata = arg; 345 | 346 | } 347 | 348 | } 349 | 350 | 351 | // prepare cursor data structures 352 | cursor->id = *utf8curid; 353 | cursor->stmt = stmt; 354 | cursor->insqlda = insqlda; 355 | 356 | // update internal references 357 | self->_cursors[ cursor->id ] = cursor; 358 | 359 | // schedule async worker 360 | Nan::Callback * cb = new Nan::Callback( info[info.Length() - 1].As< v8::Function >() ); 361 | Nan::AsyncQueueWorker( new ifx::workers::StmtExec( cursor, cb ) ); 362 | 363 | 364 | // return undefined 365 | info.GetReturnValue().Set( Nan::Undefined() ); 366 | 367 | } 368 | 369 | 370 | void Ifx::fetch( const Nan::FunctionCallbackInfo< v8::Value > &info ) { 371 | 372 | // basic validation 373 | if ( info.Length() != 2 ) { 374 | return Nan::ThrowError( "Invalid number of arguments." ); 375 | } 376 | 377 | if (! info[0]->IsString() ) { 378 | return Nan::ThrowTypeError( "Cursor ID must be a string." ); 379 | } 380 | 381 | if (! info[1]->IsFunction() ) { 382 | return Nan::ThrowTypeError( "Callback must be a function." ); 383 | } 384 | 385 | 386 | // unwrap ourself 387 | Ifx * self = ObjectWrap::Unwrap< Ifx >( info.Holder() ); 388 | 389 | Nan::Utf8String utf8curid( info[0] ); 390 | ifx::cursor_t * cursor = self->_cursors[ *utf8curid ]; 391 | 392 | if (! cursor ) { 393 | return Nan::ThrowError( "Invalid cursor ID." ); 394 | } 395 | 396 | 397 | if ( (! cursor->outsqlda ) && cursor->stmt->outsqlda ) { 398 | 399 | cursor->outsqlda = new ifx_sqlda_t(); 400 | std::memcpy( cursor->outsqlda, cursor->stmt->outsqlda, sizeof( ifx_sqlda_t ) ); 401 | 402 | // new output data buffer 403 | cursor->data = new char[ cursor->stmt->size ]; 404 | 405 | // update sqlvar->sqldata refereces 406 | size_t size = 0; 407 | ifx_sqlvar_t * sqlvar = cursor->outsqlda->sqlvar; 408 | for ( size_t i = 0; i < static_cast< size_t >( cursor->outsqlda->sqld ); i++ ) { 409 | size = rtypalign( size, sqlvar->sqltype ); 410 | 411 | if ( sqlvar->sqltype == SQLTEXT ) { 412 | ifx_loc_t *temp_loc = new ifx_loc_t(); 413 | temp_loc->loc_type = SQLTEXT; 414 | temp_loc->loc_loctype = LOCMEMORY; 415 | temp_loc->loc_indicator = 0; 416 | temp_loc->loc_bufsize = -1; 417 | temp_loc->loc_oflags = 0; 418 | temp_loc->loc_mflags = 0; 419 | sqlvar->sqllen = sizeof( ifx_loc_t ); 420 | sqlvar->sqldata = (char *) temp_loc; 421 | } else { 422 | sqlvar->sqldata = ( cursor->data + size ); 423 | } 424 | 425 | size += rtypmsize( sqlvar->sqltype, sqlvar->sqllen ); 426 | sqlvar++; 427 | } 428 | 429 | } 430 | 431 | // schedule async worker 432 | Nan::Callback * cb = new Nan::Callback( info[1].As< v8::Function >() ); 433 | Nan::AsyncQueueWorker( new ifx::workers::Fetch( cursor, cb ) ); 434 | 435 | 436 | // return undefined 437 | info.GetReturnValue().Set( Nan::Undefined() ); 438 | 439 | } 440 | 441 | 442 | void Ifx::close( const Nan::FunctionCallbackInfo< v8::Value > &info ) { 443 | 444 | // basic validation 445 | if ( info.Length() != 2 ) { 446 | return Nan::ThrowError( "Invalid number of arguments." ); 447 | } 448 | 449 | if (! info[0]->IsString() ) { 450 | return Nan::ThrowTypeError( "Cursor ID must be a string." ); 451 | } 452 | 453 | if (! info[1]->IsFunction() ) { 454 | return Nan::ThrowTypeError( "Callback must be a function." ); 455 | } 456 | 457 | 458 | // unwrap ourself 459 | Ifx * self = ObjectWrap::Unwrap< Ifx >( info.Holder() ); 460 | 461 | Nan::Utf8String utf8curid( info[0] ); 462 | ifx::cursor_t * cursor = self->_cursors[ *utf8curid ]; 463 | 464 | if (! cursor ) { 465 | return Nan::ThrowError( "Invalid cursor ID." ); 466 | } 467 | 468 | 469 | // FIXME: Deleting this here means we can't recover from any failures within 470 | // the async worker. 471 | // update internal references 472 | self->_cursors.erase( cursor->id ); 473 | 474 | // schedule async worker 475 | Nan::Callback * cb = new Nan::Callback( info[1].As< v8::Function >() ); 476 | Nan::AsyncQueueWorker( new ifx::workers::CursorClose( cursor, cb ) ); 477 | 478 | 479 | // return undefined 480 | info.GetReturnValue().Set( Nan::Undefined() ); 481 | 482 | } 483 | 484 | 485 | void Ifx::free( const Nan::FunctionCallbackInfo< v8::Value > &info ) { 486 | 487 | // basic validation 488 | if ( info.Length() != 3 ) { 489 | return Nan::ThrowError( "Invalid number of arguments." ); 490 | } 491 | 492 | if (! info[0]->IsString() ) { 493 | return Nan::ThrowTypeError( "Connection ID must be a string." ); 494 | } 495 | 496 | if (! info[1]->IsString() ) { 497 | return Nan::ThrowTypeError( "Statement ID must be a string." ); 498 | } 499 | 500 | if (! info[2]->IsFunction() ) { 501 | return Nan::ThrowTypeError( "Callback must be a function." ); 502 | } 503 | 504 | 505 | // unwrap ourself 506 | Ifx * self = ObjectWrap::Unwrap< Ifx >( info.Holder() ); 507 | 508 | Nan::Utf8String utf8connid( info[0] ); 509 | ifx::conn_t * conn = self->_conns[ *utf8connid ]; 510 | if (! conn ) { 511 | return Nan::ThrowError( "Invalid connection ID." ); 512 | } 513 | 514 | Nan::Utf8String utf8stmtid( info[1] ); 515 | ifx::stmt_t * stmt = conn->stmts[ *utf8stmtid ]; 516 | if (! stmt ) { 517 | return Nan::ThrowError( "Invalid statement ID." ); 518 | } 519 | 520 | if ( stmt->cursors.size() ) { 521 | // try and cleanup any empty cursor references (issue #27) 522 | for ( cursors_t::iterator it = stmt->cursors.begin(); it != stmt->cursors.end(); ) { 523 | if (! it->second ) { 524 | self->_cursors.erase( it->first ); 525 | stmt->cursors.erase( it++ ); 526 | } else { 527 | // there's a cursor attached to this statement so can't be freed 528 | return Nan::ThrowError( "Cursors need to be closed." ); 529 | } 530 | } 531 | } 532 | 533 | 534 | // schedule async worker 535 | Nan::Callback * cb = new Nan::Callback( info[2].As< v8::Function >() ); 536 | Nan::AsyncQueueWorker( new ifx::workers::StmtFree( stmt, cb ) ); 537 | 538 | 539 | // return undefined 540 | info.GetReturnValue().Set( Nan::Undefined() ); 541 | 542 | } 543 | 544 | 545 | void Ifx::disconnect( const Nan::FunctionCallbackInfo< v8::Value > &info ) { 546 | 547 | // basic validation 548 | if ( info.Length() != 2 ) { 549 | return Nan::ThrowError( "Invalid number of arguments." ); 550 | } 551 | 552 | if (! info[0]->IsString() ) { 553 | return Nan::ThrowTypeError( "Connection ID must be a string." ); 554 | } 555 | 556 | if (! info[1]->IsFunction() ) { 557 | return Nan::ThrowTypeError( "Callback must be a function." ); 558 | } 559 | 560 | // unwrap ourself 561 | Ifx * self = ObjectWrap::Unwrap< Ifx >( info.Holder() ); 562 | 563 | Nan::Utf8String utf8connid( info[0] ); 564 | ifx::conn_t * conn = self->_conns[ *utf8connid ]; 565 | if (! conn ) { 566 | return Nan::ThrowError( "Invalid connection ID." ); 567 | } 568 | 569 | if ( conn->stmts.size() ) { 570 | return Nan::ThrowError( "Statements must be freed." ); 571 | } 572 | 573 | 574 | // FIXME: Deleting this here means we can't recover from any failures within 575 | // the async worker. 576 | // update internal references 577 | self->_conns.erase( conn->id ); 578 | 579 | // schedule async worker 580 | Nan::Callback * cb = new Nan::Callback( info[1].As< v8::Function >() ); 581 | Nan::AsyncQueueWorker( new ifx::workers::Disconnect( conn, cb ) ); 582 | 583 | 584 | // return undefined 585 | info.GetReturnValue().Set( Nan::Undefined() ); 586 | 587 | } 588 | 589 | 590 | void Ifx::serial( const Nan::FunctionCallbackInfo< v8::Value > &info ) { 591 | 592 | // basic validation 593 | if ( info.Length() != 1 ) { 594 | return Nan::ThrowError( "Invalid number of arguments." ); 595 | } 596 | 597 | if (! info[0]->IsString() ) { 598 | return Nan::ThrowTypeError( "Cursor ID must be a string." ); 599 | } 600 | 601 | 602 | // unwrap ourself 603 | Ifx * self = ObjectWrap::Unwrap< Ifx >( info.Holder() ); 604 | 605 | Nan::Utf8String utf8curid( info[0] ); 606 | ifx::cursor_t * cursor = self->_cursors[ *utf8curid ]; 607 | 608 | if (! cursor ) { 609 | return Nan::ThrowError( "Invalid cursor ID." ); 610 | } 611 | 612 | 613 | // return serial 614 | info.GetReturnValue().Set( Nan::New< v8::Int32 >( cursor->serial ) ); 615 | 616 | } 617 | 618 | } /* end of namespace ifx */ 619 | 620 | -------------------------------------------------------------------------------- /src/ifx/ifx.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef IFX_IFX_H 3 | #define IFX_IFX_H 4 | 5 | #include 6 | 7 | #include "types.h" 8 | 9 | 10 | namespace ifx { 11 | 12 | class Ifx : public node::ObjectWrap { 13 | public: 14 | 15 | static void init( v8::Local< v8::Object > exports ); 16 | static void construct( const Nan::FunctionCallbackInfo< v8::Value > &info ); 17 | 18 | static void connect( const Nan::FunctionCallbackInfo< v8::Value > &info ); 19 | static void prepare( const Nan::FunctionCallbackInfo< v8::Value > &info ); 20 | static void exec( const Nan::FunctionCallbackInfo< v8::Value > &info ); 21 | static void fetch( const Nan::FunctionCallbackInfo< v8::Value > &info ); 22 | static void close( const Nan::FunctionCallbackInfo< v8::Value > &info ); 23 | static void free( const Nan::FunctionCallbackInfo< v8::Value > &info ); 24 | static void disconnect( const Nan::FunctionCallbackInfo< v8::Value > &info ); 25 | 26 | static void serial( const Nan::FunctionCallbackInfo< v8::Value > &info ); 27 | 28 | 29 | private: 30 | 31 | explicit Ifx(); 32 | ~Ifx(); 33 | 34 | static Nan::Persistent< v8::Function > constructor; 35 | 36 | ifx::conns_t _conns; 37 | ifx::cursors_t _cursors; 38 | 39 | }; 40 | 41 | } /* end of namespace ifx */ 42 | 43 | #endif /* !IFX_IFX_H */ 44 | 45 | -------------------------------------------------------------------------------- /src/ifx/types.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef IFX_TYPES_H 3 | #define IFX_TYPES_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | namespace ifx { 14 | 15 | // forward declarations 16 | struct conn_t; 17 | struct cursor_t; 18 | struct stmt_t; 19 | 20 | 21 | typedef std::map< std::string, conn_t * > conns_t; 22 | typedef std::map< std::string, stmt_t * > stmts_t; 23 | typedef std::map< std::string, cursor_t * > cursors_t; 24 | 25 | typedef std::list< char * > cursor_args_t; 26 | 27 | 28 | struct cursor_t { 29 | std::string id; 30 | cursor_args_t args; 31 | char * data; 32 | 33 | stmt_t * stmt; 34 | ifx_sqlda_t * insqlda; 35 | ifx_sqlda_t * outsqlda; 36 | 37 | int32_t serial; 38 | 39 | cursor_t() : data( 0 ), stmt( 0 ), insqlda( 0 ), outsqlda( 0 ) {} 40 | ~cursor_t() { 41 | 42 | for ( cursor_args_t::const_iterator it = args.begin(); it != args.end(); it++ ) { 43 | delete [] (* it); 44 | } 45 | 46 | if ( data ) { 47 | delete [] data; 48 | } 49 | 50 | if ( insqlda ) { 51 | delete insqlda; 52 | } 53 | 54 | if ( outsqlda ) { 55 | delete outsqlda; 56 | } 57 | 58 | } 59 | }; 60 | 61 | 62 | struct stmt_t { 63 | std::string id; 64 | std::string stmt; 65 | int32_t type; 66 | size_t size; 67 | 68 | cursors_t cursors; 69 | 70 | conn_t * conn; 71 | ifx_sqlda_t * insqlda; 72 | ifx_sqlda_t * outsqlda; 73 | 74 | stmt_t() : conn( 0 ), insqlda( 0 ), outsqlda( 0 ) {} 75 | ~stmt_t() { 76 | 77 | for ( cursors_t::iterator it = cursors.begin(); it != cursors.end(); it++ ) { 78 | delete it->second; 79 | } 80 | 81 | #if _WIN32 82 | SqlFreeMem( insqlda, SQLDA_FREE ); 83 | SqlFreeMem( outsqlda, SQLDA_FREE ); 84 | #else 85 | if ( insqlda ) { 86 | delete insqlda; 87 | } 88 | 89 | if ( outsqlda ) { 90 | delete outsqlda; 91 | } 92 | #endif 93 | 94 | } 95 | }; 96 | 97 | 98 | struct conn_t { 99 | std::string id; 100 | std::string database; 101 | std::string username; 102 | std::string password; 103 | 104 | stmts_t stmts; 105 | 106 | ~conn_t() { 107 | for ( stmts_t::iterator it = stmts.begin(); it != stmts.end(); it++ ) { 108 | delete it->second; 109 | } 110 | } 111 | }; 112 | 113 | } /* end of namespace ifx */ 114 | 115 | #endif /* !IFX_TYPES_H */ 116 | 117 | -------------------------------------------------------------------------------- /src/ifx/workers/connect.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "connect.h" 3 | #include "../../esqlc.h" 4 | 5 | 6 | namespace ifx { 7 | namespace workers { 8 | 9 | Connect::Connect( ifx::conn_t * conn, Nan::Callback * cb ) : Nan::AsyncWorker( cb ), _conn( conn ) { 10 | // constructor 11 | } 12 | 13 | 14 | Connect::~Connect() { 15 | // destructor 16 | } 17 | 18 | 19 | void Connect::Execute() { 20 | 21 | int32_t code = esqlc::connect( 22 | _conn->id.c_str(), 23 | _conn->database.c_str(), 24 | ( _conn->username.length() ? _conn->username.c_str() : 0 ), 25 | ( _conn->password.length() ? _conn->password.c_str() : 0 ) ); 26 | 27 | if ( code < 0 ) { 28 | SetErrorMessage( esqlc::errmsg( code ).c_str() ); 29 | } else { 30 | // release the connection to make it available to other threads 31 | esqlc::release( _conn->id.c_str() ); 32 | } 33 | 34 | } 35 | 36 | 37 | void Connect::HandleOKCallback() { 38 | 39 | // stack-allocated handle scope 40 | Nan::HandleScope scope; 41 | 42 | v8::Local< v8::Value > argv[] = { 43 | Nan::Null(), 44 | Nan::New< v8::String >( _conn->id ).ToLocalChecked() 45 | }; 46 | 47 | callback->Call( 2, argv, async_resource ); 48 | 49 | } 50 | 51 | } /* end of namespace workers */ 52 | } /* end of namespace ifx */ 53 | 54 | -------------------------------------------------------------------------------- /src/ifx/workers/connect.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef IFX_WORKERS_CONNECT_H 3 | #define IFX_WORKERS_CONNECT_H 4 | 5 | #include 6 | 7 | #include "../types.h" 8 | 9 | 10 | namespace ifx { 11 | namespace workers { 12 | 13 | class Connect : public Nan::AsyncWorker { 14 | public: 15 | 16 | Connect( ifx::conn_t * conn, Nan::Callback * cb ); 17 | virtual ~Connect(); 18 | 19 | void Execute(); 20 | 21 | 22 | protected: 23 | 24 | void HandleOKCallback(); 25 | 26 | 27 | private: 28 | 29 | ifx::conn_t * _conn; 30 | 31 | }; 32 | 33 | } /* end of namespace ifx::workers */ 34 | } /* end of namespace ifx */ 35 | 36 | #endif /* !IFX_WORKERS_CONNECT_H */ 37 | 38 | -------------------------------------------------------------------------------- /src/ifx/workers/cursorclose.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "cursorclose.h" 3 | #include "../../esqlc.h" 4 | 5 | 6 | namespace ifx { 7 | namespace workers { 8 | 9 | CursorClose::CursorClose( ifx::cursor_t * cursor, Nan::Callback * cb ) : Nan::AsyncWorker( cb ), _cursor( cursor ) { 10 | // constructor 11 | } 12 | 13 | 14 | CursorClose::~CursorClose() { 15 | // destructor 16 | } 17 | 18 | 19 | void CursorClose::Execute() { 20 | 21 | int32_t code = 0; 22 | 23 | // acquire the connection for this thread 24 | code = esqlc::acquire( _cursor->stmt->conn->id.c_str() ); 25 | if ( code < 0 ) { 26 | return SetErrorMessage( esqlc::errmsg( code ).c_str() ); 27 | } 28 | 29 | code = esqlc::close( _cursor->id.c_str() ); 30 | if ( code < 0 ) { 31 | SetErrorMessage( esqlc::errmsg( code ).c_str() ); 32 | } 33 | 34 | // release the connection 35 | esqlc::release( _cursor->stmt->conn->id.c_str() ); 36 | 37 | } 38 | 39 | 40 | void CursorClose::HandleOKCallback() { 41 | 42 | // stack-allocated handle scope 43 | Nan::HandleScope scope; 44 | 45 | // copy cursor ID 46 | Nan::MaybeLocal< v8::String > v8curid = Nan::New< v8::String >( _cursor->id ); 47 | 48 | // update internal references 49 | _cursor->stmt->cursors.erase( _cursor->id ); 50 | 51 | // delete cursor 52 | delete _cursor; 53 | 54 | v8::Local< v8::Value > argv[] = { 55 | Nan::Null(), 56 | v8curid.ToLocalChecked() 57 | }; 58 | 59 | callback->Call( 2, argv, async_resource ); 60 | 61 | } 62 | 63 | } /* end of namespace workers */ 64 | } /* end of namespace ifx */ 65 | 66 | -------------------------------------------------------------------------------- /src/ifx/workers/cursorclose.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef IFX_CURSORCLOSE_H 3 | #define IFX_CURSORCLOSE_H 4 | 5 | #include 6 | 7 | #include "../types.h" 8 | 9 | 10 | namespace ifx { 11 | namespace workers { 12 | 13 | class CursorClose : public Nan::AsyncWorker { 14 | public: 15 | 16 | CursorClose( ifx::cursor_t * cursor, Nan::Callback * cb ); 17 | virtual ~CursorClose(); 18 | 19 | void Execute(); 20 | 21 | 22 | protected: 23 | 24 | void HandleOKCallback(); 25 | 26 | 27 | private: 28 | 29 | ifx::cursor_t * _cursor; 30 | 31 | }; 32 | 33 | } /* end of namespace ifx::workers */ 34 | } /* end of namespace ifx */ 35 | 36 | #endif /* !IFX_CURSORCLOSE_H */ 37 | 38 | -------------------------------------------------------------------------------- /src/ifx/workers/disconnect.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "disconnect.h" 3 | #include "../../esqlc.h" 4 | 5 | 6 | namespace ifx { 7 | namespace workers { 8 | 9 | Disconnect::Disconnect( ifx::conn_t * conn, Nan::Callback * cb ) : Nan::AsyncWorker( cb ), _conn( conn ) { 10 | // constructor 11 | } 12 | 13 | 14 | Disconnect::~Disconnect() { 15 | // destructor 16 | } 17 | 18 | 19 | void Disconnect::Execute() { 20 | 21 | int32_t code = esqlc::disconnect( _conn->id.c_str() ); 22 | 23 | if ( code < 0 ) { 24 | SetErrorMessage( esqlc::errmsg( code ).c_str() ); 25 | } 26 | 27 | } 28 | 29 | 30 | void Disconnect::HandleOKCallback() { 31 | 32 | // stack-allocated handle scope 33 | Nan::HandleScope scope; 34 | 35 | // copy connection ID 36 | Nan::MaybeLocal< v8::String > v8connid = Nan::New< v8::String >( _conn->id ); 37 | 38 | // delete connection 39 | delete _conn; 40 | 41 | v8::Local< v8::Value > argv[] = { 42 | Nan::Null(), 43 | v8connid.ToLocalChecked() 44 | }; 45 | 46 | callback->Call( 2, argv, async_resource ); 47 | 48 | } 49 | 50 | } /* end of namespace workers */ 51 | } /* end of namespace ifx */ 52 | 53 | -------------------------------------------------------------------------------- /src/ifx/workers/disconnect.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef IFX_WORKERS_DISCONNECT_H 3 | #define IFX_WORKERS_DISCONNECT_H 4 | 5 | #include 6 | 7 | #include "../types.h" 8 | 9 | 10 | namespace ifx { 11 | namespace workers { 12 | 13 | class Disconnect : public Nan::AsyncWorker { 14 | public: 15 | 16 | Disconnect( ifx::conn_t * conn, Nan::Callback * cb ); 17 | virtual ~Disconnect(); 18 | 19 | void Execute(); 20 | 21 | 22 | protected: 23 | 24 | void HandleOKCallback(); 25 | 26 | 27 | private: 28 | 29 | ifx::conn_t * _conn; 30 | 31 | }; 32 | 33 | } /* end of namespace ifx::workers */ 34 | } /* end of namespace ifx */ 35 | 36 | #endif /* !IFX_WORKERS_DISCONNECT_H */ 37 | 38 | -------------------------------------------------------------------------------- /src/ifx/workers/fetch.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | #include "fetch.h" 7 | #include "../../esqlc.h" 8 | 9 | namespace ifx { 10 | namespace workers { 11 | 12 | Fetch::Fetch( ifx::cursor_t * cursor, Nan::Callback * cb ) : Nan::AsyncWorker( cb ), _cursor( cursor ) { 13 | // constructor 14 | } 15 | 16 | 17 | Fetch::~Fetch() { 18 | // destructor 19 | } 20 | 21 | 22 | void Fetch::Execute() { 23 | 24 | int32_t code = 0; 25 | 26 | // acquire the connection for this thread 27 | code = esqlc::acquire( _cursor->stmt->conn->id.c_str() ); 28 | if ( code < 0 ) { 29 | return SetErrorMessage( esqlc::errmsg( code ).c_str() ); 30 | } 31 | 32 | code = esqlc::fetch( _cursor->id.c_str(), _cursor->outsqlda ); 33 | std::strncpy( _sqlstate, esqlc::sqlstate(), sizeof( _sqlstate ) ); 34 | 35 | if ( code < 0 ) { 36 | SetErrorMessage( esqlc::errmsg( code ).c_str() ); 37 | } 38 | 39 | // release the connection 40 | esqlc::release( _cursor->stmt->conn->id.c_str() ); 41 | 42 | } 43 | 44 | 45 | void Fetch::HandleOKCallback() { 46 | 47 | // stack-allocated handle scope 48 | Nan::HandleScope scope; 49 | 50 | // check whether we have any results (i.e. rows returned) 51 | if ( std::strncmp( _sqlstate, "02", 2 ) == 0 ) { 52 | 53 | // no results, send back a null response 54 | v8::Local< v8::Value > argv[] = { 55 | Nan::Null(), 56 | Nan::Null() 57 | }; 58 | 59 | callback->Call( 2, argv, async_resource ); 60 | return; 61 | 62 | } 63 | 64 | // we have results, return as a data array 65 | v8::Local< v8::Array > result = Nan::New< v8::Array >( _cursor->outsqlda->sqld ); 66 | ifx_sqlvar_t * sqlvar = _cursor->outsqlda->sqlvar; 67 | for ( uint32_t i = 0; i < static_cast< size_t >( _cursor->outsqlda->sqld ); i++, sqlvar++ ) { 68 | switch ( sqlvar->sqltype ) { 69 | case SQLSMINT: 70 | result->Set( Nan::New< v8::Integer >( i ), Nan::New< v8::Int32 >(* reinterpret_cast( sqlvar->sqldata ) ) ); 71 | break; 72 | 73 | case SQLINT: 74 | case SQLSERIAL: 75 | result->Set( Nan::New< v8::Integer >( i ), Nan::New< v8::Int32 >(* reinterpret_cast( sqlvar->sqldata ) ) ); 76 | break; 77 | 78 | case SQLINFXBIGINT: 79 | case SQLBIGSERIAL: 80 | result->Set( Nan::New< v8::Integer >( i ), Nan::New< v8::Number >(* reinterpret_cast( sqlvar->sqldata ) ) ); 81 | break; 82 | 83 | case SQLFLOAT: 84 | case SQLSMFLOAT: 85 | result->Set( Nan::New< v8::Integer >( i ), Nan::New< v8::Number >( *sqlvar->sqldata ) ); 86 | break; 87 | 88 | case SQLMONEY: 89 | case SQLDECIMAL: 90 | { 91 | double n = 0; 92 | if ( dectodbl( reinterpret_cast< dec_t * >( sqlvar->sqldata ), &n ) == 0 ) { 93 | result->Set( Nan::New< v8::Integer >( i ), Nan::New< v8::Number >( n ) ); 94 | } else { 95 | char buffer[40]; 96 | std::memset( buffer, 0, sizeof( buffer ) ); 97 | dectoasc( reinterpret_cast< dec_t * >( sqlvar->sqldata ), buffer, ( sizeof( buffer ) - 1 ), -1 ); 98 | result->Set( Nan::New< v8::Integer >( i ), Nan::New< v8::String >( buffer ).ToLocalChecked() ); 99 | } 100 | } 101 | break; 102 | 103 | case SQLNULL: 104 | result->Set( Nan::New< v8::Integer >( i ), Nan::Null() ); 105 | break; 106 | 107 | case SQLDATE: 108 | { 109 | char buffer[25]; 110 | char format[] = "yyyy-mm-ddT00:00:00.000Z"; 111 | rfmtdate( *reinterpret_cast( sqlvar->sqldata ), format, buffer ); 112 | result->Set( Nan::New< v8::Integer >( i ), Nan::New< v8::String >( buffer ).ToLocalChecked() ); 113 | } 114 | break; 115 | 116 | case SQLDTIME: 117 | { 118 | char buffer[25]; 119 | char format[] = "%Y-%m-%dT%H:%M:%S.%F3Z"; 120 | dttofmtasc( reinterpret_cast< dtime * >( sqlvar->sqldata ), buffer, sizeof( buffer ), format ); 121 | result->Set( Nan::New< v8::Integer >( i ), Nan::New< v8::String >( buffer ).ToLocalChecked() ); 122 | } 123 | break; 124 | 125 | case SQLINTERVAL: 126 | { 127 | char buffer[40]; 128 | char format[] = "%d, %H:%M:%S.%F3"; 129 | intofmtasc( reinterpret_cast< intrvl * >( sqlvar->sqldata ), buffer, sizeof( buffer ), format ); 130 | result->Set( Nan::New< v8::Integer >( i ), Nan::New< v8::String >( buffer ).ToLocalChecked() ); 131 | } 132 | break; 133 | 134 | case SQLTEXT: 135 | { 136 | ifx_loc_t *loc = (ifx_loc_t *) sqlvar->sqldata; 137 | if (loc->loc_size > 0) { 138 | char buffer[loc->loc_size + 1]; 139 | memcpy( buffer, loc->loc_buffer, loc->loc_size ); 140 | buffer[loc->loc_size] = '\0'; 141 | result->Set( Nan::New< v8::Integer >( i ), Nan::New< v8::String >( buffer ).ToLocalChecked() ); 142 | } else { 143 | result->Set( Nan::New< v8::Integer >( i ), Nan::Undefined() ); 144 | } 145 | } 146 | break; 147 | case SQLBYTES: 148 | // TODO 149 | result->Set( Nan::New< v8::Integer >( i ), Nan::Undefined() ); 150 | break; 151 | 152 | case SQLINT8: 153 | case SQLSERIAL8: 154 | { 155 | int4 n = 0; 156 | if ( ifx_int8tolong( reinterpret_cast( sqlvar->sqldata ), &n ) == 0 ) { 157 | result->Set( Nan::New< v8::Integer >( i ), Nan::New< v8::Int32 >( n ) ); 158 | } else { 159 | char buffer[32]; 160 | std::memset( buffer, 0, sizeof( buffer ) ); 161 | ifx_int8toasc( reinterpret_cast< ifx_int8_t * >( sqlvar->sqldata ), buffer, sizeof( buffer ) ); 162 | result->Set( Nan::New< v8::Integer >( i ), Nan::New< v8::String >( buffer ).ToLocalChecked() ); 163 | } 164 | } 165 | break; 166 | 167 | default: 168 | result->Set( Nan::New< v8::Integer >( i ), Nan::New< v8::String >( sqlvar->sqldata ).ToLocalChecked() ); 169 | break; 170 | } 171 | } 172 | 173 | v8::Local< v8::Value > argv[] = { 174 | Nan::Null(), 175 | result 176 | }; 177 | 178 | callback->Call( 2, argv, async_resource ); 179 | 180 | } 181 | 182 | 183 | } /* end of namespace workers */ 184 | } /* end of namespace ifx */ 185 | 186 | -------------------------------------------------------------------------------- /src/ifx/workers/fetch.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef IFX_WORKERS_FETCH_H 3 | #define IFX_WORKERS_FETCH_H 4 | 5 | #include 6 | 7 | #include "../types.h" 8 | 9 | 10 | namespace ifx { 11 | namespace workers { 12 | 13 | class Fetch : public Nan::AsyncWorker { 14 | public: 15 | 16 | Fetch( ifx::cursor_t * cursor, Nan::Callback * cb ); 17 | virtual ~Fetch(); 18 | 19 | void Execute(); 20 | 21 | 22 | protected: 23 | 24 | void HandleOKCallback(); 25 | 26 | 27 | private: 28 | 29 | ifx::cursor_t * _cursor; 30 | char _sqlstate[6]; 31 | 32 | }; 33 | 34 | } /* end of namespace ifx::workers */ 35 | } /* end of namespace ifx */ 36 | 37 | #endif /* !IFX_WORKERS_FETCH_H */ 38 | 39 | -------------------------------------------------------------------------------- /src/ifx/workers/stmtexec.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include "stmtexec.h" 5 | #include "../../esqlc.h" 6 | 7 | 8 | namespace ifx { 9 | namespace workers { 10 | 11 | StmtExec::StmtExec( ifx::cursor_t * cursor, Nan::Callback * cb ) : Nan::AsyncWorker( cb ), _cursor( cursor ) { 12 | // constructor 13 | } 14 | 15 | 16 | StmtExec::~StmtExec() { 17 | // destructor 18 | } 19 | 20 | 21 | void StmtExec::Execute() { 22 | 23 | int32_t code = 0; 24 | 25 | // acquire the connection for this thread 26 | code = esqlc::acquire( _cursor->stmt->conn->id.c_str() ); 27 | if ( code < 0 ) { 28 | return SetErrorMessage( esqlc::errmsg( code ).c_str() ); 29 | } 30 | 31 | 32 | // execute the statement 33 | if ( ( _cursor->stmt->type == 0 ) 34 | || ( ( _cursor->stmt->type == SQ_EXECPROC ) 35 | && ( _cursor->stmt->outsqlda ) 36 | && ( _cursor->stmt->outsqlda->sqld > 0 ) ) 37 | ) { 38 | 39 | // this is a select statement or an execute procedure which returns 40 | // data, so execute and open a cursor 41 | code = esqlc::exec( 42 | _cursor->stmt->id.c_str(), 43 | _cursor->id.c_str(), 44 | _cursor->insqlda ); 45 | 46 | } else { 47 | code = esqlc::exec( 48 | _cursor->stmt->id.c_str(), 49 | _cursor->insqlda, 50 | &_cursor->serial ); 51 | } 52 | 53 | 54 | if ( code < 0 ) { 55 | SetErrorMessage( esqlc::errmsg( code ).c_str() ); 56 | } 57 | 58 | // release the connection 59 | esqlc::release( _cursor->stmt->conn->id.c_str() ); 60 | 61 | } 62 | 63 | 64 | void StmtExec::HandleErrorCallback() { 65 | 66 | // stack-allocated handle scope 67 | Nan::HandleScope scope; 68 | 69 | // free cursor memory allocation 70 | delete _cursor; 71 | 72 | // return value 73 | v8::Local< v8::Value > argv[] = { 74 | Nan::Error( Nan::New( ErrorMessage() ).ToLocalChecked() ), 75 | }; 76 | 77 | callback->Call( 1, argv, async_resource ); 78 | 79 | } 80 | 81 | 82 | void StmtExec::HandleOKCallback() { 83 | 84 | // stack-allocated handle scope 85 | Nan::HandleScope scope; 86 | 87 | // add cursor reference to the statement 88 | _cursor->stmt->cursors[ _cursor->id ] = _cursor; 89 | 90 | // return value 91 | v8::Local< v8::Value > argv[] = { 92 | Nan::Null(), 93 | Nan::New< v8::String >( _cursor->id ).ToLocalChecked() 94 | }; 95 | 96 | callback->Call( 2, argv, async_resource ); 97 | 98 | } 99 | 100 | } /* end of namespace workers */ 101 | } /* end of namespace ifx */ 102 | 103 | -------------------------------------------------------------------------------- /src/ifx/workers/stmtexec.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef IFX_WORKERS_STMTEXEC_H 3 | #define IFX_WORKERS_STMTEXEC_H 4 | 5 | #include 6 | 7 | #include "../types.h" 8 | 9 | 10 | namespace ifx { 11 | namespace workers { 12 | 13 | class StmtExec : public Nan::AsyncWorker { 14 | public: 15 | 16 | StmtExec( ifx::cursor_t * cursor, Nan::Callback * cb ); 17 | virtual ~StmtExec(); 18 | 19 | void Execute(); 20 | 21 | 22 | protected: 23 | 24 | void HandleErrorCallback(); 25 | void HandleOKCallback(); 26 | 27 | 28 | private: 29 | 30 | ifx::cursor_t * _cursor; 31 | 32 | }; 33 | 34 | } /* end of namespace workers */ 35 | } /* end of namespace ifx */ 36 | 37 | #endif /* !IFX_WORKERS_STMTEXEC_H */ 38 | 39 | -------------------------------------------------------------------------------- /src/ifx/workers/stmtfree.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "stmtfree.h" 3 | #include "../../esqlc.h" 4 | 5 | 6 | namespace ifx { 7 | namespace workers { 8 | 9 | StmtFree::StmtFree( ifx::stmt_t * stmt, Nan::Callback * cb ) : Nan::AsyncWorker( cb ), _stmt( stmt ) { 10 | // constructor 11 | } 12 | 13 | 14 | StmtFree::~StmtFree() { 15 | // destructor 16 | } 17 | 18 | 19 | void StmtFree::Execute() { 20 | 21 | int32_t code = 0; 22 | 23 | code = esqlc::acquire( _stmt->conn->id.c_str() ); 24 | if ( code < 0 ) { 25 | return SetErrorMessage( esqlc::errmsg( code ).c_str() ); 26 | } 27 | 28 | code = esqlc::free( _stmt->id.c_str() ); 29 | if ( code < 0 ) { 30 | SetErrorMessage( esqlc::errmsg( code ).c_str() ); 31 | } 32 | 33 | // release the connection 34 | esqlc::release( _stmt->conn->id.c_str() ); 35 | 36 | } 37 | 38 | 39 | void StmtFree::HandleOKCallback() { 40 | 41 | // stack-allocated handle scope 42 | Nan::HandleScope scope; 43 | 44 | // copy cursor ID 45 | Nan::MaybeLocal< v8::String > v8stmtid = Nan::New< v8::String >( _stmt->id ); 46 | 47 | // update internal references 48 | _stmt->conn->stmts.erase( _stmt->id ); 49 | 50 | // delete statement 51 | delete _stmt; 52 | 53 | v8::Local< v8::Value > argv[] = { 54 | Nan::Null(), 55 | v8stmtid.ToLocalChecked() 56 | }; 57 | 58 | callback->Call( 2, argv, async_resource ); 59 | 60 | } 61 | 62 | } /* end of namespace workers */ 63 | } /* end of namespace ifx */ 64 | 65 | -------------------------------------------------------------------------------- /src/ifx/workers/stmtfree.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef IFX_STMTFREE_H 3 | #define IFX_STMTFREE_H 4 | 5 | #include 6 | 7 | #include "../types.h" 8 | 9 | 10 | namespace ifx { 11 | namespace workers { 12 | 13 | class StmtFree : public Nan::AsyncWorker { 14 | public: 15 | 16 | StmtFree( ifx::stmt_t * stmt, Nan::Callback * cb ); 17 | virtual ~StmtFree(); 18 | 19 | void Execute(); 20 | 21 | 22 | protected: 23 | 24 | void HandleOKCallback(); 25 | 26 | 27 | private: 28 | 29 | ifx::stmt_t * _stmt; 30 | 31 | }; 32 | 33 | } /* end of namespace ifx::workers */ 34 | } /* end of namespace ifx */ 35 | 36 | #endif /* !IFX_STMTFREE_H */ 37 | 38 | -------------------------------------------------------------------------------- /src/ifx/workers/stmtprepare.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "stmtprepare.h" 3 | #include "../../esqlc.h" 4 | 5 | 6 | namespace ifx { 7 | namespace workers { 8 | 9 | StmtPrepare::StmtPrepare( ifx::stmt_t * stmt, Nan::Callback * cb ) : Nan::AsyncWorker( cb ), _stmt( stmt ) { 10 | // constructor 11 | } 12 | 13 | 14 | StmtPrepare::~StmtPrepare() { 15 | // destructor 16 | } 17 | 18 | 19 | void StmtPrepare::Execute() { 20 | 21 | int32_t code = 0; 22 | 23 | code = esqlc::acquire( _stmt->conn->id.c_str() ); 24 | if ( code < 0 ) { 25 | return SetErrorMessage( esqlc::errmsg( code ).c_str() ); 26 | } 27 | 28 | // Not ideal to access _stmt->(in|out)sqlda pointers directly here since we are 29 | // out of the main event loop. However it is unlikely we'll have two threads 30 | // with the same statement ID coming here due to the uniqueue checks we have 31 | // in the main event loop code. 32 | code = esqlc::prepare( 33 | _stmt->id.c_str(), 34 | _stmt->stmt.c_str(), 35 | _stmt->insqlda, 36 | _stmt->outsqlda ); 37 | 38 | if ( code < 0 ) { 39 | SetErrorMessage( esqlc::errmsg( code ).c_str() ); 40 | } else { 41 | // 0 or a positive code represents the type of statement 42 | _stmt->type = code; 43 | } 44 | 45 | // release the connection 46 | esqlc::release( _stmt->conn->id.c_str() ); 47 | 48 | } 49 | 50 | void StmtPrepare::HandleOKCallback() { 51 | 52 | // stack-allocated handle scope 53 | Nan::HandleScope scope; 54 | 55 | // calculate the size of the output data buffer we need 56 | _stmt->size = 0; 57 | ifx_sqlvar_t * sqlvar = _stmt->outsqlda->sqlvar; 58 | for ( int8_t i = 0; i < _stmt->outsqlda->sqld; i++, sqlvar++ ) { 59 | if ( sqlvar->sqltype == SQLCHAR ) { 60 | sqlvar->sqllen += 1; 61 | } 62 | 63 | _stmt->size = rtypalign( _stmt->size, sqlvar->sqltype ); 64 | _stmt->size += rtypmsize( sqlvar->sqltype, sqlvar->sqllen ); 65 | } 66 | 67 | 68 | v8::Local< v8::Value > argv[] = { 69 | Nan::Null(), 70 | Nan::New< v8::String >( _stmt->id ).ToLocalChecked() 71 | }; 72 | 73 | callback->Call( 2, argv, async_resource ); 74 | 75 | } 76 | 77 | } /* end of namespace workers */ 78 | } /* end of namespace ifx */ 79 | 80 | -------------------------------------------------------------------------------- /src/ifx/workers/stmtprepare.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef IFX_WORKERS_STMTPREPARE_H 3 | #define IFX_WORKERS_STMTPREPARE_H 4 | 5 | #include 6 | 7 | #include "../types.h" 8 | 9 | 10 | namespace ifx { 11 | namespace workers { 12 | 13 | class StmtPrepare : public Nan::AsyncWorker { 14 | public: 15 | 16 | StmtPrepare( ifx::stmt_t * stmt, Nan::Callback * cb ); 17 | virtual ~StmtPrepare(); 18 | 19 | void Execute(); 20 | 21 | 22 | protected: 23 | 24 | void HandleOKCallback(); 25 | 26 | 27 | private: 28 | 29 | ifx::stmt_t * _stmt; 30 | 31 | }; 32 | 33 | } /* end of namespace workers */ 34 | } /* end of namespace ifx */ 35 | 36 | #endif /* !IFX_WORKERS_STMTPREPARE_H */ 37 | 38 | -------------------------------------------------------------------------------- /src/module.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include "ifx/ifx.h" 5 | 6 | 7 | void init( v8::Local< v8::Object > exports ) { 8 | ifx::Ifx::init( exports ); 9 | } 10 | 11 | NODE_MODULE( ifx, init ) 12 | 13 | -------------------------------------------------------------------------------- /test/data-types.test.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | const expect = require( 'chai' ).expect; 5 | const sinon = require( 'sinon' ); 6 | const moment = require( 'moment' ); 7 | 8 | const Informix = require( '../lib/informix' ); 9 | 10 | 11 | describe( 'data-types', () => { 12 | 13 | const informix = new Informix( { 14 | database : 'test@ol_informix1210', 15 | username : 'informix', 16 | password : 'informix' 17 | } ); 18 | 19 | const values = { 20 | dt : '2017-02-17 17:20:56.002', 21 | date : new Date( '2017-02-18' ), 22 | decimal : 7.964439875659, 23 | bigint: 2^62, 24 | atext: 'A TEXT' 25 | }; 26 | 27 | before( () => { 28 | return informix.prepare( 29 | 'insert into tdatatypes(' + 30 | 'dt, ' + 31 | 'date, ' + 32 | 'decimal, ' + 33 | 'bigint,' + 34 | 'atext' + 35 | ') ' + 36 | 'values(' + 37 | '"' + values.dt + '", ' + 38 | moment( values.date ).format( '[mdy(]MM,DD,YYYY[), ]' ) + 39 | values.decimal + ',' + 40 | values.bigint + ',' + 41 | '?' + 42 | ');' 43 | ) 44 | .then( ( stmt ) => { 45 | return stmt.exec([ values.atext ]); 46 | } ) 47 | .then( ( cursor ) => { 48 | return cursor.close(); 49 | } ); 50 | } ); 51 | 52 | after( () => { 53 | return informix.query( 'truncate tdatatypes;' ) 54 | .then( ( cursor ) => { 55 | return cursor.close(); 56 | } ); 57 | } ); 58 | 59 | 60 | it( 'should fetch int8/serial8 values correctly', () => { 61 | return informix.query( 'select id from tdatatypes;' ) 62 | .then( ( cursor ) => { 63 | return cursor.fetchAll( { close : true } ); 64 | } ) 65 | .then( ( results ) => { 66 | expect( results ).to.have.length( 1 ) 67 | .with.nested.property( '[0][0]' ) 68 | .that.is.a( 'number' ); 69 | } ); 70 | } ); 71 | 72 | it( 'should return date values in ISO format', () => { 73 | return informix.query( 'select date from tdatatypes;' ) 74 | .then( ( cursor ) => { 75 | return cursor.fetchAll( { close : true } ); 76 | } ) 77 | .then( ( results ) => { 78 | expect( results ).to.have.length( 1 ) 79 | .with.nested.property( '[0][0]' ) 80 | .that.eql( values.date.toISOString() ); 81 | } ); 82 | } ); 83 | 84 | it( 'should return datetime values in ISO format', () => { 85 | return informix.query( 'select dt from tdatatypes;' ) 86 | .then( ( cursor ) => { 87 | return cursor.fetchAll( { close : true } ); 88 | } ) 89 | .then( ( results ) => { 90 | expect( results ).to.have.length( 1 ) 91 | .with.nested.property( '[0][0]' ) 92 | .that.eql( new Date( values.dt ).toISOString() ); 93 | } ); 94 | } ); 95 | 96 | it( 'should fetch decimal values correctly', () => { 97 | return informix.query( 'select decimal from tdatatypes;' ) 98 | .then( ( cursor ) => { 99 | return cursor.fetchAll( { close : true } ); 100 | } ) 101 | .then( ( results ) => { 102 | expect( results ).to.have.length( 1 ) 103 | .with.nested.property( '[0][0]' ) 104 | .that.eql( values.decimal ); 105 | } ); 106 | } ); 107 | 108 | it( 'should fetch bigint values correctly', () => { 109 | return informix.query( 'select bigint from tdatatypes;' ) 110 | .then( ( cursor ) => { 111 | return cursor.fetchAll( { close : true } ); 112 | } ) 113 | .then( ( results ) => { 114 | expect( results ).to.have.length( 1 ) 115 | .with.nested.property( '[0][0]' ) 116 | .that.eql( values.bigint ); 117 | } ); 118 | } ); 119 | 120 | it( 'should fetch text values correctly', () => { 121 | return informix.query( 'select atext from tdatatypes;' ) 122 | .then( ( cursor ) => { 123 | return cursor.fetchAll( { close : true } ); 124 | } ) 125 | .then( ( results ) => { 126 | expect( results ).to.have.length( 1 ) 127 | .with.nested.property( '[0][0]' ) 128 | .that.eql( values.atext ); 129 | } ); 130 | } ); 131 | 132 | } ); 133 | 134 | -------------------------------------------------------------------------------- /test/ifx.test.js: -------------------------------------------------------------------------------- 1 | /* jshint expr: true */ 2 | 3 | 'use strict'; 4 | 5 | var expect = require( 'chai' ).expect; 6 | var Ifx = require( '../' ).Ifx; 7 | 8 | 9 | describe( 'ifx', function () { 10 | 11 | before( function () { 12 | process.env.INFORMIXSERVER = 'ol_informix1210'; 13 | } ); 14 | 15 | 16 | context( 'when username and password is not specified', function () { 17 | 18 | var ifx = new Ifx(); 19 | 20 | it( 'should handle connection errors', function ( done ) { 21 | ifx.connect( { 22 | id : 'conn:id:1001', 23 | database : 'test', 24 | username : 'dummy', 25 | password : 'dummy', 26 | }, function ( err, conn ) { 27 | expect( err ).to.be.an.instanceof( Error ); 28 | expect( err.message ).to.be.string( '[-951] Incorrect password or user %s is not known on the database server.' ); 29 | expect( conn ).to.be.undefined; 30 | done(); 31 | } ); 32 | } ); 33 | 34 | } ); 35 | 36 | 37 | context( 'connect', function () { 38 | 39 | var ifx = new Ifx(); 40 | 41 | it( 'should validate input arguments', function () { 42 | 43 | try { 44 | ifx.connect(); 45 | } catch ( e ) { 46 | expect( e ).to.be.an.instanceof( Error ); 47 | expect( e.message ).to.be.string( 'Invalid number of arguments' ); 48 | } 49 | 50 | try { 51 | ifx.connect( 1, 2, 3 ); 52 | } catch ( e ) { 53 | expect( e ).to.be.an.instanceof( Error ); 54 | expect( e.message ).to.be.string( 'Invalid number of arguments' ); 55 | } 56 | 57 | try { 58 | ifx.connect( 'value', function () {} ); 59 | } catch ( e ) { 60 | expect( e ).to.be.an.instanceof( TypeError ); 61 | expect( e.message ).to.be.string( 'Connection parameters must be an object' ); 62 | } 63 | 64 | try { 65 | ifx.connect( {}, 'function' ); 66 | } catch ( e ) { 67 | expect( e ).to.be.an.instanceof( TypeError ); 68 | expect( e.message ).to.be.string( 'Callback must be a function' ); 69 | } 70 | 71 | try { 72 | ifx.connect( {}, function () {} ); 73 | } catch ( e ) { 74 | expect( e ).to.be.an.instanceof( TypeError ); 75 | expect( e.message ).to.be.string( "Connection parameter 'id' and 'database' are mandatory" ); 76 | } 77 | 78 | } ); 79 | 80 | 81 | /** 82 | * Note: This test assumes there is a 'test' database and a 'informix' user. 83 | */ 84 | it( 'should be able to connect to a database', function ( done ) { 85 | ifx.connect( { 86 | id : 'conn:id:2001', 87 | database : 'test', 88 | username : 'informix', 89 | password : 'informix' 90 | }, function ( err, conn ) { 91 | expect( err ).to.be.null; 92 | expect( conn ).to.be.string( 'conn:id:2001' ); 93 | done(); 94 | } ); 95 | } ); 96 | 97 | } ); 98 | 99 | 100 | context( 'prepare', function () { 101 | 102 | var ifx = new Ifx(); 103 | var conn = 'conn:id:3001'; 104 | 105 | before( function ( done ) { 106 | ifx.connect( { 107 | id : conn, 108 | database : 'test', 109 | username : 'informix', 110 | password : 'informix' 111 | }, function ( err, conn ) { 112 | done( err ); 113 | } ); 114 | } ); 115 | 116 | 117 | it( 'should be able to prepare a statement', function ( done ) { 118 | 119 | var sql = 'select tabname from systables where tabname like ?;'; 120 | 121 | ifx.prepare( conn, 'stmt_id_1001', sql, function ( err, sid ) { 122 | expect( err ).to.be.null; 123 | expect( sid ).to.be.string( 'stmt_id_1001' ); 124 | done(); 125 | } ); 126 | 127 | } ); 128 | 129 | } ); 130 | 131 | 132 | context( 'exec', function () { 133 | 134 | var ifx = new Ifx(); 135 | var connid = 'conn:id:4001'; 136 | 137 | before( function ( done ) { 138 | ifx.connect( { 139 | id : connid, 140 | database : 'test', 141 | username : 'informix', 142 | password : 'informix' 143 | }, function ( err, connid ) { 144 | done( err ); 145 | } ); 146 | } ); 147 | 148 | 149 | context( 'when a select statement is prepared', function () { 150 | 151 | var stmtid = 'statement_4001'; 152 | var curid = 'cursor_4001'; 153 | var sql = 'select tabname from systables where tabname like ?;'; 154 | 155 | before( function ( done ) { 156 | ifx.prepare( connid, stmtid, sql, function ( err, stmtid ) { 157 | done( err ); 158 | } ); 159 | } ); 160 | 161 | it( 'should be able to execute the prepared statement', function ( done ) { 162 | ifx.exec( connid, stmtid, curid, 'sys%auth', function ( err, id ) { 163 | expect( err ).to.be.null; 164 | expect( id ).to.eql( curid ); 165 | done(); 166 | } ); 167 | } ); 168 | 169 | } ); 170 | 171 | 172 | context( 'when an insert statement is prepared', function () { 173 | 174 | var stmtid = 'statement_4002'; 175 | var curid = 'cursor_4002'; 176 | var sql = 'insert into tcustomers( fname, lname ) values( ?, ? );'; 177 | 178 | before( function ( done ) { 179 | ifx.prepare( connid, stmtid, sql, function ( err, stmtid ) { 180 | done( err ); 181 | } ); 182 | } ); 183 | 184 | it( 'should be able to execute the prepared statement', function ( done ) { 185 | ifx.exec( connid, stmtid, curid, [ 'First', 'Last' ], function ( err, id ) { 186 | expect( err ).to.be.null; 187 | expect( id ).to.eql( curid ); 188 | done(); 189 | } ); 190 | } ); 191 | 192 | } ); 193 | 194 | 195 | context( 'when an execute procedure statement is prepared which returns a value', function () { 196 | 197 | var stmtid = 'statement_4003'; 198 | var curid = 'cursor_4003'; 199 | var sql = 'execute procedure pinscustomer( ?, ? );'; 200 | 201 | before( function ( done ) { 202 | ifx.prepare( connid, stmtid, sql, function ( err, stmtid ) { 203 | done( err ); 204 | } ); 205 | } ); 206 | 207 | it( 'should be able to execute the prepared statement', function ( done ) { 208 | ifx.exec( connid, stmtid, curid, [ 'First', 'Last' ], function ( err, id ) { 209 | expect( err ).to.be.null; 210 | expect( id ).to.eql( curid ); 211 | done(); 212 | } ); 213 | } ); 214 | 215 | } ); 216 | 217 | 218 | context( 'when an execute procedure statement is prepared which does not return any values', function () { 219 | 220 | var stmtid = 'statement_4004'; 221 | var curid = 'cursor_4004'; 222 | var sql = 'execute procedure ppurgecustomers();'; 223 | 224 | before( function ( done ) { 225 | ifx.prepare( connid, stmtid, sql, function ( err, stmtid ) { 226 | done( err ); 227 | } ); 228 | } ); 229 | 230 | it( 'should be able to execute the prepared statement', function ( done ) { 231 | ifx.exec( connid, stmtid, curid, function ( err, id ) { 232 | expect( err ).to.be.null; 233 | expect( id ).to.eql( curid ); 234 | done(); 235 | } ); 236 | } ); 237 | 238 | } ); 239 | 240 | } ); 241 | 242 | 243 | context( 'fetch', function () { 244 | 245 | var ifx = new Ifx(); 246 | var sql = 'select tabname from systables where tabname like ?;'; 247 | 248 | context( 'when there are results', function () { 249 | 250 | var connid = 'conn:id:5001'; 251 | var stmtid = 'statement_5001'; 252 | var curid = 'cursor_5001'; 253 | 254 | before( function ( done ) { 255 | ifx.connect( { 256 | id : connid, 257 | database : 'test', 258 | username : 'informix', 259 | password : 'informix' 260 | }, function ( err, conn ) { 261 | expect( err ).to.be.null; 262 | 263 | ifx.prepare( connid, stmtid, sql, function ( err, stmtid ) { 264 | expect( err ).to.be.null; 265 | 266 | ifx.exec( connid, stmtid, curid, 'sys%auth', function ( err, id ) { 267 | done( err ); 268 | } ); 269 | } ); 270 | } ); 271 | } ); 272 | 273 | it( 'should return a results array', function ( done ) { 274 | ifx.fetch( curid, function ( err, result ) { 275 | expect( err ).to.be.null; 276 | expect( result ).to.be.an.instanceof( Array ); 277 | expect( result ).to.have.length( 1 ); 278 | done(); 279 | } ); 280 | } ); 281 | 282 | } ); 283 | 284 | 285 | context( 'when there are no results', function () { 286 | 287 | var connid = 'conn:id:5002'; 288 | var stmtid = 'statement_5002'; 289 | var curid = 'cursor_5002'; 290 | 291 | before( function ( done ) { 292 | ifx.connect( { 293 | id : connid, 294 | database : 'test', 295 | username : 'informix', 296 | password : 'informix' 297 | }, function ( err, conn ) { 298 | expect( err ).to.be.null; 299 | 300 | ifx.prepare( connid, stmtid, sql, function ( err, stmtid ) { 301 | expect( err ).to.be.null; 302 | 303 | ifx.exec( connid, stmtid, curid, 'sys%authxxxxxx', function ( err, id ) { 304 | done( err ); 305 | } ); 306 | } ); 307 | } ); 308 | } ); 309 | 310 | it( 'should return a null result', function ( done ) { 311 | ifx.fetch( curid, function ( err, result ) { 312 | expect( err ).to.be.null; 313 | expect( result ).to.be.null; 314 | done(); 315 | } ); 316 | } ); 317 | 318 | } ); 319 | 320 | } ); 321 | 322 | } ); 323 | 324 | -------------------------------------------------------------------------------- /test/informix.test.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | var expect = require( 'chai' ).expect; 5 | var informix = require( '../' ); 6 | var Informix = require( '../' ).Informix; 7 | 8 | 9 | describe( 'informix', function () { 10 | 11 | context( 'factory function', function () { 12 | it( 'should create a new client instance', function () { 13 | var client = informix(); 14 | expect( client ).to.be.an.instanceof( Informix ); 15 | } ); 16 | } ); 17 | 18 | } ); 19 | 20 | -------------------------------------------------------------------------------- /test/issues.test.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | const expect = require( 'chai' ).expect; 5 | const Informix = require( '../lib/informix' ); 6 | 7 | 8 | describe( 'issues', () => { 9 | 10 | const informix = new Informix( { 11 | database : 'test@ol_informix1210', 12 | username : 'informix', 13 | password : 'informix' 14 | } ); 15 | 16 | context( 'issue #45', () => { 17 | before( () => { 18 | return informix 19 | .query( 20 | 'create table tissue45(' + 21 | 'c char(2) default \'AA\' not null' + 22 | ');' 23 | ) 24 | .then( ( cursor ) => { 25 | return cursor.close(); 26 | } ) 27 | .then( () => { 28 | return informix.query( 'insert into tissue45 (c) values ( \'CC\' );' ); 29 | } ) 30 | .then( ( cursor) => { 31 | return cursor.close(); 32 | } ); 33 | } ); 34 | 35 | after( () => { 36 | return informix 37 | .query( 38 | 'drop table tissue45;' 39 | ) 40 | .then( ( cursor ) => { 41 | return cursor.close(); 42 | } ); 43 | } ); 44 | 45 | it( 'should return correct length when re-using prepared statements with char columns', () => { 46 | let stmt; 47 | 48 | return informix 49 | .prepare( 'select c from tissue45 limit 1;' ) 50 | .then( ( s ) => { 51 | stmt = s; 52 | return stmt.exec(); 53 | } ) 54 | .then( ( cursor ) => { 55 | return cursor.fetchAll( { close : true } ); 56 | } ) 57 | .then( ( results ) => { 58 | expect( results ).to.have.length( 1 ) 59 | .with.nested.property( '[0][0]' ) 60 | .with.length( 2 ); 61 | } ) 62 | .then( () => { 63 | return stmt.exec(); 64 | } ) 65 | .then( ( cursor ) => { 66 | return cursor.fetchAll( { close : true } ); 67 | } ) 68 | .then( ( results ) => { 69 | expect( results ).to.have.length( 1 ) 70 | .with.nested.property( '[0][0]' ) 71 | .with.length( 2 ); 72 | } ); 73 | } ); 74 | } ); 75 | } ); 76 | 77 | -------------------------------------------------------------------------------- /test/lib.connection.test.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | var expect = require( 'chai' ).expect; 5 | var Ifx = require( '../' ).Ifx; 6 | 7 | var Connection = require( '../lib/connection' ); 8 | var Statement = require( '../lib/statement' ); 9 | var Pool = require( '../lib/pool' ); 10 | 11 | 12 | describe( 'lib/Connection', function () { 13 | 14 | it( 'should be possible to acquire and release the connection', function () { 15 | var conn = new Connection( new Ifx() ); 16 | return conn.acquire() 17 | .then( function ( conn ) { 18 | expect( conn ).to.be.an.instanceof( Connection ); 19 | return conn.release(); 20 | } ); 21 | } ); 22 | 23 | 24 | context( 'connection index', function () { 25 | 26 | it( 'should return the correct connection index when passed in as options', function () { 27 | var conn = new Connection( new Ifx(), { index : 2 } ); 28 | expect( conn.index() ).to.eql( 2 ); 29 | } ); 30 | 31 | it( 'should return -1 when index is not passed in as options', function () { 32 | var conn = new Connection( new Ifx() ); 33 | expect( conn.index() ).to.eql( -1 ); 34 | } ); 35 | 36 | } ); 37 | 38 | 39 | context( 'when connecting to a database', function () { 40 | 41 | var conn = {}; 42 | beforeEach( function () { 43 | conn = new Connection( new Ifx() ); 44 | } ); 45 | 46 | 47 | it( 'should resolve the promise upon success', function () { 48 | var params = { 49 | database : 'test@ol_informix1210', 50 | username : 'informix', 51 | password : 'informix' 52 | }; 53 | 54 | return conn.connect( params ) 55 | .then( function ( c ) { 56 | expect( c ).to.be.an.instanceof( Connection ); 57 | } ); 58 | } ); 59 | 60 | it( 'should reject the the promise upon failure', function () { 61 | var params = { 62 | database : 'dummy@ol_informix1210', 63 | username : 'informix', 64 | password : 'informix' 65 | }; 66 | 67 | return conn.connect( params ) 68 | .then( function ( c ) { 69 | throw new Error( 'Expected the connection to fail, but it did not!!!' ); 70 | } ) 71 | .catch( function ( err ) { 72 | expect( err ).to.be.an.instanceof( Error ); 73 | expect( err.message ).to.be.string( '[-329] Database not found or no system permission.' ); 74 | } ); 75 | } ); 76 | 77 | it( 'should set the INFORMIXSERVER environment variable', function () { 78 | var params = { 79 | database : 'test@ol_informix1210', 80 | username : 'informix', 81 | password : 'informix' 82 | }; 83 | 84 | process.env.INFORMIXSERVER = 'dummy'; 85 | return conn.connect( params ) 86 | .then( function ( c ) { 87 | expect( process.env.INFORMIXSERVER ).to.be.string( 'ol_informix1210' ); 88 | } ); 89 | } ); 90 | 91 | } ); 92 | 93 | 94 | context( 'when initialised as part of a connection pool', function () { 95 | 96 | var conn = {}; 97 | before( function () { 98 | var pool = new Pool( { 99 | database : 'test@ol_informix1210', 100 | username : 'informix', 101 | password : 'informix' 102 | } ); 103 | 104 | return pool.acquire() 105 | .then( function ( c ) { 106 | conn = c; 107 | pool.release( c ); 108 | } ); 109 | } ); 110 | 111 | it( 'should be able to prepare a statement', function () { 112 | var sql = 'select tabname from systables where tabname like ?;'; 113 | return conn.prepare( sql ) 114 | .then( function ( stmt ) { 115 | expect( stmt ).to.be.an.instanceof( Statement ); 116 | } ); 117 | } ); 118 | 119 | } ); 120 | 121 | } ); 122 | 123 | -------------------------------------------------------------------------------- /test/lib.context.test.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | 5 | var expect = require( 'chai' ).expect; 6 | var sinon = require( 'sinon' ); 7 | 8 | var Context = require( '../lib/context' ); 9 | var Pool = require( '../lib/pool' ); 10 | var Statement = require( '../lib/statement' ); 11 | 12 | 13 | describe( 'lib/Context', function () { 14 | 15 | var pool = new Pool( { 16 | max : 1, 17 | database : 'test@ol_informix1210', 18 | username : 'informix', 19 | password : 'informix' 20 | } ); 21 | 22 | 23 | it( 'should be possible to execute a query', function () { 24 | var ctx = new Context( pool ); 25 | return ctx.query( 'select count(*) from systables;' ) 26 | .then( function ( cursor ) { 27 | return cursor.fetchAll( { close : true } ); 28 | } ) 29 | .then( function ( results ) { 30 | expect( results ).to.have.length( 1 ); 31 | return ctx.end(); 32 | } ); 33 | } ); 34 | 35 | it( 'should be possible to prepare a query', function () { 36 | var ctx = new Context( pool ); 37 | return ctx.prepare( 'select count(*) from systables where tabname like ?;' ) 38 | .then( function ( stmt ) { 39 | expect( stmt ).to.be.an.instanceof( Statement ); 40 | return stmt.free(); 41 | } ) 42 | .then( function ( stmtid ) { 43 | return ctx.end(); 44 | } ); 45 | } ); 46 | 47 | 48 | context( 'when working with transactions', function () { 49 | 50 | var ctx = {}; 51 | before( function () { 52 | ctx = new Context( pool ); 53 | } ); 54 | 55 | after( function () { 56 | return ctx.end(); 57 | } ); 58 | 59 | 60 | it( 'should be possible to commit transactions', function () { 61 | var serial; 62 | return ctx.begin() 63 | .then( function () { 64 | return ctx.query( "execute procedure pinscustomer( 'Name', '" + ctx.id() + "' );" ); 65 | } ) 66 | .then( function ( cursor ) { 67 | return cursor.fetchAll( { close : true } ); 68 | } ) 69 | .then( function ( results ) { 70 | serial = results[0][0]; 71 | return ctx.commit(); 72 | } ) 73 | .then( function () { 74 | return ctx.query( 'select * from tcustomers where id = ' + serial + ';' ); 75 | } ) 76 | .then( function ( cursor ) { 77 | return cursor.fetchAll( { close : true } ); 78 | } ) 79 | .then( function ( results ) { 80 | expect( results ).to.have.length( 1 ); 81 | expect( results[0][0] ).to.eq( serial ); 82 | } ); 83 | } ); 84 | 85 | it( 'should be possible to rollback transactions', function () { 86 | var count, stmt; 87 | return ctx.prepare( 'select count(*) from tcustomers;' ) 88 | .then( function ( s ) { 89 | stmt = s; 90 | return stmt.exec(); 91 | } ) 92 | .then( function ( cursor ) { 93 | return cursor.fetchAll( { close : true } ); 94 | } ) 95 | .then( function ( results ) { 96 | count = results[0][0]; 97 | expect( count ).to.be.at.least( 0 ); 98 | } ) 99 | .then( function () { 100 | return ctx.begin(); 101 | } ) 102 | .then( function () { 103 | return ctx.query( "insert into tcustomers( fname, lname ) values( 'Name', '" + ctx.id() + "' );" ); 104 | } ) 105 | .then( function ( cursor ) { 106 | expect( cursor.serial() ).to.be.gt( count ); 107 | return cursor.close(); 108 | } ) 109 | .then( function () { 110 | return ctx.rollback(); 111 | } ) 112 | .then( function () { 113 | return stmt.exec(); 114 | } ) 115 | .then( function ( cursor ) { 116 | return cursor.fetchAll( { close : true } ); 117 | } ) 118 | .then( function ( results ) { 119 | expect( results[0][0] ).to.eq( count ); 120 | } ); 121 | } ); 122 | 123 | } ); 124 | 125 | 126 | context( 'when ending the context', function () { 127 | 128 | var ctx = {}; 129 | beforeEach( function () { 130 | ctx = new Context( pool ); 131 | } ); 132 | 133 | 134 | it ( 'should cleanup any cached statements', function () { 135 | return ctx.begin() 136 | .then( function () { 137 | return ctx.commit(); 138 | } ) 139 | .then( function () { 140 | return ctx.begin(); 141 | } ) 142 | .then( function () { 143 | return ctx.rollback(); 144 | } ) 145 | .then( function () { 146 | expect( ctx.$.stmts.begin ).to.be.an.instanceof( Promise ); 147 | expect( ctx.$.stmts.commit ).to.be.an.instanceof( Promise ); 148 | expect( ctx.$.stmts.rollback ).to.be.an.instanceof( Promise ); 149 | } ) 150 | .then( function () { 151 | return ctx.end(); 152 | } ); 153 | } ); 154 | 155 | 156 | it( 'should rollback if there is an open transaction', function () { 157 | var spy = sinon.spy( ctx, 'rollback' ); 158 | return ctx.begin() 159 | .then( function () { 160 | expect( ctx.$.transaction ).to.eq( true ); 161 | } ) 162 | .then( function () { 163 | return ctx.end(); 164 | } ) 165 | .then( function () { 166 | expect( spy.calledOnce ).to.eq( true ); 167 | spy.reset(); 168 | } ); 169 | } ); 170 | 171 | } ); 172 | 173 | } ); 174 | 175 | -------------------------------------------------------------------------------- /test/lib.cursor.test.js: -------------------------------------------------------------------------------- 1 | /* jshint expr: true */ 2 | 3 | 'use strict'; 4 | 5 | var expect = require( 'chai' ).expect; 6 | var sinon = require( 'sinon' ); 7 | 8 | var Statement = require( '../lib/statement' ); 9 | var Cursor = require( '../lib/cursor' ); 10 | var Pool = require( '../lib/pool' ); 11 | 12 | 13 | describe( 'lib/Cursor', function () { 14 | 15 | var conn = {}; 16 | 17 | before( function () { 18 | var pool = new Pool( { 19 | max : 1, 20 | database : 'test@ol_informix1210', 21 | username : 'informix', 22 | password : 'informix' 23 | } ); 24 | 25 | return pool.acquire() 26 | .then( function ( c ) { 27 | conn = c; 28 | pool.release( c ); 29 | } ); 30 | } ); 31 | 32 | 33 | context( 'when a query with results is executed', function () { 34 | 35 | var cursor = {}; 36 | var stmt = {}; 37 | 38 | before( function () { 39 | var sql = 'select tabname from systables where tabname like ?;'; 40 | stmt = new Statement( conn ); 41 | return stmt.prepare( sql ); 42 | } ); 43 | 44 | beforeEach( function () { 45 | return stmt.exec( 'sys%auth' ) 46 | .then( function ( c ) { 47 | expect( c ).to.be.an.instanceof( Cursor ); 48 | cursor = c; 49 | } ); 50 | } ); 51 | 52 | after( function () { 53 | return stmt.free(); 54 | } ); 55 | 56 | 57 | it( 'should be possible to fetch a result', function () { 58 | return cursor.fetch() 59 | .then( function ( result ) { 60 | expect( result ).to.be.an.instanceof( Array ); 61 | expect( result ).to.have.length( 1 ); 62 | expect( result[0] ).to.match( /^sys(\w+)?auth$/ ); 63 | return cursor.close(); 64 | } ); 65 | } ); 66 | 67 | it( 'should be possible to fetch all results', function () { 68 | return cursor.fetchAll() 69 | .then( function ( results ) { 70 | expect( results ).to.be.an.instanceof( Array ); 71 | expect( results ).to.have.length.of.at.least( 4 ); 72 | expect( results[0] ).to.be.an( 'array' ) 73 | .with.length( 1 ); 74 | return cursor.close(); 75 | } ); 76 | } ); 77 | 78 | it( 'should be possible to fetch all results and close the cursor', function () { 79 | var spy = sinon.spy( cursor, 'close' ); 80 | return cursor.fetchAll( { close : true } ) 81 | .then( function ( results ) { 82 | expect( spy.calledOnce ).to.be.true; 83 | spy.reset(); 84 | } ); 85 | } ); 86 | 87 | 88 | context( 'when failing to close cursor', function () { 89 | 90 | beforeEach( function () { 91 | sinon.stub( cursor.$.ifx, 'close', function ( curid, cb ) { 92 | cb( new Error( '[stub] Failed to close.' ) ); 93 | } ); 94 | } ); 95 | 96 | afterEach( function () { 97 | cursor.$.ifx.close.restore(); 98 | return cursor.close(); 99 | } ); 100 | 101 | 102 | it( 'should handle failures when fetching all and closing cursor', function () { 103 | return cursor.fetchAll( { close : true } ) 104 | .then( function ( results ) { 105 | throw new Error( 'Expected to fail, but it did not!!!' ); 106 | } ) 107 | .catch( function ( err ) { 108 | expect( err.message ).to.be.string( '[stub] Failed to close.' ); 109 | } ); 110 | } ); 111 | 112 | } ); 113 | 114 | } ); 115 | 116 | 117 | context( 'when an insert query is executed', function () { 118 | 119 | var cursor = {}; 120 | var stmt = {}; 121 | 122 | before( function () { 123 | var sql = 'insert into tcustomers( fname, lname ) values( ?, ? );'; 124 | stmt = new Statement( conn ); 125 | return stmt.prepare( sql ); 126 | } ); 127 | 128 | beforeEach( function () { 129 | return stmt.exec( [ conn.id(), 'Name' ] ) 130 | .then( function ( c ) { 131 | expect( c ).to.be.an.instanceof( Cursor ); 132 | cursor = c; 133 | } ); 134 | } ); 135 | 136 | afterEach( function () { 137 | return cursor.close(); 138 | } ); 139 | 140 | after( function () { 141 | return stmt.free(); 142 | } ); 143 | 144 | 145 | it( 'should be able to get the serial ID', function () { 146 | expect( cursor.serial() ).to.be.above( 0 ); 147 | } ); 148 | 149 | it( 'should fail to fetch results', function () { 150 | return cursor.fetch() 151 | .then( function ( result ) { 152 | throw new Error( 'Expected to fail, but it did not!!!' ); 153 | } ) 154 | .catch( function ( err ) { 155 | expect( err ).to.be.an.instanceof( Error ); 156 | expect( err.message ).to.be.string( '[-400] Fetch attempted on unopen cursor.' ); 157 | } ); 158 | } ); 159 | 160 | it( 'should fail to fetch all results', function () { 161 | return cursor.fetchAll() 162 | .then( function ( results ) { 163 | throw new Error( 'Expected to fail, but it did not!!!' ); 164 | } ) 165 | .catch( function ( err ) { 166 | expect( err ).to.be.an.instanceof( Error ); 167 | expect( err.message ).to.be.string( '[-400] Fetch attempted on unopen cursor.' ); 168 | } ); 169 | } ); 170 | 171 | } ); 172 | 173 | } ); 174 | 175 | -------------------------------------------------------------------------------- /test/lib.informix.test.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | var expect = require( 'chai' ).expect; 5 | var sinon = require( 'sinon' ); 6 | 7 | var Informix = require( '../lib/informix' ); 8 | var Statement = require( '../lib/statement' ); 9 | var Cursor = require( '../lib/cursor' ); 10 | var Context = require( '../lib/context' ); 11 | var Pool = require( '../lib/pool' ); 12 | 13 | 14 | describe( 'lib/Informix', function () { 15 | 16 | var opts = { 17 | database : 'test@ol_informix1210', 18 | username : 'informix', 19 | password : 'informix' 20 | }; 21 | 22 | 23 | it( 'should set connection pool options', function () { 24 | var opts = { 25 | database : 'dummy@ol_informix1210', 26 | pool : { 27 | max : 2, 28 | min : 1 29 | } 30 | }; 31 | 32 | var informix = new Informix( opts ); 33 | expect( informix.$.pool.$.min ).to.eql( opts.pool.min ); 34 | expect( informix.$.pool.$.max ).to.eql( opts.pool.max ); 35 | } ); 36 | 37 | it( 'should be able to run a query', function () { 38 | var informix = new Informix( opts ); 39 | return informix.query( 'select first 1 * from tcustomers;' ) 40 | .then( function ( cursor ) { 41 | expect( cursor ).to.be.an.instanceof( Cursor ); 42 | return cursor.close(); 43 | } ); 44 | } ); 45 | 46 | it( 'should be able to prepare a statement', function () { 47 | var informix = new Informix( opts ); 48 | return informix.prepare( 'select count(*) from tcustomers where id > ?;' ) 49 | .then( function ( stmt ) { 50 | expect( stmt ).to.be.an.instanceof( Statement ); 51 | return stmt.free(); 52 | } ); 53 | } ); 54 | 55 | it( 'should be possible to prepare a named statement', function () { 56 | var informix = new Informix( opts ); 57 | var stmtname = 'stmt_select'; 58 | return informix.prepare( 'select count(*) from tcustomers where id > ?;', stmtname ) 59 | .then( function ( stmt ) { 60 | expect( stmt.$.id ).to.eql( stmtname ); 61 | return stmt.free(); 62 | } ); 63 | } ); 64 | 65 | it( 'should be possible to create a new context', function () { 66 | var informix = new Informix( opts ); 67 | var ctx = informix.createContext(); 68 | expect( ctx ).to.be.an.instanceof( Context ); 69 | ctx.end(); 70 | } ); 71 | 72 | 73 | context( 'when acquiring a connection from the pool fails', function () { 74 | 75 | it( 'should emit an error object when running a query', function ( done ) { 76 | var informix = new Informix( opts ); 77 | sinon.stub( informix.$.pool, 'acquire', function () { 78 | return Promise.reject( new Error( '[stub] Failed to acquire.' ) ); 79 | } ); 80 | 81 | informix.on( 'error', function ( err ) { 82 | try { 83 | expect( err ).to.be.an.instanceof( Error ); 84 | expect( err.message ).to.be.string( '[stub] Failed to acquire.' ); 85 | } catch ( e ) { 86 | return done( e ); 87 | } 88 | 89 | done(); 90 | } ); 91 | 92 | informix.query( 'select first 1 * from tcustomers;' ) 93 | .then( function ( c ) { 94 | done( new Error( 'Expected to fail, but it did not!!!' ) ); 95 | } ) 96 | .catch( function ( err ) { 97 | // suppress node.js unhandled promise rejection warnings (UnhandledPromiseRejectionWarning, 98 | // [DEP0018] DeprecationWarning) 99 | } ); 100 | } ); 101 | 102 | it( 'should emit an error object when preparing a statement', function ( done ) { 103 | var informix = new Informix( opts ); 104 | sinon.stub( informix.$.pool, 'acquire', function () { 105 | return Promise.reject( new Error( '[stub] Failed to acquire.' ) ); 106 | } ); 107 | 108 | informix.on( 'error', function ( err ) { 109 | try { 110 | expect( err ).to.be.an.instanceof( Error ); 111 | expect( err.message ).to.be.string( '[stub] Failed to acquire.' ); 112 | } catch ( e ) { 113 | return done( e ); 114 | } 115 | 116 | done(); 117 | } ); 118 | 119 | informix.prepare( 'select count(*) from tcustomers where id > ?;' ) 120 | .then( function ( c ) { 121 | done( new Error( 'Expected to fail, but it did not!!!' ) ); 122 | } ) 123 | .catch( function ( err ) { 124 | // suppress node.js unhandled promise rejection warnings (UnhandledPromiseRejectionWarning, 125 | // [DEP0018] DeprecationWarning) 126 | } ); 127 | } ); 128 | 129 | } ); 130 | 131 | } ); 132 | 133 | -------------------------------------------------------------------------------- /test/lib.pool.test.js: -------------------------------------------------------------------------------- 1 | /* jshint expr: true */ 2 | 3 | 'use strict'; 4 | 5 | var expect = require( 'chai' ).expect; 6 | var Ifx = require( 'bindings' )( 'ifx' ).Ifx; 7 | 8 | var Connection = require( '../lib/connection' ); 9 | var Pool = require( '../lib/pool' ); 10 | 11 | 12 | describe( 'lib/Pool', function () { 13 | 14 | var pool = {}; 15 | before( function () { 16 | pool = new Pool( { 17 | max : 3, 18 | database : 'test@ol_informix1210', 19 | username : 'informix', 20 | password : 'informix' 21 | } ); 22 | } ); 23 | 24 | 25 | 26 | context( 'when acquiring a connection', function () { 27 | 28 | it( 'should acquire a connection successfully', function () { 29 | return pool.acquire() 30 | .then( function ( conn ) { 31 | expect( conn ).to.be.an.instanceof( Connection ); 32 | return pool.release( conn ); 33 | } ); 34 | } ); 35 | 36 | it( 'should reserve a connection for a context automatically', function () { 37 | var context = 'ctx:id:00001'; 38 | return pool.acquire( context ) 39 | .then( function ( conn ) { 40 | expect( pool.$.contexts[ context ].resolvers ).to.have.length( 1 ); 41 | pool.release( context ); 42 | pool.close( context ); 43 | } ); 44 | } ); 45 | 46 | } ); 47 | 48 | 49 | context( 'when a connection is acquired', function () { 50 | 51 | var conn; 52 | before( function () { 53 | return pool.acquire() 54 | .then( function ( c ) { 55 | expect( pool.$.resolvers[ c.index() ] ).to.have.length( 1 ); 56 | conn = c; 57 | } ); 58 | } ); 59 | 60 | after( function () { 61 | return pool.release( conn ); 62 | } ); 63 | 64 | 65 | it( 'should wait when requesting the same connection', function () { 66 | var promise = pool.acquire( conn ); 67 | 68 | expect( pool.$.resolvers[ conn.index() ] ).to.have.length( 2 ); 69 | pool.release( conn ); 70 | return promise 71 | .then( function () { 72 | expect( pool.$.resolvers[ conn.index() ] ).to.have.length( 1 ); 73 | } ); 74 | } ); 75 | 76 | } ); 77 | 78 | 79 | context( 'when reserving a connection to be used within a context', function () { 80 | 81 | it( 'should reserve a connection successfully', function () { 82 | var context = 'ctx:id:10001'; 83 | pool.reserve( context ); 84 | expect( pool.$.contexts[ context ].conn ).to.be.an.instanceof( Connection ); 85 | expect( pool.$.contexts[ context ].resolvers ).to.have.length( 0 ); 86 | pool.close( context ); 87 | } ); 88 | 89 | it( 'should not reserve connections for open contexts', function () { 90 | var context = 'ctx:id:10002'; 91 | var exception = false; 92 | pool.reserve( context ); 93 | 94 | try { 95 | pool.reserve( context ); 96 | } catch ( e ) { 97 | expect( e.message ).to.be.string( 'A connection is already reserved for the given context.' ); 98 | exception = true; 99 | } 100 | 101 | expect( exception ).to.be.true; 102 | pool.close( context ); 103 | } ); 104 | 105 | } ); 106 | 107 | 108 | context( 'when a connection is reserved for a context', function () { 109 | 110 | var context = 'ctx:id:20001'; 111 | before( function () { 112 | pool.reserve( context ); 113 | } ); 114 | 115 | after( function () { 116 | pool.close( context ); 117 | } ); 118 | 119 | 120 | it( 'should be possible to aquire the connection for the same context', function () { 121 | var ctx = pool.$.contexts[ context ]; 122 | return pool.acquire( context ) 123 | .then( function ( conn ) { 124 | expect( ctx.resolvers ).to.have.length( 1 ); 125 | pool.release( context ); 126 | } ); 127 | } ); 128 | 129 | it( 'should wait until the context is closed when the connection is acquired outside the context', function () { 130 | var conn = pool.$.contexts[ context ].conn; 131 | var promise = pool.acquire( conn ); 132 | 133 | expect( pool.$.resolvers[ conn.index() ] ).to.have.length( 1 ); 134 | pool.close( context ); 135 | 136 | return promise.then( function ( conn ) { 137 | pool.release( conn ); 138 | expect( pool.$.resolvers[ conn.index() ] ).to.have.length( 0 ); 139 | 140 | pool.reserve( context ); 141 | } ); 142 | } ); 143 | 144 | } ); 145 | 146 | 147 | context( 'when the connection pool is busy', function () { 148 | 149 | var conns = []; 150 | before( function () { 151 | var promises = []; 152 | while ( promises.push( pool.acquire() ) < 3 ); 153 | 154 | return Promise.all( promises ) 155 | .then( function ( result ) { 156 | conns = result; 157 | 158 | conns.forEach( function ( conn ) { 159 | expect( pool.$.resolvers[ conn.index() ] ).to.have.length( 1 ); 160 | } ); 161 | } ); 162 | } ); 163 | 164 | after( function () { 165 | conns.forEach( function ( conn ) { 166 | pool.release( conn ); 167 | expect( pool.$.resolvers[ conn.index() ] ).to.have.length( 0 ); 168 | } ); 169 | } ); 170 | 171 | 172 | it( 'should wait for existing promises when a connection is requested', function () { 173 | var promise = pool.acquire(); 174 | expect( pool.$.resolvers[ 0 ] ).to.have.length( 2 ); 175 | 176 | pool.release( conns[0] ); 177 | return promise 178 | .then( function ( conn ) { 179 | expect( pool.$.resolvers[ 0 ] ).to.have.length( 1 ); 180 | } ); 181 | } ); 182 | 183 | it( 'should wait for promises in a round robin manner', function () { 184 | pool.$.next = 2; 185 | var promise = pool.acquire(); 186 | expect( pool.$.resolvers[ 2 ] ).to.have.length( 2 ); 187 | 188 | pool.release( conns[2] ); 189 | return promise 190 | .then( function ( conn ) { 191 | expect( pool.$.resolvers[ 2 ] ).to.have.length( 1 ); 192 | expect( pool.$.next ).to.eql( 0 ); 193 | } ); 194 | } ); 195 | 196 | } ); 197 | 198 | } ); 199 | 200 | -------------------------------------------------------------------------------- /test/lib.statement.test.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | var expect = require( 'chai' ).expect; 5 | 6 | var Statement = require( '../lib/statement' ); 7 | var Cursor = require( '../lib/cursor' ); 8 | var Pool = require( '../lib/pool' ); 9 | 10 | 11 | describe( 'lib/Statement', function () { 12 | 13 | var conn = {}; 14 | 15 | before( function () { 16 | var pool = new Pool( { 17 | max : 1, 18 | database : 'test@ol_informix1210', 19 | username : 'informix', 20 | password : 'informix' 21 | } ); 22 | 23 | return pool.acquire() 24 | .then( function ( c ) { 25 | conn = c; 26 | pool.release( c ); 27 | } ); 28 | } ); 29 | 30 | 31 | it( 'should be able to prepare a statement', function () { 32 | var sql = 'select tabname from systables where tabname like ?;'; 33 | var stmt = new Statement( conn ); 34 | return stmt.prepare( sql ) 35 | .then( function ( stmt ) { 36 | expect( stmt ).to.be.an.instanceof( Statement ); 37 | return stmt.free(); 38 | } ); 39 | } ); 40 | 41 | 42 | context( 'constructor options', function () { 43 | 44 | context( 'when reusable=false', function () { 45 | 46 | var stmt = {}; 47 | before( function () { 48 | var sql = 'select count(*) from tcustomers'; 49 | stmt = new Statement( conn, { reusable : false } ); 50 | return stmt.prepare( sql ); 51 | } ); 52 | 53 | 54 | it( 'should free the statment automatically after closing the cursor', function () { 55 | return stmt.exec() 56 | .then( function ( cursor ) { 57 | return cursor.close(); 58 | } ) 59 | .then( function ( curid ) { 60 | return stmt.free(); 61 | } ) 62 | .then( function () { 63 | throw new Error( 'Expected the statement to fail, but it did not!!!' ); 64 | } ) 65 | .catch( function ( err ) { 66 | expect( err ).to.be.an.instanceof( Error ); 67 | expect( err.message ).to.be.string( 'Invalid statement ID.' ); 68 | } ); 69 | } ); 70 | 71 | } ); 72 | 73 | 74 | context( 'when reusable=true', function () { 75 | 76 | var stmt = {}; 77 | before( function () { 78 | var sql = 'select count(*) from tcustomers'; 79 | stmt = new Statement( conn, { reusable : true } ); 80 | return stmt.prepare( sql ); 81 | } ); 82 | 83 | 84 | it( 'should not free the statment automatically after closing the cursor', function () { 85 | return stmt.exec() 86 | .then( function ( cursor ) { 87 | return cursor.close(); 88 | } ) 89 | .then( function ( curid ) { 90 | return stmt.free(); 91 | } ); 92 | } ); 93 | 94 | } ); 95 | 96 | } ); 97 | 98 | 99 | context( 'when preparing a statement', function () { 100 | 101 | it( 'should reject the promise on syntax errors', function () { 102 | var stmt = new Statement( conn ); 103 | return stmt.prepare( 'select something;' ) 104 | .then( function ( s ) { 105 | throw new Error( 'Expected the statement to fail, but it did not!!!' ); 106 | } ) 107 | .catch( function ( err ) { 108 | expect( err ).to.be.an.instanceof( Error ); 109 | expect( err.message ).to.be.string( '[-201] A syntax error has occurred.' ); 110 | } ); 111 | } ); 112 | 113 | } ); 114 | 115 | 116 | context( 'when a statement is prepared which has input parameters', function () { 117 | 118 | var stmt = {}; 119 | before( function () { 120 | var sql = 'select * from tcustomers where id < ?;'; 121 | stmt = new Statement( conn ); 122 | return stmt.prepare( sql ); 123 | } ); 124 | 125 | after( function () { 126 | return stmt.free(); 127 | } ); 128 | 129 | 130 | context( 'when executing the statment', function () { 131 | it( 'should be able to execute the statement successfully', function () { 132 | return stmt.exec( 2 ) 133 | .then( function ( cursor ) { 134 | expect( cursor ).to.be.an.instanceof( Cursor ); 135 | return cursor.close(); 136 | } ); 137 | } ); 138 | 139 | it( 'should reject the promise if no arguments are passed in', function () { 140 | return stmt.exec() 141 | .then( function ( c ) { 142 | throw new Error( 'Expected to fail, but it did not!!!' ); 143 | } ) 144 | .catch( function ( err ) { 145 | expect( err ).to.be.an.instanceof( Error ); 146 | expect( err.message ).to.be.string( 'This statment requires input arguments.' ); 147 | } ); 148 | } ); 149 | 150 | it( 'should reject the promise if incorrect number of arguments are passed in', function () { 151 | return stmt.exec( [ 1, 2, 3 ] ) 152 | .then( function ( c ) { 153 | throw new Error( 'Expected to fail, but it did not!!!' ); 154 | } ) 155 | .catch( function ( err ) { 156 | expect( err ).to.be.an.instanceof( Error ); 157 | expect( err.message ).to.be.string( 'Too many or too few host variables given.' ); 158 | } ); 159 | } ); 160 | 161 | it( 'should be possible to execute the statement with cursor options', function () { 162 | var curname = 'cursor_select'; 163 | return stmt.exec( 0, { id : curname } ) 164 | .then( function ( cursor ) { 165 | expect( cursor.$.id ).to.eql( curname ); 166 | return cursor.close(); 167 | } ); 168 | } ); 169 | } ); 170 | 171 | } ); 172 | 173 | 174 | context( 'when a statement is prepared which does not have any input parameters', function () { 175 | 176 | var stmt = {}; 177 | before( function () { 178 | var sql = 'select * from tcustomers where id < 3;'; 179 | stmt = new Statement( conn ); 180 | return stmt.prepare( sql ); 181 | } ); 182 | 183 | after( function () { 184 | return stmt.free(); 185 | } ); 186 | 187 | 188 | context( 'when executing the statment', function () { 189 | it( 'should be able to execute the statement successfully', function () { 190 | return stmt.exec() 191 | .then( function ( cursor ) { 192 | expect( cursor ).to.be.an.instanceof( Cursor ); 193 | return cursor.close(); 194 | } ); 195 | } ); 196 | 197 | it( 'should reject the promise if argumenst are passed in', function () { 198 | return stmt.exec( 3 ) 199 | .then( function ( c ) { 200 | throw new Error( 'Expected to fail, but it did not!!!' ); 201 | } ) 202 | .catch( function ( err ) { 203 | expect( err ).to.be.an.instanceof( Error ); 204 | expect( err.message ).to.be.string( 'This statment does not expect any input arguments.' ); 205 | } ); 206 | } ); 207 | 208 | it( 'should be possible to execute the statement with cursor options', function () { 209 | var curname = 'stmt_select'; 210 | return stmt.exec( { id : curname } ) 211 | .then( function ( cursor ) { 212 | expect( cursor.$.id ).to.eql( curname ); 213 | return cursor.close(); 214 | } ); 215 | } ); 216 | } ); 217 | 218 | } ); 219 | 220 | } ); 221 | 222 | -------------------------------------------------------------------------------- /test/support/setup-db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | 4 | sql=$1 5 | status=-1 6 | attempt=0 7 | 8 | while [[ $status -ne 0 ]] && [[ $attempt -le 5 ]] ; do 9 | echo "attempt [${attempt}] using $sql" 10 | let attempt+=1 11 | 12 | INFORMIXSERVER=ol_informix1210 dbaccess - $sql 13 | status=$? 14 | 15 | echo "dbaccess returned [${status}]" 16 | if [[ $status -ne 0 ]] && [[ $attempt -le 5 ]] ; then 17 | echo "sleeping..." 18 | sleep 5 19 | fi 20 | done 21 | 22 | echo "attempts: ${attempt}" 23 | echo "status: ${status}" 24 | 25 | exit $status 26 | 27 | -------------------------------------------------------------------------------- /test/support/test-db.sql: -------------------------------------------------------------------------------- 1 | 2 | connect to '@ol_informix1210' user 'informix' using 'informix'; 3 | 4 | drop database if exists test; 5 | create database test with buffered log; 6 | 7 | 8 | create table tcustomers ( 9 | id serial, 10 | fname varchar(255), 11 | lname varchar(255) 12 | ); 13 | 14 | create unique index icustomers_x1 on tcustomers(id); 15 | 16 | alter table tcustomers add constraint primary key (id) 17 | constraint ccustomers_pk; 18 | 19 | 20 | create procedure pinscustomer( 21 | p_fname like tcustomers.fname, 22 | p_lname like tcustomers.lname 23 | ) 24 | returning int; 25 | 26 | define v_id like tcustomers.id; 27 | 28 | insert into tcustomers( 29 | fname, 30 | lname 31 | ) values ( 32 | p_fname, 33 | p_lname 34 | ); 35 | 36 | let v_id = DBINFO('sqlca.sqlerrd1'); 37 | return v_id; 38 | 39 | end procedure; 40 | 41 | 42 | create procedure ppurgecustomers() 43 | delete from tcustomers; 44 | end procedure; 45 | 46 | 47 | 48 | create table tdatatypes ( 49 | id serial8, 50 | dt datetime year to fraction, 51 | date date, 52 | decimal decimal, 53 | bigint bigint, 54 | atext text 55 | ); 56 | 57 | -------------------------------------------------------------------------------- /wercker.yml: -------------------------------------------------------------------------------- 1 | 2 | box: nukedzn/nfx 3 | 4 | build: 5 | services: 6 | - id: nukedzn/informix 7 | tag: 12.10 8 | 9 | steps: 10 | - script: 11 | name: setup sqlhosts 12 | code: | 13 | echo "ol_informix1210 onsoctcp ${INFORMIX_PORT_9088_TCP_ADDR} 9088" > ${INFORMIXDIR}/etc/sqlhosts 14 | 15 | - script: 16 | name: echo versions 17 | code: | 18 | echo "node: $(node --version)" 19 | echo "npm: v$(npm --version)" 20 | 21 | - script: 22 | name: apt-get install 23 | code: | 24 | apt-get update 25 | apt-get install -y --no-install-recommends git 26 | 27 | - script: 28 | name: npm install 29 | code: | 30 | npm install -g jshint codecov coveralls istanbul mocha 31 | npm install --unsafe-perm 32 | 33 | - script: 34 | name: lint 35 | code: | 36 | npm run lint 37 | 38 | - script: 39 | name: setup db 40 | code: | 41 | /bin/bash ${WERCKER_SOURCE_DIR}/test/support/setup-db.sh ${WERCKER_SOURCE_DIR}/test/support/test-db.sql 42 | 43 | - script: 44 | name: npm test 45 | code: | 46 | INFORMIXSERVER=ol_informix1210 npm test 47 | 48 | - script: 49 | name: coveralls.io 50 | code: | 51 | INFORMIXSERVER=ol_informix1210 npm run coveralls 52 | 53 | - script: 54 | name: codecov.io 55 | code: | 56 | INFORMIXSERVER=ol_informix1210 npm run codecov 57 | 58 | --------------------------------------------------------------------------------