Completed
Push — master ( 856555...3d58fe )
by
unknown
02:04
created

sql.js ➔ executeFromClause   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
dl 0
loc 27
rs 8.8571
c 0
b 0
f 0
nop 2

1 Function

Rating   Name   Duplication   Size   Complexity  
A sql.js ➔ ... ➔ ??? 0 8 3
1
import {parse} from 'node-sqlparser'
2
import {Promise} from 'bluebird'
3
import path from 'path'
4
import {
5
  coreUtils,
6
  config,
7
  Manager,
8
  cmsData
9
} from '../../'
10
11
/**
12
 * take a string and json to escape sql character and convert to sql like syntax
13
 *
14
 * Example: escapeAbeValuesFromStringRequest('select title from ./ where `abe_meta.template`=`{{article}}`', {article: "test"})
15
 *
16
 * Return string: select title from ___abe_dot______abe_dot______abe___ where  `abe_meta.template`=`test`
17
 * 
18
 * 
19
 * @param  {String} str      raw abe request sql string
20
 * @param  {Object} jsonPage json object of post
21
 * @return {String}          escaped string
22
 */
23
export function escapeAbeValuesFromStringRequest(str, jsonPage) {
0 ignored issues
show
Unused Code introduced by
The parameter jsonPage is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
24
  var matchFrom = /from .(.*?) /
25
  var matchVariable = /{{([a-zA-Z]*)}}/
26
27
  var matchFromExec = matchFrom.exec(str)
28
  if(matchFromExec != null && matchFromExec[1] != null) {
29
30
    var fromMatch
31
    var toReplace = matchFromExec[1]
32
    while (fromMatch = matchVariable.exec(toReplace)) {
33
      try {
34
        var value = eval('jsonPage.' + fromMatch[1])
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
35
        if(value != null) {
36
          toReplace = toReplace.replace('{{' + fromMatch[1] + '}}', value)
37
        }else {
38
          toReplace = toReplace.replace('{{' + fromMatch[1] + '}}', '')
39
        }
40
      }catch(e) {
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
41
      }
42
    }
43
44
    str = str.replace(matchFromExec[1], toReplace)
45
  }
46
47
  var from = /from ([\S\s]+)/.exec(str)
48
49
  var matches = from
50
  if(matches[1]) {
51
    var res = matches[1]
52
    var splitAttr = [' where ', ' order by ', ' limit ', ' WHERE ', ' ORDER BY ', ' LIMIT ']
53
    for(var i = 0; i < splitAttr.length; i++) {
54
      if(res.indexOf(splitAttr[i]) > -1) {
55
        res = res.substring(0, res.indexOf(splitAttr[i]))
56
      }
57
    }
58
    var escapedFrom = res.replace(/\//g, '___abe___')
59
    escapedFrom = escapedFrom.replace(/\./g, '___abe_dot___')
60
    escapedFrom = escapedFrom.replace(/-/g, '___abe_dash___')
61
    str = str.replace(res, escapedFrom)
62
  }
63
64
  str = str.replace(/``/g, '\'\'')
65
66
  return str
67
}
68
69
/**
70
 * analyse and create an object from request sql string
71
 *
72
 * Example: handleSqlRequest('select title from ./ where `abe_meta.template`=`{{article}}`', {article: "test"})
73
 * 
74
 * @param  {String} str      Sql string request
75
 * @param  {Object} jsonPage json of post
76
 * @return {Object}          {type, columns, from, where, string, limit, orderby}
77
 */
78
export function handleSqlRequest(str, jsonPage) {
79
  var req = escapeAbeValuesFromStringRequest(str, jsonPage)
80
  var request = parse(req)
81
  var reconstructSql = ''
82
83
  // SQL TYPE
84
  var type = ''
85
  if(request.type != null) {
86
    type = request.type
87
  }
88
  reconstructSql += `${type} `
89
90
  // SQL COLUMNS
91
  var columns = []
92
  if(request.columns != null) {
93
    if(request.columns === '*') {
94
      columns.push('*')
95
    }else {
96
      Array.prototype.forEach.call(request.columns, (item) => {
97
        columns.push(item.expr.column)
98
      })
99
    }
100
  }
101
  reconstructSql += `${JSON.stringify(columns)} `
102
103
  // SQL FROM
104
  var from = []
105
  if(request.from != null) {
106
107
    Array.prototype.forEach.call(request.from, (item) => {
108
      from.push(item.table)
109
    })
110
  }else {
111
    from.push('*')
112
  }
113
  reconstructSql += `from ${JSON.stringify(from)} `
114
115
  var where = null
116
  if(request.where != null) {
117
    where = request.where
118
  }
119
120
  var limit = -1
121
  if(request.limit != null) {
122
    limit = request.limit[request.limit.length - 1].value
123
  }
124
125
  var orderby = null
126
  if(request.orderby != null && request.orderby.length > 0) {
127
    orderby = {
128
      column: request.orderby[0].expr.column,
129
      type: request.orderby[0].type
130
    }
131
    reconstructSql += `ORDER BY ${orderby.column} ${orderby.type} `
132
  }
133
134
  return {
135
    type: type,
136
    columns: columns,
137
    from: from,
138
    where: where,
139
    string: reconstructSql,
140
    limit: limit,
141
    orderby: orderby
142
  }
143
}
144
145
export function getDataSource(str) {
146
  var res = str.substring(str.indexOf('source=') + 8, str.length)
147
148
  var reg = /([^'"]*=[\s\S]*?}})/g
149
  var matches = res.match(reg)
150
  if(matches != null) {
151
    Array.prototype.forEach.call(matches, (match) => {
152
      res = res.replace(match, '')
153
    })
154
  }else {
155
    res = res.replace('}}', '')
156
  }
157
158
  return res.substring(0, res.length-1)
159
}
160
161
/**
162
 * replaces escaped characters with the right ones
163
 * @param  {String} statement the from clause
164
 * @return {String}           the from sanitized
165
 */
166
export function sanitizeFromStatement(statement){
167
  var from = ''
168
169
  if(statement != null) {
170
    from = statement[0].replace(/___abe_dot___/g, '.')
171
    from = from.replace(/___abe___/g, '/')
172
    from = from.replace(/___abe_dash___/g, '-')
173
  }
174
175
  return from
176
}
177
178
/**
179
 * calculate the directory to analyze from the from clause
180
 * @param  {String} statement the from clause
181
 * @param  {String} tplPath   the path from the template originator
182
 * @return {string}           the directory to analyze
183
 */
184
export function getFromDirectory(statement, tplPath){
185
  var pathFromDir = ''
186
  if(!tplPath){
187
    tplPath = '/'
188
  }
189
190
  if(statement === '' || statement === '*' || statement === '/') {
191
    pathFromDir = path.join(config.root, config.data.url)
192
  }else if(statement === './') {
193
    pathFromDir = path.join(config.root, config.data.url, tplPath)
194
  }else if(statement.indexOf('/') === 0) {
195
    pathFromDir = path.join(config.root, config.data.url, statement)
196
  }else if(statement.indexOf('/') !== 0) {
197
    pathFromDir = path.join(config.root, config.data.url, tplPath, statement)
198
  }
199
200
  return pathFromDir
201
}
202
203
/**
204
 * sort array of files from where clause
205
 *
206
 * @param  {Array} files
207
 * @param  {Object} orderby {orderby: {column: 'date'}} | {orderby: {column: 'random', type: 'ASC'}}
208
 * @return {Array}         sorted array
209
 */
210
export function executeOrderByClause(files, orderby){
211
  if(orderby != null) {
212
    if(orderby.column.toLowerCase() === 'random') {
213
      files = coreUtils.sort.shuffle(files)
214
    }else if(orderby.column.toLowerCase() === 'date') {
215
      if(orderby.type === 'ASC') {
216
        files.sort(coreUtils.sort.byDateAsc)
217
      }else if(orderby.type === 'DESC') {
218
        files.sort(coreUtils.sort.byDateDesc)
219
      }
220
    }
221
  }
222
223
  return files
224
}
225
226
/**
227
 * Check array of files have path that match path statement
228
 *
229
 * executeFromClause(['/'], ['/'])
230
 *
231
 * @param  {Array} statement      paths
232
 * @param  {Array} pathFromClause paths
233
 * @return {Array}                files
234
 */
235
export function executeFromClause(statement, pathFromClause){
236
  var from = sanitizeFromStatement(statement)
237
238
  // if the from clause ends with a dot, we won't recurse the directory analyze
239
  if(from.slice(-1) === '.'){
240
    from = from.slice(0, -1)
241
  }
242
  
243
  var fromDirectory = getFromDirectory(from, pathFromClause)
244
245
  var list = Manager.instance.getList()
246
  var files_array = list.filter((element) => {
247
    if(element.publish) {
248
      if (element.path.indexOf(fromDirectory) > -1) {
249
        return true
250
      }
251
    }
252
    return false
253
  })
254
255
  var publishedValue = []
256
  Array.prototype.forEach.call(files_array, (file) => {
257
    publishedValue.push(file.publish)
258
  })
259
260
  return publishedValue
261
}
262
263
/**
264
 * Execute sql query like to find abe json post that match the query
265
 * 
266
 * @param  {Array} pathQuery of paths
267
 * @param  {String} match     request sql
268
 * @param  {Object} jsonPage  json of post
269
 * @return {Array}           found object that match
270
 */
271
export function execQuery(pathQuery, match, jsonPage) {
272
  var files
273
  var request = handleSqlRequest(cmsData.regex.getAttr(match, 'source'), jsonPage)
274
275
  files = executeFromClause(request.from, pathQuery)
276
  files = executeWhereClause(files, request.where, request.limit, request.columns, jsonPage)
277
  files = executeOrderByClause(files, request.orderby)
278
  return files
279
}
280
281
export function executeQuerySync(pathQuerySync, match, jsonPage) {
282
  return execQuery(pathQuerySync, match, jsonPage)
283
}
284
285
export function executeQuery(pathexecuteQuery, match, jsonPage) {
286
  var p = new Promise((resolve) => {
287
    var res = execQuery(pathexecuteQuery, match, jsonPage)
288
    resolve(res)
289
  }).catch(function(e) {
290
    console.error(e)
291
  })
292
293
  return p
294
}
295
296
/**
297
 * check if a given string an url, string json, file url, abe sql request
298
 * 
299
 * get('http://google.com')
300
 * get('{"test":"test"}')
301
 * get('select * from ../')
302
 * get('test')
303
 * 
304
 * @param  {String} str 
305
 * @return {String} url | request | value | file | other
306
 */
307
export function getSourceType(str) {
308
  if(/http:\/\/|https:\/\//.test(str)) {
309
    return 'url'
310
  }
311
312
  if(/select[\S\s]*?from/.test(str)) {
313
    return 'request'
314
  }
315
316
  try {
317
    JSON.parse(str)
318
    return 'value'
319
  }catch(e) {
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
320
321
  }
322
323
  if(/\.json/.test(str)) {
324
    return 'file'
325
  }
326
327
  return 'other'
328
}
329
330
/**
331
 * return array of post that match sql where statement
332
 *
333
 * Example: handleSqlRequest('select title from ./ where `abe_meta.template`=`article`', {})
334
 *
335
 * @param  {Array} files    
336
 * @param  {Object} wheres   clause
337
 * @param  {Int} maxLimit 
338
 * @param  {Array} columns  sql
339
 * @param  {Object} jsonPage json post
340
 * @return {Array}          of files
341
 */
342
export function executeWhereClause(files, wheres, maxLimit, columns, jsonPage){
343
  if(typeof wheres === 'undefined' || wheres === null) return files
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
344
  var res = []
345
  var limit = 0
346
  var json = {}
347
  var jsonValues = {}
348
349
  for(let file of files) {
350
    if(limit < maxLimit || maxLimit === -1) {
351
      if(wheres != null) {
352
        if(!recurseWhere(wheres, file, jsonPage)) {
353
          json = JSON.parse(JSON.stringify(file))
354
          jsonValues = {}
355
356
          if(columns != null && columns.length > 0 && columns[0] !== '*') {
357
            Array.prototype.forEach.call(columns, (column) => {
358
              if(json[column] != null) {
0 ignored issues
show
Bug introduced by
The variable json is changed as part of the for-each loop for example by JSON.parse(JSON.stringify(file)) on line 353. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
359
                jsonValues[column] = json[column]
0 ignored issues
show
Bug introduced by
The variable jsonValues is changed as part of the for-each loop for example by {} on line 354. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
360
              }
361
            })
362
            jsonValues['abe_meta'] = json['abe_meta']
363
          }else {
364
            jsonValues = json
365
          }
366
367
          res.push(jsonValues)
368
          limit++
369
        }
370
      }
371
    } else {
372
      break
373
    }
374
  }
375
376
  return res
377
}
378
379
/**
380
 * Compare where left and where right clause
381
 * 
382
 * @param  {Object} where           clause
383
 * @param  {Object} jsonDoc         json of current post
384
 * @param  {Object} jsonOriginalDoc json of post to compare
385
 * @return {Object}                 {left: value, right: value}
386
 */
387
export function getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc) {
0 ignored issues
show
Unused Code introduced by
The parameter jsonDoc is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter jsonOriginalDoc is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
388
  var regexIsVariable = /^{{(.*)}}$/
389
  var value = null
390
  var compare = null
391
392
  try {
393
    var variableLeft = where.left.column
394
    var checkIfLeftIsAVariable = regexIsVariable.exec(variableLeft)
395
    if(checkIfLeftIsAVariable != null && checkIfLeftIsAVariable.length > 0) {
396
      variableLeft = checkIfLeftIsAVariable[1]
397
    }
398
    value = eval('jsonDoc.' + variableLeft)
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
399
  }catch(e) {
400
    // console.log('e', e)
401
  }
402
  
403
  if(where.operator === 'IN' || where.operator === 'NOT IN') {
404
    compare = []
405
    Array.prototype.forEach.call(where.right.value, (right) => {
406
      var matchRightVariable = regexIsVariable.exec(right.column)
407
      if(matchRightVariable != null && matchRightVariable.length > 0) {
408
        try {
409
          var jsonOriginalValues = eval('jsonOriginalDoc.' + matchRightVariable[1])
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
410
          Array.prototype.forEach.call(jsonOriginalValues, (jsonOriginalValue) => {
0 ignored issues
show
Unused Code introduced by
The parameter jsonOriginalValue is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
411
            compare.push(eval('jsonOriginalValue.' + where.left.column))
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
412
          })
413
        }catch(e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
414
      }
415
      else{
416
        compare.push(right.column)
417
      }
418
    })
419
  } else {
420
    if(where.right.column != null) {
421
      compare = where.right.column
422
    } else if(where.right.value != null) {
423
      compare = where.right.value
424
    }
425
426
    var matchRightVariable = regexIsVariable.exec(compare)
427
428
    if(matchRightVariable != null && matchRightVariable.length > 0) {
429
      try {
430
        var shouldCompare = eval('jsonOriginalDoc.' + matchRightVariable[1])
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
431
        if(shouldCompare != null) {
432
          compare = shouldCompare
433
        }else {
434
          compare = null
435
        }
436
      }catch(e) {
437
        compare = null
438
      }
439
    }
440
  }
441
442
  return {
443
    left: value,
444
    right: compare
445
  }
446
}
447
448
/**
449
 * Check where.left value that match where operator (=, !=, >, >=, <, <=, LIKE, NOT LIKE, AND, OR, IN, NOT IN)
450
 * if operator AND or OR
451
 * Recurse on where.left and where.right sql clause
452
 *
453
 * 
454
 * @param  {Object} where           clause
455
 * @param  {Object} jsonDoc         json of current post
456
 * @param  {Object} jsonOriginalDoc json of post to compare
457
 * @return {Boolean}                 true if not matching | false if matching
458
 */
459
export function recurseWhere(where, jsonDoc, jsonOriginalDoc) {
460
  var isNotLeftCorrect = false
0 ignored issues
show
Unused Code introduced by
The assignment to variable isNotLeftCorrect seems to be never used. Consider removing it.
Loading history...
461
  var isNotRightCorrect = false
0 ignored issues
show
Unused Code introduced by
The assignment to variable isNotRightCorrect seems to be never used. Consider removing it.
Loading history...
462
  var isNotCorrect = false
463
  var values
464
465
  switch(where.operator) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
466
  case '=':
467
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
468
    isNotCorrect = !(values.left === values.right)
469
    break
470
  case '!=':
471
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
472
    isNotCorrect = !(values.left !== values.right)
473
    break
474
  case '>':
475
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
476
    isNotCorrect = !(values.left > values.right)
477
    break
478
  case '>=':
479
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
480
    isNotCorrect = !(values.left >= values.right)
481
    break
482
  case '<':
483
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
484
    isNotCorrect = !(values.left < values.right)
485
    break
486
  case '<=':
487
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
488
    isNotCorrect = !(values.left <= values.right)
489
    break
490
  case 'LIKE':
491
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
492
    isNotCorrect = !(values.left && values.left.indexOf(values.right) > -1)
493
    break
494
  case 'NOT LIKE':
495
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
496
    isNotCorrect = !(values.left && values.left.indexOf(values.right) === -1)
497
    break
498
  case 'AND':
499
    isNotLeftCorrect = recurseWhere(where.left, jsonDoc, jsonOriginalDoc)
500
    isNotRightCorrect = recurseWhere(where.right, jsonDoc, jsonOriginalDoc)
501
    isNotCorrect = (isNotLeftCorrect || isNotRightCorrect) ? true : false
502
    break
503
  case 'OR':
504
    isNotLeftCorrect = recurseWhere(where.left, jsonDoc, jsonOriginalDoc)
505
    isNotRightCorrect = recurseWhere(where.right, jsonDoc, jsonOriginalDoc)
506
    isNotCorrect = (isNotLeftCorrect && isNotRightCorrect) ? true : false
507
    break
508
  case 'IN':
509
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
510
    isNotCorrect = true
511
    Array.prototype.forEach.call(values.right, (right) => {
512
      if(values.left === right) {
513
        isNotCorrect = false
514
      }
515
    })
516
    break
517
  case 'NOT IN':
518
    values = getWhereValuesToCompare(where, jsonDoc, jsonOriginalDoc)
519
    isNotCorrect = false
520
    Array.prototype.forEach.call(values.right, (right) => {
521
      if(values.left === right) {
522
        isNotCorrect = true
523
      }
524
    })
525
    break
526
  }
527
  return isNotCorrect
528
}