UNPKG

liteq

Version:

QueryBuilder for JavaScript. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle and more. Works in NodeJS.

734 lines (726 loc) 23.1 kB
/** * @Author: richen * @Date: 2018-02-09 16:22:06 * @Copyright (c) - <richenlin(at)gmail.com> * @Last Modified by: richen * @Last Modified time: 2019-03-26 09:14:08 */ const knex = require('knex'); const helper = require('../helper.js'); const identifiers = { OR: 'OR', AND: 'AND', NOT: 'NOT', IN: 'IN', NOTIN: 'NOTIN', '>': 'OPERATOR', '<': 'OPERATOR', '<>': 'OPERATORNE', '!=': 'OPERATORNE', '=': 'OPERATOR', '<=': 'OPERATOR', '>=': 'OPERATOR', 'LIKE': 'OPERATOR' }; /** * * * @param {any} name * @returns */ const parseName = function (name) { name = name.trim(); if (!name) { return name; } //首字母如果是大写,不转义为_x name = name[0].toLowerCase() + name.substr(1); return name.replace(/[A-Z]/g, function (a) { return '_' + a.toLowerCase(); }); }; /** * 书写方法: * or: {or: [{...}, {...}]} * not: {not: {name: '', id: 1}} * notin: {notin: {'id': [1,2,3]}} * in: {id: [1,2,3]} * and: {id: 1, name: 'a'}, * operator: {id: {'<>': 1}} * operator: {id: {'<>': 1, '>=': 0, '<': 100, '<=': 10}} * like: {name: {'like': '%a'}} * @param cls * @param options * @param alias * @param extkey */ /*eslint-disable func-style */ const parseKnexWhere = function (cls, options, alias, extkey) { let idt = ''; for (let op in options) { idt = op.toUpperCase(); switch (identifiers[idt]) { case 'OR': if (helper.isArray(options[op])) { parseOr(cls, options[op], alias); } break; case 'IN': if (helper.isArray(options[op])) { parseIn(cls, op, options[op], alias); } else if (helper.isObject(options[op])) { for (let n in options[op]) { parseIn(cls, n, options[op][n], alias); } } break; case 'NOTIN': if (helper.isObject(options[op])) { parseNotIn(cls, options[op], alias); } else if (helper.isArray(options[op]) && extkey !== undefined) { parseNotIn(cls, { [extkey]: options[op] }, alias); } break; case 'NOT': if (extkey !== undefined) { parseNot(cls, { [extkey]: options[op] }, alias); } else if (helper.isObject(options[op])) { parseNot(cls, options[op], alias); } else if (helper.isArray(options[op])) { for (let n of options[op]) { parseKnexWhere(cls, { [op]: n }, alias, op); } } break; case 'OPERATORNE': if (extkey !== undefined) { if (options[op] === null) { parseNotNull(cls, extkey, alias); } else if (op === '!=') { parseOperator(cls, extkey, '<>', options[op], alias); } else { parseOperator(cls, extkey, op, options[op], alias); } } else if (helper.isObject(options[op])) { for (let n in options[op]) { if (options[op][n] === null) { parseNull(cls, n, alias); } else { parseKnexWhere(cls, { [n]: options[op][n] }, alias, op); } } } break; case 'OPERATOR': if (extkey !== undefined) { parseOperator(cls, extkey, op, options[op], alias); } else if (helper.isObject(options[op])) { for (let n in options[op]) { if (options[op][n] === null) { parseNull(cls, n, alias); } else { parseKnexWhere(cls, { [n]: options[op][n] }, alias, op); } } } break; case 'AND': default: if (helper.isArray(options[op])) { parseIn(cls, op, options[op], alias); } else if (helper.isObject(options[op])) { for (let n in options[op]) { parseKnexWhere(cls, { [n]: options[op][n] }, alias, op); } } else if (options[op] === null) { parseNull(cls, op, alias); } else { let _key = (alias && op.indexOf('.') === -1) ? `${alias}.${op}` : op; cls.where(_key, '=', options[op]); } } } }; //解析or条件 function parseOr(cls, options, alias) { cls.where(function () { options.map(item => { if (helper.isObject(item)) { this.orWhere(function () { parseKnexWhere(this, item, alias); }); } }); }); } //解析not条件 function parseNot(cls, options, alias) { cls.whereNot(function () { parseKnexWhere(this, options, alias); }); } //解析null条件 function parseNull(cls, key, alias) { let _key = (alias && key.indexOf('.') === -1) ? `${alias}.${key}` : key; cls.whereNull(_key); } //解析notnull条件 function parseNotNull(cls, key, alias) { let _key = (alias && key.indexOf('.') === -1) ? `${alias}.${key}` : key; cls.whereNotNull(_key); } //解析in条件 function parseIn(cls, key, value, alias) { let _key = (alias && key.indexOf('.') === -1) ? `${alias}.${key}` : key; cls.whereIn(_key, value); } //解析notin条件 function parseNotIn(cls, options, alias) { let _key = ''; for (let n in options) { _key = (alias && n.indexOf('.') === -1) ? `${alias}.${n}` : n; cls.whereNotIn(_key, options[n]); } } //解析operator等条件 function parseOperator(cls, key, operator, value, alias) { let _key = (alias && key.indexOf('.') === -1) ? `${alias}.${key}` : key; cls.where(_key, operator, value); } //解析JOIN ON/ORON 条件 function parseOn(cls, alias, method, joinTable, joinAlias, on) { cls[method](`${joinTable} as ${joinAlias}`, function () { for (let n in on) { if (n.toLowerCase() === 'or') { if (!helper.isArray(on[n])) { continue; } on[n].map(item => { for (let i in item) { this.orOn(`${alias}.${i}`, '=', `${joinAlias}.${item[i]}`); } }); } else { this.on(`${alias}.${n}`, '=', `${joinAlias}.${on[n]}`); } } }); } /** * //解析后结果 //.innerJoin('accounts', function() { // this.on('accounts.id', '=', 'users.account_id').on('accounts.owner_id', '=', 'users.id').orOn('accounts.owner_id', '=', 'users.id') //}) * * @param {any} cls * @param {any} type * @param {any} alias * @param {any} joinTable * @param {any} joinAlias * @param {any} on * @param {any} options * @param {any} where */ const parseKnexJoin = function (cls, type, alias, joinTable, joinAlias, on) { let method; switch (type) { case 'left': method = 'leftJoin'; break; case 'right': method = 'leftJoin'; break; default: method = 'innerJoin'; break; } parseOn(cls, alias, method, joinTable, joinAlias, on); }; /** * * @param cls * @param field * @param value * @return {string} * let types = { integer: {}, string: {size: 50}, float: {precision: 8, size: 2}, json: {}, array: [], text: {} }; */ const parseKnexSchema = function (cls, tableName, fields) { return cls.createTable(tableName, function (t) { let defaultVal = '', columns, intM = 'integer', pkArr = [], auto = 0; for (let n in fields) { if (fields[n].pk === true) { pkArr.push(n); } if (!helper.isTrueEmpty(fields[n].defaults) && !helper.isFunction(fields[n].defaults)) { defaultVal = fields[n].defaults; } // increments if (fields[n].auto) { auto = 1; columns = t.increments(n); } else { switch (fields[n].type) { case 'integer': if (fields[n].size > 11) { intM = 'bigInteger'; } if (helper.isNumber(defaultVal)) { columns = t[intM](n).defaultTo(defaultVal); } else { columns = t[intM](n); } break; case 'float': if (helper.isNumber(defaultVal)) { columns = t.float(n, 8, fields[n].size || 2).defaultTo(defaultVal); } else { columns = t.float(n, 8, fields[n].size || 2); } break; case 'json': case 'array': if (helper.isJSONStr(defaultVal)) { columns = t.json(n).defaultTo(defaultVal); } else { columns = t.json(n); } break; case 'text': if (helper.isJSONStr(defaultVal)) { columns = t.text(n).defaultTo(defaultVal); } else { columns = t.text(n); } break; case 'string': default: if (helper.isString(defaultVal)) { columns = t.string(n, fields[n].size).defaultTo(defaultVal); } else { columns = t.string(n, fields[n].size); } break; } } // --- columns.xx() // isnull if (fields[n].isnull) { columns.nullable(); } // comment if (fields[n].comment) { columns.comment(fields[n].comment || ''); } // required if (fields[n].required === true) { columns.notNullable(n); } // --- table.xx() // index if (fields[n].index === true) { fields[n].required = true; t.index(n, `${tableName}_${n}`); } // unique if (fields[n].unique === true) { t.unique(n); } } //auto_increment会默认设置字段为主键,和主键设置冲突 if (pkArr.length > 0 && !auto) { t.primary(pkArr); } }); }; module.exports = class { constructor(config = {}) { this.config = config; this.knexClient = null; } /** * * * @param {any} cls * @param {any} data * @param {any} options * @returns */ // eslint-disable-next-line no-unused-vars parseData(cls, data, options) { return cls; } /** * * @param cls * @param data * @param options */ parseField(cls, data, options) { if (helper.isEmpty(options.field)) { return cls; } const fields = []; options.field.map(fl => { if (fl) { if (fl.indexOf('.') < 0) { fields.push(options.alias ? `${options.alias}.${fl}` : fl); } else { fields.push(fl); } } }); cls.column(fields); return cls; } /** * * @param cls * @param data * @param options */ parseWhere(cls, data, options) { if (helper.isEmpty(options.where)) { return cls; } //parse where options parseKnexWhere(cls, options.where, options.alias); return cls; } /** * group('xxx') * group(['xxx', 'xxx']) * @param cls * @param data * @param options */ parseGroup(cls, data, options) { if (helper.isEmpty(options.group)) { return cls; } cls.groupBy(options.group); return cls; } /** * distinct(['first_name', 'last_name']) * * @param {any} cls * @param {any} data * @param {any} options * @returns */ parseDistinct(cls, data, options) { if (helper.isEmpty(options.distinct)) { return cls; } cls.distinct(helper.isArray(options.distinct) ? options.distinct.join(',') : options.distinct); return cls; } /** * having({"name":{">": 100}}) * * @param {any} cls * @param {any} data * @param {any} options * @returns */ parseHaving(cls, data, options) { if (helper.isEmpty(options.having)) { return cls; } for (let n in options.having) { if (helper.isObject(options.having[n])) { for (let y in options.having[n]) { cls.having(n, y, options.having[n][y]); } } } return cls; } /** * join([{from: 'Test', alias: 'test', on: {aaa: bbb, ccc: ddd}, field: ['id', 'name'], type: 'inner'}]) * join([{from: 'Test', alias: 'test', on: {or: [{aaa: bbb}, {ccc: ddd}]}, field: ['id', 'name'], type: 'left'}]) * join([{from: 'Test', alias: 'test', on: {aaa: bbb, ccc: ddd}, field: ['id', 'name'], type: 'right'}]) * @param cls * @param data * @param options */ parseJoin(cls, data, options) { if (helper.isArray(options.join)) { let type, alias = options.alias, joinAlias = '', joinTable = ''; options.join.map(item => { type = item.type ? item.type.toLowerCase() : 'inner'; joinTable = `${this.config.db_prefix}${parseName(item.from)}`; joinAlias = item.alias || item.from; parseKnexJoin(cls, type, alias, joinTable, joinAlias, item.on || {}); if (helper.isArray(item.field)) { item.field.map(f => options.field.push(`${joinAlias}.${f}`)); } }); } return cls; } /** * * @param cls * @param data * @param options */ parseLimit(cls, data, options) { if (helper.isEmpty(options.limit)) { return cls; } cls.limit(options.limit[1] || 10).offset(options.limit[0] || 0); return cls; } /** * * @param cls * @param data * @param options */ parseOrder(cls, data, options) { if (helper.isEmpty(options.order)) { return cls; } for (let n in options.order) { if (n.indexOf('.') > -1) { cls.orderBy(n, options.order[n]); } else { cls.orderBy(`${options.alias}.${n}`, options.order[n]); } } return cls; } /** * * @param cls * @param data * @param options */ parseSchema(cls, data, options) { if (helper.isEmpty(data) && helper.isEmpty(options.schema)) { return cls; } // let dbType = options.schema.dbtype; return parseKnexSchema(cls, options.schema.table || '', options.schema.fields || {}); } /** * * * @param {any} cls * @param {any} data * @param {any} options * @returns */ buildADD(cls, data, options) { this.knexClient = cls.insert(data).from(options.table); return this.parseData(this.knexClient, data, options); } /** * * * @param {any} cls * @param {any} data * @param {any} options * @returns */ buildBATCHADD(cls, data, options) { this.knexClient = cls.batchInsert(options.table, data, 100); return this.parseData(this.knexClient, data, options); } /** * * * @param {any} cls * @param {any} data * @param {any} options * @returns */ buildDELETE(cls, data, options) { this.knexClient = cls.del().from(options.table); return this.parseWhere(this.knexClient, data, options); } /** * * * @param {any} cls * @param {any} data * @param {any} options * @returns */ buildUPDATE(cls, data, options) { this.knexClient = cls.update(data).from(options.table); this.parseData(this.knexClient, data, options); return this.parseWhere(this.knexClient, data, options); } /** * * * @param {any} cls * @param {any} data * @param {any} options * @returns */ buildINCREMENT(cls, data, options) { this.knexClient = cls(options.table).increment(options.targetField, data[options.targetField]); delete data[options.targetField]; this.parseData(this.knexClient, data, options); if (!helper.isEmpty(data)) { this.knexClient.update(data); } return this.parseWhere(this.knexClient, data, options); } /** * * * @param {any} cls * @param {any} data * @param {any} options * @returns */ buildDECREMENT(cls, data, options) { this.knexClient = cls(options.table).decrement(options.targetField, data[options.targetField]); delete data[options.targetField]; this.parseData(this.knexClient, data, options); if (!helper.isEmpty(data)) { this.knexClient.update(data); } return this.parseWhere(this.knexClient, data, options); } /** * * * @param {any} cls * @param {any} data * @param {any} options * @returns */ buildSELECT(cls, data, options) { options.table = options.alias ? `${options.table} as ${options.alias}` : options.table; this.knexClient = cls.select().from(options.table); this.parseJoin(this.knexClient, data, options); this.parseField(this.knexClient, data, options); this.parseWhere(this.knexClient, data, options); this.parseLimit(this.knexClient, data, options); this.parseOrder(this.knexClient, data, options); this.parseDistinct(this.knexClient, data, options); this.parseGroup(this.knexClient, data, options); return this.parseHaving(this.knexClient, data, options); } /** * * * @param {any} cls * @param {any} data * @param {any} options */ buildCOUNT(cls, data, options) { options.table = options.alias ? `${options.table} as ${options.alias}` : options.table; const targetField = options.targetField || options.pk; if (helper.isEmpty(options.group)) { this.knexClient = cls.count(options.alias ? `${options.alias}.${targetField} as count` : `${targetField} as count`).from(options.table); } else { this.knexClient = cls.select().column(options.alias ? `${options.alias}.${targetField} as count` : `${targetField} as count`).from(options.table); } this.parseJoin(this.knexClient, data, options); this.parseWhere(this.knexClient, data, options); if (helper.isEmpty(options.group)) { this.parseLimit(this.knexClient, data, options); } if (helper.isEmpty(options.group)) { this.parseOrder(this.knexClient, data, options); } this.parseDistinct(this.knexClient, data, options); this.parseGroup(this.knexClient, data, options); return this.parseHaving(this.knexClient, data, options); } /** * * * @param {any} cls * @param {any} data * @param {any} options */ buildSUM(cls, data, options) { options.table = options.alias ? `${options.table} as ${options.alias}` : options.table; const targetField = options.targetField || options.pk; if (helper.isEmpty(options.group)) { this.knexClient = cls.sum(options.alias ? `${options.alias}.${targetField} as sum` : `${targetField} as sum`).from(options.table); } else { this.knexClient = cls.select().column(options.alias ? `${options.alias}.${targetField} as sum` : `${targetField} as sum`).from(options.table); } this.parseJoin(this.knexClient, data, options); this.parseWhere(this.knexClient, data, options); if (helper.isEmpty(options.group)) { this.parseLimit(this.knexClient, data, options); } if (helper.isEmpty(options.group)) { this.parseOrder(this.knexClient, data, options); } this.parseDistinct(this.knexClient, data, options); this.parseGroup(this.knexClient, data, options); return this.parseHaving(this.knexClient, data, options); } /** * * * @param {any} cls * @param {any} data * @param {any} options * @returns */ buildMIGRATE(cls, data, options) { this.knexClient = cls.schema; return this.parseSchema(this.knexClient, data, options); } /** * * * @static * @param {*} config * @param {*} data * @param {*} options * @param {*} cls * @returns */ static async buildSql(config, data, options, cls) { if (options === undefined) { options = data; } if (!cls) { cls = knex({ client: config.db_type || 'mysql' }); } //防止外部options被更改 let parseOptions = Object.create(options); let handle = new this(config); return handle[`build${parseOptions.method}`](cls, data, parseOptions).toString(); } /** * * * @static * @param {*} config * @param {*} data * @param {*} options * @param {*} cls * @returns */ static async querySql(config, data, options, cls) { if (options === undefined) { options = data; } //防止外部options被更改 let parseOptions = Object.create(options); let handle = new this(config); return handle[`build${parseOptions.method}`](cls, data, parseOptions); } };