├── index.js ├── example ├── admin │ ├── sqlnet.ora │ ├── tnsnames.ora │ ├── ldap.ora │ ├── demo.ldif │ └── ldap-server.js ├── stored-procedure.js ├── config.js ├── app.js └── reader.js ├── .gitignore ├── tests-settings.js ├── src ├── nodeOracleException.h ├── reader.h ├── statement.h ├── readerBaton.h ├── statementBaton.h ├── outParam.h ├── outParam.cpp ├── statement.cpp ├── executeBaton.h ├── oracle_bindings.h ├── reader.cpp ├── utils.h ├── executeBaton.cpp ├── connection.h ├── oracle_bindings.cpp └── connection.cpp ├── oracle.supp ├── package.json ├── NOTICE ├── pretest.js ├── test ├── prepare.js ├── test.sql ├── reader.js ├── outparams.js └── integration.js ├── binding.gyp ├── lib └── oracle.js ├── CONTRIBUTING.md ├── README.md ├── LICENSE.md └── CHANGES.md /index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require('./lib/oracle'); 3 | -------------------------------------------------------------------------------- /example/admin/sqlnet.ora: -------------------------------------------------------------------------------- 1 | NAMES.DIRECTORY_PATH=(LDAP,TNSNAMES,EZCONNECT) 2 | 3 | -------------------------------------------------------------------------------- /example/admin/tnsnames.ora: -------------------------------------------------------------------------------- 1 | demo1=(DESCRIPTION=(CONNECT_DATA=(SERVICE_NAME=))(ADDRESS=(PROTOCOL=TCP)(HOST=demo.strongloop.com)(PORT=1521))) 2 | -------------------------------------------------------------------------------- /example/admin/ldap.ora: -------------------------------------------------------------------------------- 1 | DIRECTORY_SERVERS=(localhost:1389) 2 | DEFAULT_ADMIN_CONTEXT="dc=strongloop,dc=com" 3 | DIRECTORY_SERVER_TYPE=OID 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/* 2 | node_modules 3 | *.node 4 | *.sh 5 | *.swp 6 | .lock* 7 | .idea 8 | .project 9 | .cproject 10 | .settings 11 | tests-settings.json.bak 12 | -------------------------------------------------------------------------------- /tests-settings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | hostname: process.env.ORACLE_HOST || 'localhost', 3 | port: process.env.ORACLE_PORT || undefined, 4 | database: process.env.ORACLE_DB || undefined, 5 | username: process.env.ORACLE_USER || 'test', 6 | password: process.env.ORACLE_PASSWORD || 'test', 7 | }; 8 | -------------------------------------------------------------------------------- /src/nodeOracleException.h: -------------------------------------------------------------------------------- 1 | #ifndef _nodeOracleException_h_ 2 | #define _nodeOracleException_h_ 3 | 4 | #include 5 | 6 | class NodeOracleException { 7 | public: 8 | NodeOracleException(std::string message) : 9 | m_message(message) { 10 | } 11 | std::string getMessage() { 12 | return m_message; 13 | } 14 | 15 | private: 16 | std::string m_message; 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /oracle.supp: -------------------------------------------------------------------------------- 1 | # For shame, Oracle, for shame... 2 | { 3 | oracle 4 | Memcheck:Cond 5 | obj:*/libclntsh.so.12.1 6 | } 7 | { 8 | oracle 9 | Memcheck:Addr16 10 | obj:*/libclntsh.so.12.1 11 | } 12 | { 13 | oracle 14 | Memcheck:Addr16 15 | obj:*/libclntshcore.so.12.1 16 | } 17 | { 18 | oracle 19 | Memcheck:Cond 20 | obj:*/libnnz12.so 21 | } 22 | { 23 | oracle 24 | Memcheck:Value8 25 | obj:*/libnnz12.so 26 | } 27 | -------------------------------------------------------------------------------- /example/admin/demo.ldif: -------------------------------------------------------------------------------- 1 | dn: dc=com 2 | 3 | dn: dc=strongloop,dc=com 4 | 5 | # OracleContext 6 | dn: cn=OracleContext,dc=strongloop,dc=com 7 | objectClass: orclContext 8 | cn: OracleContext 9 | 10 | # demo, OracleContext 11 | dn: cn=demo,cn=OracleContext,dc=strongloop,dc=com 12 | objectClass: top 13 | objectClass: orclService 14 | cn: demo 15 | orclNetDescString: (DESCRIPTION=(CONNECT_DATA=(SERVICE_NAME=))(ADDRESS=(PROTOCOL=TCP)(HOST=demo.strongloop.com)(PORT=1521))) 16 | orclSid: XE 17 | orclServiceType: DB 18 | -------------------------------------------------------------------------------- /example/stored-procedure.js: -------------------------------------------------------------------------------- 1 | var settings = require('./config'); 2 | 3 | var ora = require('../lib/oracle')(settings); 4 | 5 | var sql = 'call GETSUM(:1,:2,:3)'; 6 | 7 | ora.createConnectionPool(settings, function(err, pool) { 8 | if (err) { 9 | console.error(err); 10 | return; 11 | } 12 | pool.getConnection(function(err, conn) { 13 | console.log(sql); 14 | conn.execute(sql, [1, 2, new ora.OutParam(ora.OCCIINT)], 15 | function(err, result) { 16 | console.log(err, result); 17 | conn.close(); 18 | pool.close(); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /example/config.js: -------------------------------------------------------------------------------- 1 | // Configure the TNS_ADMIN home directory 2 | var path = require('path'); 3 | process.env.TNS_ADMIN = path.join(__dirname, 'admin'); 4 | 5 | module.exports = { 6 | minConn: 1, 7 | maxConn: 10, 8 | incrConn: 1, 9 | timeout: 10, 10 | host: 'oracle-demo.strongloop.com', 11 | database: 'XE', 12 | user: 'demo', 13 | password: 'L00pBack', 14 | // tns: 'demo', // The tns can be //host:port/database, an tns name or ldap name 15 | /* 16 | ldap: { 17 | adminContext: 'dc=strongloop,dc=com', 18 | host:'localhost', 19 | port: 1389, 20 | user:'cn=root', 21 | password:'secret' 22 | }*/ 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /example/app.js: -------------------------------------------------------------------------------- 1 | var settings = require('./config'); 2 | 3 | var ora = require('../lib/oracle')(settings); 4 | 5 | var sql = 'SELECT * FROM PRODUCT'; 6 | 7 | ora.createConnectionPool(settings, function(err, pool) { 8 | if(err) { 9 | console.error(err); 10 | return; 11 | } 12 | console.log(pool.getInfo()); 13 | pool.getConnection(function(err, conn) { 14 | conn.setAutoCommit(false); 15 | console.log(pool.getInfo()); 16 | console.log(sql); 17 | console.log(conn.executeSync(sql, [])); 18 | conn.commit(function(err, result) { 19 | console.log(err, result); 20 | conn.close(); 21 | console.log(pool.getInfo()); 22 | pool.close(); 23 | console.log(pool.getInfo()); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/reader.h: -------------------------------------------------------------------------------- 1 | #ifndef _reader_h_ 2 | #define _reader_h_ 3 | 4 | #include 5 | #include 6 | #include "nan.h" 7 | #ifndef WIN32 8 | #include 9 | #endif 10 | #include 11 | #include "readerBaton.h" 12 | 13 | using namespace node; 14 | using namespace v8; 15 | 16 | class Reader: public Nan::ObjectWrap { 17 | public: 18 | static Nan::Persistent s_ct; 19 | static void Init(Handle target); 20 | static NAN_METHOD(New); 21 | static NAN_METHOD(NextRows); 22 | static void EIO_NextRows(uv_work_t* req); 23 | static void EIO_AfterNextRows(uv_work_t* req, int status); 24 | 25 | Reader(); 26 | ~Reader(); 27 | 28 | void setBaton(ReaderBaton* baton); 29 | 30 | private: 31 | ReaderBaton* m_baton; 32 | }; 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/statement.h: -------------------------------------------------------------------------------- 1 | #ifndef _statement_h_ 2 | #define _statement_h_ 3 | 4 | #include 5 | #include 6 | #include "nan.h" 7 | #ifndef WIN32 8 | #include 9 | #endif 10 | #include 11 | #include "statementBaton.h" 12 | 13 | using namespace node; 14 | using namespace v8; 15 | 16 | class Statement: public Nan::ObjectWrap { 17 | public: 18 | static Nan::Persistent s_ct; 19 | static void Init(Handle target); 20 | static NAN_METHOD(New); 21 | static NAN_METHOD(Execute); 22 | static void EIO_Execute(uv_work_t* req); 23 | static void EIO_AfterExecute(uv_work_t* req); 24 | 25 | Statement(); 26 | ~Statement(); 27 | 28 | void setBaton(StatementBaton* baton); 29 | 30 | private: 31 | StatementBaton* m_baton; 32 | }; 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /example/reader.js: -------------------------------------------------------------------------------- 1 | var settings = require('./config'); 2 | 3 | var ora = require('../lib/oracle')(settings); 4 | 5 | var sql = 'SELECT * FROM PRODUCT'; 6 | 7 | ora.createConnectionPool(settings, function(err, pool) { 8 | if (err) { 9 | console.error(err); 10 | return; 11 | } 12 | console.log(pool.getInfo()); 13 | pool.getConnection(function(err, conn) { 14 | conn.setPrefetchRowCount(10); 15 | conn.setAutoCommit(false); 16 | var reader = conn.reader(sql, []); 17 | var count = 0; 18 | var doRead = function() { 19 | reader.nextRow(function(err, row) { 20 | if (err) { 21 | throw err; 22 | } 23 | if (row) { 24 | console.log('%d %j', ++count, row); 25 | doRead(); 26 | } else { 27 | console.log('Done'); 28 | } 29 | }); 30 | }; 31 | doRead(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "strong-oracle", 3 | "description": "Oracle database driver", 4 | "keywords": [ 5 | "database", 6 | "db", 7 | "oracle" 8 | ], 9 | "homepage": "https://github.com/strongloop/strong-oracle", 10 | "version": "1.9.0", 11 | "engines": { 12 | "node": ">=0.10.0" 13 | }, 14 | "maintainers": [ 15 | { 16 | "name": "Raymond Feng", 17 | "email": "raymond@strongloop.com" 18 | } 19 | ], 20 | "bugs": { 21 | "url": "https://github.com/strongloop/strong-oracle/issues" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git://github.com/strongloop/strong-oracle.git" 26 | }, 27 | "dependencies": { 28 | "nan": "^2.0.9" 29 | }, 30 | "devDependencies": { 31 | "async": "^1.4.0", 32 | "mocha": "~2.2.5" 33 | }, 34 | "scripts": { 35 | "pretest": "node pretest.js", 36 | "test": "./node_modules/.bin/mocha -R spec --timeout 10000 test/*.js" 37 | }, 38 | "main": "./index.js", 39 | "license": "Artistic-2.0" 40 | } 41 | -------------------------------------------------------------------------------- /src/readerBaton.h: -------------------------------------------------------------------------------- 1 | #ifndef _reader_baton_h_ 2 | #define _reader_baton_h_ 3 | 4 | #include "connection.h" 5 | #include "statementBaton.h" 6 | 7 | class ReaderBaton: public StatementBaton { 8 | public: 9 | ReaderBaton(oracle::occi::Environment* m_environment, 10 | oracle::occi::StatelessConnectionPool* m_connectionPool, 11 | oracle::occi::Connection* m_connection, 12 | bool m_autoCommit, 13 | int m_prefetchRowCount, 14 | const char* sql, 15 | v8::Local values) : 16 | StatementBaton(m_environment, m_connectionPool, m_connection, 17 | m_autoCommit, m_prefetchRowCount, 18 | sql, values) { 19 | stmt = NULL; 20 | rs = NULL; 21 | done = false; 22 | busy = false; 23 | count = 0; 24 | } 25 | ~ReaderBaton() { 26 | ResetStatement(); 27 | } 28 | 29 | void ResetStatement() { 30 | if (stmt && rs) { 31 | stmt->closeResultSet(rs); 32 | rs = NULL; 33 | } 34 | StatementBaton::ResetStatement(); 35 | } 36 | 37 | oracle::occi::ResultSet* rs; 38 | int count; 39 | }; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/statementBaton.h: -------------------------------------------------------------------------------- 1 | #ifndef _statement_baton_h_ 2 | #define _statement_baton_h_ 3 | 4 | #include "connection.h" 5 | #include "executeBaton.h" 6 | 7 | class StatementBaton: public ExecuteBaton { 8 | public: 9 | StatementBaton(oracle::occi::Environment* m_environment, 10 | oracle::occi::StatelessConnectionPool* m_connectionPool, 11 | oracle::occi::Connection* m_connection, 12 | bool m_autoCommit, 13 | int m_prefetchRowCount, 14 | const char* sql, 15 | v8::Local values = v8::Local()) 16 | : ExecuteBaton(m_environment, m_connectionPool, m_connection, 17 | m_autoCommit, m_prefetchRowCount, 18 | sql, values, Local()) { 19 | stmt = NULL; 20 | done = false; 21 | busy = false; 22 | } 23 | 24 | ~StatementBaton() { 25 | ResetStatement(); 26 | } 27 | 28 | void ResetStatement() { 29 | if (stmt) { 30 | if (m_connection) { 31 | m_connection->terminateStatement(stmt); 32 | } 33 | stmt = NULL; 34 | } 35 | } 36 | 37 | oracle::occi::Statement* stmt; 38 | bool done; 39 | bool busy; 40 | }; 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This product includes software developed at https://github.com/joeferner/node-oracle 2 | under the following MIT license. 3 | 4 | The MIT License (MIT) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /pretest.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | var fs = require('fs'); 3 | var oracle = require('./')(settings); 4 | var path = require('path'); 5 | var settings = require('./tests-settings'); 6 | 7 | var raw = fs.readFileSync(path.join(__dirname, 'test', 'test.sql'), 'utf8'); 8 | var statements = []; 9 | var stmt = ''; 10 | // split test/test.sql file into statements, but strip any trailing ;s unless 11 | // they are at the end of an indented line, wich indicates a substatement. 12 | raw.split('\n').forEach(function(line) { 13 | // test/test.sql uses "/" as dividers between related statements, so ignore 14 | // them and any blank lines 15 | if (/\//.test(line) || /^\s+$/.test(line)) { 16 | return; 17 | } 18 | if (/^\S+.*;$/.test(line)) { 19 | if (!/^end;$/i.test(line)) { 20 | stmt += line.slice(0, -1); 21 | } else { 22 | stmt += line; 23 | } 24 | statements.push(stmt.trim()); 25 | stmt = ''; 26 | return; 27 | } 28 | stmt += line + '\n'; 29 | }); 30 | 31 | oracle.connect(settings, function(err, connection) { 32 | if (err) { 33 | console.error('Error connecting to %j:', settings, err); 34 | throw err; 35 | } 36 | async.eachSeries(statements, runStmt, shutdown); 37 | 38 | function runStmt(stmt, next) { 39 | connection.execute(stmt, [], function(err) { 40 | if (err) { 41 | // ignore the errors, but mention them. The SQL script doesn't make 42 | // consistent use of exception handling for things like dropping tables 43 | // that don't exist.. (yet?) 44 | console.error('%s => %s', stmt, err); 45 | } 46 | next(); 47 | }); 48 | } 49 | function shutdown(err, res) { 50 | connection.close(); 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /test/prepare.js: -------------------------------------------------------------------------------- 1 | var settings = require('../tests-settings'); 2 | var oracle = require("../")(settings); 3 | var assert = require('assert'); 4 | 5 | function initDb(connection, done) { 6 | connection.setPrefetchRowCount(50); 7 | connection.execute("DROP TABLE test_table", [], function (err) { 8 | // ignore error 9 | connection.execute("CREATE TABLE test_table (X INT)", [], function (err) { 10 | if (err) { 11 | return done(err); 12 | } 13 | return done(); 14 | }); 15 | }); 16 | } 17 | 18 | function doInsert(stmt, i, max, done) { 19 | if (i < max) { 20 | stmt.execute([i], function (err, result) { 21 | if (err) { 22 | return done(err); 23 | } 24 | if (result.updateCount !== 1) { 25 | return done(new Error("bad count: " + result.updateCount)); 26 | } 27 | doInsert(stmt, i + 1, max, done); 28 | }); 29 | } else { 30 | return done(); 31 | } 32 | } 33 | 34 | describe('prepare', function () { 35 | beforeEach(function (done) { 36 | var self = this; 37 | oracle.connect(settings, function (err, connection) { 38 | if (err) { 39 | return done(err); 40 | } 41 | self.connection = connection; 42 | initDb(self.connection, done); 43 | }); 44 | }); 45 | 46 | afterEach(function (done) { 47 | if (this.connection) { 48 | this.connection.close(); 49 | } 50 | done(); 51 | }); 52 | 53 | it("should support prepared insert", function (done) { 54 | var self = this; 55 | var stmt = self.connection.prepare("INSERT INTO test_table (X) values (:1)"); 56 | doInsert(stmt, 0, 100, function (err) { 57 | assert.equal(err, null); 58 | self.connection.execute("SELECT COUNT(*) from test_table", [], function (err, result) { 59 | assert.equal(err, null); 60 | assert(Array.isArray(result)); 61 | assert.equal(result.length, 1); 62 | assert.equal(result[0]['COUNT(*)'], 100); 63 | done(); 64 | }); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /src/outParam.h: -------------------------------------------------------------------------------- 1 | #ifndef _outparam_h_ 2 | #define _outparam_h_ 3 | 4 | #include 5 | #include 6 | #include "nan.h" 7 | #ifndef WIN32 8 | #include 9 | #endif 10 | #include "utils.h" 11 | #include 12 | 13 | using namespace node; 14 | using namespace v8; 15 | 16 | struct inout_t { 17 | bool hasInParam; 18 | const char* stringVal; 19 | int intVal; 20 | double doubleVal; 21 | float floatVal; 22 | oracle::occi::Date dateVal; 23 | oracle::occi::Timestamp timestampVal; 24 | oracle::occi::Number numberVal; 25 | }; 26 | 27 | struct outparam_t { 28 | int type; 29 | int size; 30 | inout_t inOut; 31 | }; 32 | 33 | /** 34 | * Oracle out parameter 35 | */ 36 | class OutParam: public Nan::ObjectWrap { 37 | public: 38 | static void Init(Handle target); 39 | static NAN_METHOD(New); 40 | static Nan::Persistent constructorTemplate; 41 | int _type; 42 | int _size; 43 | inout_t _inOut; 44 | OutParam(); 45 | ~OutParam(); 46 | 47 | int type(); 48 | int size(); 49 | 50 | /** 51 | * Create a copy of the outparam to C style struct 52 | */ 53 | outparam_t* c_outparam() { 54 | outparam_t* p = new outparam_t(); 55 | p->type = _type; 56 | p->size = _size; 57 | p->inOut.hasInParam = _inOut.hasInParam; 58 | p->inOut.stringVal = _inOut.stringVal; 59 | p->inOut.intVal = _inOut.intVal; 60 | p->inOut.doubleVal = _inOut.doubleVal; 61 | p->inOut.floatVal = _inOut.floatVal; 62 | p->inOut.dateVal = _inOut.dateVal; 63 | p->inOut.timestampVal = _inOut.timestampVal; 64 | p->inOut.numberVal = _inOut.numberVal; 65 | return p; 66 | } 67 | 68 | static const int OCCIINT = 0; 69 | static const int OCCISTRING = 1; 70 | static const int OCCIDOUBLE = 2; 71 | static const int OCCIFLOAT = 3; 72 | static const int OCCICURSOR = 4; 73 | static const int OCCICLOB = 5; 74 | static const int OCCIDATE = 6; 75 | static const int OCCITIMESTAMP = 7; 76 | static const int OCCINUMBER = 8; 77 | static const int OCCIBLOB = 9; 78 | 79 | }; 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /src/outParam.cpp: -------------------------------------------------------------------------------- 1 | #include "outParam.h" 2 | #include "nodeOracleException.h" 3 | 4 | using namespace std; 5 | 6 | Nan::Persistent OutParam::constructorTemplate; 7 | 8 | /** 9 | * The C++ class represents a JS constructor as follows: 10 | * 11 | * function OutParam(type, options) { 12 | * this._type = type || 0; 13 | * this._size = options.size; 14 | * this._inOut.hasInParam = options.in; 15 | * } 16 | */ 17 | void OutParam::Init(Handle target) { 18 | Nan::HandleScope scope; 19 | 20 | Local t = Nan::New(New); 21 | constructorTemplate.Reset( t); 22 | t->InstanceTemplate()->SetInternalFieldCount(1); 23 | t->SetClassName(Nan::New("OutParam").ToLocalChecked()); 24 | target->Set(Nan::New("OutParam").ToLocalChecked(), 25 | t->GetFunction()); 26 | } 27 | 28 | NAN_METHOD(OutParam::New) { 29 | Nan::HandleScope scope; 30 | OutParam *outParam = new OutParam(); 31 | outParam->Wrap(info.This()); 32 | 33 | if (info.Length() >= 1) { 34 | outParam->_type = 35 | info[0]->IsUndefined() ? OutParam::OCCIINT : info[0]->NumberValue(); 36 | } else { 37 | outParam->_type = OutParam::OCCIINT; 38 | } 39 | 40 | if (info.Length() >= 2 && !info[1]->IsUndefined()) { 41 | REQ_OBJECT_ARG(1, opts); 42 | OBJ_GET_NUMBER(opts, "size", outParam->_size, 200); 43 | 44 | // check if there's an 'in' param 45 | if (opts->Has(Nan::New("in").ToLocalChecked())) { 46 | outParam->_inOut.hasInParam = true; 47 | switch (outParam->_type) { 48 | case OutParam::OCCIINT: { 49 | OBJ_GET_NUMBER(opts, "in", outParam->_inOut.intVal, 0); 50 | break; 51 | } 52 | case OutParam::OCCIDOUBLE: { 53 | OBJ_GET_NUMBER(opts, "in", outParam->_inOut.doubleVal, 0); 54 | break; 55 | } 56 | case OutParam::OCCIFLOAT: { 57 | OBJ_GET_NUMBER(opts, "in", outParam->_inOut.floatVal, 0); 58 | break; 59 | } 60 | case OutParam::OCCINUMBER: { 61 | OBJ_GET_NUMBER(opts, "in", outParam->_inOut.numberVal, 0); 62 | break; 63 | } 64 | case OutParam::OCCISTRING: { 65 | OBJ_GET_STRING(opts, "in", outParam->_inOut.stringVal); 66 | break; 67 | } 68 | default: 69 | throw NodeOracleException("Unhandled OutParam type!"); 70 | } 71 | } 72 | } 73 | info.GetReturnValue().Set(info.This()); 74 | } 75 | 76 | OutParam::OutParam() { 77 | _type = OutParam::OCCIINT; 78 | _inOut.hasInParam = false; 79 | _size = 200; 80 | } 81 | 82 | OutParam::~OutParam() { 83 | } 84 | 85 | int OutParam::type() { 86 | return _type; 87 | } 88 | 89 | int OutParam::size() { 90 | return _size; 91 | } 92 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "conditions": [ 3 | ["OS=='mac'", { 4 | "variables": { "oci_version%": "11" }, 5 | }, { 6 | "variables": { "oci_version%": "12" }, 7 | }], 8 | ], 9 | "targets": [ 10 | { 11 | "target_name": "oracle_bindings", 12 | "sources": [ "src/connection.cpp", 13 | "src/oracle_bindings.cpp", 14 | "src/executeBaton.cpp", 15 | "src/outParam.cpp", 16 | "src/reader.cpp", 17 | "src/statement.cpp" ], 18 | "conditions": [ 19 | ["OS=='mac'", { 20 | "xcode_settings": { 21 | "GCC_ENABLE_CPP_EXCEPTIONS": "YES", 22 | "GCC_ENABLE_CPP_RTTI": "YES" 23 | }, 24 | "variables": { 25 | "oci_include_dir%": " 2 | #include "connection.h" 3 | #include "executeBaton.h" 4 | #include "statement.h" 5 | 6 | using namespace std; 7 | 8 | Nan::Persistent Statement::s_ct; 9 | 10 | void Statement::Init(Handle target) { 11 | Nan::HandleScope scope; 12 | 13 | Local t = Nan::New(New); 14 | Statement::s_ct.Reset( t); 15 | t->InstanceTemplate()->SetInternalFieldCount(1); 16 | t->SetClassName(Nan::New("Statement").ToLocalChecked()); 17 | 18 | Nan::SetPrototypeMethod(t, "execute", Execute); 19 | target->Set(Nan::New("Statement").ToLocalChecked(), t->GetFunction()); 20 | } 21 | 22 | Statement::Statement(): Nan::ObjectWrap() { 23 | m_baton = NULL; 24 | } 25 | 26 | Statement::~Statement() { 27 | delete m_baton; 28 | m_baton = NULL; 29 | } 30 | 31 | NAN_METHOD(Statement::New) { 32 | Nan::HandleScope scope; 33 | 34 | Statement* statement = new Statement(); 35 | statement->Wrap(info.This()); 36 | 37 | info.GetReturnValue().Set(info.This()); 38 | } 39 | 40 | void Statement::setBaton(StatementBaton* baton) { 41 | m_baton = baton; 42 | } 43 | 44 | NAN_METHOD(Statement::Execute) { 45 | Nan::HandleScope scope; 46 | Statement* statement = Nan::ObjectWrap::Unwrap(info.This()); 47 | StatementBaton* baton = statement->m_baton; 48 | 49 | REQ_ARRAY_ARG(0, values); 50 | REQ_FUN_ARG(1, callback); 51 | 52 | baton->callback = new Nan::Callback(callback); 53 | 54 | ExecuteBaton::CopyValuesToBaton(baton, values); 55 | if (baton->error) { 56 | Local message = Nan::New(baton->error->c_str()).ToLocalChecked(); 57 | return Nan::ThrowError(message); 58 | } 59 | 60 | if (baton->busy) { 61 | return Nan::ThrowError("invalid state: statement is busy with another execute call"); 62 | } 63 | baton->busy = true; 64 | 65 | uv_queue_work(uv_default_loop(), 66 | &baton->work_req, 67 | EIO_Execute, 68 | (uv_after_work_cb) EIO_AfterExecute); 69 | 70 | return; 71 | } 72 | 73 | void Statement::EIO_Execute(uv_work_t* req) { 74 | StatementBaton* baton = CONTAINER_OF(req, StatementBaton, work_req); 75 | 76 | if (!baton->m_connection) { 77 | baton->error = new std::string("Connection already closed"); 78 | return; 79 | } 80 | if (!baton->stmt) { 81 | baton->stmt = Connection::CreateStatement(baton); 82 | if (baton->error) return; 83 | } 84 | Connection::ExecuteStatement(baton, baton->stmt); 85 | } 86 | 87 | void Statement::EIO_AfterExecute(uv_work_t* req) { 88 | Nan::HandleScope scope; 89 | StatementBaton* baton = CONTAINER_OF(req, StatementBaton, work_req); 90 | 91 | baton->busy = false; 92 | 93 | Local argv[2]; 94 | Connection::handleResult(baton, argv); 95 | 96 | baton->ResetValues(); 97 | baton->ResetRows(); 98 | baton->ResetOutputs(); 99 | baton->ResetError(); 100 | 101 | // invoke callback at the very end because callback may re-enter execute. 102 | baton->callback->Call(2, argv); 103 | } 104 | 105 | -------------------------------------------------------------------------------- /lib/oracle.js: -------------------------------------------------------------------------------- 1 | var bindings = null; 2 | try { 3 | // Try the release version 4 | bindings = require("../build/Release/oracle_bindings"); 5 | } catch (err) { 6 | if (err.code === 'MODULE_NOT_FOUND') { 7 | // Fall back to the debug version 8 | bindings = require("../build/Debug/oracle_bindings"); 9 | } else { 10 | throw err; 11 | } 12 | } 13 | 14 | function getSettings(settings) { 15 | settings = settings || { 16 | hostname: '127.0.0.1', 17 | database: 'XE' 18 | }; 19 | settings.hostname = settings.hostname || settings.host; 20 | settings.user = settings.user || settings.username; 21 | return settings; 22 | } 23 | 24 | module.exports = function (settings) { 25 | var ldap = settings && settings.ldap; 26 | var oracle = null; 27 | if (ldap === null || (typeof ldap !== 'object')) { 28 | oracle = new bindings.OracleClient(); 29 | } else { 30 | oracle = new bindings.OracleClient(ldap); 31 | } 32 | var result = {}; 33 | result.connect = function (settings, callback) { 34 | settings = getSettings(settings); 35 | oracle.connect(settings, callback); 36 | }; 37 | 38 | result.connectSync = function (settings) { 39 | settings = getSettings(settings); 40 | return oracle.connectSync(settings); 41 | }; 42 | 43 | result.createConnectionPool = function (settings, callback) { 44 | settings = getSettings(settings); 45 | oracle.createConnectionPool(settings, callback); 46 | }; 47 | 48 | result.createConnectionPoolSync = function (settings) { 49 | settings = getSettings(settings); 50 | return oracle.createConnectionPoolSync(settings); 51 | }; 52 | 53 | result.OutParam = bindings.OutParam; 54 | 55 | result.OCCIINT = 0; 56 | result.OCCISTRING = 1; 57 | result.OCCIDOUBLE = 2; 58 | result.OCCIFLOAT = 3; 59 | result.OCCICURSOR = 4; 60 | result.OCCICLOB = 5; 61 | result.OCCIDATE = 6; 62 | result.OCCITIMESTAMP = 7; 63 | result.OCCINUMBER = 8; 64 | result.OCCIBLOB = 9; 65 | 66 | return result; 67 | } 68 | 69 | // Reader is implemented in JS around a C++ handle 70 | // This is easier and also more efficient because we don't cross the JS/C++ boundary 71 | // every time we read a record. 72 | function Reader(handle) { 73 | this._handle = handle; 74 | this._error = null; 75 | this._rows = []; 76 | } 77 | 78 | Reader.prototype.nextRows = function () { 79 | this._handle.nextRows.apply(this._handle, arguments); 80 | } 81 | 82 | Reader.prototype.nextRow = function (cb) { 83 | var self = this; 84 | if (!self._handle || self._error || (self._rows && self._rows.length > 0)) { 85 | process.nextTick(function () { 86 | return cb(self._error, self._rows && self._rows.shift()); 87 | }); 88 | } else { 89 | // nextRows willl use the prefetch row count as window size 90 | self._handle.nextRows(function (err, result) { 91 | self._error = err || self._error; 92 | self._rows = result; 93 | if (err || result.length === 0) { 94 | self._handle = null; 95 | } 96 | return cb(self._error, self._rows && self._rows.shift()); 97 | }); 98 | } 99 | }; 100 | 101 | bindings.Connection.prototype.reader = function (sql, args) { 102 | return new Reader(this.readerHandle(sql, args)); 103 | }; 104 | -------------------------------------------------------------------------------- /test/test.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE person_test; 2 | CREATE TABLE person_test ( 3 | id INTEGER PRIMARY KEY, name VARCHAR(255) 4 | ); 5 | 6 | DROP SEQUENCE person_test_seq; 7 | CREATE SEQUENCE person_test_seq START WITH 1 INCREMENT BY 1 NOMAXVALUE; 8 | CREATE OR REPLACE TRIGGER person_test_pk_trigger BEFORE 9 | INSERT ON person_test FOR EACH row BEGIN 10 | SELECT person_test_seq.nextval INTO :new.id FROM dual; 11 | END; 12 | / 13 | DROP TABLE datatype_test; 14 | CREATE TABLE datatype_test ( 15 | id INTEGER PRIMARY KEY, 16 | tvarchar2 VARCHAR2(255), 17 | tnvarchar2 NVARCHAR2(255), 18 | tchar CHAR(255), 19 | tnchar NCHAR(255), 20 | tnumber NUMBER(10,5), 21 | tdate DATE, 22 | ttimestamp TIMESTAMP, 23 | tclob CLOB, 24 | tnclob NCLOB, 25 | tblob BLOB 26 | ); 27 | DROP SEQUENCE datatype_test_seq; 28 | CREATE SEQUENCE datatype_test_seq START WITH 1 INCREMENT BY 1 NOMAXVALUE; 29 | CREATE OR REPLACE TRIGGER datatype_test_pk_trigger BEFORE 30 | INSERT ON datatype_test FOR EACH row BEGIN 31 | SELECT datatype_test_seq.nextval INTO :new.id FROM dual; 32 | END; 33 | / 34 | 35 | CREATE OR REPLACE PROCEDURE procNumericOutParam(param1 IN VARCHAR2, outParam1 OUT NUMBER) 36 | IS 37 | BEGIN 38 | DBMS_OUTPUT.PUT_LINE('Hello '|| param1); 39 | outParam1 := 42; 40 | END; 41 | / 42 | CREATE OR REPLACE PROCEDURE procStringOutParam(param1 IN VARCHAR2, outParam1 OUT STRING) 43 | IS 44 | BEGIN 45 | DBMS_OUTPUT.PUT_LINE('Hello '|| param1); 46 | outParam1 := 'Hello ' || param1; 47 | END; 48 | / 49 | CREATE OR REPLACE PROCEDURE procVarChar2OutParam(param1 IN VARCHAR2, outParam1 OUT VARCHAR2) 50 | IS 51 | BEGIN 52 | DBMS_OUTPUT.PUT_LINE('Hello '|| param1); 53 | outParam1 := 'Hello ' || param1; 54 | END; 55 | / 56 | CREATE OR REPLACE PROCEDURE procDoubleOutParam(param1 IN VARCHAR2, outParam1 OUT DOUBLE PRECISION) 57 | IS 58 | BEGIN 59 | outParam1 := -43.123456789012; 60 | END; 61 | / 62 | CREATE OR REPLACE PROCEDURE procFloatOutParam(param1 IN VARCHAR2, outParam1 OUT FLOAT) 63 | IS 64 | BEGIN 65 | outParam1 := 43; 66 | END; 67 | / 68 | 69 | CREATE OR REPLACE PROCEDURE procTwoOutParams(param1 IN VARCHAR2, outParam1 OUT NUMBER, outParam2 OUT STRING) 70 | IS 71 | BEGIN 72 | outParam1 := 42; 73 | outParam2 := 'Hello ' || param1; 74 | END; 75 | / 76 | CREATE OR REPLACE PROCEDURE procCursorOutParam(outParam OUT SYS_REFCURSOR) 77 | IS 78 | BEGIN 79 | open outParam for 80 | select * from person_test; 81 | END; 82 | / 83 | CREATE OR REPLACE PROCEDURE procCLOBOutParam(outParam OUT CLOB) 84 | IS 85 | BEGIN 86 | outParam := 'IAMCLOB'; 87 | END; 88 | / 89 | 90 | BEGIN 91 | EXECUTE IMMEDIATE 'DROP TABLE basic_lob_table'; 92 | EXCEPTION 93 | WHEN OTHERS THEN 94 | IF SQLCODE != -942 THEN 95 | RAISE; 96 | END IF; 97 | END; 98 | / 99 | 100 | create table basic_lob_table (x varchar2 (30), b blob, c clob); 101 | insert into basic_lob_table values('one', '010101010101010101010101010101', 'onetwothreefour'); 102 | select * from basic_lob_table where x='one' and ROWNUM = 1; 103 | 104 | CREATE OR REPLACE PROCEDURE ReadBasicBLOB (outBlob OUT BLOB) 105 | IS 106 | BEGIN 107 | SELECT b INTO outBlob FROM basic_lob_table where x='one' and ROWNUM = 1; 108 | END; 109 | / 110 | 111 | CREATE OR REPLACE procedure doSquareInteger(z IN OUT Integer) 112 | is 113 | begin 114 | z := z * z; 115 | end; 116 | / 117 | create or replace PROCEDURE procDateTimeOutParam( 118 | outParam1 OUT DATE, 119 | outParam2 OUT TIMESTAMP) 120 | IS 121 | BEGIN 122 | outParam1 := sysdate; 123 | outParam2 :=CURRENT_TIMESTAMP; 124 | END; 125 | / 126 | -------------------------------------------------------------------------------- /src/executeBaton.h: -------------------------------------------------------------------------------- 1 | #ifndef _excute_baton_h_ 2 | #define _excute_baton_h_ 3 | 4 | class Connection; 5 | 6 | #include 7 | #include 8 | #include "nan.h" 9 | #include 10 | #ifndef WIN32 11 | #include 12 | #endif 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "utils.h" 20 | 21 | // Basic LONG ROW support. 22 | // LONG ROW data types say they can be up to 2GB in data. But, if we use 2147483648 as the max size, 23 | // we get this error: 24 | // ORA-32107: internal OCI memory allocation failure 25 | // So, for now, using something smaller, hoping that's good enough (20MB) 26 | // 27 | #define OCI_TYPECODE_LONG_RAW 24 28 | #define LONG_ROW_MAX_SIZE (20 * 1024 * 1024) 29 | 30 | enum { 31 | VALUE_TYPE_NULL = 1, 32 | VALUE_TYPE_OUTPUT = 2, 33 | VALUE_TYPE_STRING = 3, 34 | VALUE_TYPE_NUMBER = 4, 35 | VALUE_TYPE_DATE = 5, 36 | VALUE_TYPE_TIMESTAMP = 6, 37 | VALUE_TYPE_CLOB = 7, 38 | VALUE_TYPE_BLOB = 8, 39 | VALUE_TYPE_LONG_RAW = 9 40 | }; 41 | 42 | struct column_t { 43 | int type; 44 | int charForm; 45 | std::string name; 46 | }; 47 | 48 | struct row_t { 49 | std::vector values; 50 | }; 51 | 52 | struct value_t { 53 | int type; 54 | void* value; 55 | }; 56 | 57 | struct buffer_t { 58 | size_t length; 59 | uint8_t* data; 60 | }; 61 | 62 | struct output_t { 63 | int type; 64 | int index; 65 | std::string strVal; 66 | int intVal; 67 | double doubleVal; 68 | float floatVal; 69 | std::vector* rows; 70 | std::vector columns; 71 | oracle::occi::Clob clobVal; 72 | oracle::occi::Date dateVal; 73 | oracle::occi::Timestamp timestampVal; 74 | oracle::occi::Number numberVal; 75 | oracle::occi::Blob blobVal; 76 | size_t bufLength; 77 | uint8_t *bufVal; 78 | }; 79 | 80 | /** 81 | * Baton for execute function 82 | */ 83 | class ExecuteBaton { 84 | public: 85 | ExecuteBaton(oracle::occi::Environment* m_environment, 86 | oracle::occi::StatelessConnectionPool* m_connectionPool, 87 | oracle::occi::Connection* m_connection, 88 | bool m_autoCommit, 89 | int m_prefetchRowCount, 90 | const char* sql, 91 | v8::Local values, 92 | v8::Local options, 93 | v8::Local callback = v8::Local()); 94 | 95 | ~ExecuteBaton(); 96 | 97 | oracle::occi::Environment* m_environment; 98 | oracle::occi::StatelessConnectionPool* m_connectionPool; 99 | oracle::occi::Connection* m_connection; 100 | // Autocommit flag 101 | bool m_autoCommit; 102 | // Prefetch row count 103 | int m_prefetchRowCount; 104 | Nan::Callback *callback; // The JS callback function 105 | std::vector values; // The array of parameter values 106 | std::string sql; // The sql statement string 107 | std::vector columns; // The list of columns 108 | std::vector* rows; // The list of rows 109 | std::vector* outputs; // The output values 110 | std::string* error; // The error message 111 | bool getColumnMetaData; 112 | int updateCount; // The update count 113 | uv_work_t work_req; 114 | 115 | void ResetValues(); 116 | void ResetRows(); 117 | void ResetOutputs(); 118 | void ResetError(); 119 | 120 | static void CopyValuesToBaton(ExecuteBaton* baton, 121 | v8::Local values); 122 | static void SetOptionsInBaton(ExecuteBaton* baton, 123 | v8::Local options); 124 | }; 125 | 126 | #endif 127 | -------------------------------------------------------------------------------- /test/reader.js: -------------------------------------------------------------------------------- 1 | var settings = require('../tests-settings'); 2 | var oracle = require("../")(settings); 3 | var assert = require('assert'); 4 | 5 | function initDb(connection, max, cb) { 6 | connection.setPrefetchRowCount(50); 7 | connection.execute("DROP TABLE test_table", [], function (err) { 8 | // ignore error 9 | connection.execute("CREATE TABLE test_table (X INT)", [], function (err) { 10 | if (err) { 11 | return cb(err); 12 | } 13 | 14 | function insert(i, cb) { 15 | connection.execute("INSERT INTO test_table (X) VALUES (:1)", [i], function (err) { 16 | if (err) { 17 | return cb(err); 18 | } 19 | if (i < max) { 20 | insert(i + 1, cb); 21 | } 22 | else { 23 | cb(); 24 | } 25 | }); 26 | } 27 | 28 | insert(1, cb); 29 | }); 30 | }); 31 | } 32 | 33 | function doRead(reader, fn, count, cb) { 34 | if (count === 0) { 35 | return cb(); 36 | } 37 | reader.nextRow(function (err, row) { 38 | if (err) { 39 | return cb(err); 40 | } 41 | if (row) { 42 | fn(row); 43 | return doRead(reader, fn, count - 1, cb) 44 | } else { 45 | return cb(); 46 | } 47 | }); 48 | } 49 | 50 | function testNextRow(done, connection, prefetch, requested, expected) { 51 | connection.setPrefetchRowCount(prefetch); 52 | var reader = connection.reader("SELECT X FROM test_table ORDER BY X", []); 53 | var total = 0; 54 | doRead(reader, function (row) { 55 | total += row.X; 56 | }, requested, function (err) { 57 | if (err) { 58 | return done(err); 59 | } 60 | assert.equal(total, expected * (expected + 1) / 2); 61 | done(); 62 | }); 63 | } 64 | 65 | function testNextRows(done, connection, requested, len1, len2) { 66 | var reader = connection.reader("SELECT X FROM test_table ORDER BY X", []); 67 | reader.nextRows(requested, function (err, rows) { 68 | if (err) { 69 | return done(err); 70 | } 71 | assert.equal(rows.length, len1); 72 | reader.nextRows(requested, function (err, rows) { 73 | if (err) { 74 | return done(err); 75 | } 76 | assert.equal(rows.length, len2); 77 | done(); 78 | }); 79 | }); 80 | } 81 | 82 | describe('reader', function () { 83 | before(function (done) { 84 | var self = this; 85 | oracle.connect(settings, function (err, connection) { 86 | if (err) { 87 | return done(err); 88 | } 89 | self.connection = connection; 90 | initDb(self.connection, 100, done); 91 | }); 92 | }); 93 | 94 | after(function (done) { 95 | if (this.connection) { 96 | this.connection.close(); 97 | } 98 | done(); 99 | }); 100 | 101 | it("should support nextRow - request 20 with prefetch of 5", function (done) { 102 | testNextRow(done, this.connection, 5, 20, 20); 103 | }); 104 | 105 | it("should support nextRow - request 20 with prefetch of 200", function (done) { 106 | testNextRow(done, this.connection, 200, 20, 20); 107 | }); 108 | 109 | it("should support nextRow - request 200 with prefetch of 5", function (done) { 110 | testNextRow(done, this.connection, 5, 200, 100); 111 | }); 112 | 113 | it("should support nextRow - request 200 with prefetch of 200", function (done) { 114 | testNextRow(done, this.connection, 200, 200, 100); 115 | }); 116 | 117 | it("should support nextRows - request 20", function (done) { 118 | testNextRows(done, this.connection, 20, 20, 20); 119 | }); 120 | 121 | it("should support nextRows - request 70", function (done) { 122 | testNextRows(done, this.connection, 70, 70, 30); 123 | }); 124 | 125 | it("should support nextRows - request 200", function (done) { 126 | testNextRows(done, this.connection, 200, 100, 0); 127 | }); 128 | 129 | }); 130 | -------------------------------------------------------------------------------- /src/oracle_bindings.h: -------------------------------------------------------------------------------- 1 | #ifndef _oracle_binding_h_ 2 | #define _oracle_binding_h_ 3 | 4 | #include 5 | #include 6 | #include "nan.h" 7 | #ifndef WIN32 8 | #include 9 | #endif 10 | #include 11 | #include "utils.h" 12 | 13 | using namespace node; 14 | using namespace v8; 15 | 16 | /** 17 | * C++ class for OracleClient as follows: 18 | * 19 | * function OracleClient() { 20 | * ... 21 | * } 22 | * 23 | * OracleClient.prototype.connect = function(..., callback) { 24 | * ... 25 | * }; 26 | * 27 | * OracleClient.prototype.connectSync(...) { 28 | * ... 29 | * } 30 | * 31 | * OracleClient.prototype.createConnectionPool = function(..., callback) { 32 | * ... 33 | * }; 34 | * 35 | * OracleClient.prototype.createConnectionPoolSync(...) { 36 | * ... 37 | * } 38 | */ 39 | class OracleClient: public Nan::ObjectWrap { 40 | public: 41 | /** 42 | * Define OracleClient class 43 | */ 44 | static void Init(Handle target); 45 | /** 46 | * Constructor 47 | */ 48 | static NAN_METHOD(New); 49 | 50 | /** 51 | * connect(..., callback) 52 | */ 53 | static NAN_METHOD(Connect); 54 | static void EIO_Connect(uv_work_t* req); 55 | static void EIO_AfterConnect(uv_work_t* req); 56 | 57 | /** 58 | * connectSync(...) 59 | */ 60 | static NAN_METHOD(ConnectSync); 61 | 62 | /** 63 | * createConnectionPool(..., callback) 64 | */ 65 | static NAN_METHOD(CreateConnectionPool); 66 | static void EIO_CreateConnectionPool(uv_work_t* req); 67 | static void EIO_AfterCreateConnectionPool(uv_work_t* req); 68 | 69 | /** 70 | * createConnectionPoolSync(...) 71 | */ 72 | static NAN_METHOD(CreateConnectionPoolSync); 73 | 74 | explicit OracleClient(oracle::occi::Environment::Mode mode = oracle::occi::Environment::THREADED_MUTEXED); 75 | ~OracleClient(); 76 | 77 | oracle::occi::Environment* getEnvironment() { 78 | return m_environment; 79 | } 80 | 81 | private: 82 | static Nan::Persistent s_ct; 83 | oracle::occi::Environment* m_environment; 84 | // oracle::occi::StatelessConnectionPool* m_connectionPool; 85 | }; 86 | 87 | /** 88 | * The Baton for the asynchronous connect 89 | */ 90 | class ConnectBaton { 91 | public: 92 | ConnectBaton(OracleClient* client, 93 | oracle::occi::Environment* environment, 94 | v8::Local callback = v8::Local()); 95 | ~ConnectBaton(); 96 | 97 | /** 98 | * The OracleClient instance 99 | */ 100 | OracleClient* client; 101 | 102 | /** 103 | * The callback function 104 | */ 105 | Nan::Callback callback; 106 | 107 | /** 108 | * host name or ip address for the DB server 109 | */ 110 | std::string hostname; 111 | /** 112 | * user name 113 | */ 114 | std::string user; 115 | /** 116 | * password 117 | */ 118 | std::string password; 119 | /** 120 | * The database name 121 | */ 122 | std::string database; 123 | /** 124 | * The TNS connection string 125 | */ 126 | std::string tns; 127 | /** 128 | * The port number 129 | */ 130 | uint32_t port; 131 | /** 132 | * The minimum number of connections for pool 133 | */ 134 | uint32_t minConn; 135 | /** 136 | * The maximum number of connections for pool 137 | */ 138 | uint32_t maxConn; 139 | /** 140 | * The number of connections for increment 141 | */ 142 | uint32_t incrConn; 143 | /** 144 | * timeout 145 | */ 146 | uint32_t timeout; 147 | /** 148 | * Statement cache size 149 | */ 150 | uint32_t stmtCacheSize; 151 | /** 152 | * OCCI stateless connection pool's busy option 153 | */ 154 | oracle::occi::StatelessConnectionPool::BusyOption busyOption; 155 | 156 | /** 157 | * The OCCI environment 158 | */ 159 | oracle::occi::Environment* environment; 160 | /** 161 | * OCCI stateless connection pool 162 | */ 163 | oracle::occi::StatelessConnectionPool* connectionPool; 164 | /** 165 | * OCCI connection 166 | */ 167 | oracle::occi::Connection* connection; 168 | 169 | /** 170 | * Error message 171 | */ 172 | std::string* error; 173 | 174 | uv_work_t work_req; 175 | }; 176 | 177 | #endif 178 | -------------------------------------------------------------------------------- /src/reader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "connection.h" 3 | #include "executeBaton.h" 4 | #include "reader.h" 5 | 6 | using namespace std; 7 | 8 | Nan::Persistent Reader::s_ct; 9 | 10 | void Reader::Init(Handle target) { 11 | Nan::HandleScope scope; 12 | 13 | Local t = Nan::New(New); 14 | Reader::s_ct.Reset( t); 15 | 16 | t->InstanceTemplate()->SetInternalFieldCount(1); 17 | t->SetClassName(Nan::New("Reader").ToLocalChecked()); 18 | 19 | Nan::SetPrototypeMethod(t, "nextRows", NextRows); 20 | target->Set(Nan::New("Reader").ToLocalChecked(), t->GetFunction()); 21 | } 22 | 23 | Reader::Reader(): Nan::ObjectWrap() { 24 | m_baton = NULL; 25 | } 26 | 27 | Reader::~Reader() { 28 | delete m_baton; 29 | m_baton = NULL; 30 | } 31 | 32 | NAN_METHOD(Reader::New) { 33 | Nan::HandleScope scope; 34 | 35 | Reader* reader = new Reader(); 36 | reader->Wrap(info.This()); 37 | 38 | info.GetReturnValue().Set(info.This()); 39 | } 40 | 41 | void Reader::setBaton(ReaderBaton* baton) { 42 | m_baton = baton; 43 | } 44 | 45 | NAN_METHOD(Reader::NextRows) { 46 | Nan::HandleScope scope; 47 | Reader* reader = Nan::ObjectWrap::Unwrap(info.This()); 48 | ReaderBaton* baton = reader->m_baton; 49 | if (baton->error) { 50 | Local message = Nan::New(baton->error->c_str()).ToLocalChecked(); 51 | return Nan::ThrowError(message); 52 | } 53 | if (baton->busy) { 54 | return Nan::ThrowError("invalid state: reader is busy with another nextRows call"); 55 | } 56 | baton->busy = true; 57 | 58 | if (info.Length() > 1) { 59 | REQ_INT_ARG(0, count); 60 | REQ_FUN_ARG(1, callback); 61 | baton->count = count; 62 | baton->callback = new Nan::Callback(callback); 63 | } else { 64 | REQ_FUN_ARG(0, callback); 65 | baton->count = baton->m_prefetchRowCount; 66 | baton->callback = new Nan::Callback(callback); 67 | } 68 | if (baton->count <= 0) baton->count = 1; 69 | 70 | uv_queue_work(uv_default_loop(), 71 | &baton->work_req, 72 | EIO_NextRows, 73 | (uv_after_work_cb) EIO_AfterNextRows); 74 | 75 | return; 76 | } 77 | 78 | void Reader::EIO_NextRows(uv_work_t* req) { 79 | ReaderBaton* baton = CONTAINER_OF(req, ReaderBaton, work_req); 80 | 81 | baton->rows = new vector(); 82 | if (baton->done) return; 83 | 84 | if (!baton->m_connection) { 85 | baton->error = new std::string("Connection already closed"); 86 | return; 87 | } 88 | if (!baton->rs) { 89 | try { 90 | baton->stmt = baton->m_connection->createStatement(baton->sql); 91 | baton->stmt->setAutoCommit(baton->m_autoCommit); 92 | baton->stmt->setPrefetchRowCount(baton->count); 93 | Connection::SetValuesOnStatement(baton->stmt, baton->values); 94 | if (baton->error) return; 95 | 96 | int status = baton->stmt->execute(); 97 | if (status != oracle::occi::Statement::RESULT_SET_AVAILABLE) { 98 | baton->error = new std::string("Connection already closed"); 99 | return; 100 | } 101 | baton->rs = baton->stmt->getResultSet(); 102 | } catch (oracle::occi::SQLException &ex) { 103 | baton->error = new string(ex.getMessage()); 104 | return; 105 | } 106 | Connection::CreateColumnsFromResultSet(baton->rs, baton->columns); 107 | if (baton->error) return; 108 | } 109 | for (int i = 0; i < baton->count && baton->rs->next(); i++) { 110 | row_t* row = Connection::CreateRowFromCurrentResultSetRow(baton->rs, baton->columns); 111 | if (baton->error) return; 112 | baton->rows->push_back(row); 113 | } 114 | if (baton->rows->size() < (size_t)baton->count) baton->done = true; 115 | } 116 | 117 | void Reader::EIO_AfterNextRows(uv_work_t* req, int status) { 118 | Nan::HandleScope scope; 119 | ReaderBaton* baton = CONTAINER_OF(req, ReaderBaton, work_req); 120 | 121 | baton->busy = false; 122 | 123 | Local argv[2]; 124 | Connection::handleResult(baton, argv); 125 | 126 | baton->ResetRows(); 127 | if (baton->done || baton->error) { 128 | // free occi resources so that we don't run out of cursors if gc is not fast enough 129 | // reader destructor will delete the baton and everything else. 130 | baton->ResetStatement(); 131 | } 132 | 133 | // invoke callback at the very end because callback may re-enter nextRows. 134 | baton->callback->Call(2, argv); 135 | } 136 | -------------------------------------------------------------------------------- /example/admin/ldap-server.js: -------------------------------------------------------------------------------- 1 | var ldap = require('ldapjs'); 2 | var Logger = require('bunyan'); 3 | 4 | ///--- Shared handlers 5 | 6 | function authorize(req, res, next) { 7 | /* Any user may search after bind, only cn=root has full power */ 8 | var isSearch = (req instanceof ldap.SearchRequest); 9 | if (!req.connection.ldap.bindDN.equals('cn=root') && !isSearch) 10 | return next(new ldap.InsufficientAccessRightsError()); 11 | 12 | return next(); 13 | } 14 | 15 | 16 | ///--- Globals 17 | 18 | var SUFFIX = 'dc=com'; 19 | var db = {}; 20 | 21 | var options = { 22 | log: new Logger({ 23 | name: 'ldapjs', 24 | component: 'server', 25 | level: 'trace', 26 | stream: process.stderr 27 | }) 28 | }; 29 | 30 | var server = ldap.createServer(options); 31 | 32 | 33 | 34 | server.bind('cn=root', function (req, res, next) { 35 | if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret') 36 | return next(new ldap.InvalidCredentialsError()); 37 | 38 | res.end(); 39 | return next(); 40 | }); 41 | 42 | server.add(SUFFIX, authorize, function (req, res, next) { 43 | var dn = req.dn.toString(); 44 | 45 | if (db[dn]) 46 | return next(new ldap.EntryAlreadyExistsError(dn)); 47 | 48 | db[dn] = req.toObject().attributes; 49 | res.end(); 50 | return next(); 51 | }); 52 | 53 | server.bind(SUFFIX, function (req, res, next) { 54 | var dn = req.dn.toString(); 55 | if (!db[dn]) 56 | return next(new ldap.NoSuchObjectError(dn)); 57 | 58 | if (!db[dn].userpassword) 59 | return next(new ldap.NoSuchAttributeError('userPassword')); 60 | 61 | if (db[dn].userpassword.indexOf(req.credentials) === -1) 62 | return next(new ldap.InvalidCredentialsError()); 63 | 64 | res.end(); 65 | return next(); 66 | }); 67 | 68 | server.compare(SUFFIX, authorize, function (req, res, next) { 69 | var dn = req.dn.toString(); 70 | if (!db[dn]) 71 | return next(new ldap.NoSuchObjectError(dn)); 72 | 73 | if (!db[dn][req.attribute]) 74 | return next(new ldap.NoSuchAttributeError(req.attribute)); 75 | 76 | var matches = false; 77 | var vals = db[dn][req.attribute]; 78 | for (var i = 0; i < vals.length; i++) { 79 | if (vals[i] === req.value) { 80 | matches = true; 81 | break; 82 | } 83 | } 84 | 85 | res.end(matches); 86 | return next(); 87 | }); 88 | 89 | server.del(SUFFIX, authorize, function (req, res, next) { 90 | var dn = req.dn.toString(); 91 | if (!db[dn]) 92 | return next(new ldap.NoSuchObjectError(dn)); 93 | 94 | delete db[dn]; 95 | 96 | res.end(); 97 | return next(); 98 | }); 99 | 100 | server.modify(SUFFIX, authorize, function (req, res, next) { 101 | var dn = req.dn.toString(); 102 | if (!req.changes.length) 103 | return next(new ldap.ProtocolError('changes required')); 104 | if (!db[dn]) 105 | return next(new ldap.NoSuchObjectError(dn)); 106 | 107 | var entry = db[dn]; 108 | 109 | for (var i = 0; i < req.changes.length; i++) { 110 | mod = req.changes[i].modification; 111 | switch (req.changes[i].operation) { 112 | case 'replace': 113 | if (!entry[mod.type]) 114 | return next(new ldap.NoSuchAttributeError(mod.type)); 115 | 116 | if (!mod.vals || !mod.vals.length) { 117 | delete entry[mod.type]; 118 | } else { 119 | entry[mod.type] = mod.vals; 120 | } 121 | 122 | break; 123 | 124 | case 'add': 125 | if (!entry[mod.type]) { 126 | entry[mod.type] = mod.vals; 127 | } else { 128 | mod.vals.forEach(function (v) { 129 | if (entry[mod.type].indexOf(v) === -1) 130 | entry[mod.type].push(v); 131 | }); 132 | } 133 | 134 | break; 135 | 136 | case 'delete': 137 | if (!entry[mod.type]) 138 | return next(new ldap.NoSuchAttributeError(mod.type)); 139 | 140 | delete entry[mod.type]; 141 | 142 | break; 143 | } 144 | } 145 | 146 | res.end(); 147 | return next(); 148 | }); 149 | 150 | server.search(SUFFIX, authorize, function (req, res, next) { 151 | var dn = req.dn.toString(); 152 | if (!db[dn]) 153 | return next(new ldap.NoSuchObjectError(dn)); 154 | 155 | var scopeCheck; 156 | 157 | switch (req.scope) { 158 | case 'base': 159 | if (req.filter.matches(db[dn])) { 160 | res.send({ 161 | dn: dn, 162 | attributes: db[dn] 163 | }); 164 | } 165 | 166 | res.end(); 167 | return next(); 168 | 169 | case 'one': 170 | scopeCheck = function (k) { 171 | if (req.dn.equals(k)) 172 | return true; 173 | 174 | var parent = ldap.parseDN(k).parent(); 175 | return (parent ? parent.equals(req.dn) : false); 176 | }; 177 | break; 178 | 179 | case 'sub': 180 | scopeCheck = function (k) { 181 | return (req.dn.equals(k) || req.dn.parentOf(k)); 182 | }; 183 | 184 | break; 185 | } 186 | 187 | Object.keys(db).forEach(function (key) { 188 | if (!scopeCheck(key)) 189 | return; 190 | 191 | if (req.filter.matches(db[key])) { 192 | res.send({ 193 | dn: key, 194 | attributes: db[key] 195 | }); 196 | } 197 | }); 198 | 199 | res.end(); 200 | return next(); 201 | }); 202 | 203 | 204 | 205 | ///--- Fire it up 206 | 207 | server.listen(1389, function () { 208 | console.log('LDAP server up at: %s', server.url); 209 | }); 210 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef _util_h_ 2 | #define _util_h_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include "nan.h" 11 | 12 | using namespace v8; 13 | 14 | #ifdef _WIN32 15 | // emulate snprintf() on windows, _snprintf() doesn't zero-terminate the buffer 16 | // on overflow... 17 | #include 18 | inline static int snprintf(char* buf, unsigned int len, const char* fmt, ...) { 19 | va_list ap; 20 | va_start(ap, fmt); 21 | int n = _vsprintf_p(buf, len, fmt, ap); 22 | if (len) 23 | buf[len - 1] = '\0'; 24 | va_end(ap); 25 | return n; 26 | } 27 | #endif 28 | 29 | /** 30 | * Requires a JS boolean argument 31 | */ 32 | #define REQ_BOOL_ARG(I, VAR) \ 33 | if (info.Length() <= (I) || !info[I]->IsBoolean()) \ 34 | return Nan::ThrowTypeError("Argument " #I " must be a bool"); \ 35 | bool VAR = info[I]->IsTrue(); 36 | 37 | /** 38 | * Requires a JS number argument 39 | */ 40 | #define REQ_INT_ARG(I, VAR) \ 41 | if (info.Length() <= (I) || !info[I]->IsNumber()) \ 42 | return Nan::ThrowTypeError("Argument " #I " must be an integer"); \ 43 | int VAR = info[I]->NumberValue(); 44 | 45 | /** 46 | * Requires a JS string argument 47 | */ 48 | #define REQ_STRING_ARG(I, VAR) \ 49 | if (info.Length() <= (I) || !info[I]->IsString()) \ 50 | return Nan::ThrowTypeError("Argument " #I " must be a string"); \ 51 | Local VAR = Local::Cast(info[I]); 52 | 53 | /** 54 | * Requires an JS array argument 55 | */ 56 | #define REQ_ARRAY_ARG(I, VAR) \ 57 | if (info.Length() <= (I) || !info[I]->IsArray()) \ 58 | return Nan::ThrowTypeError("Argument " #I " must be an array"); \ 59 | Local VAR = Local::Cast(info[I]); 60 | 61 | /** 62 | * Requires an JS function argument 63 | */ 64 | #define REQ_FUN_ARG(I, VAR) \ 65 | if (info.Length() <= (I) || !info[I]->IsFunction()) \ 66 | return Nan::ThrowTypeError("Argument " #I " must be a function"); \ 67 | Local VAR = Local::Cast(info[I]); 68 | 69 | /** 70 | * Requires an JS object argument 71 | */ 72 | #define REQ_OBJECT_ARG(I, VAR) \ 73 | if (info.Length() <= (I) || !info[I]->IsObject()) \ 74 | return Nan::ThrowTypeError("Argument " #I " must be an object"); \ 75 | Local VAR = Local::Cast(info[I]); 76 | 77 | /** 78 | * Get the value of a string property from the object 79 | */ 80 | #define OBJ_GET_STRING(OBJ, KEY, VAR) \ 81 | { \ 82 | Local __val = OBJ->Get(Nan::New(KEY).ToLocalChecked()); \ 83 | if(__val->IsString()) { \ 84 | String::Utf8Value __utf8Val(__val); \ 85 | VAR = *__utf8Val; \ 86 | } \ 87 | } 88 | 89 | /** 90 | * Get the number value of a property from the object 91 | * 92 | */ 93 | #define OBJ_GET_NUMBER(OBJ, KEY, VAR, DEFAULT) \ 94 | { \ 95 | Local __val = OBJ->Get(Nan::New(KEY).ToLocalChecked()); \ 96 | if(__val->IsNumber()) { \ 97 | VAR = __val->ToNumber()->Value(); \ 98 | } \ 99 | else if(__val->IsString()) { \ 100 | String::Utf8Value __utf8Value(__val); \ 101 | VAR = atoi(*__utf8Value); \ 102 | } else { \ 103 | VAR = DEFAULT; \ 104 | } \ 105 | } 106 | 107 | #endif 108 | 109 | -------------------------------------------------------------------------------- /src/executeBaton.cpp: -------------------------------------------------------------------------------- 1 | #include "executeBaton.h" 2 | #include "outParam.h" 3 | #include "nodeOracleException.h" 4 | #include "connection.h" 5 | #include 6 | 7 | using namespace std; 8 | 9 | ExecuteBaton::ExecuteBaton(oracle::occi::Environment* m_environment, 10 | oracle::occi::StatelessConnectionPool* m_connectionPool, 11 | oracle::occi::Connection* m_connection, 12 | bool m_autoCommit, 13 | int m_prefetchRowCount, 14 | const char* sql, 15 | v8::Local values, 16 | v8::Local options, 17 | v8::Local callback) { 18 | this->m_environment = m_environment; 19 | this->m_connectionPool = m_connectionPool; 20 | this->m_connection = m_connection; 21 | this->m_autoCommit = m_autoCommit; 22 | this->m_prefetchRowCount = m_prefetchRowCount; 23 | this->sql = sql; 24 | this->outputs = new vector(); 25 | this->error = NULL; 26 | this->callback = callback.IsEmpty() ? NULL : new Nan::Callback(callback); 27 | CopyValuesToBaton(this, values); 28 | SetOptionsInBaton(this, options); 29 | } 30 | 31 | ExecuteBaton::~ExecuteBaton() { 32 | delete callback; 33 | 34 | for (vector::iterator iterator = columns.begin(), end = 35 | columns.end(); iterator != end; ++iterator) { 36 | column_t* col = *iterator; 37 | delete col; 38 | } 39 | 40 | ResetValues(); 41 | ResetRows(); 42 | ResetOutputs(); 43 | ResetError(); 44 | } 45 | 46 | double CallDateMethod(v8::Local date, const char* methodName) { 47 | Handle info[1]; // should be zero but on windows the compiler will not allow a zero length array 48 | v8::Local result = v8::Local::Cast( 49 | date->Get(Nan::New(methodName).ToLocalChecked()))->Call(date, 0, info); 50 | return v8::Local::Cast(result)->Value(); 51 | } 52 | 53 | oracle::occi::Timestamp* V8DateToOcciDate(oracle::occi::Environment* env, 54 | v8::Local val) { 55 | 56 | int year = CallDateMethod(val, "getUTCFullYear"); 57 | int month = CallDateMethod(val, "getUTCMonth") + 1; 58 | int day = CallDateMethod(val, "getUTCDate"); 59 | int hours = CallDateMethod(val, "getUTCHours"); 60 | int minutes = CallDateMethod(val, "getUTCMinutes"); 61 | int seconds = CallDateMethod(val, "getUTCSeconds"); 62 | 63 | int fs = CallDateMethod(val, "getUTCMilliseconds") * 1000000; // occi::Timestamp() wants nanoseconds 64 | oracle::occi::Timestamp* d = new oracle::occi::Timestamp(env, year, month, day, hours, minutes, seconds, fs); 65 | 66 | return d; 67 | } 68 | 69 | void ExecuteBaton::CopyValuesToBaton(ExecuteBaton* baton, 70 | v8::Local values) { 71 | if (values.IsEmpty()) return; 72 | 73 | for (uint32_t i = 0; i < values->Length(); i++) { 74 | v8::Local val = values->Get(i); 75 | value_t *value = new value_t(); 76 | 77 | // null or undefined 78 | if (val->IsNull() || val->IsUndefined()) { 79 | value->type = VALUE_TYPE_NULL; 80 | value->value = NULL; 81 | baton->values.push_back(value); 82 | } 83 | 84 | 85 | // VALUE_TYPE_LONG_RAW needed somewhere here. 86 | 87 | // string 88 | else if (val->IsString()) { 89 | String::Utf8Value utf8Value(val); 90 | value->type = VALUE_TYPE_STRING; 91 | value->value = new string(*utf8Value); 92 | baton->values.push_back(value); 93 | } 94 | 95 | // date 96 | else if (val->IsDate()) { 97 | value->type = VALUE_TYPE_TIMESTAMP; 98 | value->value = V8DateToOcciDate(baton->m_environment, 99 | val.As()); 100 | baton->values.push_back(value); 101 | } 102 | 103 | // number 104 | else if (val->IsNumber()) { 105 | value->type = VALUE_TYPE_NUMBER; 106 | double d = v8::Number::Cast(*val)->Value(); 107 | value->value = new oracle::occi::Number(d); // XXX not deleted in dtor 108 | baton->values.push_back(value); 109 | } 110 | 111 | // Buffer 112 | else if (node::Buffer::HasInstance(val)) { 113 | value->type = VALUE_TYPE_CLOB; 114 | v8::Local buffer = val->ToObject(); 115 | size_t length = node::Buffer::Length(buffer); 116 | uint8_t* data = (uint8_t*) node::Buffer::Data(buffer); 117 | 118 | buffer_t* buf = new buffer_t(); 119 | buf->length = length; 120 | 121 | // Copy the buffer to a new array as the original copy can be GCed 122 | buf->data = new uint8_t[length]; 123 | memcpy(buf->data, data, length); 124 | 125 | value->value = buf; 126 | baton->values.push_back(value); 127 | } 128 | 129 | 130 | // output 131 | else if (Nan::New(OutParam::constructorTemplate)->HasInstance( val)) { 132 | 133 | OutParam* op = Nan::ObjectWrap::Unwrap(val->ToObject()); 134 | 135 | // [rfeng] The OutParam object will be destroyed. We need to create a new copy. 136 | value->type = VALUE_TYPE_OUTPUT; 137 | value->value = op->c_outparam(); 138 | baton->values.push_back(value); 139 | 140 | output_t* output = new output_t(); 141 | output->rows = NULL; 142 | output->type = op->type(); 143 | output->index = i + 1; 144 | baton->outputs->push_back(output); 145 | 146 | } 147 | 148 | // unhandled type 149 | else { 150 | //XXX leaks new value on error 151 | baton->error = new string("CopyValuesToBaton: Unhandled value type"); 152 | // throw NodeOracleException(message.str()); 153 | } 154 | 155 | } 156 | } 157 | 158 | void ExecuteBaton::SetOptionsInBaton(ExecuteBaton* baton, 159 | v8::Local options) { 160 | baton->getColumnMetaData = 161 | !options.IsEmpty() 162 | ? options->Get(Nan::New("getColumnMetaData").ToLocalChecked())->BooleanValue() 163 | : false; 164 | } 165 | 166 | void ExecuteBaton::ResetValues() { 167 | for (std::vector::iterator iterator = values.begin(), end = values.end(); iterator != end; ++iterator) { 168 | 169 | value_t* val = *iterator; 170 | switch (val->type) { 171 | case VALUE_TYPE_LONG_RAW: 172 | delete (std::string*)val->value; 173 | break; 174 | case VALUE_TYPE_STRING: 175 | delete (std::string*)val->value; 176 | break; 177 | case VALUE_TYPE_NUMBER: 178 | delete (oracle::occi::Number*)val->value; 179 | break; 180 | case VALUE_TYPE_TIMESTAMP: 181 | delete (oracle::occi::Timestamp*)val->value; 182 | break; 183 | } 184 | delete val; 185 | } 186 | values.clear(); 187 | } 188 | 189 | void ExecuteBaton::ResetRows() { 190 | if (rows) { 191 | for (std::vector::iterator iterator = rows->begin(), end = rows->end(); iterator != end; ++iterator) { 192 | row_t* currentRow = *iterator; 193 | delete currentRow; 194 | } 195 | 196 | delete rows; 197 | rows = NULL; 198 | } 199 | } 200 | 201 | void ExecuteBaton::ResetOutputs() { 202 | if (outputs) { 203 | for (std::vector::iterator iterator = outputs->begin(), end = outputs->end(); iterator != end; ++iterator) { 204 | output_t* o = *iterator; 205 | delete o; 206 | } 207 | delete outputs; 208 | outputs = NULL; 209 | } 210 | } 211 | 212 | void ExecuteBaton::ResetError() { 213 | if (error) { 214 | delete error; 215 | error = NULL; 216 | } 217 | } 218 | 219 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Contributing ### 2 | 3 | Thank you for your interest in `strong-oracle`, an open source project 4 | administered by StrongLoop. 5 | 6 | Contributing to `strong-oracle` is easy. In a few simple steps: 7 | 8 | * Ensure that your effort is aligned with the project's roadmap by 9 | talking to the maintainers, especially if you are going to spend a 10 | lot of time on it. 11 | 12 | * Make something better or fix a bug. 13 | 14 | * Adhere to code style outlined in the [Google C++ Style Guide][] and 15 | [Google Javascript Style Guide][]. 16 | 17 | * Sign the [Contributor License Agreement](https://cla.strongloop.com/agreements/strongloop/strong-oracle) 18 | 19 | * Submit a pull request through Github. 20 | 21 | 22 | ### Contributor License Agreement ### 23 | 24 | ``` 25 | Individual Contributor License Agreement 26 | 27 | By signing this Individual Contributor License Agreement 28 | ("Agreement"), and making a Contribution (as defined below) to 29 | StrongLoop, Inc. ("StrongLoop"), You (as defined below) accept and 30 | agree to the following terms and conditions for Your present and 31 | future Contributions submitted to StrongLoop. Except for the license 32 | granted in this Agreement to StrongLoop and recipients of software 33 | distributed by StrongLoop, You reserve all right, title, and interest 34 | in and to Your Contributions. 35 | 36 | 1. Definitions 37 | 38 | "You" or "Your" shall mean the copyright owner or the individual 39 | authorized by the copyright owner that is entering into this 40 | Agreement with StrongLoop. 41 | 42 | "Contribution" shall mean any original work of authorship, 43 | including any modifications or additions to an existing work, that 44 | is intentionally submitted by You to StrongLoop for inclusion in, 45 | or documentation of, any of the products owned or managed by 46 | StrongLoop ("Work"). For purposes of this definition, "submitted" 47 | means any form of electronic, verbal, or written communication 48 | sent to StrongLoop or its representatives, including but not 49 | limited to communication or electronic mailing lists, source code 50 | control systems, and issue tracking systems that are managed by, 51 | or on behalf of, StrongLoop for the purpose of discussing and 52 | improving the Work, but excluding communication that is 53 | conspicuously marked or otherwise designated in writing by You as 54 | "Not a Contribution." 55 | 56 | 2. You Grant a Copyright License to StrongLoop 57 | 58 | Subject to the terms and conditions of this Agreement, You hereby 59 | grant to StrongLoop and recipients of software distributed by 60 | StrongLoop, a perpetual, worldwide, non-exclusive, no-charge, 61 | royalty-free, irrevocable copyright license to reproduce, prepare 62 | derivative works of, publicly display, publicly perform, 63 | sublicense, and distribute Your Contributions and such derivative 64 | works under any license and without any restrictions. 65 | 66 | 3. You Grant a Patent License to StrongLoop 67 | 68 | Subject to the terms and conditions of this Agreement, You hereby 69 | grant to StrongLoop and to recipients of software distributed by 70 | StrongLoop a perpetual, worldwide, non-exclusive, no-charge, 71 | royalty-free, irrevocable (except as stated in this Section) 72 | patent license to make, have made, use, offer to sell, sell, 73 | import, and otherwise transfer the Work under any license and 74 | without any restrictions. The patent license You grant to 75 | StrongLoop under this Section applies only to those patent claims 76 | licensable by You that are necessarily infringed by Your 77 | Contributions(s) alone or by combination of Your Contributions(s) 78 | with the Work to which such Contribution(s) was submitted. If any 79 | entity institutes a patent litigation against You or any other 80 | entity (including a cross-claim or counterclaim in a lawsuit) 81 | alleging that Your Contribution, or the Work to which You have 82 | contributed, constitutes direct or contributory patent 83 | infringement, any patent licenses granted to that entity under 84 | this Agreement for that Contribution or Work shall terminate as 85 | of the date such litigation is filed. 86 | 87 | 4. You Have the Right to Grant Licenses to StrongLoop 88 | 89 | You represent that You are legally entitled to grant the licenses 90 | in this Agreement. 91 | 92 | If Your employer(s) has rights to intellectual property that You 93 | create, You represent that You have received permission to make 94 | the Contributions on behalf of that employer, that Your employer 95 | has waived such rights for Your Contributions, or that Your 96 | employer has executed a separate Corporate Contributor License 97 | Agreement with StrongLoop. 98 | 99 | 5. The Contributions Are Your Original Work 100 | 101 | You represent that each of Your Contributions are Your original 102 | works of authorship (see Section 8 (Submissions on Behalf of 103 | Others) for submission on behalf of others). You represent that to 104 | Your knowledge, no other person claims, or has the right to claim, 105 | any right in any intellectual property right related to Your 106 | Contributions. 107 | 108 | You also represent that You are not legally obligated, whether by 109 | entering into an agreement or otherwise, in any way that conflicts 110 | with the terms of this Agreement. 111 | 112 | You represent that Your Contribution submissions include complete 113 | details of any third-party license or other restriction (including, 114 | but not limited to, related patents and trademarks) of which You 115 | are personally aware and which are associated with any part of 116 | Your Contributions. 117 | 118 | 6. You Don't Have an Obligation to Provide Support for Your Contributions 119 | 120 | You are not expected to provide support for Your Contributions, 121 | except to the extent You desire to provide support. You may provide 122 | support for free, for a fee, or not at all. 123 | 124 | 6. No Warranties or Conditions 125 | 126 | StrongLoop acknowledges that unless required by applicable law or 127 | agreed to in writing, You provide Your Contributions on an "AS IS" 128 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 129 | EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES 130 | OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR 131 | FITNESS FOR A PARTICULAR PURPOSE. 132 | 133 | 7. Submission on Behalf of Others 134 | 135 | If You wish to submit work that is not Your original creation, You 136 | may submit it to StrongLoop separately from any Contribution, 137 | identifying the complete details of its source and of any license 138 | or other restriction (including, but not limited to, related 139 | patents, trademarks, and license agreements) of which You are 140 | personally aware, and conspicuously marking the work as 141 | "Submitted on Behalf of a Third-Party: [named here]". 142 | 143 | 8. Agree to Notify of Change of Circumstances 144 | 145 | You agree to notify StrongLoop of any facts or circumstances of 146 | which You become aware that would make these representations 147 | inaccurate in any respect. Email us at callback@strongloop.com. 148 | ``` 149 | 150 | [Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html 151 | [Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml 152 | -------------------------------------------------------------------------------- /src/connection.h: -------------------------------------------------------------------------------- 1 | #ifndef _connection_h_ 2 | #define _connection_h_ 3 | 4 | #include 5 | #include 6 | #include "nan.h" 7 | #ifndef WIN32 8 | #include 9 | #endif 10 | #include 11 | #include 12 | #include "utils.h" 13 | #include "nodeOracleException.h" 14 | #include "executeBaton.h" 15 | 16 | #define OFFSET_OF(TypeName, Field) \ 17 | (reinterpret_cast(&(reinterpret_cast(8)->Field)) - 8) 18 | 19 | #define CONTAINER_OF(Pointer, TypeName, Field) \ 20 | reinterpret_cast( \ 21 | reinterpret_cast(Pointer) - OFFSET_OF(TypeName, Field)) 22 | 23 | using namespace node; 24 | using namespace v8; 25 | 26 | class ConnectionBaton; 27 | class ConnectionPoolBaton; 28 | class ExecuteBaton; 29 | class ConnectionPool; 30 | 31 | /** 32 | * Wrapper for an OCCI Connection class so that it can be used in JavaScript 33 | */ 34 | class Connection: public Nan::ObjectWrap { 35 | public: 36 | static Nan::Persistent s_ct; 37 | static void Init(Handle target); 38 | static NAN_METHOD(New); 39 | 40 | // asynchronous execute method 41 | static NAN_METHOD(Execute); 42 | static void EIO_Execute(uv_work_t* req); 43 | static void EIO_AfterExecute(uv_work_t* req); 44 | 45 | // synchronous execute method 46 | static NAN_METHOD(ExecuteSync); 47 | 48 | // asynchronous commit method 49 | static NAN_METHOD(Commit); 50 | static void EIO_Commit(uv_work_t* req); 51 | static void EIO_AfterCommit(uv_work_t* req); 52 | 53 | // asynchronous rollback method 54 | static NAN_METHOD(Rollback); 55 | static void EIO_Rollback(uv_work_t* req); 56 | static void EIO_AfterRollback(uv_work_t* req); 57 | 58 | // asynchronous rollback method 59 | static NAN_METHOD(Close); 60 | static void EIO_Close(uv_work_t* req); 61 | static void EIO_AfterClose(uv_work_t* req); 62 | 63 | static NAN_METHOD(Prepare); 64 | static NAN_METHOD(CreateReader); 65 | 66 | static NAN_METHOD(SetAutoCommit); 67 | static NAN_METHOD(SetPrefetchRowCount); 68 | static NAN_METHOD(IsConnected); 69 | 70 | static NAN_METHOD(CloseSync); 71 | 72 | void closeConnection(); 73 | 74 | Connection(); 75 | ~Connection(); 76 | 77 | void setConnection(oracle::occi::Environment* environment, 78 | ConnectionPool* connectionPool, 79 | oracle::occi::Connection* connection); 80 | 81 | oracle::occi::Environment* getEnvironment() { 82 | return m_environment; 83 | } 84 | 85 | oracle::occi::Connection* getConnection() { 86 | return m_connection; 87 | } 88 | 89 | bool getAutoCommit() { 90 | return m_autoCommit; 91 | } 92 | 93 | int getPrefetchRowCount() { 94 | return m_prefetchRowCount; 95 | } 96 | 97 | 98 | static int SetValuesOnStatement(oracle::occi::Statement* stmt, 99 | std::vector &values); 100 | static void CreateColumnsFromResultSet(oracle::occi::ResultSet* rs, 101 | std::vector &columns); 102 | static row_t* CreateRowFromCurrentResultSetRow(oracle::occi::ResultSet* rs, 103 | std::vector &columns); 104 | static Local CreateV8ArrayFromRows(std::vector columns, 105 | std::vector* rows); 106 | static Local CreateV8ObjectFromRow(std::vector columns, 107 | row_t* currentRow); 108 | static Local CreateV8ArrayFromCols(std::vector columns); 109 | 110 | // shared with Statement 111 | static oracle::occi::Statement* CreateStatement(ExecuteBaton* baton); 112 | static void ExecuteStatement(ExecuteBaton* baton, oracle::occi::Statement* stmt); 113 | 114 | static void handleResult(ExecuteBaton* baton, Local (&argv)[2]); 115 | 116 | private: 117 | // The OOCI environment 118 | oracle::occi::Environment* m_environment; 119 | 120 | // The underlying connection pool 121 | ConnectionPool* connectionPool; 122 | 123 | // The underlying connection 124 | oracle::occi::Connection* m_connection; 125 | 126 | // Autocommit flag 127 | bool m_autoCommit; 128 | 129 | // Prefetch row count 130 | int m_prefetchRowCount; 131 | 132 | static void EIO_AfterCall(uv_work_t* req); 133 | static buffer_t* readClob(oracle::occi::Clob& clobVal, int charForm = SQLCS_IMPLICIT); 134 | static buffer_t* readBlob(oracle::occi::Blob& blobVal); 135 | 136 | friend class ConnectionBaton; 137 | friend class ExecuteBaton; 138 | }; 139 | 140 | /** 141 | * Wrapper for an OCCI StatelessConnectionPool class so that it can be used in JavaScript 142 | */ 143 | class ConnectionPool: public Nan::ObjectWrap { 144 | public: 145 | static Nan::Persistent s_ct; 146 | 147 | static void Init(Handle target); 148 | static NAN_METHOD(New); 149 | static NAN_METHOD(GetInfo); 150 | static NAN_METHOD(CloseSync); 151 | 152 | static NAN_METHOD(GetConnection); 153 | static void EIO_GetConnection(uv_work_t* req); 154 | static void EIO_AfterGetConnection(uv_work_t* req); 155 | 156 | static NAN_METHOD(Close); 157 | static void EIO_Close(uv_work_t* req); 158 | static void EIO_AfterClose(uv_work_t* req); 159 | 160 | // asynchronous execute method 161 | static NAN_METHOD(Execute); 162 | static void EIO_Execute(uv_work_t* req); 163 | static void EIO_AfterExecute(uv_work_t* req); 164 | 165 | static NAN_METHOD(GetConnectionSync); 166 | 167 | void closeConnectionPool( 168 | oracle::occi::StatelessConnectionPool::DestroyMode mode = 169 | oracle::occi::StatelessConnectionPool::DEFAULT); 170 | 171 | ConnectionPool(); 172 | ~ConnectionPool(); 173 | 174 | void setConnectionPool(oracle::occi::Environment* environment, 175 | oracle::occi::StatelessConnectionPool* connectionPool); 176 | oracle::occi::Environment* getEnvironment() { 177 | return m_environment; 178 | } 179 | oracle::occi::StatelessConnectionPool* getConnectionPool() { 180 | return m_connectionPool; 181 | } 182 | 183 | private: 184 | oracle::occi::Environment* m_environment; 185 | oracle::occi::StatelessConnectionPool* m_connectionPool; 186 | 187 | friend class ConnectionPoolBaton; 188 | }; 189 | 190 | /** 191 | * Baton for ConnectionPool 192 | */ 193 | class ConnectionPoolBaton { 194 | public: 195 | ConnectionPoolBaton(oracle::occi::Environment* environment, 196 | ConnectionPool* connectionPool, 197 | oracle::occi::StatelessConnectionPool::DestroyMode destroyMode, 198 | const v8::Local& callback) { 199 | this->environment = environment; 200 | this->connectionPool = connectionPool; 201 | this->connectionPool->Ref(); 202 | if (callback.IsEmpty() || callback->IsUndefined()) { 203 | this->callback = NULL; 204 | } else { 205 | this->callback = new Nan::Callback(callback); 206 | } 207 | this->connection = NULL; 208 | this->error = NULL; 209 | this->destroyMode = destroyMode; 210 | } 211 | 212 | ~ConnectionPoolBaton() { 213 | delete error; 214 | delete callback; 215 | } 216 | 217 | oracle::occi::Environment* environment; 218 | ConnectionPool *connectionPool; 219 | oracle::occi::Connection* connection; 220 | Nan::Callback *callback; 221 | std::string *error; 222 | oracle::occi::StatelessConnectionPool::DestroyMode destroyMode; 223 | uv_work_t work_req; 224 | }; 225 | 226 | class ConnectionBaton { 227 | public: 228 | ConnectionBaton(Connection* connection, const v8::Local& callback) { 229 | this->connection = connection; 230 | this->connection->Ref(); 231 | if (callback.IsEmpty() || callback->IsUndefined()) { 232 | this->callback = NULL; 233 | } else { 234 | this->callback = new Nan::Callback(callback); 235 | } 236 | this->error = NULL; 237 | } 238 | ~ConnectionBaton() { 239 | delete error; 240 | delete callback; 241 | } 242 | 243 | Connection *connection; 244 | Nan::Callback *callback; 245 | std::string *error; 246 | uv_work_t work_req; 247 | }; 248 | 249 | #endif 250 | -------------------------------------------------------------------------------- /test/outparams.js: -------------------------------------------------------------------------------- 1 | /* 2 | tests-settings.json: 3 | { 4 | "hostname": "localhost", 5 | "user": "test", 6 | "password": "test" 7 | } 8 | 9 | Database: 10 | CREATE OR REPLACE PROCEDURE procNumericOutParam(param1 IN VARCHAR2, outParam1 OUT NUMBER) 11 | IS 12 | BEGIN 13 | DBMS_OUTPUT.PUT_LINE('Hello '|| param1); 14 | outParam1 := 42; 15 | END; 16 | / 17 | CREATE OR REPLACE PROCEDURE procStringOutParam(param1 IN VARCHAR2, outParam1 OUT STRING) 18 | IS 19 | BEGIN 20 | DBMS_OUTPUT.PUT_LINE('Hello '|| param1); 21 | outParam1 := 'Hello ' || param1; 22 | END; 23 | / 24 | CREATE OR REPLACE PROCEDURE procVarChar2OutParam(param1 IN VARCHAR2, outParam1 OUT VARCHAR2) 25 | IS 26 | BEGIN 27 | DBMS_OUTPUT.PUT_LINE('Hello '|| param1); 28 | outParam1 := 'Hello ' || param1; 29 | END; 30 | / 31 | CREATE OR REPLACE PROCEDURE procDoubleOutParam(param1 IN VARCHAR2, outParam1 OUT DOUBLE PRECISION) 32 | IS 33 | BEGIN 34 | outParam1 := -43.123456789012; 35 | END; 36 | / 37 | CREATE OR REPLACE PROCEDURE procFloatOutParam(param1 IN VARCHAR2, outParam1 OUT FLOAT) 38 | IS 39 | BEGIN 40 | outParam1 := 43; 41 | END; 42 | / 43 | 44 | CREATE OR REPLACE PROCEDURE procTwoOutParams(param1 IN VARCHAR2, outParam1 OUT NUMBER, outParam2 OUT STRING) 45 | IS 46 | BEGIN 47 | outParam1 := 42; 48 | outParam2 := 'Hello ' || param1; 49 | END; 50 | / 51 | CREATE OR REPLACE PROCEDURE procCursorOutParam(outParam OUT SYS_REFCURSOR) 52 | IS 53 | BEGIN 54 | open outParam for 55 | select * from person_test; 56 | END; 57 | / 58 | CREATE OR REPLACE PROCEDURE procCLOBOutParam(outParam OUT CLOB) 59 | IS 60 | BEGIN 61 | outParam := 'IAMCLOB'; 62 | END; 63 | / 64 | 65 | BEGIN 66 | EXECUTE IMMEDIATE 'DROP TABLE basic_lob_table'; 67 | EXCEPTION 68 | WHEN OTHERS THEN 69 | IF SQLCODE != -942 THEN 70 | RAISE; 71 | END IF; 72 | END; 73 | / 74 | 75 | create table basic_lob_table (x varchar2 (30), b blob, c clob); 76 | insert into basic_lob_table values('one', '010101010101010101010101010101', 'onetwothreefour'); 77 | select * from basic_lob_table where x='one' and ROWNUM = 1; 78 | 79 | CREATE OR REPLACE PROCEDURE ReadBasicBLOB (outBlob OUT BLOB) 80 | IS 81 | BEGIN 82 | SELECT b INTO outBlob FROM basic_lob_table where x='one' and ROWNUM = 1; 83 | END; 84 | / 85 | 86 | CREATE OR REPLACE procedure doSquareInteger(z IN OUT Integer) 87 | is 88 | begin 89 | z := z * z; 90 | end; 91 | / 92 | */ 93 | 94 | var settings = require('../tests-settings'); 95 | var oracle = require("../")(settings); 96 | var assert = require('assert'); 97 | 98 | describe('stored procedures with out params', function () { 99 | beforeEach(function (done) { 100 | var self = this; 101 | oracle.connect(settings, function (err, connection) { 102 | if (err) { 103 | done(err); 104 | return; 105 | } 106 | self.connection = connection; 107 | done(); 108 | }); 109 | }); 110 | 111 | afterEach(function (done) { 112 | if (this.connection) { 113 | this.connection.close(); 114 | } 115 | done(); 116 | }); 117 | 118 | it("should support multiple out params", function (done) { 119 | var self = this; 120 | 121 | self.connection.execute("call procTwoOutParams(:1,:2,:3)", 122 | ["node", new oracle.OutParam(oracle.OCCIINT), 123 | new oracle.OutParam(oracle.OCCISTRING)], function (err, results) { 124 | if (err) { 125 | done(err); 126 | return; 127 | } 128 | assert.equal(results.returnParam, 42); 129 | assert.equal(results.returnParam1, "Hello node"); 130 | done(); 131 | }); 132 | }); 133 | 134 | it("should support numeric out param - occiint", function (done) { 135 | var self = this; 136 | self.connection.execute("call procNumericOutParam(:1,:2)", 137 | ["node", new oracle.OutParam()], function (err, results) { 138 | if (err) { 139 | done(err); 140 | return; 141 | } 142 | assert.equal(results.returnParam, 42); 143 | done(); 144 | }); 145 | }); 146 | 147 | it("should support numeric out param - occinumber", function (done) { 148 | var self = this; 149 | self.connection.execute("call procNumericOutParam(:1,:2)", 150 | ["node", new oracle.OutParam(oracle.OCCINUMBER)], function (err, results) { 151 | if (err) { 152 | done(err); 153 | return; 154 | } 155 | assert.equal(results.returnParam, 42); 156 | done(); 157 | }); 158 | }); 159 | 160 | it("should support string out param", function (done) { 161 | var self = this; 162 | self.connection.execute("call procStringOutParam(:1,:2)", 163 | ["node", new oracle.OutParam(oracle.OCCISTRING)], function (err, results) { 164 | if (err) { 165 | done(err); 166 | return; 167 | } 168 | assert.equal(results.returnParam, "Hello node"); 169 | done(); 170 | }); 171 | }); 172 | 173 | it("should support varchar2 out param", function (done) { 174 | var self = this; 175 | self.connection.execute("call procVarChar2OutParam(:1,:2)", 176 | ["node", new oracle.OutParam(oracle.OCCISTRING, {size: 40})], 177 | function (err, results) { 178 | if (err) { 179 | done(err); 180 | return; 181 | } 182 | assert.equal(results.returnParam, "Hello node"); 183 | done(); 184 | }); 185 | }); 186 | 187 | it("should support double out param", function (done) { 188 | var self = this; 189 | self.connection.execute("call procDoubleOutParam(:1,:2)", 190 | ["node", new oracle.OutParam(oracle.OCCIDOUBLE)], function (err, results) { 191 | if (err) { 192 | done(err); 193 | return; 194 | } 195 | assert.equal(results.returnParam, -43.123456789012); 196 | done(); 197 | }); 198 | }); 199 | 200 | it("should support float out param", function (done) { 201 | var self = this; 202 | self.connection.execute("call procFloatOutParam(:1,:2)", 203 | ["node", new oracle.OutParam(oracle.OCCIFLOAT)], function (err, results) { 204 | if (err) { 205 | done(err); 206 | return; 207 | } 208 | assert(Math.abs(results.returnParam - 43) < 0.01); 209 | done(); 210 | }); 211 | }); 212 | 213 | it("should support cursor out param", function (done) { 214 | var self = this; 215 | 216 | self.connection.execute("DELETE FROM person_test", [], function (err, results) { 217 | if (err) { 218 | done(err); 219 | return; 220 | } 221 | 222 | self.connection.execute("call procCursorOutParam(:1)", 223 | [new oracle.OutParam(oracle.OCCICURSOR)], function (err, results) { 224 | if (err) { 225 | done(err); 226 | return; 227 | } 228 | assert.equal(results.returnParam.length, 0); 229 | done(); 230 | }); 231 | }); 232 | }); 233 | 234 | it("should support clob out param", function (done) { 235 | var self = this; 236 | self.connection.execute("call procCLOBOutParam(:1)", 237 | [new oracle.OutParam(oracle.OCCICLOB)], function (err, results) { 238 | if (err) { 239 | done(err); 240 | return; 241 | } 242 | assert.equal(results.returnParam, "IAMCLOB"); 243 | done(); 244 | }); 245 | }); 246 | 247 | it("should support date timestamp out param", function (done) { 248 | var self = this; 249 | self.connection.execute("call procDateTimeOutParam(:1, :2)", 250 | [new oracle.OutParam(oracle.OCCIDATE), 251 | new oracle.OutParam(oracle.OCCITIMESTAMP)], function (err, results) { 252 | if (err) { 253 | done(err); 254 | return; 255 | } 256 | var d = new Date(); 257 | assert.equal(results.returnParam.getFullYear(), d.getFullYear()); 258 | done(); 259 | }); 260 | }); 261 | 262 | it("should support blob out param", function (done) { 263 | var self = this; 264 | self.connection.execute("call ReadBasicBLOB(:1)", 265 | [new oracle.OutParam(oracle.OCCIBLOB)], function (err, results) { 266 | if (err) { 267 | done(err); 268 | return; 269 | } 270 | done(); 271 | }); 272 | }); 273 | 274 | it("should support in out params", function (done) { 275 | var self = this; 276 | self.connection.execute("call doSquareInteger(:1)", 277 | [new oracle.OutParam(oracle.OCCIINT, {in: 5})], function (err, results) { 278 | if (err) { 279 | done(err); 280 | return; 281 | } 282 | assert.equal(results.returnParam, 25); 283 | done(); 284 | }); 285 | }); 286 | 287 | }); 288 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Install 2 | 3 | You need to download and install [Oracle instant client](http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html) from following links: 4 | 5 | http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html 6 | 7 | 1. Instant Client Package - Basic or Basic Lite: All files required to run OCI, OCCI, and JDBC-OCI applications 8 | 2. Instant Client Package - SDK: Additional header files and an example makefile for developing Oracle applications with Instant Client 9 | 10 | For Windows, please make sure 12_1 version is used. 11 | 12 | **Please make sure you download the correct packages for your system architecture, such as 64 bit vs 32 bit** 13 | **Unzip the files 1 and 2 into the same directory, such as /opt/instantclient\_11\_2 or c:\instantclient\_12\_1** 14 | 15 | On MacOS or Linux: 16 | 17 | 1. Set up the following environment variables 18 | 19 | MacOS/Linux: 20 | 21 | export OCI_HOME= 22 | export OCI_LIB_DIR=$OCI_HOME 23 | export OCI_INCLUDE_DIR=$OCI_HOME/sdk/include 24 | 25 | 2. Create the following symbolic links 26 | 27 | MacOS: 28 | 29 | cd $OCI_LIB_DIR 30 | ln -s libclntsh.dylib.11.1 libclntsh.dylib 31 | ln -s libocci.dylib.11.1 libocci.dylib 32 | 33 | Linux: 34 | 35 | cd $OCI_LIB_DIR 36 | ln -s libclntsh.so.12.1 libclntsh.so 37 | ln -s libocci.so.12.1 libocci.so 38 | 39 | `libaio` library is required on Linux systems: 40 | 41 | * On Unbuntu/Debian 42 | 43 | sudo apt-get install libaio1 44 | 45 | * On Fedora/CentOS/RHEL 46 | 47 | sudo yum install libaio 48 | 49 | 3. Configure the dynamic library path 50 | 51 | MacOS: 52 | 53 | export DYLD_LIBRARY_PATH=$OCI_LIB_DIR 54 | 55 | Linux: 56 | 57 | Add the shared object files to the ld cache: 58 | 59 | # Replace /opt/instantclient_11_2/ with wherever you extracted the Basic Lite files to 60 | echo '/opt/instantclient_11_2/' | sudo tee -a /etc/ld.so.conf.d/oracle_instant_client.conf 61 | sudo ldconfig 62 | 63 | On Windows, you need to set the environment variables: 64 | 65 | If you have VisualStudio 2012 installed, 66 | 67 | OCI_INCLUDE_DIR=C:\instantclient_12_1\sdk\include 68 | OCI_LIB_DIR=C:\instantclient_12_1\sdk\lib\msvc\vc11 69 | Path=...;c:\instantclient_12_1\vc11;c:\instantclient_12_1 70 | 71 | **Please make sure c:\instantclient_12_1\vc11 comes before c:\instantclient_12_1** 72 | 73 | If you have VisualStudio 2010 installed, 74 | 75 | OCI_INCLUDE_DIR=C:\instantclient_12_1\sdk\include 76 | OCI_LIB_DIR=C:\instantclient_12_1\sdk\lib\msvc\vc10 77 | Path=...;c:\instantclient_12_1\vc10;c:\instantclient_12_1 78 | 79 | **Please make sure c:\instantclient_12_1\vc10 comes before c:\instantclient_12_1** 80 | 81 | 4. Optionally configure the OCI version 82 | 83 | `strong-oracle` defaults to OCI v12 (v11 on Mac OS X.) To make it use an older 84 | or newer version of the OCI libraries, use the following: 85 | 86 | export GYP_DEFINES="oci_version=11" 87 | 88 | On Windows: 89 | 90 | GYP_DEFINES="oci_version=11" 91 | 92 | # Examples 93 | 94 | The simplest way to connect to the database uses the following code: 95 | 96 | ```javascript 97 | var settings = {}; 98 | var oracle = require("strong-oracle")(settings); 99 | 100 | var connectData = { "hostname": "localhost", "user": "test", "password": "test", "database": "ORCL" }; 101 | 102 | oracle.connect(connectData, function(err, connection) { 103 | ... 104 | connection.close(); // call this when you are done with the connection 105 | }); 106 | }); 107 | ``` 108 | The `database` parameter contains the "service name" or "SID" of the database. If you have an Oracle RAC or some other kind of setup, or if you prefer to use "TNS", you can do so as well. You can either provide a complete connection string, like: 109 | 110 | ```javascript 111 | var connString = 112 | "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=orcl)))"; 113 | ``` 114 | 115 | or just the shortcut as declared in your `tnsnames.ora`: 116 | 117 | DEV = 118 | (DESCRIPTION = 119 | (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521)) 120 | (CONNECT_DATA = 121 | (SERVER = DEDICATED) 122 | (SERVICE_NAME = orcl) 123 | ) 124 | ) 125 | 126 | The connection parameter would then be: 127 | 128 | ```javascript 129 | var connectData = { "tns": "DEV", "user": "test", "password": "test" }; 130 | // or: var connectData = { "tns": connString, "user": "test", "password": "test" }; 131 | ``` 132 | 133 | To access a table you could then use: 134 | 135 | ```javascript 136 | oracle.connect(connData, function(err, connection) { 137 | 138 | or just the shortcut as declared in your `tnsnames.ora`: 139 | 140 | // selecting rows 141 | connection.execute("SELECT * FROM person", [], function(err, results) { 142 | if ( err ) { 143 | console.log(err); 144 | } else { 145 | console.log(results); 146 | 147 | // inserting with return value 148 | connection.execute( 149 | "INSERT INTO person (name) VALUES (:1) RETURNING id INTO :2", 150 | ['joe ferner', new oracle.OutParam()], 151 | function(err, results) { 152 | if ( err ) { console.log(err) } else { 153 | console.log(results); 154 | } 155 | // results.updateCount = 1 156 | // results.returnParam = the id of the person just inserted 157 | connection.close(); // call this when you are done with the connection 158 | } 159 | ); 160 | } 161 | 162 | }); 163 | }); 164 | ``` 165 | 166 | To validate whether the connection is still established after some time: 167 | 168 | ```javascript 169 | if (! connection.isConnected()) { 170 | // retire this connection from a pool 171 | } 172 | ``` 173 | 174 | 175 | ## Out Params 176 | 177 | The following Out Params are supported in Stored Procedures: 178 | 179 | ``` 180 | 181 | OCCIINT 182 | OCCISTRING 183 | OCCIDOUBLE 184 | OCCIFLOAT 185 | OCCICURSOR 186 | OCCICLOB 187 | OCCIDATE 188 | OCCITIMESTAMP 189 | OCCINUMBER 190 | OCCIBLOB 191 | 192 | ``` 193 | 194 | And can be used as follows: 195 | 196 | ``` 197 | 198 | connection.execute("call myProc(:1,:2)", ["nodejs", new oracle.OutParam(oracle.OCCISTRING)], function(err, results){ 199 | console.dir(results); 200 | }; 201 | 202 | ``` 203 | 204 | When using Strings as Out Params, the size can be optionally specified as follows: 205 | 206 | ``` 207 | 208 | connection.execute("call myProc(:1,:2)", ["nodejs", new oracle.OutParam(oracle.OCCISTRING, {size: 1000})], function(err, results){ 209 | 210 | ``` 211 | 212 | If no size is specified, a default size of 200 chars is used. 213 | 214 | See tests for more examples. 215 | 216 | ## In/Out Params 217 | 218 | The following INOUT param types are supported: 219 | 220 | ``` 221 | 222 | OCCIINT 223 | OCCISTRING 224 | OCCIDOUBLE 225 | OCCIFLOAT 226 | OCCINUMBER 227 | 228 | ``` 229 | 230 | INOUT params are used like normal OUT prams, with the optional 'in' paramater value being passed in the options object: 231 | 232 | ``` 233 | 234 | connection.execute("call myProc(:1)", [new oracle.OutParam(oracle.OCCIINT, {in: 42})], function(err, results){ 235 | console.dir(results); 236 | }; 237 | 238 | ``` 239 | 240 | 241 | ## Connection options 242 | 243 | The following options can be set on the connection: 244 | 245 | ``` 246 | connection.setAutoCommit(true/false); 247 | connection.setPrefetchRowCount(count); 248 | ``` 249 | 250 | # Develop 251 | 252 | ## Install Oracle/Oracle Express 253 | 254 | * Download [Oracle Express 10g](http://www.oracle.com/technetwork/database/express-edition/database10gxe-459378.html) 255 | * Download [Instant Client](http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html) 256 | * Instant Client Package - Basic Lite 257 | * Instant Client Package - SQL*Plus 258 | * Instant Client Package - SDK 259 | * Install Oracle Express (Ubuntu) 260 | 261 | ```bash 262 | sudo dpkg -i oracle-xe_11.2.0.3.0-1.0_i386.deb 263 | sudo apt-get install alien 264 | sudo alien oracle-instantclient11.2-* 265 | sudo dpkg -i oracle-instantclient11.2-*.deb 266 | sudo /etc/init.d/oracle-xe configure 267 | ``` 268 | 269 | * Open http://localhost:9999/apex/ change 9999 to the port you configured. Log-in with "sys" and the password. 270 | * Create a user called "test" with password "test" and give all accesses. 271 | 272 | ```bash 273 | sudo vi /etc/ld.so.conf.d/oracle.conf -- add this line /usr/lib/oracle/11.2/client/lib/ 274 | sudo ldconfig 275 | 276 | export ORACLE_SID=test 277 | export ORACLE_HOME=/usr/lib/oracle/xe/app/oracle/product/11.2/server 278 | export OCI_INCLUDE_DIR=/usr/include/oracle/11.2/client/ 279 | export OCI_LIB_DIR=/usr/lib/oracle/11.2/client/lib/ 280 | sqlplus test@XE 281 | ``` 282 | 283 | ## Build 284 | 285 | ```bash 286 | npm install 287 | npm test 288 | ``` 289 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) IBM Corp. 2011,2016. All Rights Reserved. 2 | Node module: strong-oracle 3 | This project is licensed under the Artistic License 2.0, full text below. 4 | 5 | -------- 6 | 7 | The Artistic License 2.0 8 | 9 | Copyright (c) 2000-2006, The Perl Foundation. 10 | 11 | Everyone is permitted to copy and distribute verbatim copies 12 | of this license document, but changing it is not allowed. 13 | 14 | Preamble 15 | 16 | This license establishes the terms under which a given free software 17 | Package may be copied, modified, distributed, and/or redistributed. 18 | The intent is that the Copyright Holder maintains some artistic 19 | control over the development of that Package while still keeping the 20 | Package available as open source and free software. 21 | 22 | You are always permitted to make arrangements wholly outside of this 23 | license directly with the Copyright Holder of a given Package. If the 24 | terms of this license do not permit the full use that you propose to 25 | make of the Package, you should contact the Copyright Holder and seek 26 | a different licensing arrangement. 27 | 28 | Definitions 29 | 30 | "Copyright Holder" means the individual(s) or organization(s) 31 | named in the copyright notice for the entire Package. 32 | 33 | "Contributor" means any party that has contributed code or other 34 | material to the Package, in accordance with the Copyright Holder's 35 | procedures. 36 | 37 | "You" and "your" means any person who would like to copy, 38 | distribute, or modify the Package. 39 | 40 | "Package" means the collection of files distributed by the 41 | Copyright Holder, and derivatives of that collection and/or of 42 | those files. A given Package may consist of either the Standard 43 | Version, or a Modified Version. 44 | 45 | "Distribute" means providing a copy of the Package or making it 46 | accessible to anyone else, or in the case of a company or 47 | organization, to others outside of your company or organization. 48 | 49 | "Distributor Fee" means any fee that you charge for Distributing 50 | this Package or providing support for this Package to another 51 | party. It does not mean licensing fees. 52 | 53 | "Standard Version" refers to the Package if it has not been 54 | modified, or has been modified only in ways explicitly requested 55 | by the Copyright Holder. 56 | 57 | "Modified Version" means the Package, if it has been changed, and 58 | such changes were not explicitly requested by the Copyright 59 | Holder. 60 | 61 | "Original License" means this Artistic License as Distributed with 62 | the Standard Version of the Package, in its current version or as 63 | it may be modified by The Perl Foundation in the future. 64 | 65 | "Source" form means the source code, documentation source, and 66 | configuration files for the Package. 67 | 68 | "Compiled" form means the compiled bytecode, object code, binary, 69 | or any other form resulting from mechanical transformation or 70 | translation of the Source form. 71 | 72 | 73 | Permission for Use and Modification Without Distribution 74 | 75 | (1) You are permitted to use the Standard Version and create and use 76 | Modified Versions for any purpose without restriction, provided that 77 | you do not Distribute the Modified Version. 78 | 79 | 80 | Permissions for Redistribution of the Standard Version 81 | 82 | (2) You may Distribute verbatim copies of the Source form of the 83 | Standard Version of this Package in any medium without restriction, 84 | either gratis or for a Distributor Fee, provided that you duplicate 85 | all of the original copyright notices and associated disclaimers. At 86 | your discretion, such verbatim copies may or may not include a 87 | Compiled form of the Package. 88 | 89 | (3) You may apply any bug fixes, portability changes, and other 90 | modifications made available from the Copyright Holder. The resulting 91 | Package will still be considered the Standard Version, and as such 92 | will be subject to the Original License. 93 | 94 | 95 | Distribution of Modified Versions of the Package as Source 96 | 97 | (4) You may Distribute your Modified Version as Source (either gratis 98 | or for a Distributor Fee, and with or without a Compiled form of the 99 | Modified Version) provided that you clearly document how it differs 100 | from the Standard Version, including, but not limited to, documenting 101 | any non-standard features, executables, or modules, and provided that 102 | you do at least ONE of the following: 103 | 104 | (a) make the Modified Version available to the Copyright Holder 105 | of the Standard Version, under the Original License, so that the 106 | Copyright Holder may include your modifications in the Standard 107 | Version. 108 | 109 | (b) ensure that installation of your Modified Version does not 110 | prevent the user installing or running the Standard Version. In 111 | addition, the Modified Version must bear a name that is different 112 | from the name of the Standard Version. 113 | 114 | (c) allow anyone who receives a copy of the Modified Version to 115 | make the Source form of the Modified Version available to others 116 | under 117 | 118 | (i) the Original License or 119 | 120 | (ii) a license that permits the licensee to freely copy, 121 | modify and redistribute the Modified Version using the same 122 | licensing terms that apply to the copy that the licensee 123 | received, and requires that the Source form of the Modified 124 | Version, and of any works derived from it, be made freely 125 | available in that license fees are prohibited but Distributor 126 | Fees are allowed. 127 | 128 | 129 | Distribution of Compiled Forms of the Standard Version 130 | or Modified Versions without the Source 131 | 132 | (5) You may Distribute Compiled forms of the Standard Version without 133 | the Source, provided that you include complete instructions on how to 134 | get the Source of the Standard Version. Such instructions must be 135 | valid at the time of your distribution. If these instructions, at any 136 | time while you are carrying out such distribution, become invalid, you 137 | must provide new instructions on demand or cease further distribution. 138 | If you provide valid instructions or cease distribution within thirty 139 | days after you become aware that the instructions are invalid, then 140 | you do not forfeit any of your rights under this license. 141 | 142 | (6) You may Distribute a Modified Version in Compiled form without 143 | the Source, provided that you comply with Section 4 with respect to 144 | the Source of the Modified Version. 145 | 146 | 147 | Aggregating or Linking the Package 148 | 149 | (7) You may aggregate the Package (either the Standard Version or 150 | Modified Version) with other packages and Distribute the resulting 151 | aggregation provided that you do not charge a licensing fee for the 152 | Package. Distributor Fees are permitted, and licensing fees for other 153 | components in the aggregation are permitted. The terms of this license 154 | apply to the use and Distribution of the Standard or Modified Versions 155 | as included in the aggregation. 156 | 157 | (8) You are permitted to link Modified and Standard Versions with 158 | other works, to embed the Package in a larger work of your own, or to 159 | build stand-alone binary or bytecode versions of applications that 160 | include the Package, and Distribute the result without restriction, 161 | provided the result does not expose a direct interface to the Package. 162 | 163 | 164 | Items That are Not Considered Part of a Modified Version 165 | 166 | (9) Works (including, but not limited to, modules and scripts) that 167 | merely extend or make use of the Package, do not, by themselves, cause 168 | the Package to be a Modified Version. In addition, such works are not 169 | considered parts of the Package itself, and are not subject to the 170 | terms of this license. 171 | 172 | 173 | General Provisions 174 | 175 | (10) Any use, modification, and distribution of the Standard or 176 | Modified Versions is governed by this Artistic License. By using, 177 | modifying or distributing the Package, you accept this license. Do not 178 | use, modify, or distribute the Package, if you do not accept this 179 | license. 180 | 181 | (11) If your Modified Version has been derived from a Modified 182 | Version made by someone other than you, you are nevertheless required 183 | to ensure that your Modified Version complies with the requirements of 184 | this license. 185 | 186 | (12) This license does not grant you the right to use any trademark, 187 | service mark, tradename, or logo of the Copyright Holder. 188 | 189 | (13) This license includes the non-exclusive, worldwide, 190 | free-of-charge patent license to make, have made, use, offer to sell, 191 | sell, import and otherwise transfer the Package with respect to any 192 | patent claims licensable by the Copyright Holder that are necessarily 193 | infringed by the Package. If you institute patent litigation 194 | (including a cross-claim or counterclaim) against any party alleging 195 | that the Package constitutes direct or contributory patent 196 | infringement, then this Artistic License to you shall terminate on the 197 | date that such litigation is filed. 198 | 199 | (14) Disclaimer of Warranty: 200 | THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS 201 | IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED 202 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR 203 | NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL 204 | LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL 205 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 206 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF 207 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 208 | 209 | 210 | -------- 211 | -------------------------------------------------------------------------------- /test/integration.js: -------------------------------------------------------------------------------- 1 | /* 2 | tests-settings.json: 3 | { 4 | "hostname": "localhost", 5 | "user": "test", 6 | "password": "test" 7 | } 8 | 9 | Database: 10 | CREATE TABLE person_test (id INTEGER PRIMARY KEY, name VARCHAR(255)); 11 | CREATE SEQUENCE person_seq START WITH 1 INCREMENT BY 1 NOMAXVALUE; 12 | CREATE TRIGGER person_pk_trigger BEFORE INSERT ON person_test FOR EACH row 13 | BEGIN 14 | SELECT person_seq.nextval INTO :new.id FROM dual; 15 | END; 16 | / 17 | CREATE TABLE datatype_test ( 18 | id INTEGER PRIMARY KEY, 19 | tvarchar2 VARCHAR2(255), 20 | tnvarchar2 NVARCHAR2(255), 21 | tchar CHAR(255), 22 | tnchar NCHAR(255), 23 | tnumber NUMBER(10,5), 24 | tdate DATE, 25 | ttimestamp TIMESTAMP, 26 | tclob CLOB, 27 | tnclob NCLOB, 28 | tblob BLOB); 29 | CREATE SEQUENCE datatype_test_seq START WITH 1 INCREMENT BY 1 NOMAXVALUE; 30 | CREATE TRIGGER datatype_test_pk_trigger BEFORE INSERT ON datatype_test FOR EACH row 31 | BEGIN 32 | SELECT datatype_test_seq.nextval INTO :new.id FROM dual; 33 | END; 34 | / 35 | */ 36 | 37 | var settings = require('../tests-settings'); 38 | var oracle = require("../")(settings); 39 | var assert = require('assert'); 40 | 41 | describe('oracle driver', function () { 42 | beforeEach(function (done) { 43 | var self = this; 44 | //console.log("connecting: ", settings); 45 | oracle.connect(settings, function (err, connection) { 46 | if (err) { 47 | done(err); 48 | return; 49 | } 50 | //console.log("connected"); 51 | self.connection = connection; 52 | self.connection.execute("DELETE FROM person_test", [], function (err, results) { 53 | if (err) { 54 | done(err); 55 | return; 56 | } 57 | self.connection.execute("DELETE FROM datatype_test", [], function (err, results) { 58 | if (err) { 59 | done(err); 60 | return; 61 | } 62 | //console.log("rows deleted: ", results); 63 | done(); 64 | }); 65 | }); 66 | }); 67 | }); 68 | 69 | afterEach(function (done) { 70 | if (this.connection) { 71 | this.connection.close(); 72 | } 73 | done(); 74 | }); 75 | 76 | it("should select with single quote", function (done) { 77 | var self = this; 78 | self.connection.execute("INSERT INTO person_test (name) VALUES (:1)", ["Bill O'Neil"], function (err, results) { 79 | if (err) { 80 | console.error(err); 81 | return; 82 | } 83 | self.connection.execute("INSERT INTO person_test (name) VALUES (:1)", ["Bob Johnson"], function (err, results) { 84 | if (err) { 85 | console.error(err); 86 | return; 87 | } 88 | self.connection.execute("SELECT * FROM person_test WHERE name = :1", ["Bill O'Neil"], function (err, results) { 89 | if (err) { 90 | console.error(err); 91 | return; 92 | } 93 | //console.log(results); 94 | assert.equal(results.length, 1); 95 | assert.equal(results[0]['NAME'], "Bill O'Neil"); 96 | done(); 97 | }); 98 | }); 99 | }); 100 | }); 101 | 102 | it("should insert with returning value", function (done) { 103 | var self = this; 104 | self.connection.execute("INSERT INTO person_test (name) VALUES (:1) RETURNING id INTO :2", 105 | ["Bill O'Neil", new oracle.OutParam()], function (err, results) { 106 | if (err) { 107 | console.error(err); 108 | return; 109 | } 110 | assert(results.returnParam > 0); 111 | done(); 112 | }); 113 | }); 114 | 115 | it("should support datatypes with valid values", function (done) { 116 | var self = this; 117 | var date1 = new Date(2011, 10, 30, 1, 2, 3); 118 | var date2 = new Date(2011, 11, 1, 1, 2, 3); 119 | self.connection.execute( 120 | "INSERT INTO datatype_test " 121 | + "(tvarchar2, tnvarchar2, tchar, tnchar, tnumber, tdate, ttimestamp, tclob, tnclob, tblob) VALUES " 122 | + "(:1, :2, :3, :4, :5, :6, :7, :8, :9, :10) RETURNING id INTO :11", 123 | [ 124 | "tvarchar2 value", 125 | "tnvarchar2 value", 126 | "tchar value", 127 | "tnchar value", 128 | 42.5, 129 | date1, 130 | date2, 131 | "tclob value", 132 | "tnclob value", 133 | null, //new Buffer("tblob value"), 134 | new oracle.OutParam() 135 | ], 136 | function (err, results) { 137 | if (err) { 138 | console.error(err); 139 | return; 140 | } 141 | assert(results.returnParam > 0); 142 | 143 | self.connection.execute("SELECT * FROM datatype_test", [], function (err, results) { 144 | if (err) { 145 | console.error(err); 146 | return; 147 | } 148 | assert.equal(results.length, 1); 149 | assert.equal(results[0]['TVARCHAR2'], "tvarchar2 value"); 150 | assert.equal(results[0]['TNVARCHAR2'], "tnvarchar2 value"); 151 | assert.equal(results[0]['TCHAR'], "tchar value "); 152 | assert.equal(results[0]['TNCHAR'], "tnchar value "); 153 | assert.equal(results[0]['TNUMBER'], 42.5); 154 | assert.equal(results[0]['TDATE'].getTime(), date1.getTime()); 155 | assert.equal(results[0]['TTIMESTAMP'].getTime(), date2.getTime()); 156 | assert.equal(results[0]['TCLOB'], "tclob value"); 157 | assert.equal(results[0]['TNCLOB'], "tnclob value"); 158 | // todo: assert.equal(results[0]['TBLOB'], null); 159 | done(); 160 | }); 161 | }); 162 | }); 163 | 164 | it("should support datatypes with null values", function (done) { 165 | var self = this; 166 | self.connection.execute( 167 | "INSERT INTO datatype_test " 168 | + "(tvarchar2, tnvarchar2, tchar, tnchar, tnumber, tdate, ttimestamp, tclob, tnclob, tblob) VALUES " 169 | + "(:1, :2, :3, :4, :5, :6, :7, :8, :9, :10) RETURNING id INTO :11", 170 | [ 171 | null, 172 | null, 173 | null, 174 | null, 175 | null, 176 | null, 177 | null, 178 | null, 179 | null, 180 | null, 181 | new oracle.OutParam() 182 | ], 183 | function (err, results) { 184 | if (err) { 185 | console.error(err); 186 | return; 187 | } 188 | assert(results.returnParam > 0); 189 | 190 | self.connection.execute("SELECT * FROM datatype_test", [], function (err, results) { 191 | if (err) { 192 | console.error(err); 193 | return; 194 | } 195 | assert.equal(results.length, 1); 196 | assert.equal(results[0]['TVARCHAR2'], null); 197 | assert.equal(results[0]['TNVARCHAR2'], null); 198 | assert.equal(results[0]['TCHAR'], null); 199 | assert.equal(results[0]['TNCHAR'], null); 200 | assert.equal(results[0]['TNUMBER'], null); 201 | assert.equal(results[0]['TDATE'], null); 202 | assert.equal(results[0]['TTIMESTAMP'], null); 203 | assert.equal(results[0]['TCLOB'], null); 204 | assert.equal(results[0]['TNCLOB'], null); 205 | assert.equal(results[0]['TBLOB'], null); 206 | done(); 207 | }); 208 | }); 209 | }); 210 | 211 | it("should support date_types_insert_milliseconds", function (done) { 212 | var self = this, 213 | date = new Date(2013, 11, 24, 1, 2, 59, 999); 214 | 215 | self.connection.execute( 216 | "INSERT INTO datatype_test (tdate, ttimestamp) VALUES (:1, :2)", 217 | [date, date], 218 | function (err, results) { 219 | if (err) { 220 | console.error(err); 221 | return; 222 | } 223 | assert(results.updateCount === 1); 224 | 225 | self.connection.execute("SELECT tdate, ttimestamp FROM datatype_test", 226 | [], function (err, results) { 227 | if (err) { 228 | console.error(err); 229 | return; 230 | } 231 | assert.equal(results.length, 1); 232 | var dateWithoutMs = new Date(date); 233 | dateWithoutMs.setMilliseconds(0); 234 | assert.equal(results[0]['TDATE'].getTime(), dateWithoutMs.getTime(), 235 | "Milliseconds should not be stored for DATE"); 236 | assert.equal(results[0]['TTIMESTAMP'].getTime(), date.getTime(), 237 | "Milliseconds should be stored for TIMESTAMP"); 238 | done(); 239 | }); 240 | }); 241 | }); 242 | 243 | it("should support select with getColumnMetaData", function(done) { 244 | var self = this; 245 | self.connection.execute("INSERT INTO person_test (name) VALUES (:1)", ["Bill O'Neil"], function(err, results) { 246 | if(err) { console.error(err); return; } 247 | self.connection.execute("SELECT * FROM person_test", [], {getColumnMetaData:true}, function(err, results) { 248 | if(err) { console.error(err); return; } 249 | assert.equal(results.length, 1); 250 | assert.deepEqual(results.columnMetaData, [ { name: 'ID', type: 4 }, { name: 'NAME', type: 3 } ]); 251 | self.connection.execute("SELECT * FROM datatype_test", [], {getColumnMetaData:true}, function(err, results) { 252 | if(err) { console.error(err); return; } 253 | assert.equal(results.length, 0); 254 | assert.deepEqual(results.columnMetaData, 255 | [ { name: 'ID', type: 4 }, 256 | { name: 'TVARCHAR2', type: 3 }, 257 | { name: 'TNVARCHAR2', type: 3 }, 258 | { name: 'TCHAR', type: 3 }, 259 | { name: 'TNCHAR', type: 3 }, 260 | { name: 'TNUMBER', type: 4 }, 261 | { name: 'TDATE', type: 5 }, 262 | { name: 'TTIMESTAMP', type: 6 }, 263 | { name: 'TCLOB', type: 7 }, 264 | { name: 'TNCLOB', type: 7 }, 265 | { name: 'TBLOB', type: 8 } ]); 266 | done(); 267 | }); 268 | }); 269 | }); 270 | }); 271 | 272 | // FIXME: [rfeng] Investigate why the following test is failing 273 | /* 274 | it("should support utf8_chars_in_query", function (done) { 275 | var self = this, 276 | cyrillicString = "тест"; 277 | 278 | self.connection.execute("SELECT '" + cyrillicString + "' as test FROM DUAL", 279 | [], function (err, results) { 280 | if (err) { 281 | console.error(err); 282 | return; 283 | } 284 | assert.equal(results.length, 1); 285 | assert.equal(results[0]['TEST'], cyrillicString, 286 | "UTF8 characters in sql query should be preserved"); 287 | done(); 288 | }); 289 | }); 290 | */ 291 | }); 292 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | 2016-06-03, Version 1.9.0 2 | ========================= 3 | 4 | * Remove segfault-handler (Sam Roberts) 5 | 6 | * update copyright notices and license (Ryan Graham) 7 | 8 | * Fix issue with gcc5 and occi based on gcc4 (Vincent Schoettke) 9 | 10 | * Added basic LONG RAW support. (Paul Kehle) 11 | 12 | 13 | 2016-02-02, Version 1.8.1 14 | ========================= 15 | 16 | * Fix the assertion (Raymond Feng) 17 | 18 | * instant client v12 is now out on ppc64 (elkorep) 19 | 20 | 21 | 2015-12-17, Version 1.8.0 22 | ========================= 23 | 24 | * check for ppc64 architecture (elkorep) 25 | 26 | 27 | 2015-09-25, Version 1.7.0 28 | ========================= 29 | 30 | * Fix windows build issue (Raymond Feng) 31 | 32 | * Upgrade to nan 2.0.x (Raymond Feng) 33 | 34 | * Fix the demo host name (Raymond Feng) 35 | 36 | * deps: make segfault-handler optional instead of dev (Ryan Graham) 37 | 38 | * load test DB/schema as pretest task (Ryan Graham) 39 | 40 | * test: allow tests to use CI provided DB (Ryan Graham) 41 | 42 | * Update oracle.js (kathan) 43 | 44 | 45 | 2015-07-15, Version 1.6.5 46 | ========================= 47 | 48 | * fix initialization order of date (Vincent Schoettke) 49 | 50 | * fixed crash when reading clobs with multi byte characters (Vincent Schoettke) 51 | 52 | 53 | 2015-05-15, Version 1.6.4 54 | ========================= 55 | 56 | * Update nan deps to work with iojs 2.0 (Raymond Feng) 57 | 58 | 59 | 2015-05-15, Version 1.6.3 60 | ========================= 61 | 62 | * Fix the prefetch count (Raymond Feng) 63 | 64 | 65 | 2015-05-04, Version 1.6.2 66 | ========================= 67 | 68 | * Add back prefetchRowCount settings (Raymond Feng) 69 | 70 | * Update linux instruction (Raymond Feng) 71 | 72 | 73 | 2015-04-10, Version 1.6.1 74 | ========================= 75 | 76 | * Update .gitignore (Raymond Feng) 77 | 78 | * Restore auto commit control (Raymond Feng) 79 | 80 | 81 | 2015-03-26, Version 1.6.0 82 | ========================= 83 | 84 | * Change the table name to avoid conflicts (Raymond Feng) 85 | 86 | * Add ConnectionPool.execute (Raymond Feng) 87 | 88 | 89 | 2015-03-24, Version 1.5.1 90 | ========================= 91 | 92 | * Update deps (Raymond Feng) 93 | 94 | 95 | 2015-03-24, Version 1.5.0 96 | ========================= 97 | 98 | 99 | 100 | 2015-03-24, Version 1.4.0 101 | ========================= 102 | 103 | * Enable Segfault handler (Raymond Feng) 104 | 105 | * Fixed invalid freeing of memory. Added type cast for buffer length (Vincent Schoettke) 106 | 107 | * allow for undefined statement parameters (richlim) 108 | 109 | * apply prefetch row count to every result set (richlim) 110 | 111 | * Fix the module name (Raymond Feng) 112 | 113 | 114 | 2015-01-22, Version 1.3.0 115 | ========================= 116 | 117 | * Update deps (Raymond Feng) 118 | 119 | * rm test file extra_file.txt (Eric Waldheim) 120 | 121 | * add optional 3rd argument "options" to connection.execute. Process "options" argument for getColumnMetaData flag. Return result.columnMetaData (Eric Waldheim) 122 | 123 | * adding a temporary file (Benjamin Clark) 124 | 125 | * Fix bad CLA URL in CONTRIBUTING.md (Ryan Graham) 126 | 127 | 128 | 2014-11-20, Version 1.2.2 129 | ========================= 130 | 131 | * Bump version (Raymond Feng) 132 | 133 | * Update samples (Raymond Feng) 134 | 135 | * Fix GC issue around ConnectionPool/Connection (Raymond Feng) 136 | 137 | * Fix README (Raymond Feng) 138 | 139 | * Update contribution guidelines (Ryan Graham) 140 | 141 | 142 | 2014-09-25, Version 1.2.1 143 | ========================= 144 | 145 | * Bump version (Raymond Feng) 146 | 147 | * build: make oci version configurable (Ben Noordhuis) 148 | 149 | * build: fix whitespace errors in binding.gyp (Ben Noordhuis) 150 | 151 | * Upgrade to nan 1.3.0 (Raymond Feng) 152 | 153 | * Fixed compilation with VS2012 and node 0.11.13 (Vincent Schoettke) 154 | 155 | 156 | 2014-05-22, Version 1.2.0 157 | ========================= 158 | 159 | * doc: add CONTRIBUTING.md and LICENSE.md (Ben Noordhuis) 160 | 161 | * Remove the status arg (Raymond Feng) 162 | 163 | * Tidy up the destruction of Connection to respect the pool (Raymond Feng) 164 | 165 | * Fix LICENSE (Raymond Feng) 166 | 167 | * src: ref and unref connection in ExecuteBaton (Ben Noordhuis) 168 | 169 | * src: RAII-ify refcount management (Ben Noordhuis) 170 | 171 | * src: fix memory leak in callback-less close() methods (Ben Noordhuis) 172 | 173 | * src: pass values array by value (Ben Noordhuis) 174 | 175 | * src: deduplicate ExecuteBaton constructors (Ben Noordhuis) 176 | 177 | * src: embed NanCallback in ConnectBaton, don't new (Ben Noordhuis) 178 | 179 | * src: CONTAINER_OF-ize StatementBaton (Ben Noordhuis) 180 | 181 | * src: CONTAINER_OF-ize ReaderBaton (Ben Noordhuis) 182 | 183 | * src: CONTAINER_OF-ize ExecuteBaton (Ben Noordhuis) 184 | 185 | * src: CONTAINER_OF-ize ConnectionPoolBaton (Ben Noordhuis) 186 | 187 | * src: CONTAINER_OF-ize ConnectionBaton (Ben Noordhuis) 188 | 189 | * src: CONTAINER_OF-ize ConnectBaton (Ben Noordhuis) 190 | 191 | * tools: add valgrind suppression file for libocci (Ben Noordhuis) 192 | 193 | * Use NanNew to be compatible with v0.11.13 (Raymond Feng) 194 | 195 | * Fix the sql for tests (Raymond Feng) 196 | 197 | * Tidy up the code based on Ben's comments (Raymond Feng) 198 | 199 | * Fix test cases (Raymond Feng) 200 | 201 | * Bump version (Raymond Feng) 202 | 203 | * Restore char form setting for clobs (Raymond Feng) 204 | 205 | * Fix memory management related issues (Raymond Feng) 206 | 207 | * Move reading LOBs out of the callback thread (Raymond Feng) 208 | 209 | * Make ConnectionPool/Connection.close() async (Raymond Feng) 210 | 211 | * Add demo ldap server (Raymond Feng) 212 | 213 | * Enable LDAP naming method (Raymond Feng) 214 | 215 | * Upgrade to NAN 1.0.0 (Raymond Feng) 216 | 217 | 218 | 2014-03-31, Version 1.1.4 219 | ========================= 220 | 221 | * Bump version (Raymond Feng) 222 | 223 | * Catch exceptions during destructor (Raymond Feng) 224 | 225 | * Fix the compiler flag to enable C++ exception catch (Raymond Feng) 226 | 227 | 228 | 2014-03-29, Version 1.1.3 229 | ========================= 230 | 231 | * Bump version (Raymond Feng) 232 | 233 | * Fix date/timestamp handling (Raymond Feng) 234 | 235 | * Fix the prepare/reader wrappers (Raymond Feng) 236 | 237 | * Add more tests from upstream (Raymond Feng) 238 | 239 | * Fix the callback function name (Raymond Feng) 240 | 241 | * Convert to mocha test cases (Raymond Feng) 242 | 243 | 244 | 2014-03-26, Version 1.1.2 245 | ========================= 246 | 247 | * Bump version (Raymond Feng) 248 | 249 | * Add support for statement cache size (Raymond Feng) 250 | 251 | 252 | 2014-03-26, Version 1.1.1 253 | ========================= 254 | 255 | * Bump version (Raymond Feng) 256 | 257 | * Fix the CLOB reading issue (Raymond Feng) 258 | 259 | 260 | 2014-03-25, Version 1.1.0 261 | ========================= 262 | 263 | * Bump version (Raymond Feng) 264 | 265 | * Use a plain C struct to pass OutParm (Raymond Feng) 266 | 267 | * Copy the buffer data (Raymond Feng) 268 | 269 | * Remove using namespace v8 (Raymond Feng) 270 | 271 | * Use As() for the cast (Raymond Feng) 272 | 273 | * Use NAN to wrap V8 APIs (Raymond Feng) 274 | 275 | * Update .gitignore (Raymond Feng) 276 | 277 | * Reformat the code and add more comments (Raymond Feng) 278 | 279 | 280 | 2014-03-21, Version 1.0.0 281 | ========================= 282 | 283 | * Add an option to close connection pool (Raymond Feng) 284 | 285 | * Make require more flexible (Raymond Feng) 286 | 287 | * Add license (Raymond Feng) 288 | 289 | * Bump version (Raymond Feng) 290 | 291 | * Add emulated snprintf() for Windows (Raymond Feng) 292 | 293 | * Replace the insertion operator with snprintf (Raymond Feng) 294 | 295 | * Return the pool (Raymond Feng) 296 | 297 | * Remove the exception to favor the error attr (Raymond Feng) 298 | 299 | * Cherry-pick setPrefetchRowCount from nearinfinity/node-oracle (Raymond Feng) 300 | 301 | * Catch std exceptions (Raymond Feng) 302 | 303 | * Tidy up package.json (Raymond Feng) 304 | 305 | * Extract the sql (Raymond Feng) 306 | 307 | * Add a sample app (Raymond Feng) 308 | 309 | * Clean up code (Raymond Feng) 310 | 311 | * Add async version of getConnection (Raymond Feng) 312 | 313 | * Add getInfo to connection pool (Raymond Feng) 314 | 315 | * Start to add connection pool support (Raymond Feng) 316 | 317 | * Fix the merge problem (Raymond Feng) 318 | 319 | * Use different version for linux build (Raymond Feng) 320 | 321 | * Update README to add instructions to install libaio (Raymond Feng) 322 | 323 | * Update README (Raymond Feng) 324 | 325 | * Update package.json (Raymond Feng) 326 | 327 | * Update binding.gyp to use Oracle instance client for Windows 12_1 version (Raymond Feng) 328 | 329 | * Catch unknown exceptions (Raymond Feng) 330 | 331 | * More HandleScope close (Raymond Feng) 332 | 333 | * Convert connect_baton_t to a class (Raymond Feng) 334 | 335 | * Add HandleScope scope (Raymond Feng) 336 | 337 | * Use delete[] with new[] (Sam Roberts) 338 | 339 | * Introduced HandleScope to cleanup memory properly, fixed other minor memory leaks (Andrei Karpushonak) 340 | 341 | * Documentation for usage of "tns" connection parameter (Victor Volle) 342 | 343 | * Make sure the vars are initialized (Raymond Feng) 344 | 345 | * Initialize the properties to provide default values (Raymond Feng) 346 | 347 | * Throw exception if the job cannot be queued (Raymond Feng) 348 | 349 | * Clean up the handle scopes (Raymond Feng) 350 | 351 | * Fix OutParam destruction issue (Raymond Feng) 352 | 353 | * Check the args length (Raymond Feng) 354 | 355 | * Start to explore occi connection pool (Raymond Feng) 356 | 357 | * Remove std prefix (Raymond Feng) 358 | 359 | * Fix the message concatation (Raymond Feng) 360 | 361 | * Throw exception (Raymond Feng) 362 | 363 | * Add sync methods for connect and execute (Raymond Feng) 364 | 365 | * Find a way to get windows working (Raymond Feng) 366 | 367 | * Assign floating pointers to NULL, for safety (Sam Roberts) 368 | 369 | * Convert oracle fractional seconds to milliseconds and fix typo in setUTCMilliseconds. (John Coulter) 370 | 371 | * Fix memory leak on throw (mscdex) 372 | 373 | * Handle timestamp with timezone data in the result set. Done by using additional OCI_TYPECODE constants from oro.h. See the occi programmer's guide ch. 5 on datatypes: http://docs.oracle.com/cd/B19306_01/appdev.102/b14294/types.htm#sthref434 (John Coulter) 374 | 375 | * Fixed CLOB UTF8 bug where value would be truncated and padded with null characters when multi-byte charset is set. (John Coulter) 376 | 377 | 378 | 2013-07-22, Version 0.3.2 379 | ========================= 380 | 381 | * Add v8::TryCatch calls to handle exceptions in function callbacks (John Coulter) 382 | 383 | * Implement connection.isConnected(), don't segfault when doing work on closed connection (Michael Olson) 384 | 385 | * Documentation for usage of "tns" connection parameter (Victor Volle) 386 | 387 | * Support for TNS strings (Michael Friese) 388 | 389 | 390 | 2013-05-30, Version 0.3.1 391 | ========================= 392 | 393 | * Introduced HandleScope to cleanup memory properly, fixed other minor memory leaks (Andrei Karpushonak) 394 | 395 | * Add detailed instructions for installing the oracle instant client, including notes about the default directories that the binding.gyp looking for. (John Coulter) 396 | 397 | * Provide somewhat sane defaults for OCI_INCLUDE_DIR and OCI_LIB_DIR (oracle instant client directories) on windows. (John Coulter) 398 | 399 | * Properly resolve merge conflict between 5e9ed4a4665751becc865dcd800fcd533875a0e1 and f60745e3e5a464539ba184082c1dcd8fc20d1270 (the variables have now been moved to within OS specific conditions so that they can be resolved correctly on each platform.) (John Coulter) 400 | 401 | 402 | 2013-05-03, Version 0.3.0 403 | ========================= 404 | 405 | * fix merge conflict (Joe Ferner) 406 | 407 | * Added default values for oci_include_dir and oci_lib_dir variables in binding.gyp (so if you don't have OCCI_INCLUDE_DIR and OCCI_LIB_DIR set in your environment, the default oracle locations will be used.. (Damian Beresford) 408 | 409 | * Fix for setting default string size, 'outputs' destructed properly, & misc minor tidyup (Damian Beresford) 410 | 411 | * Minor update to README (Damian Beresford) 412 | 413 | * Support for INOUT types in stored procedures. Split tests into two files. Minor refactoring (Damian Beresford) 414 | 415 | * Support for unstreamed blobs in node-oracle (both in coulmns and as OUT params) (Damian Beresford) 416 | 417 | * Added out param support for OCCIDATE, OCCITIMESTAMP, OCCINUMBER (Damian Beresford) 418 | 419 | * Documentation updated in README.md. (Damian Beresford) 420 | 421 | * Minor refactoring: renamed variables in output_t (Damian Beresford) 422 | 423 | * Support for CLOB as out param (Damian Beresford) 424 | 425 | * Support for OCCICURSOR return type (Damian Beresford) 426 | 427 | * Support for multiple out params in stored procedures (Damian Beresford) 428 | 429 | * Support for node 0.8.x (and 0.10.x) (Damian Beresford) 430 | 431 | * Ticket 3818 Support for more OCCI types in node-oracle: doubles and floats now supported (Damian Beresford) 432 | 433 | * Support for both OCCIINT and OCCISTRING out params in stored procedures (Damian Beresford) 434 | 435 | * Get the result set after execute() (Raymond Feng) 436 | 437 | * Fix the gyp file so that it works on Windows (Raymond Feng) 438 | 439 | * Fix the date/timestamp conversion (Raymond Feng) 440 | 441 | * Enable the gyp build Upgrade to support node v0.10.x Fix the clob char form set issue to avoid addon 'abort' (Raymond Feng) 442 | 443 | * Added CLOB support (Danny Brain) 444 | 445 | * switch to utf8 in insert and select (Victor Volle) 446 | 447 | 448 | 2012-12-03, Version 0.2.0 449 | ========================= 450 | 451 | * version 0.2.0 (Joe Ferner) 452 | 453 | * now compiles and runs with node 0.8.8 (Romain Bossart) 454 | 455 | * [fix] path.existsSync was moved to fs.existsSync (Farrin Reid) 456 | 457 | 458 | 2012-06-22, Version 0.1.3 459 | ========================= 460 | 461 | * First release! 462 | -------------------------------------------------------------------------------- /src/oracle_bindings.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "oracle_bindings.h" 3 | #include "connection.h" 4 | #include "outParam.h" 5 | #include "reader.h" 6 | #include "statement.h" 7 | #include 8 | 9 | Nan::Persistent OracleClient::s_ct; 10 | 11 | // ConnectBaton implementation 12 | ConnectBaton::ConnectBaton(OracleClient* client, 13 | oracle::occi::Environment* environment, 14 | v8::Local callback) : 15 | callback(callback) { 16 | this->client = client; 17 | this->environment = environment; 18 | this->error = NULL; 19 | this->connection = NULL; 20 | this->connectionPool = NULL; 21 | 22 | this->hostname = "127.0.0.1"; 23 | this->user = ""; 24 | this->password = ""; 25 | this->database = ""; 26 | 27 | this->port = 1521; 28 | this->minConn = 0; 29 | this->maxConn = 4; 30 | this->incrConn = 1; 31 | this->timeout =0; 32 | this->busyOption = oracle::occi::StatelessConnectionPool::WAIT; 33 | this->stmtCacheSize = 0; 34 | } 35 | 36 | ConnectBaton::~ConnectBaton() { 37 | if(error) { 38 | delete error; 39 | } 40 | } 41 | 42 | // OracleClient implementation 43 | OracleClient::OracleClient(oracle::occi::Environment::Mode mode) { 44 | m_environment = oracle::occi::Environment::createEnvironment(mode); 45 | } 46 | 47 | OracleClient::~OracleClient() { 48 | oracle::occi::Environment::terminateEnvironment(m_environment); 49 | } 50 | 51 | void OracleClient::Init(Handle target) { 52 | Nan::HandleScope scope; 53 | 54 | Local t = Nan::New(New); 55 | OracleClient::s_ct.Reset( t); 56 | t->InstanceTemplate()->SetInternalFieldCount(1); 57 | t->SetClassName(Nan::New("OracleClient").ToLocalChecked()); 58 | 59 | Nan::SetPrototypeMethod(t, "connect", Connect); 60 | Nan::SetPrototypeMethod(t, "connectSync", ConnectSync); 61 | Nan::SetPrototypeMethod(t, "createConnectionPoolSync", CreateConnectionPoolSync); 62 | Nan::SetPrototypeMethod(t, "createConnectionPool", CreateConnectionPool); 63 | 64 | target->Set(Nan::New("OracleClient").ToLocalChecked(), t->GetFunction()); 65 | } 66 | 67 | NAN_METHOD(OracleClient::New) { 68 | Nan::HandleScope scope; 69 | 70 | OracleClient *client = NULL; 71 | if(info.Length() == 0) { 72 | client = new OracleClient(); 73 | } else { 74 | client = new OracleClient(oracle::occi::Environment::USE_LDAP); 75 | } 76 | client->Wrap(info.This()); 77 | if(info.Length() > 0) { 78 | REQ_OBJECT_ARG(0, ldap); 79 | std::string adminContext, host, user, password; 80 | int port; 81 | OBJ_GET_STRING(ldap, "adminContext", adminContext); 82 | OBJ_GET_STRING(ldap, "host", host); 83 | OBJ_GET_NUMBER(ldap, "port", port, 389); 84 | OBJ_GET_STRING(ldap, "user", user); 85 | OBJ_GET_STRING(ldap, "password", password); 86 | client->getEnvironment()->setLDAPAdminContext(adminContext); 87 | client->getEnvironment()->setLDAPAuthentication(0x1); 88 | client->getEnvironment()->setLDAPHostAndPort(host, port); 89 | client->getEnvironment()->setLDAPLoginNameAndPassword(user, password); 90 | } 91 | info.GetReturnValue().Set(info.This()); 92 | } 93 | 94 | 95 | NAN_METHOD(OracleClient::Connect) { 96 | Nan::HandleScope scope; 97 | 98 | REQ_OBJECT_ARG(0, settings); 99 | REQ_FUN_ARG(1, callback); 100 | 101 | OracleClient* client = Nan::ObjectWrap::Unwrap(info.This()); 102 | ConnectBaton* baton = new ConnectBaton(client, client->m_environment, callback); 103 | 104 | OBJ_GET_STRING(settings, "hostname", baton->hostname); 105 | OBJ_GET_STRING(settings, "user", baton->user); 106 | OBJ_GET_STRING(settings, "password", baton->password); 107 | OBJ_GET_STRING(settings, "database", baton->database); 108 | OBJ_GET_NUMBER(settings, "port", baton->port, 1521); 109 | OBJ_GET_STRING(settings, "tns", baton->tns); 110 | OBJ_GET_NUMBER(settings, "stmtCacheSize", baton->stmtCacheSize, 16); 111 | 112 | uv_queue_work(uv_default_loop(), 113 | &baton->work_req, 114 | EIO_Connect, 115 | (uv_after_work_cb) EIO_AfterConnect); 116 | 117 | return; 118 | } 119 | 120 | void OracleClient::EIO_Connect(uv_work_t* req) { 121 | ConnectBaton* baton = CONTAINER_OF(req, ConnectBaton, work_req); 122 | 123 | baton->error = NULL; 124 | 125 | try { 126 | char connectionStr[512]; 127 | if (baton->tns != "") { 128 | snprintf(connectionStr, sizeof(connectionStr), "%s", baton->tns.c_str()); 129 | } else { 130 | snprintf(connectionStr, sizeof(connectionStr), "//%s:%d/%s", baton->hostname.c_str(), baton->port, baton->database.c_str()); 131 | } 132 | std::string connStr = std::string(connectionStr); 133 | baton->connection = baton->environment->createConnection(baton->user, baton->password, connStr); 134 | baton->connection->setStmtCacheSize(baton->stmtCacheSize); 135 | } catch(oracle::occi::SQLException &ex) { 136 | baton->error = new std::string(ex.getMessage()); 137 | } catch (const std::exception& ex) { 138 | baton->error = new std::string(ex.what()); 139 | } 140 | catch (...) { 141 | baton->error = new std::string("Unknown exception thrown from OCCI"); 142 | } 143 | } 144 | 145 | void OracleClient::EIO_AfterConnect(uv_work_t* req) { 146 | Nan::HandleScope scope; 147 | ConnectBaton* baton = CONTAINER_OF(req, ConnectBaton, work_req); 148 | 149 | Local argv[2]; 150 | if(baton->error) { 151 | argv[0] = Exception::Error(Nan::New(baton->error->c_str()).ToLocalChecked()); 152 | argv[1] = Nan::Undefined(); 153 | } else { 154 | argv[0] = Nan::Undefined(); 155 | Local ft = Nan::New(Connection::s_ct); 156 | Local connection = ft->GetFunction()->NewInstance(); 157 | (Nan::ObjectWrap::Unwrap(connection))->setConnection(baton->environment, NULL, baton->connection); 158 | argv[1] = connection; 159 | } 160 | 161 | Nan::TryCatch tryCatch; 162 | 163 | baton->callback.Call(2, argv); 164 | delete baton; 165 | 166 | if (tryCatch.HasCaught()) { 167 | Nan::FatalException(tryCatch); 168 | } 169 | } 170 | 171 | NAN_METHOD(OracleClient::ConnectSync) { 172 | Nan::HandleScope scope; 173 | 174 | REQ_OBJECT_ARG(0, settings); 175 | 176 | OracleClient* client = Nan::ObjectWrap::Unwrap(info.This()); 177 | ConnectBaton baton(client, client->m_environment); 178 | 179 | OBJ_GET_STRING(settings, "hostname", baton.hostname); 180 | OBJ_GET_STRING(settings, "user", baton.user); 181 | OBJ_GET_STRING(settings, "password", baton.password); 182 | OBJ_GET_STRING(settings, "database", baton.database); 183 | OBJ_GET_NUMBER(settings, "port", baton.port, 1521); 184 | OBJ_GET_NUMBER(settings, "stmtCacheSize", baton.stmtCacheSize, 16); 185 | 186 | try { 187 | char connectionStr[512]; 188 | snprintf(connectionStr, sizeof(connectionStr), "//%s:%d/%s", baton.hostname.c_str(), baton.port, baton.database.c_str()); 189 | std::string connStr = std::string(connectionStr); 190 | baton.connection = baton.environment->createConnection(baton.user, baton.password, connStr); 191 | baton.connection->setStmtCacheSize(baton.stmtCacheSize); 192 | } catch(oracle::occi::SQLException &ex) { 193 | return Nan::ThrowError(ex.getMessage().c_str()); 194 | } catch (const std::exception& ex) { 195 | return Nan::ThrowError(ex.what()); 196 | } 197 | 198 | Local ft = Nan::New(Connection::s_ct); 199 | Local connection = ft->GetFunction()->NewInstance(); 200 | 201 | (Nan::ObjectWrap::Unwrap(connection))->setConnection(baton.environment, NULL, baton.connection); 202 | 203 | info.GetReturnValue().Set(connection); 204 | 205 | } 206 | 207 | 208 | NAN_METHOD(OracleClient::CreateConnectionPoolSync) { 209 | Nan::HandleScope scope; 210 | REQ_OBJECT_ARG(0, settings); 211 | 212 | OracleClient* client = Nan::ObjectWrap::Unwrap(info.This()); 213 | 214 | std::string hostname, user, password, database, tns; 215 | unsigned int port, minConn, maxConn, incrConn, timeout, busyOption, stmtCacheSize; 216 | 217 | OBJ_GET_STRING(settings, "hostname", hostname); 218 | OBJ_GET_STRING(settings, "user", user); 219 | OBJ_GET_STRING(settings, "password", password); 220 | OBJ_GET_STRING(settings, "database", database); 221 | OBJ_GET_NUMBER(settings, "port", port, 1521); 222 | OBJ_GET_NUMBER(settings, "minConn", minConn, 0); 223 | OBJ_GET_NUMBER(settings, "maxConn", maxConn, 4); 224 | OBJ_GET_NUMBER(settings, "incrConn", incrConn, 1); 225 | OBJ_GET_NUMBER(settings, "timeout", timeout, 0); 226 | OBJ_GET_NUMBER(settings, "busyOption", busyOption, 0); 227 | OBJ_GET_STRING(settings, "tns", tns); 228 | OBJ_GET_NUMBER(settings, "stmtCacheSize", stmtCacheSize, 16); 229 | 230 | try { 231 | char connectionStr[512]; 232 | if (tns != "") { 233 | snprintf(connectionStr, sizeof(connectionStr), "%s", tns.c_str()); 234 | } else { 235 | snprintf(connectionStr, sizeof(connectionStr), "//%s:%d/%s", hostname.c_str(), port, database.c_str()); 236 | } 237 | std::string connStr = std::string(connectionStr); 238 | oracle::occi::StatelessConnectionPool *scp = client->m_environment->createStatelessConnectionPool( 239 | user, password, connStr, maxConn, 240 | minConn, incrConn, 241 | oracle::occi::StatelessConnectionPool::HOMOGENEOUS); 242 | scp->setTimeOut(timeout); 243 | scp->setBusyOption(static_cast(busyOption)); 244 | scp->setStmtCacheSize(stmtCacheSize); 245 | 246 | Local ft = Nan::New(ConnectionPool::s_ct); 247 | Local connectionPool = ft->GetFunction()->NewInstance(); 248 | (Nan::ObjectWrap::Unwrap(connectionPool))->setConnectionPool(client->m_environment, scp); 249 | 250 | info.GetReturnValue().Set(connectionPool); 251 | } catch(oracle::occi::SQLException &ex) { 252 | return Nan::ThrowError(ex.getMessage().c_str()); 253 | } catch (const std::exception& ex) { 254 | return Nan::ThrowError(ex.what()); 255 | } 256 | } 257 | 258 | NAN_METHOD(OracleClient::CreateConnectionPool) { 259 | Nan::HandleScope scope; 260 | 261 | REQ_OBJECT_ARG(0, settings); 262 | REQ_FUN_ARG(1, callback); 263 | 264 | OracleClient* client = Nan::ObjectWrap::Unwrap(info.This()); 265 | ConnectBaton* baton = new ConnectBaton(client, client->m_environment, callback); 266 | 267 | uint32_t busyOption; 268 | 269 | OBJ_GET_STRING(settings, "hostname", baton->hostname); 270 | OBJ_GET_STRING(settings, "user", baton->user); 271 | OBJ_GET_STRING(settings, "password", baton->password); 272 | OBJ_GET_STRING(settings, "database", baton->database); 273 | OBJ_GET_NUMBER(settings, "port", baton->port, 1521); 274 | OBJ_GET_NUMBER(settings, "minConn", baton->minConn, 0); 275 | OBJ_GET_NUMBER(settings, "maxConn", baton->maxConn, 4); 276 | OBJ_GET_NUMBER(settings, "incrConn", baton->incrConn, 1); 277 | OBJ_GET_NUMBER(settings, "timeout", baton->timeout, 0); 278 | OBJ_GET_NUMBER(settings, "busyOption", busyOption, 0); 279 | baton->busyOption = static_cast(busyOption); 280 | OBJ_GET_STRING(settings, "tns", baton->tns); 281 | OBJ_GET_NUMBER(settings, "stmtCacheSize", baton->stmtCacheSize, 16); 282 | 283 | 284 | uv_queue_work(uv_default_loop(), 285 | &baton->work_req, 286 | EIO_CreateConnectionPool, 287 | (uv_after_work_cb) EIO_AfterCreateConnectionPool); 288 | 289 | return; 290 | } 291 | 292 | void OracleClient::EIO_CreateConnectionPool(uv_work_t* req) { 293 | ConnectBaton* baton = CONTAINER_OF(req, ConnectBaton, work_req); 294 | baton->error = NULL; 295 | 296 | try { 297 | char connectionStr[512]; 298 | if (baton->environment->getLDAPHost() != "") { 299 | // http://docs.oracle.com/cd/E16655_01/network.121/e17610/config_concepts.htm#NETAG1054 300 | snprintf(connectionStr, sizeof(connectionStr), "%s", baton->database.c_str()); 301 | } else if (baton->tns != "") { 302 | snprintf(connectionStr, sizeof(connectionStr), "%s", baton->tns.c_str()); 303 | } else { 304 | snprintf(connectionStr, sizeof(connectionStr), "//%s:%d/%s", baton->hostname.c_str(), baton->port, baton->database.c_str()); 305 | } 306 | std::string connStr = std::string(connectionStr); 307 | oracle::occi::StatelessConnectionPool *scp = baton->environment->createStatelessConnectionPool( 308 | baton->user, 309 | baton->password, 310 | connStr, 311 | baton->maxConn, 312 | baton->minConn, 313 | baton->incrConn, 314 | oracle::occi::StatelessConnectionPool::HOMOGENEOUS); 315 | scp->setTimeOut(baton->timeout); 316 | scp->setBusyOption(baton->busyOption); 317 | scp->setStmtCacheSize(baton->stmtCacheSize); 318 | baton->connectionPool = scp; 319 | 320 | } catch(oracle::occi::SQLException &ex) { 321 | baton->error = new std::string(ex.getMessage()); 322 | } catch (const std::exception& ex) { 323 | baton->error = new std::string(ex.what()); 324 | } catch (...) { 325 | baton->error = new std::string("Unknown exception is thrown"); 326 | } 327 | } 328 | 329 | void OracleClient::EIO_AfterCreateConnectionPool(uv_work_t* req) { 330 | Nan::HandleScope scope; 331 | ConnectBaton* baton = CONTAINER_OF(req, ConnectBaton, work_req); 332 | 333 | Local argv[2]; 334 | if(baton->error) { 335 | argv[0] = Exception::Error(Nan::New(baton->error->c_str()).ToLocalChecked()); 336 | argv[1] = Nan::Undefined(); 337 | } else { 338 | argv[0] = Nan::Undefined(); 339 | Local ft = Nan::New(ConnectionPool::s_ct); 340 | Local connectionPool = ft->GetFunction()->NewInstance(); 341 | (Nan::ObjectWrap::Unwrap(connectionPool))->setConnectionPool(baton->client->m_environment, baton->connectionPool); 342 | argv[1] = connectionPool; 343 | } 344 | 345 | Nan::TryCatch tryCatch; 346 | 347 | baton->callback.Call(2, argv); 348 | delete baton; 349 | 350 | if (tryCatch.HasCaught()) { 351 | Nan::FatalException(tryCatch); 352 | } 353 | } 354 | 355 | extern "C" { 356 | static void init(Handle target) { 357 | OracleClient::Init(target); 358 | ConnectionPool::Init(target); 359 | Connection::Init(target); 360 | OutParam::Init(target); 361 | Reader::Init(target); 362 | Statement::Init(target); 363 | } 364 | 365 | NODE_MODULE(oracle_bindings, init); 366 | } 367 | -------------------------------------------------------------------------------- /src/connection.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "connection.h" 3 | #include "executeBaton.h" 4 | #include "outParam.h" 5 | #include "statement.h" 6 | #include "reader.h" 7 | #include "node_buffer.h" 8 | #include 9 | #include 10 | #include 11 | using namespace std; 12 | 13 | Nan::Persistent ConnectionPool::s_ct; 14 | Nan::Persistent Connection::s_ct; 15 | 16 | // ConnectionPool implementation 17 | ConnectionPool::ConnectionPool() : 18 | m_environment(NULL), m_connectionPool(NULL) { 19 | } 20 | 21 | ConnectionPool::~ConnectionPool() { 22 | /* 23 | try { 24 | closeConnectionPool(oracle::occi::StatelessConnectionPool::SPD_FORCE); 25 | } catch (std::exception &ex) { 26 | m_connectionPool = NULL; 27 | fprintf(stderr, "%s\n", ex.what()); 28 | } 29 | */ 30 | } 31 | 32 | void ConnectionPool::Init(Handle target) { 33 | Nan::HandleScope scope; 34 | 35 | Local t = Nan::New(ConnectionPool::New); 36 | ConnectionPool::s_ct.Reset( t); 37 | 38 | t->InstanceTemplate()->SetInternalFieldCount(1); 39 | t->SetClassName(Nan::New("ConnectionPool").ToLocalChecked()); 40 | 41 | Nan::SetPrototypeMethod(t, "getConnectionSync", ConnectionPool::GetConnectionSync); 42 | Nan::SetPrototypeMethod(t, "getConnection", ConnectionPool::GetConnection); 43 | Nan::SetPrototypeMethod(t, "execute", ConnectionPool::Execute); 44 | Nan::SetPrototypeMethod(t, "close", ConnectionPool::Close); 45 | Nan::SetPrototypeMethod(t, "closeSync", ConnectionPool::CloseSync); 46 | Nan::SetPrototypeMethod(t, "getInfo", ConnectionPool::GetInfo); 47 | 48 | target->Set(Nan::New("ConnectionPool").ToLocalChecked(), t->GetFunction()); 49 | } 50 | 51 | NAN_METHOD(ConnectionPool::New) { 52 | Nan::HandleScope scope; 53 | 54 | ConnectionPool *connectionPool = new ConnectionPool(); 55 | connectionPool->Wrap(info.This()); 56 | info.GetReturnValue().Set(info.This()); 57 | } 58 | 59 | void ConnectionPool::setConnectionPool(oracle::occi::Environment* environment, 60 | oracle::occi::StatelessConnectionPool* connectionPool) { 61 | m_environment = environment; 62 | m_connectionPool = connectionPool; 63 | } 64 | 65 | NAN_METHOD(ConnectionPool::CloseSync) { 66 | Nan::HandleScope scope; 67 | try { 68 | ConnectionPool* connectionPool = Nan::ObjectWrap::Unwrap( 69 | info.This()); 70 | 71 | // Get the optional destroy mode 72 | oracle::occi::StatelessConnectionPool::DestroyMode mode = 73 | oracle::occi::StatelessConnectionPool::DEFAULT; 74 | if (info.Length() == 1 || info[0]->IsUint32()) { 75 | mode = 76 | static_cast(info[0]->Uint32Value()); 77 | } 78 | 79 | connectionPool->closeConnectionPool(mode); 80 | connectionPool->Unref(); 81 | 82 | return; 83 | } catch (const exception& ex) { 84 | return Nan::ThrowError(ex.what()); 85 | } 86 | } 87 | 88 | NAN_METHOD(ConnectionPool::Close) { 89 | Nan::HandleScope scope; 90 | ConnectionPool* connectionPool = Nan::ObjectWrap::Unwrap( 91 | info.This()); 92 | 93 | // Get the optional destroy mode 94 | oracle::occi::StatelessConnectionPool::DestroyMode mode = 95 | oracle::occi::StatelessConnectionPool::DEFAULT; 96 | if (info.Length() >= 1 && info[0]->IsUint32()) { 97 | mode = 98 | static_cast(info[0]->Uint32Value()); 99 | } 100 | 101 | Local callback; 102 | if (info.Length() == 1 && info[0]->IsFunction()) { 103 | callback = Local::Cast(info[0]); 104 | } else if (info.Length() > 1 && info[1]->IsFunction()) { 105 | callback = Local::Cast(info[1]); 106 | } 107 | 108 | ConnectionPoolBaton* baton = 109 | new ConnectionPoolBaton(connectionPool->getEnvironment(), 110 | connectionPool, 111 | mode, 112 | callback); 113 | uv_queue_work(uv_default_loop(), 114 | &baton->work_req, 115 | ConnectionPool::EIO_Close, 116 | (uv_after_work_cb) ConnectionPool::EIO_AfterClose); 117 | 118 | return; 119 | } 120 | 121 | void ConnectionPool::EIO_Close(uv_work_t* req) { 122 | ConnectionPoolBaton* baton = CONTAINER_OF(req, ConnectionPoolBaton, work_req); 123 | try { 124 | baton->connectionPool->closeConnectionPool(baton->destroyMode); 125 | } catch(const exception &ex) { 126 | baton->error = new std::string(ex.what()); 127 | baton->connectionPool->m_connectionPool = NULL; 128 | } 129 | } 130 | 131 | void ConnectionPool::EIO_AfterClose(uv_work_t* req) { 132 | Nan::HandleScope scope; 133 | ConnectionPoolBaton* baton = CONTAINER_OF(req, ConnectionPoolBaton, work_req); 134 | baton->connectionPool->Unref(); 135 | 136 | Nan::TryCatch tryCatch; 137 | if (baton->callback != NULL) { 138 | Local argv[2]; 139 | if (baton->error) { 140 | argv[0] = Nan::Error(baton->error->c_str()); 141 | argv[1] = Nan::Undefined(); 142 | } else { 143 | argv[0] = Nan::Undefined(); 144 | argv[1] = Nan::Undefined(); 145 | } 146 | baton->callback->Call(2, argv); 147 | } 148 | delete baton; 149 | 150 | if (tryCatch.HasCaught()) { 151 | Nan::FatalException(tryCatch); 152 | } 153 | } 154 | 155 | NAN_METHOD(ConnectionPool::GetInfo) { 156 | Nan::HandleScope scope; 157 | ConnectionPool* connectionPool = Nan::ObjectWrap::Unwrap( 158 | info.This()); 159 | if (connectionPool->m_connectionPool) { 160 | Local obj = Nan::New(); 161 | 162 | obj->Set(Nan::New("openConnections").ToLocalChecked(), 163 | Nan::New(connectionPool->m_connectionPool->getOpenConnections())); 164 | obj->Set(Nan::New("busyConnections").ToLocalChecked(), 165 | Nan::New(connectionPool->m_connectionPool->getBusyConnections())); 166 | obj->Set(Nan::New("maxConnections").ToLocalChecked(), 167 | Nan::New(connectionPool->m_connectionPool->getMaxConnections())); 168 | obj->Set(Nan::New("minConnections").ToLocalChecked(), 169 | Nan::New(connectionPool->m_connectionPool->getMinConnections())); 170 | obj->Set(Nan::New("incrConnections").ToLocalChecked(), 171 | Nan::New(connectionPool->m_connectionPool->getIncrConnections())); 172 | obj->Set(Nan::New("busyOption").ToLocalChecked(), 173 | Nan::New(static_cast(connectionPool->m_connectionPool->getBusyOption()))); 174 | obj->Set(Nan::New("timeout").ToLocalChecked(), 175 | Nan::New(connectionPool->m_connectionPool->getTimeOut())); 176 | 177 | obj->Set(Nan::New("poolName").ToLocalChecked(), 178 | Nan::New(connectionPool->m_connectionPool->getPoolName().c_str()).ToLocalChecked()); 179 | 180 | info.GetReturnValue().Set(obj); 181 | } else { 182 | return; 183 | } 184 | } 185 | 186 | NAN_METHOD(ConnectionPool::GetConnectionSync) { 187 | Nan::HandleScope scope; 188 | try { 189 | ConnectionPool* connectionPool = Nan::ObjectWrap::Unwrap( 190 | info.This()); 191 | // std::string tag = "strong-oracle"; 192 | oracle::occi::Connection *conn = 193 | connectionPool->getConnectionPool()->getConnection("strong-oracle"); 194 | Local ft = Nan::New(Connection::s_ct); 195 | Local connection = ft->GetFunction()->NewInstance(); 196 | (Nan::ObjectWrap::Unwrap(connection))->setConnection( 197 | connectionPool->getEnvironment(), 198 | connectionPool, 199 | conn); 200 | info.GetReturnValue().Set(connection); 201 | } catch (const exception& ex) { 202 | return Nan::ThrowError(ex.what()); 203 | } 204 | } 205 | 206 | NAN_METHOD(ConnectionPool::GetConnection) { 207 | Nan::HandleScope scope; 208 | ConnectionPool* connectionPool = Nan::ObjectWrap::Unwrap( 209 | info.This()); 210 | 211 | REQ_FUN_ARG(0, callback); 212 | 213 | ConnectionPoolBaton* baton = 214 | new ConnectionPoolBaton(connectionPool->getEnvironment(), 215 | connectionPool, 216 | oracle::occi::StatelessConnectionPool::DEFAULT, 217 | callback); 218 | uv_queue_work(uv_default_loop(), 219 | &baton->work_req, 220 | ConnectionPool::EIO_GetConnection, 221 | (uv_after_work_cb) ConnectionPool::EIO_AfterGetConnection); 222 | 223 | return; 224 | } 225 | 226 | void ConnectionPool::EIO_GetConnection(uv_work_t* req) { 227 | ConnectionPoolBaton* baton = CONTAINER_OF(req, ConnectionPoolBaton, work_req); 228 | baton->error = NULL; 229 | try { 230 | oracle::occi::Connection *conn = 231 | baton->connectionPool->getConnectionPool()->getConnection( 232 | "strong-oracle"); 233 | baton->connection = conn; 234 | } catch (oracle::occi::SQLException &ex) { 235 | baton->error = new std::string(ex.getMessage()); 236 | } 237 | } 238 | 239 | void ConnectionPool::EIO_AfterGetConnection(uv_work_t* req) { 240 | Nan::HandleScope scope; 241 | ConnectionPoolBaton* baton = CONTAINER_OF(req, ConnectionPoolBaton, work_req); 242 | 243 | Local argv[2]; 244 | if (baton->error) { 245 | argv[0] = Nan::Error(baton->error->c_str()); 246 | argv[1] = Nan::Undefined(); 247 | } else { 248 | argv[0] = Nan::Undefined(); 249 | Local ft = Nan::New(Connection::s_ct); 250 | Local connection = ft->GetFunction()->NewInstance(); 251 | (Nan::ObjectWrap::Unwrap(connection))->setConnection( 252 | baton->connectionPool->getEnvironment(), 253 | baton->connectionPool, 254 | baton->connection); 255 | argv[1] = connection; 256 | } 257 | 258 | Nan::TryCatch tryCatch; 259 | baton->callback->Call(2, argv); 260 | delete baton; 261 | 262 | if (tryCatch.HasCaught()) { 263 | Nan::FatalException(tryCatch); 264 | } 265 | 266 | } 267 | 268 | void ConnectionPool::closeConnectionPool( 269 | oracle::occi::StatelessConnectionPool::DestroyMode mode) { 270 | if (m_environment && m_connectionPool) { 271 | m_environment->terminateStatelessConnectionPool(m_connectionPool, mode); 272 | m_connectionPool = NULL; 273 | } 274 | } 275 | 276 | NAN_METHOD(ConnectionPool::Execute) { 277 | Nan::HandleScope scope; 278 | ConnectionPool* connectionPool = Nan::ObjectWrap::Unwrap(info.This()); 279 | 280 | REQ_STRING_ARG(0, sql); 281 | REQ_ARRAY_ARG(1, values); 282 | Local options; 283 | int cbIndex = 2; 284 | if (info.Length() > 3 && info[2]->IsObject() && !info[2]->IsFunction()) 285 | { 286 | options = Local::Cast(info[2]); 287 | ++cbIndex; 288 | } 289 | if (info.Length() <= cbIndex || !info[cbIndex]->IsFunction()) 290 | { 291 | ostringstream oss; 292 | oss << "Argument " << cbIndex << " must be a function"; 293 | std::string strMsg = std::string(oss.str().c_str()); 294 | throw NodeOracleException(strMsg); 295 | } 296 | Local callback = Local::Cast(info[cbIndex]); 297 | 298 | String::Utf8Value sqlVal(sql); 299 | 300 | ExecuteBaton* baton = new ExecuteBaton(connectionPool->m_environment, 301 | connectionPool->m_connectionPool, 302 | NULL, 303 | true, 304 | 10, 305 | *sqlVal, values, options, 306 | callback); 307 | uv_queue_work(uv_default_loop(), 308 | &baton->work_req, 309 | ConnectionPool::EIO_Execute, 310 | (uv_after_work_cb) ConnectionPool::EIO_AfterExecute); 311 | 312 | return; 313 | } 314 | 315 | void ConnectionPool::EIO_Execute(uv_work_t* req) { 316 | ExecuteBaton* baton = CONTAINER_OF(req, ExecuteBaton, work_req); 317 | 318 | if(!baton->m_connection) { 319 | oracle::occi::Connection *conn = 320 | baton->m_connectionPool->getConnection("strong-oracle"); 321 | baton->m_connection = conn; 322 | } 323 | oracle::occi::Statement* stmt = Connection::CreateStatement(baton); 324 | if (baton->error) return; 325 | 326 | Connection::ExecuteStatement(baton, stmt); 327 | 328 | if (stmt) { 329 | if (baton->m_connection) { 330 | baton->m_connection->terminateStatement(stmt); 331 | } 332 | stmt = NULL; 333 | } 334 | if (baton->m_connectionPool && baton->m_connection) { 335 | baton->m_connectionPool->releaseConnection(baton->m_connection, 336 | "strong-oracle"); 337 | baton->m_connection = NULL; 338 | } 339 | } 340 | 341 | void ConnectionPool::EIO_AfterExecute(uv_work_t* req) { 342 | Nan::HandleScope scope; 343 | ExecuteBaton* baton = CONTAINER_OF(req, ExecuteBaton, work_req); 344 | 345 | try { 346 | Local argv[2]; 347 | Connection::handleResult(baton, argv); 348 | baton->callback->Call(2, argv); 349 | } catch (NodeOracleException &ex) { 350 | Local argv[2]; 351 | argv[0] = Nan::Error(ex.getMessage().c_str()); 352 | argv[1] = Nan::Undefined(); 353 | baton->callback->Call(2, argv); 354 | } catch (const exception &ex) { 355 | Local argv[2]; 356 | argv[0] = Nan::Error(ex.what()); 357 | argv[1] = Nan::Undefined(); 358 | baton->callback->Call(2, argv); 359 | } 360 | 361 | delete baton; 362 | } 363 | 364 | void Connection::Init(Handle target) { 365 | Nan::HandleScope scope; 366 | 367 | Local t = Nan::New(Connection::New); 368 | Connection::s_ct.Reset( t); 369 | 370 | t->InstanceTemplate()->SetInternalFieldCount(1); 371 | t->SetClassName(Nan::New("Connection").ToLocalChecked()); 372 | 373 | Nan::SetPrototypeMethod(t, "execute", Connection::Execute); 374 | Nan::SetPrototypeMethod(t, "executeSync", ExecuteSync); 375 | Nan::SetPrototypeMethod(t, "readerHandle", CreateReader); 376 | Nan::SetPrototypeMethod(t, "prepare", Prepare); 377 | Nan::SetPrototypeMethod(t, "close", Connection::Close); 378 | Nan::SetPrototypeMethod(t, "closeSync", Connection::CloseSync); 379 | Nan::SetPrototypeMethod(t, "isConnected", IsConnected); 380 | Nan::SetPrototypeMethod(t, "setAutoCommit", SetAutoCommit); 381 | Nan::SetPrototypeMethod(t, "setPrefetchRowCount", SetPrefetchRowCount); 382 | Nan::SetPrototypeMethod(t, "commit", Commit); 383 | Nan::SetPrototypeMethod(t, "rollback", Rollback); 384 | 385 | target->Set(Nan::New("Connection").ToLocalChecked(), t->GetFunction()); 386 | } 387 | 388 | NAN_METHOD(Connection::New) { 389 | Nan::HandleScope scope; 390 | 391 | Connection *connection = new Connection(); 392 | connection->Wrap(info.This()); 393 | info.GetReturnValue().Set(info.This()); 394 | } 395 | 396 | NAN_METHOD(Connection::Prepare) { 397 | Nan::HandleScope scope; 398 | Connection* connection = Nan::ObjectWrap::Unwrap(info.This()); 399 | 400 | REQ_STRING_ARG(0, sql); 401 | 402 | String::Utf8Value sqlVal(sql); 403 | 404 | StatementBaton* baton = new StatementBaton(connection->m_environment, NULL, connection->m_connection, 405 | connection->getAutoCommit(), connection->getPrefetchRowCount(), 406 | *sqlVal); 407 | Local ft = Nan::New(Statement::s_ct); 408 | Local statementHandle = ft->GetFunction()->NewInstance(); 409 | Statement* statement = Nan::ObjectWrap::Unwrap(statementHandle); 410 | statement->setBaton(baton); 411 | 412 | info.GetReturnValue().Set(statementHandle); 413 | } 414 | 415 | NAN_METHOD(Connection::CreateReader) { 416 | Nan::HandleScope scope; 417 | Connection* connection = Nan::ObjectWrap::Unwrap(info.This()); 418 | 419 | REQ_STRING_ARG(0, sql); 420 | REQ_ARRAY_ARG(1, values); 421 | 422 | String::Utf8Value sqlVal(sql); 423 | 424 | ReaderBaton* baton = new ReaderBaton(connection->m_environment, NULL, connection->m_connection, 425 | connection->getAutoCommit(), connection->getPrefetchRowCount(), 426 | *sqlVal, values); 427 | 428 | Local ft = Nan::New(Reader::s_ct); 429 | Local readerHandle = ft->GetFunction()->NewInstance(); 430 | Reader* reader = Nan::ObjectWrap::Unwrap(readerHandle); 431 | reader->setBaton(baton); 432 | 433 | info.GetReturnValue().Set(readerHandle); 434 | } 435 | 436 | Connection::Connection() : 437 | m_environment(NULL), connectionPool(NULL), m_connection(NULL), 438 | m_autoCommit(true), m_prefetchRowCount(0) { 439 | } 440 | 441 | Connection::~Connection() { 442 | /* 443 | try { 444 | closeConnection(); 445 | } catch (std::exception &ex) { 446 | m_connection = NULL; 447 | fprintf(stderr, "%s\n", ex.what()); 448 | } 449 | */ 450 | } 451 | 452 | NAN_METHOD(Connection::Execute) { 453 | Nan::HandleScope scope; 454 | Connection* connection = Nan::ObjectWrap::Unwrap(info.This()); 455 | 456 | REQ_STRING_ARG(0, sql); 457 | REQ_ARRAY_ARG(1, values); 458 | Local options; 459 | int cbIndex = 2; 460 | if (info.Length() > 3 && info[2]->IsObject() && !info[2]->IsFunction()) 461 | { 462 | options = Local::Cast(info[2]); 463 | ++cbIndex; 464 | } 465 | if (info.Length() <= cbIndex || !info[cbIndex]->IsFunction()) 466 | { 467 | ostringstream oss; 468 | oss << "Argument " << cbIndex << " must be a function"; 469 | std::string strMsg = std::string(oss.str().c_str()); 470 | throw NodeOracleException(strMsg); 471 | } 472 | Local callback = Local::Cast(info[cbIndex]); 473 | 474 | String::Utf8Value sqlVal(sql); 475 | 476 | ExecuteBaton* baton = new ExecuteBaton(connection->m_environment, 477 | NULL, 478 | connection->m_connection, 479 | connection->getAutoCommit(), 480 | connection->getPrefetchRowCount(), 481 | *sqlVal, values, options, 482 | callback); 483 | uv_queue_work(uv_default_loop(), 484 | &baton->work_req, 485 | EIO_Execute, 486 | (uv_after_work_cb) EIO_AfterExecute); 487 | 488 | return; 489 | } 490 | 491 | NAN_METHOD(Connection::CloseSync) { 492 | Nan::HandleScope scope; 493 | try { 494 | Connection* connection = Nan::ObjectWrap::Unwrap(info.This()); 495 | connection->closeConnection(); 496 | connection->Unref(); 497 | 498 | return; 499 | } catch (const exception& ex) { 500 | return Nan::ThrowError(ex.what()); 501 | } 502 | } 503 | 504 | NAN_METHOD(Connection::IsConnected) { 505 | Nan::HandleScope scope; 506 | Connection* connection = Nan::ObjectWrap::Unwrap(info.This()); 507 | 508 | if (connection && connection->m_connection) { 509 | info.GetReturnValue().Set(Nan::New(true)); 510 | } else { 511 | info.GetReturnValue().Set(Nan::New(false)); 512 | } 513 | } 514 | 515 | NAN_METHOD(Connection::Commit) { 516 | Nan::HandleScope scope; 517 | Connection* connection = Nan::ObjectWrap::Unwrap(info.This()); 518 | 519 | REQ_FUN_ARG(0, callback); 520 | 521 | ConnectionBaton* baton = new ConnectionBaton(connection, callback); 522 | uv_queue_work(uv_default_loop(), 523 | &baton->work_req, 524 | EIO_Commit, 525 | (uv_after_work_cb) EIO_AfterCommit); 526 | 527 | return; 528 | } 529 | 530 | NAN_METHOD(Connection::Rollback) { 531 | Nan::HandleScope scope; 532 | Connection* connection = Nan::ObjectWrap::Unwrap(info.This()); 533 | 534 | REQ_FUN_ARG(0, callback); 535 | 536 | ConnectionBaton* baton = new ConnectionBaton(connection, callback); 537 | uv_queue_work(uv_default_loop(), 538 | &baton->work_req, 539 | EIO_Rollback, 540 | (uv_after_work_cb) EIO_AfterRollback); 541 | 542 | return; 543 | } 544 | 545 | NAN_METHOD(Connection::SetAutoCommit) { 546 | Nan::HandleScope scope; 547 | Connection* connection = Nan::ObjectWrap::Unwrap(info.This()); 548 | REQ_BOOL_ARG(0, autoCommit); 549 | connection->m_autoCommit = autoCommit; 550 | return; 551 | } 552 | 553 | NAN_METHOD(Connection::SetPrefetchRowCount) { 554 | Nan::HandleScope scope; 555 | Connection* connection = Nan::ObjectWrap::Unwrap(info.This()); 556 | REQ_INT_ARG(0, prefetchRowCount); 557 | connection->m_prefetchRowCount = prefetchRowCount; 558 | return; 559 | } 560 | 561 | void Connection::closeConnection() { 562 | if(!m_connection) { 563 | return; 564 | } 565 | if(m_environment && !connectionPool) { 566 | // The connection is not pooled 567 | m_environment->terminateConnection(m_connection); 568 | m_connection = NULL; 569 | return; 570 | } 571 | // Make sure the connection pool is still open 572 | if (m_environment && connectionPool && connectionPool->getConnectionPool()) { 573 | connectionPool->getConnectionPool()->releaseConnection(m_connection, "strong-oracle"); 574 | m_connection = NULL; 575 | connectionPool = NULL; 576 | } 577 | } 578 | 579 | void RandomBytesFree(char* data, void* hint) { 580 | delete[] data; 581 | } 582 | 583 | int Connection::SetValuesOnStatement(oracle::occi::Statement* stmt, 584 | vector &values) { 585 | uint32_t index = 1; 586 | int outputParam = -1; 587 | outparam_t * outParam = NULL; 588 | for (vector::iterator iterator = values.begin(), end = values.end(); 589 | iterator != end; ++iterator, index++) { 590 | value_t* val = *iterator; 591 | int outParamType; 592 | 593 | switch (val->type) { 594 | case VALUE_TYPE_NULL: 595 | stmt->setNull(index, oracle::occi::OCCISTRING); 596 | break; 597 | case VALUE_TYPE_LONG_RAW: 598 | stmt->setString(index, *((string*) val->value)); 599 | break; 600 | case VALUE_TYPE_STRING: 601 | stmt->setString(index, *((string*) val->value)); 602 | break; 603 | case VALUE_TYPE_NUMBER: 604 | stmt->setNumber(index, *((oracle::occi::Number*) val->value)); 605 | break; 606 | case VALUE_TYPE_DATE: 607 | stmt->setDate(index, *((oracle::occi::Date*) val->value)); 608 | break; 609 | case VALUE_TYPE_TIMESTAMP: 610 | stmt->setTimestamp(index, *((oracle::occi::Timestamp*) val->value)); 611 | break; 612 | case VALUE_TYPE_OUTPUT: 613 | outParam = static_cast(val->value); 614 | outParamType = outParam->type; 615 | switch (outParamType) { 616 | case OutParam::OCCIINT: 617 | if (outParam->inOut.hasInParam) { 618 | stmt->setInt(index, outParam->inOut.intVal); 619 | } else { 620 | stmt->registerOutParam(index, oracle::occi::OCCIINT); 621 | } 622 | break; 623 | case OutParam::OCCISTRING: 624 | if (outParam->inOut.hasInParam) { 625 | stmt->setString(index, outParam->inOut.stringVal); 626 | } else { 627 | stmt->registerOutParam(index, oracle::occi::OCCISTRING, 628 | outParam->size); 629 | } 630 | break; 631 | case OutParam::OCCIDOUBLE: 632 | if (outParam->inOut.hasInParam) { 633 | stmt->setDouble(index, outParam->inOut.doubleVal); 634 | } else { 635 | stmt->registerOutParam(index, oracle::occi::OCCIDOUBLE); 636 | } 637 | break; 638 | case OutParam::OCCIFLOAT: 639 | if (outParam->inOut.hasInParam) { 640 | stmt->setFloat(index, outParam->inOut.floatVal); 641 | } else { 642 | stmt->registerOutParam(index, oracle::occi::OCCIFLOAT); 643 | } 644 | break; 645 | case OutParam::OCCICURSOR: 646 | stmt->registerOutParam(index, oracle::occi::OCCICURSOR); 647 | break; 648 | case OutParam::OCCICLOB: 649 | stmt->registerOutParam(index, oracle::occi::OCCICLOB); 650 | break; 651 | case OutParam::OCCIDATE: 652 | stmt->registerOutParam(index, oracle::occi::OCCIDATE); 653 | break; 654 | case OutParam::OCCITIMESTAMP: 655 | stmt->registerOutParam(index, oracle::occi::OCCITIMESTAMP); 656 | break; 657 | case OutParam::OCCINUMBER: { 658 | if (outParam->inOut.hasInParam) { 659 | stmt->setNumber(index, outParam->inOut.numberVal); 660 | } else { 661 | stmt->registerOutParam(index, oracle::occi::OCCINUMBER); 662 | } 663 | break; 664 | } 665 | case OutParam::OCCIBLOB: 666 | stmt->registerOutParam(index, oracle::occi::OCCIBLOB); 667 | break; 668 | default: 669 | delete outParam; 670 | char msg[128]; 671 | snprintf(msg, sizeof(msg), 672 | "SetValuesOnStatement: Unknown OutParam type: %d", outParamType); 673 | std::string strMsg = std::string(msg); 674 | throw NodeOracleException(strMsg); 675 | } 676 | delete outParam; 677 | outputParam = index; 678 | break; 679 | default: 680 | throw NodeOracleException("SetValuesOnStatement: Unhandled value type"); 681 | } 682 | } 683 | return outputParam; 684 | } 685 | 686 | void Connection::CreateColumnsFromResultSet(oracle::occi::ResultSet* rs, 687 | vector &columns) { 688 | vector metadata = rs->getColumnListMetaData(); 689 | 690 | int colIndex = 1; 691 | for (vector::iterator iterator = metadata.begin(), 692 | end = metadata.end(); iterator != end; ++iterator, colIndex++) { 693 | oracle::occi::MetaData metadata = *iterator; 694 | column_t* col = new column_t(); 695 | col->name = metadata.getString(oracle::occi::MetaData::ATTR_NAME); 696 | int type = metadata.getInt(oracle::occi::MetaData::ATTR_DATA_TYPE); 697 | col->charForm = metadata.getInt(oracle::occi::MetaData::ATTR_CHARSET_FORM); 698 | 699 | 700 | switch (type) { 701 | case oracle::occi::OCCI_TYPECODE_NUMBER: 702 | case oracle::occi::OCCI_TYPECODE_FLOAT: 703 | case oracle::occi::OCCI_TYPECODE_DOUBLE: 704 | case oracle::occi::OCCI_TYPECODE_REAL: 705 | case oracle::occi::OCCI_TYPECODE_DECIMAL: 706 | case oracle::occi::OCCI_TYPECODE_INTEGER: 707 | case oracle::occi::OCCI_TYPECODE_SMALLINT: 708 | col->type = VALUE_TYPE_NUMBER; 709 | break; 710 | case oracle::occi::OCCI_TYPECODE_VARCHAR2: 711 | case oracle::occi::OCCI_TYPECODE_VARCHAR: 712 | case oracle::occi::OCCI_TYPECODE_CHAR: 713 | col->type = VALUE_TYPE_STRING; 714 | break; 715 | case OCI_TYPECODE_LONG_RAW: 716 | rs->setMaxColumnSize(colIndex, LONG_ROW_MAX_SIZE); 717 | col->type = VALUE_TYPE_LONG_RAW; 718 | break; 719 | case oracle::occi::OCCI_TYPECODE_CLOB: 720 | col->type = VALUE_TYPE_CLOB; 721 | break; 722 | case oracle::occi::OCCI_TYPECODE_DATE: 723 | col->type = VALUE_TYPE_DATE; 724 | break; 725 | //Use OCI_TYPECODE from oro.h because occiCommon.h does not re-export these in its TypeCode enum 726 | case OCI_TYPECODE_TIMESTAMP: 727 | case OCI_TYPECODE_TIMESTAMP_TZ: //Timezone 728 | case OCI_TYPECODE_TIMESTAMP_LTZ: //Local Timezone 729 | col->type = VALUE_TYPE_TIMESTAMP; 730 | break; 731 | case oracle::occi::OCCI_TYPECODE_BLOB: 732 | col->type = VALUE_TYPE_BLOB; 733 | break; 734 | default: 735 | char msg[128]; 736 | snprintf(msg, sizeof(msg), 737 | "CreateColumnsFromResultSet: Unhandled oracle data type: %d", type); 738 | delete col; 739 | std::string strMsg = std::string(msg); 740 | throw NodeOracleException(strMsg); 741 | break; 742 | } 743 | columns.push_back(col); 744 | } 745 | } 746 | 747 | row_t* Connection::CreateRowFromCurrentResultSetRow(oracle::occi::ResultSet* rs, 748 | vector &columns) { 749 | row_t* row = new row_t(); 750 | int colIndex = 1; 751 | for (vector::iterator iterator = columns.begin(), end = 752 | columns.end(); iterator != end; ++iterator, colIndex++) { 753 | column_t* col = *iterator; 754 | if (rs->isNull(colIndex)) { 755 | row->values.push_back(NULL); 756 | } else { 757 | switch (col->type) { 758 | case VALUE_TYPE_LONG_RAW: 759 | row->values.push_back(new string(rs->getString(colIndex))); 760 | break; 761 | case VALUE_TYPE_STRING: 762 | row->values.push_back(new string(rs->getString(colIndex))); 763 | break; 764 | case VALUE_TYPE_NUMBER: 765 | row->values.push_back( 766 | new oracle::occi::Number(rs->getNumber(colIndex))); 767 | break; 768 | case VALUE_TYPE_DATE: 769 | row->values.push_back(new oracle::occi::Date(rs->getDate(colIndex))); 770 | break; 771 | case VALUE_TYPE_TIMESTAMP: 772 | row->values.push_back( 773 | new oracle::occi::Timestamp(rs->getTimestamp(colIndex))); 774 | break; 775 | case VALUE_TYPE_CLOB: { 776 | oracle::occi::Clob clob = rs->getClob(colIndex); 777 | row->values.push_back(Connection::readClob(clob, col->charForm)); 778 | } 779 | break; 780 | case VALUE_TYPE_BLOB: { 781 | oracle::occi::Blob blob = rs->getBlob(colIndex); 782 | row->values.push_back(Connection::readBlob(blob)); 783 | } 784 | break; 785 | default: 786 | char msg[128]; 787 | snprintf(msg, sizeof(msg), 788 | "CreateRowFromCurrentResultSetRow: Unhandled type: %d", col->type); 789 | std::string strMsg = std::string(msg); 790 | throw NodeOracleException(strMsg); 791 | break; 792 | } 793 | } 794 | } 795 | return row; 796 | } 797 | 798 | void Connection::EIO_AfterCall(uv_work_t* req) { 799 | Nan::HandleScope scope; 800 | ConnectionBaton* baton = CONTAINER_OF(req, ConnectionBaton, work_req); 801 | 802 | Nan::TryCatch tryCatch; 803 | if (baton->callback != NULL) { 804 | Local argv[2]; 805 | if (baton->error) { 806 | argv[0] = Nan::Error(baton->error->c_str()); 807 | argv[1] = Nan::Undefined(); 808 | } else { 809 | argv[0] = Nan::Undefined(); 810 | argv[1] = Nan::Undefined(); 811 | } 812 | baton->callback->Call(2, argv); 813 | } 814 | delete baton; 815 | 816 | if (tryCatch.HasCaught()) { 817 | Nan::FatalException(tryCatch); 818 | } 819 | } 820 | 821 | void Connection::EIO_Commit(uv_work_t* req) { 822 | ConnectionBaton* baton = CONTAINER_OF(req, ConnectionBaton, work_req); 823 | 824 | baton->connection->m_connection->commit(); 825 | } 826 | 827 | void Connection::EIO_AfterCommit(uv_work_t* req) { 828 | Connection::EIO_AfterCall(req); 829 | } 830 | 831 | void Connection::EIO_Rollback(uv_work_t* req) { 832 | ConnectionBaton* baton = CONTAINER_OF(req, ConnectionBaton, work_req); 833 | 834 | baton->connection->m_connection->rollback(); 835 | } 836 | 837 | void Connection::EIO_AfterRollback(uv_work_t* req) { 838 | Connection::EIO_AfterCall(req); 839 | } 840 | 841 | NAN_METHOD(Connection::Close) { 842 | Nan::HandleScope scope; 843 | Connection* connection = Nan::ObjectWrap::Unwrap(info.This()); 844 | 845 | Local callback; 846 | if (info.Length() > 0 && info[0]->IsFunction()) { 847 | callback = Local::Cast(info[0]); 848 | } 849 | 850 | ConnectionBaton* baton = new ConnectionBaton(connection, callback); 851 | uv_queue_work(uv_default_loop(), 852 | &baton->work_req, 853 | Connection::EIO_Close, 854 | (uv_after_work_cb) Connection::EIO_AfterClose); 855 | 856 | return; 857 | } 858 | 859 | void Connection::EIO_Close(uv_work_t* req) { 860 | ConnectionBaton* baton = CONTAINER_OF(req, ConnectionBaton, work_req); 861 | try { 862 | baton->connection->closeConnection(); 863 | } catch(const exception &ex) { 864 | baton->error = new std::string(ex.what()); 865 | } 866 | } 867 | 868 | void Connection::EIO_AfterClose(uv_work_t* req) { 869 | Nan::HandleScope scope; 870 | ConnectionBaton* baton = CONTAINER_OF(req, ConnectionBaton, work_req); 871 | baton->connection->Unref(); 872 | Nan::TryCatch tryCatch; 873 | if (baton->callback != NULL) { 874 | Local argv[2]; 875 | if (baton->error) { 876 | argv[0] = Nan::Error(baton->error->c_str()); 877 | argv[1] = Nan::Undefined(); 878 | } else { 879 | argv[0] = Nan::Undefined(); 880 | argv[1] = Nan::Undefined(); 881 | } 882 | baton->callback->Call(2, argv); 883 | } 884 | delete baton; 885 | 886 | if (tryCatch.HasCaught()) { 887 | Nan::FatalException(tryCatch); 888 | } 889 | } 890 | 891 | void Connection::EIO_Execute(uv_work_t* req) { 892 | ExecuteBaton* baton = CONTAINER_OF(req, ExecuteBaton, work_req); 893 | 894 | oracle::occi::Statement* stmt = CreateStatement(baton); 895 | if (baton->error) return; 896 | 897 | ExecuteStatement(baton, stmt); 898 | 899 | if (stmt) { 900 | if (baton->m_connection) { 901 | baton->m_connection->terminateStatement(stmt); 902 | } 903 | stmt = NULL; 904 | } 905 | } 906 | 907 | void CallDateMethod(v8::Local date, const char* methodName, int val) { 908 | Local info[1]; 909 | info[0] = Nan::New(val); 910 | Local::Cast(date->Get(Nan::New(methodName).ToLocalChecked()))->Call(date, 1, 911 | info); 912 | } 913 | 914 | Local OracleDateToV8Date(oracle::occi::Date* d) { 915 | int year; 916 | unsigned int month, day, hour, min, sec; 917 | d->getDate(year, month, day, hour, min, sec); 918 | Local date = Nan::New(0.0).ToLocalChecked(); 919 | CallDateMethod(date, "setUTCFullYear", year); 920 | CallDateMethod(date, "setUTCMonth", month - 1); 921 | CallDateMethod(date, "setUTCDate", day); 922 | CallDateMethod(date, "setUTCHours", hour); 923 | CallDateMethod(date, "setUTCMinutes", min); 924 | CallDateMethod(date, "setUTCSeconds", sec); 925 | CallDateMethod(date, "setUTCMilliseconds", 0); 926 | return date; 927 | } 928 | 929 | Local OracleTimestampToV8Date(oracle::occi::Timestamp* d) { 930 | int year; 931 | unsigned int month, day, hour, min, sec, fs, ms; 932 | d->getDate(year, month, day); 933 | d->getTime(hour, min, sec, fs); 934 | Local date = Nan::New(0.0).ToLocalChecked(); 935 | //occi always returns nanoseconds, regardless of precision set on timestamp column 936 | ms = (fs / 1000000.0) + 0.5; // add 0.5 to round to nearest millisecond 937 | 938 | CallDateMethod(date, "setUTCFullYear", year); 939 | CallDateMethod(date, "setUTCMonth", month - 1); 940 | CallDateMethod(date, "setUTCDate", day); 941 | CallDateMethod(date, "setUTCHours", hour); 942 | CallDateMethod(date, "setUTCMinutes", min); 943 | CallDateMethod(date, "setUTCSeconds", sec); 944 | CallDateMethod(date, "setUTCMilliseconds", ms); 945 | return date; 946 | } 947 | 948 | Local Connection::CreateV8ObjectFromRow(vector columns, 949 | row_t* currentRow) { 950 | Local obj = Nan::New(); 951 | uint32_t colIndex = 0; 952 | for (vector::iterator iterator = columns.begin(), end = 953 | columns.end(); iterator != end; ++iterator, colIndex++) { 954 | column_t* col = *iterator; 955 | void* val = currentRow->values[colIndex]; 956 | Local colName = Nan::New(col->name.c_str()).ToLocalChecked(); 957 | if (val == NULL) { 958 | obj->Set(colName, Nan::Null()); 959 | } else { 960 | switch (col->type) { 961 | case VALUE_TYPE_LONG_RAW: { 962 | // NOTE: We could take the time to convert the string to binary here. 963 | string* v = (string*) val; 964 | obj->Set(colName, Nan::New(v->c_str()).ToLocalChecked()); 965 | delete v; 966 | } 967 | break; 968 | case VALUE_TYPE_STRING: { 969 | string* v = (string*) val; 970 | obj->Set(colName, Nan::New(v->c_str()).ToLocalChecked()); 971 | delete v; 972 | } 973 | break; 974 | case VALUE_TYPE_NUMBER: { 975 | oracle::occi::Number* v = (oracle::occi::Number*) val; 976 | obj->Set(colName, Nan::New((double) (*v))); 977 | delete v; 978 | } 979 | break; 980 | case VALUE_TYPE_DATE: { 981 | oracle::occi::Date* v = (oracle::occi::Date*) val; 982 | obj->Set(colName, OracleDateToV8Date(v)); 983 | delete v; 984 | } 985 | break; 986 | case VALUE_TYPE_TIMESTAMP: { 987 | oracle::occi::Timestamp* v = (oracle::occi::Timestamp*) val; 988 | obj->Set(colName, OracleTimestampToV8Date(v)); 989 | delete v; 990 | } 991 | break; 992 | case VALUE_TYPE_CLOB: { 993 | buffer_t *v = (buffer_t *) val; 994 | obj->Set(colName, Nan::New((const char*)v->data, v->length).ToLocalChecked()); 995 | delete[] v->data; 996 | delete v; 997 | } 998 | break; 999 | case VALUE_TYPE_BLOB: { 1000 | buffer_t *v = (buffer_t *) val; 1001 | // convert to V8 buffer 1002 | v8::Local v8Buffer = Nan::NewBuffer((char *)v->data, (uint32_t)v->length).ToLocalChecked(); 1003 | obj->Set(colName, v8Buffer); 1004 | // Nan will free the memory of the buffer 1005 | delete v; 1006 | break; 1007 | } 1008 | break; 1009 | default: 1010 | char msg[128]; 1011 | snprintf(msg, sizeof(msg), "reateV8ObjectFromRow: Unhandled type: %d", 1012 | col->type); 1013 | std::string strMsg = std::string(msg); 1014 | throw NodeOracleException(strMsg); 1015 | break; 1016 | } 1017 | } 1018 | } 1019 | return obj; 1020 | } 1021 | 1022 | Local Connection::CreateV8ArrayFromRows(vector columns, 1023 | vector* rows) { 1024 | size_t totalRows = rows->size(); 1025 | Local retRows = Nan::New(totalRows); 1026 | uint32_t index = 0; 1027 | for (vector::iterator iterator = rows->begin(), end = rows->end(); 1028 | iterator != end; ++iterator, index++) { 1029 | row_t* currentRow = *iterator; 1030 | Local obj = CreateV8ObjectFromRow(columns, currentRow); 1031 | retRows->Set(index, obj); 1032 | } 1033 | return retRows; 1034 | } 1035 | 1036 | Local Connection::CreateV8ArrayFromCols(std::vector columns) 1037 | { 1038 | Local v8cols = Nan::New(columns.size()); 1039 | uint32_t index = 0; 1040 | for (std::vector::iterator iterator = columns.begin(), end =columns.end(); iterator != end; ++iterator, ++index) 1041 | { 1042 | column_t* col = *iterator; 1043 | Local v8col = Nan::New(); 1044 | v8col->Set(Nan::New("name").ToLocalChecked(), Nan::New(col->name.c_str()).ToLocalChecked()); 1045 | v8col->Set(Nan::New("type").ToLocalChecked(), Nan::New((double)(col->type))); 1046 | v8cols->Set(index, v8col); 1047 | } 1048 | return v8cols; 1049 | } 1050 | 1051 | void Connection::EIO_AfterExecute(uv_work_t* req) { 1052 | Nan::HandleScope scope; 1053 | ExecuteBaton* baton = CONTAINER_OF(req, ExecuteBaton, work_req); 1054 | 1055 | try { 1056 | Local argv[2]; 1057 | handleResult(baton, argv); 1058 | baton->callback->Call(2, argv); 1059 | } catch (NodeOracleException &ex) { 1060 | Local argv[2]; 1061 | argv[0] = Nan::Error(ex.getMessage().c_str()); 1062 | argv[1] = Nan::Undefined(); 1063 | baton->callback->Call(2, argv); 1064 | } catch (const exception &ex) { 1065 | Local argv[2]; 1066 | argv[0] = Nan::Error(ex.what()); 1067 | argv[1] = Nan::Undefined(); 1068 | baton->callback->Call(2, argv); 1069 | } 1070 | 1071 | delete baton; 1072 | } 1073 | 1074 | void Connection::handleResult(ExecuteBaton* baton, Local (&argv)[2]) { 1075 | try { 1076 | if (baton->error) { 1077 | argv[0] = Nan::Error(baton->error->c_str()); 1078 | argv[1] = Nan::Undefined(); 1079 | } else { 1080 | argv[0] = Nan::Undefined(); 1081 | if (baton->rows) { 1082 | Local obj = CreateV8ArrayFromRows(baton->columns, baton->rows); 1083 | if (baton->getColumnMetaData) { 1084 | obj->Set(Nan::New("columnMetaData").ToLocalChecked(), 1085 | CreateV8ArrayFromCols(baton->columns)); 1086 | } 1087 | argv[1] = obj; 1088 | } else { 1089 | Local obj = Nan::New(); 1090 | obj->Set(Nan::New("updateCount").ToLocalChecked(), Nan::New(baton->updateCount)); 1091 | 1092 | /* Note: attempt to keep backward compatability here: existing users of this library will have code that expects a single out param 1093 | called 'returnParam'. For multiple out params, the first output will continue to be called 'returnParam' and subsequent outputs 1094 | will be called 'returnParamX'. 1095 | */ 1096 | uint32_t index = 0; 1097 | for (vector::iterator iterator = baton->outputs->begin(), 1098 | end = baton->outputs->end(); iterator != end; ++iterator, index++) { 1099 | output_t* output = *iterator; 1100 | char msg[256]; 1101 | if (index > 0) { 1102 | snprintf(msg, sizeof(msg), "returnParam%d", index); 1103 | } else { 1104 | snprintf(msg, sizeof(msg), "returnParam"); 1105 | } 1106 | std::string returnParam(msg); 1107 | Local prop = Nan::New(returnParam.c_str()).ToLocalChecked(); 1108 | switch (output->type) { 1109 | case OutParam::OCCIINT: 1110 | obj->Set(prop, Nan::New(output->intVal)); 1111 | break; 1112 | case OutParam::OCCISTRING: 1113 | obj->Set(prop, Nan::New(output->strVal.c_str()).ToLocalChecked()); 1114 | break; 1115 | case OutParam::OCCIDOUBLE: 1116 | obj->Set(prop, Nan::New(output->doubleVal)); 1117 | break; 1118 | case OutParam::OCCIFLOAT: 1119 | obj->Set(prop, Nan::New(output->floatVal)); 1120 | break; 1121 | case OutParam::OCCICURSOR: 1122 | obj->Set(prop, 1123 | CreateV8ArrayFromRows(output->columns, output->rows)); 1124 | break; 1125 | case OutParam::OCCICLOB: { 1126 | obj->Set(prop, Nan::New((const char *)output->bufVal, output->bufLength).ToLocalChecked()); 1127 | delete[] output->bufVal; 1128 | break; 1129 | } 1130 | case OutParam::OCCIBLOB: { 1131 | // convert to V8 buffer 1132 | v8::Local v8Buffer = Nan::NewBuffer((char *)output->bufVal, output->bufLength).ToLocalChecked(); 1133 | obj->Set(prop, v8Buffer); 1134 | // Memory will be freed by NewBuffer GC 1135 | break; 1136 | } 1137 | case OutParam::OCCIDATE: 1138 | obj->Set(prop, OracleDateToV8Date(&output->dateVal)); 1139 | break; 1140 | case OutParam::OCCITIMESTAMP: 1141 | obj->Set(prop, OracleTimestampToV8Date(&output->timestampVal)); 1142 | break; 1143 | case OutParam::OCCINUMBER: 1144 | obj->Set(prop, Nan::New((double) output->numberVal)); 1145 | break; 1146 | default: 1147 | char msg[128]; 1148 | snprintf(msg, sizeof(msg), "Unknown OutParam type: %d", 1149 | output->type); 1150 | std::string strMsg = std::string(msg); 1151 | throw NodeOracleException(strMsg); 1152 | break; 1153 | } 1154 | } 1155 | argv[1] = obj; 1156 | } 1157 | } 1158 | } catch (NodeOracleException &ex) { 1159 | Local argv[2]; 1160 | argv[0] = Nan::Error(ex.getMessage().c_str()); 1161 | argv[1] = Nan::Undefined(); 1162 | baton->callback->Call(2, argv); 1163 | } catch (const std::exception &ex) { 1164 | Local argv[2]; 1165 | argv[0] = Nan::Error(ex.what()); 1166 | argv[1] = Nan::Undefined(); 1167 | baton->callback->Call(2, argv); 1168 | } 1169 | 1170 | } 1171 | 1172 | void Connection::setConnection(oracle::occi::Environment* environment, 1173 | ConnectionPool* _connectionPool, 1174 | oracle::occi::Connection* connection) { 1175 | m_environment = environment; 1176 | connectionPool = _connectionPool; 1177 | if (connectionPool) { 1178 | m_environment = connectionPool->getEnvironment(); 1179 | } 1180 | m_connection = connection; 1181 | } 1182 | 1183 | NAN_METHOD(Connection::ExecuteSync) { 1184 | Nan::HandleScope scope; 1185 | Connection* connection = Nan::ObjectWrap::Unwrap(info.This()); 1186 | 1187 | REQ_STRING_ARG(0, sql); 1188 | REQ_ARRAY_ARG(1, values); 1189 | Local options; 1190 | if (info.Length() > 2 && info[2]->IsObject() && !info[2]->IsFunction()) 1191 | options = Local::Cast(info[2]); 1192 | 1193 | String::Utf8Value sqlVal(sql); 1194 | 1195 | ExecuteBaton* baton = new ExecuteBaton(connection->m_environment, NULL, connection->m_connection, 1196 | connection->getAutoCommit(), connection->getPrefetchRowCount(), 1197 | *sqlVal, values, options); 1198 | EIO_Execute(&baton->work_req); 1199 | Local argv[2]; 1200 | handleResult(baton, argv); 1201 | 1202 | if (baton->error) { 1203 | delete baton; 1204 | return Nan::ThrowError(argv[0]); 1205 | } 1206 | 1207 | delete baton; 1208 | info.GetReturnValue().Set(argv[1]); 1209 | } 1210 | 1211 | 1212 | oracle::occi::Statement* Connection::CreateStatement(ExecuteBaton* baton) { 1213 | baton->rows = NULL; 1214 | baton->error = NULL; 1215 | 1216 | if (! baton->m_connection) { 1217 | baton->error = new std::string("Connection already closed"); 1218 | return NULL; 1219 | } 1220 | try { 1221 | oracle::occi::Statement* stmt = baton->m_connection->createStatement(baton->sql); 1222 | stmt->setAutoCommit(baton->m_autoCommit); 1223 | if (baton->m_prefetchRowCount > 0) { 1224 | stmt->setPrefetchRowCount(baton->m_prefetchRowCount); 1225 | } 1226 | return stmt; 1227 | } catch(oracle::occi::SQLException &ex) { 1228 | baton->error = new string(ex.getMessage()); 1229 | return NULL; 1230 | } 1231 | } 1232 | 1233 | buffer_t* Connection::readClob(oracle::occi::Clob& clobVal, int charForm) { 1234 | clobVal.open(oracle::occi::OCCI_LOB_READONLY); 1235 | switch (charForm) { 1236 | case SQLCS_IMPLICIT: 1237 | clobVal.setCharSetForm(oracle::occi::OCCI_SQLCS_IMPLICIT); 1238 | break; 1239 | case SQLCS_NCHAR: 1240 | clobVal.setCharSetForm(oracle::occi::OCCI_SQLCS_NCHAR); 1241 | break; 1242 | case SQLCS_EXPLICIT: 1243 | clobVal.setCharSetForm(oracle::occi::OCCI_SQLCS_EXPLICIT); 1244 | break; 1245 | case SQLCS_FLEXIBLE: 1246 | clobVal.setCharSetForm(oracle::occi::OCCI_SQLCS_FLEXIBLE); 1247 | break; 1248 | } 1249 | 1250 | unsigned int clobCharCount = clobVal.length(); 1251 | // maximum 4 bytes in a encoded character so the buffer is 4 times as large 1252 | unsigned char* lob = new unsigned char[clobCharCount * 4]; 1253 | unsigned int totalBytesRead = 0; 1254 | oracle::occi::Stream* instream = clobVal.getStream(1, 0); 1255 | // chunk size is set when the table is created 1256 | size_t chunkSize = clobVal.getChunkSize(); 1257 | char* buffer = new char[chunkSize]; 1258 | memset(buffer, 0, chunkSize); 1259 | int numBytesRead = 0; 1260 | while (numBytesRead != -1) { 1261 | numBytesRead = instream->readBuffer(buffer, chunkSize); 1262 | if (numBytesRead > 0) { 1263 | memcpy(lob + totalBytesRead, buffer, numBytesRead); 1264 | totalBytesRead += numBytesRead; 1265 | } 1266 | } 1267 | clobVal.closeStream(instream); 1268 | clobVal.close(); 1269 | delete[] buffer; 1270 | 1271 | buffer_t* b = new buffer_t(); 1272 | b->data = lob; 1273 | b->length = totalBytesRead; 1274 | 1275 | return b; 1276 | } 1277 | 1278 | buffer_t* Connection::readBlob(oracle::occi::Blob& blobVal) { 1279 | blobVal.open(oracle::occi::OCCI_LOB_READONLY); 1280 | unsigned int lobLength = blobVal.length(); 1281 | unsigned char* lob = new unsigned char[lobLength]; 1282 | unsigned int totalBytesRead = 0; 1283 | oracle::occi::Stream* instream = blobVal.getStream(1, 0); 1284 | // chunk size is set when the table is created 1285 | size_t chunkSize = blobVal.getChunkSize(); 1286 | char* buffer = new char[chunkSize]; 1287 | memset(buffer, 0, chunkSize); 1288 | int numBytesRead = 0; 1289 | while (numBytesRead != -1) { 1290 | numBytesRead = instream->readBuffer(buffer, chunkSize); 1291 | if (numBytesRead > 0) { 1292 | memcpy(lob + totalBytesRead, buffer, numBytesRead); 1293 | totalBytesRead += numBytesRead; 1294 | } 1295 | } 1296 | blobVal.closeStream(instream); 1297 | blobVal.close(); 1298 | delete[] buffer; 1299 | 1300 | buffer_t* b = new buffer_t(); 1301 | b->data = lob; 1302 | b->length = totalBytesRead; 1303 | return b; 1304 | } 1305 | 1306 | void Connection::ExecuteStatement(ExecuteBaton* baton, oracle::occi::Statement* stmt) { 1307 | oracle::occi::ResultSet* rs = NULL; 1308 | 1309 | int outputParam = SetValuesOnStatement(stmt, baton->values); 1310 | if (baton->error) goto cleanup; 1311 | 1312 | if (!baton->outputs) baton->outputs = new std::vector(); 1313 | 1314 | try { 1315 | int status = stmt->execute(); 1316 | if (status == oracle::occi::Statement::UPDATE_COUNT_AVAILABLE) { 1317 | baton->updateCount = stmt->getUpdateCount(); 1318 | if (outputParam >= 0) { 1319 | for (vector::iterator iterator = baton->outputs->begin(), end = baton->outputs->end(); iterator != end; ++iterator) { 1320 | output_t* output = *iterator; 1321 | oracle::occi::ResultSet* rs; 1322 | switch(output->type) { 1323 | case OutParam::OCCIINT: 1324 | output->intVal = stmt->getInt(output->index); 1325 | break; 1326 | case OutParam::OCCISTRING: 1327 | output->strVal = string(stmt->getString(output->index)); 1328 | break; 1329 | case OutParam::OCCIDOUBLE: 1330 | output->doubleVal = stmt->getDouble(output->index); 1331 | break; 1332 | case OutParam::OCCIFLOAT: 1333 | output->floatVal = stmt->getFloat(output->index); 1334 | break; 1335 | case OutParam::OCCICURSOR: 1336 | rs = stmt->getCursor(output->index); 1337 | if (baton->m_prefetchRowCount > 0) { 1338 | rs->setPrefetchRowCount(baton->m_prefetchRowCount); 1339 | } 1340 | CreateColumnsFromResultSet(rs, output->columns); 1341 | if (baton->error) goto cleanup; 1342 | output->rows = new vector(); 1343 | while(rs->next()) { 1344 | row_t* row = CreateRowFromCurrentResultSetRow(rs, output->columns); 1345 | if (baton->error) goto cleanup; 1346 | output->rows->push_back(row); 1347 | } 1348 | break; 1349 | case OutParam::OCCICLOB: { 1350 | output->clobVal = stmt->getClob(output->index); 1351 | buffer_t *buf = Connection::readClob(output->clobVal); 1352 | output->bufVal = buf->data; 1353 | output->bufLength = buf->length; 1354 | delete buf; 1355 | } 1356 | break; 1357 | case OutParam::OCCIBLOB: { 1358 | output->blobVal = stmt->getBlob(output->index); 1359 | buffer_t *buf = Connection::readBlob(output->blobVal); 1360 | output->bufVal = buf->data; 1361 | output->bufLength = buf->length; 1362 | delete buf; 1363 | } 1364 | break; 1365 | case OutParam::OCCIDATE: 1366 | output->dateVal = stmt->getDate(output->index); 1367 | break; 1368 | case OutParam::OCCITIMESTAMP: 1369 | output->timestampVal = stmt->getTimestamp(output->index); 1370 | break; 1371 | case OutParam::OCCINUMBER: 1372 | output->numberVal = stmt->getNumber(output->index); 1373 | break; 1374 | default: 1375 | { 1376 | ostringstream oss; 1377 | oss << "Unknown OutParam type: " << output->type; 1378 | baton->error = new std::string(oss.str()); 1379 | goto cleanup; 1380 | } 1381 | } 1382 | } 1383 | } 1384 | } else if (status == oracle::occi::Statement::RESULT_SET_AVAILABLE) { 1385 | rs = stmt->getResultSet(); 1386 | if (baton->m_prefetchRowCount > 0) { 1387 | rs->setPrefetchRowCount(baton->m_prefetchRowCount); 1388 | } 1389 | CreateColumnsFromResultSet(rs, baton->columns); 1390 | if (baton->error) goto cleanup; 1391 | baton->rows = new vector(); 1392 | 1393 | while(rs->next()) { 1394 | row_t* row = CreateRowFromCurrentResultSetRow(rs, baton->columns); 1395 | if (baton->error) goto cleanup; 1396 | baton->rows->push_back(row); 1397 | } 1398 | } 1399 | } catch (oracle::occi::SQLException &ex) { 1400 | baton->error = new string(ex.getMessage()); 1401 | } catch (NodeOracleException &ex) { 1402 | baton->error = new string(ex.getMessage()); 1403 | } catch (const exception& ex) { 1404 | baton->error = new string(ex.what()); 1405 | } catch (...) { 1406 | baton->error = new string("Unknown exception thrown from OCCI"); 1407 | } 1408 | cleanup: 1409 | if (rs) { 1410 | stmt->closeResultSet(rs); 1411 | rs = NULL; 1412 | } 1413 | } 1414 | 1415 | --------------------------------------------------------------------------------