orm
Version:
NodeJS Object-relational mapping
367 lines (308 loc) • 9.18 kB
JavaScript
var _ = require("lodash");
var Promise = require("bluebird");
var pg = require("pg");
var Query = require("sql-query").Query;
var shared = require("./_shared");
var utils = require("./_utils");
var DDL = require("../DDL/SQL");
exports.Driver = Driver;
var switchableFunctions = {
pool: {
connect: function (cb) {
this.db.connect(function (err, client, done) {
if (!err) {
done();
}
cb(err);
});
},
execSimpleQuery: function (query, cb) {
if (this.opts.debug) {
require("../../Debug").sql('postgres', query);
}
this.db.connect(function (err, client, done) {
if (err) {
return cb(err);
}
client.query(query, function (err, result) {
done();
if (err) {
cb(err);
} else {
cb(null, result.rows);
}
});
});
return this;
}
},
client: {
connect: function (cb) {
this.db.connect(cb);
},
execSimpleQuery: function (query, cb) {
if (this.opts.debug) {
require("../../Debug").sql('postgres', query);
}
this.db.query(query, function (err, result) {
if (err) {
cb(err);
} else {
cb(null, result.rows);
}
});
return this;
}
}
};
function Driver(config, connection, opts) {
var functions = switchableFunctions.client;
this.dialect = 'postgresql';
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 {
if (this.config.query && this.config.query.ssl) {
config.ssl = true;
this.config = _.extend(this.config, config);
// } else {
// this.config = _.extend(this.config, config);
// this.config = config.href || config;
}
pg.types.setTypeParser(20, Number);
if (opts.pool) {
functions = switchableFunctions.pool;
this.db = new pg.Pool(this.config);
} else {
this.db = new pg.Client(this.config);
}
}
_.extend(this.constructor.prototype, functions);
this.constructor.prototype.execSimpleQueryAsync = Promise.promisify(this.constructor.prototype.execSimpleQuery);
this.aggregate_functions = [
"ABS", "CEIL", "FLOOR", "ROUND",
"AVG", "MIN", "MAX",
"LOG", "EXP", "POWER",
"ACOS", "ASIN", "ATAN", "COS", "SIN", "TAN",
"RANDOM", "RADIANS", "DEGREES",
"SUM", "COUNT",
"DISTINCT"
];
}
_.extend(Driver.prototype, shared, DDL);
Driver.prototype.on = function(ev, cb) {
if (ev == "error") {
this.db.on("error", cb);
}
return this;
};
Driver.prototype.ping = function (cb) {
this.execSimpleQuery("SELECT * FROM pg_stat_activity LIMIT 1", function () {
return cb();
});
return this;
};
Driver.prototype.close = function (cb) {
this.db.end();
if (typeof cb == "function") cb();
return;
};
Driver.prototype.getQuery = function () {
return this.query;
};
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);
}
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();
this.execSimpleQuery(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();
this.execSimpleQuery(q, cb);
};
Driver.prototype.insert = function (table, data, keyProperties, cb) {
var q = this.query.insert().into(table).set(data).build();
this.execSimpleQuery(q + " RETURNING *", function (err, results) {
if (err) {
return cb(err);
}
var i, ids = {}, prop;
if (keyProperties) {
for (i = 0; i < keyProperties.length; i++) {
prop = keyProperties[i];
// Zero is a valid value for an ID column
ids[prop.name] = results[0][prop.mapsTo] !== undefined ? results[0][prop.mapsTo] : null;
}
}
return cb(null, ids);
});
};
Driver.prototype.update = function (table, changes, conditions, cb) {
var q = this.query.update().into(table).set(changes).where(conditions).build();
this.execSimpleQuery(q, cb);
};
Driver.prototype.remove = function (table, conditions, cb) {
var q = this.query.remove().from(table).where(conditions).build();
this.execSimpleQuery(q, cb);
};
Driver.prototype.clear = function (table, cb) {
var q = "TRUNCATE TABLE " + this.query.escapeId(table);
this.execSimpleQuery(q, cb);
};
Driver.prototype.valueToProperty = function (value, property) {
var customType, v;
switch (property.type) {
case "object":
if (typeof value == "object" && !Buffer.isBuffer(value)) {
break;
}
try {
value = JSON.parse(value);
} catch (e) {
value = null;
}
break;
case "point":
if (typeof value == "string") {
var m = value.match(/\((\-?[\d\.]+)[\s,]+(\-?[\d\.]+)\)/);
if (m) {
value = { x : parseFloat(m[1], 10) , y : parseFloat(m[2], 10) };
}
}
break;
case "date":
if (_.isDate(value) && this.config.timezone && this.config.timezone != 'local') {
var tz = convertTimezone(this.config.timezone);
// shift local to UTC
value.setTime(value.getTime() - (value.getTimezoneOffset() * 60000));
if (tz !== false) {
// shift UTC to timezone
value.setTime(value.getTime() - (tz * 60000));
}
}
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;
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 "object":
if (value !== null && !Buffer.isBuffer(value)) {
value = new Buffer(JSON.stringify(value));
}
break;
case "date":
if (_.isDate(value) && this.config.timezone && this.config.timezone != 'local') {
var tz = convertTimezone(this.config.timezone);
// shift local to UTC
value.setTime(value.getTime() + (value.getTimezoneOffset() * 60000));
if (tz !== false) {
// shift UTC to timezone
value.setTime(value.getTime() + (tz * 60000));
}
}
break;
case "point":
return function () {
return "POINT(" + value.x + ', ' + value.y + ")";
};
break;
default:
customType = this.customTypes[property.type];
if (customType && 'propertyToValue' in customType) {
value = customType.propertyToValue(value);
}
}
return value;
};
utils.promisifyFunctions(Driver.prototype, ['ping', '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;
}