const knex = require('knex'); knex.QueryBuilder.extend('whereGroup', function(condition, rules) { condition = condition.toLowerCase(); for (let rule of rules) { if (!rule.condition) { let where = condition == 'or' ? 'orWhere' : 'where'; let column = rule.data ? rule.data.column : rule.column; if (rule.data && rule.data.table) { if (rule.data.schema) { column = rule.data.schema + '.' + rule.data.table + '.' + column; } else { column = rule.data.table + '.' + column; } } else if (rule.table) { if (rule.schema) { column = rule.schema + '.' + rule.table + '.' + column; } else { column = rule.table + '.' + column; } } if (typeof rule.value == 'undefined') { rule.value = null; } if (rule.operator == 'between') { this[where + 'Between'](column, rule.value); } else if (rule.operator == 'not_between') { this[where + 'NotBetween'](column, rule.value); } else if (rule.operator == 'is_null') { this[where + 'Null'](column); } else if (rule.operator == 'is_not_null') { this[where + 'NotNull'](column); } else if (rule.operator == 'in') { this[where + 'In'](column, rule.value); } else if (rule.operator == 'not_in') { this[where + 'NotIn'](column, rule.value); } else if (rule.operator == 'begins_with') { // simple escaping for likes, knex doesn't do this (\ is default in most databases) // perhaps improve by using raw query like // this[where](knex.raw("?? like ? escape '\'"), [column, escapedValue]); let escapedValue = rule.value.replace(/[\\%_^]/g, '\\$&'); this[where](column, 'like', escapedValue + '%'); } else if (rule.operator == 'not_begins_with') { let escapedValue = rule.value.replace(/[\\%_^]/g, '\\$&'); this[where + 'Not'](column, 'like', escapedValue + '%'); } else if (rule.operator == 'ends_with') { let escapedValue = rule.value.replace(/[\\%_^]/g, '\\$&'); this[where](column, 'like', '%' + escapedValue); } else if (rule.operator == 'not_ends_with') { let escapedValue = rule.value.replace(/[\\%_^]/g, '\\$&'); this[where + 'Not'](column, 'like', '%' + escapedValue); } else if (rule.operator == 'contains') { let escapedValue = rule.value.replace(/[\\%_^]/g, '\\$&'); this[where](column, 'like', '%' + escapedValue + '%'); } else if (rule.operator == 'not_contains') { let escapedValue = rule.value.replace(/[\\%_^]/g, '\\$&'); this[where + 'Not'](column, 'like', '%' + escapedValue + '%'); } else { this[where](column, rule.operation, rule.value); } } else { this[condition + 'Where'](function() { this.whereGroup(rule.condition, rule.rules); }); } } return this; }); knex.QueryBuilder.extend('fromJSON', function(ast, meta) { if (ast.type == 'count') { return this.count('* as Total').from(function() { this.fromJSON(Object.assign({}, ast, { type: 'select', offset: null, limit: null, orders: null })).as('t1'); }).first(); } else if (ast.type == 'insert' || ast.type == 'update') { let values = {}; for (let val of ast.values) { if (val.type == 'json') { // json support values[val.column] = JSON.stringify(val.value); } else { values[val.column] = val.value; } } this[ast.type](values); } else { this[ast.type](); } if (ast.returning && !(this.client?.config?.client?.includes('mysql'))) { // Adding the option includeTriggerModifications allows you // to run statements on tables that contain triggers. Only affects MSSQL. this.returning(ast.returning, { includeTriggerModifications: true }); } if (ast.table) { let table = ast.table.name || ast.table; if (ast.table.schema) { table = ast.table.schema + '.' + table; } if (ast.table.alias) { table += ' as ' + ast.table.alias; } this.from(table); } if ((ast.type == 'select' || ast.type == 'first') && ast.joins && ast.joins.length) { for (let join of ast.joins) { let table = join.table; if (join.schema) { table = join.schema + '.' + table; } if (join.alias) { table += ' as ' + join.alias; } this[(join.type || 'inner').toLowerCase() + 'Join'](table, function() { for (let clause of join.clauses.rules) { this.on( (clause.schema ? clause.schema + '.' : '') + clause.table + '.' + clause.column, clause.operation, (clause.value.schema ? clause.value.schema + '.' : '') + clause.value.table + '.' + clause.value.column ); } }); } } if ((ast.type == 'select' || ast.type == 'first') && ast.columns) { for (let col of ast.columns) { let column = col.column || col; if (ast.joins && ast.joins.length && col.table) { column = col.table + '.' + column; if (col.schema) { column = col.schema + '.' + column; } } if (col.alias) { column += ' as ' + col.alias; } this[col.aggregate ? col.aggregate.toLowerCase() : 'column'](column); } } if ((ast.type == 'select' || ast.type == 'first') && ast.groupBy && ast.groupBy.length) { for (let col of ast.groupBy) { if (ast.joins && ast.joins.length && col.table) { if (col.schema) { this.groupBy(col.schema + '.' + col.table + '.' + col.column); } else { this.groupBy(col.table + '.' + col.column); } } else { this.groupBy(col.column || col); } } } if (ast.wheres && ast.wheres.condition) { this.whereGroup(ast.wheres.condition, ast.wheres.rules); } if ((ast.type == 'select' || ast.type == 'first') && ast.orders && ast.orders.length) { for (let order of ast.orders) { if (ast.joins && ast.joins.length && order.table) { if (order.schema) { this.orderBy(order.schema + '.' + order.table + '.' + order.column, order.direction); } else { this.orderBy(order.table + '.' + order.column, order.direction); } } else { this.orderBy(order.column, order.direction); } } } if (ast.distinct) { this.distinct(); } if (ast.type == 'select' && ast.limit) { this.limit(ast.limit); } if (ast.type == 'select' && ast.offset) { this.offset(ast.offset); } if (meta) { this.queryContext(meta); } return this; }); module.exports = knex;