Skip to content

Commit

Permalink
Merge pull request #14 from dwyl/id-generation
Browse files Browse the repository at this point in the history
Id generation
  • Loading branch information
nelsonic authored Nov 15, 2016
2 parents 75f9988 + e35f05f commit bfbad44
Show file tree
Hide file tree
Showing 12 changed files with 78 additions and 32 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ The schema is in align with the requirements made by [abase]((https://github.com

The schema must be an object (or an array of objects for multiple tables) of the form: `{ tableName, fields }`.

`fields` is of the form `{ [afieldName]: { type, rest: optional }`
`fields` is of the form `{ [fieldName]: { type, rest: optional }`

Table and field names must be valid postgres table and column names. (non empty, alphanumeric, no leading number, less than 64)

Expand All @@ -102,6 +102,7 @@ Each field must be given a type prop. Data/joi types we support:
| `number` | `DOUBLE PRECISION` or `BIGINT` | set `integer: true` for latter |
| `string` | `VARCHAR(80 or max)` | `80` default, set `max: 123` as you like for more/less |
|boolean | BOOLEAN | |
| `id` | VARCHAR(36) | **warning** if using this type do not add this field to your insert, we will generate an id on each insertion (Generated with [aguid](https://github.com/dwyl/aguid)) |

More information can be inferred from `lib/config_validator.js`

Expand All @@ -110,6 +111,7 @@ Each field can also take more properties most of which will be used by other aba
| Property | Notes |
|---|---|
| `unique` | set to `true` if you want column unique |
| `primaryKey` | set to `true` if you want this field to act as your primary key (note only one field allowed!) |
| `max`, `timestamp`, `integer` | see types table above for relevance |

##### Under the hood
Expand Down
3 changes: 2 additions & 1 deletion example_schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
max: 20,
unique: true
},
dob: { type: 'date' }
dob: { type: 'date' },
id: { type: 'id' }
}
};
6 changes: 5 additions & 1 deletion lib/config_validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ var mapObj = require('./create_table_map.js').mapObj;
var dbNameRegEx = /^[A-Za-z_]\w{0,62}$/;
var fieldTypes = Object.keys(mapObj);

var typeSchema = Joi.any()
.valid(fieldTypes)
.required()
;
var fieldSchema = Joi.object()
.keys({ type: Joi.any().valid(fieldTypes).required() })
.keys({ type: typeSchema })
.unknown()
;
var tableSchema = Joi.object().keys({
Expand Down
6 changes: 6 additions & 0 deletions lib/create_table_map.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
'use strict';

var mapObj = {
id: function () {
return 'VARCHAR(36)';
},
number: function (opts) {
return opts.integer ? 'BIGINT' : 'DOUBLE PRECISION';
},
Expand All @@ -21,6 +24,9 @@ function mapper (name, type, options, tableName) {
var opts = options || {};
var constraints = '';

if (opts.primaryKey) {
constraints += ' CONSTRAINT ' + tableName + '_pk PRIMARY KEY';
}
if (opts.unique) {
constraints += ' CONSTRAINT ' + tableName + '_' + name + '_unique UNIQUE';
}
Expand Down
4 changes: 2 additions & 2 deletions lib/db_handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ methods.flush = function (client, config, options, cb) {
};

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

return client.query.apply(client, args);
};
Expand Down
40 changes: 28 additions & 12 deletions lib/sql_gen.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use strict';

var aguid = require('aguid');

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


function paramStr (columns, opts) {
Expand All @@ -20,7 +22,7 @@ function paramStr (columns, opts) {
function processWhere (where, query, values) {
var keys = Object.keys(where);
var conds = paramStr(keys, { offset: values.length, assign: true });
var vals = _.values(where, keys);
var vals = utils.values(where, keys);

return {
query: query.concat('WHERE').concat(conds.join(' AND ')),
Expand All @@ -35,7 +37,7 @@ exports.init = function init (config) {

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

return mapper(key, type, opts, tableName);
});
Expand All @@ -47,7 +49,7 @@ exports.init = function init (config) {
};


exports.select = function select (options) {
exports.select = function select (_, options) {
var columns = options.select || ['*'];
var values = [];
var query = ['SELECT']
Expand All @@ -68,28 +70,42 @@ exports.select = function select (options) {
};


exports.insert = function insert (options) {
exports.insert = function insert (config, options) {
var fields = options.fields || {};
var columns = Object.keys(fields);
var values = _.values(fields, columns);
var tableConfig = []
.concat(config)
.filter(function (table) {
return table.tableName === options.tableName;
})[0]
;
var idFields = Object.keys(tableConfig.fields).filter(function (field) {
return tableConfig.fields[field].type === 'id';
});
var ids = idFields.map(function () {
return aguid();
});
var normalColumns = Object.keys(fields);
var values = utils.values(fields, normalColumns).concat(ids);
var columns = normalColumns.concat(idFields);
var params = paramStr(columns);

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

return [query, values];
};


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

var query = ['UPDATE "' + options.tableName + '"']
.concat('SET')
Expand All @@ -108,7 +124,7 @@ exports.update = function update (options) {
};


exports.delete = function _delete (options) {
exports.delete = function del (_, options) {
var query = ['DELETE FROM "' + options.tableName + '"'];
var values = [];
var result = processWhere(options.where, query, values);
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "abase-db",
"version": "0.2.0",
"version": "0.2.3",
"description": "A little experiment in defining models in Joi and creating PostgreSQL Tables",
"main": "lib/",
"devDependencies": {
Expand All @@ -11,6 +11,7 @@
"hapi": "^15.1.1"
},
"dependencies": {
"aguid": "^1.0.4",
"env2": "^2.1.1",
"hoek": "^4.1.0",
"joi": "^9.0.4",
Expand All @@ -23,7 +24,6 @@
"cover": "node_modules/.bin/istanbul cover node_modules/.bin/tape './test/*.test.js'",
"check-coverage": "node_modules/.bin/istanbul check-coverage --statements 100 --functions 100 --lines 100 --branches 100",
"example": "node example/"

},
"repository": {
"type": "git",
Expand Down
9 changes: 9 additions & 0 deletions test/create_table_map.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,12 @@ test('Create Table Mapper Function w/ unique option', function (t) {
);
t.end();
});

test('Create Table Mapper Function w/ primaryKey option', function (t) {
t.equal(
mapper('email', 'string', { primaryKey: true }, 'test_table'),
'email VARCHAR(80) CONSTRAINT test_table_pk PRIMARY KEY',
'pk constraint added to column'
);
t.end();
});
4 changes: 3 additions & 1 deletion test/db_handlers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ test('db.init multiple tables', function (t) {

test('db.insert & default select w custom where', function (t) {
db.insert(client, schema, { fields: testInsert, tableName: testTab })
.then(function () {
.then(function (res) {
t.ok(res.rows[0].id, 'id returned in response');

return db.select(client, schema, {
where: { dob: '2001-09-27' },
tableName: testTab
Expand Down
2 changes: 1 addition & 1 deletion test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
var test = require('tape');
var Hapi = require('hapi');
var path = require('path');
var plugin = require('../lib/index.js');

var plugin = require('../lib/index.js');

test('Can register DB plugin with `schemaPath` option', function (t) {
var server = new Hapi.Server();
Expand Down
1 change: 1 addition & 0 deletions test/istantiate_db.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ test('db bound .insert adds to DB :: promise interface', function (t) {
});
})
.then(function (result) {
t.equal(result.rows[0].id.length, 36, 'guid generated');
t.equal(result.rows[0].email, testInsert.email, 'Email matches');
t.end();
})
Expand Down
27 changes: 16 additions & 11 deletions test/sql_gen.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@ tape('::init - generate SQL to create a table if none exists', function (t) {
'CREATE TABLE IF NOT EXISTS "user_data" ('
+ 'email VARCHAR(80), '
+ 'username VARCHAR(20) CONSTRAINT user_data_username_unique UNIQUE, '
+ 'dob DATE'
+ 'dob DATE, '
+ 'id VARCHAR(36)'
+ ')',
'Create table query generation from config object'
);
t.end();
});

tape('::select - generate SQL to select columns from a table', function (t) {
var query = sqlGen.select({
var query = sqlGen.select(null, {
tableName: schema.tableName,
select: ['email', 'dob']
});
Expand All @@ -43,7 +44,7 @@ tape('::select - generate SQL to select columns from a table', function (t) {
});

tape('::select - gen. SQL to select cols from table w/ where', function (t) {
var query = sqlGen.select({
var query = sqlGen.select(null, {
tableName: schema.tableName,
select: ['email', 'dob'],
where: { foo: 'bar' }
Expand All @@ -60,12 +61,13 @@ tape('::select - gen. SQL to select cols from table w/ where', function (t) {

tape('::insert - generate SQL to insert a column into a table', function (t) {
var query = sqlGen.insert(
{ tableName: schema.tableName, fields: {} },
{ tableName: schema.tableName, fields: { email: '[email protected]' } }
);

t.equal(
query[0],
'INSERT INTO "user_data" (email) VALUES ($1)',
'INSERT INTO "user_data" (email) VALUES ($1) RETURNING ()',
'Generate parameterised query'
);
t.deepEqual(
Expand All @@ -77,11 +79,14 @@ tape('::insert - generate SQL to insert a column into a table', function (t) {
});

tape('::insert - generate SQL to insert blank col into table', function (t) {
var query = sqlGen.insert({ tableName: schema.tableName });
var query = sqlGen.insert(
{ tableName: schema.tableName, fields: {} },
{ tableName: schema.tableName }
);

t.equal(
query[0],
'INSERT INTO "user_data" () VALUES ()',
'INSERT INTO "user_data" () VALUES () RETURNING ()',
'Generate query for blank line'
);
t.deepEqual(
Expand All @@ -93,7 +98,7 @@ tape('::insert - generate SQL to insert blank col into table', function (t) {
});

tape('::update - generate SQL to update a column in a table', function (t) {
var query = sqlGen.update({
var query = sqlGen.update(null, {
tableName: schema.tableName,
fields: { email: '[email protected]' }
});
Expand All @@ -112,7 +117,7 @@ tape('::update - generate SQL to update a column in a table', function (t) {
});

tape('::update - generate SQL to update no fields of column', function (t) {
var query = sqlGen.update({ tableName: schema.tableName });
var query = sqlGen.update(null, { tableName: schema.tableName });

t.equal(
query[0],
Expand All @@ -128,7 +133,7 @@ tape('::update - generate SQL to update no fields of column', function (t) {
});

tape('::update - gen. SQL to update a col in table w/ where', function (t) {
var query = sqlGen.update({
var query = sqlGen.update(null, {
tableName: schema.tableName,
fields: { email: '[email protected]' },
where: { foo: 'bar' }
Expand All @@ -148,7 +153,7 @@ tape('::update - gen. SQL to update a col in table w/ where', function (t) {
});

tape('::delete should generate SQL to delete a row from a table', function (t) {
var query = sqlGen.delete({
var query = sqlGen.delete(null, {
tableName: schema.tableName,
where: { username: 'bob' }
});
Expand All @@ -167,7 +172,7 @@ tape('::delete should generate SQL to delete a row from a table', function (t) {
});

tape('::delete should gen SQL to delete row w/ multiple where', function (t) {
var query = sqlGen.delete({
var query = sqlGen.delete(null, {
tableName: schema.tableName,
where: { username: 'bob', dob: '20/04/1988' }
});
Expand Down

0 comments on commit bfbad44

Please sign in to comment.