Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
936 additions
and
642 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
|
||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) }`); | ||
} | ||
|
||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 + '\'' :''); | ||
} | ||
|
||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
}); | ||
} | ||
|
||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); | ||
} | ||
|
||
}; | ||
|
Oops, something went wrong.