orm
Version:
NodeJS Object-relational mapping
382 lines (328 loc) • 10.3 kB
JavaScript
var _ = require("lodash");
var util = require("util");
var sqlite3 = require("sqlite3");
var Query = require("sql-query").Query;
var shared = require("./_shared");
var utils = require("./_utils");
var DDL = require("../DDL/SQL");
exports.Driver = Driver;
function Driver(config, connection, opts) {
this.dialect = 'sqlite';
this.config = config || {};
this.opts = opts || {};
if (!this.config.timezone) {
this.config.timezone = "local";
}
this.query = new Query({ dialect: this.dialect, timezone: this.config.timezone });
this.customTypes = {};
if (connection) {
this.db = connection;
} else {
// on Windows, paths have a drive letter which is parsed by
// url.parse() as the hostname. If host is defined, assume
// it's the drive letter and add ":"
if (process.platform == "win32" && config.host && config.host.match(/^[a-z]$/i)) {
this.db = new sqlite3.Database(decodeURIComponent((config.host ? config.host + ":" : "") + (config.pathname || "")) || ':memory:');
} else {
this.db = new sqlite3.Database(decodeURIComponent((config.host ? config.host : "") + (config.pathname || "")) || ':memory:');
}
}
this.aggregate_functions = [ "ABS", "ROUND",
"AVG", "MIN", "MAX",
"RANDOM",
"SUM", "COUNT",
"DISTINCT" ];
}
_.extend(Driver.prototype, shared, DDL);
Driver.prototype.ping = function (cb) {
process.nextTick(cb);
return this;
};
Driver.prototype.on = function (ev, cb) {
if (ev == "error") {
this.db.on("error", cb);
}
return this;
};
Driver.prototype.connect = function (cb) {
process.nextTick(cb);
};
Driver.prototype.close = function (cb) {
this.db.close();
if (typeof cb == "function") process.nextTick(cb);
};
Driver.prototype.getQuery = function () {
return this.query;
};
Driver.prototype.execSimpleQuery = function (query, cb) {
if (this.opts.debug) {
require("../../Debug").sql('sqlite', query);
}
this.db.all(query, cb);
};
Driver.prototype.find = function (fields, table, conditions, opts, cb) {
var q = this.query.select()
.from(table).select(fields);
if (opts.offset) {
q.offset(opts.offset);
}
if (typeof opts.limit == "number") {
q.limit(opts.limit);
} else if (opts.offset) {
// OFFSET cannot be used without LIMIT so we use the biggest INTEGER number possible
q.limit('9223372036854775807');
}
if (opts.order) {
for (var i = 0; i < opts.order.length; i++) {
q.order(opts.order[i][0], opts.order[i][1]);
}
}
if (opts.merge) {
q.from(opts.merge.from.table, opts.merge.from.field, opts.merge.to.field).select(opts.merge.select);
if (opts.merge.where && Object.keys(opts.merge.where[1]).length) {
q = q.where(opts.merge.where[0], opts.merge.where[1], opts.merge.table || null, conditions);
} else {
q = q.where(opts.merge.table || null, conditions);
}
} else {
q = q.where(conditions);
}
if (opts.exists) {
for (var k in opts.exists) {
q.whereExists(opts.exists[k].table, table, opts.exists[k].link, opts.exists[k].conditions);
}
}
q = q.build();
if (this.opts.debug) {
require("../../Debug").sql('sqlite', q);
}
this.db.all(q, cb);
};
Driver.prototype.count = function (table, conditions, opts, cb) {
var q = this.query.select()
.from(table)
.count(null, 'c');
if (opts.merge) {
q.from(opts.merge.from.table, opts.merge.from.field, opts.merge.to.field);
if (opts.merge.where && Object.keys(opts.merge.where[1]).length) {
q = q.where(opts.merge.where[0], opts.merge.where[1], conditions);
} else {
q = q.where(conditions);
}
} else {
q = q.where(conditions);
}
if (opts.exists) {
for (var k in opts.exists) {
q.whereExists(opts.exists[k].table, table, opts.exists[k].link, opts.exists[k].conditions);
}
}
q = q.build();
if (this.opts.debug) {
require("../../Debug").sql('sqlite', q);
}
this.db.all(q, cb);
};
Driver.prototype.insert = function (table, data, keyProperties, cb) {
var q = this.query.insert()
.into(table)
.set(data)
.build();
if (this.opts.debug) {
require("../../Debug").sql('sqlite', q);
}
this.db.all(q, function (err, info) {
if (err) return cb(err);
if (!keyProperties) return cb(null);
var i, ids = {}, prop;
if (keyProperties.length == 1 && keyProperties[0].type == 'serial') {
this.db.get("SELECT last_insert_rowid() AS last_row_id", function (err, row) {
if (err) return cb(err);
ids[keyProperties[0].name] = row.last_row_id;
return cb(null, ids);
});
} else {
for (i = 0; i < keyProperties.length; i++) {
prop = keyProperties[i];
// Zero is a valid value for an ID column
ids[prop.name] = data[prop.mapsTo] !== undefined ? data[prop.mapsTo] : null;
}
return cb(null, ids);
}
}.bind(this));
};
Driver.prototype.update = function (table, changes, conditions, cb) {
var q = this.query.update()
.into(table)
.set(changes)
.where(conditions)
.build();
if (this.opts.debug) {
require("../../Debug").sql('sqlite', q);
}
this.db.all(q, cb);
};
Driver.prototype.remove = function (table, conditions, cb) {
var q = this.query.remove()
.from(table)
.where(conditions)
.build();
if (this.opts.debug) {
require("../../Debug").sql('sqlite', q);
}
this.db.all(q, cb);
};
Driver.prototype.clear = function (table, cb) {
var debug = this.opts.debug;
var self = this;
this.execQuery("DELETE FROM ??", [table], function (err) {
if (err) return cb(err);
self.execQuery("SELECT count(*) FROM ?? WHERE type=? AND name=?;", ['sqlite_master', 'table', 'sqlite_sequence'], function (err, data) {
if (err) return cb(err);
if (data[0] && data[0]['count(*)'] === 1) {
self.execQuery("DELETE FROM ?? WHERE NAME = ?", ['sqlite_sequence', table], cb);
} else {
cb();
}
});
});
};
Driver.prototype.valueToProperty = function (value, property) {
var v, customType;
switch (property.type) {
case "boolean":
value = !!value;
break;
case "object":
if (typeof value == "object" && !Buffer.isBuffer(value)) {
break;
}
try {
value = JSON.parse(value);
} catch (e) {
value = null;
}
break;
case "number":
if (typeof value == 'string') {
switch (value.trim()) {
case 'Infinity':
case '-Infinity':
case 'NaN':
value = Number(value);
break;
default:
v = parseFloat(value);
if (Number.isFinite(v)) {
value = v;
}
}
}
break;
case "integer":
if (typeof value == 'string') {
v = parseInt(value);
if (Number.isFinite(v)) {
value = v;
}
}
break;
case "date":
if (typeof value == 'string') {
if (value.indexOf('Z', value.length - 1) === -1) {
value = new Date(value + 'Z');
} else {
value = new Date(value);
}
if (this.config.timezone && this.config.timezone != 'local') {
var tz = convertTimezone(this.config.timezone);
if (tz !== false) {
// shift UTC to timezone
value.setTime(value.getTime() - (tz * 60000));
}
}else {
// shift local to UTC
value.setTime(value.getTime() + (value.getTimezoneOffset() * 60000));
}
}
break;
default:
customType = this.customTypes[property.type];
if(customType && 'valueToProperty' in customType) {
value = customType.valueToProperty(value);
}
}
return value;
};
Driver.prototype.propertyToValue = function (value, property) {
var customType;
switch (property.type) {
case "boolean":
value = (value) ? 1 : 0;
break;
case "object":
if (value !== null) {
value = JSON.stringify(value);
}
break;
case "date":
if (this.config.query && this.config.query.strdates) {
if (value instanceof Date) {
var year = value.getUTCFullYear();
var month = value.getUTCMonth() + 1;
if (month < 10) {
month = '0' + month;
}
var date = value.getUTCDate();
if (date < 10) {
date = '0' + date;
}
var strdate = year + '-' + month + '-' + date;
if (property.time === false) {
value = strdate;
break;
}
var hours = value.getUTCHours();
if (hours < 10) {
hours = '0' + hours;
}
var minutes = value.getUTCMinutes();
if (minutes < 10) {
minutes = '0' + minutes;
}
var seconds = value.getUTCSeconds();
if (seconds < 10) {
seconds = '0' + seconds;
}
var millis = value.getUTCMilliseconds();
if (millis < 10) {
millis = '0' + millis;
}
if (millis < 100) {
millis = '0' + millis;
}
strdate += ' ' + hours + ':' + minutes + ':' + seconds + '.' + millis + '000';
value = strdate;
}
}
break;
default:
customType = this.customTypes[property.type];
if(customType && 'propertyToValue' in customType) {
value = customType.propertyToValue(value);
}
}
return value;
};
utils.promisifyFunctions(Driver.prototype, ['ping', 'execSimpleQuery', 'find', 'count', 'insert', 'update', 'remove', 'clear']);
Object.defineProperty(Driver.prototype, "isSql", {
value: true
});
function convertTimezone(tz) {
if (tz == "Z") return 0;
var m = tz.match(/([\+\-\s])(\d\d):?(\d\d)?/);
if (m) {
return (m[1] == '-' ? -1 : 1) * (parseInt(m[2], 10) + ((m[3] ? parseInt(m[3], 10) : 0) / 60)) * 60;
}
return false;
}