Skip to content

Commit

Permalink
Test conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
1602 committed Jul 16, 2016
1 parent 195966c commit 0067b23
Show file tree
Hide file tree
Showing 14 changed files with 936 additions and 642 deletions.
32 changes: 32 additions & 0 deletions lib/adapter.js
@@ -0,0 +1,32 @@
'use strict';

const createInformationSchema = require('./information-schema');
const queryWrapper = require('./query-wrapper');

const update = require('./update');
const insert = require('./insert');
const remove = require('./delete');
const lookup = require('./lookup-by-id');
const search = require('./lookup-by-query');
const schema = require('./schema-operations');

module.exports = function createSQLite3Adapter(client) {
const db = queryWrapper(client);
const informationSchema = createInformationSchema();

const adapter = Object.assign(
{ name: 'sqlite3' },
{ disconnect: cb => client.close(cb) },
{ isActual: cb => cb(null, false) },
{ define: spec => informationSchema.registerModel(spec.model.modelName, spec) },
update(informationSchema, db),
insert(informationSchema, db),
remove(informationSchema, db),
lookup(informationSchema, db),
search(informationSchema, db),
schema(informationSchema, db)
);

return adapter;
};

24 changes: 24 additions & 0 deletions lib/connection.js
@@ -0,0 +1,24 @@
'use strict';

const sqlite3 = require('sqlite3');

module.exports = function(settings) {

let Database = null;

switch (settings.type) {
case 'cached':
Database = sqlite3.cached.Database;
break;
case 'normal':
default:
Database = sqlite3.Database;
break;
case 'verbose':
Database = sqlite3.verbose().Database;
}

return new Database(settings.database);

};

24 changes: 24 additions & 0 deletions lib/delete.js
@@ -0,0 +1,24 @@
'use strict';

module.exports = function(initializeSchema, db) {

const tableName = initializeSchema.tableName;
const command = db.command;

return {
destroy,
destroyAll
};

function destroy(model, id) {
const sql = `DELETE FROM ${ tableName(model) } WHERE \`id\` = ?`;

return command(sql, [ id ]);
}

function destroyAll(model) {
return command(`DELETE FROM ${ tableName(model) }`);
}

};

155 changes: 155 additions & 0 deletions lib/information-schema.js
@@ -0,0 +1,155 @@
'use strict';

module.exports = function() {

const models = {};

return {
registerModel,
getModelNames,
getModel: name => models[name],
castForDb,
castFromDb,
castObjectFromDb,
escapeKey,
dataType,
propertiesSQL,
tableName,
tableNameUnescaped
};

function tableName(model) {
return escapeKey(tableNameUnescaped(model));
}

function tableNameUnescaped(model) {
return models[model].model.tableName;
}

function getModelNames() {
return Object.keys(models);
}

function escapeKey(key) {
return `\`${key}\``;
}

function registerModel(model, spec) {
models[model] = spec;
}

function castForDb(model, key, value) {
const prop = models[model].properties[key];
let val = value;

if (prop && prop.type.name === 'JSON') {
return JSON.stringify(val);
}

if (val && val.constructor.name === 'Object') {
const operator = Object.keys(val)[0];
val = val[operator];
if (operator === 'between') {
return castForDb(model, key, val[0]) +
' AND ' +
castForDb(model, key, val[1]);
} else if (operator === 'inq' || operator === 'nin') {
if (!(val.propertyIsEnumerable('length')) && typeof val === 'object' && typeof val.length === 'number') { //if value is array
for (let i = 0; i < val.length; i++) {
val[i] = `"${ val[i] }"`;
}
return val.join(',');
}
return val;
}
}

if (!prop || 'undefined' === typeof val) return val;
if (prop.type.name === 'Number') return val;
if (val === null) return 'NULL';
if (prop.type.name === 'Date') {
if (!val) return 'NULL';
if (!val.toUTCString) {
val = new Date(val);
}
return val;
}

if (prop.type.name === 'Boolean') return val ? 1 : 0;
return val.toString();
return value;
}

function castObjectFromDb(model, obj) {
if (!obj) {
return null;
}

return Object.keys(obj)
.reduce((result, key) => {
result[key] = castFromDb(model, key, obj[key]);
return result;
}, {});
}

function castFromDb(model, key, value) {
const props = models[model].properties;
let val = value;
if (typeof val === 'undefined' || val === null) {
return;
}
if (props[key]) {
switch (props[key].type.name) {
case 'JSON':
val = JSON.parse(val);
break;
case 'Date':
val = new Date(parseInt(val));
break;
case 'Boolean':
val = Boolean(val);
break;
}
}
return val;
}

function dataType(property) {
switch (property.type.name) {
case 'String':
return 'VARCHAR(' + (property.limit || 255) + ')';
case 'Text':
case 'JSON':
return 'TEXT';
case 'Number':
return 'INT(11)';
case 'Date':
return 'DATETIME';
case 'Boolean':
return 'TINYINT(1)';
}
}

function propertiesSQL(model) {
const sql = ['`id` INTEGER PRIMARY KEY'];
Object.keys(models[model].properties).forEach(function(prop) {
if (prop === 'id') return;
sql.push('`' + prop + '` ' + propertySettingsSQL(model, prop));
});
return sql.join(',\n ');
}

function propertySettingsSQL(model, prop) {
const p = models[model].properties[prop];
return dataType(p) +
//// In case in the future support user defined PK, un-comment the following:
// (p.primaryKey === true ? ' PRIMARY KEY' : '') +
// (p.primaryKey === true && p.autoIncrement === true ? ' AUTOINCREMENT' : '') +
(p.allowNull === false || p['null'] === false ? ' NOT NULL' : ' NULL') +
(p.unique === true ? ' UNIQUE' : '') +
(typeof p.default === 'number' ? ' DEFAULT ' + p.default :'') +
(typeof p.default === 'string' ? ' DEFAULT \'' + p.default + '\'' :'');
}

};

66 changes: 66 additions & 0 deletions lib/insert.js
@@ -0,0 +1,66 @@
'use strict';

module.exports = function(informationSchema, db) {

const {
tableName,
castForDb,
escapeKey,
} = informationSchema;

const command = db.command;

return {
updateOrCreate,
create
};

function buildInsertSpec(model, data) {
const table = tableName(model);
const keys = Object.keys(data);
const queryParams = keys.map(key => castForDb(model, key, data[key]));
const fields = keys.map(key => escapeKey(key));
const marks = new Array(keys.length).fill('?').join(', ');

return {
table,
fields,
marks,
queryParams
};
}

function create(model, data) {
const {
table,
fields,
marks,
queryParams
} = buildInsertSpec(model, data);

const sql = `INSERT INTO ${ table } (${ fields }) VALUES (${ marks })`;

return command(sql, queryParams)
.then(r => r.meta.lastID);
}

function updateOrCreate(model, data) {
const {
table,
fields,
marks,
queryParams
} = buildInsertSpec(model, data);

const sql = `INSERT OR REPLACE
INTO ${ table } ( ${ fields } ) VALUES ( ${ marks } )`;

return command(sql, queryParams)
.then(r => {
data.id = r.meta.lastID;
return data;
});
}

};

43 changes: 43 additions & 0 deletions lib/lookup-by-id.js
@@ -0,0 +1,43 @@
'use strict';

const assert = require('assert');

module.exports = function(informationSchema, db) {

const {
tableName,
castObjectFromDb
} = informationSchema;

return {
find,
exists
};

function exists(model, id) {
assert(id, 'Required "id" argument is missing');

const table = tableName(model);
const sql = `SELECT 1 as found FROM ${table} WHERE id = ? LIMIT 1`;

return db.queryOne(sql, [ id ])
.then(r => Boolean(r && r.found === 1));
}

function find(model, id) {
assert(id, 'Required "id" argument is missing');

const table = tableName(model);
const sql = `SELECT * FROM ${table} WHERE id = ? LIMIT 1`;

return db.queryOne(sql, [ id ])
.then(data => {
if (data) {
data.id = id;
}
return castObjectFromDb(model, data);
});
}

};

0 comments on commit 0067b23

Please sign in to comment.