├── .npmignore ├── .gitignore ├── test ├── image.png ├── test.js └── run.js ├── lib ├── firebird.msg ├── errors.js ├── messages.js └── serialize.js ├── .travis.yml ├── package.json ├── index.d.ts ├── README.md └── LICENSE /.npmignore: -------------------------------------------------------------------------------- 1 | test/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test/*.fdb 2 | *.fdb 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /test/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdnetwork/node-firebird/HEAD/test/image.png -------------------------------------------------------------------------------- /lib/firebird.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdnetwork/node-firebird/HEAD/lib/firebird.msg -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | - "iojs" 5 | notifications: 6 | email: 7 | - mapopa@gmail.com 8 | before_install: 9 | - sudo apt-get update -qq 10 | - sudo apt-get install -qq firebird2.5-superclassic 11 | - sudo sed /ENABLE_FIREBIRD_SERVER=/s/no/yes/ -i /etc/default/firebird2.5 12 | - cat /etc/default/firebird2.5 | grep ENABLE_FIREBIRD_SERVER 13 | - sudo service firebird2.5-superclassic start 14 | install: 15 | #script: tap --gc test/js/*-test.js 16 | -------------------------------------------------------------------------------- /lib/errors.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | GDSError: GDSError 3 | }; 4 | 5 | function GDSError(gdsObject) { 6 | this.message = gdsObject && gdsObject.message || ''; 7 | this.code = gdsObject && gdsObject.status && gdsObject.status.length > 0 && gdsObject.status[0].gdscode || -1; 8 | 9 | if (Error.captureStackTrace) { 10 | Error.captureStackTrace(this, GDSError); 11 | } else { 12 | this.stack = (new Error()).stack; 13 | } 14 | } 15 | 16 | GDSError.prototype = Object.create(Error.prototype); 17 | GDSError.prototype.name = 'GDSError'; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-firebird-dev", 3 | "version": "1.3.2", 4 | "description": "Pure JavaScript and Asynchronous Firebird client for Node.js.", 5 | "keywords": [ 6 | "firebird", 7 | "database", 8 | "rdbms", 9 | "sql" 10 | ], 11 | "homepage": "https://github.com/hgourvest/node-firebird", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/hgourvest/node-firebird" 15 | }, 16 | "author": { 17 | "name": "Henri Gourvest", 18 | "email": "hgourvest@gmail.com" 19 | }, 20 | "contributors": [ 21 | "Popa Marius Adrian ", 22 | "Peter Širka ", 23 | "Arnaud Le Roy " 24 | ], 25 | "main": "./lib", 26 | "licenses": [ 27 | { 28 | "type": "MPL-2.0", 29 | "url": "https://raw.githubusercontent.com/hgourvest/node-firebird/master/LICENSE" 30 | } 31 | ], 32 | "dependencies": { 33 | "long": "^2.2.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var fb = require('../lib'); 2 | var fs = require('fs'); 3 | var os = require('os'); 4 | 5 | var assert = require('assert'); 6 | var Path = require('path'); 7 | var now = new Date(); 8 | 9 | var config = { 10 | 11 | // Problem with privileges in OSX 12 | // database: Path.join(os.tmpdir(), 'test-' + new Date().getTime() + '.fdb'), 13 | 14 | database: Path.join(process.cwd(), 'test-' + new Date().getTime() + '.fdb'), 15 | host: '127.0.0.1', // default 16 | port: 3050, // default 17 | user: 'SYSDBA', // default 18 | password: 'masterkey', // default 19 | role: null, // default 20 | pageSize: 4096, // default when creating database 21 | timeout: 3000 // default query timeout 22 | } 23 | 24 | Array.prototype.async = function(cb) { 25 | 26 | var self = this; 27 | var item = self.shift(); 28 | 29 | if (item === undefined) { 30 | if (cb) 31 | cb(); 32 | return; 33 | } 34 | 35 | item(function() { 36 | setImmediate(function() { 37 | self.async(cb); 38 | }); 39 | }); 40 | }; 41 | 42 | fb.attachOrCreate(config, function (err, db) { 43 | 44 | if (err) 45 | throw err.message; 46 | 47 | database = db; 48 | 49 | var task = []; 50 | 51 | task.push(test_create); 52 | task.push(test_insert); 53 | 54 | task.push(function(next) { 55 | db.detach(next); 56 | }); 57 | 58 | task.async(); 59 | }); 60 | 61 | function test_create(next) { 62 | 63 | var name = 'TEST ---> test_create'; 64 | console.time(name); 65 | 66 | // Create table 67 | database.query('CREATE TABLE test (ID INT, PARENT BIGINT, NAME VARCHAR(50), FILE BLOB, CREATED TIMESTAMP)', function(err) { 68 | console.timeEnd(name); 69 | next(); 70 | }); 71 | } 72 | 73 | function test_insert(next) { 74 | 75 | var name = 'TEST ---> test_insert'; 76 | var query = []; 77 | 78 | console.time(name); 79 | var lng = 862304020112911; 80 | database.query('INSERT INTO test (ID, NAME, CREATED, PARENT) VALUES(?, ?, ?, ?) RETURNING ID', [3, 'Firebird 3', now, 862304020112911], function(err, r) { 81 | 82 | database.query('SELECT PARENT, CAST(PARENT AS VARCHAR(20)) AS NEVIEM FROM test', function(e, r) { 83 | console.log(r); 84 | console.timeEnd(name); 85 | next(); 86 | }); 87 | 88 | }); 89 | } -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for node-firebird 0.8.3 2 | // Project: node-firebird 3 | // Definitions by: Marco Warm 4 | 5 | declare module 'node-firebird' { 6 | type DatabaseCallback = (err: any, db: Database) => void; 7 | 8 | type TransactionCallback = (err: Options, transaction: Transaction) => void; 9 | type QueryCallback = (err: any, result: any[]) => void; 10 | type SimpleCallback = (err: any) => void; 11 | type SequentialCallback = (row: any, index: number, next: any) => void; 12 | 13 | export const ISOLATION_READ_UNCOMMITTED: number[]; 14 | export const ISOLATION_READ_COMMITED: number[]; 15 | export const ISOLATION_REPEATABLE_READ: number[]; 16 | export const ISOLATION_SERIALIZABLE: number[]; 17 | export const ISOLATION_READ_COMMITED_READ_ONLY: number[]; 18 | 19 | export type Isolation = number[]; 20 | 21 | export interface Database { 22 | detach(callback?: SimpleCallback): void; 23 | transaction(isolation: Isolation, callback: TransactionCallback): void; 24 | query(query: string, params: any[], callback: QueryCallback): void; 25 | execute(query: string, params: any[], callback: QueryCallback): void; 26 | sequentially(query: string, params: any[], rowCallback: SequentialCallback, callback: SimpleCallback, asArray?: boolean): void; 27 | } 28 | 29 | export interface Transaction { 30 | query(query: string, params: any[], callback: QueryCallback): void; 31 | execute(query: string, params: any[], callback: QueryCallback): void; 32 | commit(callback?: SimpleCallback): void; 33 | rollback(callback?: SimpleCallback): void; 34 | sequentially(query: string, params: any[], rowCallback: SequentialCallback, callback: SimpleCallback, asArray?: boolean): void; 35 | } 36 | 37 | export interface Options { 38 | host?: string; 39 | port?: number; 40 | database?: string; 41 | user?: string; 42 | password?: string; 43 | lowercase_keys?: boolean; 44 | role?: string; 45 | pageSize?: number; 46 | } 47 | 48 | export interface ConnectionPool { 49 | get(callback: DatabaseCallback): void; 50 | destroy(): void; 51 | } 52 | 53 | export function attach(options: Options, callback: DatabaseCallback): void; 54 | export function escape(value: string): string; 55 | export function create(options: Options, callback: DatabaseCallback): void; 56 | export function attachOrCreate(options: Options, callback: DatabaseCallback): void; 57 | export function pool(max: number,options: Options, callback: DatabaseCallback): ConnectionPool; 58 | 59 | } 60 | -------------------------------------------------------------------------------- /lib/messages.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | const 4 | //ISC_MASK = 0x14000000, // Defines the code as a valid ISC code 5 | FAC_MASK = 0x00FF0000, // Specifies the facility where the code is located 6 | CODE_MASK = 0x0000FFFF, // Specifies the code in the message file 7 | CLASS_MASK = 0xF0000000; // Defines the code as warning, error, info, or other 8 | 9 | 10 | var msg_info = { 11 | bucket_size: null, 12 | top_tree: null, 13 | levels: null, 14 | buffer: null, 15 | idxbuffer: null, 16 | initialized: false 17 | } 18 | 19 | var readMsgFile = function () { 20 | if (msg_info.initialized == false) { 21 | try { 22 | var buffer = fs.readFileSync(__dirname + "/firebird.msg") 23 | msg_info.initialized = true; 24 | msg_info.idxbuffer = buffer.slice(0); 25 | msg_info.bucket_size = buffer.readUInt16LE(2); 26 | msg_info.top_tree = buffer.readUInt32LE(4); 27 | msg_info.levels = buffer.readUInt16LE(12); 28 | msg_info.buffer = new Buffer(bucket_size); 29 | 30 | } catch (er) { 31 | msg_info.initialized = true; 32 | } 33 | } 34 | return msg_info; 35 | } 36 | 37 | var msgNumber = exports.msgNumber = function (facility, code) { 38 | return (facility * 10000 + code); 39 | }; 40 | 41 | var getCode = exports.getCode = function (code) { 42 | return (code & CODE_MASK) 43 | }; 44 | 45 | var getFacility = exports.getFacility = function (code) { 46 | return (code & FAC_MASK) >> 16; 47 | }; 48 | 49 | exports.getClass = function (code) { 50 | return (code & CLASS_MASK) >> 30 51 | }; 52 | 53 | exports.lookupMessages = function (status, callback) { 54 | var buffer; 55 | var top_tree; 56 | var levels; 57 | function lookup(item, callback) { 58 | 59 | var code = msgNumber(getFacility(item.gdscode), getCode(item.gdscode)); 60 | 61 | function readIndex(stackSize, position) { 62 | 63 | function readNode(from) { 64 | var ret = {}; 65 | ret.code = buffer.readUInt32LE(from); 66 | ret.seek = buffer.readUInt32LE(from + 4); 67 | return ret; 68 | } 69 | 70 | buffer = msg_info.idxbuffer.slice(position, position+msg_info.bucket_size); 71 | var bufferSize = buffer.length; 72 | //fs.read(handle, buffer, 0, bucket_size, position, function(err, bufferSize) { 73 | 74 | if (bufferSize <= 0) { 75 | callback(); 76 | return; 77 | } 78 | 79 | if (stackSize === levels) { 80 | search(); 81 | return; 82 | } 83 | 84 | var from = 0; 85 | var node = readNode(from); 86 | 87 | while (true) { 88 | 89 | if (node.code >= code) { 90 | readIndex(stackSize + 1, node.seek); 91 | break; 92 | } 93 | 94 | from += 8; 95 | if (from >= bufferSize) { 96 | callback(); 97 | break; 98 | } 99 | 100 | node = readNode(from); 101 | } 102 | //}); 103 | } 104 | 105 | function search() { 106 | 107 | function readRec(from) { 108 | 109 | function align(v) { 110 | return (v + 3) & ~3; 111 | } 112 | 113 | var ret = {}; 114 | ret.code = buffer.readUInt32LE(from); 115 | ret.length = buffer.readUInt16LE(from + 4); 116 | 117 | if (ret.code == code) { 118 | from += 8; 119 | ret.text = buffer.toString(undefined, from, from + ret.length); 120 | } else 121 | ret.seek = from + align(8 + ret.length, 4); 122 | 123 | return ret; 124 | } 125 | 126 | var rec = readRec(0); 127 | 128 | while (rec.seek) { 129 | if (rec.seek >= buffer.length) 130 | break; 131 | else 132 | rec = readRec(rec.seek); 133 | } 134 | 135 | var str = rec.text; 136 | if (item.params) { 137 | for (var i = 0; i < item.params.length; i++) 138 | str = str.replace('@' + String(i + 1), item.params[i]); 139 | } 140 | 141 | callback(str); 142 | } 143 | 144 | readIndex(1, top_tree); 145 | } 146 | 147 | 148 | 149 | var i = 0; 150 | var text; 151 | function loop() { 152 | lookup(status[i], function (line) { 153 | if (text) 154 | text = text + ', ' + line 155 | else 156 | text = line; 157 | 158 | if (i === status.length - 1) { 159 | callback(text); 160 | } else { 161 | i++; 162 | loop(); 163 | } 164 | }); 165 | } 166 | var imsg = readMsgFile(); 167 | if (imsg.idxbuffer === null) 168 | return callback(); 169 | top_tree = imsg.top_tree; 170 | levels = imsg.levels; 171 | try { 172 | loop(0); 173 | } catch (err) { 174 | return callback(text); 175 | } 176 | }; 177 | -------------------------------------------------------------------------------- /lib/serialize.js: -------------------------------------------------------------------------------- 1 | 2 | var Long = require('long'); 3 | 4 | function align(n) { 5 | return (n + 3) & ~3; 6 | } 7 | 8 | /*************************************** 9 | * 10 | * BLR Writer 11 | * 12 | ***************************************/ 13 | 14 | const 15 | MAX_STRING_SIZE = 255; 16 | 17 | var BlrWriter = exports.BlrWriter = function(size){ 18 | this.buffer = new Buffer(size || 32); 19 | this.pos = 0; 20 | }; 21 | 22 | BlrWriter.prototype.addByte = function (b) { 23 | this.ensure(1); 24 | this.buffer.writeUInt8(b, this.pos); 25 | this.pos++; 26 | }; 27 | 28 | BlrWriter.prototype.addShort = function (b) { 29 | this.ensure(1); 30 | this.buffer.writeInt8(b, this.pos); 31 | this.pos++; 32 | }; 33 | 34 | BlrWriter.prototype.addSmall = function (b) { 35 | this.ensure(2); 36 | this.buffer.writeInt16LE(b, this.pos); 37 | this.pos += 2; 38 | }; 39 | 40 | BlrWriter.prototype.addWord = function (b) { 41 | this.ensure(2); 42 | this.buffer.writeUInt16LE(b, this.pos); 43 | this.pos += 2; 44 | }; 45 | 46 | BlrWriter.prototype.addInt32 = function (b) { 47 | this.ensure(4); 48 | this.buffer.writeUInt32LE(b, this.pos); 49 | this.pos += 4; 50 | }; 51 | 52 | BlrWriter.prototype.addByteInt32 = function (c, b) { 53 | this.addByte(c); 54 | this.ensure(4); 55 | this.buffer.writeUInt32LE(b, this.pos); 56 | this.pos += 4; 57 | }; 58 | 59 | BlrWriter.prototype.addNumeric = function (c, v) { 60 | 61 | if (v < 256){ 62 | this.ensure(3); 63 | this.buffer.writeUInt8(c, this.pos); 64 | this.pos++; 65 | this.buffer.writeUInt8(1, this.pos); 66 | this.pos++; 67 | this.buffer.writeUInt8(v, this.pos); 68 | this.pos++; 69 | return; 70 | } 71 | 72 | this.ensure(6); 73 | this.buffer.writeUInt8(c, this.pos); 74 | this.pos++; 75 | this.buffer.writeUInt8(4, this.pos); 76 | this.pos++; 77 | this.buffer.writeInt32BE(v, this.pos); 78 | this.pos += 4; 79 | 80 | }; 81 | 82 | BlrWriter.prototype.addBytes = function (b) { 83 | 84 | this.ensure(b.length); 85 | for (var i = 0, length = b.length; i < length; i++) { 86 | this.buffer.writeUInt8(b[i], this.pos); 87 | this.pos++; 88 | } 89 | }; 90 | 91 | BlrWriter.prototype.addString = function (c, s, encoding) { 92 | this.addByte(c); 93 | 94 | var len = Buffer.byteLength(s, encoding); 95 | if (len > MAX_STRING_SIZE) 96 | throw new Error('blr string is too big'); 97 | 98 | this.ensure(len + 1); 99 | this.buffer.writeUInt8(len, this.pos); 100 | this.pos++; 101 | this.buffer.write(s, this.pos, len, encoding); 102 | this.pos += len; 103 | }; 104 | 105 | BlrWriter.prototype.addBuffer = function (b) { 106 | this.addSmall(b.length); 107 | this.ensure(b.length); 108 | b.copy(this.buffer, this.pos); 109 | this.pos += b.length; 110 | }; 111 | 112 | BlrWriter.prototype.addString2 = function (c, s, encoding) { 113 | this.addByte(c); 114 | 115 | var len = Buffer.byteLength(s, encoding); 116 | if (len > MAX_STRING_SIZE* MAX_STRING_SIZE) 117 | throw new Error('blr string is too big'); 118 | 119 | this.ensure(len + 2); 120 | this.buffer.writeUInt16LE(len, this.pos); 121 | this.pos += 2; 122 | this.buffer.write(s, this.pos, len, encoding); 123 | this.pos += len; 124 | }; 125 | 126 | BlrWriter.prototype.addBuffer = function (b) { 127 | this.addSmall(b.length); 128 | this.ensure(b.length); 129 | b.copy(this.buffer, this.pos); 130 | this.pos += b.length; 131 | }; 132 | 133 | /*************************************** 134 | * 135 | * BLR Reader 136 | * 137 | ***************************************/ 138 | 139 | var BlrReader = exports.BlrReader = function(buffer) { 140 | this.buffer = buffer; 141 | this.pos = 0; 142 | }; 143 | 144 | BlrReader.prototype.readByteCode = function(){ 145 | return this.buffer.readUInt8(this.pos++); 146 | }; 147 | 148 | BlrReader.prototype.readInt32 = function () { 149 | var value = this.buffer.readUInt32LE(this.pos); 150 | this.pos += 4; 151 | return value; 152 | } 153 | 154 | BlrReader.prototype.readInt = function(){ 155 | var len = this.buffer.readUInt16LE(this.pos); 156 | this.pos += 2; 157 | var value; 158 | switch (len) { 159 | case 1: 160 | value = this.buffer.readInt8(this.pos); 161 | break; 162 | case 2: 163 | value = this.buffer.readInt16LE(this.pos); 164 | break; 165 | case 4: 166 | value = this.buffer.readInt32LE(this.pos) 167 | } 168 | this.pos += len; 169 | return value; 170 | }; 171 | 172 | BlrReader.prototype.readString = function(encoding){ 173 | 174 | var len = this.buffer.readUInt16LE(this.pos); 175 | var str; 176 | 177 | this.pos += 2; 178 | if (len <= 0) 179 | return ''; 180 | 181 | str = this.buffer.toString(encoding, this.pos, this.pos + len); 182 | this.pos += len; 183 | return str; 184 | }; 185 | 186 | BlrReader.prototype.readBuffer = function (encoding) { 187 | 188 | var len = this.buffer.readUInt16LE(this.pos); 189 | var str; 190 | 191 | this.pos += 2; 192 | if (len <= 0) 193 | return new Buffer(0); 194 | 195 | ret = new Buffer(len); 196 | this.buffer.copy(ret, 0, this.pos, this.pos + len); 197 | this.pos += len; 198 | return ret; 199 | }; 200 | 201 | BlrReader.prototype.readSegment = function() { 202 | 203 | var ret, tmp; 204 | var len = this.buffer.readUInt16LE(this.pos); 205 | 206 | this.pos += 2; 207 | 208 | while (len > 0) { 209 | 210 | if (ret) { 211 | tmp = ret; 212 | ret = new Buffer(tmp.length + len); 213 | tmp.copy(ret); 214 | this.buffer.copy(ret, tmp.length, this.pos, this.pos + len); 215 | } else { 216 | ret = new Buffer(len); 217 | this.buffer.copy(ret, 0, this.pos, this.pos + len); 218 | } 219 | 220 | this.pos += len; 221 | 222 | if (this.pos === this.buffer.length) 223 | break; 224 | 225 | len = this.buffer.readUInt16LE(this.pos); 226 | this.pos += 2; 227 | } 228 | 229 | // in some case len could be zero so ret is undefined 230 | return ret ? ret : new Buffer(0); 231 | }; 232 | 233 | /*************************************** 234 | * 235 | * XDR Writer 236 | * 237 | ***************************************/ 238 | 239 | var XdrWriter = exports.XdrWriter = function(size){ 240 | this.buffer = new Buffer(size || 32); 241 | this.pos = 0; 242 | }; 243 | 244 | XdrWriter.prototype.ensure = BlrWriter.prototype.ensure = function (len) { 245 | var newlen = this.buffer.length; 246 | 247 | while (newlen < this.pos + len) 248 | newlen *= 2 249 | 250 | if (this.buffer.length >= newlen) 251 | return; 252 | 253 | var b = new Buffer(newlen); 254 | this.buffer.copy(b); 255 | delete(this.buffer); 256 | this.buffer = b; 257 | }; 258 | 259 | XdrWriter.prototype.addInt = function (value) { 260 | this.ensure(4); 261 | this.buffer.writeInt32BE(value, this.pos); 262 | this.pos += 4; 263 | }; 264 | 265 | XdrWriter.prototype.addInt64 = function (value) { 266 | this.ensure(8); 267 | var l = Long.fromNumber(value); 268 | this.buffer.writeInt32BE(l.high, this.pos); 269 | this.pos += 4; 270 | this.buffer.writeInt32BE(l.low, this.pos); 271 | this.pos += 4; 272 | }; 273 | 274 | XdrWriter.prototype.addUInt = function (value) { 275 | this.ensure(4); 276 | this.buffer.writeUInt32BE(value, this.pos); 277 | this.pos += 4; 278 | }; 279 | 280 | XdrWriter.prototype.addString = function(s, encoding) { 281 | var len = Buffer.byteLength(s, encoding); 282 | var alen = align(len); 283 | this.ensure(alen + 4); 284 | this.buffer.writeInt32BE(len, this.pos); 285 | this.pos += 4; 286 | this.buffer.write(s, this.pos, len, encoding); 287 | this.pos += alen; 288 | }; 289 | 290 | XdrWriter.prototype.addText = function(s, encoding) { 291 | var len = Buffer.byteLength(s, encoding); 292 | var alen = align(len); 293 | this.ensure(alen); 294 | this.buffer.write(s, this.pos, len, encoding); 295 | this.pos += alen; 296 | }; 297 | 298 | XdrWriter.prototype.addBlr = function(blr) { 299 | var alen = align(blr.pos); 300 | this.ensure(alen + 4); 301 | this.buffer.writeInt32BE(blr.pos, this.pos); 302 | this.pos += 4; 303 | blr.buffer.copy(this.buffer, this.pos); 304 | this.pos += alen; 305 | }; 306 | 307 | XdrWriter.prototype.getData = function() { 308 | return this.buffer.slice(0, this.pos); 309 | }; 310 | 311 | XdrWriter.prototype.addDouble = function(value) { 312 | this.ensure(8); 313 | this.buffer.writeDoubleBE(value, this.pos); 314 | this.pos += 8; 315 | }; 316 | 317 | XdrWriter.prototype.addQuad = function(quad) { 318 | this.ensure(8); 319 | var b = this.buffer; 320 | b.writeInt32BE(quad.high, this.pos); 321 | this.pos += 4; 322 | b.writeInt32BE(quad.low, this.pos); 323 | this.pos += 4; 324 | }; 325 | 326 | /*************************************** 327 | * 328 | * XDR Reader 329 | * 330 | ***************************************/ 331 | 332 | var XdrReader = exports.XdrReader = function(buffer){ 333 | this.buffer = buffer; 334 | this.pos = 0; 335 | }; 336 | 337 | XdrReader.prototype.readInt = function () { 338 | var r = this.buffer.readInt32BE(this.pos); 339 | this.pos += 4; 340 | return r; 341 | }; 342 | 343 | XdrReader.prototype.readUInt = function () { 344 | var r = this.buffer.readUInt32BE(this.pos); 345 | this.pos += 4; 346 | return r; 347 | }; 348 | 349 | XdrReader.prototype.readInt64 = function () { 350 | var high = this.buffer.readInt32BE(this.pos); 351 | this.pos += 4; 352 | var low = this.buffer.readInt32BE(this.pos); 353 | this.pos += 4; 354 | return new Long(low, high).toNumber(); 355 | }; 356 | 357 | XdrReader.prototype.readShort = function () { 358 | var r = this.buffer.readInt16BE(this.pos); 359 | this.pos += 2; 360 | return r; 361 | }; 362 | 363 | XdrReader.prototype.readQuad = function () { 364 | var b = this.buffer; 365 | var high = b.readInt32BE(this.pos); 366 | this.pos += 4; 367 | var low = b.readInt32BE(this.pos); 368 | this.pos += 4; 369 | return {low: low, high: high} 370 | }; 371 | 372 | XdrReader.prototype.readFloat = function () { 373 | var r = this.buffer.readFloatBE(this.pos); 374 | this.pos += 4; 375 | return r; 376 | }; 377 | 378 | XdrReader.prototype.readDouble = function () { 379 | var r = this.buffer.readDoubleBE(this.pos); 380 | this.pos += 8; 381 | return r; 382 | }; 383 | 384 | XdrReader.prototype.readArray = function () { 385 | var len = this.readInt(); 386 | if (!len) 387 | return; 388 | var r = this.buffer.slice(this.pos, this.pos + len); 389 | this.pos += align(len); 390 | return r; 391 | }; 392 | 393 | XdrReader.prototype.readBuffer = function (len) { 394 | if (!arguments.length) 395 | len = this.readInt(); 396 | 397 | if (!len) 398 | return; 399 | 400 | var r = this.buffer.slice(this.pos, this.pos + len); 401 | this.pos += align(len); 402 | return r; 403 | }; 404 | 405 | XdrReader.prototype.readString = function (encoding) { 406 | var len = this.readInt(); 407 | return this.readText(len, encoding); 408 | }; 409 | 410 | XdrReader.prototype.readText = function (len, encoding) { 411 | if (len <= 0) 412 | return ''; 413 | 414 | var r = this.buffer.toString(encoding, this.pos, this.pos + len); 415 | this.pos += align(len); 416 | return r; 417 | }; 418 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Firebird Logo](https://www.totaljs.com/exports/firebird-logo.png) 2 | 3 | [![NPM version][npm-version-image]][npm-url] [![NPM downloads][npm-downloads-image]][npm-url] [![Mozilla License][license-image]][license-url] 4 | [![Build Status](https://travis-ci.org/mariuz/node-firebird.svg?branch=master)](https://travis-ci.org/mariuz/node-firebird) 5 | 6 | [![NPM](https://nodei.co/npm/node-firebird.png?downloads=true&downloadRank=true)](https://nodei.co/npm/node-firebird/) [![NPM](https://nodei.co/npm-dl/node-firebird.png?months=6&height=3)](https://nodei.co/npm/node-firebird/) 7 | # Pure JavaScript Firebird client for Node.js. 8 | 9 | Pure JavaScript and Asynchronous Firebird client for Node.js. [Firebird forum](https://groups.google.com/forum/#!forum/node-firebird) on Google Groups. 10 | 11 | __Firebird database on social networks__ 12 | 13 | - [Firebird on Google+](https://plus.google.com/111558763769231855886/posts) 14 | - [Firebird on Twitter](https://twitter.com/firebirdsql/) 15 | - [Firebird on Facebook](https://www.facebook.com/FirebirdSQL) 16 | 17 | __Changelog for version v0.9.x__ 18 | 19 | - Better blob management (warning API Breaking Changes) 20 | - Much unit-test 21 | - Better sequentially select (warning API Breaking Changes) 22 | - Added transation.sequentially 23 | - Bugs correction 24 | 25 | --- 26 | 27 | __Changelog for version v0.2.x__ 28 | 29 | - added auto-reconnect 30 | - added [sequentially selects](https://github.com/hgourvest/node-firebird/wiki/What-is-sequentially-selects) 31 | - events for connection (attach, detach, row, result, transaction, commit, rollback, error, etc.) 32 | - performance improvements 33 | - supports inserting/updating buffers and streams 34 | - reading blobs (sequentially) 35 | - pooling 36 | - `database.detach()` waits for last command 37 | - better unit-test 38 | - best of use with [total.js - web application framework for node.js](http://www.totaljs.com) 39 | 40 | --- 41 | 42 | - [Firebird documentation](http://www.firebirdsql.org/en/documentation/) 43 | - [Firebird limits and data types](http://www.firebirdsql.org/en/firebird-technical-specifications/) 44 | 45 | ## Installation 46 | 47 | ```bash 48 | npm install node-firebird 49 | ``` 50 | 51 | ## Usage 52 | 53 | ```js 54 | var Firebird = require('node-firebird'); 55 | ``` 56 | 57 | ### Methods 58 | 59 | - `Firebird.escape(value) -> return {String}` - prevent for SQL Injections 60 | - `Firebird.attach(options, function(err, db))` attach a database 61 | - `Firebird.create(options, function(err, db))` create a database 62 | - `Firebird.attachOrCreate(options, function(err, db))` attach or create database 63 | - `Firebird.pool(max, options, function(err, db)) -> return {Object}` create a connection pooling 64 | 65 | ## Connection types 66 | 67 | ### Connection options 68 | 69 | ```js 70 | var options = {}; 71 | 72 | options.host = '127.0.0.1'; 73 | options.port = 3050; 74 | options.database = 'database.fdb'; 75 | options.user = 'SYSDBA'; 76 | options.password = 'masterkey'; 77 | options.lowercase_keys = false; // set to true to lowercase keys 78 | options.role = null; // default 79 | options.pageSize = 4096; // default when creating database 80 | 81 | ``` 82 | 83 | ### Classic 84 | 85 | ```js 86 | Firebird.attach(options, function(err, db) { 87 | 88 | if (err) 89 | throw err; 90 | 91 | // db = DATABASE 92 | db.query('SELECT * FROM TABLE', function(err, result) { 93 | // IMPORTANT: close the connection 94 | db.detach(); 95 | }); 96 | 97 | }); 98 | ``` 99 | 100 | ### Pooling 101 | 102 | ```js 103 | // 5 = the number is count of opened sockets 104 | var pool = Firebird.pool(5, options); 105 | 106 | // Get a free pool 107 | pool.get(function(err, db) { 108 | 109 | if (err) 110 | throw err; 111 | 112 | // db = DATABASE 113 | db.query('SELECT * FROM TABLE', function(err, result) { 114 | // IMPORTANT: release the pool connection 115 | db.detach(); 116 | }); 117 | }); 118 | 119 | // Destroy pool 120 | pool.destroy(); 121 | ``` 122 | 123 | ## Database object (db) 124 | 125 | ### Methods 126 | 127 | - `db.query(query, [params], function(err, result))` - classic query, returns Array of Object 128 | - `db.execute(query, [params], function(err, result))` - classic query, returns Array of Array 129 | - `db.sequentially(query, [params], function(row, index, next), function(err), asArray)` - sequentially query 130 | - `db.detach(function(err))` detach a database 131 | - `db.transaction(isolation, function(err, transaction))` create transaction 132 | 133 | ### Transaction methods 134 | 135 | - `transaction.query(query, [params], function(err, result))` - classic query, returns Array of Object 136 | - `transaction.execute(query, [params], function(err, result))` - classic query, returns Array of Array 137 | - `transaction.sequentially(query, [params], function(row, index, next), function(err), asArray)` - sequentially query 138 | - `transaction.commit(function(err))` commit current transaction 139 | - `transaction.rollback(function(err))` rollback current transaction 140 | 141 | ## Examples 142 | 143 | ### PARAMETRIZED QUERIES 144 | 145 | ### Parameters 146 | 147 | ```js 148 | Firebird.attach(options, function(err, db) { 149 | 150 | if (err) 151 | throw err; 152 | 153 | // db = DATABASE 154 | db.query('INSERT INTO USERS (ID, ALIAS, CREATED) VALUES(?, ?, ?) RETURNING ID', [1, 'Pe\'ter', new Date()] function(err, result) { 155 | console.log(result[0].id); 156 | db.query('SELECT * FROM USERS WHERE Alias=?', ['Peter'], function(err, result) { 157 | console.log(result); 158 | db.detach(); 159 | }); 160 | }); 161 | }); 162 | ``` 163 | 164 | ### BLOB (stream) 165 | 166 | ```js 167 | Firebird.attach(options, function(err, db) { 168 | 169 | if (err) 170 | throw err; 171 | 172 | // db = DATABASE 173 | // INSERT STREAM as BLOB 174 | db.query('INSERT INTO USERS (ID, ALIAS, FILE) VALUES(?, ?, ?)', [1, 'Peter', fs.createReadStream('/users/image.jpg')] function(err, result) { 175 | // IMPORTANT: close the connection 176 | db.detach(); 177 | }); 178 | }); 179 | ``` 180 | 181 | ### BLOB (buffer) 182 | 183 | ```js 184 | Firebird.attach(options, function(err, db) { 185 | 186 | if (err) 187 | throw err; 188 | 189 | // db = DATABASE 190 | // INSERT BUFFER as BLOB 191 | db.query('INSERT INTO USERS (ID, ALIAS, FILE) VALUES(?, ?, ?)', [1, 'Peter', fs.readFileSync('/users/image.jpg')] function(err, result) { 192 | // IMPORTANT: close the connection 193 | db.detach(); 194 | }); 195 | }); 196 | ``` 197 | 198 | ### READING BLOBS (ASYNCHRONOUS) 199 | 200 | ```js 201 | // async (only with transaction) 202 | Firebird.attach(options, function(err, db) { 203 | 204 | if (err) 205 | throw err; 206 | db.transaction(function(err, transaction) { 207 | // db = DATABASE 208 | db.query('SELECT ID, ALIAS, USERPICTURE FROM USER', function(err, rows) { 209 | 210 | if (err) 211 | throw err; 212 | 213 | // first row 214 | rows[0].userpicture(function(err, name, e) { 215 | 216 | if (err) 217 | throw err; 218 | 219 | // +v0.2.4 220 | // e.pipe(writeStream/Response); 221 | 222 | // e === EventEmitter 223 | e.on('data', function(chunk) { 224 | // reading data 225 | }); 226 | 227 | e.on('end', function() { 228 | // end reading 229 | // IMPORTANT: close the connection 230 | db.detach(); 231 | }); 232 | }); 233 | }); 234 | }); 235 | }); 236 | 237 | // sync blob are fetched automatically 238 | Firebird.attach(options, function(err, db) { 239 | 240 | if (err) 241 | throw err; 242 | 243 | // db = DATABASE 244 | db.query('SELECT ID, ALIAS, USERPICTURE FROM USER', function(err, rows) { 245 | 246 | if (err) 247 | throw err; 248 | 249 | // first row 250 | // userpicture is a Buffer that contain the blob data 251 | rows[0].userpicture; 252 | }); 253 | }); 254 | ``` 255 | 256 | ### STREAMING A BIG DATA 257 | 258 | ```js 259 | Firebird.attach(options, function(err, db) { 260 | 261 | if (err) 262 | throw err; 263 | 264 | // db = DATABASE 265 | db.sequentially('SELECT * FROM BIGTABLE', function(row, index, next) { 266 | 267 | // EXAMPLE 268 | stream.write(JSON.stringify(row)); 269 | next() 270 | }, function(err) { 271 | // END 272 | // IMPORTANT: close the connection 273 | db.detach(); 274 | }); 275 | }); 276 | ``` 277 | 278 | ### TRANSACTIONS 279 | 280 | __Transaction types:__ 281 | 282 | - `Firebird.ISOLATION_READ_UNCOMMITTED` 283 | - `Firebird.ISOLATION_READ_COMMITED` 284 | - `Firebird.ISOLATION_REPEATABLE_READ` 285 | - `Firebird.ISOLATION_SERIALIZABLE` 286 | - `Firebird.ISOLATION_READ_COMMITED_READ_ONLY` 287 | 288 | ```js 289 | Firebird.attach(options, function(err, db) { 290 | 291 | if (err) 292 | throw err; 293 | 294 | // db = DATABASE 295 | db.transaction(Firebird.ISOLATION_READ_COMMITED, function(err, transaction) { 296 | transaction.query('INSERT INTO users VALUE(?,?)', [1, 'Janko'], function(err, result) { 297 | 298 | if (err) { 299 | transaction.rollback(); 300 | return; 301 | } 302 | 303 | transaction.commit(function(err) { 304 | if (err) 305 | transaction.rollback(); 306 | else 307 | db.detach(); 308 | }); 309 | }); 310 | }); 311 | }); 312 | ``` 313 | 314 | ### EVENTS 315 | 316 | ```js 317 | Firebird.attach(options, function(err, db) { 318 | 319 | if (err) 320 | throw err; 321 | 322 | db.on('row', function(row, index, isObject) { 323 | // index === Number 324 | // isObject === is row object or array? 325 | }); 326 | 327 | db.on('result', function(result) { 328 | // result === Array 329 | }); 330 | 331 | db.on('attach', function() { 332 | 333 | }); 334 | 335 | db.on('detach', function(isPoolConnection) { 336 | // isPoolConnection == Boolean 337 | }); 338 | 339 | db.on('reconnect', function() { 340 | 341 | }); 342 | 343 | db.on('error', function(err) { 344 | 345 | }); 346 | 347 | db.on('transaction', function(isolation) { 348 | // isolation === Number 349 | }); 350 | 351 | db.on('commit', function() { 352 | 353 | }); 354 | 355 | db.on('rollback', function() { 356 | 357 | }); 358 | 359 | db.detach(); 360 | }); 361 | ``` 362 | 363 | ### Escaping query values 364 | 365 | ```js 366 | var sql1 = 'SELECT * FROM TBL_USER WHERE ID>' + Firebird.escape(1); 367 | var sql2 = 'SELECT * FROM TBL_USER WHERE NAME=' + Firebird.escape('Pe\'er'); 368 | var sql3 = 'SELECT * FROM TBL_USER WHERE CREATED<=' + Firebird.escape(new Date()); 369 | var sql4 = 'SELECT * FROM TBL_USER WHERE NEWSLETTER=' + Firebird.escape(true); 370 | 371 | // or db.escape() 372 | 373 | console.log(sql1); 374 | console.log(sql2); 375 | console.log(sql3); 376 | console.log(sql4); 377 | ``` 378 | ### Service Manager functions 379 | - backup 380 | - restore 381 | - fixproperties 382 | - serverinfo 383 | - database validation 384 | - commit transaction 385 | - rollback transaction 386 | - recover transaction 387 | - database stats 388 | - users infos 389 | - user actions (add modify remove) 390 | - get firebird file log 391 | - tracing 392 | 393 | ```js 394 | // each row : fctname : [params], typeofreturn 395 | var fbsvc = { 396 | "backup" : { [ "options"], "stream" }, 397 | "nbackup" : { [ "options"], "stream" }, 398 | "restore" : { [ "options"], "stream" }, 399 | "nrestore" : { [ "options"], "stream" }, 400 | "setDialect": { [ "database","dialect"], "stream" }, 401 | "setSweepinterval": { [ "database","sweepinterval"], "stream" }, 402 | "setCachebuffer" : { [ "database","nbpagebuffers"], "stream" }, 403 | "BringOnline" : { [ "database"], "stream" }, 404 | "Shutdown" : { [ "database","shutdown","shutdowndelay","shutdownmode"], "stream" }, 405 | "setShadow" : { [ "database","activateshadow"], "stream" }, 406 | "setForcewrite" : { [ "database","forcewrite"], "stream" }, 407 | "setReservespace" : { [ "database","reservespace"], "stream" }, 408 | "setReadonlyMode" : { [ "database"], "stream" }, 409 | "setReadwriteMode" : { [ "database"], "stream" }, 410 | "validate" : { [ "options"], "stream" }, 411 | "commit" : { [ "database", "transactid"], "stream" }, 412 | "rollback" : { [ "database", "transactid"], "stream" }, 413 | "recover" : { [ "database", "transactid"], "stream" }, 414 | "getStats" : { [ "options"], "stream" }, 415 | "getLog" : { [ "options"], "stream" }, 416 | "getUsers" : { [ "username"], "object" }, 417 | "addUser" : { [ "username", "password", "options"], "stream" }, 418 | "editUser" : { [ "username", "options"], "stream" }, 419 | "removeUser" : { [ "username","rolename"], "stream" }, 420 | "getFbserverInfos" : { [ "options", "options"], "object" }, 421 | "startTrace" : { [ "options"], "stream" }, 422 | "suspendTrace" : { [ "options"], "stream" }, 423 | "resumeTrace" : { [ "options"], "stream" }, 424 | "stopTrace" : { [ "options"], "stream" }, 425 | "getTraceList" : { [ "options"], "stream" }, 426 | "hasActionRunning" : { [ "options"], "object"} 427 | } 428 | 429 | ``` 430 | 431 | ### Backup Service example 432 | 433 | ```js 434 | 435 | Firebird.attach(options, function(err, svc) { 436 | if (err) 437 | return; 438 | svc.backup( 439 | { 440 | database:'/DB/MYDB.FDB', 441 | files: [ 442 | { 443 | filename:'/DB/MYDB.FBK', 444 | sizefile:'0' 445 | } 446 | ] 447 | }, 448 | function(err, data) { 449 | console.log(data); 450 | }); 451 | ``` 452 | 453 | ### getLog and getFbserverInfos Service examples with use of stream and object return 454 | ``` 455 | fb.attach(_connection, function(err, svc) { 456 | if (err) 457 | return; 458 | // all function that return a stream take two optional parameter 459 | // optread => byline or buffer byline use isc_info_svc_line and buffer use isc_info_svc_to_eof 460 | // buffersize => is the buffer for service manager it can't exceed 8ko (i'm not sure) 461 | 462 | svc.getLog({optread:'buffer', buffersize:2048}, function (err, data) { 463 | // data is a readablestream that contain the firebird.log file 464 | console.log(err); 465 | data.on('data', function (data) { 466 | console.log(data.toString()); 467 | }); 468 | data.on('end', function() { 469 | console.log('finish'); 470 | }); 471 | }); 472 | 473 | // an other exemple to use function that return object 474 | svc.getFbserverInfos( 475 | { 476 | "dbinfo" : true, 477 | "fbconfig" : true, 478 | "svcversion" : true, 479 | "fbversion" : true, 480 | "fbimplementation" : true, 481 | "fbcapatibilities" : true, 482 | "pathsecuritydb" : true, 483 | "fbenv" : true, 484 | "fbenvlock" : true, 485 | "fbenvmsg" : true 486 | }, {}, function (err, data) { 487 | console.log(err); 488 | console.log(data); 489 | }); 490 | }); 491 | 492 | ``` 493 | 494 | ### Charset for database connection is always UTF-8 495 | 496 | node-firebird doesn't let you chose the charset connection, it will always use UTF8. 497 | Node is unicode, no matter if your database is using another charset to store string or blob, Firebird will transliterate automatically. 498 | 499 | This is why you should use **Firebird 2.5** server at least. 500 | 501 | ### Firebird 3.0 Support 502 | 503 | Firebird new wire protocol is not supported yet so 504 | for Firebird 3.0 you need to add the following in firebird.conf 505 | ``` 506 | AuthServer = Legacy_Auth 507 | WireCrypt = Disabled 508 | ``` 509 | ## Contributors 510 | 511 | - Henri Gourvest, 512 | - Popa Marius Adrian, 513 | - Peter Širka, 514 | - Arnaud Le Roy 515 | 516 | [license-image]: http://img.shields.io/badge/license-MOZILLA-blue.svg?style=flat 517 | [license-url]: LICENSE 518 | 519 | [npm-url]: https://npmjs.org/package/node-firebird 520 | [npm-version-image]: http://img.shields.io/npm/v/node-firebird.svg?style=flat 521 | [npm-downloads-image]: http://img.shields.io/npm/dm/node-firebird.svg?style=flat 522 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. -------------------------------------------------------------------------------- /test/run.js: -------------------------------------------------------------------------------- 1 | var fb = require('../lib'); 2 | var fs = require('fs'); 3 | var os = require('os'); 4 | 5 | var assert = require('assert'); 6 | var Path = require('path'); 7 | var now = new Date(); 8 | 9 | var config = { 10 | 11 | // Problem with privileges in OSX 12 | // database: Path.join(os.tmpdir(), 'test-' + new Date().getTime() + '.fdb'), 13 | 14 | database: Path.join(process.cwd(), 'test-' + new Date().getTime() + '.fdb'), 15 | host: '127.0.0.1', // default 16 | port: 3050, // default 17 | user: 'SYSDBA', // default 18 | password: 'masterkey', // default 19 | role: null, // default 20 | pageSize: 4096, // default when creating database 21 | timeout: 3000, // default query timeout 22 | lowercase_keys: true 23 | } 24 | 25 | var database = null; 26 | 27 | Array.prototype.async = function(cb) { 28 | 29 | var self = this; 30 | var item = self.shift(); 31 | 32 | if (item === undefined) { 33 | if (cb) 34 | cb(); 35 | return; 36 | } 37 | 38 | item(function() { 39 | setImmediate(function() { 40 | self.async(cb); 41 | }); 42 | }); 43 | }; 44 | 45 | fb.attachOrCreate(config, function(err, db) { 46 | 47 | if (err) 48 | throw err.message; 49 | 50 | database = db; 51 | 52 | var task = []; 53 | 54 | task.push(test_create); 55 | task.push(test_insert); 56 | task.push(test_reconnect); 57 | task.push(test_select_insert); // for inserted rows 58 | task.push(test_select_with_error); 59 | task.push(test_update); 60 | task.push(test_select_update); // for updated rows 61 | task.push(test_transaction); 62 | task.push(test_gds_error); 63 | task.push(test_transaction_params); 64 | task.push(test_biginsert_blob); 65 | task.push(test_bigread_blob); 66 | 67 | task.push(function(next) { 68 | db.detach(next); 69 | }); 70 | 71 | task.push(test_pooling); 72 | task.async(function() { 73 | setTimeout(function() { 74 | process.exit(); 75 | }, 2000); 76 | }); 77 | }); 78 | 79 | function test_create(next) { 80 | 81 | var name = 'TEST ---> test_create'; 82 | console.time(name); 83 | 84 | // Create table 85 | database.query('CREATE TABLE test (ID INT, PARENT BIGINT, NAME VARCHAR(50), FILE BLOB, CREATED TIMESTAMP)', function (err) { 86 | assert.ok(!err, name + ': create table ' + err); 87 | // Check if table exists 88 | database.query('SELECT COUNT(*) FROM test', function (err, r) { 89 | assert.ok(!err, name + ': check existing of table ' + err); 90 | assert.ok(r[0].count === 0, name + ': check rows in new table'); 91 | // create exception 92 | database.query("CREATE EXCEPTION RAISEEXCEPTION 'message'", function (err, r) { 93 | assert.ok(!err, name + ': check existing of exception ' + err); 94 | // Create stored proc 95 | database.query("create or alter procedure TEST_ERROR_DURING_FETCH " 96 | + " returns (" 97 | + " MSG varchar(200))" 98 | + " AS" 99 | + " declare variable dbpath varchar(100);" 100 | + " declare variable dbpass varchar(100);" 101 | + " begin" 102 | + " msg = 'oupsss';" 103 | + " suspend;" 104 | + " exception raiseexception('bug');" 105 | + " end", function (err) { 106 | assert.ok(!err, name + ': check existing of stored procedure ' + err); 107 | console.timeEnd(name); 108 | // Next test 109 | next(); 110 | }); 111 | }); 112 | }); 113 | }); 114 | } 115 | 116 | function test_reconnect(next) { 117 | 118 | var name = 'TEST ---> test_reconnect'; 119 | console.time(name); 120 | database.connection._socket.end(); 121 | database.on('reconnect', function() { 122 | console.timeEnd(name); 123 | next(); 124 | }); 125 | } 126 | 127 | function test_insert(next) { 128 | 129 | var name = 'TEST ---> test_insert'; 130 | var query = []; 131 | 132 | console.time(name); 133 | 134 | // Insert record with blob (STREAM) 135 | query.push(function(next) { 136 | database.query('INSERT INTO test (ID, NAME, FILE, CREATED) VALUES(?, ?, ?, ?) RETURNING ID', [1, 'Firebird 1', fs.createReadStream('image.png'), '14.12.2014 12:12:12'], function(err, r) { 137 | assert.ok(!err, name + ': insert blob (stream) ' + err); 138 | assert.ok(r['id'] === 1, name + ': blob (stream) returning value'); 139 | next(); 140 | }); 141 | }); 142 | 143 | // Insert record with blob (BUFFER) 144 | query.push(function(next) { 145 | database.query('INSERT INTO test (ID, NAME, FILE, CREATED) VALUES(?, ?, ?, ?) RETURNING ID', [2, 'Firebird 2', fs.readFileSync('image.png'), '14.12.2014T12:12:12'], function(err, r) { 146 | assert.ok(!err, name + ': insert blob (buffer) ' + err); 147 | assert.ok(r['id'] === 2, name + ': blob (buffer) returning value'); 148 | next(); 149 | }); 150 | }); 151 | 152 | // Insert record without blob 153 | query.push(function(next) { 154 | database.query('INSERT INTO test (ID, NAME, CREATED, PARENT) VALUES(?, ?, ?, ?) RETURNING ID', [3, 'Firebird 3', now, 862304020112911], function(err, r) { 155 | assert.ok(!err, name + ': insert without blob (buffer) (1) ' + err); 156 | assert.ok(r['id'] === 3, name + ': without blob (buffer) returning value'); 157 | next(); 158 | }); 159 | }); 160 | 161 | // Insert record without blob (without returning value) 162 | query.push(function(next) { 163 | database.query('INSERT INTO test (ID, NAME, CREATED) VALUES(?, ?, ?)', [4, 'Firebird 4', '2014-12-12 13:59'], function(err, r) { 164 | assert.ok(!err, name + ': insert without blob (buffer) (2) ' + err); 165 | assert.ok(err === undefined, name + ': insert without blob + without returning value'); 166 | next(); 167 | }); 168 | }); 169 | 170 | query.async(function() { 171 | console.timeEnd(name); 172 | next(); 173 | }); 174 | 175 | } 176 | 177 | function test_update(next) { 178 | 179 | var name = 'TEST ---> test_update'; 180 | console.time(name); 181 | 182 | var query = []; 183 | 184 | // Insert record with blob (STREAM) 185 | query.push(function(next) { 186 | database.query('UPDATE test SET NAME=?, FILE=? WHERE Id=1', ['Firebird 1 (UPD)', fs.createReadStream('image.png')], function(err, r) { 187 | assert.ok(!err, name + ': update blob (stream) ' + err); 188 | next(); 189 | }); 190 | }); 191 | 192 | // Insert record with blob (BUFFER) 193 | query.push(function(next) { 194 | database.query('UPDATE test SET NAME=?, FILE=? WHERE Id=2', ['Firebird 2 (UPD)', fs.readFileSync('image.png')], function(err, r) { 195 | assert.ok(!err, name + ': update blob (buffer) ' + err); 196 | next(); 197 | }); 198 | }); 199 | 200 | query.async(function() { 201 | console.timeEnd(name); 202 | next(); 203 | }); 204 | 205 | } 206 | 207 | function test_select_with_error(next) { 208 | 209 | var name = 'TEST ---> test_select_with_error'; 210 | console.time(name); 211 | 212 | database.query('SELECT * FROM TEST_ERROR_DURING_FETCH', function (err, r) { 213 | assert.ok(err, name + ': problem no error'); 214 | assert.ok(!r, name + ': got data'); 215 | console.timeEnd(name); 216 | next(); 217 | }) 218 | } 219 | 220 | function test_select_insert(next) { 221 | 222 | var name = 'TEST ---> test_select_insert'; 223 | console.time(name); 224 | 225 | var query = []; 226 | 227 | // Classic select 228 | query.push(function(next) { 229 | database.query('SELECT * FROM test', function(err, r) { 230 | 231 | var row = r[0]; 232 | var row2 = r[2]; 233 | var row4 = r[3]; 234 | 235 | assert.ok(!err, name + ': problem (1) ' + err); 236 | assert.ok(row !== undefined, name + ': problem (2)'); 237 | assert.ok(row.id === 1 && row.name === 'Firebird 1', name + ': problem with deserializer'); 238 | assert.ok(typeof(row.file) === 'object', name + ': blob'); 239 | assert.ok(row.file.length === 5472, name + ': problem with retrieving blob data'); 240 | assert.ok(row.created.getMonth() === 11 && row.created.getDate() === 14 && row.created.getFullYear() === 2014 && row.created.getHours() === 12 && row.created.getMinutes() === 12, name + ': date problem (1)'); 241 | assert.ok(row2.created.getTime() === now.getTime(), name + ': date problem (2)'); 242 | assert.ok(row4.created.getMonth() === 11 && row4.created.getDate() === 12 && row4.created.getFullYear() === 2014 && row4.created.getHours() === 13 && row4.created.getMinutes() === 59, name + ': date problem (3)'); 243 | next(); 244 | }); 245 | }); 246 | 247 | // Scalar testing 248 | query.push(function(next) { 249 | database.query('SELECT CAST(123 as NUMERIC(10,2)) As a, MAX(2) AS b, COUNT(*) AS c FROM RDB$DATABASE', function(err, results) { 250 | var row = results[0]; 251 | assert.ok(row.a === 123, name + ': cast problem'); 252 | assert.ok(row.b === 2, name + ': max problem'); 253 | assert.ok(row.c !== 0, name + ': count problem'); 254 | next(); 255 | }); 256 | }); 257 | 258 | // Select to array 259 | query.push(function(next) { 260 | // Deserialize to array 261 | database.execute('SELECT COUNT(*), SUM(Id) FROM test', function(err, r) { 262 | assert.ok(r[0][0] === 4 && r[0][1] === 10, name + ': array deserializer problem'); 263 | next(); 264 | }); 265 | }); 266 | 267 | // Sequentially select (object) 268 | query.push(function(next) { 269 | var counter = 0; 270 | database.sequentially('SELECT Id FROM test', function(row, index, nextrow) { 271 | counter += row.id; 272 | nextrow(); 273 | }, function(err) { 274 | assert.ok(!err, name + ': problem (1) ' + err); 275 | assert.ok(counter === 10, name + ': sequentially (object)'); 276 | next(); 277 | }); 278 | }); 279 | 280 | // Sequentially select (array) 281 | query.push(function(next) { 282 | var counter = 0; 283 | database.sequentially('SELECT Id FROM test', function(row, index, nextrow) { 284 | counter += row[0]; 285 | nextrow(); 286 | }, function() { 287 | assert.ok(counter === 10, name + ': sequentially (array)'); 288 | next(); 289 | }, true); 290 | }); 291 | 292 | query.async(function() { 293 | console.timeEnd(name); 294 | next(); 295 | }); 296 | } 297 | 298 | function test_select_update(next) { 299 | 300 | var name = 'TEST ---> test_select_update'; 301 | console.time(name); 302 | 303 | var query = []; 304 | 305 | // Classic select 1 306 | query.push(function(next) { 307 | database.query('SELECT * FROM test WHERE Id=1', function(err, r) { 308 | 309 | var row = r[0]; 310 | 311 | assert.ok(!err, name + ': problem (1) ' + err); 312 | assert.ok(row !== undefined, name + ': problem (2)'); 313 | assert.ok(row.id === 1 && row.name === 'Firebird 1 (UPD)', name + ': problem with deserializer'); 314 | assert.ok(typeof(row.file) === 'object', name + ': blob'); 315 | assert.ok(row.file.length === 5472, name + ': problem with retrieving blob data'); 316 | next(); 317 | }); 318 | }); 319 | 320 | // Classic select 2 321 | query.push(function(next) { 322 | database.query('SELECT * FROM test WHERE Id=2', function(err, r) { 323 | 324 | var row = r[0]; 325 | 326 | assert.ok(!err, name + ': problem (1) ' + err); 327 | assert.ok(row !== undefined, name + ': problem (2)'); 328 | assert.ok(row.id === 2 && row.name === 'Firebird 2 (UPD)', name + ': problem with deserializer'); 329 | assert.ok(typeof(row.file) === 'object', name + ': blob'); 330 | assert.ok(row.file.length === 5472, name + ': problem with retrieving blob data'); 331 | next(); 332 | }); 333 | }); 334 | 335 | query.async(function() { 336 | console.timeEnd(name); 337 | next(); 338 | }); 339 | } 340 | 341 | function test_transaction(next) { 342 | 343 | var name = 'TEST ---> test_transaction'; 344 | console.time(name); 345 | 346 | var query = []; 347 | 348 | // Invalid transaction 349 | query.push(function(next) { 350 | database.transaction(function(err, transaction) { 351 | transaction.query('INSERT INTO test (ID, NAME) VALUES(?, ?)', [5, 'Transaction 1'], function(err) { 352 | assert.ok(!err, name + ': problem (1) ' + err); 353 | transaction.query('INSERT INTO test (ID, NAME) VALUES(?, ?)', [6, 'Transaction 2'], function(err) { 354 | assert.ok(!err, name + ': problem (2)'); 355 | transaction.query('INSERT INTO testa (ID, NAME) VALUES(?, ?)', [7, 'Transaction 3'], function(err) { 356 | assert.ok(err, name + ': problem (3)'); 357 | transaction.rollback(function(err) { 358 | assert.ok(!err, name + ': rollback problem'); 359 | next(); 360 | }); 361 | }); 362 | }); 363 | }); 364 | }); 365 | }); 366 | 367 | // Select to array 368 | query.push(function(next) { 369 | database.query('SELECT COUNT(*) FROM test', function(err, r) { 370 | assert.ok(r[0].count === 4, name + ': transaction does not work (rollback)'); 371 | next(); 372 | }); 373 | }); 374 | 375 | // Valid transaction 376 | query.push(function(next) { 377 | database.transaction(function(err, transaction) { 378 | transaction.query('INSERT INTO test (ID, NAME) VALUES(?, ?)', [5, 'Transaction 1'], function(err) { 379 | assert.ok(!err, name + ': problem (4) ' + err); 380 | transaction.query('INSERT INTO test (ID, NAME) VALUES(?, ?)', [6, 'Transaction 2'], function(err) { 381 | assert.ok(!err, name + ': problem (5)'); 382 | transaction.query('INSERT INTO test (ID, NAME) VALUES(?, ?)', [7, 'Transaction 3'], function(err) { 383 | assert.ok(!err, name + ': problem (6) ' + err); 384 | transaction.commit(function(err) { 385 | assert.ok(!err, name + ': commit problem ' + err); 386 | next(); 387 | }); 388 | }); 389 | }); 390 | }); 391 | }); 392 | }); 393 | 394 | // Select to array 395 | query.push(function(next) { 396 | database.query('SELECT COUNT(*) FROM test', function(err, r) { 397 | assert.ok(r[0].count === 7, name + ': transaction does not work (commit)'); 398 | next(); 399 | }); 400 | }); 401 | 402 | // blob with transaction object 403 | query.push(function(next) { 404 | database.transaction(function(err, transaction) { 405 | transaction.query('SELECT * FROM test WHERE Id=2', function(err, r) { 406 | 407 | var row = r[0]; 408 | 409 | assert.ok(!err, name + ': problem (1) ' + err); 410 | assert.ok(row !== undefined, name + ': problem (2)'); 411 | assert.ok(row.id === 2 && row.name === 'Firebird 2 (UPD)', name + ': problem with deserializer'); 412 | assert.ok(typeof(row.file) === 'function', name + ': blob'); 413 | row.file(function(err, name, e) { 414 | 415 | assert.ok(!err, name + ': reading blob'); 416 | 417 | var count = 0; 418 | 419 | e.on('data', function(buffer) { 420 | count += buffer.length; 421 | }); 422 | 423 | e.on('end', function() { 424 | assert.ok(count === 5472, name + ': problem with retrieving blob data'); 425 | transaction.commit(function(err) { 426 | assert.ok(!err, name + ': commit problem ' + err); 427 | next(); 428 | }) 429 | }); 430 | }); 431 | }) 432 | }) 433 | }) 434 | 435 | // blob with transaction array 436 | query.push(function(next) { 437 | database.transaction(function(err, transaction) { 438 | transaction.execute('SELECT * FROM test WHERE Id=2', function(err, r) { 439 | 440 | var row = r[0]; 441 | assert.ok(typeof(row[3]) === 'function', name + ': blob'); 442 | row[3](function(err, name, e) { 443 | 444 | assert.ok(!err, name + ': reading blob'); 445 | 446 | var count = 0; 447 | 448 | e.on('data', function(buffer) { 449 | count += buffer.length; 450 | }); 451 | 452 | e.on('end', function() { 453 | assert.ok(count === 5472, name + ': problem with retrieving blob data'); 454 | transaction.commit(function(err) { 455 | assert.ok(!err, name + ': commit problem ' + err); 456 | next(); 457 | }) 458 | }); 459 | }); 460 | }, function(err) { 461 | 462 | }, true) 463 | }) 464 | }) 465 | // sequentially with transaction object 466 | query.push(function(next) { 467 | database.transaction(function(err, transaction) { 468 | var counter = 0; 469 | transaction.sequentially('SELECT * FROM test', function(row, i, nextrow) { 470 | counter++; 471 | if (!row.file) 472 | return nextrow(); 473 | assert.ok(typeof(row.file) === 'function', name + ': blob'); 474 | row.file(function(err, name, e) { 475 | 476 | assert.ok(!err, name + ': reading blob'); 477 | 478 | var count = 0; 479 | 480 | e.on('data', function(buffer) { 481 | count += buffer.length; 482 | }); 483 | 484 | e.on('end', function() { 485 | assert.ok(count === 5472, name + ': problem with retrieving blob data'); 486 | nextrow(); 487 | }); 488 | }); 489 | }, function(err) { 490 | assert.ok(!err, name + ': problem (1) ' + err); 491 | assert.ok(counter === 7, name + ': sequentially (object)'); 492 | transaction.commit(function(err) { 493 | assert.ok(!err, name + ': commit problem ' + err); 494 | next() 495 | }) 496 | 497 | }) 498 | }) 499 | }) 500 | 501 | // sequentially with transaction array 502 | query.push(function(next) { 503 | database.transaction(function(err, transaction) { 504 | var counter = 0; 505 | transaction.sequentially('SELECT * FROM test', function(row, i, nextrow) { 506 | 507 | counter++; 508 | if (!row[3]) 509 | return nextrow(); 510 | assert.ok(typeof(row[3]) === 'function', name + ': blob'); 511 | row[3](function(err, name, e) { 512 | 513 | assert.ok(!err, name + ': reading blob'); 514 | 515 | var count = 0; 516 | 517 | e.on('data', function(buffer) { 518 | count += buffer.length; 519 | }); 520 | 521 | e.on('end', function() { 522 | assert.ok(count === 5472, name + ': problem with retrieving blob data'); 523 | nextrow(); 524 | }); 525 | }); 526 | }, function(err) { 527 | assert.ok(!err, name + ': problem (1) ' + err); 528 | assert.ok(counter === 7, name + ': sequentially (array)'); 529 | transaction.commit(function(err) { 530 | assert.ok(!err, name + ': commit problem ' + err); 531 | next() 532 | }) 533 | }, true) 534 | }) 535 | }) 536 | 537 | // database sequentially and error 538 | query.push(function(next) { 539 | var counter = 0; 540 | database.sequentially("select * from test", function(row, i, nextrow) { 541 | counter++; 542 | nextrow(new Error('bye')); 543 | }, function(err) { 544 | assert.ok(err, name + ': commit problem ' + err); 545 | assert.ok(counter === 1, name + ': error sequentially (database)'); 546 | next(); 547 | }) 548 | }) 549 | 550 | // transaction sequentially and error 551 | query.push(function(next) { 552 | database.transaction(function(err, transaction) { 553 | var counter = 0; 554 | transaction.sequentially('SELECT * FROM test', function(row, i, nextrow) { 555 | counter++; 556 | nextrow(new Error('bye')); 557 | }, function(err) { 558 | assert.ok(err, name + ': commit problem ' + err); 559 | assert.ok(counter === 1, name + ': error sequentially (transaction)'); 560 | transaction.commit(function(err) { 561 | assert.ok(!err, name + ': commit problem ' + err); 562 | next(); 563 | }) 564 | }) 565 | }) 566 | }) 567 | 568 | query.async(function() { 569 | console.timeEnd(name); 570 | next(); 571 | }); 572 | } 573 | 574 | function test_pooling(next) { 575 | 576 | var name = 'TEST ---> test_pooling'; 577 | console.time(name); 578 | 579 | var query = []; 580 | var pool = fb.pool(2, config); 581 | 582 | query.push(function(next) { 583 | pool.get(function(err, db) { 584 | setTimeout(function() { 585 | // detach a current connection (socket is opened) 586 | db.detach(); 587 | }, 1000); 588 | }); 589 | next(); 590 | }); 591 | 592 | query.push(function(next) { 593 | pool.get(function(err, db) { 594 | setTimeout(function() { 595 | // detach a current connection (socket is still opened) 596 | db.detach(); 597 | }, 2000); 598 | }); 599 | next(); 600 | }); 601 | 602 | query.push(function(next) { 603 | pool.get(function(err, db) { 604 | db.query('SELECT * FROM test WHERE id=1', function(err, results) { 605 | setImmediate(function() { 606 | assert.ok(results.length === 1, 'pool selector 1'); 607 | db.detach(); 608 | }); 609 | }); 610 | }); 611 | next(); 612 | }); 613 | 614 | query.push(function(next) { 615 | assert.ok(pool.pending.length === 1, name + ': pool pending'); 616 | next(); 617 | }); 618 | 619 | query.push(function(next) { 620 | pool.get(function(err, db) { 621 | db.query('SELECT * FROM test WHERE id=2', function(err, results) { 622 | assert.ok(results.length === 1, 'pool selector 2'); 623 | db.detach(); 624 | next(); 625 | }); 626 | }); 627 | }); 628 | 629 | query.push(function(next) { 630 | pool.get(function(err, db) { 631 | db.query('SELECT * FROM test WHERE id=1', function(err, results) { 632 | setImmediate(function() { 633 | assert.ok(results.length === 1, 'pool selector 3'); 634 | db.detach(); 635 | }); 636 | next(); 637 | }); 638 | }); 639 | }); 640 | 641 | query.push(function(next) { 642 | pool.get(function(err, db) { 643 | db.query('SELECT * FROM test WHERE id=2', function(err, results) { 644 | assert.ok(results.length === 1, 'pool selector 4'); 645 | db.detach(); 646 | next(); 647 | }); 648 | }); 649 | }); 650 | 651 | query.push(function(next) { 652 | pool.get(function(err, db) { 653 | db.query('INSERT INTO test (ID) VALUES(?)', function(err, results) { 654 | assert.ok(err, 'pool exception'); 655 | db.detach(); 656 | next(); 657 | }); 658 | }); 659 | }); 660 | 661 | query.push(function(next) { 662 | setTimeout(function() { 663 | assert.ok(pool.dbinuse === 0, 'pool detach'); 664 | console.timeEnd(name); 665 | next(); 666 | }, 1000); 667 | }); 668 | 669 | setTimeout(function() { 670 | query.async(next); 671 | }, 1000); 672 | } 673 | 674 | function test_gds_error(next) { 675 | const 676 | ERR_EXCEPT = 335544517, 677 | ERR_DSQL_ERROR = 335544569, 678 | ERR_NO_METADATA_UPDATE = 335544351; 679 | 680 | var name = 'TEST ---> gds_error'; 681 | console.time(name); 682 | 683 | var query = []; 684 | 685 | // Test 'user exception' error code 686 | query.push(function (next) { 687 | var sql = 'EXECUTE BLOCK AS BEGIN EXCEPTION RAISEEXCEPTION; END'; 688 | database.query(sql, [], function (err, r) { 689 | assert.ok(err, name + ': exception was not thrown ' + err); 690 | assert.ok(err.code === ERR_EXCEPT, name + ': GDS exception has invalid code "' + err.code + '": ' + err); 691 | next(); 692 | }); 693 | }); 694 | 695 | // Test 'Dynamic SQL Error' error code 696 | query.push(function (next) { 697 | var sql = 'SELECT * FROM invalid_table'; 698 | database.query(sql, [], function (err, r) { 699 | assert.ok(err, name + ': exception was not thrown ' + err); 700 | assert.ok(err.code === ERR_DSQL_ERROR, name + ': GDS exception has invalid code "' + err.code + '": ' + err); 701 | next(); 702 | }); 703 | }); 704 | 705 | // Test 'Unsuccessful metadata update' error code 706 | query.push(function (next) { 707 | var sql = 'CREATE TABLE test (ID INT)'; 708 | database.query(sql, [], function (err, r) { 709 | assert.ok(err, name + ': exception was not thrown ' + err); 710 | assert.ok(err.code === ERR_NO_METADATA_UPDATE, name + ': GDS exception has invalid code "' + err.code + '": ' + err); 711 | next(); 712 | }); 713 | }); 714 | 715 | query.async(function () { 716 | console.timeEnd(name); 717 | next(); 718 | }); 719 | } 720 | 721 | function test_transaction_params(next) { 722 | // recommended params for long working readonly transaction, 723 | // that doesn't hold record versions and server transaction counters 724 | const ISOLATION_READ = [ 725 | fb.ISC_TPB.version3, 726 | fb.ISC_TPB.read, 727 | fb.ISC_TPB.read_committed, 728 | fb.ISC_TPB.rec_version 729 | ]; 730 | const ERR_READ_ONLY_TRANS = 335544361; 731 | 732 | var name = 'TEST ---> test_transaction_params'; 733 | console.time(name); 734 | 735 | var query = []; 736 | 737 | // Read transaction cannot write 738 | query.push(function (next) { 739 | database.transaction(ISOLATION_READ, function (err, transaction) { 740 | transaction.query('INSERT INTO test (ID, NAME) VALUES(?, ?)', [1, 'Transaction Read'], function (err) { 741 | assert.ok(err, name + ': insert in read transaction was successful ' + err); 742 | assert.ok(err.code === ERR_READ_ONLY_TRANS, name + ': GDS exception has invalid code "' + err.code + '": ' + err); 743 | next(); 744 | }); 745 | }); 746 | }); 747 | 748 | query.async(function () { 749 | console.timeEnd(name); 750 | next(); 751 | }); 752 | } 753 | 754 | function test_biginsert_blob(next) { 755 | var name = 'TEST ---> test_biginsert_blob'; 756 | console.time(name); 757 | function insert(transaction, id, max, end) { 758 | if (!transaction) { 759 | database.transaction(function (err, transaction) { 760 | assert.ok(!err, 'Transaction problem (' + id + ') ' + err); 761 | ins(transaction); 762 | }) 763 | } else { 764 | ins(transaction); 765 | } 766 | 767 | function ins(transaction) { 768 | transaction.query('INSERT INTO test (ID, NAME, FILE, CREATED) VALUES(?, ?, ?, ?) RETURNING ID', [id, 'Firebird 2', new Buffer("firebird is great"), '14.12.2014T12:12:12'], function (err, r) { 769 | assert.ok(!err, 'Insert problem (' + id + ') ' + err); 770 | if (id < max) 771 | if (id % 1000 == 0) { 772 | transaction.commit(function () { 773 | insert(null, id + 1, max, end); 774 | }) 775 | } else 776 | insert(transaction, id + 1, max, end); 777 | else 778 | end(); 779 | }) 780 | } 781 | } 782 | 783 | insert(null, 0, 20000, function () { 784 | console.timeEnd(name); 785 | next(); 786 | }) 787 | } 788 | 789 | function test_bigread_blob(next) { 790 | var name = 'TEST ---> test_bigread_blob'; 791 | console.time(name); 792 | var t = setTimeout(() => { 793 | assert.fail("Too long read probably blocked"); 794 | }, 20000); 795 | database.query("SELECT ID,FILE FROM TEST", function (err, result) { 796 | assert.ok(!err, 'Read problem' + err); 797 | clearTimeout(t); 798 | console.timeEnd(name); 799 | next(); 800 | }) 801 | } --------------------------------------------------------------------------------