1 | var _ = require("underscore");
|
2 | var DataLayer = require("./DataLayer");
|
3 |
|
4 | var operatorsRE = [
|
5 | /^=$/i,
|
6 | /^between$/i,
|
7 | /^not\sbetween$/i,
|
8 | /^[<>]=?$/i,
|
9 | /^<=?>$/i,
|
10 | /^!=$/i,
|
11 | /^in$/i,
|
12 | /^not\sin$/i,
|
13 | /^is$/i,
|
14 | /^is\snot$/i,
|
15 | /^like$/i,
|
16 | /^not\slike$/i,
|
17 | /^not$/i,
|
18 | ];
|
19 |
|
20 | function isOperator(val){
|
21 | return _.some(operatorsRE, function(re){
|
22 | return re.test(val);
|
23 | });
|
24 | }
|
25 |
|
26 | function wrapArrayValues(operator, values, field, postgres){
|
27 | field = field.split(".").map(postgres.escapeIdentifier).join(".");
|
28 | switch(operator.toUpperCase()){
|
29 | case "=":
|
30 | case "!=":
|
31 | case ">":
|
32 | case "<":
|
33 | case "<>":
|
34 | case "<=>":
|
35 | case ">=":
|
36 | case "<=":
|
37 | case "NOT":
|
38 | case "IS":
|
39 | case "IS NOT":
|
40 | case "LIKE":
|
41 | case "NOT LIKE":
|
42 | return [field, operator.toUpperCase(), postgres.escapeLiteral(values[0].toString())];
|
43 | case "BETWEEN":
|
44 | return Array.isArray(values[0])? [field, "BETWEEN", postgres.escapeLiteral(values[0][0].toString()), "AND", postgres.escapeLiteral(values[0][1].toString())]
|
45 | :[field, "BETWEEN", postgres.escapeLiteral(values[0].toString()), "AND", postgres.escapeLiteral(values[1].toString())];
|
46 | case "IN":
|
47 | case "NOT IN":
|
48 | default:
|
49 | var vals = (Array.isArray(values[0])? values[0]: values).map(function(v){return v.toString();}).map(postgres.escapeLiteral).join(",");
|
50 | return [field, operator.toUpperCase(), "(", vals, ")"];
|
51 | }
|
52 | }
|
53 |
|
54 | module.exports = DataLayer.extend("PostgresLayer", {
|
55 |
|
56 | parseArguments: function(args){
|
57 | switch(args.length){
|
58 | case 0: return false;
|
59 | case 1:
|
60 | if(typeof args[0] !== "function") return false;
|
61 | else return [undefined,{},args[0]];
|
62 | case 2:
|
63 | if(typeof args[1] !== "function") return false;
|
64 | else return [args[0],{}, args[1]];
|
65 | case 3:
|
66 | if(typeof args[2] !== "function") return false;
|
67 | else return [args[0],args[1], args[2]];
|
68 | default: return false;
|
69 | }
|
70 | },
|
71 |
|
72 | parseData: function(data, options){
|
73 | var result = {options: {}};
|
74 | var dataFields = _.pick(data, _.keys(this.fields));
|
75 |
|
76 | if(options.count){
|
77 | result.options.fields = "COUNT(*)";
|
78 | }
|
79 | else if(options.fields){
|
80 | result.options.fields = _.intersection(this.publicFields || _.keys(this.fields), options.fields).map(this.postgres.escapeIdentifier);
|
81 | }
|
82 | else result.options.fields = this.publicFields?this.publicFields.map(this.postgres.escapeIdentifier) : "*";
|
83 |
|
84 | if(result.options.fields.length === 0) {
|
85 | result.options.fields = this.publicFields || "*";
|
86 | }
|
87 |
|
88 | if(options.limit){
|
89 | if(Array.isArray(options.limit)){
|
90 | result.options.limit = "LIMIT "+this.postgres.escapeLiteral(options.limit[0].toString())+" OFFSET "+ this.postgres.escapeLiteral(options.limit[1].toString());
|
91 | }
|
92 | else result.options.limit = "LIMIT "+this.postgres.escapeLiteral(options.limit.toString());
|
93 | }
|
94 | else{ result.options.limit = ""; }
|
95 |
|
96 | if(options.order){
|
97 | var orderFields = typeof options.order === "string"?[options.order]:options.order, direction = "";
|
98 | if(typeof options.order === "string") options.order = [options.order];
|
99 | if(_.last(options.order).match(/^(a|de)sc$/i)){
|
100 | direction = options.order.pop();
|
101 | }
|
102 | orderFields = _.intersection(this.publicFields || _.keys(this.fields), orderFields);
|
103 | if(orderFields.length){
|
104 | result.options.order = "ORDER BY "+orderFields.map(this.postgres.escapeIdentifier).join(", ")+" "+direction.toUpperCase();
|
105 | }
|
106 | else{
|
107 | result.options.order = "";
|
108 | }
|
109 | }
|
110 | else result.options.order = "";
|
111 |
|
112 | if(options.where && typeof options.where !== "string" ){
|
113 | var cond_fields = _.intersection(this.publicFields || _.keys(this.fields), _.keys(options.where));
|
114 | if(cond_fields.length===0) result.options.where = "";
|
115 | else{
|
116 | var conditions = [];
|
117 | var allowed_conditions = _.pick(options.where, cond_fields);
|
118 | for(var field in allowed_conditions){
|
119 | var cond_field = field;
|
120 | var cond_value = allowed_conditions[field];
|
121 | if(_.some([_.isNumber, _.isString, _.isDate, _.isNull], function(iterator){ return iterator(cond_value); })){
|
122 | conditions.push([(cond_field.split(".").map(this.postgres.escapeIdentifier).join(".")), "=", this.postgres.escapeLiteral(cond_value.toString())].join(" "));
|
123 | }
|
124 | else if(Array.isArray(cond_value)){
|
125 | if(isOperator(cond_value[0])){
|
126 | var operator = cond_value[0];
|
127 | var rest = cond_value.slice(1);
|
128 | conditions.push(wrapArrayValues(operator, rest, cond_field, this.postgres).join(" "));
|
129 | }
|
130 | else{
|
131 | conditions.push(wrapArrayValues("IN", cond_value, cond_field, this.postgres).join(" "));
|
132 | }
|
133 | }
|
134 | else if(_.isFunction(cond_value)){
|
135 | throw new Error("TODO - computed where condition");
|
136 | }
|
137 | else if(_.isObject(cond_value)){
|
138 | throw new Error("TODO - where condition as object");
|
139 | }
|
140 | else result.options.where = "";
|
141 |
|
142 | }
|
143 | result.options.where = "WHERE "+conditions.join(" AND ");
|
144 | }
|
145 | }
|
146 | else if(typeof options.where == "string"){
|
147 | result.options.where = this.postgres.format(options.where, {options: _.extend({},options,_.pick(this, ["primaryKey", "publicFields", "tableName"])), values: data});
|
148 | }
|
149 | else result.options.where = "";
|
150 |
|
151 | _.defaults(result.options, _.omit(options, ["where", "order", "limit", "fields"]));
|
152 |
|
153 |
|
154 | result.values = data;
|
155 | return result;
|
156 | },
|
157 |
|
158 | create: function(obj, options, cb){
|
159 | var data = this.parseData(obj, options), self = this;
|
160 | data.options.fields = _.intersection(this.publicFields || _.keys(this.fields), _.keys(obj));
|
161 | var valuesTemplate = data.options.fields.map(function(fieldName){return "#"+fieldName}).join(",");
|
162 | this.query("INSERT INTO %tableName (%fields) VALUES ("+valuesTemplate+") RETURNING %primaryKey;", data, function(err, result){
|
163 | if(err) return cb(err);
|
164 | obj[self.primaryKey] = result.rows[0][self.primaryKey];
|
165 | cb(null, obj);
|
166 | });
|
167 | },
|
168 |
|
169 | find: function(pattern, options, cb){
|
170 | if(typeof pattern === "number" || typeof pattern === "string"){
|
171 | options.where = {}, options.limit = 1;
|
172 | options.where[this.primaryKey] = pattern;
|
173 | }
|
174 | else if(Array.isArray(pattern)){
|
175 | options.where = {}, options.limit = pattern.length;
|
176 | options.where[this.primaryKey] = ["IN", pattern];
|
177 | }
|
178 | else{
|
179 | options.where = pattern;
|
180 | }
|
181 | var data = this.parseData(pattern, options), self = this;
|
182 | this.query("SELECT @fields FROM %tableName @where @order @limit;", data, function(err, models){
|
183 | // avoid third argument
|
184 | cb(err, models? models.rows : models);
|
185 | });
|
186 | },
|
187 |
|
188 | count: function(pattern, options, cb){
|
189 | options.count = true;
|
190 | return this.find(pattern, options, function(err, result){
|
191 | if(err) return cb(err);
|
192 | cb(null, result[0].count);
|
193 | });
|
194 | },
|
195 |
|
196 | findOne: function(pattern, options, cb){
|
197 | options.limit = 1;
|
198 | return this.find(pattern, options, cb);
|
199 | },
|
200 |
|
201 | update: function(pattern, options, cb){
|
202 | var self = this;
|
203 | var fields = _.intersection(this.publicFields || _.keys(this.fields), _.keys(pattern));
|
204 | if(fields.length === 0) return cb("Invalid fields to update");
|
205 |
|
206 | if(fields.indexOf(this.primaryKey)!==-1){
|
207 | options.where = _.pick(pattern, [this.primaryKey]);
|
208 | fields = _.without(fields, this.primaryKey);
|
209 | if(Array.isArray(pattern[this.primaryKey])) {
|
210 | options.limit = pattern[this.primaryKey].length;
|
211 | if(!isOperator(options.where[this.primaryKey][0])) {
|
212 |
|
213 | options.where[this.primaryKey] = ["IN", options.where[this.primaryKey]];
|
214 | }
|
215 | }
|
216 | else options.limit = 1;
|
217 | }
|
218 | var data = this.parseData(pattern, options), self = this;
|
219 | var updates = fields.map(function(field){
|
220 | return self.postgres.escapeIdentifier(field) +" = #"+field;
|
221 | });
|
222 | data.options.updates = updates;
|
223 | this.query("UPDATE %tableName SET @updates @where;", data, cb);
|
224 | },
|
225 |
|
226 | delete: function(pattern, options, cb){
|
227 |
|
228 | if(typeof pattern === "number" || typeof pattern === "string"){
|
229 | options.where = {}, options.limit = 1;
|
230 | options.where[this.primaryKey] = pattern;
|
231 | }
|
232 | else if(Array.isArray(pattern)){
|
233 | options.where = {}, options.limit = pattern.length;
|
234 | options.where[this.primaryKey] = ["IN", pattern];
|
235 | }
|
236 | else{
|
237 | options.where = pattern;
|
238 | }
|
239 | var data = this.parseData(pattern, options), self = this;
|
240 | this.query("DELETE FROM %tableName @where;", data, cb);
|
241 | },
|
242 |
|
243 | }, {
|
244 |
|
245 | setupDatabase: function(self, env, name){
|
246 | var Prototype = this;
|
247 | self.setupNode = function(cb){ Prototype.setupTable(self, env, function(err){
|
248 | if(err) return cb(err);
|
249 | env.i.do("log.sys", "DataLayer:postgres", name);
|
250 | cb();
|
251 | }); }
|
252 | },
|
253 |
|
254 | setupTable: function(instance, env, cb){
|
255 | instance.postgres = env.engines.postgres;
|
256 | var _ = require("underscore");
|
257 | instance.query = function(query, data, cb){
|
258 | _.extend(data.options, this.options, {tableName: this.tableName, primaryKey: this.primaryKey});
|
259 | if(data.options.debug){
|
260 | env.i.do("log.debug", ["[ Postgres debug ]",
|
261 | [ " V V V V V V V V V V V V V V V V ","==============================================================================================>>>",
|
262 | data.options.debug,
|
263 | "postgres> "+instance.postgres.format(query, data),
|
264 | "==============================================================================================<<<",""
|
265 | ].join("\n")
|
266 | ]);
|
267 | delete data.options.debug;
|
268 | }
|
269 |
|
270 | return this.postgres.query(this.postgres.format(query, data), function(err, results){
|
271 | instance.postgres.release();
|
272 | cb(err, results);
|
273 | });
|
274 | }
|
275 | cb();
|
276 | },
|
277 |
|
278 | extend: function(name, props, statics){
|
279 | this.setMethods(this.prototype, props);
|
280 | return DataLayer.extend.apply(this, arguments);
|
281 | }
|
282 | });
|
283 |
|
284 |
|
285 |
|
\ | No newline at end of file |