Skip to content

Commit

Permalink
➕ port over code from dwyl/abase #2
Browse files Browse the repository at this point in the history
  • Loading branch information
jrans committed Oct 25, 2016
1 parent b51e2db commit 88b5368
Show file tree
Hide file tree
Showing 16 changed files with 995 additions and 2 deletions.
18 changes: 18 additions & 0 deletions example_schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

module.exports = {
table_name: 'user_data', // eslint-disable-line
fields: {
email: {
type: 'string',
email: true
},
username: {
type: 'string',
min: 3,
max: 20,
unique: true
},
dob: { type: 'date' }
}
};
24 changes: 24 additions & 0 deletions lib/config_validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

var Joi = require('joi');

var mapObj = require('./create_table_map.js').mapObj;

// non empty, alphanumeric, no leading number, less than 64
var dbNameRegEx = /^[A-Za-z_]\w{0,62}$/;
var fieldTypes = Object.keys(mapObj);

var fieldSchema = Joi.object()
.keys({ type: Joi.any().valid(fieldTypes) })
.unknown()
;
var configSchema = Joi.object().keys({
table_name: Joi.string().regex(dbNameRegEx).required(), // eslint-disable-line
fields: Joi.object().pattern(dbNameRegEx, fieldSchema).required() // eslint-disable-line
});

module.exports = function (config) {
return Joi.assert(config, configSchema);
};

module.exports.dbNameRegEx = dbNameRegEx;
32 changes: 32 additions & 0 deletions lib/create_table_map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict';

var mapObj = {
number: function (opts) {
return opts.integer ? 'BIGINT' : 'DOUBLE PRECISION';
},
string: function (opts) {
var length = opts.max || 80;

return 'VARCHAR(' + length + ')';
},
boolean: function () {
return 'BOOLEAN';
},
date: function (opts) {
return opts.timestamp ? 'TIMESTAMP' : 'DATE';
}
};

function mapper (name, type, options) {
var opts = options || {};
var constraints = '';

if (opts.unique) {
constraints += ' CONSTRAINT ' + name + '_unique UNIQUE';
}

return name + ' ' + mapObj[type](opts) + constraints;
}

module.exports = mapper;
module.exports.mapObj = mapObj;
50 changes: 50 additions & 0 deletions lib/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict';

var sqlGen = require('./sql_gen.js');
var configValidator = require('./config_validator.js');

var methods = {
init: function (client, config, _, cb) {
var callback = (!cb && typeof _ === 'function') ? _ : cb;

configValidator(config);

return client.query(sqlGen.init(config), callback);
}
};

['select', 'update', 'delete', 'insert'].forEach(function (method) {
methods[method] = function (client, config, options, cb) {
var tableName = config.table_name;
var args = sqlGen[method](tableName, options).concat([cb]);

return client.query.apply(client, args);
};
});

methods.bindAll = function (pool, schema) {
return [
'select', 'update', 'delete', 'insert'
].reduce(function (acc, method) {
acc[method] = function (options, cb) {
return pool.connect()
.then(function (client) {
return methods[method](client, schema, options)
.then(function (result) {
client.release();

return cb ? cb(null, result) : result;
})
.catch(function (err) {
client.release();

return cb ? cb(err) : null;
});
});
};

return acc;
}, {});
};

module.exports = methods;
49 changes: 49 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Abase DB plugin
*
* Accepts path the schema defining the user model in the plugin options,
* or relies on the schema attached to the server settings object.
*
* Provides database helper functions to do schema-compatible CRUD operations.
* Attaches these methods to the request object at the pre-handler lifecycle
* point.
*/
'use strict';

var pg = require('pg');
var db = require('./helpers.js');
var parse = require('./parse.js');

exports.register = function (server, options, next) {
var schema = parse.schema(server.app.abase || {}, options.schemaPath);

var connection = parse.dbConfig(options.dbConnection);
var pool = new pg.Pool(connection);

pool.connect(function (dbConnErr, client, release) {
if (dbConnErr) {
return next(dbConnErr);
}

return db.init(client, schema, function (dbErr) {
release();

if (dbErr) {
return next(dbErr);
}

server.ext('onPreHandler', function (request, reply) {
request.abase = { db: db.bindAll(pool, schema) };
reply.continue();
});

server.decorate('server', 'endAbaseDb', function () {
pool.end();
});

return next();
});
});
};

exports.register.attributes = { name: 'abase-db' };
34 changes: 34 additions & 0 deletions lib/parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use strict';

var url = require('url');

exports.schema = function (schema, schemaPath) {
var sch;

if (!schema || Object.keys(schema).length === 0) {
sch = require(schemaPath); // eslint-disable-line
} else {
sch = schema;
}

return sch;
};


exports.dbConfig = function (dbConnection) {
var parsed;

if (typeof dbConnection === 'string') {
parsed = url.parse(dbConnection);

return {
host: parsed.hostname,
port: parsed.port,
database: parsed.pathname.split('/')[1],
user: (parsed.auth || '').split(':')[0],
password: (parsed.auth || '').split(':')[1]
};
}

return dbConnection;
};
122 changes: 122 additions & 0 deletions lib/sql_gen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
'use strict';

var mapper = require('./create_table_map.js');
var _ = require('./utils.js');


function paramStr (columns, opts) {
var offset = (opts && opts.offset) || 0;
var assign = (opts && opts.assign) || false;

return columns.map(function (k, i) {
var suff = '$' + (1 + i + (offset || 0));
var pref = assign ? k + '=' : '';

return pref + suff;
});
}


function processWhere (where, query, values) {
var keys = Object.keys(where);
var conds = paramStr(keys, { offset: values.length, assign: true });
var vals = _.values(where, keys);

return {
query: query.concat('WHERE').concat(conds.join(' AND ')),
values: values.concat(vals)
};
}


exports.init = function init (config) {
var tableName = config.table_name;
var fields = config.fields;

var columns = Object.keys(fields).map(function (key) {
var type = fields[key].type;
var opts = _.except(['type'], fields[key]);

return mapper(key, type, opts);
});

return ['CREATE TABLE IF NOT EXISTS "' + tableName + '"']
.concat('(' + columns.join(', ') + ')')
.join(' ')
.trim();
};


exports.select = function select (tableName, options) {
var columns = options.select || ['*'];
var values = [];
var query = ['SELECT']
.concat(columns.join(', '))
.concat('FROM')
.concat('"' + tableName + '"');
var result;

if (options.where) {
result = processWhere(options.where, query, values);
query = result.query;
values = result.values;
}

query = query.join(' ').trim();

return [query, values];
};


exports.insert = function insert (tableName, options) {
var fields = options.fields || {};
var columns = Object.keys(fields);
var values = _.values(fields, columns);
var params = paramStr(columns);

var query = ['INSERT INTO "' + tableName + '"']
.concat('(' + columns.join(', ') + ')')
.concat('VALUES')
.concat('(' + params.join(', ') + ')')
.join(' ')
.trim();

return [query, values];
};


exports.update = function update (tableName, options) {
var fields = options.fields || {};
var columns = Object.keys(fields);
var conditions = paramStr(columns, { assign: true });
var values = _.values(fields, columns);

var query = ['UPDATE "' + tableName + '"']
.concat('SET')
.concat(conditions.join(', '));
var result;

if (options.where) {
result = processWhere(options.where, query, values);
query = result.query;
values = result.values;
}

query = query.join(' ').trim();

return [query, values];
};


exports.delete = function _delete (tableName, options) {
var query = ['DELETE FROM "' + tableName + '"'];
var values = [];
var result = processWhere(options.where, query, values);

query = result.query;
values = result.values;

query = query.join(' ').trim();

return [query, values];
};
26 changes: 26 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';

exports.values = function (obj, keys) {
return (keys || Object.keys(obj))
.map(function (k) { return obj[k] });
};


function except (fields, obj) {
var o = {};

Object.keys(obj).forEach(function (k) {
if (fields.indexOf(k) === -1) {
o[k] = obj[k];
}
});

return o;
}


exports.except = except;

exports.shallowCopy = function (obj) {
return except([], obj);
};
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@
"goodparts": "^1.1.0",
"istanbul": "^0.4.5",
"pre-commit": "^1.1.3",
"tape": "^4.6.2"
"tape": "^4.6.2",
"hapi": "^15.1.1"
},
"dependencies": {
"env2": "^2.1.1"
"env2": "^2.1.1",
"hoek": "^4.1.0",
"joi": "^9.0.4",
"pg": "^6.1.0"
},
"scripts": {
"test": "tape ./test/**/*.test.js",
Expand Down
Loading

0 comments on commit 88b5368

Please sign in to comment.