UNPKG

10.4 kBJavaScriptView Raw
1var _ = require("underscore");
2var DataLayer = require("./DataLayer");
3
4var 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
20function isOperator(val){
21 return _.some(operatorsRE, function(re){
22 return re.test(val);
23 });
24}
25
26function 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
54module.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