const _ = require('lodash')
const SqlString = require('sqlstring')
class LeoSQL {
/**
* **处理请求**
*
* @constructor
* @param {Object} request 请求对象,一般为`qs.parse(QueryString)`
*/
constructor(request) {
this.request = request
const operators = ['_eq', '_ne', '_gt', '_lt', '_gte', '_lte', '_have', '_has', '_start', '_end']
const ignore = ['_q', '_method', '_table', '_column', '_value', '_logic', '_desc', '_asc', '_limit', '_page']
const where = { column: [], items: [] }
_.forIn(this.request, (value, key) => {
if(ignore.indexOf(key) === -1) {
const index = key.lastIndexOf('_')
const operator = key.substring(index)
if(operators.indexOf(operator) > -1) {
const column = key.substring(0, index)
where.column.push(column)
switch(operator) {
case '_eq': { where.items.push([SqlString.escapeId(column), 'IN (', SqlString.escape(value), ')'].join(' ')); break }
case '_ne': { where.items.push([SqlString.escapeId(column), 'NOT IN (', SqlString.escape(value), ')'].join(' ')); break }
case '_gt': { where.items.push([SqlString.escapeId(column), '>', SqlString.escape(value)].join(' ')); break }
case '_lt': { where.items.push([SqlString.escapeId(column), '<', SqlString.escape(value)].join(' ')); break }
case '_gte': { where.items.push([SqlString.escapeId(column), '>=', SqlString.escape(value)].join(' ')); break }
case '_lte': { where.items.push([SqlString.escapeId(column), '<=', SqlString.escape(value)].join(' ')); break }
case '_have': { where.items.push([SqlString.escapeId(column), 'like', SqlString.escape('%' + value + '%')].join(' ')); break }
case '_has': { where.items.push([SqlString.escapeId(column), 'like', SqlString.escape('%' + value + '%')].join(' ')); break }
case '_start': { where.items.push([SqlString.escapeId(column), 'like', SqlString.escape(value + '%')].join(' ')); break }
case '_end': { where.items.push([SqlString.escapeId(column), 'like', SqlString.escape('%' + value)].join(' ')); break }
}
} else {
where.column.push(key)
where.items.push([SqlString.escapeId(key), 'IN (', SqlString.escape(value), ')'].join(' '))
}
}
})
where.column = _.uniq(where.column)
// 组装条件逻辑
const logic = this.request._logic
const logicOr = []
const logicAnd = []
where.items.forEach((item, index) => {
// if(index) {
let _logic = Array.isArray(logic) ? logic[index] : logic
_logic = _logic ? _logic.toUpperCase() : 'AND'
_logic = ['AND', 'OR'].indexOf(_logic) === -1 ? 'AND' : _logic
if(_logic === 'OR') {
logicOr.push(item)
} else {
logicAnd.push(item)
}
// }
})
const whereAll = []
!_.isEmpty(logicOr) && whereAll.push('(' + logicOr.join(' OR ') + ')')
!_.isEmpty(logicAnd) && whereAll.push(logicAnd.join(' AND '))
const sql = _.isEmpty(whereAll) ? '' : 'WHERE ' + whereAll.join(' AND ')
this.where = { column: where.column, sql: sql }
}
/**
* SQL操作方法,默认`SELECT`
*
* @prop {String}
* @memberof LeoSQL
*/
get method() {
return this.request._method || 'SELECT'
}
/**
* 表,数据表名称
*
* @prop {String|Array}
* @memberof LeoSQL
*/
get table() {
return this.request._table
}
/**
* 被写入的列的列表,用于`UPDATE`和`INSERT`方法
*
* @prop {String|Array}
* @memberof LeoSQL
*/
get column() {
return this.request._column
}
/**
* 被写入的值的列表,用于`UPDATE`和`INSERT`方法
*
* @prop {String|Array}
* @memberof LeoSQL
*/
get value() {
return this.request._value
}
/**
* 条件包含的列名称列表
*
* @prop {String|Array}
* @memberof LeoSQL
*/
get whereColumn() {
return this.where.column
}
/**
* 请求的偏移量,相当于 `LIMIT m, n` 的 `n`
*
* @return {Number}
* @memberof LeoSQL
*/
get limit() {
const _limit = parseInt(this.request._limit)
const limit = _limit > 0 ? _limit : 0
return limit
}
/**
* 请求的起始位置,相当于 `LIMIT m, n` 的 `m`
*
* @return {Number}
* @memberof LeoSQL
*/
get offset() {
const _page = parseInt(this.request._page)
const page = _page > 0 ? _page : 1
const limit = this.limit
const offset = (page - 1) * limit
return offset
}
/**
* `WHERE`的`SQL`语句片段
*
* @return {String}
* @memberof LeoSQL
*/
get whereSql() {
return this.where.sql
}
/**
* `LIMIT`的`SQL`语句片段
*
* @return {String}
* @memberof LeoSQL
*/
get limitSql() {
const limit = this.limit ? 'LIMIT ' + [this.offset, this.limit].join() : ''
return limit
}
/**
* `ORDER BY`的`SQL`语句片段
*
* @todo 区分先后顺序
* @return {String}
* @memberof LeoSQL
*/
get orderSql() {
const orders = []
if(!_.isEmpty(this.request._asc)) {
const asc = Array.isArray(this.request._asc) ? this.request._asc : [this.request._asc]
_.each(asc, item => orders.push([SqlString.escapeId(item), 'ASC'].join(' ')))
}
if(!_.isEmpty(this.request._desc)) {
const desc = Array.isArray(this.request._desc) ? this.request._desc : [this.request._desc]
_.each(desc, item => orders.push([SqlString.escapeId(item), 'DESC'].join(' ')))
}
if(_.isEmpty(orders)) return ''
return 'ORDER BY ' + orders.join()
}
/**
* `INSERT`的`SQL`语句
*
* @return {String}
* @memberof LeoSQL
*/
get insert() {
if(_.isEmpty(this.column)) return ''
const sql = [
'INSERT INTO ',
SqlString.escapeId(this.table),
'(', SqlString.escapeId(this.column), ') VALUES (',
SqlString.escape(this.value), ')'].join(' ')
return sql
}
/**
* `SELECT`的`SQL`语句
*
* @return {String}
* @memberof LeoSQL
*/
get select() {
const select = this.column ? ('SELECT ' + SqlString.escapeId(this.column)) : ''
const from = this.table ? ('FROM ' + SqlString.escapeId(this.table)) : ''
const where = this.whereSql
const order = this.orderSql
const limit = this.limitSql
const sql = [select, from, where, order, limit].join(' ')
return sql
}
/**
* `UPDATE`的`SQL`语句
*
* @return {String}
* @memberof LeoSQL
*/
get update() {
const columns = this.column
if(_.isEmpty(columns)) return ''
const values = this.value
const sets = []
columns.forEach((column, index) => {
sets.push([SqlString.escapeId(column), '=', SqlString.escape(values[index])].join(' '))
})
const sql = [
'UPDATE', SqlString.escapeId(this.table),
'SET', sets.join(','),
this.whereSql
].join(' ')
return sql
}
/**
* `DELETE`的`SQL`语句
*
* @return {String}
* @memberof LeoSQL
*/
get delete() {
const from = this.table ? ('FROM ' + SqlString.escapeId(this.table)) : ''
const where = this.whereSql
const sql = ['DELETE', from, where].join(' ')
return sql
}
/**
* `COUNT`的`SQL`语句
*
* @return {String}
* @memberof LeoSQL
*/
get count() {
const select = 'SELECT COUNT(*) count'
const from = this.table ? ('FROM ' + SqlString.escapeId(this.table)) : ''
const where = this.whereSql
const sql = [select, from, where].join(' ')
return sql
}
}
module.exports = (request) => new LeoSQL(request)