From 65fe453ac6ab5cb1b35e6cf85817b91d98a7583a Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 1 Dec 2015 04:44:52 +0000 Subject: [PATCH] auto-grow indices --- lib/dbwrap.js | 158 ++++++++++++++++++++++++++++++++++++++++-------- tests/dbwrap.js | 3 +- tests/setup.js | 12 +++- 3 files changed, 144 insertions(+), 29 deletions(-) diff --git a/lib/dbwrap.js b/lib/dbwrap.js index abf9a2e..487cd82 100644 --- a/lib/dbwrap.js +++ b/lib/dbwrap.js @@ -5,8 +5,12 @@ function wrap(db, dir) { // why doesn't the unhandled promise rejection fire? var PromiseA = require('bluebird'); var promises = []; + var earr = []; var dbsMap = {}; - var arr = true; + + db.escape = function (str) { + return (str||'').replace(/'/g, "''"); + }; function lowerFirst(str) { return str.charAt(0).toLowerCase() + str.slice(1); @@ -36,11 +40,98 @@ function wrap(db, dir) { return str.charAt(0).toUpperCase() + camelCase(str).slice(1); } + // PRAGMA schema.table_info(table-name); + // + 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; + } + + console.log('sqlite3 rows 0'); + console.log(result); + + 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" + ; + console.log('sqlite3 1'); + console.log(sql); + + db.all(sql, earr, function (err, results) { + if (err) { + console.error("[Error] add column '" + tablename + "'"); + console.error(err.stack); + cb(err); + return; + } + + console.log('sqlite3 rows 1'); + console.log(results); + + alterTable(); + }); + } + 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; + } + function createTable(opts) { + if (!opts.modelname && !opts.tablename) { + throw new Error('Please specify opts.modelname'); + } + + if (!opts.tablename) { + opts.tablename = snakeCase(opts.modelname); + } + + if (!opts.indices) { + opts.indices = []; + } + var DB = {}; var tablename = db.escape(snakeCase(opts.tablename) || 'data'); - var idname = db.escape(snakeCase(opts.idname) || 'id'); - var idnameCased = (camelCase(opts.idname) || 'id'); + var idname = db.escape(snakeCase(opts.idname || 'id')); + var idnameCased = (camelCase(opts.idname || 'id')); + + opts.indices.forEach(normalizeColumn); db = PromiseA.promisifyAll(db); @@ -98,6 +189,21 @@ function wrap(db, dir) { return results; } + DB.migrate = function (columns) { + columns.forEach(normalizeColumn); + + return new PromiseA(function (resolve, reject) { + sqlite3GetColumns(tablename, columns, function (err) { + if (err) { + reject(err); + return; + } + + resolve(); + }); + }); + }; + DB.find = function (opts, params) { var sql = 'SELECT * FROM \'' + tablename + '\' '; var keys = opts && Object.keys(opts); @@ -109,7 +215,12 @@ function wrap(db, dir) { if (i !== 0) { sql += 'AND '; } - sql += db.escape(snakeCase(key)) + " = '" + db.escape(opts[key]) + "'"; + if (null === opts[key]) { + sql += db.escape(snakeCase(key)) + " IS '" + db.escape(opts[key]) + "'"; + } + else { + sql += db.escape(snakeCase(key)) + " = '" + db.escape(opts[key]) + "'"; + } }); } else if (null !== opts || (params && !params.limit)) { @@ -264,14 +375,7 @@ function wrap(db, dir) { }); }); - (opts.indices || []).forEach(function (col) { - if ('string' === typeof col) { - col = { name: col, type: 'TEXT' }; - } - if (!col.type) { - col.type = 'TEXT'; - } - + opts.indices.forEach(function (col) { var val = data[camelCase(col.name)]; //if (col.name in data) @@ -386,7 +490,7 @@ function wrap(db, dir) { var indexable = [idname + ' TEXT']; var sql; - (opts.indices || []).forEach(function (col) { + opts.indices.forEach(function (col) { if ('string' === typeof col) { col = { name: col, type: 'TEXT' }; } @@ -404,19 +508,25 @@ function wrap(db, dir) { + "(" + indexable.join(', ') + ", PRIMARY KEY(" + idname + "))" ; - db.runAsync(sql).then(function () { resolve(DB); }, reject); - }); - } + db.runAsync(sql).then(function () { + sqlite3GetColumns(tablename, opts.indices, function (err) { + if (err) { + console.error('[Error] dbwrap get columns'); + console.error(err.stack); + reject(err); + return; + } - if (!Array.isArray(dir)) { - arr = false; - dir = [dir]; + resolve(DB); + }); + }, reject); + }); } dir.forEach(function (opts) { promises.push(createTable(opts).then(function (dbw) { - var modelname = opts.modelname; - + var modelname = opts.modelname; + if (!modelname) { modelname = (opts.tablename || 'data'); modelname = upperCamelCase(modelname); @@ -430,11 +540,7 @@ function wrap(db, dir) { dbsMap.sql = db; - return PromiseA.all(promises).then(function (dbs) { - if (!arr) { - return dbs[0]; - } - + return PromiseA.all(promises).then(function (/*dbs*/) { return dbsMap; }); } diff --git a/tests/dbwrap.js b/tests/dbwrap.js index a8f77ec..56f63e6 100644 --- a/tests/dbwrap.js +++ b/tests/dbwrap.js @@ -3,6 +3,7 @@ var PromiseA = require('bluebird').Promise; function testDb(DB) { + DB = DB.Data; return PromiseA.resolve(DB).then(function (DB) { var data = { secret: 'super secret', verifiedAt: 1437207288791 }; //return DB.set('aj@the.dj', data) @@ -43,7 +44,7 @@ function testDb(DB) { } function run(/*isMaster*/) { - require('./setup').run().then(testDb); + require('./setup').run([{ modelname: 'Data', indices: ['data'] }]).then(testDb); /* if (require.main === module) { diff --git a/tests/setup.js b/tests/setup.js index 1c76a83..f90d666 100644 --- a/tests/setup.js +++ b/tests/setup.js @@ -1,8 +1,15 @@ +'use strict'; + function run(opts) { var config = require('../config.test.js'); - var sqlite3 = require('sqlite3-cluster'); var wrap = require('../lib/dbwrap'); + var sqlite3 = require('sqlite3'); + var db = new sqlite3.Database(config.filename); + return wrap.wrap(db, opts); + +/* + var sqlite3 = require('sqlite3-cluster'); var promise = sqlite3.create({ standalone: true , bits: 128 @@ -13,8 +20,9 @@ function run(opts) { return promise.then(function (db) { return db.init({ bits: 128, key: config.key }); }).then(function (db) { - return wrap.wrap(db, Array.isArray(opts) && opts || { idname: 'uuid', tablename: opts && opts.tablename || 'authn' }); + return wrap.wrap(db, opts); }); +*/ /* if (require.main === module) {