2015-08-28 03:33:46 +00:00
|
|
|
'use strict';
|
2019-04-04 08:00:13 +00:00
|
|
|
/*global Promise*/
|
|
|
|
var PromiseA = Promise;
|
2015-08-28 03:33:46 +00:00
|
|
|
|
2018-09-15 20:15:54 +00:00
|
|
|
function lowerFirst(str) {
|
|
|
|
return str.charAt(0).toLowerCase() + str.substr(1);
|
|
|
|
}
|
|
|
|
function snakeCase(str) {
|
|
|
|
return lowerFirst(str).replace(/([A-Z])/g, function (match) {
|
|
|
|
return "_" + match.toLowerCase();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
function camelCase(str) {
|
|
|
|
return str.replace(/_([a-z])/g, function (match) {
|
|
|
|
return match[1].toUpperCase();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
function upperCamelCase(str) {
|
|
|
|
var camel = camelCase(str);
|
|
|
|
return camel.charAt(0).toUpperCase() + camel.substr(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
var searchConditions = {
|
|
|
|
'=': true,
|
|
|
|
'==': true,
|
|
|
|
'!=': true,
|
|
|
|
'<>': true,
|
|
|
|
'<': true,
|
|
|
|
'<=': true,
|
|
|
|
'!<': true,
|
|
|
|
'>': true,
|
|
|
|
'>=': true,
|
|
|
|
'!>': true,
|
|
|
|
'IS': true,
|
|
|
|
'IS NOT': true,
|
|
|
|
|
|
|
|
'IN': true,
|
|
|
|
'NOT IN': true,
|
|
|
|
'LIKE': true,
|
|
|
|
'NOT LIKE': true,
|
|
|
|
'GLOB': true,
|
|
|
|
'NOT GLOB': true,
|
|
|
|
'BETWEEN': true,
|
|
|
|
'NOT BETWEEN': true,
|
|
|
|
};
|
|
|
|
|
2016-03-04 01:22:16 +00:00
|
|
|
function wrap(db, dir, dbsMap) {
|
2015-08-28 03:33:46 +00:00
|
|
|
// TODO if I put a failure right here,
|
|
|
|
// why doesn't the unhandled promise rejection fire?
|
|
|
|
var promises = [];
|
2015-12-01 04:44:52 +00:00
|
|
|
var earr = [];
|
2015-12-09 00:57:31 +00:00
|
|
|
var debug = false;
|
2015-12-01 04:44:52 +00:00
|
|
|
|
2016-03-04 01:22:16 +00:00
|
|
|
if (!dbsMap) {
|
|
|
|
dbsMap = {};
|
|
|
|
}
|
|
|
|
|
2015-12-01 04:44:52 +00:00
|
|
|
// PRAGMA schema.table_info(table-name);
|
2016-01-03 05:24:17 +00:00
|
|
|
//
|
2015-12-01 04:44:52 +00:00
|
|
|
function sqlite3GetColumns(tablename, columns, cb) {
|
|
|
|
var sql = "PRAGMA table_info(" + db.escape(tablename) + ")";
|
|
|
|
|
|
|
|
db.all(sql, earr, function (err, result) {
|
|
|
|
if (err) {
|
|
|
|
console.error('[Error] query columns');
|
|
|
|
console.error(err.stack);
|
|
|
|
cb(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-12-09 00:57:31 +00:00
|
|
|
if (debug) {
|
|
|
|
console.log('sqlite3 rows 0');
|
|
|
|
console.log(result);
|
|
|
|
}
|
2015-12-01 04:44:52 +00:00
|
|
|
|
|
|
|
function alterTable() {
|
|
|
|
var column = columns.pop();
|
|
|
|
var sql;
|
|
|
|
|
|
|
|
if (!column) {
|
|
|
|
cb(null);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((result.rows||result).some(function (row) {
|
|
|
|
return (row.column_name || row.name) === snakeCase(column.name);
|
|
|
|
})) {
|
|
|
|
alterTable();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
sql = "ALTER TABLE " + db.escape(tablename)
|
|
|
|
+ " ADD COLUMN "
|
|
|
|
+ db.escape(column.name) + " " + db.escape(column.type)
|
|
|
|
+ " DEFAULT null"
|
|
|
|
;
|
2015-12-09 00:57:31 +00:00
|
|
|
|
|
|
|
if (debug) {
|
|
|
|
console.log('sqlite3 1');
|
|
|
|
console.log(sql);
|
|
|
|
}
|
2015-12-01 04:44:52 +00:00
|
|
|
|
|
|
|
db.all(sql, earr, function (err, results) {
|
|
|
|
if (err) {
|
|
|
|
console.error("[Error] add column '" + tablename + "'");
|
2017-01-14 22:56:11 +00:00
|
|
|
console.error(sql);
|
|
|
|
console.error(err.stack || new Error('stack').stack);
|
2015-12-01 04:44:52 +00:00
|
|
|
cb(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-12-09 00:57:31 +00:00
|
|
|
if (debug) {
|
|
|
|
console.log('sqlite3 rows 1');
|
|
|
|
console.log(results);
|
|
|
|
}
|
2015-12-01 04:44:52 +00:00
|
|
|
|
|
|
|
alterTable();
|
|
|
|
});
|
|
|
|
}
|
2015-12-09 00:57:31 +00:00
|
|
|
|
|
|
|
columns = columns.slice(0);
|
2015-12-01 04:44:52 +00:00
|
|
|
alterTable();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function normalizeColumn(col, i, arr) {
|
|
|
|
if ('string' === typeof col) {
|
|
|
|
col = arr[i] = { name: col, type: 'text' };
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!col.type) {
|
|
|
|
col.type = 'text';
|
|
|
|
}
|
|
|
|
|
|
|
|
col.type = col.type.toLowerCase(); // oh postgres...
|
|
|
|
col.name = snakeCase(col.name);
|
|
|
|
|
|
|
|
return col;
|
|
|
|
}
|
|
|
|
|
2015-12-09 00:57:31 +00:00
|
|
|
function createTable(dir) {
|
|
|
|
if (!dir.modelname && !dir.tablename) {
|
|
|
|
throw new Error('Please specify dir.modelname');
|
2015-12-01 04:44:52 +00:00
|
|
|
}
|
|
|
|
|
2015-12-09 00:57:31 +00:00
|
|
|
if (!dir.tablename) {
|
|
|
|
dir.tablename = snakeCase(dir.modelname);
|
2015-12-01 04:44:52 +00:00
|
|
|
}
|
|
|
|
|
2015-12-09 00:57:31 +00:00
|
|
|
if (!dir.modelname) {
|
|
|
|
dir.modelname = upperCamelCase(dir.tablename);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!dir.indices) {
|
|
|
|
dir.indices = [];
|
2015-12-01 04:44:52 +00:00
|
|
|
}
|
|
|
|
|
2015-08-28 03:33:46 +00:00
|
|
|
var DB = {};
|
2015-12-09 00:57:31 +00:00
|
|
|
var tablename = (db.escape(dir.tablename || 'data'));
|
|
|
|
var idname = (db.escape(dir.idname || 'id'));
|
|
|
|
var idnameCased = (camelCase(dir.idname || 'id'));
|
2015-12-01 04:44:52 +00:00
|
|
|
|
2015-12-09 00:57:31 +00:00
|
|
|
dir.indices.forEach(normalizeColumn);
|
2016-07-12 15:37:08 +00:00
|
|
|
DB._indices = dir.indices;
|
2018-09-15 20:04:43 +00:00
|
|
|
DB._indicesMap = {};
|
|
|
|
DB._indices.forEach(function (col) {
|
|
|
|
DB._indicesMap[col.name] = col;
|
|
|
|
});
|
2015-08-28 03:33:46 +00:00
|
|
|
|
|
|
|
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 = {};
|
|
|
|
}
|
2018-09-15 20:15:54 +00:00
|
|
|
delete row.json;
|
2015-08-28 03:33:46 +00:00
|
|
|
|
2015-09-22 02:14:07 +00:00
|
|
|
obj[idnameCased] = row[idname];
|
2015-10-20 08:47:43 +00:00
|
|
|
delete row[idname];
|
|
|
|
|
|
|
|
Object.keys(row).forEach(function (fieldname) {
|
2016-03-26 21:03:19 +00:00
|
|
|
// Ideally it shouldn't be possible to overriding a former proper column,
|
|
|
|
// but when a new indexable field is added, the old value is still in json
|
|
|
|
// TODO one-time upgrade of all rows when a new column is added
|
|
|
|
if (null === row[fieldname] || 'undefined' === typeof row[fieldname] || '' === row[fieldname]) {
|
|
|
|
obj[camelCase(fieldname)] = row[fieldname] || obj[camelCase(fieldname)] || row[fieldname];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
obj[camelCase(fieldname)] = row[fieldname];
|
|
|
|
}
|
2015-10-20 08:47:43 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return obj;
|
2015-08-28 03:33:46 +00:00
|
|
|
});
|
|
|
|
// set up for garbage collection
|
|
|
|
rows.length = 0;
|
|
|
|
rows = null;
|
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
2018-09-15 20:15:54 +00:00
|
|
|
function simpleParse(row) {
|
|
|
|
if (!row) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return simpleMap([row])[0] || null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// pull indices from object
|
|
|
|
function strainUpdate(id, data/*, vals*/, cb, oldId) {
|
|
|
|
var fieldable = [];
|
|
|
|
var sql;
|
|
|
|
var vals = [];
|
|
|
|
|
|
|
|
['hasOne', 'hasMany', 'hasAndBelongsToMany', 'belongsTo', 'belongsToMany'].forEach(function (relname) {
|
|
|
|
var rels = dir[relname];
|
|
|
|
|
|
|
|
if (!rels) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Array.isArray(rels)) {
|
|
|
|
rels = [rels];
|
|
|
|
}
|
|
|
|
|
|
|
|
// don't save relationships
|
|
|
|
rels.forEach(function (colname) {
|
|
|
|
delete data[colname];
|
|
|
|
delete data[camelCase(colname)];
|
|
|
|
// TODO placehold relationships on find / get?
|
|
|
|
// data[camelCase(colname)] = null;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
dir.indices.forEach(function (col) {
|
|
|
|
// We prioritze the raw name rather than the camelCase name because it's not in the object
|
|
|
|
// we give for retrieved entries, so if it's present then the user put it there themselves.
|
|
|
|
var val = data[col.name] || data[camelCase(col.name)];
|
|
|
|
|
|
|
|
//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);
|
|
|
|
}
|
|
|
|
|
|
|
|
delete data[col.name];
|
|
|
|
delete data[camelCase(col.name)];
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!oldId) {
|
|
|
|
delete data[idnameCased];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!fieldable.length || Object.keys(data).length) {
|
|
|
|
vals.push(JSON.stringify(data));
|
|
|
|
} else {
|
|
|
|
vals.push(null);
|
|
|
|
}
|
|
|
|
fieldable.push('json');
|
|
|
|
|
|
|
|
vals.push(id);
|
|
|
|
|
|
|
|
sql = cb(fieldable);
|
|
|
|
|
|
|
|
if (debug) {
|
|
|
|
console.log('[masterquest-sqlite3] dbwrap.js');
|
|
|
|
console.log(sql);
|
|
|
|
console.log(vals);
|
|
|
|
}
|
|
|
|
|
|
|
|
vals.forEach(function (val) {
|
|
|
|
if (null === val || 'number' === typeof val) {
|
|
|
|
sql = sql.replace('?', String(val));
|
|
|
|
} else {
|
|
|
|
sql = sql.replace('?', "'" + db.escape(val) + "'");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return sql;
|
|
|
|
}
|
|
|
|
|
2015-12-01 04:44:52 +00:00
|
|
|
DB.migrate = function (columns) {
|
|
|
|
columns.forEach(normalizeColumn);
|
|
|
|
|
|
|
|
return new PromiseA(function (resolve, reject) {
|
|
|
|
sqlite3GetColumns(tablename, columns, function (err) {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2015-12-09 00:57:31 +00:00
|
|
|
DB.find = function (obj, params) {
|
2016-03-23 03:37:20 +00:00
|
|
|
var err;
|
2015-10-20 08:00:12 +00:00
|
|
|
var sql = 'SELECT * FROM \'' + tablename + '\' ';
|
2015-12-09 00:57:31 +00:00
|
|
|
var keys = obj && Object.keys(obj);
|
2015-08-28 03:33:46 +00:00
|
|
|
|
2016-03-23 03:38:59 +00:00
|
|
|
if (obj) {
|
|
|
|
Object.keys(obj).forEach(function (key) {
|
|
|
|
if (undefined === obj[key]) {
|
|
|
|
err = new Error("'" + key + "' was `undefined'. For security purposes you must explicitly set the value to null or ''");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2016-03-23 03:37:20 +00:00
|
|
|
if (err) {
|
|
|
|
return PromiseA.reject(err);
|
2016-03-22 21:43:13 +00:00
|
|
|
}
|
2015-08-28 03:33:46 +00:00
|
|
|
|
2018-09-15 20:15:54 +00:00
|
|
|
if (params && params.limit) {
|
|
|
|
params.limit = parseInt(params.limit, 10);
|
|
|
|
// remember to check for the case of NaN
|
|
|
|
if (!params.limit || params.limit <= 0) {
|
|
|
|
return PromiseA.reject(new Error('limit must be a positive integer'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-09 00:57:31 +00:00
|
|
|
if (obj && keys.length) {
|
2018-09-15 20:15:54 +00:00
|
|
|
var conditions = keys.map(function (key) {
|
|
|
|
var dbKey = db.escape(snakeCase(key));
|
|
|
|
var value = obj[key];
|
|
|
|
if (null === value) {
|
|
|
|
return dbKey + ' IS NULL';
|
|
|
|
}
|
2015-10-20 08:00:12 +00:00
|
|
|
|
2018-09-15 20:15:54 +00:00
|
|
|
var split, cmd;
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
value = value.trim();
|
|
|
|
if (['IS NULL', 'IS NOT NULL'].indexOf(value.toUpperCase()) !== -1) {
|
|
|
|
return dbKey + ' ' + value.toUpperCase();
|
|
|
|
}
|
|
|
|
|
|
|
|
split = value.split(' ');
|
|
|
|
if (searchConditions[split[0].toUpperCase()]) {
|
|
|
|
cmd = split[0].toUpperCase();
|
|
|
|
value = split.slice(1).join(' ');
|
|
|
|
} else if (searchConditions[split.slice(0, 2).join(' ').toUpperCase()]) {
|
|
|
|
cmd = split.slice(0, 2).join(' ').toUpperCase();
|
|
|
|
value = split.slice(2).join(' ');
|
|
|
|
}
|
|
|
|
// If we were given something like "BEGINS WITH 'something quoted'" we don't want
|
|
|
|
// to include the quotes (we'll quote it again later) so we strip them out here.
|
|
|
|
if (cmd) {
|
|
|
|
value = value.replace(/^(['"])(.*)\1$/, '$2');
|
|
|
|
}
|
2015-10-20 08:00:12 +00:00
|
|
|
}
|
2018-09-15 20:15:54 +00:00
|
|
|
if (typeof value === 'object') {
|
|
|
|
cmd = value.condition || value.relation || value.cmd;
|
|
|
|
value = value.value;
|
|
|
|
if (!cmd || !value) {
|
|
|
|
err = new Error("'"+key+"' was an object, but missing condition and/or value");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (typeof cmd !== 'string' || !searchConditions[cmd.toUpperCase()]) {
|
|
|
|
err = new Error("'"+key+"' tried to use invalid condition '"+cmd+"'");
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
cmd = cmd.toUpperCase();
|
|
|
|
}
|
2015-12-01 04:44:52 +00:00
|
|
|
}
|
2018-09-15 20:15:54 +00:00
|
|
|
|
|
|
|
if (!cmd) {
|
|
|
|
cmd = '=';
|
|
|
|
}
|
|
|
|
// The IN condition is special in that we can't quote the value as a single value,
|
|
|
|
// so it requires a little more logic to actually work and still be sanitary.
|
|
|
|
if (cmd === 'IN' || cmd === 'NOT IN') {
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
value = value.split((params || {}).seperator || /[\s,]+/);
|
|
|
|
}
|
|
|
|
if (!Array.isArray(value)) {
|
|
|
|
err = new Error("'"+key+"' has invalid value for use with 'IN'");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
value = value.map(function (val) {
|
|
|
|
return "'"+db.escape(val)+"'";
|
|
|
|
});
|
|
|
|
return dbKey + ' ' + cmd + ' (' + value.join(',') + ')';
|
|
|
|
}
|
|
|
|
// The BETWEEN condition is also special for the same reason as IN
|
|
|
|
if (cmd === 'BETWEEN' || cmd === 'NOT BETWEEN') {
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
value = value.split((params || {}).seperator || /[\s,]+(AND\s+)?/i);
|
|
|
|
}
|
|
|
|
if (!Array.isArray(value) || value.length !== 2) {
|
|
|
|
err = new Error("'"+key+"' has invalid value for use with 'BETWEEN'");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
value = value.map(function (val) {
|
|
|
|
return "'"+db.escape(val)+"'";
|
|
|
|
});
|
|
|
|
return dbKey + ' ' + cmd + ' ' + value.join(' AND ');
|
|
|
|
}
|
|
|
|
// If we are supposed to compare to another field then make sure the name is correct,
|
|
|
|
// and that we don't try to quote the name.
|
|
|
|
if (typeof value === 'string' && /^[a-zA-Z0-9_]*$/.test(value)) {
|
|
|
|
var snake = snakeCase(value);
|
|
|
|
if (dir.indices.some(function (col) { return snake === col.name; })) {
|
|
|
|
return dbKey + ' ' + cmd + ' ' + snake;
|
|
|
|
}
|
2015-12-01 04:44:52 +00:00
|
|
|
}
|
2018-09-15 20:15:54 +00:00
|
|
|
return dbKey + ' ' + cmd + " '" + db.escape(value) + "'";
|
2015-10-20 08:00:12 +00:00
|
|
|
});
|
2018-09-15 20:15:54 +00:00
|
|
|
if (err) {
|
|
|
|
return PromiseA.reject(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
sql += 'WHERE ' + conditions.join(' AND ');
|
2015-10-20 08:00:12 +00:00
|
|
|
}
|
2018-09-15 20:15:54 +00:00
|
|
|
else if (null !== obj || !(params && params.limit)) {
|
2015-10-20 08:00:12 +00:00
|
|
|
return PromiseA.reject(new Error("to find all you must explicitly specify find(null, { limit: <<int>> })"));
|
|
|
|
}
|
2015-08-28 03:33:46 +00:00
|
|
|
|
2015-09-22 02:14:07 +00:00
|
|
|
if (params) {
|
2018-09-15 20:15:54 +00:00
|
|
|
if (typeof params.orderByDesc === 'string' && !params.orderBy) {
|
|
|
|
params.orderBy = params.orderByDesc;
|
|
|
|
params.orderByDesc = true;
|
|
|
|
}
|
2018-09-15 20:47:04 +00:00
|
|
|
|
|
|
|
// IMPORTANT: " is not the same to sqlite as '.
|
|
|
|
// // " is exact and necessary
|
2015-09-22 02:14:07 +00:00
|
|
|
if (params.orderBy) {
|
2018-09-15 20:47:04 +00:00
|
|
|
sql += " ORDER BY \"" + db.escape(snakeCase(params.orderBy)) + "\" ";
|
2015-09-22 02:14:07 +00:00
|
|
|
if (params.orderByDesc) {
|
2018-09-15 20:04:43 +00:00
|
|
|
sql += "DESC ";
|
2015-09-22 02:14:07 +00:00
|
|
|
}
|
2018-09-15 20:04:43 +00:00
|
|
|
} else if (DB._indicesMap.updated_at) {
|
2018-09-15 20:47:04 +00:00
|
|
|
sql += " ORDER BY \"updated_at\" DESC ";
|
2018-09-15 20:04:43 +00:00
|
|
|
} else if (DB._indicesMap.created_at) {
|
2018-09-15 20:47:04 +00:00
|
|
|
sql += " ORDER BY \"created_at\" DESC ";
|
2015-09-22 02:14:07 +00:00
|
|
|
}
|
2018-09-15 20:04:43 +00:00
|
|
|
if (isFinite(params.limit)) {
|
2015-10-20 08:00:12 +00:00
|
|
|
sql += " LIMIT " + parseInt(params.limit, 10);
|
|
|
|
}
|
2015-09-22 02:14:07 +00:00
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
};
|
|
|
|
|
2017-03-17 00:20:19 +00:00
|
|
|
DB.upsert = function (id, data, oldId) {
|
2015-11-17 08:29:45 +00:00
|
|
|
if (!data) {
|
|
|
|
data = id;
|
|
|
|
id = data[idnameCased];
|
|
|
|
}
|
|
|
|
|
2017-03-17 00:20:19 +00:00
|
|
|
return DB.set(oldId || id, data, oldId).then(function (result) {
|
2015-08-28 03:33:46 +00:00
|
|
|
var success = result.changes >= 1;
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
return DB.create(id, data);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2017-03-17 00:20:19 +00:00
|
|
|
DB.save = function (data, oldId) {
|
|
|
|
if (!data[idnameCased] && !oldId) {
|
2015-08-28 03:33:46 +00:00
|
|
|
// NOTE saving the id both in the object and the id for now
|
2019-04-04 08:00:13 +00:00
|
|
|
data[idnameCased] = require('crypto').randomBytes(16).toString('hex').split('');
|
|
|
|
data[idnameCased].splice(8, 0, '-');
|
|
|
|
data[idnameCased].splice(8 + 1 + 4, 0, '-');
|
|
|
|
data[idnameCased].splice(8 + 1 + 4 + 1 + 4, 0, '-');
|
|
|
|
data[idnameCased].splice(8 + 1 + 4 + 1 + 4 + 1 + 4, 0, '-');
|
|
|
|
data[idnameCased][14] = 4; // TODO look at the mock uuid in the Go code I wrote
|
|
|
|
data[idnameCased] = data[idnameCased].join('');
|
2015-09-22 02:14:07 +00:00
|
|
|
return DB.create(data[idnameCased], data).then(function (/*stats*/) {
|
2015-08-28 03:33:46 +00:00
|
|
|
//data._rowid = stats.id;
|
|
|
|
return data;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-03-17 00:20:19 +00:00
|
|
|
return DB.set(oldId || data[idnameCased], data, oldId).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 {
|
2015-10-06 06:31:07 +00:00
|
|
|
//console.log('[debug result of set]', result.sql);
|
2015-09-22 02:14:07 +00:00
|
|
|
delete result.sql;
|
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
|
|
|
|
2016-03-16 18:20:16 +00:00
|
|
|
obj.createdAt = Date.now();
|
|
|
|
obj.updatedAt = Date.now();
|
2016-03-04 01:22:16 +00:00
|
|
|
|
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-10-06 06:31:07 +00:00
|
|
|
//console.log('[debug] DB.create() sql:', sql);
|
2015-09-22 08:08:58 +00:00
|
|
|
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);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2017-03-17 00:20:19 +00:00
|
|
|
DB.set = function (id, obj, oldId) {
|
2016-03-16 18:20:16 +00:00
|
|
|
obj.updatedAt = Date.now();
|
|
|
|
|
2015-09-22 08:08:58 +00:00
|
|
|
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
|
2016-03-04 01:22:16 +00:00
|
|
|
data.updated_at = Date.now();
|
2017-03-17 00:20:19 +00:00
|
|
|
var sql = strainUpdate(id, data/*, vals*/, sqlTpl, oldId);
|
2015-09-22 08:08:58 +00:00
|
|
|
|
2015-10-06 06:31:07 +00:00
|
|
|
//console.log('[debug] DB.set() sql:', sql);
|
2015-09-22 08:08:58 +00:00
|
|
|
db.run(sql, /*vals*/[], function (err) {
|
2015-10-06 06:31:07 +00:00
|
|
|
//console.log('[debug] error:', 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;
|
|
|
|
|
2015-12-09 00:57:31 +00:00
|
|
|
dir.indices.forEach(function (col) {
|
2015-08-28 03:33:46 +00:00
|
|
|
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 + "))"
|
|
|
|
;
|
|
|
|
|
2015-12-01 04:44:52 +00:00
|
|
|
db.runAsync(sql).then(function () {
|
2015-12-09 00:57:31 +00:00
|
|
|
sqlite3GetColumns(tablename, dir.indices, function (err) {
|
2015-12-01 04:44:52 +00:00
|
|
|
if (err) {
|
|
|
|
console.error('[Error] dbwrap get columns');
|
|
|
|
console.error(err.stack);
|
|
|
|
reject(err);
|
|
|
|
return;
|
|
|
|
}
|
2015-08-28 03:33:46 +00:00
|
|
|
|
2015-12-01 04:44:52 +00:00
|
|
|
resolve(DB);
|
|
|
|
});
|
|
|
|
}, reject);
|
|
|
|
});
|
2015-08-28 03:33:46 +00:00
|
|
|
}
|
|
|
|
|
2019-04-04 08:00:13 +00:00
|
|
|
function promisify(key) {
|
|
|
|
if ('function' !== typeof db[key] || /Async$/.test(key) || db[key + 'Async']) { return; }
|
|
|
|
db[key + 'Async'] = require('util').promisify(db[key]);
|
|
|
|
}
|
2017-05-30 01:38:04 +00:00
|
|
|
if (!db.__masterquest_init) {
|
|
|
|
db.__masterquest_init = true;
|
2019-04-04 08:00:13 +00:00
|
|
|
Object.keys(db).forEach(promisify);
|
|
|
|
['run', 'all'].forEach(promisify);
|
2017-05-30 01:38:04 +00:00
|
|
|
db.__masterquest_init = true;
|
|
|
|
db.escape = function (str) {
|
|
|
|
// TODO? literals for true,false,null
|
|
|
|
// error on undefined?
|
|
|
|
if (undefined === str) {
|
|
|
|
str = '';
|
|
|
|
}
|
|
|
|
return String(str).replace(/'/g, "''");
|
|
|
|
};
|
|
|
|
|
|
|
|
if (dir && dir.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);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dbsMap.sql = db;
|
|
|
|
|
2015-12-09 00:57:31 +00:00
|
|
|
dir.forEach(function (dir) {
|
2016-03-04 01:22:16 +00:00
|
|
|
// TODO if directive is the same as existing dbsMap, skip it
|
2015-12-09 00:57:31 +00:00
|
|
|
promises.push(createTable(dir).then(function (dbw) {
|
2015-08-28 03:33:46 +00:00
|
|
|
|
2015-12-09 00:57:31 +00:00
|
|
|
dbsMap[dir.modelname] = dbw;
|
2015-08-28 03:33:46 +00:00
|
|
|
|
|
|
|
return dbw;
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
2015-12-01 04:44:52 +00:00
|
|
|
return PromiseA.all(promises).then(function (/*dbs*/) {
|
2015-08-28 03:33:46 +00:00
|
|
|
return dbsMap;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports.wrap = wrap;
|