UNPKG

offshore-sql

Version:
352 lines (301 loc) 13 kB
var _ = require('lodash'); var crypto = require('crypto'); var LOG_QUERIES = false; var Dialect = require('../../dialect.js'); var MysqlCriteriaProcessor = require('./mysqlCriteriaProcessor'); var MysqlDialect = module.exports = function(){}; MysqlDialect.prototype = new Dialect(); MysqlDialect.prototype.CriteriaProcessor = MysqlCriteriaProcessor; MysqlDialect.prototype.describe = function(connection, collection, callback) { var tableName = this.normalizeTableName(collection.tableName); var query = 'DESCRIBE ' + tableName; var pkQuery = 'SHOW INDEX FROM ' + tableName; if (LOG_QUERIES) { console.log('Executing MySQL query :', query); console.log('Executing MySQL query :', pkQuery); } connection.client.raw(query).then(function __DESCRIBE__(result) { connection.client.raw(pkQuery).then(function (pkResult) { var schema = result[0]; schema.forEach(function (attr) { if (attr.Key === 'PRI') { attr.primaryKey = true; if (attr.Type === 'int(11)') { attr.autoIncrement = true; } } if (attr.Key === 'UNI') { attr.unique = true; } }); pkResult.forEach(function (result) { schema.forEach(function (attr) { if (attr.Field !== result.Column_name) return; attr.indexed = true; }); }); callback(null,schema); }); }).catch(function (e) { callback(e,null); }); }; MysqlDialect.prototype.createAlias = function(tableAlias,columnName) { var alias = tableAlias + '_' + columnName; if(!columnName) { alias = tableAlias; } if (alias.length > 64) { return crypto.createHash('sha1').update(alias).digest('base64').replace('=', ''); } return alias; }; MysqlDialect.prototype.normalizeSchema = function (schema) { var normalized = _.reduce(schema, function (memo, field) { var attrName = field.Field; var type = field.Type; type = type.replace(/\([0-9]+\)$/, ''); memo[attrName] = { type: type, defaultsTo: field.Default, autoIncrement: field.Extra === 'autoIncrement' }; if (field.primaryKey) { memo[attrName].primaryKey = field.primaryKey; } if (field.autoIncrement) { memo[attrName].autoIncrement = field.autoIncrement; } if (field.unique) { memo[attrName].unique = field.unique; } if (field.indexed) { memo[attrName].indexed = field.indexed; } return memo; }, {}); return normalized; }; MysqlDialect.prototype.escapeString = function(string) { if (_.isUndefined(string)) { return null; } return this.stringDelimiter + string + this.stringDelimiter; }; MysqlDialect.prototype.afterSelect = function(connection, select) { if (!select.skipLimitQuery) { return; } select.skipLimitSubQuery = select.skipLimitSubQuery.from(select.query.as('SKLMT')); select.query = select.skipLimitQuery.from(select.skipLimitSubQuery.as('SUBSKLMT')); }; MysqlDialect.prototype.selectSkipLimit = function(connection,select) { var self = this; if (select.options.skip || select.options.limit) { if (!select.options.joins) { if (select.options.skip) { select.query.offset(select.options.skip); } if (select.options.limit) { select.query.limit(select.options.limit); } } else { select.query.andWhere(select.alias + '.' + select.pk, 'IN', connection.client.select('*').from(function() { var from = this; this.select(select.pk); this.from(select.tableName); new self.CriteriaProcessor(connection, select.tableName, select.options, this); if (select.options.skip) { this.offset(select.options.skip); } if (select.options.limit) { this.limit(select.options.limit); } if (select.options.sort) { _.keys(select.options.sort).forEach(function(toSort) { var direction = select.options.sort[toSort] === 1 ? 'ASC' : 'DESC'; from.orderBy(select.tableName + '.' + toSort, direction); }); } this.as('SKLMT'); })); } } }; MysqlDialect.prototype.joinSkipLimit = function(select, association) { var self = this; var connection = select.connection; var join = association.join; var childDefinition = connection.getCollection(join.child).definition; var skip = join.criteria.skip; var limit = join.criteria.limit; delete join.criteria.skip; delete join.criteria.limit; this.join(select, association); join.criteria.skip = skip; join.criteria.limit = limit; if (join.select) { join.select.forEach(function(columnName) { if (childDefinition[columnName]) { var childAlias = self.createAlias(association.name, columnName); var column = join.alias + '.' + columnName + ' as ' + childAlias; if (select.selection.indexOf(column) < 0) { select.selection.push(column); select.query.select(column); } } }); } if (!join.criteria.sort) { join.criteria.sort = {}; join.criteria.sort[join.childKey] = 1; } if (join.criteria && join.criteria.sort) { _.keys(join.criteria.sort).forEach(function(toSort) { var direction = join.criteria.sort[toSort] === 1 ? 'ASC' : 'DESC'; select.query.orderBy(join.alias + '.' + toSort, direction); }); } // Subquery 'order by' needs a limit or the parser ignores it (0xefffffffffffffff is around max javascript parseInt() number) select.query.limit(0xefffffffffffffff); if (!select.skipLimitQuery) { select.skipLimitQuery = connection.client.select('SUBSKLMT.*'); select.skipLimitSubQuery = connection.client.select('SKLMT.*'); } var parentPk = connection.client.raw('??.??', ['SKLMT', connection.getPk(select.tableName)]); var childPk = connection.client.raw('??.??', ['SKLMT', self.createAlias(association.name, connection.getPk(join.child))]); var sklmtAlias = self.createAlias('SKLMT', join.alias); select.skipLimitSubQuery = this._getDenseRank(connection, sklmtAlias, select.skipLimitSubQuery, [parentPk, childPk]); if (join.criteria.skip && join.criteria.limit) { select.skipLimitQuery.having(function() { this.andWhere(function() { this.andWhere(sklmtAlias, '>', join.criteria.skip); this.andWhere(sklmtAlias, '<=', join.criteria.limit + join.criteria.skip); }); // do not skip parent data when there is no child this.orWhereNull(sklmtAlias); // do not skip parent data when child skip >= child count this.orWhere(sklmtAlias, '=', 1); }); // inform cursor to skip first child data association.skipFirst = sklmtAlias; } else if (join.criteria.skip) { select.skipLimitQuery.having(function() { this.andWhere(sklmtAlias, '>', join.criteria.skip); // do not skip parent data when there is no child this.orWhereNull(sklmtAlias); // do not skip parent data when child skip >= child count this.orWhere(sklmtAlias, '=', 1); }); association.skipFirst = sklmtAlias; } else if (join.criteria.limit) { select.skipLimitQuery.having(function() { this.andWhere(sklmtAlias, '<=', join.criteria.limit); }); } }; MysqlDialect.prototype.joinManyToManySkipLimit = function(select, association) { var self = this; var connection = select.connection; var join = association.join; var junction = association.junction; var childDefinition = connection.getCollection(join.child).definition; var junctionAlias = this.createAlias('junction_', join.alias); select.query.leftJoin(function() { var query = this; join.select.forEach(function(columnName) { if (childDefinition[columnName]) { query.select(join.child + '.' + columnName); } }); this.select(junction.child + '.' + junction.childKey + ' as ' + junctionAlias); new self.CriteriaProcessor(connection, join.child, join.criteria, this); this.from(join.child).leftJoin(join.parent, function() { this.on(join.parent + '.' + join.parentKey, '=', join.child + '.' + join.childKey); }).as(join.alias); }, function() { this.on(select.alias + '.' + junction.parentKey, join.alias + '.' + junctionAlias); if (association.joinId) { this.on('joins.id', '=', association.joinId); } }); join.select.forEach(function(columnName) { var childAlias = self.createAlias(association.name, columnName); select.query.select(join.alias + '.' + columnName + ' as ' + childAlias); }); var skLmtAlias = this.createAlias('_SKLMT_', join.alias); if (!join.criteria.sort) { join.criteria.sort = {}; join.criteria.sort[join.childKey] = 1; } if (join.criteria && join.criteria.sort) { _.keys(join.criteria.sort).forEach(function(toSort) { var direction = join.criteria.sort[toSort] === 1 ? 'ASC' : 'DESC'; select.query.orderBy(join.alias + '.' + toSort, direction); }); } // Subquery 'order by' needs a limit or the parser ignores it (0xefffffffffffffff is around max javascript parseInt() number) select.query.limit(0xefffffffffffffff); if (!select.skipLimitQuery) { select.skipLimitQuery = connection.client.select('SUBSKLMT.*'); select.skipLimitSubQuery = connection.client.select('SKLMT.*'); } var parentPk = connection.client.raw('??.??', ['SKLMT', connection.getPk(select.tableName)]); var childPk = connection.client.raw('??.??', ['SKLMT', this.createAlias(association.name, connection.getPk(join.child))]); select.skipLimitSubQuery = this._getDenseRank(connection, skLmtAlias, select.skipLimitSubQuery, [parentPk, childPk]); if (join.criteria.skip && join.criteria.limit) { select.skipLimitQuery.andHaving(function() { this.andWhere(skLmtAlias, '>', join.criteria.skip); this.andWhere(skLmtAlias, '<=', join.criteria.limit + join.criteria.skip); }); select.skipLimitQuery.orHaving(skLmtAlias, '=', 1); association.skipFirst = skLmtAlias; } else if (join.criteria.skip) { select.skipLimitQuery.andHaving(function() { this.andWhere(skLmtAlias, '>', join.criteria.skip); this.orWhere(skLmtAlias, '=', 1); }); association.skipFirst = skLmtAlias; } else if (join.criteria.limit) { select.skipLimitQuery.andHaving(skLmtAlias, '<=', join.criteria.limit); } }; MysqlDialect.prototype._getDenseRank = function(connection, alias, query, partitionBy) { /* select *, @rank := if(@previous_id=`selectQuery`.`id`, if(@previous_child_id=`selectQuery`.`child_id`, @rank, @rank+1),1) as denseRank, @previous_id := `selectQuery`.`id`, @previous_child_id := `selectQuery`.`child_id` * * (SELECT @rank := 1, @previous_id := '', @previous_child_id := '') as varDeclaration) * */ // incDenseRank contains strings like '@rank := if(@previous_id=`selectQuery`.`id`, if(@previous_child_id=`selectQuery`.`child_id`, @rank, @rank+1),1) as denseRank' // it checks if any of the values have changed, and, if they have, increment rank // updateVar contains strings like '@previous_id := `selectQuery`.`id`, @previous_child_id := `selectQuery`.`child_id`' // it simply assign variable values for each new row // updateVar contains strings like '(SELECT @rank := 1, @previous_id := '', @previous_child_id := '') as varDeclaration)' // it's used for inline variable initialization (we can't use "set" as Knex doesn't support it yet) // For each given column, create its corresponding variable and add it to all three parts var incDenseRank = '@rank_' + alias + ' := '; partitionBy.forEach(function(partition, index) { var previousVar = '@previous_' + alias + '_' + index; incDenseRank += 'if(' + previousVar + ' = ' + partition + ', '; }); // Add last arguments to all 'if' incDenseRank += '@rank_' + alias + ', @rank_' + alias + '+1)'; for(var i = 0; i < partitionBy.length-1; i++) { incDenseRank += ',1)'; } incDenseRank += ' as ' + alias + ', '; var updateVar = []; partitionBy.forEach(function(partition, index) { var previousVar = '@previous_' + alias + '_' + index; updateVar.push(previousVar + ' := ' + partition); }); incDenseRank += updateVar.join(); var initVar = ['(SELECT @rank_' + alias + ' := 0']; partitionBy.forEach(function(partition, index) { var previousVar = '@previous_' + alias + '_' + index; initVar.push(previousVar + ' := \'\''); }); initVar = initVar.join(); initVar += ') as varDeclaration_' + alias; return query.select(connection.client.raw(incDenseRank)).join(connection.client.raw(initVar)); };