From ca38da6d3bb7da4e337b060ebad0216524a8c438 Mon Sep 17 00:00:00 2001 From: Unitech Date: Fri, 26 Oct 2018 16:20:41 +0200 Subject: [PATCH 1/2] add rqlite support --- examples/odm-rqlite.js | 56 ++++++ lib/Drivers/DML/rqlite.js | 381 ++++++++++++++++++++++++++++++++++++++ package.json | 3 +- 3 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 examples/odm-rqlite.js create mode 100644 lib/Drivers/DML/rqlite.js diff --git a/examples/odm-rqlite.js b/examples/odm-rqlite.js new file mode 100644 index 00000000..6215cdf7 --- /dev/null +++ b/examples/odm-rqlite.js @@ -0,0 +1,56 @@ +var orm = require(".."); + +orm.connect("rqlite://localhost:4005", function (err, db) { + if (err) throw err; + + var Person = db.define("person", { + name : String, + surname : String, + age : Number, // FLOAT + male : Boolean, + continent : [ "Europe", "America", "Asia", "Africa", "Australia", "Antarctica" ], // ENUM type + photo : Buffer, // BLOB/BINARY + data : Object // JSON encoded + }, { + methods: { + fullName: function () { + return this.name + ' ' + this.surname; + } + }, + validations: { + age: orm.enforce.ranges.number(15, undefined, "under-age") + } + }); + + // add the table to the database + db.sync(function(err) { + if (err) throw err; + + // add a row to the person table + Person.create({ name: "TOTO", surname: "Doe", age: 27, data: { hair_color: 'red'} }, function(err) { + if (err) throw err; + + // query the person table by surname + Person.find({ name: "TOTO" }, function (err, people) { + // SQL: "SELECT * FROM person WHERE surname = 'Doe'" + if (err) throw err; + + + console.log(people[0].data) + // var items = people.map(function (m) { + // return m.serialize() + // }) + + + //console.log(people[0].id) + //console.log(people.values()); + console.log("First person: %s, age %d", people[0].fullName(), people[0].age); + + people[0].age = 16; + people[0].save(function (err) { + // err.msg == "under-age"; + }); + }); + }); + }); +}); diff --git a/lib/Drivers/DML/rqlite.js b/lib/Drivers/DML/rqlite.js new file mode 100644 index 00000000..ceded491 --- /dev/null +++ b/lib/Drivers/DML/rqlite.js @@ -0,0 +1,381 @@ +var _ = require("lodash"); +var util = require("util"); +var rqlite = require('rqlite-connect') +var Query = require("sql-query").Query; +var shared = require("./_shared"); +var DDL = require("../DDL/SQL"); +var Promise = require("bluebird"); + +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 = {}; + + this.db = new rqlite.Client([`http://${config.host}:${config.port}`]); + + 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) { + return this; +}; + +Driver.prototype.connect = function (cb) { + process.nextTick(cb); +}; + +Driver.prototype.close = function (cb) { + if (typeof cb == "function") process.nextTick(cb); +}; + +Driver.prototype.getQuery = function () { + return this.query; +}; + +Driver.prototype.execSimpleQuery = function (query, cb) { + this.db.query(query) + .then((res) => { + cb(null, res) + }) + .catch((err) => { + cb(err) + }) +}; + +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.query(q) + .then((res) => { + cb(null, res) + }) + .catch((err) => { + cb(err) + }) +}; + +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.query(q) + .then((res) => { + cb(null, res) + }) + .catch((err) => { + cb(err) + }) +}; + +Driver.prototype.insert = function (table, data, keyProperties, cb) { + var q = this.query.insert() + .into(table) + .set(data) + .build(); + + this.db.query(q) + .then((res) => { + if (!keyProperties) return cb(null); + + var i, ids = {}, prop; + + if (keyProperties.length == 1 && keyProperties[0].type == 'serial') { + this.db.query("SELECT last_insert_rowid() AS last_row_id") + .then(function (row) { + 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); + } + }) + .catch((err) => { + cb(err) + }) +}; + +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.query(q) + .then((res) => { + cb(null, res) + }) + .catch((err) => { + cb(err) + }) +}; + +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.query(q) + .then((res) => { + cb(null, res) + }) + .catch((err) => { + cb(err) + }) +}; + +Driver.prototype.clear = function (table, cb) { + var debug = this.opts.debug; + + this.execQuery("DELETE FROM ??", [table], function (err) { + if (err) return cb(err); + + this.execQuery("DELETE FROM ?? WHERE NAME = ?", ['sqlite_sequence', table], cb); + }.bind(this)); +}; + +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; +}; + +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; +} diff --git a/package.json b/package.json index ff29df8d..a4738526 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,8 @@ "lodash": "4.17.10", "path-is-absolute": "1.0.1", "sql-ddl-sync": "0.3.14", - "sql-query": "0.1.27" + "sql-query": "0.1.27", + "rqlite-connect": "latest" }, "devDependencies": { "chalk": "2.4.1", From 74e5b7e0fe267a19afd49739f9a4db46d71a2740 Mon Sep 17 00:00:00 2001 From: Unitech Date: Sun, 4 Nov 2018 18:47:22 +0100 Subject: [PATCH 2/2] fix read only / write sql commands --- lib/Drivers/DML/rqlite.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Drivers/DML/rqlite.js b/lib/Drivers/DML/rqlite.js index ceded491..435c1851 100644 --- a/lib/Drivers/DML/rqlite.js +++ b/lib/Drivers/DML/rqlite.js @@ -53,7 +53,7 @@ Driver.prototype.getQuery = function () { }; Driver.prototype.execSimpleQuery = function (query, cb) { - this.db.query(query) + this.db.exec(query) .then((res) => { cb(null, res) }) @@ -155,7 +155,7 @@ Driver.prototype.insert = function (table, data, keyProperties, cb) { .set(data) .build(); - this.db.query(q) + this.db.exec(q) .then((res) => { if (!keyProperties) return cb(null); @@ -193,7 +193,7 @@ Driver.prototype.update = function (table, changes, conditions, cb) { require("../../Debug").sql('sqlite', q); } - this.db.query(q) + this.db.exec(q) .then((res) => { cb(null, res) }) @@ -212,7 +212,7 @@ Driver.prototype.remove = function (table, conditions, cb) { require("../../Debug").sql('sqlite', q); } - this.db.query(q) + this.db.exec(q) .then((res) => { cb(null, res) })