Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SQL Generation #31

Merged
merged 49 commits into from
Sep 29, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
0f46477
Add skeleton of plugin and example hapi server. Issue #30
eliasmalik Sep 23, 2016
70b3e00
Add env2 for DB config: issue #34
eliasmalik Sep 23, 2016
80522d4
add hapi-postgres-connection as a plugin to example server #33
eliasmalik Sep 23, 2016
e834b0b
use pg instead of hapi-postgres-connection because we need access to …
eliasmalik Sep 23, 2016
228ebc4
update sql queries in plugin setup
eliasmalik Sep 26, 2016
b3c23d1
add first pass of sql data map
eliasmalik Sep 26, 2016
c435d44
[WIP] add module file for generating sql, and corresponding test file
eliasmalik Sep 26, 2016
5821399
:heavy_plus_sign: mapper for creating sql columns
jrans Sep 26, 2016
e059f22
change field-names to align with Joi API; moved away from JSON schema…
eliasmalik Sep 26, 2016
da01305
write and test init method of sqlGen module to make query to create t…
eliasmalik Sep 26, 2016
3cb409a
write and test function to generate update SQL queries for arbitrary …
eliasmalik Sep 26, 2016
3110924
rename previous 'update' method to 'insert' (since that's what it doe…
eliasmalik Sep 26, 2016
9c5fb29
delete dead line
eliasmalik Sep 26, 2016
a45b93f
:heavy_plus_sign: config validator
jrans Sep 26, 2016
31ce282
Merge branch 'sql-generation' of https://github.com/dwyl/abase into s…
jrans Sep 26, 2016
431e786
change module inputs to inject table_name directly instead of entire …
eliasmalik Sep 26, 2016
9ee0209
write and test sql generator for SELECT queries
eliasmalik Sep 26, 2016
5cd2536
write and test SQL generator function for DELETE queries
eliasmalik Sep 26, 2016
e8dd68a
Merge branch 'sql-generation' of https://github.com/dwyl/abase into s…
eliasmalik Sep 26, 2016
ad3139b
migrate to new interface, expecting an options object instead to conf…
eliasmalik Sep 26, 2016
5b08cf6
add where clause to select method
eliasmalik Sep 26, 2016
aff1574
:bug: allow extra keys on field config #39
jrans Sep 26, 2016
0f670c8
:art: :fire: refactor out test fixture
jrans Sep 26, 2016
35f96ad
Merge branch 'sql-generation' of https://github.com/dwyl/abase into s…
jrans Sep 26, 2016
3296cab
:heavy_plus_sign: Expose database methods #11
jrans Sep 27, 2016
743656a
Fix tests
eliasmalik Sep 27, 2016
f6a61b5
Merge branch 'master' into sql-generation
eliasmalik Sep 27, 2016
b2d42f6
:memo: Update readme on test config
jrans Sep 27, 2016
b7c8d8d
Merge branch 'sql-generation' of https://github.com/dwyl/abase into s…
jrans Sep 27, 2016
f3b5da6
delete .eslintrc file, b/c will be using goodparts package v. soon
eliasmalik Sep 27, 2016
866aae6
add utils module to help with DRYing out the sql_gen module
eliasmalik Sep 27, 2016
e11d697
only drop table if it exists, to avoid error
eliasmalik Sep 27, 2016
a8f20c0
DRY out sql_gen
eliasmalik Sep 27, 2016
4b16cda
Merge branch 'sql-generation' of https://github.com/dwyl/abase into s…
eliasmalik Sep 27, 2016
22f8643
symlink the good parts eslint config for now, because we'd need a plu…
eliasmalik Sep 28, 2016
208b982
:art: :bug: Change confgig validator to throw #39
jrans Sep 28, 2016
d4f8fea
fixing merge conflicts
samhstn Sep 28, 2016
a57d5d3
fixing linting errors
samhstn Sep 28, 2016
cd802d4
change eslint back to goodparts
eliasmalik Sep 28, 2016
d9eefed
auto fix linter errors
eliasmalik Sep 28, 2016
ae66662
fix linter errors
eliasmalik Sep 28, 2016
cc62b23
add config.env to gitignore
eliasmalik Sep 28, 2016
ea3daca
Merge branch 'master' into goodparts
eliasmalik Sep 28, 2016
9b05448
fixing linting
samhstn Sep 28, 2016
19889da
bring branch coverage back up to 100%
eliasmalik Sep 28, 2016
2148f3f
add istanbul YAML
eliasmalik Sep 28, 2016
273bc9c
add postgres to travis to fix failing tests
eliasmalik Sep 28, 2016
7389e59
create travis test database before script execution
eliasmalik Sep 28, 2016
281c883
:art: Change ordering of require
jrans Sep 28, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules
coverage
npm-debug.log
.eslintrc*
config.env
11 changes: 11 additions & 0 deletions .istanbul.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
verbose: false
instrumentation:
root: ./src
excludes: []
include-all-sources: true
check:
global:
branches : 100
functions : 100
lines : 100
statements : 100
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@ language: node_js
node_js:
- "6"
script: npm run coverage
before_script:
- psql -c 'CREATE DATABASE travis_ci_test;' -U postgres
after_script:
- npm run codecov
services:
- postgresql
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,11 @@ a well-managed PostgreSQL cluster copes very well.
> If you're still Curious or Worried about scaling PostgreSQL?
see: https://www.citusdata.com
> Want to model the network of people as a graph? https://github.com/cayleygraph/cayley

## Running the Project

For the tests to work you will need to have the url to a test database in a `config.env` file as followed:
```
TEST_DATABASE_URL=psql://localhost:5432/testdb
```
**Note** before tests are run the tables may be removed from the database so don't keep your family photos there.
File renamed without changes.
22 changes: 22 additions & 0 deletions examples/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

var Hapi = require('hapi');
var Abase = require('../lib/index.js');
var server = new Hapi.Server();
var config = require('./config.json');

require('env2')('config.env');

server.connection({ port: process.env.PORT || 8888 });

server.register({
register: Abase, options: config
}, function (err) {
if (err) {
throw err;
}

server.start(function () {
server.log('Server started');
});
});
18 changes: 9 additions & 9 deletions lib/add_default_values.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
'use strict';

/**
* Initialse with the default values the schema if they are not defined Yet
* Write schema in the file if the default values are added
* @param {object} schema - the user schema object
* @return {boolean} - any changes apply to the schema
*/
var defaultSchema = {
email: {
type: 'email'
},
password: {
type: 'password'
}
email: { type: 'email' },
password: { type: 'password' }
};

module.exports = function (schema) {
var anyChanges = false;
Object.keys(defaultSchema).forEach(function(field) {
if(!schema.hasOwnProperty(field)){

Object.keys(defaultSchema).forEach(function (field) {
if (!schema.hasOwnProperty(field)) {
schema[field] = defaultSchema[field];
anyChanges = true;
};
}
});

return anyChanges;
};
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);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like a comment here explaining this regex

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WIll do!

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

purely out of curiosity, why do these lines need to be disabled for eslint?

Copy link
Contributor

@eliasmalik eliasmalik Sep 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nelsonic because there's a rule that says max of 2 chained method calls per line. Bit annoying in this case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, lame. although for git diff purposes splitting over more lines is more review-able... (fine for now)

fields: Joi.object().pattern(dbNameRegEx, fieldSchema).required() // eslint-disable-line
});

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

module.exports.dbNameRegEx = dbNameRegEx;
27 changes: 27 additions & 0 deletions lib/create_table_map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'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 || {};

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

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

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

var methods = { init: function (client, config, _, cb) {
configValidator(config);

return client.query(sqlGen.init(config), cb);
} };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this pass the linter?
(thought it would require the init to be on a new line and these closing brackets too... if not. fair enough...)


['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);
};
});

module.exports = methods;
24 changes: 13 additions & 11 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
'use strict';

var addDefaultValues = require('./add_default_values.js');
var loadSchema = require('./load_schema.js');
var updateSchema = require('./update_schema.js');
var fs = require('fs');

module.exports = function (server, options, next) {
if (!options.hasOwnProperty('user_schema_path')) {
return next('Error: user_schema_path is not defined, please define a user_schema_path');
return next(new Error('user_schema_path is not defined'));
}

loadSchema(options.user_schema_path, function(errorLoad, schema) {
return loadSchema(options.user_schema_path, function (errorLoad, schema) {
if (errorLoad) {
return next(errorLoad);
}

if (addDefaultValues(schema)) {
updateSchema(options.user_schema_path, schema, function(errorUpdateSchema) {
return next(errorUpdateSchema);
});
} else {
if (!addDefaultValues(schema)) {
return next();
}

return updateSchema(
options.user_schema_path,
schema,
function (errorUpdateSchema) {
return next(errorUpdateSchema);
});
});
};

module.exports.attributes = {
name: 'Abase'
};
module.exports.attributes = { name: 'Abase' };
22 changes: 16 additions & 6 deletions lib/load_schema.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
'use strict';

var fs = require('fs');

/**
* load the shema user
* load the schema user
* @param {sting} schemaPath - the file path of the schema
* @param {function} cb - callback with error and the schema object
* @returns {void}
*/
var fs = require('fs');

module.exports = function (schemaPath, cb) {
fs.readFile(schemaPath, function (err, data) {
var schema;

if (err) {
return cb('Error: sorry impossible to read the file at ' + schemaPath);
}

try {
var schema = JSON.parse(data);
schema = JSON.parse(data);
} catch (parseError) {
return cb('Error: the schema user file contains unconventianal type, please make sure an schema object is defined');
return cb(
'Error: the schema user file contains unconventianal type,'
+ 'please make sure an schema object is defined'
);
}
return cb(undefined, schema);

return cb(null, schema);
});
};
124 changes: 124 additions & 0 deletions lib/sql_gen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
'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(', ')),
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];
};
13 changes: 8 additions & 5 deletions lib/update_schema.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
'use strict';

var fs = require('fs');

/**
* Update the schema json file
* @param {string} user_schema_path, the path of the schema file
* @param {string} userSchemaPath, the path of the schema file
* @param {object} schema - the schema user object
* @param {function} cb - callback with error
* @returns {void}
*/

var fs = require('fs');
module.exports = function (user_schema_path, schema, cb) {
fs.writeFile(user_schema_path, JSON.stringify(schema), 'utf8', cb);
module.exports = function (userSchemaPath, schema, cb) {
fs.writeFile(userSchemaPath, JSON.stringify(schema), 'utf8', cb);
};
Loading