node-query/node_modules/dblite/README.md

414 lines
18 KiB
Markdown

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.