diff --git a/node_modules/dblite/.npmignore b/node_modules/dblite/.npmignore new file mode 100644 index 0000000..b361e47 --- /dev/null +++ b/node_modules/dblite/.npmignore @@ -0,0 +1,10 @@ +src/* +test/* +benchmark/* +template/* +node_modules/* +build/*.amd.js +Makefile +index.html +.gitignore +.travis.yml \ No newline at end of file diff --git a/node_modules/dblite/LICENSE.txt b/node_modules/dblite/LICENSE.txt new file mode 100644 index 0000000..468a071 --- /dev/null +++ b/node_modules/dblite/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (C) 2013 by WebReflection + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/dblite/README.md b/node_modules/dblite/README.md new file mode 100644 index 0000000..9f84ae0 --- /dev/null +++ b/node_modules/dblite/README.md @@ -0,0 +1,413 @@ +dblite +====== +a zero hassle wrapper for sqlite +```javascript +var dblite = require('dblite'), + db = dblite('file.name'); + +// Asynchronous, fast, and ... +db.query('SELECT * FROM table', function(err, rows) { + // ... that easy! +}); +``` +More in [the related blogpost](http://webreflection.blogspot.com/2013/07/dblite-sqlite3-for-nodejs-made-easy.html) and here too :-) + +[![build status](https://secure.travis-ci.org/WebReflection/dblite.png)](http://travis-ci.org/WebReflection/dblite) + +[![NPM](https://nodei.co/npm/dblite.png?downloads=true)](https://nodei.co/npm/dblite/) + + +### Important +Starting from **sqlite3** version `3.8.6` you need a "_new line agnostic_" version of `dblite`, right now represented by the latest version `0.6.0`. This **will break** compatibility with older version of the database cli. + + +### The What And The Why +I've created `dblite` module because there's still not a simple and straight forward or standard way to have [sqlite](http://www.sqlite.org) in [node.js](http://nodejs.org) without requiring to re-compile, re-build, download sources a part or install dependencies instead of simply `apt-get install sqlite3` or `pacman -S sqlite3` in your \*nix system. + +`dblite` has been created with portability, simplicity, and reasonable performance for **embedded Hardware** such [Raspberry Pi](http://www.raspberrypi.org) and [Cubieboard](http://cubieboard.org) in mind. + +Generally speaking all linux based distributions like [Arch Linux](https://www.archlinux.org), where is not always that easy to `node-gyp` a module and add dependencies that work, can now use this battle tested wrap and perform basic to advanced sqlite operations. + + +### Bootstrap +To install dblite simply `npm install dblite` then in node: +```javascript +var dblite = require('dblite'), + db = dblite('/folder/to/file.sqlite'); + +// ready to go, i.e. +db.query('.databases'); +db.query( + 'SELECT * FROM users WHERE pass = ?', + [pass], + function (err, rows) { + var user = rows.length && rows[0]; + } +); +``` +By default the `dblite` function uses **sqlite3 as executable**. If you need to change the path simply update `dblite.bin = "/usr/local/bin/sqlite3";` before invoking the function. + + +### API +Right now a created `EventEmitter` `db` instance has 3 extra methods: `.query()`, `.lastRowID()`, and `.close()`. + +The `.lastRowID(table, callback(rowid))` helper simplifies a common operation with SQL tables after inserts, handful as shortcut for the following query: +`SELECT ROWID FROM ``table`` ORDER BY ROWID DESC LIMIT 1`. + +The method `.close()` does exactly what it suggests: it closes the database connection. +Please note that it is **not possible to perform other operations once it has been closed**. + +Being an `EventEmitter` instance, the database variable will be notified with the `close` listener, if any. + + +### Understanding The .query() Method +The main role in this module is played by the `db.query()` method, a method rich in overloads all with perfect and natural meaning. + +The amount of parameters goes from one to four, left to right, where left is the input going through the right which is the eventual output. + +All parameters are optionals except the SQL one. + +### db.query() Possible Combinations +```javascript +db.query(SQL) +db.query(SQL, callback:Function) +db.query(SQL, params:Array|Object) +db.query(SQL, fields:Array|Object) +db.query(SQL, params:Array|Object, callback:Function) +db.query(SQL, fields:Array|Object, callback:Function) +db.query(SQL, params:Array|Object, fields:Array|Object) +db.query(SQL, params:Array|Object, fields:Array|Object, callback:Function) +``` +All above combinations are [tested properly in this file](test/dblite.js) together with many other tests able to make `dblite` robust enough and ready to be used. + +Please note how `params` is always before `fields` and/or `callback` if `fields` is missing, just as reminder that order is left to right accordingly with what we are trying to do. + +Following detailed explanation per each parameter. + +#### The SQL:string +This string [accepts any query understood by SQLite](http://www.sqlite.org/lang.html) plus it accepts all commands that regular SQLite shell would accept such `.databases`, `.tables`, `.show` and all others passing through the specified `info` listener, if any, using just the console as fallback otherwise. +```javascript +var dblite = require('dblite'), + db = dblite('./db.sqlite'); + +// will call the implicit `info` console.log +db.query('.show'); +/* will console.log something like: + + echo: off + explain: off + headers: off + mode: csv +nullvalue: "" + output: stdout +separator: "," + stats: off + width: +*/ + +// normal query +db.query('CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, value TEXT)'); +db.query('INSERT INTO test VALUES(null, ?)', ['some text']); +db.query('SELECT * FROM test'); +// will implicitly log the following +// [ [ '1', 'some text' ] ] +``` + +#### The params:Array|Object +If the SQL string **contains special chars** such `?`, `:key`, `$key`, or `@key` properties, these will be replaced accordingly with the `params` `Array` or `Object` that, in this case, MUST be present. +```javascript +// params as Array +db.query('SELECT * FROM test WHERE id = ?', [1]); + +// params as Object +db.query('SELECT * FROM test WHERE id = :id', {id:1}); +// same as +db.query('SELECT * FROM test WHERE id = $id', {id:1}); +// same as +db.query('SELECT * FROM test WHERE id = @id', {id:1}); +``` + +#### The fields:Array|Object +By default, results are returned as an `Array` where all rows are the outer `Array` and each single row is another `Array`. +```javascript +db.query('SELECT * FROM test'); +// will log something like: +[ + [ '1', 'some text' ], // row1 + [ '2', 'something else' ] // rowN +] +``` +If we specify a fields parameter we can have each row represented by an object, instead of an array. +```javascript +// same query using fields as Array +db.query('SELECT * FROM test', ['key', 'value']); +// will log something like: +[ + {key: '1', value: 'some text'}, // row1 + {key: '2', value: 'something else'} // rowN +] +``` + +#### Parsing Through The fields:Object +[SQLite Datatypes](http://www.sqlite.org/datatype3.html) are different from JavaScript plus SQLite works via affinity. +This module also parses sqlite3 output which is **always a string** and as string every result will always be returned **unless** we specify `fields` parameter as object, suggesting validation per each field. +```javascript +// same query using fields as Object +db.query('SELECT * FROM test', { + key: Number, + value: String +}); +// note the key as integer! +[ + {key: 1, value: 'some text'}, // row1 + {key: 2, value: 'something else'} // rowN +] +``` +More complex validators/transformers can be passed without problems: +```javascript +// same query using fields as Object +db.query('SELECT * FROM `table.users`', { + id: Number, + name: String, + adult: Boolean, + skills: JSON.parse, + birthday: Date, + cube: function (fieldValue) { + return fieldValue * 3; + } +}); +``` + +#### The params:Array|Object AND The fields:Array|Object +Not a surprise we can combine both params, using the left to right order input to output so **params first**! +```javascript +// same query using params AND fields +db.query('SELECT * FROM test WHERE id = :id', { + id: 1 +},{ + key: Number, + value: String +}); + +// same as... +db.query('SELECT * FROM test WHERE id = ?', [1], ['key', 'value']); +// same as... +db.query('SELECT * FROM test WHERE id = ?', [1], { + key: Number, + value: String +}); +// same as... +db.query('SELECT * FROM test WHERE id = :id', { + id: 1 +}, [ + 'key', 'value' +]); +``` + +#### The callback:Function +When a `SELECT` or a `PRAGMA` `SQL` is executed the module puts itself in a *waiting for results* state. + +**Update** - Starting from `0.4.0` the callback will be invoked with `err` and `data` if the callback length is greater than one. `function(err, data){}` VS `function(data){}`. However, latter mode will keep working in order to not break backward compatibility. +**Update** - Starting from `0.3.3` every other `SQL` statement will invoke the callback after the operation has been completed. + +As soon as results are fully pushed to the output the module parses this result, if any, and send it to the specified callback. + +The callback is **always the last specified parameter**, if any, or the implicit equivalent of `console.log.bind(console)`. +Latter case is simply helpful to operate directly via `node` **console** and see results without bothering writing a callback each `.query()` call. + +#### Extra Bonus: JSON Serialization With fields:Array|Object +If one field value is not scalar (boolean, number, string, null) `JSON.stringify` is performed in order to save data. +This helps lazy developers that don't want to pre parse every field and let `dblite` do the magic. +```javascript +// test has two fields, id and value +db.query('INSERT INTO test VALUES(?, ?)', [ + 123, + {name: 'dblite', rate: 'awesome'} // value serialized +]); + +// use the fields to parse back the object +db.query('SELECT * FROM test WHERE id = ?', [123], { + id: Number, + value: JSON.parse // value unserialized +}, function (err, rows) { + var record = rows[0]; + console.log(record.id); // 123 + console.log(record.value.name); // "dblite" + console.log(record.value.rate); // "awesome"" +}); +``` + +### Automatic Fields Through Headers +Since version `0.3.0` it is possible to enable automatic fields parsing either through initialization (suggested) or at runtime. +```javascript +var dblite = require('dblite'), + // passing extra argument at creation + db = dblite('file.name', '-header'); + +db.query('SELECT * FROM table', function(err, rows) { + rows[0]; // {header0: value0, headerN: valueN} +}); + +// at runtime +db + .query('.headers ON') + .query('SELECT * FROM table', function(err, rows) { + rows[0]; // {header0: value0, headerN: valueN} + }) + .query('.headers OFF') +; +``` + +In version `0.3.2` a smarter approach for combined _headers/fields_ is used where the right key order is granted by headers but it's possible to validate known fields too. + +```javascript +var db = require('dblite')('file.name', '-header'); + +db.query('SELECT 1 as one, 2 as two', {two:Number}, function(err, rows) { + rows[0]; // {one: "1", two: 2} // note "1" as String +}); +``` +In this way these two options can be supplementary when and if necessary. + + +### Handling Infos And Errors - Listeners +The `EventEmitter` will notify any listener attached to `info`, `error`, or `close` accordingly with the current status. +```javascript +db.on('info', function (data) { + // show data returned by special syntax + // such: .databases .tables .show and others + console.log(data); + // by default, it does the same +}); + +db.on('error', function (err) { + // same as `info` but for errors + console.error(err.toString()); + // by default, it does the same +}); + +db.on('close', function (code) { + // by default, it logs "bye bye" + // invoked once the database has been closed + // and every statement in the queue executed + // the code is the exit code returned via SQLite3 + // usually 0 if everything was OK + console.log('safe to get out of here ^_^_'); +}); +``` +Please **note** that error is invoked only if the callback is not handling it already via double argument. + +The `close` event ensures that all operations have been successfully performed and your app is ready to exit or move next. + +Please note that after invoking `db.close()` any other query will be ignored and the instance will be put in a _waiting to complete_ state which will invoke the `close` listener once operations have been completed. + + +### Raspberry Pi Performance +This is the output generated after a `make test` call in this repo folder within Arch Linux for RPi. +``` +npm test + +> dblite@0.1.2 test /home/dblite +> node test/.test.js + +/home/dblite/dblite.test.sqlite +------------------------------ +main +passes: 1, fails: 0, errors: 0 +------------------------------ +create table if not exists +passes: 1, fails: 0, errors: 0 +------------------------------ +100 sequential inserts +100 records in 3.067 seconds +passes: 1, fails: 0, errors: 0 +------------------------------ +1 transaction with 100 inserts +200 records in 0.178 seconds +passes: 1, fails: 0, errors: 0 +------------------------------ +auto escape +passes: 1, fails: 0, errors: 0 +------------------------------ +auto field +fetched 201 rows as objects in 0.029 seconds +passes: 1, fails: 0, errors: 0 +------------------------------ +auto parsing field +fetched 201 rows as normalized objects in 0.038 seconds +passes: 1, fails: 0, errors: 0 +------------------------------ +many selects at once +different selects in 0.608 seconds +passes: 1, fails: 0, errors: 0 +------------------------------ +db.query() arguments +[ [ '1' ] ] +[ [ '2' ] ] +[ { id: 1 } ] +[ { id: 2 } ] +passes: 5, fails: 0, errors: 0 +------------------------------ +utf-8 +¥ · £ · € · $ · ¢ · ₡ · ₢ · ₣ · ₤ · ₥ · ₦ · ₧ · ₨ · ₩ · ₪ · ₫ · ₭ · ₮ · ₯ · ₹ +passes: 1, fails: 0, errors: 0 +------------------------------ +erease file +passes: 1, fails: 0, errors: 0 + +------------------------------ + 15 Passes +------------------------------ +``` +If an SD card can do this good, I guess any other environment should not have problems here ;-) + +### F.A.Q. +Here a list of probably common Q&A about this module. Please do not hesitate to ask me more, if necessary, thanks. + + * **How Does It Work?** `dblite` uses a [spawned](http://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options) version of the `sqlite3` executable. It could theoretically work with any other SQL like database but it's tested with `sqlite3-shell` only + * **Does It Spawn Per Each Query?** this is a quick one: **NO**! `dblite` spawns once per each database file where usually there is only one database file opened per time. + * **How About Memory And Performance?** Accordingly with `node` manual: + + > These child Nodes are still whole new instances of V8. + > Assume at least 30ms startup and 10mb memory for each new Node. + > That is, you cannot create many thousands of them. + + Since `dblite` spawns only once, there is a little overhead during the database initialization but that's pretty much it, the amount of RAM increases with the amount of data we save or retrieve from the database. The above **Raspberry Pi Benchmark** should ensure that with most common operation, and using transactions where possible, latency and RAM aren't a real issue. + * **Why Not The Native One?** I had some difficulty installing this [node-sqlite3 module](https://github.com/developmentseed/node-sqlite3#name) due `node-gyp` incompatibilities with some **ARM** based device in both *Debian* and *ArchLinux*. Since I really needed an sqlite manager for the next version of [polpetta](https://github.com/WebReflection/polpetta#က-polpetta) which aim is to have a complete, lightweight, and super fast web server in many embedded hardware such RPi, Cubieboard, and others, and since I needed something able to work with multiple core too, I've decided to try this road wrapping the native, easy to install and update, `sqlite3` shell client and do everything I need. So far, so good I would say ;-) + * **Isn't `params` and `fields` an ambiguous choice?** At the very beginning I wasn't sure myself if that would have worked as API choice but later on I've changed my mind. First of all, it's very easy to spot special chars in the `SQL` statement. If present, params is mandatory and used, as easy as that. Secondly, if an object has functions as value, it's obviously a `fields` object, 'cause `params` cannot contains functions since these are not compatible with `JSON` serialization, neither meaningful for the database. The only case where `fields` might be confused with `params` is when no `params` has been specified, and `fields` is an `Array`. In this case I believe you are the same one that wrote the SQL too and know upfront if there are fields to retrieve from `params` or not so this is actually a *non real-world* problem and as soon as you try this API you'll realize it feels intuitive and right. + * **Are Transactions Supported?** ... **YES**, transactions are supported simply performing multiple queries as you would do in *sqlite3* shell: + ```javascript + db.query('BEGIN TRANSACTION'); + for(var i = 0; i < 100; i++) { + db.query('INSERT INTO table VALUES(?, ?)', [null, Math.random()]); + } + db.query('COMMIT'); + ``` + The test file has a transaction with 100 records in it, [have a look](test/dblite.js). + * **Can I Connect To A `:memory:` Database?** well, you can do anything you would do with `sqlite3` shell so **YES** + ```javascript + var db = dblite(':memory:'); // that's it! + ``` + +### License +The usual Mit Style, thinking about the [WTFPL](http://en.wikipedia.org/wiki/WTFPL) though ... stay tuned for updates. + + Copyright (C) 2013 by WebReflection + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. diff --git a/node_modules/dblite/package.json b/node_modules/dblite/package.json new file mode 100644 index 0000000..7890187 --- /dev/null +++ b/node_modules/dblite/package.json @@ -0,0 +1,60 @@ +{ + "version": "0.6.1", + "license": "MIT", + "engine": "node >= 0.6.0", + "name": "dblite", + "description": "a zero hassle wrapper for sqlite - requires sqlite3 3.8.6 or greater", + "homepage": "https://github.com/WebReflection/dblite", + "keywords": [ + "sqlite", + "sqlite3", + "shell", + "query", + "embedded", + "arch linux", + "raspberry pi", + "cubieboard", + "cubieboard2", + "simple", + "no gyp", + "no compile", + "no hassle", + "wrap", + "spawn" + ], + "author": { + "name": "Andrea Giammarchi", + "url": "http://webreflection.blogspot.com/" + }, + "repository": { + "type": "git", + "url": "git://github.com/WebReflection/dblite.git" + }, + "main": "./build/dblite.node.js", + "scripts": { + "test": "node test/.test.js" + }, + "bugs": { + "url": "https://github.com/WebReflection/dblite/issues" + }, + "_id": "dblite@0.6.1", + "_shasum": "18e6d3811f4031ff0119e8a7b13ef1558215376a", + "_from": "dblite@", + "_npmVersion": "1.4.9", + "_npmUser": { + "name": "webreflection", + "email": "andrea.giammarchi@gmail.com" + }, + "maintainers": [ + { + "name": "webreflection", + "email": "andrea.giammarchi@gmail.com" + } + ], + "dist": { + "shasum": "18e6d3811f4031ff0119e8a7b13ef1558215376a", + "tarball": "http://registry.npmjs.org/dblite/-/dblite-0.6.1.tgz" + }, + "directories": {}, + "_resolved": "https://registry.npmjs.org/dblite/-/dblite-0.6.1.tgz" +} diff --git a/node_modules/node-firebird/.npmignore b/node_modules/node-firebird/.npmignore new file mode 100644 index 0000000..47443cf --- /dev/null +++ b/node_modules/node-firebird/.npmignore @@ -0,0 +1,2 @@ + +*.fdb diff --git a/node_modules/node-firebird/LICENSE b/node_modules/node-firebird/LICENSE new file mode 100644 index 0000000..f4bbcd2 --- /dev/null +++ b/node_modules/node-firebird/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. \ No newline at end of file diff --git a/node_modules/node-firebird/README.md b/node_modules/node-firebird/README.md new file mode 100644 index 0000000..964aa35 --- /dev/null +++ b/node_modules/node-firebird/README.md @@ -0,0 +1,360 @@ +![Firebird Logo](https://www.totaljs.com/exports/firebird-logo.png) + +[![NPM version][npm-version-image]][npm-url] [![NPM downloads][npm-downloads-image]][npm-url] [![Mozilla License][license-image]][license-url] +# Pure JavaScript Firebird client for Node.js. + +Pure JavaScript and Asynchronous Firebird client for Node.js. [Firebird forum](https://groups.google.com/forum/#!forum/node-firebird) on Google Groups. Please share and follow Firebird database, it's a very good open-source product. + +__Firebird database on social networks__ + +- [Firebird on Google+](https://plus.google.com/111558763769231855886/posts) +- [Firebird on Twitter](https://twitter.com/firebirdsql/) +- [Firebird on Facebook](https://www.facebook.com/FirebirdSQL) + +__New version v0.2.0 supports__ + +- added auto-reconnect +- added [sequentially selects](https://github.com/hgourvest/node-firebird/wiki/What-is-sequentially-selects) +- events for connection (attach, detach, row, result, transaction, commit, rollback, error, etc.) +- performance improvements +- supports inserting/updating buffers and streams +- reading blobs (sequentially) +- pooling +- `database.detach()` waits for last command +- better unit-test +- best of use with [total.js - web application framework for node.js](http://www.totaljs.com) + +--- + +- [Firebird documentation](http://www.firebirdsql.org/en/documentation/) +- [Firebird limits and data types](http://www.firebirdsql.org/en/firebird-technical-specifications/) + +## Installation + +```bash +npm install node-firebird +``` + +## Usage + +```js +var Firebird = require('node-firebird'); +``` + +### Methods + +- `Firebird.escape(value) -> return {String}` - prevent for SQL Injections +- `Firebird.attach(options, function(err, db))` attach a database +- `Firebird.create(options, function(err, db))` create a database +- `Firebird.attachOrCreate(options, function(err, db))` attach or create database +- `Firebird.pool(maxSockets, options, function(err, db)) -> return {Object}` create a connection pooling + +## Connection types + +### Connection options + +```js +var options = {}; + +options.host = '127.0.0.1'; +// options.port = 3050; +options.database = 'database.fdb'; +options.user = 'SYSDBA'; +options.password = 'masterkey'; +``` + +### Classic + +```js +Firebird.attach(options, function(err, db) { + + if (err) + throw err; + + // db = DATABASE + db.query('SELECT * FROM TABLE', function(err, result) { + // IMPORTANT: close the connection + db.detach(); + }); + +}); +``` + +### Pooling + +```js +// 5 = the number is count of opened sockets +var pool = Firebird.pool(5, options); + +// Get a free pool +pool.get(function(err, db) { + + if (err) + throw err; + + // db = DATABASE + db.query('SELECT * FROM TABLE', function(err, result) { + // IMPORTANT: release the pool connection + db.detach(); + }); +}); + +// close all opened connections +pool.detach(); + +// Destroy pool +pool.destroy(); +``` + +## Database object (db) + +### Methods + +- `db.query(query, [params], function(err, result))` - classic query, returns Array of Object +- `db.execute(query, [params], function(err, result))` - classic query, returns Array of Array +- `db.sequentially(query, [params], function(row, index), function(err))` - sequentially query +- `db.detach(function(err))` detach a database +- `db.transaction(isolation, function(err, transaction))` create transaction + +### Transaction methods + +- `transaction.query(query, [params], function(err, result))` - classic query, returns Array of Object +- `transaction.execute(query, [params], function(err, result))` - classic query, returns Array of Array +- `transaction.commit(function(err))` commit current transaction +- `transaction.rollback(function(err))` rollback current transaction + +## Examples + +### PARAMETRIZED QUERIES + +### Parameters + +```js +Firebird.attach(options, function(err, db) { + + if (err) + throw err; + + // db = DATABASE + db.query('INSERT INTO USERS (ID, ALIAS, CREATED) VALUES(?, ?, ?) RETURNING ID', [1, 'Pe\'ter', new Date()] function(err, result) { + console.log(result[0].id); + db.query('SELECT * FROM USERS WHERE Alias=?', ['Peter'], function(err, result) { + console.log(result); + db.detach(); + }); + }); +}); +``` + +### BLOB (stream) + +```js +Firebird.attach(options, function(err, db) { + + if (err) + throw err; + + // db = DATABASE + // INSERT STREAM as BLOB + db.query('INSERT INTO USERS (ID, ALIAS, FILE) VALUES(?, ?, ?)', [1, 'Peter', fs.createReadStream('/users/image.jpg')] function(err, result) { + // IMPORTANT: close the connection + db.detach(); + }); +}); +``` + +### BLOB (buffer) + +```js +Firebird.attach(options, function(err, db) { + + if (err) + throw err; + + // db = DATABASE + // INSERT BUFFER as BLOB + db.query('INSERT INTO USERS (ID, ALIAS, FILE) VALUES(?, ?, ?)', [1, 'Peter', fs.readFileSync('/users/image.jpg')] function(err, result) { + // IMPORTANT: close the connection + db.detach(); + }); +}); +``` + +### READING BLOBS (ASYNCHRONOUS) + +```js +Firebird.attach(options, function(err, db) { + + if (err) + throw err; + + // db = DATABASE + db.query('SELECT ID, ALIAS, USERPICTURE FROM USER', function(err, rows) { + + if (err) + throw err; + + // first row + rows[0].userpicture(function(err, name, e) { + + if (err) + throw err; + + // e === EventEmitter + e.on('data', function(chunk) { + // reading data + }); + + e.on('end', function() { + // end reading + // IMPORTANT: close the connection + db.detach(); + }); + }); + + }); +}); +``` + +### STREAMING A BIG DATA + +```js +Firebird.attach(options, function(err, db) { + + if (err) + throw err; + + // db = DATABASE + db.sequentially('SELECT * FROM BIGTABLE', function(row, index) { + + // EXAMPLE + stream.write(JSON.stringify(row)); + + }, function(err) { + // END + // IMPORTANT: close the connection + db.detach(); + }); +}); +``` + +### TRANSACTIONS + +__Transaction types:__ + +- `Firebird.ISOLATION_READ_UNCOMMITTED` +- `Firebird.ISOLATION_READ_COMMITED` +- `Firebird.ISOLATION_REPEATABLE_READ` +- `Firebird.ISOLATION_SERIALIZABLE` +- `Firebird.ISOLATION_READ_COMMITED_READ_ONLY` + +```js +Firebird.attach(options, function(err, db) { + + if (err) + throw err; + + // db = DATABASE + db.transaction(Firebird.ISOLATION_READ_COMMITED, function(err, transaction) { + transaction.query('INSERT INTO users VALUE(?,?)', [1, 'Janko'], function(err, result) { + + if (err) { + transaction.rollback(); + return; + } + + transaction.commit(function(err) { + if (err) + transaction.rollback(); + else + db.detach(); + }); + }); + }); +}); +``` + +### EVENTS + +```js +Firebird.attach(options, function(err, db) { + + if (err) + throw err; + + db.on('row', function(row, index, isObject) { + // index === Number + // isObject === is row object or array? + }); + + db.on('result', function(result) { + // result === Array + }); + + db.on('attach', function() { + + }); + + db.on('detach', function(isPoolConnection) { + // isPoolConnection == Boolean + }); + + db.on('reconnect', function() { + + }); + + db.on('error', function(err) { + + }); + + db.on('transaction', function(isolation) { + // isolation === Number + }); + + db.on('commit', function() { + + }); + + db.on('rollback', function() { + + }); + + db.detach(); +}); +``` + +### Escaping query values + +```js +var sql1 = 'SELECT * FROM TBL_USER WHERE ID>' + Firebird.escape(1); +var sql2 = 'SELECT * FROM TBL_USER WHERE NAME=' + Firebird.escape('Pe\'er'); +var sql3 = 'SELECT * FROM TBL_USER WHERE CREATED<=' + Firebird.escape(new Date()); +var sql4 = 'SELECT * FROM TBL_USER WHERE NEWSLETTER=' + Firebird.escape(true); + +// or db.escape() + +console.log(sql1); +console.log(sql2); +console.log(sql3); +console.log(sql4); +``` + +### Charset for database connection is always UTF-8 + +node-firebird doesn't let you chose the charset connection, it will always use UTF8. +Node is unicode, no matter if your database is using another charset to store string or blob, Firebird will transliterate automatically. + +This is why you should use **Firebird 2.5** server at least. + +## Contributors + +- Henri Gourvest, +- Popa Marius Adrian, +- Peter Širka, + +[license-image]: http://img.shields.io/badge/license-MOZILLA-blue.svg?style=flat +[license-url]: LICENSE + +[npm-url]: https://npmjs.org/package/node-firebird +[npm-version-image]: http://img.shields.io/npm/v/node-firebird.svg?style=flat +[npm-downloads-image]: http://img.shields.io/npm/dm/node-firebird.svg?style=flat diff --git a/node_modules/node-firebird/lib/firebird.msg b/node_modules/node-firebird/lib/firebird.msg new file mode 100644 index 0000000..90a7376 Binary files /dev/null and b/node_modules/node-firebird/lib/firebird.msg differ diff --git a/node_modules/node-firebird/lib/index.js b/node_modules/node-firebird/lib/index.js new file mode 100644 index 0000000..b77e802 --- /dev/null +++ b/node_modules/node-firebird/lib/index.js @@ -0,0 +1,2716 @@ +var + net = require('net'), + os = require('os'), + Events = require('events'), + serialize = require('./serialize.js'), + XdrReader = serialize.XdrReader, + BlrReader = serialize.BlrReader, + XdrWriter = serialize.XdrWriter, + BlrWriter = serialize.BlrWriter, + messages = require('./messages.js'); + +if (typeof(setImmediate) === 'undefined') { + global.setImmediate = function(cb) { + process.nextTick(cb); + }; +} + +/** + * Parse date from string + * @return {Date} + */ +if (String.prototype.parseDate === undefined) { + String.prototype.parseDate = function() { + var self = this.trim(); + var arr = self.indexOf(' ') === -1 ? self.split('T') : self.split(' '); + var index = arr[0].indexOf(':'); + var length = arr[0].length; + + if (index !== -1) { + var tmp = arr[1]; + arr[1] = arr[0]; + arr[0] = tmp; + } + + if (arr[0] === undefined) + arr[0] = ''; + + var noTime = arr[1] === undefined ? true : arr[1].length === 0; + + for (var i = 0; i < length; i++) { + var c = arr[0].charCodeAt(i); + if (c > 47 && c < 58) + continue; + if (c === 45 || c === 46) + continue; + + if (noTime) + return new Date(self); + } + + if (arr[1] === undefined) + arr[1] = '00:00:00'; + + var firstDay = arr[0].indexOf('-') === -1; + + var date = (arr[0] || '').split(firstDay ? '.' : '-'); + var time = (arr[1] || '').split(':'); + var parsed = []; + + if (date.length < 4 && time.length < 2) + return new Date(self); + + index = (time[2] || '').indexOf('.'); + + // milliseconds + if (index !== -1) { + time[3] = time[2].substring(index + 1); + time[2] = time[2].substring(0, index); + } else + time[3] = '0'; + + parsed.push(parseInt(date[firstDay ? 2 : 0], 10)); // year + parsed.push(parseInt(date[1], 10)); // month + parsed.push(parseInt(date[firstDay ? 0 : 2], 10)); // day + parsed.push(parseInt(time[0], 10)); // hours + parsed.push(parseInt(time[1], 10)); // minutes + parsed.push(parseInt(time[2], 10)); // seconds + parsed.push(parseInt(time[3], 10)); // miliseconds + + var def = new Date(); + + for (var i = 0, length = parsed.length; i < length; i++) { + if (isNaN(parsed[i])) + parsed[i] = 0; + + var value = parsed[i]; + if (value !== 0) + continue; + + switch (i) { + case 0: + if (value <= 0) + parsed[i] = def.getFullYear(); + break; + case 1: + if (value <= 0) + parsed[i] = def.getMonth() + 1; + break; + case 2: + if (value <= 0) + parsed[i] = def.getDate(); + break; + } + } + + return new Date(parsed[0], parsed[1] - 1, parsed[2], parsed[3], parsed[4], parsed[5]); + }; +} + +function noop() {} + +const + op_void = 0, // Packet has been voided + op_connect = 1, // Connect to remote server + op_exit = 2, // Remote end has exitted + op_accept = 3, // Server accepts connection + op_reject = 4, // Server rejects connection + op_disconnect = 6, // Connect is going away + op_response = 9, // Generic response block + + // Full context server operations + + op_attach = 19, // Attach database + op_create = 20, // Create database + op_detach = 21, // Detach database + op_compile = 22, // Request based operations + op_start = 23, + op_start_and_send = 24, + op_send = 25, + op_receive = 26, + op_unwind = 27, // apparently unused, see protocol.cpp's case op_unwind + op_release = 28, + + op_transaction = 29, // Transaction operations + op_commit = 30, + op_rollback = 31, + op_prepare = 32, + op_reconnect = 33, + + op_create_blob = 34, // Blob operations + op_open_blob = 35, + op_get_segment = 36, + op_put_segment = 37, + op_cancel_blob = 38, + op_close_blob = 39, + + op_info_database = 40, // Information services + op_info_request = 41, + op_info_transaction = 42, + op_info_blob = 43, + + op_batch_segments = 44, // Put a bunch of blob segments + + op_que_events = 48, // Que event notification request + op_cancel_events = 49, // Cancel event notification request + op_commit_retaining = 50, // Commit retaining (what else) + op_prepare2 = 51, // Message form of prepare + op_event = 52, // Completed event request (asynchronous) + op_connect_request = 53, // Request to establish connection + op_aux_connect = 54, // Establish auxiliary connection + op_ddl = 55, // DDL call + op_open_blob2 = 56, + op_create_blob2 = 57, + op_get_slice = 58, + op_put_slice = 59, + op_slice = 60, // Successful response to op_get_slice + op_seek_blob = 61, // Blob seek operation + +// DSQL operations + + op_allocate_statement = 62, // allocate a statment handle + op_execute = 63, // execute a prepared statement + op_exec_immediate = 64, // execute a statement + op_fetch = 65, // fetch a record + op_fetch_response = 66, // response for record fetch + op_free_statement = 67, // free a statement + op_prepare_statement = 68, // prepare a statement + op_set_cursor = 69, // set a cursor name + op_info_sql = 70, + + op_dummy = 71, // dummy packet to detect loss of client + op_response_piggyback = 72, // response block for piggybacked messages + op_start_and_receive = 73, + op_start_send_and_receive = 74, + op_exec_immediate2 = 75, // execute an immediate statement with msgs + op_execute2 = 76, // execute a statement with msgs + op_insert = 77, + op_sql_response = 78, // response from execute, exec immed, insert + op_transact = 79, + op_transact_response = 80, + op_drop_database = 81, + op_service_attach = 82, + op_service_detach = 83, + op_service_info = 84, + op_service_start = 85, + op_rollback_retaining = 86, + op_partial = 89, // packet is not complete - delay processing + op_trusted_auth = 90, + op_cancel = 91, + op_cont_auth = 92, + op_ping = 93, + op_accept_data = 94, // Server accepts connection and returns some data to client + op_abort_aux_connection = 95, // Async operation - stop waiting for async connection to arrive + op_crypt = 96, + op_crypt_key_callback = 97, + op_cond_accept = 98; // Server accepts connection, returns some data to client + // and asks client to continue authentication before attach call + +const + CONNECT_VERSION2 = 2; + ARCHITECTURE_GENERIC = 1; + +const +// Protocol 10 includes support for warnings and removes the requirement for +// encoding and decoding status codes + PROTOCOL_VERSION10 = 10, + +// Since protocol 11 we must be separated from Borland Interbase. +// Therefore always set highmost bit in protocol version to 1. +// For unsigned protocol version this does not break version's compare. + + FB_PROTOCOL_FLAG = 0x8000, + +// Protocol 11 has support for user authentication related +// operations (op_update_account_info, op_authenticate_user and +// op_trusted_auth). When specific operation is not supported, +// we say "sorry". + + PROTOCOL_VERSION11 = (FB_PROTOCOL_FLAG | 11), + +// Protocol 12 has support for asynchronous call op_cancel. +// Currently implemented asynchronously only for TCP/IP. + + PROTOCOL_VERSION12 = (FB_PROTOCOL_FLAG | 12), + +// Protocol 13 has support for authentication plugins (op_cont_auth). + + PROTOCOL_VERSION13 = (FB_PROTOCOL_FLAG | 13); + + +const + DSQL_close = 1, + DSQL_drop = 2, + DSQL_unprepare = 4; // >= 2.5 + +const + ptype_batch_send = 3; + +const + SQL_TEXT = 452, // Array of char + SQL_VARYING = 448, + SQL_SHORT = 500, + SQL_LONG = 496, + SQL_FLOAT = 482, + SQL_DOUBLE = 480, + SQL_D_FLOAT = 530, + SQL_TIMESTAMP = 510, + SQL_BLOB = 520, + SQL_ARRAY = 540, + SQL_QUAD = 550, + SQL_TYPE_TIME = 560, + SQL_TYPE_DATE = 570, + SQL_INT64 = 580, + SQL_BOOLEAN = 32764, // >= 3.0 + SQL_NULL = 32766; // >= 2.5 + +/***********************/ +/* ISC Error Codes */ +/***********************/ +const + isc_arg_end = 0, // end of argument list + isc_arg_gds = 1, // generic DSRI status value + isc_arg_string = 2, // string argument + isc_arg_cstring = 3, // count & string argument + isc_arg_number = 4, // numeric argument (long) + isc_arg_interpreted = 5, // interpreted status code (string) + isc_arg_unix = 7, // UNIX error code + isc_arg_next_mach = 15, // NeXT/Mach error code + isc_arg_win32 = 17, // Win32 error code + isc_arg_warning = 18, // warning argument + isc_arg_sql_state = 19; // SQLSTATE + +const + isc_sqlerr = 335544436; + +/**********************************/ +/* Database parameter block stuff */ +/**********************************/ +const + isc_dpb_version1 = 1, + isc_dpb_version2 = 2, // >= FB30 + isc_dpb_cdd_pathname = 1, + isc_dpb_allocation = 2, + isc_dpb_journal = 3, + isc_dpb_page_size = 4, + isc_dpb_num_buffers = 5, + isc_dpb_buffer_length = 6, + isc_dpb_debug = 7, + isc_dpb_garbage_collect = 8, + isc_dpb_verify = 9, + isc_dpb_sweep = 10, + isc_dpb_enable_journal = 11, + isc_dpb_disable_journal = 12, + isc_dpb_dbkey_scope = 13, + isc_dpb_number_of_users = 14, + isc_dpb_trace = 15, + isc_dpb_no_garbage_collect = 16, + isc_dpb_damaged = 17, + isc_dpb_license = 18, + isc_dpb_sys_user_name = 19, + isc_dpb_encrypt_key = 20, + isc_dpb_activate_shadow = 21, + isc_dpb_sweep_interval = 22, + isc_dpb_delete_shadow = 23, + isc_dpb_force_write = 24, + isc_dpb_begin_log = 25, + isc_dpb_quit_log = 26, + isc_dpb_no_reserve = 27, + isc_dpb_user_name = 28, + isc_dpb_password = 29, + isc_dpb_password_enc = 30, + isc_dpb_sys_user_name_enc = 31, + isc_dpb_interp = 32, + isc_dpb_online_dump = 33, + isc_dpb_old_file_size = 34, + isc_dpb_old_num_files = 35, + isc_dpb_old_file = 36, + isc_dpb_old_start_page = 37, + isc_dpb_old_start_seqno = 38, + isc_dpb_old_start_file = 39, + isc_dpb_old_dump_id = 41, + isc_dpb_lc_messages = 47, + isc_dpb_lc_ctype = 48, + isc_dpb_cache_manager = 49, + isc_dpb_shutdown = 50, + isc_dpb_online = 51, + isc_dpb_shutdown_delay = 52, + isc_dpb_reserved = 53, + isc_dpb_overwrite = 54, + isc_dpb_sec_attach = 55, + isc_dpb_connect_timeout = 57, + isc_dpb_dummy_packet_interval = 58, + isc_dpb_gbak_attach = 59, + isc_dpb_sql_role_name = 60, + isc_dpb_set_page_buffers = 61, + isc_dpb_working_directory = 62, + isc_dpb_sql_dialect = 63, + isc_dpb_set_db_readonly = 64, + isc_dpb_set_db_sql_dialect = 65, + isc_dpb_gfix_attach = 66, + isc_dpb_gstat_attach = 67, + isc_dpb_set_db_charset = 68, + isc_dpb_gsec_attach = 69, + isc_dpb_address_path = 70, + isc_dpb_process_id = 71, + isc_dpb_no_db_triggers = 72, + isc_dpb_trusted_auth = 73, + isc_dpb_process_name = 74, + isc_dpb_trusted_role = 75, + isc_dpb_org_filename = 76, + isc_dpb_utf8_filename = 77, + isc_dpb_ext_call_depth = 78; + +/*************************************/ +/* Transaction parameter block stuff */ +/*************************************/ +const + isc_tpb_version1 = 1, + isc_tpb_version3 = 3, + isc_tpb_consistency = 1, + isc_tpb_concurrency = 2, + isc_tpb_shared = 3, // < FB21 + isc_tpb_protected = 4, // < FB21 + isc_tpb_exclusive = 5, // < FB21 + isc_tpb_wait = 6, + isc_tpb_nowait = 7, + isc_tpb_read = 8, + isc_tpb_write = 9, + isc_tpb_lock_read = 10, + isc_tpb_lock_write = 11, + isc_tpb_verb_time = 12, + isc_tpb_commit_time = 13, + isc_tpb_ignore_limbo = 14, + isc_tpb_read_committed = 15, + isc_tpb_autocommit = 16, + isc_tpb_rec_version = 17, + isc_tpb_no_rec_version = 18, + isc_tpb_restart_requests = 19, + isc_tpb_no_auto_undo = 20, + isc_tpb_lock_timeout = 21; // >= FB20 + +/****************************/ +/* Common, structural codes */ +/****************************/ +const + isc_info_end = 1, + isc_info_truncated = 2, + isc_info_error = 3, + isc_info_data_not_ready = 4, + isc_info_length = 126, + isc_info_flag_end = 127; + +/*************************/ +/* SQL information items */ +/*************************/ +const + isc_info_sql_select = 4, + isc_info_sql_bind = 5, + isc_info_sql_num_variables = 6, + isc_info_sql_describe_vars = 7, + isc_info_sql_describe_end = 8, + isc_info_sql_sqlda_seq = 9, + isc_info_sql_message_seq = 10, + isc_info_sql_type = 11, + isc_info_sql_sub_type = 12, + isc_info_sql_scale = 13, + isc_info_sql_length = 14, + isc_info_sql_null_ind = 15, + isc_info_sql_field = 16, + isc_info_sql_relation = 17, + isc_info_sql_owner = 18, + isc_info_sql_alias = 19, + isc_info_sql_sqlda_start = 20, + isc_info_sql_stmt_type = 21, + isc_info_sql_get_plan = 22, + isc_info_sql_records = 23, + isc_info_sql_batch_fetch = 24, + isc_info_sql_relation_alias = 25, // >= 2.0 + isc_info_sql_explain_plan = 26; // >= 3.0 + +/*******************/ +/* Blr definitions */ +/*******************/ +const + blr_text = 14, + blr_text2 = 15, + blr_short = 7, + blr_long = 8, + blr_quad = 9, + blr_float = 10, + blr_double = 27, + blr_d_float = 11, + blr_timestamp = 35, + blr_varying = 37, + blr_varying2 = 38, + blr_blob = 261, + blr_cstring = 40, + blr_cstring2 = 41, + blr_blob_id = 45, + blr_sql_date = 12, + blr_sql_time = 13, + blr_int64 = 16, + blr_blob2 = 17, // >= 2.0 + blr_domain_name = 18, // >= 2.1 + blr_domain_name2 = 19, // >= 2.1 + blr_not_nullable = 20, // >= 2.1 + blr_column_name = 21, // >= 2.5 + blr_column_name2 = 22, // >= 2.5 + blr_bool = 23, // >= 3.0 + + blr_version4 = 4, + blr_version5 = 5, // dialect 3 + blr_eoc = 76, + blr_end = 255, + + blr_assignment = 1, + blr_begin = 2, + blr_dcl_variable = 3, + blr_message = 4; + +const + isc_info_sql_stmt_select = 1, + isc_info_sql_stmt_insert = 2, + isc_info_sql_stmt_update = 3, + isc_info_sql_stmt_delete = 4, + isc_info_sql_stmt_ddl = 5, + isc_info_sql_stmt_get_segment = 6, + isc_info_sql_stmt_put_segment = 7, + isc_info_sql_stmt_exec_procedure = 8, + isc_info_sql_stmt_start_trans = 9, + isc_info_sql_stmt_commit = 10, + isc_info_sql_stmt_rollback = 11, + isc_info_sql_stmt_select_for_upd = 12, + isc_info_sql_stmt_set_generator = 13, + isc_info_sql_stmt_savepoint = 14; + +const + isc_blob_text = 1; + +const + DESCRIBE = + [isc_info_sql_stmt_type, + isc_info_sql_select, + isc_info_sql_describe_vars, + isc_info_sql_sqlda_seq, + isc_info_sql_type, + isc_info_sql_sub_type, + isc_info_sql_scale, + isc_info_sql_length, + isc_info_sql_field, + isc_info_sql_relation, + //isc_info_sql_owner, + isc_info_sql_alias, + isc_info_sql_describe_end, + isc_info_sql_bind, + isc_info_sql_describe_vars, + isc_info_sql_sqlda_seq, + isc_info_sql_type, + isc_info_sql_sub_type, + isc_info_sql_scale, + isc_info_sql_length, + isc_info_sql_describe_end]; + +const + ISOLATION_READ_UNCOMMITTED = [isc_tpb_version3, isc_tpb_write, isc_tpb_wait, isc_tpb_read_committed, isc_tpb_rec_version], + ISOLATION_READ_COMMITED = [isc_tpb_version3, isc_tpb_write, isc_tpb_wait, isc_tpb_read_committed, isc_tpb_no_rec_version], + ISOLATION_REPEATABLE_READ = [isc_tpb_version3, isc_tpb_write, isc_tpb_wait, isc_tpb_concurrency], + ISOLATION_SERIALIZABLE = [isc_tpb_version3, isc_tpb_write, isc_tpb_wait, isc_tpb_consistency], + ISOLATION_READ_COMMITED_READ_ONLY = [isc_tpb_version3, isc_tpb_read, isc_tpb_wait, isc_tpb_read_committed, isc_tpb_no_rec_version]; + +const + DEFAULT_HOST = '127.0.0.1', + DEFAULT_PORT = 3050, + DEFAULT_USER = 'SYSDBA', + DEFAULT_PASSWORD = 'masterkey', + DEFAULT_PAGE_SIZE = 4096; + +exports.ISOLATION_READ_UNCOMMITTED = ISOLATION_READ_UNCOMMITTED; +exports.ISOLATION_READ_COMMITED = ISOLATION_READ_COMMITED; +exports.ISOLATION_REPEATABLE_READ = ISOLATION_REPEATABLE_READ; +exports.ISOLATION_SERIALIZABLE = ISOLATION_SERIALIZABLE; +exports.ISOLATION_READ_COMMITED_READ_ONLY = ISOLATION_READ_COMMITED_READ_ONLY; + +if (!String.prototype.padLeft) { + String.prototype.padLeft = function(max, c) { + var self = this; + return new Array(Math.max(0, max - self.length + 1)).join(c || ' ') + self; + }; +} + +/** + * Escape value + * @param {Object} value + * @return {String} + */ +exports.escape = function(value) { + + if (value === null || value === undefined) + return 'NULL'; + + switch (typeof(value)) { + case 'boolean': + return value ? '1' : '0'; + case 'number': + return value.toString(); + case 'string': + return "'" + value.replace(/'/g, "''").replace(/\\/g, '\\\\') + "'"; + } + + if (value instanceof Date) + return "'" + value.getFullYear() + '-' + value.getMonth().toString().padLeft(2, '0') + '-' + value.getDate().toString().padLeft(2, '0') + ' ' + value.getHours().toString().padLeft(2, '0') + ':' + value.getMinutes().toString().padLeft(2, '0') + ':' + value.getSeconds().toString().padLeft(2, '0') + "'"; + + throw new Error('Escape supports only primitive values.'); +}; + +const + DEFAULT_ENCODING = 'utf8'; + DEFAULT_FETCHSIZE = 200; + +const + MAX_INT = Math.pow(2, 31) - 1; + MIN_INT = - Math.pow(2, 31); + +/*************************************** + * + * SQLVar + * + ***************************************/ + + +const + ScaleDivisor = [1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000,10000000000, 100000000000,1000000000000,10000000000000,100000000000000,1000000000000000]; +const + DateOffset = 40587, + TimeCoeff = 86400000; + MsPerMinute = 60000; + +//------------------------------------------------------ + +function SQLVarText() {} + +SQLVarText.prototype.decode = function(data) { + var ret; + if (this.subType > 1) { + ret = data.readText(this.length, DEFAULT_ENCODING); + } else { + ret = data.readBuffer(this.length); + } + + if (!data.readInt()) { + return ret; + } + return null; +}; + +SQLVarText.prototype.calcBlr = function(blr) { + blr.addByte(blr_text); + blr.addWord(this.length); +}; + +//------------------------------------------------------ + +function SQLVarNull() {} +SQLVarNull.prototype = new SQLVarText(); +SQLVarNull.prototype.constructor = SQLVarNull; + +//------------------------------------------------------ + +function SQLVarString() {} + +SQLVarString.prototype.decode = function(data) { + var ret; + if (this.subType > 1) { + ret = data.readString(DEFAULT_ENCODING) + } else { + ret = data.readBuffer() + } + if (!data.readInt()) { + return ret; + } + return null; +}; + +SQLVarString.prototype.calcBlr = function(blr) { + blr.addByte(blr_varying); + blr.addWord(this.length); +}; + +//------------------------------------------------------ + +function SQLVarQuad() {} + +SQLVarQuad.prototype.decode = function(data) { + var ret = data.readQuad(); + if (!data.readInt()) { + return ret; + } + return null; +}; + +SQLVarQuad.prototype.calcBlr = function(blr) { + blr.addByte(blr_quad); + blr.addShort(this.scale); +}; + +//------------------------------------------------------ + +function SQLVarBlob() {} +SQLVarBlob.prototype = new SQLVarQuad(); +SQLVarBlob.prototype.constructor = SQLVarBlob; + +SQLVarBlob.prototype.calcBlr = function(blr) { + blr.addByte(blr_quad); + blr.addShort(0); +}; + +//------------------------------------------------------ + +function SQLVarArray() {} +SQLVarArray.prototype = new SQLVarQuad(); +SQLVarArray.prototype.constructor = SQLVarArray; + +SQLVarArray.prototype.calcBlr = function(blr) { + blr.addByte(blr_quad); + blr.addShort(0); +}; + +//------------------------------------------------------ + +function SQLVarInt() {} + +SQLVarInt.prototype.decode = function(data) { + var ret = data.readInt(); + if (!data.readInt()) { + if (this.scale) { + ret = ret / ScaleDivisor[Math.abs(this.scale)]; + } + return ret; + } + return null; +}; + +SQLVarInt.prototype.calcBlr = function(blr) { + blr.addByte(blr_long); + blr.addShort(this.scale); +}; + +//------------------------------------------------------ + +function SQLVarShort() {} +SQLVarShort.prototype = new SQLVarInt(); +SQLVarShort.prototype.constructor = SQLVarShort; + +SQLVarShort.prototype.calcBlr = function(blr) { + blr.addByte(blr_short); + blr.addShort(this.scale); +}; + +//------------------------------------------------------ + +function SQLVarInt64() {} + +SQLVarInt64.prototype.decode = function(data) { + var ret = data.readInt64(); + if (!data.readInt()) { + if (this.scale) { + ret = ret / ScaleDivisor[Math.abs(this.scale)]; + } + return ret; + } + return null; +}; + +SQLVarInt64.prototype.calcBlr = function(blr) { + blr.addByte(blr_int64); + blr.addShort(this.scale); +}; + +//------------------------------------------------------ + +function SQLVarFloat() {} + +SQLVarFloat.prototype.decode = function(data) { + var ret = data.readFloat(); + if (!data.readInt()) { + return ret; + } + return null; +}; + +SQLVarFloat.prototype.calcBlr = function(blr) { + blr.addByte(blr_float); +}; + +//------------------------------------------------------ + +function SQLVarDouble() {} + +SQLVarDouble.prototype.decode = function(data) { + var ret = data.readDouble(); + if (!data.readInt()) { + return ret; + } + return null; +}; + +SQLVarDouble.prototype.calcBlr = function(blr) { + blr.addByte(blr_double); +}; + +//------------------------------------------------------ + +function SQLVarDate() {} + +SQLVarDate.prototype.decode = function(data) { + var ret = data.readInt(); + if (!data.readInt()) { + var d = new Date(0); + d.setMilliseconds((ret - DateOffset) * TimeCoeff + d.getTimezoneOffset() * MsPerMinute); + return d; + } + return null; +}; + +SQLVarDate.prototype.calcBlr = function(blr) { + blr.addByte(blr_sql_date); +}; + +//------------------------------------------------------ + +function SQLVarTime() {} + +SQLVarTime.prototype.decode = function(data) { + var ret = data.readUInt(); + if (!data.readInt()) { + var d = new Date(0); + d.setMilliseconds(Math.floor(ret / 10) + d.getTimezoneOffset() * MsPerMinute); + return d; + } + return null; +}; + +SQLVarTime.prototype.calcBlr = function(blr) { + blr.addByte(blr_sql_time); +}; + +//------------------------------------------------------ + +function SQLVarTimeStamp() {} + +SQLVarTimeStamp.prototype.decode = function(data) { + var date = data.readInt(); + var time = data.readUInt(); + if (!data.readInt()) { + var d = new Date(0); + d.setMilliseconds((date - DateOffset) * TimeCoeff + Math.floor(time / 10) + d.getTimezoneOffset() * MsPerMinute); + return d; + } + return null; +}; + +SQLVarTimeStamp.prototype.calcBlr = function(blr) { + blr.addByte(blr_timestamp); +}; + +//------------------------------------------------------ + +// todo: test it +function SQLVarBoolean() {} + +SQLVarBoolean.prototype.decode = function(data) { + var ret = data.readInt(); + if (!data.readInt()) { + return Boolean(ret); + } + return null; +}; + +SQLVarBoolean.prototype.calcBlr = function(blr) { + blr.addByte(blr_bool); +}; + +//------------------------------------------------------ + +function SQLParamInt(value){ + this.value = value; +} + +SQLParamInt.prototype.calcBlr = function(blr) { + blr.addByte(blr_long); + blr.addShort(0); +}; + +SQLParamInt.prototype.encode = function(data) { + if (this.value != null) { + data.addInt(this.value); + data.addInt(0); + } else { + data.addInt(0); + data.addInt(1); + } +}; + +//------------------------------------------------------ + +function SQLParamInt64(value){ + this.value = value; +} + +SQLParamInt64.prototype.calcBlr = function(blr) { + blr.addByte(blr_int64); + blr.addShort(0); +}; + +SQLParamInt64.prototype.encode = function(data) { + if (this.value != null) { + data.addInt64(this.value); + data.addInt(0); + } else { + data.addInt64(0); + data.addInt(1); + } +}; + +//------------------------------------------------------ + +function SQLParamDouble(value) { + this.value = value; +} + +SQLParamDouble.prototype.encode = function(data) { + if (this.value != null) { + data.addDouble(this.value); + data.addInt(0); + } else { + data.addDouble(0); + data.addInt(1); + } +}; + +SQLParamDouble.prototype.calcBlr = function(blr) { + blr.addByte(blr_double); +}; + +//------------------------------------------------------ + +function SQLParamString(value) { + this.value = value; +} + +SQLParamString.prototype.encode = function(data) { + if (this.value != null) { + data.addText(this.value, DEFAULT_ENCODING); + data.addInt(0); + } else { + data.addInt(1); + } +}; + +SQLParamString.prototype.calcBlr = function(blr) { + blr.addByte(blr_text); + var len = this.value ? Buffer.byteLength(this.value, DEFAULT_ENCODING) : 0; + blr.addWord(len); +}; + +//------------------------------------------------------ + +function SQLParamQuad(value) { + this.value = value; +} + +SQLParamQuad.prototype.encode = function(data) { + if (this.value != null) { + data.addInt(this.value.high); + data.addInt(this.value.low); + data.addInt(0); + } else { + data.addInt(0); + data.addInt(0); + data.addInt(1); + } +}; + +SQLParamQuad.prototype.calcBlr = function(blr) { + blr.addByte(blr_quad); + blr.addShort(0); +}; + +//------------------------------------------------------ + +function SQLParamDate(value) { + this.value = value; +} + +SQLParamDate.prototype.encode = function(data) { + if (this.value != null) { + + var value = this.value.getTime() - this.value.getTimezoneOffset() * MsPerMinute; + var time = value % TimeCoeff; + var date = (value - time) / TimeCoeff + DateOffset; + time *= 10; + data.addInt(date); + data.addUInt(time); + data.addInt(0); + } else { + data.addInt(0); + data.addUInt(0); + data.addInt(1); + } +}; + +SQLParamDate.prototype.calcBlr = function(blr) { + blr.addByte(blr_timestamp); +}; + +//------------------------------------------------------ + +function SQLParamBool(value) { + this.value = value; +} + +SQLParamBool.prototype.encode = function(data) { + if (this.value != null) { + data.addInt(this.value ? 1 : 0); + data.addInt(0); + } else { + data.addInt(0); + data.addInt(1); + } +}; + +SQLParamBool.prototype.calcBlr = function(blr) { + blr.addByte(blr_short); + blr.addShort(0); +}; + + +/*************************************** + * + * Error handling + * + ***************************************/ + +function isError(obj) { + return (obj instanceof Object && obj.status); +} + +function doCallback(obj, callback) { + + if (!callback) + return; + + if (isError(obj)) { + callback(new Error(obj.message)); + return; + } + + callback(undefined, obj); + +} + +function doError(obj, callback) { + if (callback) + callback(obj) +} + +/*************************************** + * + * Statement + * + ***************************************/ + +function Statement(connection) { + this.connection = connection; +} + +Statement.prototype.close = function(callback) { + this.connection.closeStatement(this, callback); +}; + +Statement.prototype.drop = function(callback) { + this.connection.dropStatement(this, callback); +}; + +Statement.prototype.execute = function(transaction, params, callback, custom) { + + if (params instanceof Function) { + custom = callback; + callback = params; + params = undefined; + } + + this.custom = custom; + this.connection.executeStatement(transaction, this, params, callback, custom); +}; + +Statement.prototype.fetch = function(transaction, count, callback) { + this.connection.fetch(this, transaction, count, callback); +}; + +Statement.prototype.fetchAll = function(transaction, callback) { + this.connection.fetchAll(this, transaction, callback); +}; + +/*************************************** + * + * Transaction + * + ***************************************/ + +function Transaction(connection) { + this.connection = connection; + this.db = connection.db; +} + +Transaction.prototype.newStatement = function(query, callback) { + var cnx = this.connection; + var self = this; + + cnx.allocateStatement(function(err, statement) { + if (err) { + doError(err, callback); + return; + } + cnx.prepareStatement(self, statement, query, false, callback); + }); +}; + +Transaction.prototype.execute = function(query, params, callback, custom) { + + if (params instanceof Function) { + callback = params; + params = undefined; + } + + var self = this; + + this.newStatement(query, function(err, statement) { + + if (err) { + doError(err, callback); + return; + } + + function dropError(err) { + statement.drop(); + doCallback(err, callback); + } + + statement.execute(self, params, function(err) { + + if (err) { + dropError(err); + return; + } + + switch (statement.type) { + + case isc_info_sql_stmt_select: + statement.fetchAll(self, function(err, ret) { + + if (err) { + dropError(err); + return; + } + + statement.drop(); + + if (callback) + callback(undefined, ret, statement.output, true); + + }); + + break; + + case isc_info_sql_stmt_exec_procedure: + if (statement.output.length) { + statement.fetch(self, 1, function(err, ret) { + if (err) { + dropError(err); + return; + } + + statement.drop(); + + if (callback) + callback(undefined, ret.data[0], statement.output, false); + }); + + break; + } + + // Fall through is normal + default: + statement.drop(); + if (callback) + callback() + break; + } + + }, custom); + }); +}; + +Transaction.prototype.query = function(query, params, callback) { + + if (params instanceof Function) { + callback = params; + params = undefined; + } + + if (callback === undefined) + callback = noop; + + this.execute(query, params, callback, { asObject: true, asStream: callback === undefined || callback === null }); + +}; + +Transaction.prototype.commit = function(callback) { + this.connection.commit(this, callback); +}; + +Transaction.prototype.rollback = function(callback) { + this.connection.rollback(this, callback); +}; + +Transaction.prototype.commitRetaining = function(callback) { + this.connection.commitRetaining(this, callback); +}; + +Transaction.prototype.rollbackRetaining = function(callback) { + this.connection.rollbackRetaining(this, callback); +}; + +/*************************************** + * + * Database + * + ***************************************/ + +function Database(connection) { + this.connection = connection; + connection.db = this; +} + +Database.prototype.__proto__ = new Events.EventEmitter(); + +Database.prototype.escape = function(value) { + return exports.escape(value); +}; + +Database.prototype.detach = function(callback, force) { + + var self = this; + + if (!force && self.connection._pending.length > 0) { + self.connection._detachAuto = true; + self.connection._detachCallback = callback; + return self; + } + + self.connection.detach(function(err, obj) { + + self.connection.disconnect(); + self.emit('detach', false); + + if (callback) + callback(err, obj); + + }, force); + + return self; +}; + +Database.prototype.transaction = function(isolation, callback) { + return this.startTransaction(isolation, callback); +}; + +Database.prototype.startTransaction = function(isolation, callback) { + this.connection.startTransaction(isolation, callback); + return this; +}; + +Database.prototype.newStatement = function (query, callback) { + + this.startTransaction(function(err, transaction) { + + if (err) { + callback(err); + return; + } + + transaction.newStatement(query, function(err, statement) { + + if (err) { + callback(err); + return; + } + + transaction.commit(function(err) { + callback(err, statement); + }); + }); + }); + + return this; +}; + +Database.prototype.execute = function(query, params, callback, custom) { + + if (params instanceof Function) { + callback = params; + params = undefined; + } + + var self = this; + + self.connection.startTransaction(function(err, transaction) { + + if (err) { + doError(err, callback); + return; + } + + transaction.execute(query, params, function(err, result, meta, isSelect) { + + if (err) { + transaction.rollback(function() { + doError(err, callback); + }); + return; + } + + transaction.commit(function(err) { + if (callback) + callback(err, result, meta, isSelect); + }); + + }, custom); + }); + + return self; +}; + +Database.prototype.sequentially = function(query, params, on, callback, asArray) { + + if (params instanceof Function) { + asArray = callback; + callback = on; + on = params; + params = undefined; + } + + if (on === undefined) + throw new Error('Expected "on" delegate.'); + + var self = this; + self.execute(query, params, callback, { asObject: !asArray, asStream: true, on: on }); + return self; +}; + +Database.prototype.query = function(query, params, callback) { + + if (params instanceof Function) { + callback = params; + params = undefined; + } + + var self = this; + self.execute(query, params, callback, { asObject: true, asStream: callback === undefined || callback === null }); + return self; +}; + +exports.attach = function(options, callback) { + + var host = options.host || DEFAULT_HOST; + var port = options.port || DEFAULT_PORT; + + var cnx = this.connection = new Connection(host, port, function(err) { + + if (err) { + doError(err, callback); + return; + } + + cnx.connect(options.database || options.filename, function(err) { + if (err) + doError(err, callback); + else + cnx.attach(options, callback); + }); + + }, options); +}; + +exports.create = function(options, callback) { + var host = options.host || DEFAULT_HOST; + var port = options.port || DEFAULT_PORT; + var cnx = this.connection = new Connection(host, port, function(err) { + cnx.connect(options.database || options.filename, function(err) { + + if (err) { + self.db.emit('error', err); + doError(err, callback); + return; + } + + cnx.createDatabase(options, callback); + }); + }, options); +}; + +exports.attachOrCreate = function(options, callback) { + + var host = options.host || DEFAULT_HOST; + var port = options.port || DEFAULT_PORT; + + var cnx = this.connection = new Connection(host, port, function(err) { + + var self = cnx; + + if (err) { + callback({ error: err, message: "Connect error" }); + return; + } + + cnx.connect(options.database || options.filename, function(err) { + + if (err) { + doError(err, callback); + return; + } + + cnx.attach(options, function(err, ret) { + + if (!err) { + if (self.db) + self.db.emit('connect', ret); + doCallback(ret, callback); + return; + } + + cnx.createDatabase(options, callback); + }); + }); + + }, options); +}; + +// Pooling +exports.pool = function(max, options, callback) { + + var pool = new Pool(); + + options.isPool = true; + + function create(max) { + exports.attach(options, function(err, db) { + + if (err) + throw err; + + max--; + + pool.db.push(db); + poolEvents(db, pool); + + if (max <= 0) { + pool.isReady = true; + pool.check(); + if (callback) + callback(null, pool); + return; + } + + create(max); + }); + }; + + create(max); + + return pool; +}; + +function poolEvents(db, pool) { + db.on('detach', function(is) { + + if (!is) + return; + + db.connection._queue = []; + db.connection._pending = []; + db.connection._isUsed = false; + + setImmediate(function() { + pool.check(); + }); + }); +} + +/*************************************** + * + * Simple Pooling + * + ***************************************/ + +function Pool() { + this.db = []; + this.pending = []; + this.isReady = false; + this.isDestroy = false; +} + +Pool.prototype.get = function(callback) { + + var self = this; + if (self.isDestroy) + return self; + + self.pending.push(callback); + self.check(); + return self; +}; + +Pool.prototype.check = function() { + + var self = this; + + for (var i = 0, length = self.db.length; i < length; i++) { + + var db = self.db[i]; + if (db.connection._isUsed) + continue; + + db.removeAllListeners(); + poolEvents(db, self); + + var cb = self.pending.shift(); + if (cb) { + db.connection._isUsed = true; + cb(null, db); + } + + return self; + } + + return self; +}; + +Pool.prototype.detach = function() { + + var self = this; + var count = self.db.length; + + var fn = function() { + count--; + if (count > 0 || !self.isDestroy) + return; + self.db = null; + self.pending = null; + }; + + for (var i = 0; i < self.db.length; i++) + self.db[i].detach(fn, true); + + return self; +}; + +Pool.prototype.destroy = function() { + var self = this; + self.detach(); + self.isDestroy = true; + return self; +}; + +/*************************************** + * + * Connection + * + ***************************************/ + +var Connection = exports.Connection = function (host, port, callback, options, db) { + var self = this; + this.db = db; + this._msg = new XdrWriter(32); + this._blr = new BlrWriter(32); + this._queue = []; + this._detachTimeout; + this._detachCallback; + this._detachAuto; + this._socket = net.createConnection(port, host); + this._pending = []; + this._isClosed = false; + this._isDetach = false; + this._isUsed = false; + this.options = options; + this._bind_events(host, port, callback); + this.error; +}; + +exports.Connection.prototype._bind_events = function(host, port, callback) { + + var self = this; + + self._socket.on('close', function() { + + self._isClosed = true; + + if (self._isDetach) + return; + + if (!self.db) { + if (callback) + callback(self.error); + return; + } + + setImmediate(function() { + + self._socket = null; + self._msg = null; + self._blr = null; + + var ctx = new Connection(host, port, function(err) { + ctx.connect(self.options.filename, function(err) { + + if (err) { + self.emit('error', err); + return; + } + + ctx.attach(self.options, function(err) { + + if (err) { + self.emit('error', err); + return; + } + + ctx._queue = ctx._queue.concat(self._queue); + ctx._pending = ctx._pending.concat(self._pending); + self.db.emit('reconnect'); + + }, self.db); + }); + + }, self.options, self.db); + }); + + }); + + self._socket.on('error', function(e) { + + self.error = e; + + if (self.db) + self.db.emit('error', e) + + if (callback) + callback(e); + + }); + + self._socket.on('connect', function() { + self._isClosed = false; + self._isOpened = true; + if (callback) + callback(); + }); + + self._socket.on('data', function(data) { + + var obj, cb, pos, xdr, buf; + + if (!self._xdr) { + xdr = new XdrReader(data); + } else { + xdr = self._xdr; + delete(self._xdr); + buf = new Buffer(data.length + xdr.buffer.length); + xdr.buffer.copy(buf); + data.copy(buf, xdr.buffer.length); + xdr.buffer = buf; + } + + + while (xdr.pos < xdr.buffer.length) { + + pos = xdr.pos; + + try { + cb = self._queue[0]; + obj = decodeResponse(xdr, cb, self.db); + } catch(err) { + buf = new Buffer(xdr.buffer.length - pos); + xdr.buffer.copy(buf, 0, pos); + xdr.buffer = buf; + xdr.pos = 0; + self._xdr = xdr; + return; + } + + self._queue.shift(); + self._pending.shift(); + + if (obj && obj.status) { + + messages.lookupMessages(obj.status, function(message) { + obj.message = message; + doCallback(obj, cb); + }); + + } else + doCallback(obj, cb); + } + + if (!self._detachAuto || self._pending.length !== 0) + return; + + clearTimeout(self._detachTimeout); + self._detachTimeout = setTimeout(function() { + self.db.detach(self._detachCallback); + self._detachAuto = false; + }, 100); + + }); +} + +exports.Connection.prototype.disconnect = function() { + this._socket.end(); +}; + +function decodeResponse(data, callback, db){ + + do { + var r = data.readInt(); + } while (r === op_dummy); + + var item, op; + switch (r) { + case op_response: + + var response; + + if (callback) + response = callback.response || {}; + else + response = {}; + + response.handle = data.readInt(); + var oid = data.readQuad(); + if (oid.low || oid.high) + response.oid = oid + + var buf = data.readArray(); + if (buf) + response.buffer = buf; + + var num; + while (true) { + op = data.readInt(); + switch (op){ + case isc_arg_end: + return response; + case isc_arg_gds: + num = data.readInt(); + if (!num) + break; + item = { gdscode: num }; + if (response.status) + response.status.push(item); + else + response.status = [item]; + break; + case isc_arg_string: + case isc_arg_interpreted: + case isc_arg_sql_state: + + if (item.params) { + var str = data.readString(DEFAULT_ENCODING); + item.params.push(str); + } else + item.params = [data.readString(DEFAULT_ENCODING)]; + + break; + + case isc_arg_number: + num = data.readInt(); + + if (item.params) + item.params.push(num); + else + item.params = [num]; + + if (item.gdscode === isc_sqlerr) + response.sqlcode = num; + + break; + + default: + throw new Error('Unexpected: ' + op); + } + } + break; + + case op_fetch_response: + + var status = data.readInt(); + var count = data.readInt(); + var statement = callback.statement; + var output = statement.output; + var custom = statement.custom || {}; + var cols = null; + var rows = custom.asStream ? null : []; + var index = 0; + + if (custom.asObject) { + cols = []; + for (var i = 0, length = output.length; i < length; i++) + cols.push(output[i].alias.toLowerCase()); + } + + while (count && (status !== 100)) { + + var row = custom.asObject ? {} : new Array(output.length); + + for (var i = 0, length = output.length; i < length; i++) { + + item = output[i]; + var value = item.decode(data); + + if (item.type === 580 && value !== null) + value = value.low_; + + if (custom.asObject) { + if (item.type === SQL_BLOB) + value = fetch_blob_async(statement, value, cols[i]); + row[cols[i]] = value; + } + else { + if (item.type === SQL_BLOB) + value = fetch_blob_async(statement, value, i); + row[i] = value; + } + } + + statement.connection.db.emit('row', row, index, custom.asObject); + + op = data.readInt(); // ?? + status = data.readInt(); + count = data.readInt(); + + if (!custom.asStream) + rows.push(row); + + if (custom.on) + custom.on(row, index); + + index++; + } + + statement.connection.db.emit('result', rows); + return { data: rows, fetched: Boolean(status === 100) }; + + case op_accept: + if (data.readInt() !== PROTOCOL_VERSION10 || data.readInt() !== ARCHITECTURE_GENERIC || data.readInt() !== ptype_batch_send) + throw new Error('Invalid connect result'); + return {}; + + default: + throw new Error('Unexpected:' + r); + } +} + +Connection.prototype._queueEvent = function(callback){ + var self = this; + + if (self._isClosed) { + if (callback) + callback(new Error('Connection is closed.')); + return; + } + + self._queue.push(callback); + self._socket.write(self._msg.getData()); +}; + +Connection.prototype.connect = function (database, callback) { + + var msg = this._msg; + var blr = this._blr; + + msg.pos = 0; + blr.pos = 0; + + msg.addInt(op_connect); + msg.addInt(op_attach); + msg.addInt(CONNECT_VERSION2); + msg.addInt(ARCHITECTURE_GENERIC); + msg.addString(database || '', DEFAULT_ENCODING); + msg.addInt(1); // Protocol version understood count. + + blr.addString(1, process.env['USER'] || process.env['USERNAME'] || 'Unknown', DEFAULT_ENCODING); + var hostname = os.hostname(); + blr.addString(4, hostname, DEFAULT_ENCODING); + blr.addBytes([6, 0]); + msg.addBlr(this._blr); + + msg.addInt(PROTOCOL_VERSION10); + msg.addInt(ARCHITECTURE_GENERIC); + msg.addInt(2); // Min type + msg.addInt(3); // Max type + msg.addInt(2); // Preference weight + + this._queueEvent(callback); +}; + +Connection.prototype.attach = function (options, callback, db) { + + var database = options.database || options.filename; + var user = options.user || DEFAULT_USER; + var password = options.password || DEFAULT_PASSWORD; + var role = options.role; + var self = this; + var msg = this._msg; + var blr = this._blr; + msg.pos = 0; + blr.pos = 0; + + blr.addByte(1); + blr.addString(isc_dpb_lc_ctype, 'UTF8', DEFAULT_ENCODING); + blr.addString(isc_dpb_user_name, user, DEFAULT_ENCODING); + blr.addString(isc_dpb_password, password, DEFAULT_ENCODING); + + if (role) + blr.addString(isc_dpb_sql_role_name, role, DEFAULT_ENCODING); + + msg.addInt(op_attach); + msg.addInt(0); // Database Object ID + msg.addString(database, DEFAULT_ENCODING); + msg.addBlr(this._blr); + + var self = this; + + function cb(err, ret) { + + if (err) { + doError(err, callback); + return; + } + + self.dbhandle = ret.handle; + if (callback) + callback(undefined, ret); + } + + // For reconnect + if (db) { + db.connection = this; + cb.response = db; + } else { + cb.response = new Database(this); + cb.response.on('error', noop); + } + + this._queueEvent(cb); +}; + +Connection.prototype.detach = function (callback, force) { + + var self = this; + + if (self._isClosed) + return; + + if (self.options.isPool && !force) { + self._isUsed = false; + // self._queue = []; + // self._pending = []; + self.db.emit('detach', true); + return; + } + + self._isUsed = false; + self._isDetach = true; + + var msg = self._msg; + + msg.pos = 0; + msg.addInt(op_detach); + msg.addInt(0); // Database Object ID + + self._queueEvent(function(err, ret) { + delete(self.dbhandle); + if (callback) + callback(err, ret); + }); +}; + +Connection.prototype.createDatabase = function (options, callback) { + + var database = options.database || options.filename; + var user = options.user || DEFAULT_USER; + var password = options.password || DEFAULT_PASSWORD; + var pageSize = options.pageSize || DEFAULT_PAGE_SIZE; + var role = options.role; + var blr = this._blr; + + blr.pos = 0; + blr.addByte(1); + blr.addString(isc_dpb_set_db_charset, 'UTF8', DEFAULT_ENCODING); + blr.addString(isc_dpb_lc_ctype, 'UTF8', DEFAULT_ENCODING); + blr.addString(isc_dpb_user_name, user, DEFAULT_ENCODING); + blr.addString(isc_dpb_password, password, DEFAULT_ENCODING); + + if (role) + blr.addString(isc_dpb_sql_role_name, role, DEFAULT_ENCODING); + + blr.addNumeric(isc_dpb_sql_dialect, 3); + blr.addNumeric(isc_dpb_force_write, 1); + blr.addNumeric(isc_dpb_overwrite, 1); + blr.addNumeric(isc_dpb_page_size, pageSize); + + var msg = this._msg; + msg.pos = 0; + msg.addInt(op_create); // op_create + msg.addInt(0); // Database Object ID + msg.addString(database, DEFAULT_ENCODING); + msg.addBlr(blr); + + var self = this; + + function cb(err, ret) { + + if (ret) + self.dbhandle = ret.handle; + + setImmediate(function() { + if (self.db) + self.db.emit('attach', ret); + }); + + if (callback) + callback(err, ret); + } + + cb.response = new Database(this); + this._queueEvent(cb); +}; + +Connection.prototype.throwClosed = function(callback) { + var err = new Error('Connection is closed.'); + this.db.emit('error', err); + if (callback) + callback(err); + return this; +}; + +Connection.prototype.startTransaction = function(isolation, callback) { + + if (typeof(isolation) === 'function') { + var tmp = isolation; + isolation = callback; + callback = tmp; + } + + if (this._isClosed) + return this.throwClosed(callback); + + // for auto detach + this._pending.push('startTransaction'); + + var blr = this._blr; + var msg = this._msg; + + blr.pos = 0; + msg.pos = 0; + + if (isolation instanceof Function) { + callback = isolation; + isolation = null; + } + + blr.addBytes(isolation || ISOLATION_REPEATABLE_READ); + msg.addInt(op_transaction); + msg.addInt(this.dbhandle); + msg.addBlr(blr); + callback.response = new Transaction(this); + + this.db.emit('transaction', isolation); + this._queueEvent(callback); +}; + +Connection.prototype.commit = function (transaction, callback) { + + if (this._isClosed) + return this.throwClosed(callback); + + // for auto detach + this._pending.push('commit'); + + var msg = this._msg; + msg.pos = 0; + msg.addInt(op_commit); + msg.addInt(transaction.handle); + this.db.emit('commit'); + this._queueEvent(callback); +}; + +Connection.prototype.rollback = function (transaction, callback) { + + if (this._isClosed) + return this.throwClosed(callback); + + // for auto detach + this._pending.push('rollback'); + + var msg = this._msg; + msg.pos = 0; + msg.addInt(op_rollback); + msg.addInt(transaction.handle); + this.db.emit('rollback'); + this._queueEvent(callback); +}; + +Connection.prototype.commitRetaining = function (transaction, callback) { + + if (this._isClosed) + throw new Error('Connection is closed.'); + + // for auto detach + this._pending.push('commitRetaining'); + + var msg = this._msg; + msg.pos = 0; + msg.addInt(op_commit_retaining); + msg.addInt(transaction.handle); + this._queueEvent(callback); +}; + +Connection.prototype.rollbackRetaining = function (transaction, callback) { + + if (this._isClosed) + return this.throwClosed(callback); + + // for auto detach + this._pending.push('rollbackRetaining'); + + var msg = this._msg; + msg.pos = 0; + msg.addInt(op_rollback_retaining); + msg.addInt(transaction.handle); + this._queueEvent(callback); +}; + +Connection.prototype.allocateStatement = function (callback) { + + if (this._isClosed) + return this.throwClosed(callback); + + // for auto detach + this._pending.push('allocateStatement'); + + var msg = this._msg; + msg.pos = 0; + msg.addInt(op_allocate_statement); + msg.addInt(this.dbhandle); + callback.response = new Statement(this); + this._queueEvent(callback); +}; + +Connection.prototype.dropStatement = function (statement, callback) { + + if (this._isClosed) + return this.throwClosed(callback); + + // for auto detach + this._pending.push('dropStatement'); + + var msg = this._msg; + msg.pos = 0; + msg.addInt(op_free_statement); + msg.addInt(statement.handle); + msg.addInt(DSQL_drop); + this._queueEvent(callback); +}; + +Connection.prototype.closeStatement = function (statement, callback) { + + if (this._isClosed) + return this.throwClosed(callback); + + // for auto detach + this._pending.push('closeStatement'); + + var msg = this._msg; + msg.pos = 0; + msg.addInt(op_free_statement); + msg.addInt(statement.handle); + msg.addInt(DSQL_close); + this._queueEvent(callback); +}; + +function describe(ret, statement){ + + var br = new BlrReader(ret.buffer); + var parameters = null; + var type, param; + + while (br.pos < br.buffer.length) { + switch (br.readByteCode()) { + case isc_info_sql_stmt_type: + statement.type = br.readInt(); + break; + case isc_info_sql_get_plan: + statement.plan = br.readString(DEFAULT_ENCODING); + break; + case isc_info_sql_select: + statement.output = parameters = []; + break; + case isc_info_sql_bind: + statement.input = parameters = []; + break; + case isc_info_sql_num_variables: + br.readInt(); // eat int + break; + case isc_info_sql_describe_vars: + if (!parameters) {return} + br.readInt(); // eat int ? + var finishDescribe = false; + param = null; + while (!finishDescribe){ + switch (br.readByteCode()) { + case isc_info_sql_describe_end: + break; + case isc_info_sql_sqlda_seq: + var num = br.readInt(); + break; + case isc_info_sql_type: + type = br.readInt(); + switch (type&~1) { + case SQL_VARYING: param = new SQLVarString(); break; + case SQL_NULL: param = new SQLVarNull(); break; + case SQL_TEXT: param = new SQLVarText(); break; + case SQL_DOUBLE: param = new SQLVarDouble(); break; + case SQL_FLOAT: + case SQL_D_FLOAT: param = new SQLVarFloat(); break; + case SQL_TYPE_DATE: param = new SQLVarDate(); break; + case SQL_TYPE_TIME: param = new SQLVarTime(); break; + case SQL_TIMESTAMP: param = new SQLVarTimeStamp(); break; + case SQL_BLOB: param = new SQLVarBlob(); break; + case SQL_ARRAY: param = new SQLVarArray(); break; + case SQL_QUAD: param = new SQLVarQuad(); break; + case SQL_LONG: param = new SQLVarInt(); break; + case SQL_SHORT: param = new SQLVarShort(); break; + case SQL_INT64: param = new SQLVarInt64(); break; + case SQL_BOOLEAN: param = new SQLVarBoolean(); break; + default: + throw new Error('Unexpected'); + } + parameters[num-1] = param; + param.type = type; + param.nullable = Boolean(param.type & 1); + param.type &= ~1; + break; + case isc_info_sql_sub_type: + param.subType = br.readInt(); + break; + case isc_info_sql_scale: + param.scale = br.readInt(); + break; + case isc_info_sql_length: + param.length = br.readInt(); + break; + case isc_info_sql_null_ind: + param.nullable = Boolean(br.readInt()); + break; + case isc_info_sql_field: + param.field = br.readString(DEFAULT_ENCODING); + break; + case isc_info_sql_relation: + param.relation = br.readString(DEFAULT_ENCODING); + break; + case isc_info_sql_owner: + param.owner = br.readString(DEFAULT_ENCODING); + break; + case isc_info_sql_alias: + param.alias = br.readString(DEFAULT_ENCODING); + break; + case isc_info_sql_relation_alias: + param.relationAlias = br.readString(DEFAULT_ENCODING); + break; + case isc_info_truncated: + throw new Error('Truncated'); + default: + finishDescribe = true; + br.pos--; + } + } + } + } +} + +Connection.prototype.prepareStatement = function (transaction, statement, query, plan, callback) { + + if (this._isClosed) + return this.throwClosed(callback); + + var msg = this._msg; + var blr = this._blr; + + msg.pos = 0; + blr.pos = 0; + + if (plan instanceof Function) { + callback = plan; + plan = false; + } + + blr.addBytes(DESCRIBE); + + if (plan) + blr.addByte(isc_info_sql_get_plan); + + msg.addInt(op_prepare_statement); + msg.addInt(transaction.handle); + msg.addInt(statement.handle); + msg.addInt(3); // dialect = 3 + msg.addString(query, DEFAULT_ENCODING); + msg.addBlr(blr); + msg.addInt(65535); // buffer_length + + var self = this; + + this._queueEvent(function(err, ret) { + + if (!err) { + describe(ret, statement); + statement.query = query; + self.db.emit('query', query); + ret = statement; + } + + if (callback) + callback(err, ret); + }); + +}; + +function CalcBlr(blr, xsqlda) { + blr.addBytes([blr_version5, blr_begin, blr_message, 0]); // + message number + blr.addWord(xsqlda.length * 2); + + for (var i = 0, length = xsqlda.length; i < length; i++) { + xsqlda[i].calcBlr(blr); + blr.addByte(blr_short); + blr.addByte(0); + } + + blr.addByte(blr_end); + blr.addByte(blr_eoc); +} + +Connection.prototype.executeStatement = function(transaction, statement, params, callback, custom) { + + if (this._isClosed) + return this.throwClosed(callback); + + // for auto detach + this._pending.push('executeStatement'); + + if (params instanceof Function) { + callback = params; + params = undefined; + } + + var self = this; + + function PrepareParams(params, input, callback) { + + var value, meta; + var ret = new Array(params.length); + var wait = params.length; + + function done() { + wait--; + if (wait === 0) + callback(ret); + } + + function putBlobData(index, value, callback) { + + self.createBlob2(transaction, function(err, blob) { + + var b; + var isStream = value.Readable; + + if (Buffer.isBuffer(value)) + b = value; + else if (typeof(value) === 'string') + b = new Buffer(value, DEFAULT_ENCODING) + else if (!isStream) + b = new Buffer(JSON.stringify(value), DEFAULT_ENCODING) + + if (Buffer.isBuffer(value)) { + bufferReader(b, 1024, function(b, next) { + self.batchSegments(blob, b, next); + }, function() { + ret[index] = new SQLParamQuad(blob.oid); + self.closeBlob(blob, callback); + }); + return; + } + + var isReading = false; + var isEnd = false; + + value.on('data', function(chunk) { + value.pause(); + isReading = true; + bufferReader(chunk, 1024, function(b, next) { + self.batchSegments(blob, b, next); + }, function() { + isReading = false; + + if (isEnd) { + ret[index] = new SQLParamQuad(blob.oid); + self.closeBlob(blob, callback); + } else + value.resume(); + }); + }); + + value.on('end', function() { + isEnd = true; + if (isReading) + return; + ret[index] = new SQLParamQuad(blob.oid); + self.closeBlob(blob, callback); + }); + }); + } + + for (var i = 0, length = params.length; i < length; i++) { + value = params[i]; + meta = input[i]; + + if (value === null) { + switch (meta.type) { + case SQL_VARYING: + case SQL_NULL: + case SQL_TEXT: + ret[i] = new SQLParamString(null); + break; + case SQL_DOUBLE: + case SQL_FLOAT: + case SQL_D_FLOAT: + ret[i] = new SQLParamDouble(null); + break; + case SQL_TYPE_DATE: + case SQL_TYPE_TIME: + case SQL_TIMESTAMP: + ret[i] = new SQLParamDate(null); + break; + case SQL_BLOB: + case SQL_ARRAY: + case SQL_QUAD: + ret[i] = new SQLParamQuad(null); + break; + case SQL_LONG: + case SQL_SHORT: + case SQL_INT64: + case SQL_BOOLEAN: + ret[i] = new SQLParamInt(null); + break; + default: + ret[i] = null; + } + done(); + } else { + switch (meta.type) { + case SQL_BLOB: + putBlobData(i, value, done); + break; + + case SQL_TIMESTAMP: + case SQL_TYPE_DATE: + case SQL_TYPE_TIME: + + if (value instanceof Date) + ret[i] = new SQLParamDate(value); + else if (typeof(value) === 'string') + ret[i] = new SQLParamDate(value.parseDate()); + else + ret[i] = new SQLParamDate(new Date(value)); + + done(); + break; + + default: + switch (typeof value) { + case 'number': + if (value % 1 === 0) { + if (value >= MIN_INT && value <= MAX_INT) + ret[i] = new SQLParamInt(value); + else + ret[i] = new SQLParamInt64(value); + } else + ret[i] = new SQLParamDouble(value); + break; + case 'string': + ret[i] = new SQLParamString(value); + break; + case 'boolean': + ret[i] = new SQLParamBool(value); + break; + default: + throw new Error('Unexpected parametter'); + } + done(); + } + } + } + } + + var input = statement.input; + + if (input.length) { + + if (!(params instanceof Array)) { + if (params !== undefined) + params = [params]; + else + params = []; + } + + if (params === undefined || params.length !== input.length) + throw new Error('Expected parameters: ' + input.length); + + PrepareParams(params, input, function(prms) { + + var msg = self._msg; + var blr = self._blr; + msg.pos = 0; + blr.pos = 0; + CalcBlr(blr, prms); + + msg.addInt(op_execute); + msg.addInt(statement.handle); + msg.addInt(transaction.handle); + msg.addBlr(blr); + msg.addInt(0); // message number + msg.addInt(1); // param count + + for(var i = 0, length = prms.length; i < length; i++) + prms[i].encode(msg); + + self._queueEvent(callback); + }); + + return; + } + + var msg = this._msg; + var blr = this._blr; + msg.pos = 0; + blr.pos = 0; + + msg.addInt(op_execute); + msg.addInt(statement.handle); + msg.addInt(transaction.handle); + + msg.addBlr(blr); // empty + msg.addInt(0); // message number + msg.addInt(0); // param count + + this._queueEvent(callback); +}; + +function fetch_blob_async(statement, id, name) { + + if (!id) + return null; + + return function(callback) { + // callback(err, buffer, name); + statement.connection.startTransaction(ISOLATION_READ_UNCOMMITTED, function(err, transaction) { + + if (err) { + callback(err); + return; + } + + statement.connection._pending.push('openBlob'); + statement.connection.openBlob(id, transaction, function(err, blob) { + + var e = new Events.EventEmitter(); + + if (err) { + callback(err, name, e); + return; + } + + function read() { + statement.connection.getSegment(blob, function(err, ret) { + + if (err) { + e.emit('error', err); + return; + } + + var blr = new BlrReader(ret.buffer); + var data = blr.readSegment(); + + e.emit('data', data); + + if (ret.handle !== 2) { + read(); + return; + } + + e.emit('end'); + e = null; + statement.connection.closeBlob(blob); + + }); + } + + callback(err, name, e); + read(); + + }); + }); + }; +} + +Connection.prototype.fetch = function(statement, transaction, count, callback) { + + var msg = this._msg; + var blr = this._blr; + + msg.pos = 0; + blr.pos = 0; + + if (count instanceof Function) { + callback = count; + count = DEFAULT_FETCHSIZE; + } + + msg.addInt(op_fetch); + msg.addInt(statement.handle); + CalcBlr(blr, statement.output); + msg.addBlr(blr); + msg.addInt(0); // message number + msg.addInt(count || DEFAULT_FETCHSIZE); // fetch count + + if (!transaction) { + callback.statement = statement; + this._queueEvent(callback); + return; + } + + callback.statement = statement; + this._queueEvent(callback); +}; + +Connection.prototype.fetchAll = function(statement, transaction, callback) { + + var self = this; + var data; + var loop = function(err, ret) { + + if (err) { + callback(err); + return; + } + + if (!data) { + data = ret.data; + } else { + for (var i = 0, length = ret.data.length; i < length; i++) + data.push(ret.data[i]); + } + + if (ret.fetched) + callback(undefined, data); + else + self.fetch(statement, transaction, DEFAULT_FETCHSIZE, loop); + } + + this.fetch(statement, transaction, DEFAULT_FETCHSIZE, loop); +}; + +Connection.prototype.openBlob = function(blob, transaction, callback) { + var msg = this._msg; + msg.pos = 0; + msg.addInt(op_open_blob); + msg.addInt(transaction.handle); + msg.addQuad(blob); + this._queueEvent(callback); +}; + +Connection.prototype.closeBlob = function(blob, callback) { + var msg = this._msg; + msg.pos = 0; + msg.addInt(op_close_blob); + msg.addInt(blob.handle); + this._queueEvent(callback); +}; + +Connection.prototype.getSegment = function(blob, callback) { + var msg = this._msg; + msg.pos = 0; + msg.addInt(op_get_segment); + msg.addInt(blob.handle); + msg.addInt(1024); // buffer length + msg.addInt(0); // ??? + this._queueEvent(callback); +}; + +Connection.prototype.createBlob2 = function (transaction, callback) { + var msg = this._msg; + msg.pos = 0; + msg.addInt(op_create_blob2); + msg.addInt(0); + msg.addInt(transaction.handle); + msg.addInt(0); + msg.addInt(0); + this._queueEvent(callback); +}; + +Connection.prototype.batchSegments = function(blob, buffer, callback){ + var msg = this._msg; + var blr = this._blr; + msg.pos = 0; + blr.pos = 0; + msg.addInt(op_batch_segments); + msg.addInt(blob.handle); + msg.addInt(buffer.length + 2); + blr.addBuffer(buffer); + msg.addBlr(blr); + this._queueEvent(callback); +}; + +function bufferReader(buffer, max, writer, cb, beg, end) { + + if (!beg) + beg = 0; + + if (!end) + end = max; + + if (end >= buffer.length) + end = undefined; + + var b = buffer.slice(beg, end); + + writer(b, function() { + + if (end === undefined) { + cb(); + return; + } + + bufferReader(buffer, max, writer, cb, beg + max, end + max); + }); +} \ No newline at end of file diff --git a/node_modules/node-firebird/lib/long.js b/node_modules/node-firebird/lib/long.js new file mode 100644 index 0000000..43e0b79 --- /dev/null +++ b/node_modules/node-firebird/lib/long.js @@ -0,0 +1,855 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright 2009 Google Inc. All Rights Reserved + +/** + * Defines a Long class for representing a 64-bit two's-complement + * integer value, which faithfully simulates the behavior of a Java "Long". This + * implementation is derived from LongLib in GWT. + * + * Constructs a 64-bit two's-complement integer, given its low and high 32-bit + * values as *signed* integers. See the from* functions below for more + * convenient ways of constructing Longs. + * + * The internal representation of a Long is the two given signed, 32-bit values. + * We use 32-bit pieces because these are the size of integers on which + * Javascript performs bit-operations. For operations like addition and + * multiplication, we split each number into 16-bit pieces, which can easily be + * multiplied within Javascript's floating-point representation without overflow + * or change in sign. + * + * In the algorithms below, we frequently reduce the negative case to the + * positive case by negating the input(s) and then post-processing the result. + * Note that we must ALWAYS check specially whether those values are MIN_VALUE + * (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as + * a positive number, it overflows back into a negative). Not handling this + * case would often result in infinite recursion. + * + * @class Represents the BSON Long type. + * @param {Number} low the low (signed) 32 bits of the Long. + * @param {Number} high the high (signed) 32 bits of the Long. + */ +function Long(low, high) { + + if (!(this instanceof Long)) + return new Long(low, high); + + /** + * @type {number} + * @api private + */ + this.low_ = low | 0; // force into 32 signed bits. + + /** + * @type {number} + * @api private + */ + this.high_ = high | 0; // force into 32 signed bits. +}; + +/** + * Return the int value. + * + * @return {Number} the value, assuming it is a 32-bit integer. + * @api public + */ +Long.prototype.toInt = function() { + return this.low_; +}; + +/** + * Return the Number value. + * + * @return {Number} the closest floating-point representation to this value. + * @api public + */ +Long.prototype.toNumber = function() { + return this.high_ * Long.TWO_PWR_32_DBL_ + + this.getLowBitsUnsigned(); +}; + +/** + * Return the JSON value. + * + * @return {String} the JSON representation. + * @api public + */ +Long.prototype.toJSON = function() { + return this.toString(); +} + +/** + * Return the String value. + * + * @param {Number} [opt_radix] the radix in which the text should be written. + * @return {String} the textual representation of this value. + * @api public + */ +Long.prototype.toString = function(opt_radix) { + var radix = opt_radix || 10; + if (radix < 2 || 36 < radix) + throw Error('radix out of range: ' + radix); + + if (this.isZero()) + return '0'; + + if (this.isNegative()) { + if (this.equals(Long.MIN_VALUE)) { + // We need to change the Long value before it can be negated, so we remove + // the bottom-most digit in this base and then recurse to do the rest. + var radixLong = Long.fromNumber(radix); + var div = this.div(radixLong); + var rem = div.multiply(radixLong).subtract(this); + return div.toString(radix) + rem.toInt().toString(radix); + } else { + return '-' + this.negate().toString(radix); + } + } + + // Do several (6) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = Long.fromNumber(Math.pow(radix, 6)); + + var rem = this; + var result = ''; + while (true) { + var remDiv = rem.div(radixToPower); + var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt(); + var digits = intval.toString(radix); + + rem = remDiv; + if (rem.isZero()) { + return digits + result; + } else { + while (digits.length < 6) { + digits = '0' + digits; + } + result = '' + digits + result; + } + } +}; + +/** + * Return the high 32-bits value. + * + * @return {Number} the high 32-bits as a signed value. + * @api public + */ +Long.prototype.getHighBits = function() { + return this.high_; +}; + +/** + * Return the low 32-bits value. + * + * @return {Number} the low 32-bits as a signed value. + * @api public + */ +Long.prototype.getLowBits = function() { + return this.low_; +}; + +/** + * Return the low unsigned 32-bits value. + * + * @return {Number} the low 32-bits as an unsigned value. + * @api public + */ +Long.prototype.getLowBitsUnsigned = function() { + return (this.low_ >= 0) ? + this.low_ : Long.TWO_PWR_32_DBL_ + this.low_; +}; + +/** + * Returns the number of bits needed to represent the absolute value of this Long. + * + * @return {Number} Returns the number of bits needed to represent the absolute value of this Long. + * @api public + */ +Long.prototype.getNumBitsAbs = function() { + if (this.isNegative()) { + if (this.equals(Long.MIN_VALUE)) { + return 64; + } else { + return this.negate().getNumBitsAbs(); + } + } else { + var val = this.high_ != 0 ? this.high_ : this.low_; + for (var bit = 31; bit > 0; bit--) { + if ((val & (1 << bit)) != 0) { + break; + } + } + return this.high_ != 0 ? bit + 33 : bit + 1; + } +}; + +/** + * Return whether this value is zero. + * + * @return {Boolean} whether this value is zero. + * @api public + */ +Long.prototype.isZero = function() { + return this.high_ == 0 && this.low_ == 0; +}; + +/** + * Return whether this value is negative. + * + * @return {Boolean} whether this value is negative. + * @api public + */ +Long.prototype.isNegative = function() { + return this.high_ < 0; +}; + +/** + * Return whether this value is odd. + * + * @return {Boolean} whether this value is odd. + * @api public + */ +Long.prototype.isOdd = function() { + return (this.low_ & 1) == 1; +}; + +/** + * Return whether this Long equals the other + * + * @param {Long} other Long to compare against. + * @return {Boolean} whether this Long equals the other + * @api public + */ +Long.prototype.equals = function(other) { + return (this.high_ == other.high_) && (this.low_ == other.low_); +}; + +/** + * Return whether this Long does not equal the other. + * + * @param {Long} other Long to compare against. + * @return {Boolean} whether this Long does not equal the other. + * @api public + */ +Long.prototype.notEquals = function(other) { + return (this.high_ != other.high_) || (this.low_ != other.low_); +}; + +/** + * Return whether this Long is less than the other. + * + * @param {Long} other Long to compare against. + * @return {Boolean} whether this Long is less than the other. + * @api public + */ +Long.prototype.lessThan = function(other) { + return this.compare(other) < 0; +}; + +/** + * Return whether this Long is less than or equal to the other. + * + * @param {Long} other Long to compare against. + * @return {Boolean} whether this Long is less than or equal to the other. + * @api public + */ +Long.prototype.lessThanOrEqual = function(other) { + return this.compare(other) <= 0; +}; + +/** + * Return whether this Long is greater than the other. + * + * @param {Long} other Long to compare against. + * @return {Boolean} whether this Long is greater than the other. + * @api public + */ +Long.prototype.greaterThan = function(other) { + return this.compare(other) > 0; +}; + +/** + * Return whether this Long is greater than or equal to the other. + * + * @param {Long} other Long to compare against. + * @return {Boolean} whether this Long is greater than or equal to the other. + * @api public + */ +Long.prototype.greaterThanOrEqual = function(other) { + return this.compare(other) >= 0; +}; + +/** + * Compares this Long with the given one. + * + * @param {Long} other Long to compare against. + * @return {Boolean} 0 if they are the same, 1 if the this is greater, and -1 if the given one is greater. + * @api public + */ +Long.prototype.compare = function(other) { + if (this.equals(other)) { + return 0; + } + + var thisNeg = this.isNegative(); + var otherNeg = other.isNegative(); + if (thisNeg && !otherNeg) { + return -1; + } + if (!thisNeg && otherNeg) { + return 1; + } + + // at this point, the signs are the same, so subtraction will not overflow + if (this.subtract(other).isNegative()) { + return -1; + } else { + return 1; + } +}; + +/** + * The negation of this value. + * + * @return {Long} the negation of this value. + * @api public + */ +Long.prototype.negate = function() { + if (this.equals(Long.MIN_VALUE)) { + return Long.MIN_VALUE; + } else { + return this.not().add(Long.ONE); + } +}; + +/** + * Returns the sum of this and the given Long. + * + * @param {Long} other Long to add to this one. + * @return {Long} the sum of this and the given Long. + * @api public + */ +Long.prototype.add = function(other) { + // Divide each number into 4 chunks of 16 bits, and then sum the chunks. + + var a48 = this.high_ >>> 16; + var a32 = this.high_ & 0xFFFF; + var a16 = this.low_ >>> 16; + var a00 = this.low_ & 0xFFFF; + + var b48 = other.high_ >>> 16; + var b32 = other.high_ & 0xFFFF; + var b16 = other.low_ >>> 16; + var b00 = other.low_ & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 + b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 + b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 + b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 + b48; + c48 &= 0xFFFF; + return Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32); +}; + +/** + * Returns the difference of this and the given Long. + * + * @param {Long} other Long to subtract from this. + * @return {Long} the difference of this and the given Long. + * @api public + */ +Long.prototype.subtract = function(other) { + return this.add(other.negate()); +}; + +/** + * Returns the product of this and the given Long. + * + * @param {Long} other Long to multiply with this. + * @return {Long} the product of this and the other. + * @api public + */ +Long.prototype.multiply = function(other) { + if (this.isZero()) { + return Long.ZERO; + } else if (other.isZero()) { + return Long.ZERO; + } + + if (this.equals(Long.MIN_VALUE)) { + return other.isOdd() ? Long.MIN_VALUE : Long.ZERO; + } else if (other.equals(Long.MIN_VALUE)) { + return this.isOdd() ? Long.MIN_VALUE : Long.ZERO; + } + + if (this.isNegative()) { + if (other.isNegative()) { + return this.negate().multiply(other.negate()); + } else { + return this.negate().multiply(other).negate(); + } + } else if (other.isNegative()) { + return this.multiply(other.negate()).negate(); + } + + // If both Longs are small, use float multiplication + if (this.lessThan(Long.TWO_PWR_24_) && + other.lessThan(Long.TWO_PWR_24_)) { + return Long.fromNumber(this.toNumber() * other.toNumber()); + } + + // Divide each Long into 4 chunks of 16 bits, and then add up 4x4 products. + // We can skip products that would overflow. + + var a48 = this.high_ >>> 16; + var a32 = this.high_ & 0xFFFF; + var a16 = this.low_ >>> 16; + var a00 = this.low_ & 0xFFFF; + + var b48 = other.high_ >>> 16; + var b32 = other.high_ & 0xFFFF; + var b16 = other.low_ >>> 16; + var b00 = other.low_ & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 * b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 * b00; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c16 += a00 * b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 * b00; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a16 * b16; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a00 * b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; + c48 &= 0xFFFF; + return Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32); +}; + +/** + * Returns this Long divided by the given one. + * + * @param {Long} other Long by which to divide. + * @return {Long} this Long divided by the given one. + * @api public + */ +Long.prototype.div = function(other) { + if (other.isZero()) { + throw Error('division by zero'); + } else if (this.isZero()) { + return Long.ZERO; + } + + if (this.equals(Long.MIN_VALUE)) { + if (other.equals(Long.ONE) || + other.equals(Long.NEG_ONE)) { + return Long.MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE + } else if (other.equals(Long.MIN_VALUE)) { + return Long.ONE; + } else { + // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|. + var halfThis = this.shiftRight(1); + var approx = halfThis.div(other).shiftLeft(1); + if (approx.equals(Long.ZERO)) { + return other.isNegative() ? Long.ONE : Long.NEG_ONE; + } else { + var rem = this.subtract(other.multiply(approx)); + var result = approx.add(rem.div(other)); + return result; + } + } + } else if (other.equals(Long.MIN_VALUE)) { + return Long.ZERO; + } + + if (this.isNegative()) { + if (other.isNegative()) { + return this.negate().div(other.negate()); + } else { + return this.negate().div(other).negate(); + } + } else if (other.isNegative()) { + return this.div(other.negate()).negate(); + } + + // Repeat the following until the remainder is less than other: find a + // floating-point that approximates remainder / other *from below*, add this + // into the result, and subtract it from the remainder. It is critical that + // the approximate value is less than or equal to the real value so that the + // remainder never becomes negative. + var res = Long.ZERO; + var rem = this; + while (rem.greaterThanOrEqual(other)) { + // Approximate the result of division. This may be a little greater or + // smaller than the actual value. + var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber())); + + // We will tweak the approximate result by changing it in the 48-th digit or + // the smallest non-fractional digit, whichever is larger. + var log2 = Math.ceil(Math.log(approx) / Math.LN2); + var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48); + + // Decrease the approximation until it is smaller than the remainder. Note + // that if it is too large, the product overflows and is negative. + var approxRes = Long.fromNumber(approx); + var approxRem = approxRes.multiply(other); + while (approxRem.isNegative() || approxRem.greaterThan(rem)) { + approx -= delta; + approxRes = Long.fromNumber(approx); + approxRem = approxRes.multiply(other); + } + + // We know the answer can't be zero... and actually, zero would cause + // infinite recursion since we would make no progress. + if (approxRes.isZero()) { + approxRes = Long.ONE; + } + + res = res.add(approxRes); + rem = rem.subtract(approxRem); + } + return res; +}; + +/** + * Returns this Long modulo the given one. + * + * @param {Long} other Long by which to mod. + * @return {Long} this Long modulo the given one. + * @api public + */ +Long.prototype.modulo = function(other) { + return this.subtract(this.div(other).multiply(other)); +}; + +/** + * The bitwise-NOT of this value. + * + * @return {Long} the bitwise-NOT of this value. + * @api public + */ +Long.prototype.not = function() { + return Long.fromBits(~this.low_, ~this.high_); +}; + +/** + * Returns the bitwise-AND of this Long and the given one. + * + * @param {Long} other the Long with which to AND. + * @return {Long} the bitwise-AND of this and the other. + * @api public + */ +Long.prototype.and = function(other) { + return Long.fromBits(this.low_ & other.low_, this.high_ & other.high_); +}; + +/** + * Returns the bitwise-OR of this Long and the given one. + * + * @param {Long} other the Long with which to OR. + * @return {Long} the bitwise-OR of this and the other. + * @api public + */ +Long.prototype.or = function(other) { + return Long.fromBits(this.low_ | other.low_, this.high_ | other.high_); +}; + +/** + * Returns the bitwise-XOR of this Long and the given one. + * + * @param {Long} other the Long with which to XOR. + * @return {Long} the bitwise-XOR of this and the other. + * @api public + */ +Long.prototype.xor = function(other) { + return Long.fromBits(this.low_ ^ other.low_, this.high_ ^ other.high_); +}; + +/** + * Returns this Long with bits shifted to the left by the given amount. + * + * @param {Number} numBits the number of bits by which to shift. + * @return {Long} this shifted to the left by the given amount. + * @api public + */ +Long.prototype.shiftLeft = function(numBits) { + numBits &= 63; + if (numBits == 0) { + return this; + } else { + var low = this.low_; + if (numBits < 32) { + var high = this.high_; + return Long.fromBits( + low << numBits, + (high << numBits) | (low >>> (32 - numBits))); + } else { + return Long.fromBits(0, low << (numBits - 32)); + } + } +}; + +/** + * Returns this Long with bits shifted to the right by the given amount. + * + * @param {Number} numBits the number of bits by which to shift. + * @return {Long} this shifted to the right by the given amount. + * @api public + */ +Long.prototype.shiftRight = function(numBits) { + numBits &= 63; + if (numBits == 0) { + return this; + } else { + var high = this.high_; + if (numBits < 32) { + var low = this.low_; + return Long.fromBits( + (low >>> numBits) | (high << (32 - numBits)), + high >> numBits); + } else { + return Long.fromBits( + high >> (numBits - 32), + high >= 0 ? 0 : -1); + } + } +}; + +/** + * Returns this Long with bits shifted to the right by the given amount, with the new top bits matching the current sign bit. + * + * @param {Number} numBits the number of bits by which to shift. + * @return {Long} this shifted to the right by the given amount, with zeros placed into the new leading bits. + * @api public + */ +Long.prototype.shiftRightUnsigned = function(numBits) { + numBits &= 63; + if (numBits == 0) { + return this; + } else { + var high = this.high_; + if (numBits < 32) { + var low = this.low_; + return Long.fromBits( + (low >>> numBits) | (high << (32 - numBits)), + high >>> numBits); + } else if (numBits == 32) { + return Long.fromBits(high, 0); + } else { + return Long.fromBits(high >>> (numBits - 32), 0); + } + } +}; + +/** + * Returns a Long representing the given (32-bit) integer value. + * + * @param {Number} value the 32-bit integer in question. + * @return {Long} the corresponding Long value. + * @api public + */ +Long.fromInt = function(value) { + if (-128 <= value && value < 128) { + var cachedObj = Long.INT_CACHE_[value]; + if (cachedObj) { + return cachedObj; + } + } + + var obj = new Long(value | 0, value < 0 ? -1 : 0); + if (-128 <= value && value < 128) { + Long.INT_CACHE_[value] = obj; + } + return obj; +}; + +/** + * Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned. + * + * @param {Number} value the number in question. + * @return {Long} the corresponding Long value. + * @api public + */ +Long.fromNumber = function(value) { + if (isNaN(value) || !isFinite(value)) { + return Long.ZERO; + } else if (value <= -Long.TWO_PWR_63_DBL_) { + return Long.MIN_VALUE; + } else if (value + 1 >= Long.TWO_PWR_63_DBL_) { + return Long.MAX_VALUE; + } else if (value < 0) { + return Long.fromNumber(-value).negate(); + } else { + return new Long( + (value % Long.TWO_PWR_32_DBL_) | 0, + (value / Long.TWO_PWR_32_DBL_) | 0); + } +}; + +/** + * Returns a Long representing the 64-bit integer that comes by concatenating the given high and low bits. Each is assumed to use 32 bits. + * + * @param {Number} lowBits the low 32-bits. + * @param {Number} highBits the high 32-bits. + * @return {Long} the corresponding Long value. + * @api public + */ +Long.fromBits = function(lowBits, highBits) { + return new Long(lowBits, highBits); +}; + +/** + * Returns a Long representation of the given string, written using the given radix. + * + * @param {String} str the textual representation of the Long. + * @param {Number} opt_radix the radix in which the text is written. + * @return {Long} the corresponding Long value. + * @api public + */ +Long.fromString = function(str, opt_radix) { + if (str.length == 0) { + throw Error('number format error: empty string'); + } + + var radix = opt_radix || 10; + if (radix < 2 || 36 < radix) { + throw Error('radix out of range: ' + radix); + } + + if (str.charAt(0) == '-') { + return Long.fromString(str.substring(1), radix).negate(); + } else if (str.indexOf('-') >= 0) { + throw Error('number format error: interior "-" character: ' + str); + } + + // Do several (8) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = Long.fromNumber(Math.pow(radix, 8)); + + var result = Long.ZERO; + for (var i = 0; i < str.length; i += 8) { + var size = Math.min(8, str.length - i); + var value = parseInt(str.substring(i, i + size), radix); + if (size < 8) { + var power = Long.fromNumber(Math.pow(radix, size)); + result = result.multiply(power).add(Long.fromNumber(value)); + } else { + result = result.multiply(radixToPower); + result = result.add(Long.fromNumber(value)); + } + } + return result; +}; + +// NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the +// from* methods on which they depend. + + +/** + * A cache of the Long representations of small integer values. + * @type {Object} + * @api private + */ +Long.INT_CACHE_ = {}; + +// NOTE: the compiler should inline these constant values below and then remove +// these variables, so there should be no runtime penalty for these. + +/** + * Number used repeated below in calculations. This must appear before the + * first call to any from* function below. + * @type {number} + * @api private + */ +Long.TWO_PWR_16_DBL_ = 1 << 16; + +/** + * @type {number} + * @api private + */ +Long.TWO_PWR_24_DBL_ = 1 << 24; + +/** + * @type {number} + * @api private + */ +Long.TWO_PWR_32_DBL_ = Long.TWO_PWR_16_DBL_ * Long.TWO_PWR_16_DBL_; + +/** + * @type {number} + * @api private + */ +Long.TWO_PWR_31_DBL_ = Long.TWO_PWR_32_DBL_ / 2; + +/** + * @type {number} + * @api private + */ +Long.TWO_PWR_48_DBL_ = Long.TWO_PWR_32_DBL_ * Long.TWO_PWR_16_DBL_; + +/** + * @type {number} + * @api private + */ +Long.TWO_PWR_64_DBL_ = Long.TWO_PWR_32_DBL_ * Long.TWO_PWR_32_DBL_; + +/** + * @type {number} + * @api private + */ +Long.TWO_PWR_63_DBL_ = Long.TWO_PWR_64_DBL_ / 2; + +/** @type {Long} */ +Long.ZERO = Long.fromInt(0); + +/** @type {Long} */ +Long.ONE = Long.fromInt(1); + +/** @type {Long} */ +Long.NEG_ONE = Long.fromInt(-1); + +/** @type {Long} */ +Long.MAX_VALUE = + Long.fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0); + +/** @type {Long} */ +Long.MIN_VALUE = Long.fromBits(0, 0x80000000 | 0); + +/** + * @type {Long} + * @api private + */ +Long.TWO_PWR_24_ = Long.fromInt(1 << 24); + +/** + * Expose. + */ +if(typeof window === 'undefined') { + exports.Long = Long; +} \ No newline at end of file diff --git a/node_modules/node-firebird/lib/messages.js b/node_modules/node-firebird/lib/messages.js new file mode 100644 index 0000000..3348a2a --- /dev/null +++ b/node_modules/node-firebird/lib/messages.js @@ -0,0 +1,162 @@ +var fs = require('fs'); + +const + //ISC_MASK = 0x14000000, // Defines the code as a valid ISC code + FAC_MASK = 0x00FF0000, // Specifies the facility where the code is located + CODE_MASK = 0x0000FFFF, // Specifies the code in the message file + CLASS_MASK = 0xF0000000; // Defines the code as warning, error, info, or other + +var msgNumber = exports.msgNumber = function(facility, code) { + return (facility * 10000 + code); +}; + +var getCode = exports.getCode = function(code) { + return (code & CODE_MASK) +}; + +var getFacility = exports.getFacility = function(code) { + return (code & FAC_MASK) >> 16; +}; + +exports.getClass = function(code) { + return (code & CLASS_MASK) >> 30 +}; + +exports.lookupMessages = function(status, callback){ + + var handle; + var bucket_size; + var top_tree; + var levels; + var buffer; + + function lookup(item, callback) { + + var code = msgNumber(getFacility(item.gdscode), getCode(item.gdscode)); + + function readIndex(stackSize, position) { + + function readNode(from) { + var ret = {}; + ret.code = buffer.readUInt32LE(from); + ret.seek = buffer.readUInt32LE(from + 4); + return ret; + } + + fs.read(handle, buffer, 0, bucket_size, position, function(err, bufferSize) { + + if (bufferSize <= 0) { + callback(); + return; + } + + if (stackSize === levels) { + search(); + return; + } + + var from = 0; + var node = readNode(from); + + while (true) { + + if (node.code >= code) + { + readIndex(stackSize + 1, node.seek); + break; + } + + from += 8; + if (from >= bufferSize) + { + callback(); + break; + } + + node = readNode(from); + } + }); + } + + function search() { + + function readRec(from) { + + function align(v) { + return (v + 3) & ~3; + } + + var ret = {}; + ret.code = buffer.readUInt32LE(from); + ret.length = buffer.readUInt16LE(from + 4); + + if (ret.code == code){ + from += 8; + ret.text = buffer.toString(null, from, from + ret.length); + } else + ret.seek = from + align(8 + ret.length, 4); + + return ret; + } + + var rec = readRec(0); + + while (rec.seek) { + if (rec.seek >= buffer.length) + break; + else + rec = readRec(rec.seek); + } + + var str = rec.text; + if (item.params) { + for (var i = 0; i < item.params.length; i++) + str = str.replace('@' + String(i+1), item.params[i]); + } + + callback(str); + } + + readIndex(1, top_tree); + } + + fs.open(__dirname + "/firebird.msg", 'r', function(err, h) { + + if (!h) { + callback(); + return; + } + + buffer = new Buffer(14); + fs.read(h, buffer, 0, 14, 0, function(){ + + handle = h; + bucket_size = buffer.readUInt16LE(2); + top_tree = buffer.readUInt32LE(4); + levels = buffer.readUInt16LE(12); + buffer = new Buffer(bucket_size); + + var i = 0; + var text; + + function loop() { + lookup(status[i], function(line) { + if (text) + text = text + ', ' + line + else + text = line; + + if (i === status.length - 1) { + fs.close(handle); + callback(text); + } else { + i++; + loop(); + } + }); + } + + loop(0); + }); + }); +}; \ No newline at end of file diff --git a/node_modules/node-firebird/lib/serialize.js b/node_modules/node-firebird/lib/serialize.js new file mode 100644 index 0000000..f4013fe --- /dev/null +++ b/node_modules/node-firebird/lib/serialize.js @@ -0,0 +1,361 @@ + +var Long = require('./long.js').Long; + +function align(n) { + return (n + 3) & ~3; +} + +/*************************************** + * + * BLR Writer + * + ***************************************/ + +const + MAX_STRING_SIZE = 255; + +var BlrWriter = exports.BlrWriter = function(size){ + this.buffer = new Buffer(size || 32); + this.pos = 0; +}; + +BlrWriter.prototype.addByte = function (b) { + this.ensure(1); + this.buffer.writeUInt8(b, this.pos); + this.pos++; +}; + +BlrWriter.prototype.addShort = function (b) { + this.ensure(1); + this.buffer.writeInt8(b, this.pos); + this.pos++; +}; + +BlrWriter.prototype.addSmall = function (b) { + this.ensure(2); + this.buffer.writeInt16LE(b, this.pos); + this.pos += 2; +}; + +BlrWriter.prototype.addWord = function (b) { + this.ensure(2); + this.buffer.writeUInt16LE(b, this.pos); + this.pos += 2; +}; + +BlrWriter.prototype.addNumeric = function (c, v) { + + if (v < 256){ + this.ensure(3); + this.buffer.writeUInt8(c, this.pos); + this.pos++; + this.buffer.writeUInt8(1, this.pos); + this.pos++; + this.buffer.writeUInt8(v, this.pos); + this.pos++; + return; + } + + this.ensure(6); + this.buffer.writeUInt8(c, this.pos); + this.pos++; + this.buffer.writeUInt8(4, this.pos); + this.pos++; + this.buffer.writeInt32BE(v, this.pos); + this.pos += 4; + +}; + +BlrWriter.prototype.addBytes = function (b) { + + this.ensure(b.length); + for (var i = 0, length = b.length; i < length; i++) { + this.buffer.writeUInt8(b[i], this.pos); + this.pos++; + } +}; + +BlrWriter.prototype.addString = function (c, s, encoding) { + this.addByte(c); + + var len = Buffer.byteLength(s, encoding); + if (len > MAX_STRING_SIZE) + throw new Error('blr string is too big'); + + this.ensure(len + 1); + this.buffer.writeUInt8(len, this.pos); + this.pos++; + this.buffer.write(s, this.pos, s.length, encoding); + this.pos += len; +}; + +BlrWriter.prototype.addBuffer = function (b) { + this.addSmall(b.length); + this.ensure(b.length); + b.copy(this.buffer, this.pos); + this.pos += b.length; +}; + +/*************************************** + * + * BLR Reader + * + ***************************************/ + +var BlrReader = exports.BlrReader = function(buffer) { + this.buffer = buffer; + this.pos = 0; +}; + +BlrReader.prototype.readByteCode = function(){ + return this.buffer.readUInt8(this.pos++); +}; + +BlrReader.prototype.readInt = function(){ + var len = this.buffer.readUInt16LE(this.pos); + this.pos += 2; + var value; + switch (len) { + case 1: + value = this.buffer.readInt8(this.pos); + break; + case 2: + value = this.buffer.readInt16LE(this.pos); + break; + case 4: + value = this.buffer.readInt32LE(this.pos) + } + this.pos += len; + return value; +}; + +BlrReader.prototype.readString = function(encoding){ + + var len = this.buffer.readUInt16LE(this.pos); + var str; + + this.pos += 2; + if (len <= 0) + return ''; + + str = this.buffer.toString(encoding, this.pos, this.pos + len); + this.pos += len; + return str; +}; + +BlrReader.prototype.readSegment = function() { + + var ret, tmp; + var len = this.buffer.readUInt16LE(this.pos); + + this.pos += 2; + + while (len > 0) { + + if (ret) { + tmp = ret; + ret = new Buffer(tmp.length + len); + tmp.copy(ret); + this.buffer.copy(ret, tmp.length, this.pos, this.pos + len); + } else { + ret = new Buffer(len); + this.buffer.copy(ret, 0, this.pos, this.pos + len); + } + + this.pos += len; + + if (this.pos === this.buffer.length) + break; + + len = this.buffer.readUInt16LE(this.pos); + this.pos += 2; + } + + return ret; +}; + +/*************************************** + * + * XDR Writer + * + ***************************************/ + +var XdrWriter = exports.XdrWriter = function(size){ + this.buffer = new Buffer(size || 32); + this.pos = 0; +}; + +XdrWriter.prototype.ensure = BlrWriter.prototype.ensure = function (len) { + var newlen = this.buffer.length; + + while (newlen < this.pos + len) + newlen *= 2 + + if (this.buffer.length >= newlen) + return; + + var b = new Buffer(newlen); + this.buffer.copy(b); + delete(this.buffer); + this.buffer = b; +}; + +XdrWriter.prototype.addInt = function (value) { + this.ensure(4); + this.buffer.writeInt32BE(value, this.pos); + this.pos += 4; +}; + +XdrWriter.prototype.addInt64 = function (value) { + this.ensure(8); + var l = new Long(value); + this.buffer.writeInt32BE(l.high_, this.pos); + this.pos += 4; + this.buffer.writeInt32BE(l.low_, this.pos); + this.pos += 4; +}; + +XdrWriter.prototype.addUInt = function (value) { + this.ensure(4); + this.buffer.writeUInt32BE(value, this.pos); + this.pos += 4; +}; + +XdrWriter.prototype.addString = function(s, encoding) { + var len = Buffer.byteLength(s, encoding); + var alen = align(len); + this.ensure(alen + 4); + this.buffer.writeInt32BE(len, this.pos); + this.pos += 4; + this.buffer.write(s, this.pos, len, encoding); + this.pos += alen; +}; + +XdrWriter.prototype.addText = function(s, encoding) { + var len = Buffer.byteLength(s, encoding); + var alen = align(len); + this.ensure(alen); + this.buffer.write(s, this.pos, len, encoding); + this.pos += alen; +}; + +XdrWriter.prototype.addBlr = function(blr) { + var alen = align(blr.pos); + this.ensure(alen + 4); + this.buffer.writeInt32BE(blr.pos, this.pos); + this.pos += 4; + blr.buffer.copy(this.buffer, this.pos); + this.pos += alen; +}; + +XdrWriter.prototype.getData = function() { + return this.buffer.slice(0, this.pos); +}; + +XdrWriter.prototype.addDouble = function(value) { + this.ensure(8); + this.buffer.writeDoubleBE(value, this.pos); + this.pos += 8; +}; + +XdrWriter.prototype.addQuad = function(quad) { + this.ensure(8); + var b = this.buffer; + b.writeInt32BE(quad.high, this.pos); + this.pos += 4; + b.writeInt32BE(quad.low, this.pos); + this.pos += 4; +}; + +/*************************************** + * + * XDR Reader + * + ***************************************/ + +var XdrReader = exports.XdrReader = function(buffer){ + this.buffer = buffer; + this.pos = 0; +}; + +XdrReader.prototype.readInt = function () { + var r = this.buffer.readInt32BE(this.pos); + this.pos += 4; + return r; +}; + +XdrReader.prototype.readUInt = function () { + var r = this.buffer.readUInt32BE(this.pos); + this.pos += 4; + return r; +}; + +XdrReader.prototype.readInt64 = function () { + var high = this.buffer.readInt32BE(this.pos); + this.pos += 4; + var low = this.buffer.readInt32BE(this.pos); + this.pos += 4; + return new Long(low, high); +}; + +XdrReader.prototype.readShort = function () { + var r = this.buffer.readInt16BE(this.pos); + this.pos += 2; + return r; +}; + +XdrReader.prototype.readQuad = function () { + var b = this.buffer; + var high = b.readInt32BE(this.pos); + this.pos += 4; + var low = b.readInt32BE(this.pos); + this.pos += 4; + return {low: low, high: high} +}; + +XdrReader.prototype.readFloat = function () { + var r = this.buffer.readFloatBE(this.pos); + this.pos += 4; + return r; +}; + +XdrReader.prototype.readDouble = function () { + var r = this.buffer.readDoubleBE(this.pos); + this.pos += 8; + return r; +}; + +XdrReader.prototype.readArray = function () { + var len = this.readInt(); + if (!len) + return; + var r = this.buffer.slice(this.pos, this.pos + len); + this.pos += align(len); + return r; +}; + +XdrReader.prototype.readBuffer = function (len) { + if (!arguments.length) + len = this.readInt(); + + if (!len) + return; + + var r = this.buffer.slice(this.pos, this.pos + len); + this.pos += align(len); + return r; +}; + +XdrReader.prototype.readString = function (encoding) { + var len = this.readInt(); + return this.readText(len, encoding); +}; + +XdrReader.prototype.readText = function (len, encoding) { + if (len <= 0) + return ''; + + var r = this.buffer.toString(encoding, this.pos, this.pos + len); + this.pos += align(len); + return r; +}; \ No newline at end of file diff --git a/node_modules/node-firebird/package.json b/node_modules/node-firebird/package.json new file mode 100644 index 0000000..624305e --- /dev/null +++ b/node_modules/node-firebird/package.json @@ -0,0 +1,70 @@ +{ + "name": "node-firebird", + "version": "0.2.3", + "description": "Pure JavaScript and Asynchronous Firebird client for Node.js.", + "keywords": [ + "firebird", + "database", + "rdbms", + "sql" + ], + "homepage": "https://github.com/hgourvest/node-firebird", + "repository": { + "type": "git", + "url": "https://github.com/hgourvest/node-firebird" + }, + "author": { + "name": "Henri Gourvest", + "email": "hgourvest@gmail.com" + }, + "contributors": [ + { + "name": "Popa Marius Adrian", + "email": "mapopa@gmail.com" + }, + { + "name": "Peter Širka", + "email": "petersirka@gmail.com" + } + ], + "main": "./lib", + "licenses": [ + { + "type": "MPL-2.0", + "url": "https://raw.githubusercontent.com/hgourvest/node-firebird/master/LICENSE" + } + ], + "gitHead": "39dadf20e203a8300eb766a24deecbfe3757954a", + "bugs": { + "url": "https://github.com/hgourvest/node-firebird/issues" + }, + "_id": "node-firebird@0.2.3", + "scripts": {}, + "_shasum": "5af05dc7575543188b5005a59bd3e10537cff160", + "_from": "node-firebird@", + "_npmVersion": "1.4.28", + "_npmUser": { + "name": "mariuz", + "email": "mapopa@gmail.com" + }, + "maintainers": [ + { + "name": "hgourvest", + "email": "hgourvest@gmail.com" + }, + { + "name": "mariuz", + "email": "mapopa@gmail.com" + }, + { + "name": "petersirka", + "email": "petersirka@gmail.com" + } + ], + "dist": { + "shasum": "5af05dc7575543188b5005a59bd3e10537cff160", + "tarball": "http://registry.npmjs.org/node-firebird/-/node-firebird-0.2.3.tgz" + }, + "directories": {}, + "_resolved": "https://registry.npmjs.org/node-firebird/-/node-firebird-0.2.3.tgz" +} diff --git a/node_modules/node-firebird/test/image.png b/node_modules/node-firebird/test/image.png new file mode 100644 index 0000000..1f6799a Binary files /dev/null and b/node_modules/node-firebird/test/image.png differ diff --git a/node_modules/node-firebird/test/run.js b/node_modules/node-firebird/test/run.js new file mode 100644 index 0000000..a85cfc7 --- /dev/null +++ b/node_modules/node-firebird/test/run.js @@ -0,0 +1,441 @@ +var fb = require('../lib'); +var fs = require('fs'); +var os = require('os'); + +var assert = require('assert'); +var Path = require('path'); +var now = new Date(); + +var config = { + + // Problem with privileges in OSX + // database: Path.join(os.tmpdir(), 'test-' + new Date().getTime() + '.fdb'), + + database: Path.join(process.cwd(), 'test-' + new Date().getTime() + '.fdb'), + host: '127.0.0.1', // default + port: 3050, // default + user: 'SYSDBA', // default + password: 'masterkey', // default + role: null, // default + pageSize: 4096, // default when creating database + timeout: 3000 // default query timeout +} + +Array.prototype.async = function(cb) { + + var self = this; + var item = self.shift(); + + if (item === undefined) { + if (cb) + cb(); + return; + } + + item(function() { + setImmediate(function() { + self.async(cb); + }); + }); +}; + +fb.attachOrCreate(config, function (err, db) { + + if (err) + throw err.message; + + database = db; + + var task = []; + + task.push(test_create); + task.push(test_reconnect); + task.push(test_insert); + task.push(test_select_insert); // for inserted rows + task.push(test_update); + task.push(test_select_update); // for updated rows + task.push(test_transaction); + + task.push(function(next) { + db.detach(next); + }); + + task.push(test_pooling); + task.async(); +}); + +function test_create(next) { + + var name = 'TEST ---> test_create'; + console.time(name); + + // Create table + database.query('CREATE TABLE test (ID INT, NAME VARCHAR(50), FILE BLOB, CREATED TIMESTAMP)', function(err) { + assert.ok(!err, name + ': create table ' + err); + + // Check if table exists + database.query('SELECT COUNT(*) FROM test', function(err, r) { + assert.ok(!err, name + ': check existing of table ' + err); + + assert.ok(r[0].count === 0, name + ': check rows in new table'); + console.timeEnd(name); + + // Next test + next(); + }); + }); +} + +function test_reconnect(next) { + + var name = 'TEST ---> test_reconnect'; + console.time(name); + database.connection._socket.end(); + database.on('reconnect', function() { + console.timeEnd(name); + next(); + }); +} + +function test_insert(next) { + + var name = 'TEST ---> test_insert'; + var query = []; + + console.time(name); + + // Insert record with blob (STREAM) + query.push(function(next) { + 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) { + assert.ok(!err, name + ': insert blob (stream) ' + err); + assert.ok(r['id'] === 1, name + ': blob (stream) returning value'); + next(); + }); + }); + + // Insert record with blob (BUFFER) + query.push(function(next) { + 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) { + assert.ok(!err, name + ': insert blob (buffer) ' + err); + assert.ok(r['id'] === 2, name + ': blob (buffer) returning value'); + next(); + }); + }); + + // Insert record without blob + query.push(function(next) { + database.query('INSERT INTO test (ID, NAME, CREATED) VALUES(?, ?, ?) RETURNING ID', [3, 'Firebird 3', now], function(err, r) { + assert.ok(!err, name + ': insert without blob (buffer) (1) ' + err); + assert.ok(r['id'] === 3, name + ': without blob (buffer) returning value'); + next(); + }); + }); + + // Insert record without blob (without returning value) + query.push(function(next) { + database.query('INSERT INTO test (ID, NAME, CREATED) VALUES(?, ?, ?)', [4, 'Firebird 4', '2014-12-12 13:59'], function(err, r) { + assert.ok(!err, name + ': insert without blob (buffer) (2) ' + err); + assert.ok(err === undefined, name + ': insert without blob + without returning value'); + next(); + }); + }); + + query.async(function() { + console.timeEnd(name); + next(); + }); + +} + +function test_update(next) { + + var name = 'TEST ---> test_update'; + console.time(name); + + var query = []; + + // Insert record with blob (STREAM) + query.push(function(next) { + database.query('UPDATE test SET NAME=?, FILE=? WHERE Id=1', ['Firebird 1 (UPD)', fs.createReadStream('image.png')], function(err, r) { + assert.ok(!err, name + ': update blob (stream) ' + err); + next(); + }); + }); + + // Insert record with blob (BUFFER) + query.push(function(next) { + database.query('UPDATE test SET NAME=?, FILE=? WHERE Id=2', ['Firebird 2 (UPD)', fs.readFileSync('image.png')], function(err, r) { + assert.ok(!err, name + ': update blob (buffer) ' + err); + next(); + }); + }); + + query.async(function() { + console.timeEnd(name); + next(); + }); + +} + +function test_select_insert(next) { + + var name = 'TEST ---> test_select_insert'; + console.time(name); + + var query = []; + + // Classic select + query.push(function(next) { + database.query('SELECT * FROM test', function(err, r) { + + var row = r[0]; + var row2 = r[2]; + var row4 = r[3]; + + assert.ok(!err, name + ': problem (1) ' + err); + assert.ok(row !== undefined, name + ': problem (2)'); + assert.ok(row.id === 1 && row.name === 'Firebird 1', name + ': problem with deserializer'); + assert.ok(typeof(row.file) === 'function', name + ': blob'); + 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)'); + assert.ok(row2.created.getTime() === now.getTime(), name + ': date problem (2)'); + 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)'); + + row.file(function(err, name, e) { + + assert.ok(!err, name + ': reading blob ' + err); + + var count = 0; + + e.on('data', function(buffer) { + count += buffer.length; + }); + + e.on('end', function() { + assert.ok(count === 5472, name + ': problem with retrieving blob data'); + next(); + }); + }); + + }); + }); + + // Select to array + query.push(function(next) { + // Deserialize to array + database.execute('SELECT COUNT(*), SUM(Id) FROM test', function(err, r) { + assert.ok(r[0][0] === 4 && r[0][1] === 10, name + ': array deserializer problem'); + next(); + }); + }); + + // Sequentially select (object) + query.push(function(next) { + var counter = 0; + database.sequentially('SELECT Id FROM test', function(row, index) { + counter += row.id; + }, function() { + assert.ok(counter === 10, name + ': sequentially (object)'); + next(); + }); + }); + + // Sequentially select (array) + query.push(function(next) { + var counter = 0; + database.sequentially('SELECT Id FROM test', function(row, index) { + counter += row[0]; + }, function() { + assert.ok(counter === 10, name + ': sequentially (array)'); + next(); + }, true); + }); + + query.async(function() { + console.timeEnd(name); + next(); + }); +} + +function test_select_update(next) { + + var name = 'TEST ---> test_select_update'; + console.time(name); + + var query = []; + + // Classic select 1 + query.push(function(next) { + database.query('SELECT * FROM test WHERE Id=1', function(err, r) { + + var row = r[0]; + + assert.ok(!err, name + ': problem (1) ' + err); + assert.ok(row !== undefined, name + ': problem (2)'); + assert.ok(row.id === 1 && row.name === 'Firebird 1 (UPD)', name + ': problem with deserializer'); + assert.ok(typeof(row.file) === 'function', name + ': blob'); + + row.file(function(err, name, e) { + + assert.ok(!err, name + ': reading blob'); + + var count = 0; + + e.on('data', function(buffer) { + count += buffer.length; + }); + + e.on('end', function() { + assert.ok(count === 5472, name + ': problem with retrieving blob data'); + next(); + }); + }); + }); + }); + + // Classic select 2 + query.push(function(next) { + database.query('SELECT * FROM test WHERE Id=2', function(err, r) { + + var row = r[0]; + + assert.ok(!err, name + ': problem (1) ' + err); + assert.ok(row !== undefined, name + ': problem (2)'); + assert.ok(row.id === 2 && row.name === 'Firebird 2 (UPD)', name + ': problem with deserializer'); + assert.ok(typeof(row.file) === 'function', name + ': blob'); + + row.file(function(err, name, e) { + + assert.ok(!err, name + ': reading blob'); + + var count = 0; + + e.on('data', function(buffer) { + count += buffer.length; + }); + + e.on('end', function() { + assert.ok(count === 5472, name + ': problem with retrieving blob data'); + next(); + }); + }); + }); + }); + + query.async(function() { + console.timeEnd(name); + next(); + }); +} + +function test_transaction(next) { + + var name = 'TEST ---> test_transaction'; + console.time(name); + + var query = []; + + // Invalid transaction + query.push(function(next) { + database.transaction(function(err, transaction) { + transaction.query('INSERT INTO test (ID, NAME) VALUES(?, ?)', [5, 'Transaction 1'], function(err) { + assert.ok(!err, name + ': problem (1) ' + err); + transaction.query('INSERT INTO test (ID, NAME) VALUES(?, ?)', [6, 'Transaction 2'], function(err) { + assert.ok(!err, name + ': problem (2)'); + transaction.query('INSERT INTO testa (ID, NAME) VALUES(?, ?)', [7, 'Transaction 3'], function(err) { + assert.ok(err, name + ': problem (3)'); + transaction.rollback(function(err) { + assert.ok(!err, name + ': rollback problem'); + next(); + }); + }); + }); + }); + }); + }); + + // Select to array + query.push(function(next) { + database.query('SELECT COUNT(*) FROM test', function(err, r) { + assert.ok(r[0].count === 4, name + ': transaction does not work (rollback)'); + next(); + }); + }); + + // Valid transaction + query.push(function(next) { + database.transaction(function(err, transaction) { + transaction.query('INSERT INTO test (ID, NAME) VALUES(?, ?)', [5, 'Transaction 1'], function(err) { + assert.ok(!err, name + ': problem (4) ' + err); + transaction.query('INSERT INTO test (ID, NAME) VALUES(?, ?)', [6, 'Transaction 2'], function(err) { + assert.ok(!err, name + ': problem (5)'); + transaction.query('INSERT INTO test (ID, NAME) VALUES(?, ?)', [7, 'Transaction 3'], function(err) { + assert.ok(!err, name + ': problem (6) ' + err); + transaction.commit(function(err) { + assert.ok(!err, name + ': commit problem ' + err); + next(); + }); + }); + }); + }); + }); + }); + + // Select to array + query.push(function(next) { + database.query('SELECT COUNT(*) FROM test', function(err, r) { + assert.ok(r[0].count === 7, name + ': transaction does not work (commit)'); + next(); + }); + }); + + query.async(function() { + console.timeEnd(name); + next(); + }); +} + +function test_pooling(next) { + + var name = 'TEST ---> test_pooling'; + console.time(name); + + var query = []; + var pool = fb.pool(2, config); + + query.push(function(next) { + pool.get(function(err, db) { + setTimeout(function() { + // detach a current connection (socket is opened) + db.detach(); + }, 1000); + next(); + }); + }); + + query.push(function(next) { + pool.get(function(err, db) { + setTimeout(function() { + // detach a current connection (socket is still opened) + db.detach(); + }, 1500); + next(); + }); + }); + + query.push(function(next) { + pool.get(function(err, db) { + next(); + }); + assert.ok(pool.pending.length > 0, name + ': pool pending'); + }); + + query.push(function(next) { + setTimeout(function() { + pool.destroy(); + console.timeEnd(name); + }, 500); + next(); + }); + + query.async(next); +}