src/databases/SqliteConnection.js
/**
* @module SqliteConnection.js
* @author Joe Groseclose <@benderTheCrime>
* @date 8/23/2015
*/
// System Modules
import fs from 'fs';
import sqlite3 from 'sqlite3';
import {cyan, magenta, gray} from 'chalk';
import $LogProvider from 'angie-log';
// Angie Modules
import BaseDBConnection, {
$$DatabaseConnectivityError
} from './BaseDBConnection';
import {
$$InvalidDatabaseConfigError
} from '../util/$ExceptionsProvider';
sqlite3.verbose();
const p = process;
$LogProvider.sqliteInfo = $LogProvider.info.bind(null, 'Sqlite');
class SqliteConnection extends BaseDBConnection {
constructor(database, destructive, dryRun) {
super(database, destructive, dryRun);
let db = this.database;
if (!db.name) {
throw new $$InvalidDatabaseConfigError();
}
this.name = this.database.name || this.database.alias;
}
types(field) {
let type = field.type;
if (!type) {
return;
}
switch (type) {
case 'CharField':
return `TEXT${field.nullable ? ' NOT NULL' : ''}`;
// TODO support different size integers: TINY, SMALL, MEDIUM
case 'IntegerField':
return `INTEGER${field.nullable ? ' NOT NULL' : ''}`;
case 'KeyField':
return `INTEGER${field.nullable ? ' NOT NULL' : ''}`;
case 'ForeignKeyField':
return `INTEGER REFERENCES ${field.rel}(id)`;
default:
return undefined;
}
}
connect() {
let db = this.database;
if (!this.connection) {
try {
this.connection = new sqlite3.Database(this.name);
$LogProvider.sqliteInfo('Connection successful');
} catch(err) {
throw new $$DatabaseConnectivityError(db);
}
}
return this.connection;
}
disconnect() {
this.connect().close();
return this;
}
query(query, model, key) {
let me = this,
name = this.name;
return new Promise(function(resolve) {
$LogProvider.sqliteInfo(`Query: ${cyan(name)}: ${magenta(query)}`);
me.connect().parallelize(function() {
me.connection[ key ](query, function(e, rows = []) {
if (e) {
$LogProvider.warn(e);
}
resolve([ rows, e ]);
});
});
}).then(function(args) {
return me.$$queryset(model, query, args[0], args[1]);
});
}
run(query, model) {
return this.query(query, model, 'run');
}
all() {
const query = super.all.apply(this, arguments);
return this.query(query, arguments[0].model, 'all');
}
create() {
const query = super.create.apply(this, arguments);
return this.query(query, arguments[0].model, 'all');
}
delete() {
const query = super.delete.apply(this, arguments);
return this.query(query, arguments[0].model, 'all');
}
update() {
const query = super.update.apply(this, arguments);
return this.query(query, arguments[0].model, 'all');
}
raw(query, model) {
return this.query(query, model, 'all');
}
sync() {
let me = this;
super.sync().then(function() {
let proms = [];
// TODO test this in another directory
if (!fs.existsSync(me.name)) {
// Connection does not exist and we must touch the db file
fs.closeSync(fs.openSync(me.name, 'w'));
}
let models = me.models();
for (let model in models) {
// Fetch models and get model name
let instance = models[ model ],
modelName = instance.name || instance.alias ||
me.$$name(instance);
// Run a table creation with an ID for each table
proms.push(me.run(
`CREATE TABLE ${modelName} ` +
'(id INTEGER PRIMARY KEY AUTOINCREMENT);',
modelName
));
}
return Promise.all(proms).then(function() {
return me.migrate();
});
});
}
migrate() {
let me = this;
return super.migrate().then(function() {
let models = me.models(),
proms = [],
logged = true;
for (let key in models) {
const model = models[ key ],
modelName = me.$$name(model.name || model.alias),
fields = model.$fields();
if (me.destructive && logged) {
// TODO recommmend that the user copy the table over
// without the column, delete the original, and
// rename the table
$LogProvider.error(
'You have specified a destructive Migration and ' +
`have fields in the ${cyan(modelName)} model ` +
'which do not exist in your app.Model code. ' +
'However, sqlite3 destructive migrations are not ' +
'supported. Please see the docs for more info.'
);
logged = false;
}
fields.forEach(function(v) {
if (model[ v ].type === 'ManyToManyField') {
return;
}
let query,
$default;
if (model[ v ].default) {
$default = model[ v ].default;
if (typeof $default === 'function') {
$default = $default();
}
}
query =
`ALTER TABLE ${modelName} ADD COLUMN ${v} ` +
`${me.types(model[ v ])}` +
`${model[ v ].unique ? ' UNIQUE' : ''}` +
`${$default ? ` DEFAULT '${$default}'` : ''};`;
if (!me.dryRun) {
proms.push(me.run(query));
} else {
$LogProvider.sqliteInfo(`Dry Run Query: ${gray(query)}`);
}
});
}
return Promise.all(proms);
}).then(function() {
me.disconnect();
$LogProvider.sqliteInfo(
`Successfully Synced & Migrated ${cyan(me.name)}`
);
p.exit(0);
});
}
}
export default SqliteConnection;