Skip to content

Commit

Permalink
Added SQLite3 support.
Browse files Browse the repository at this point in the history
  • Loading branch information
wbyoung committed Oct 31, 2014
1 parent d943bbf commit 826072e
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 1 deletion.
1 change: 1 addition & 0 deletions lib/db/adapters/mixins/returning.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ var ExtractPseudoReturn = Mixin.create({
capture = function(id) {
captured = { id: id, column: arg.field };
};
capture.enabled = true;
}
return !pseudo;
});
Expand Down
Empty file removed lib/db/adapters/sqlite.js
Empty file.
167 changes: 167 additions & 0 deletions lib/db/adapters/sqlite3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
'use strict';

var _ = require('lodash');
var BluebirdPromise = require('bluebird');
var Adapter = require('./base');
var sqlite3 = require('sqlite3');

BluebirdPromise.promisifyAll(sqlite3.Database.prototype);

var like = Adapter.Translator.like,
contains = Adapter.Translator.contains,
startsWith = Adapter.Translator.startsWith,
endsWith = Adapter.Translator.endsWith,
regex = Adapter.Translator.regex,
wrapValue = Adapter.Translator.wrapValue;

var returning = require('./mixins/returning'),
EmbedPseudoReturn = returning.EmbedPseudoReturn,
ExtractPseudoReturn = returning.ExtractPseudoReturn;

/**
* SQLite3 Adapter
*
* Documentation forthcoming.
*
* @since 1.0
* @public
* @constructor
* @extends Adapter
*/
var SQLite3Adapter = Adapter.extend(/** @lends SQLite3Adapter# */ {

init: function() {
this._super.apply(this, arguments);
this._connections = {};
this._connectionID = 0;
this._database = undefined;
this._databasePromise = undefined;
},

/**
* Get or create the a database for this adapter.
*
* @method
* @private
*/
_resolveDatabase: BluebirdPromise.method(function() {
if (this._databasePromise) { return this._databasePromise; }

var filename = this._connection.filename;
var mode = this._connection.mode;
var promise = new BluebirdPromise(function(resolve, reject) {
var db = new sqlite3.Database(filename, mode)
.on('open', function() { resolve(db); })
.on('error', reject);
})
.bind(this).tap(function(db) {
this._database = db;
});

return (this._databasePromise = promise);
}),

/**
* Disconnect this adapter's database & setup to allow another one to be
* created.
*
* @method
* @private
*/
_disconnectDatabase: BluebirdPromise.method(function() {
var result = this._database.closeAsync();
this._database = undefined;
this._databasePromise = undefined;
return result;
}),

/**
* Connect for SQLite3Adapter.
*
* @method
* @protected
* @see {Adapter#_connect}
*/
_connect: BluebirdPromise.method(function() {
var id = (this._connectionID += 1);
var result = this._connections[id] = { id: id };
return this._resolveDatabase().bind(this).then(function(db) {
return _.merge(result, { db: db });
});
}),

/**
* Disconnect for SQLite3Adapter.
*
* @method
* @protected
* @see {Adapter#_disconnect}
*/
_disconnect: BluebirdPromise.method(function(connection) {
delete this._connections[connection.id];
return _.size(this._connections) === 0 &&
this._disconnectDatabase();
}),

/**
* Execute for SQLite3Adapter.
*
* @method
* @private
* @see {Adapter#_execute}
*/
_execute: BluebirdPromise.method(function(connection, sql, args, id) {
return BluebirdPromise.bind({})
.then(function() {
return new BluebirdPromise(function(resolve, reject) {
var method = id.enabled ? 'run' : 'all';
connection.db[method](sql, args, function(err, result) {
if (err) { reject(err); }
else { resolve([result, this]); }
});
});
})
.spread(function(result, details) {
if (details.lastID) { id(details.lastID); }
return {
rows: result || [],
fields: _.keys(_.reduce(result, _.extend, {})).sort()
};
});
})

});

SQLite3Adapter.reopenClass(/** @lends SQLite3Adapter */ {

Phrasing: Adapter.Phrasing.extend(),
Translator: Adapter.Translator.extend({
predicates: function(p) {
this._super.apply(this, arguments);

var likeFormat = '%s LIKE %s ESCAPE \'\\\'';
p('iexact', likeFormat);
p('contains', likeFormat, wrapValue(like, contains));
p('icontains', likeFormat, wrapValue(like, contains));
p('startsWith', likeFormat, wrapValue(like, startsWith));
p('istartsWith', likeFormat, wrapValue(like, startsWith));
p('endsWith', likeFormat, wrapValue(like, endsWith));
p('iendsWith', likeFormat, wrapValue(like, endsWith));
p('regex', '%s REGEXP %s', wrapValue(regex));
p('iregex', '%s REGEXP \'(?i)\' || %s', wrapValue(regex));
},

type: function(type/*, options*/) {
// TODO: handle more types & options
var result;
if (type === 'serial') { result = 'integer primary key autoincrement'; }
else { result = this._super.apply(this, arguments); }
return result;
}
}, { __name__: 'SQLite3Translator' })
});

SQLite3Adapter.Phrasing.reopen(EmbedPseudoReturn);
SQLite3Adapter.reopen(ExtractPseudoReturn);

module.exports = SQLite3Adapter.reopenClass({ __name__: 'SQLite3Adapter' });
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
},
"optionalDependencies": {
"mysql": "^2.5.2",
"pg": "^3.4.1"
"pg": "^3.4.1",
"sqlite3": "^3.0.2"
},
"dependencies": {
"bluebird": "^2.3.5",
Expand Down
72 changes: 72 additions & 0 deletions test/db/integration/sqlite3_tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use strict';

if (!/^(1|true)$/i.test(process.env.TEST_SQLITE || '1')) { return; }

var _ = require('lodash');
var expect = require('chai').expect;
var Database = require('../../../lib/db/database');
var BluebirdPromise = require('bluebird');

var shared = require('./shared_behaviors');
var returning = require('../../../lib/db/adapters/mixins/returning');
var PseudoReturn = returning.PseudoReturn;

var db, connection = {
adapter: 'sqlite3',
filename: ''
};

var resetSequence = BluebirdPromise.method(function(/*table*/) {
// no need to reset
});

describe('SQLite3', function() {
before(function() { db = this.db = Database.create(connection); });
before(function() { this.resetSequence = resetSequence; });
after(function(done) { db.disconnect().then(done, done); });

it('executes raw sql', function(done) {
var returnId = PseudoReturn.create('id');
var queries = [
['CREATE TABLE azul_raw_sql_test ' +
'(id integer primary key autoincrement, name varchar(255))'],
['INSERT INTO azul_raw_sql_test (name) VALUES (\'Azul\')', [returnId]],
['SELECT * FROM azul_raw_sql_test'],
['DROP TABLE azul_raw_sql_test']
];
BluebirdPromise.reduce(queries, function(array, info) {
var query = info[0], args = info[1] || [];
return db._adapter.execute(query, args).then(function(result) {
return array.concat([result]);
});
}, [])
.spread(function(result1, result2, result3, result4) {
expect(result1).to.eql({ rows: [], fields: [] });
expect(result2).to.eql({
rows: [{ id: 1 }], fields: ['id'] });
expect(result3).to.eql({
rows: [{ id: 1, name: 'Azul' }],
fields: ['id', 'name'] });
expect(result4).to.eql({ rows: [], fields: [] });
})
.done(done, done);
});

it('receives rows from raw sql', function(done) {
var query = 'SELECT CAST(? AS INTEGER) AS number';
var args = ['1'];
db._adapter.execute(query, args)
.then(function(result) {
expect(result.rows).to.eql([{ number: 1 }]);
})
.done(done, done);
});

// run all shared examples
_.each(shared, function(fn, name) {
if (fn.length !== 0) {
throw new Error('Cannot execute shared example: ' + name);
}
fn();
});
});

0 comments on commit 826072e

Please sign in to comment.