2015-08-28 03:33:46 +00:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
function wrap(db, dir) {
|
|
|
|
// TODO if I put a failure right here,
|
|
|
|
// why doesn't the unhandled promise rejection fire?
|
|
|
|
var PromiseA = require('bluebird');
|
|
|
|
var promises = [];
|
|
|
|
var dbsMap = {};
|
|
|
|
var arr = true;
|
|
|
|
|
|
|
|
function lowerFirst(str) {
|
|
|
|
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
function snakeCase(str) {
|
|
|
|
return lowerFirst(str).replace(
|
|
|
|
/([A-Z])/g
|
|
|
|
, function ($1) {
|
|
|
|
return "_" + $1.toLowerCase();
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function camelCase(str) {
|
|
|
|
return str.replace(
|
|
|
|
/_([a-z])/g
|
|
|
|
, function (g) {
|
|
|
|
return g[1].toUpperCase();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function upperCamelCase(str) {
|
|
|
|
// TODO handle UTF-8 properly (use codePointAt, don't use slice)
|
|
|
|
return camelCase(str).charAt(0).toUpperCase() + str.slice(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
function createTable(opts) {
|
|
|
|
var DB = {};
|
|
|
|
var tablename = db.escape(snakeCase(opts.tablename) || 'data');
|
|
|
|
var idname = db.escape(snakeCase(opts.idname) || 'id');
|
2015-09-22 02:14:07 +00:00
|
|
|
var idnameCased = (camelCase(opts.idname) || 'id');
|
2015-08-28 03:33:46 +00:00
|
|
|
|
|
|
|
db = PromiseA.promisifyAll(db);
|
|
|
|
|
|
|
|
if (opts && opts.verbose || db.verbose) {
|
|
|
|
console.log('Getting Verbose up in here');
|
|
|
|
db.on('trace', function (str) {
|
|
|
|
console.log('SQL:', str);
|
|
|
|
});
|
|
|
|
|
|
|
|
db.on('profile', function (sql, ms) {
|
|
|
|
console.log('Profile:', ms);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function simpleParse(row) {
|
|
|
|
var obj;
|
|
|
|
|
|
|
|
if (!row) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (row.json) {
|
|
|
|
obj = JSON.parse(row.json);
|
|
|
|
} else {
|
|
|
|
obj = {};
|
|
|
|
}
|
|
|
|
|
2015-09-22 02:14:07 +00:00
|
|
|
obj[idnameCased] = row[idname];
|
2015-08-28 03:33:46 +00:00
|
|
|
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
function simpleMap(rows) {
|
|
|
|
if (!rows) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
var results = rows.map(function (row, i) {
|
|
|
|
// set up for garbage collection
|
|
|
|
rows[i] = null;
|
|
|
|
|
|
|
|
var obj;
|
|
|
|
|
|
|
|
if (row.json) {
|
|
|
|
obj = JSON.parse(row.json);
|
|
|
|
} else {
|
|
|
|
obj = {};
|
|
|
|
}
|
|
|
|
|
2015-09-22 02:14:07 +00:00
|
|
|
obj[idnameCased] = row[idname];
|
2015-08-28 03:33:46 +00:00
|
|
|
});
|
|
|
|
// set up for garbage collection
|
|
|
|
rows.length = 0;
|
|
|
|
rows = null;
|
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
2015-09-22 02:14:07 +00:00
|
|
|
DB.find = function (opts, params) {
|
2015-08-28 03:33:46 +00:00
|
|
|
var sql = 'SELECT * FROM ' + tablename + ' WHERE ';
|
|
|
|
|
|
|
|
Object.keys(opts).forEach(function (key, i) {
|
|
|
|
if (i !== 0) {
|
|
|
|
sql += 'AND ';
|
|
|
|
}
|
2015-09-22 02:51:08 +00:00
|
|
|
sql += db.escape(snakeCase(key)) + " = '" + db.escape(opts[key]) + "'";
|
2015-08-28 03:33:46 +00:00
|
|
|
});
|
|
|
|
|
2015-09-22 02:14:07 +00:00
|
|
|
if (params) {
|
|
|
|
if (params.orderBy) {
|
|
|
|
sql += ' ORDER BY ' + db.escape(snakeCase(params.orderBy));
|
|
|
|
if (params.orderByDesc) {
|
|
|
|
sql += ' DESC';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-22 02:51:08 +00:00
|
|
|
return db.allAsync(sql, []).then(simpleMap);
|
2015-08-28 03:33:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
DB.get = function (id) {
|
|
|
|
var sql = "SELECT * FROM " + tablename + " WHERE " + idname + " = ?";
|
|
|
|
var values = [id];
|
|
|
|
|
|
|
|
return db.getAsync(sql, values).then(function (rows) {
|
|
|
|
if (Array.isArray(rows)) {
|
|
|
|
if (!rows.length) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rows[0] || null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rows;
|
|
|
|
}).then(simpleParse);
|
|
|
|
};
|
|
|
|
|
|
|
|
DB.upsert = function (id, data) {
|
|
|
|
return DB.set(id, data).then(function (result) {
|
|
|
|
var success = result.changes >= 1;
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
return DB.create(id, data);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
DB.save = function (data) {
|
2015-09-22 02:14:07 +00:00
|
|
|
if (!data[idnameCased]) {
|
2015-08-28 03:33:46 +00:00
|
|
|
// NOTE saving the id both in the object and the id for now
|
|
|
|
var UUID = require('node-uuid');
|
2015-09-22 02:14:07 +00:00
|
|
|
data[idnameCased] = UUID.v4();
|
|
|
|
return DB.create(data[idnameCased], data).then(function (/*stats*/) {
|
2015-08-28 03:33:46 +00:00
|
|
|
//data._rowid = stats.id;
|
|
|
|
return data;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-09-22 02:14:07 +00:00
|
|
|
return DB.set(data[idnameCased], data).then(function (result) {
|
2015-08-28 03:33:46 +00:00
|
|
|
var success = result.changes >= 1;
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
return result;
|
2015-09-22 02:14:07 +00:00
|
|
|
} else {
|
|
|
|
console.log('[debug result of set]', result.sql);
|
|
|
|
delete result.sql;
|
|
|
|
console.log(result);
|
2015-08-28 03:33:46 +00:00
|
|
|
}
|
2015-09-22 02:14:07 +00:00
|
|
|
|
|
|
|
return null;
|
2015-08-28 03:33:46 +00:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2015-09-22 02:14:07 +00:00
|
|
|
DB.create = function (id, obj) {
|
|
|
|
if (!obj) {
|
|
|
|
obj = id;
|
|
|
|
id = obj[idnameCased];
|
|
|
|
}
|
|
|
|
if (!id) {
|
|
|
|
return PromiseA.reject(new Error("no id supplied"));
|
|
|
|
}
|
2015-08-28 03:33:46 +00:00
|
|
|
|
|
|
|
return new PromiseA(function (resolve, reject) {
|
2015-09-22 02:14:07 +00:00
|
|
|
var json = JSON.stringify(obj);
|
|
|
|
var data = JSON.parse(json);
|
|
|
|
|
2015-09-22 08:08:58 +00:00
|
|
|
var sql;
|
|
|
|
|
|
|
|
// removes known fields from data
|
|
|
|
sql = strainUpdate(id, data, function sqlTpl(fieldable) {
|
|
|
|
return "INSERT INTO " + tablename + " (" + fieldable.join(', ') + ", " + idname + ")"
|
|
|
|
//+ " VALUES ('" + vals.join("', '") + "')"
|
|
|
|
+ " VALUES (" + fieldable.map(function () { return '?'; }).join(", ") + ", ?)"
|
|
|
|
;
|
|
|
|
});
|
2015-08-28 03:33:46 +00:00
|
|
|
|
2015-09-22 08:08:58 +00:00
|
|
|
console.log('[debug] DB.create() sql:', sql);
|
|
|
|
db.run(sql, [], function (err) {
|
2015-08-28 03:33:46 +00:00
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE changes is 1 even if the value of the updated record stays the same
|
|
|
|
// (PostgreSQL would return 0 in that case)
|
|
|
|
// thus if changes is 0 then it failed, otherwise it succeeded
|
|
|
|
/*
|
|
|
|
console.log('[log db wrapper insert]');
|
|
|
|
console.log(this); // sql, lastID, changes
|
|
|
|
console.log(this.sql);
|
|
|
|
console.log('insert lastID', this.lastID); // sqlite's internal rowId
|
|
|
|
console.log('insert changes', this.changes);
|
|
|
|
*/
|
|
|
|
|
|
|
|
//this.id = id;
|
|
|
|
resolve(this);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2015-09-22 08:08:58 +00:00
|
|
|
// pull indices from object
|
|
|
|
function strainUpdate(id, data/*, vals*/, cb) {
|
|
|
|
var fieldable = [];
|
|
|
|
var json;
|
|
|
|
var sql;
|
|
|
|
var vals = [];
|
2015-08-28 03:33:46 +00:00
|
|
|
|
2015-09-22 08:08:58 +00:00
|
|
|
(opts.indices || []).forEach(function (col) {
|
|
|
|
if ('string' === typeof col) {
|
|
|
|
col = { name: col, type: 'TEXT' };
|
|
|
|
}
|
|
|
|
if (!col.type) {
|
|
|
|
col.type = 'TEXT';
|
|
|
|
}
|
2015-08-28 03:33:46 +00:00
|
|
|
|
2015-09-22 08:08:58 +00:00
|
|
|
var val = data[camelCase(col.name)];
|
2015-08-28 03:33:46 +00:00
|
|
|
|
2015-09-22 08:08:58 +00:00
|
|
|
//if (col.name in data)
|
|
|
|
if ('undefined' !== typeof val) {
|
|
|
|
/*
|
|
|
|
fieldable.push(
|
|
|
|
db.escape(snakeCase(col.name))
|
|
|
|
+ " = '" + db.escape(val) + "'"
|
|
|
|
);
|
|
|
|
*/
|
|
|
|
fieldable.push(db.escape(snakeCase(col.name)));
|
|
|
|
vals.push(val);
|
|
|
|
}
|
2015-08-28 03:33:46 +00:00
|
|
|
|
2015-09-22 08:08:58 +00:00
|
|
|
delete data[col.name];
|
|
|
|
delete data[camelCase(col.name)];
|
|
|
|
});
|
|
|
|
|
|
|
|
delete data[idnameCased];
|
2015-08-28 03:33:46 +00:00
|
|
|
|
2015-09-22 08:08:58 +00:00
|
|
|
if (!fieldable.length || Object.keys(data).length) {
|
2015-08-28 03:33:46 +00:00
|
|
|
json = JSON.stringify(data);
|
2015-09-22 08:08:58 +00:00
|
|
|
fieldable.push("json");
|
2015-08-28 03:33:46 +00:00
|
|
|
//fieldable.push("json = '" + db.escape(json) + "'");
|
|
|
|
vals.push(json);
|
2015-09-22 08:08:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
vals.push(id);
|
|
|
|
|
|
|
|
sql = cb(fieldable);
|
|
|
|
|
|
|
|
while (vals.length) {
|
|
|
|
sql = sql.replace(/\?/, "'" + db.escape(vals.shift()) + "'");
|
|
|
|
}
|
|
|
|
|
|
|
|
return sql;
|
|
|
|
}
|
2015-08-28 03:33:46 +00:00
|
|
|
|
2015-09-22 08:08:58 +00:00
|
|
|
DB.set = function (id, obj) {
|
|
|
|
var json = JSON.stringify(obj);
|
|
|
|
var data = JSON.parse(json);
|
2015-08-28 03:33:46 +00:00
|
|
|
|
2015-09-22 08:08:58 +00:00
|
|
|
return new PromiseA(function (resolve, reject) {
|
|
|
|
function sqlTpl(fieldable) {
|
|
|
|
// this will always at least have one fieldable value: json
|
|
|
|
return "UPDATE " + tablename + " SET "
|
|
|
|
+ (fieldable.join(' = ?, ') + " = ?")
|
|
|
|
+ " WHERE " + idname + " = ?"
|
|
|
|
;
|
2015-08-28 03:33:46 +00:00
|
|
|
}
|
|
|
|
|
2015-09-22 08:08:58 +00:00
|
|
|
//var vals = [];
|
|
|
|
// removes known fields from data
|
|
|
|
var sql = strainUpdate(id, data/*, vals*/, sqlTpl);
|
|
|
|
|
|
|
|
console.log('[debug] DB.set() sql:', sql);
|
|
|
|
db.run(sql, /*vals*/[], function (err) {
|
2015-08-28 03:33:46 +00:00
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// it isn't possible to tell if the update succeeded or failed
|
|
|
|
// only if the update resulted in a change or not
|
|
|
|
/*
|
|
|
|
console.log('[log db wrapper set]');
|
|
|
|
console.log(this); // sql, lastID, changes
|
|
|
|
console.log(this.sql);
|
|
|
|
console.log('update lastID', this.lastID); // always 0 (except on INSERT)
|
|
|
|
console.log('update changes', this.changes);
|
|
|
|
*/
|
|
|
|
|
|
|
|
resolve(this);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
DB.destroy = function (id) {
|
|
|
|
if ('object' === typeof id) {
|
2015-09-22 02:14:07 +00:00
|
|
|
id = id[idnameCased];
|
2015-08-28 03:33:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return new PromiseA(function (resolve, reject) {
|
|
|
|
var sql = "DELETE FROM " + tablename + " WHERE " + idname + " = ?";
|
|
|
|
var values = [id];
|
|
|
|
|
|
|
|
db.run(sql, values, function (err) {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// it isn't possible to tell if the update succeeded or failed
|
|
|
|
// only if the update resulted in a change or not
|
|
|
|
/*
|
|
|
|
console.log('[log db wrapper delete]');
|
|
|
|
console.log(this); // sql, lastID, changes
|
|
|
|
console.log(this.sql);
|
|
|
|
console.log('delete lastID', this.lastID); // always 0 (except on INSERT)
|
|
|
|
console.log('delete changes', this.changes);
|
|
|
|
*/
|
|
|
|
|
|
|
|
resolve(this);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
DB._db = db;
|
|
|
|
|
|
|
|
return new PromiseA(function (resolve, reject) {
|
|
|
|
var indexable = [idname + ' TEXT'];
|
|
|
|
var sql;
|
|
|
|
|
|
|
|
(opts.indices || []).forEach(function (col) {
|
|
|
|
if ('string' === typeof col) {
|
|
|
|
col = { name: col, type: 'TEXT' };
|
|
|
|
}
|
|
|
|
if (!col.type) {
|
|
|
|
col.type = 'TEXT';
|
|
|
|
}
|
|
|
|
indexable.push(
|
|
|
|
db.escape(snakeCase(col.name))
|
|
|
|
+ ' ' + db.escape(col.type)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
indexable.push('json TEXT');
|
|
|
|
|
|
|
|
sql = "CREATE TABLE IF NOT EXISTS '" + tablename + "' "
|
|
|
|
+ "(" + indexable.join(', ') + ", PRIMARY KEY(" + idname + "))"
|
|
|
|
;
|
|
|
|
|
|
|
|
db.runAsync(sql).then(function () { resolve(DB); }, reject);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Array.isArray(dir)) {
|
|
|
|
arr = false;
|
|
|
|
dir = [dir];
|
|
|
|
}
|
|
|
|
|
|
|
|
dir.forEach(function (opts) {
|
|
|
|
promises.push(createTable(opts).then(function (dbw) {
|
|
|
|
var tablename = (opts.tablename || 'data');
|
|
|
|
|
|
|
|
tablename = upperCamelCase(tablename);
|
|
|
|
|
|
|
|
dbsMap[tablename] = dbw;
|
|
|
|
|
|
|
|
return dbw;
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
|
|
|
dbsMap.sql = db;
|
|
|
|
|
|
|
|
return PromiseA.all(promises).then(function (dbs) {
|
|
|
|
if (!arr) {
|
|
|
|
return dbs[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
return dbsMap;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports.wrap = wrap;
|