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, mysql){
|
27 | switch(operator.toUpperCase()){
|
28 | case "=":
|
29 | case "!=":
|
30 | case ">":
|
31 | case "<":
|
32 | case "<>":
|
33 | case "<=>":
|
34 | case ">=":
|
35 | case "<=":
|
36 | case "NOT":
|
37 | case "IS":
|
38 | case "IS NOT":
|
39 | case "LIKE":
|
40 | case "NOT LIKE":
|
41 | return [field, operator.toUpperCase(), mysql.escape(values[0])];
|
42 | case "BETWEEN":
|
43 | return Array.isArray(values[0])? [field, "BETWEEN", mysql.escape(values[0][0]), "AND", mysql.escape(values[0][1])]
|
44 | :[field, "BETWEEN", mysql.escape(values[0]), "AND", mysql.escape(values[1])];
|
45 | case "IN":
|
46 | case "NOT IN":
|
47 | default:
|
48 | return [field, operator.toUpperCase(), "(", mysql.escape(Array.isArray(values[0])?values[0]:values ), ")"];
|
49 | }
|
50 | }
|
51 |
|
52 | module.exports = DataLayer.extend("MySQLLayer", {
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 | // SELECT count(distinct p.products_id) as total
|
59 | // FROM products p
|
60 | // LEFT JOIN manufacturers m USING(manufacturers_id)
|
61 | // LEFT JOIN products_description pd using products_id
|
62 | // LEFT JOIN categories c USING (categories_id)
|
63 | // LEFT JOIN products_to_categories p2c USING (products_id)
|
64 | // LEFT JOIN meta_tags_products_description mtpd
|
65 | // ON mtpd.products_id= p2c.products_id
|
66 | // AND mtpd.language_id = 1
|
67 | // WHERE (p.products_status = 1
|
68 | // AND pd.language_id = 1
|
69 | // AND (
|
70 | // (pd.products_name LIKE '%3220%'
|
71 | // OR p.products_model LIKE '%3220%'
|
72 | // OR m.manufacturers_name LIKE '%3220%'
|
73 | // OR (mtpd.metatags_keywords LIKE '%3220%'
|
74 | // AND mtpd.metatags_keywords !='')
|
75 | // OR
|
76 | // (mtpd.metatags_description LIKE '%3220%'
|
77 | // AND mtpd.metatags_description !='')
|
78 | // OR
|
79 | // pd.products_description LIKE '%3220%'
|
80 | // )
|
81 | // )
|
82 | // )
|
83 | // OR (p.products_id=3220)
|
84 |
|
85 | parseArguments: function(args){
|
86 | switch(args.length){
|
87 | case 0: return false;
|
88 | case 1:
|
89 | if(typeof args[0] !== "function") return false;
|
90 | else return [undefined,{},args[0]];
|
91 | case 2:
|
92 | if(typeof args[1] !== "function") return false;
|
93 | else return [args[0],{}, args[1]];
|
94 | case 3:
|
95 | if(typeof args[2] !== "function") return false;
|
96 | else return [args[0],args[1], args[2]];
|
97 | default: return false;
|
98 | }
|
99 | },
|
100 |
|
101 | parseData: function(data, options){
|
102 | var result = {options: {}};
|
103 | var dataFields = _.pick(data, _.keys(this.fields));
|
104 |
|
105 | if(options.count){
|
106 | result.options.fields = "COUNT(*)";
|
107 | }
|
108 | else if(options.fields){
|
109 | result.options.fields = _.intersection(this.publicFields || _.keys(this.fields), options.fields);
|
110 | }
|
111 | else result.options.fields = this.publicFields || "*";
|
112 |
|
113 | if(result.options.fields.length === 0) {
|
114 | result.options.fields = this.publicFields || "*";
|
115 | }
|
116 |
|
117 | if(options.limit){ result.options.limit = "LIMIT "+this.mysql.escape(options.limit); }
|
118 | else{ result.options.limit = ""; }
|
119 |
|
120 | if(options.count === true){
|
121 | result.options.count = ",COUNT(*)";
|
122 | }
|
123 | else result.options.count = "";
|
124 |
|
125 | if(options.order){
|
126 | var orderFields = typeof options.order === "string"?[options.order]:options.order, direction = "";
|
127 | if(_.last(options.order).match(/^(a|de)sc$/i)){
|
128 | direction = options.order.pop();
|
129 | }
|
130 | orderFields = _.intersection(this.publicFields || _.keys(this.fields), orderFields);
|
131 | if(orderFields.length){
|
132 | result.options.order = "ORDER BY "+orderFields.join(", ")+" "+direction.toUpperCase();
|
133 | }
|
134 | else{
|
135 | result.options.order = "";
|
136 | }
|
137 | }
|
138 | else result.options.order = "";
|
139 |
|
140 | if(options.where && typeof options.where !== "string" ){
|
141 | var cond_fields = _.intersection(this.publicFields || _.keys(this.fields), _.keys(options.where));
|
142 | if(cond_fields.length===0) result.options.where = "";
|
143 | else{
|
144 | var conditions = [];
|
145 | var allowed_conditions = _.pick(options.where, cond_fields);
|
146 | for(var cond_field in allowed_conditions){
|
147 | var cond_value = allowed_conditions[cond_field];
|
148 | if(_.some([_.isNumber, _.isString, _.isDate, _.isNull], function(iterator){ return iterator(cond_value); })){
|
149 | conditions.push([cond_field, "=", this.mysql.escape(cond_value)].join(" "));
|
150 | }
|
151 | else if(Array.isArray(cond_value)){
|
152 | if(isOperator(cond_value[0])){
|
153 | var operator = cond_value[0];
|
154 | var rest = cond_value.slice(1);
|
155 | conditions.push(wrapArrayValues(operator, rest, cond_field, this.mysql).join(" "));
|
156 | }
|
157 | else{
|
158 | conditions.push(wrapArrayValues("IN", cond_value, cond_field, this.mysql).join(" "));
|
159 | }
|
160 | }
|
161 | else if(_.isFunction(cond_value)){
|
162 | throw new Error("TODO - computed where condition");
|
163 | }
|
164 | else if(_.isObject(cond_value)){
|
165 | throw new Error("TODO - where condition as object");
|
166 | }
|
167 | else result.options.where = "";
|
168 |
|
169 | }
|
170 | result.options.where = "WHERE "+conditions.join(" AND ");
|
171 | }
|
172 | }
|
173 | else if(typeof options.where == "string"){
|
174 | result.options.where = this.mysql.format(options.where, {options: _.extend({},options,_.pick(this, ["primaryKey", "publicFields", "tableName"])), values: data});
|
175 | }
|
176 | else result.options.where = "";
|
177 |
|
178 | _.defaults(result.options, _.omit(options, ["where", "order", "limit", "fields"]));
|
179 |
|
180 |
|
181 | result.values = data;
|
182 | return result;
|
183 | },
|
184 |
|
185 | create: function(obj, options, cb){
|
186 | var data = this.parseData(obj, options), self = this;
|
187 | data.options.fields = _.intersection(this.publicFields || _.keys(this.fields), _.keys(obj));
|
188 | var valuesTemplate = data.options.fields.map(function(fieldName){return "#"+fieldName}).join(",");
|
189 | this.query("INSERT INTO @tableName (@fields) VALUES ("+valuesTemplate+");", data, function(err, result){
|
190 | if(err) return cb(err);
|
191 | obj[self.primaryKey] = result.insertId;
|
192 | cb(null, obj);
|
193 | });
|
194 | },
|
195 |
|
196 | find: function(pattern, options, cb){
|
197 | if(typeof pattern === "number" || typeof pattern === "string"){
|
198 | options.where = {}, options.limit = 1;
|
199 | options.where[this.primaryKey] = pattern;
|
200 | }
|
201 | else if(Array.isArray(pattern)){
|
202 | options.where = {}, options.limit = pattern.length;
|
203 | options.where[this.primaryKey] = ["IN", pattern];
|
204 | }
|
205 | else{
|
206 | options.where = pattern;
|
207 | }
|
208 | var data = this.parseData(pattern, options), self = this;
|
209 | this.query("SELECT @fields FROM @tableName @where @order @order @limit;", data, function(err, models){
|
210 | // avoid third argument
|
211 | cb(err, models);
|
212 | });
|
213 | },
|
214 |
|
215 | count: function(pattern, options, cb){
|
216 | options.count = true;
|
217 | this.find(pattern, options, function(err, result){
|
218 | if(err) return cb(err);
|
219 | cb(null, result[0]["COUNT(*)"]);
|
220 | });
|
221 | },
|
222 |
|
223 | findOne: function(pattern, options, cb){
|
224 | options.limit = 1;
|
225 | return this.find(pattern, options, cb);
|
226 | },
|
227 |
|
228 | update: function(pattern, options, cb){
|
229 | var self = this;
|
230 | var fields = _.intersection(this.publicFields || _.keys(this.fields), _.keys(pattern));
|
231 | if(fields.length === 0) return cb("Invalid fields to update");
|
232 |
|
233 | if(fields.indexOf(this.primaryKey)!==-1){
|
234 | options.where = _.pick(pattern, [this.primaryKey]);
|
235 | fields = _.without(fields, this.primaryKey);
|
236 | if(Array.isArray(pattern[this.primaryKey])) {
|
237 | options.limit = pattern[this.primaryKey].length;
|
238 | if(!isOperator(options.where[this.primaryKey][0])) {
|
239 |
|
240 | options.where[this.primaryKey] = ["IN", options.where[this.primaryKey]];
|
241 | }
|
242 | }
|
243 | else options.limit = 1;
|
244 | }
|
245 | var data = this.parseData(pattern, options), self = this;
|
246 | var updates = fields.map(function(field){
|
247 | return field +" = #"+field;
|
248 | });
|
249 | data.options.updates = updates;
|
250 |
|
251 | this.query("UPDATE @tableName SET @updates @where @order @limit;", data, function(err, models){
|
252 | // avoid third argument
|
253 | cb(err, models);
|
254 | });
|
255 |
|
256 |
|
257 | },
|
258 |
|
259 | delete: function(pattern, options, cb){
|
260 |
|
261 | if(typeof pattern === "number" || typeof pattern === "string"){
|
262 | options.where = {}, options.limit = 1;
|
263 | options.where[this.primaryKey] = pattern;
|
264 | }
|
265 | else if(Array.isArray(pattern)){
|
266 | options.where = {}, options.limit = pattern.length;
|
267 | options.where[this.primaryKey] = ["IN", pattern];
|
268 | }
|
269 | else{
|
270 | options.where = pattern;
|
271 | }
|
272 |
|
273 | var data = this.parseData(pattern, options), self = this;
|
274 |
|
275 | this.query("DELETE FROM @tableName @where @order @limit;", data, function(err, models){
|
276 | // avoid third argument
|
277 | cb(err, models);
|
278 | });
|
279 | },
|
280 |
|
281 |
|
282 | }, {
|
283 |
|
284 | setupDatabase: function(self, env, name){
|
285 | var Prototype = this;
|
286 | self.setupNode = function(cb){ Prototype.setupTable(self, env, function(err){
|
287 | if(err) return cb(err);
|
288 | env.i.do("log.sys", "DataLayer:mysql", name);
|
289 | cb();
|
290 | }); }
|
291 | },
|
292 |
|
293 | setupTable: function(instance, env, cb){
|
294 | instance.mysql = env.engines.mysql;
|
295 | var _ = require("underscore");
|
296 | instance.query = function(query, data, cb){
|
297 | _.extend(data.options, this.options, {tableName: this.tableName, primaryKey: this.primaryKey});
|
298 | if(data.options.debug){
|
299 | env.i.do("log.debug", ["[ MySQL debug ]",
|
300 | [ " V V V V V V V V V V V V V V V V ","==============================================================================================>>>",
|
301 | data.options.debug,
|
302 | "SQL> "+instance.mysql.format(query, data),
|
303 | "==============================================================================================<<<",""
|
304 | ].join("\n")
|
305 | ]);
|
306 | delete data.options.debug;
|
307 | }
|
308 | return this.mysql.query(query, data, cb);
|
309 | }
|
310 | cb();
|
311 | },
|
312 |
|
313 | extend: function(name, props, statics){
|
314 | this.setMethods(this.prototype, props);
|
315 | return DataLayer.extend.apply(this, arguments);
|
316 | }
|
317 | });
|
318 |
|
319 |
|
320 |
|
\ | No newline at end of file |