lebretr/sequelize-oracle

View on GitHub
lib/dialects/oracle/query-generator.js

Summary

Maintainability
F
1 mo
Test Coverage
var Utils     = require("../../utils")
  , DataTypes = require("../../data-types")
  , util      = require("util")

module.exports = (function() {
  var QueryGenerator = {
    dialect: 'oracle',
    
    addSchema: function(opts) {
      var tableName
      var schema        = (!!opts && !!opts.options && !!opts.options.schema ? opts.options.schema : undefined)
      var schemaDelimiter  = (!!opts && !!opts.options && !!opts.options.schemaDelimiter ? opts.options.schemaDelimiter : undefined)

      if (!!opts && !!opts.tableName) {
        tableName = opts.tableName
      }
      else if (typeof opts === "string") {
        tableName = opts
      }

      if (!schema || schema.toString().trim() === "") {
        return tableName
      }

      return this.quoteIdentifier(schema + (!schemaDelimiter ? '.' : schemaDelimiter) + tableName, false)
    },

    createSchema: function(schema) {
      var query = "CREATE SCHEMA AUTHORIZATION <%= schema%>"
      return Utils._.template(query)({schema: schema})
    },

    dropSchema: function(schema) {
      var query = "DROP SCHEMA <%= schema%> RESTRICT"
      return Utils._.template(query)({schema: schema})
    },

    //OK ORACLE
    showSchemasQuery: function() {
      return "SELECT owner, table_name FROM all_tables"
    },

    //OK ORACLE partial
    createTableQuery: function(tableName, attributes, options) {
    //Warning: you must have CREATE ANY TABLE system privilege

      options = Utils._.extend({
      }, options || {})


      var query   = "call SEQUELIZE.createTableQuery('<%= table %>','<%= attributes %>','<%= autoIncrement %>') "
        , autoIncrement = ''
        , primaryKeys = []
        , foreignKeys = {}
        , attrStr = []

      for (var attr in attributes) {
        if (attributes.hasOwnProperty(attr)) {
          var dataType = this.oracleDataTypeMapping(tableName, attr, attributes[attr])

          if (Utils._.includes(dataType, 'PRIMARY KEY')) {
            primaryKeys.push(attr)
            dataType = dataType.replace(/PRIMARY KEY/, '');
          }

          if (Utils._.includes(dataType, 'auto_increment')) {
            autoIncrement=attr;
            dataType = dataType.replace(/auto_increment/, '');
          }

          if (Utils._.includes(dataType, 'REFERENCES')) {
            // MySQL doesn't support inline REFERENCES declarations: move to the end
            var m = dataType.match(/^(.+) (REFERENCES.*)$/)
            dataType = m[1];
            foreignKeys[attr] = m[2]
          }

          attrStr.push(this.quoteIdentifier(attr) + " " + dataType)
        }
      }

      var values = {
        table: this.quoteIdentifier(tableName),
        attributes: attrStr.join(", "),
        comment: options.comment && Utils._.isString(options.comment) ? " COMMENT " + this.escape(options.comment) : "",
        engine: options.engine,
        charset: (options.charset ? "DEFAULT CHARSET=" + options.charset : ""),
        collation: (options.collate ? "COLLATE " + options.collate : ""),
        autoIncrement: autoIncrement ? this.quoteIdentifier(autoIncrement) : ''
      }
      , pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk) }.bind(this)).join(", ")

      if (!!options.uniqueKeys) {
        Utils._.each(options.uniqueKeys, function(columns) {
          values.attributes += ", UNIQUE uniq_" + tableName + '_' + columns.fields.join('_') + " (" + columns.fields.join(', ') + ")"
        })
      }

      if (pkString.length > 0) {
        values.attributes += ", CONSTRAINT "+this.quoteIdentifier(tableName+"_PK")+" PRIMARY KEY (" + pkString + ")"
      }

      for (var fkey in foreignKeys) {
        if(foreignKeys.hasOwnProperty(fkey)) {
          values.attributes += ", FOREIGN KEY (" + this.quoteIdentifier(fkey) + ") " + foreignKeys[fkey]
        }
      }

      return Utils._.template(query)(values).trim() + ";"
    },

    //OK ORACLE
    dropTableQuery: function(tableName, options) {
      if(Object.prototype.toString.call(tableName) === '[object Object]' && Object.prototype.toString.call(tableName['TABLE_NAME']) === '[object String]'){
        tableName=tableName['TABLE_NAME'];
      }

      options = options || {}

      var query = "call SEQUELIZE.dropTableQuery('<%= table %>'); "

      return Utils._.template(query)({
        table: this.quoteIdentifier(tableName)
      })
    },

    showTablesQuery: function() {
      return 'SELECT table_name FROM user_tables'
    },

    uniqueConstraintMapping: {
      code: 'ORA-00001',
      map: function(str) {
        // we're manually remvoving uniq_ here for a future capability of defining column names explicitly
        var match = str.replace('uniq_', '').match(/unique constraint '(.*?)' violated$/)
        if (match === null || match.length < 2) {
          return false
        }

        return match[1].split('_')
      }
    },

    addColumnQuery: function(tableName, attributes) {
      throwMethodUndefined('addColumnQuery')
    },

    removeColumnQuery: function(tableName, attributeName) {
      throwMethodUndefined('removeColumnQuery')
    },

    changeColumnQuery: function(tableName, attributes) {
      throwMethodUndefined('changeColumnQuery')
    },

    renameColumnQuery: function(tableName, attrBefore, attributes) {
      throwMethodUndefined('renameColumnQuery')
    },

    bulkInsertQuery: function(tableName, attrValueHashes, options) {
      var query = "INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>;"
        , tuples = []
        , allAttributes = []

      Utils._.forEach(attrValueHashes, function(attrValueHash, i) {
        Utils._.forOwn(attrValueHash, function(value, key, hash) {
          if (allAttributes.indexOf(key) === -1) allAttributes.push(key)
        })
      })

      Utils._.forEach(attrValueHashes, function(attrValueHash, i) {
        tuples.push("(" +
          allAttributes.map(function (key) {
            return this.escape(attrValueHash[key])
          }.bind(this)).join(",") +
        ")")
      }.bind(this))

      var replacements  = {
        ignoreDuplicates: options && options.ignoreDuplicates ? ' IGNORE' : '',
        table: this.quoteIdentifier(tableName),
        attributes: allAttributes.map(function(attr){
                      return this.quoteIdentifier(attr)
                    }.bind(this)).join(","),
        tuples: tuples
      }

      return Utils._.template(query)(replacements)
    },

    deleteQuery: function(tableName, where, options) {
      options = options || {}

      var table = this.quoteIdentifier(tableName)
      if (options.truncate === true) {
        // Truncate does not allow LIMIT and WHERE
        return "TRUNCATE " + table
      }

      where = this.getWhereConditions(where)
      var limit = ""

      if(Utils._.isUndefined(options.limit)) {
        options.limit = 1;
      }

      if(!!options.limit) {
        limit = " AND ROWNUM <= " + this.escape(options.limit)
      }

      return "DELETE FROM " + table + " WHERE " + where + limit
    },

    bulkDeleteQuery: function(tableName, where, options) {
      options = options || {}

      var table = this.quoteIdentifier(tableName)
      where = this.getWhereConditions(where)

      var query = "DELETE FROM " + table + " WHERE " + where

      return query
    },

    addIndexQuery: function(tableName, attributes, options) {
      throwMethodUndefined('addIndexQuery')
    },

    showIndexQuery: function(tableName, options) {
      throwMethodUndefined('showIndexQuery')
    },

    removeIndexQuery: function(tableName, indexNameOrAttributes) {
      throwMethodUndefined('removeIndexQuery')
    },
    
    attributesToSQL: function(attributes) {
      var result = {}

      for (var name in attributes) {
        var dataType = attributes[name]

        if (Utils.isHash(dataType)) {
          var template

          if (dataType.type.toString() === DataTypes.ENUM.toString()) {
            var len=1;
            for (var i in dataType.values){
              if(len<dataType.values[i].length){
                len=dataType.values[i].length;
              }
            }
            if (Array.isArray(dataType.values) && (dataType.values.length > 0)) {
              template = "VARCHAR2("+len+") CHECK( \""+name+"\" IN (" + Utils._.map(dataType.values, function(value) {
                return this.escape(this.escape(value))
              }.bind(this)).join(", ") + "))"
            } else {
              throw new Error('Values for ENUM haven\'t been defined.')
            }
          } else {
            template = dataType.type.toString();
          }

          if (dataType.type === "STRING") {
            dataType.originalType = "STRING"
            dataType.type = 'VARCHAR2'
          }

          if (dataType.type === "TEXT") {
            dataType.originalType = "TEXT"
            dataType.type = 'CLOB'
          }

          if (dataType.type === "BIGINT" || dataType.type.toString()=== "BIGINT") {
            dataType.originalType = "BIGINT"
            dataType.type = 'NUMBER(19, 0)'
          }

          if (dataType.hasOwnProperty('allowNull') && (!dataType.allowNull)) {
            template += " NOT NULL"
          }

          if (dataType.autoIncrement) {
            template += " auto_increment"
          }

          // Blobs/texts cannot have a defaultValue
          if (dataType.type !== "TEXT" && dataType.type._binary !== true && Utils.defaultValueSchemable(dataType.defaultValue)) {
            template += " DEFAULT " + this.escape(dataType.defaultValue)
          }

          if (dataType.unique === true) {
            template += " UNIQUE"
          }

          if (dataType.primaryKey) {
            template += " PRIMARY KEY"
          }

          if(dataType.references) {
            template += " REFERENCES " + this.quoteIdentifier(dataType.references)

            if(dataType.referencesKey) {
              template += " (" + this.quoteIdentifier(dataType.referencesKey) + ")"
            } else {
              template += " (" + this.quoteIdentifier('id') + ")"
            }

            if(dataType.onDelete) {
              template += " ON DELETE " + dataType.onDelete.toUpperCase()
            }

            if(dataType.onUpdate) {
              template += " ON UPDATE " + dataType.onUpdate.toUpperCase()
            }

          }

          if (dataType.comment && Utils._.isString(dataType.comment) && dataType.comment.length) {
            template += " COMMENT " + this.escape(dataType.comment)
          }

          result[name] = template
        } else {
          result[name] = dataType
        }
      }

      return result
    },

    findAutoIncrementField: function(factory) {
      var fields = []

      // for (var name in factory.attributes) {
      //   if (factory.attributes.hasOwnProperty(name)) {
      //     var definition = factory.attributes[name]

      //     if (definition && (definition.indexOf('auto_increment') > -1)) {
      //       fields.push(name)
      //     }
      //   }
      // }

      return fields
    },

    addLimitAndOffset: function(options, query){
      query = query || ""
      if (!options.offset && options.limit) {
        query = " SELECT * FROM (  SELECT t.*, ROWNUM ROWNUM_1 FROM (" + query + ")t )t2 WHERE t2.ROWNUM_1 <=" + options.limit
      }

      if (options.offset && !options.limit) {
        query = " SELECT * FROM (  SELECT t.*, ROWNUM ROWNUM_1 FROM (" + query + ")t )t2 WHERE t2.ROWNUM_1 >" + options.offset
      }
      if (options.offset && options.limit) {
        query = " SELECT * FROM (  SELECT t.*, ROWNUM ROWNUM_1 FROM (" + query + ")t )t2 WHERE t2.ROWNUM_1 BETWEEN " + (parseInt(options.offset,10) + 1) + " AND " +  (parseInt(options.offset,10) + parseInt(options.limit,10));
      }
      return query;
    },

    quoteIdentifier: function(identifier, force) {
      //identifier=identifier.replace(/\./g,'_');
      // identifier=identifier.replace(/ AS /g,' ');
      if (identifier === '*') return identifier
      if(!force && this.options && this.options.quoteIdentifiers === false) { // default is `true`
        // In Oracle, if tables or attributes are created double-quoted,
        // they are also case sensitive. If they contain any lowercase
        // characters, they must always be double-quoted. This makes it
        // impossible to write queries in portable SQL if tables are created in
        // this way. Hence, we strip quotes if we don't want case sensitivity.
        return Utils.removeTicks(identifier, '"')
      } else {
        return Utils.addTicks(identifier, '"')
      }
    },

    quoteIdentifiers: function(identifiers, force) {
      return identifiers.split('.').map(function(v) { return this.quoteIdentifier(v, force) }.bind(this)).join('.')
    },

    quoteTable: function(table) {
      return this.quoteIdentifier(table)
    },

    /**
     * Generates an SQL query that returns all foreign keys of a table.
     *
     * @param  {String} tableName  The name of the table.
     * @param  {String} schemaName The name of the schema.
     * @return {String}            The generated sql query.
     */
    getForeignKeysQuery: function(tableName, schemaName) {
      if(Object.prototype.toString.call(tableName) === '[object Object]' && Object.prototype.toString.call(tableName['TABLE_NAME']) === '[object String]'){
        tableName=tableName['TABLE_NAME'];
      }
      return "SELECT a.constraint_name as constraint_name \
                FROM all_cons_columns a \
                JOIN all_constraints c ON a.owner = c.owner \
                    AND a.constraint_name = c.constraint_name \
                WHERE c.constraint_type = 'R' \
                AND a.table_name ='" + tableName.TABLE_NAME + "'";
    },

    /**
     * Generates an SQL query that removes a foreign key from a table.
     *
     * @param  {String} tableName  The name of the table.
     * @param  {String} foreignKey The name of the foreign key constraint.
     * @return {String}            The generated sql query.
     */
    dropForeignKeyQuery: function(tableName, foreignKey) {
      throwMethodUndefined('dropForeignKeyQuery')
    },

    selectQuery: function(tableName, options, factory) {
      // Enter and change at your own peril -- Mick Hansen

      options = options || {}

      var table               = null
        , self                = this
        , query
        , limit               = options.limit
        , mainQueryItems      = []
        , mainAttributes      = options.attributes
        , mainJoinQueries     = []
        // We'll use a subquery if we have hasMany associations and a limit and a filtered/required association
        , subQuery            = limit && (options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation)
        , subQueryItems       = []
        , subQueryAs          = []
        , subQueryAttributes  = null
        , subJoinQueries      = []

      // Escape table
      options.table = table = !Array.isArray(tableName) ? this.quoteIdentifiers(tableName) : tableName.map(function(t) {
        return this.quoteIdentifiers(t)
      }.bind(this)).join(", ")

      if (subQuery && mainAttributes) {
        if (factory.hasPrimaryKeys) {
          factory.primaryKeyAttributes.forEach(function(keyAtt){
            if(mainAttributes.indexOf(keyAtt) == -1){
              mainAttributes.push(keyAtt)
            }
          })
        } else {
          mainAttributes.push("id")
        }          
      }

      // Escape attributes
      mainAttributes = mainAttributes && mainAttributes.map(function(attr){
        var addTable = true

        if (attr instanceof Utils.literal) {
          return attr.toString(this)
        }

        if (attr instanceof Utils.fn || attr instanceof Utils.col) {
          return attr.toString(self)
        }

        if(Array.isArray(attr) && attr.length == 2) {
          if (attr[0] instanceof Utils.fn || attr[0] instanceof Utils.col) {
            attr[0] = attr[0].toString(self)
            addTable = false
          }
          attr = [attr[0], this.quoteIdentifier(attr[1])].join(' as ')
        } else {
          attr = attr.indexOf(Utils.TICK_CHAR) < 0 && attr.indexOf('"') < 0 ? this.quoteIdentifiers(attr) : attr
        }

        if (options.include && attr.indexOf('.') === -1 && addTable) {
          attr = this.quoteIdentifier(options.table) + '.' + attr
        }

        return attr
      }.bind(this))

      // If no attributes specified, use *
      mainAttributes = mainAttributes || (options.include ? [options.table+'.*'] : ['*'])

      // If subquery, we ad the mainAttributes to the subQuery and set the mainAttributes to select * from subquery
      if (subQuery) {
        // We need primary keys
        subQueryAttributes = mainAttributes
        !Array.isArray(tableName) ? subQueryAs.push(tableName) : subQueryAs.concat(tableName)
        mainAttributes = [options.table+'.*']
      }

      if (options.include) {
        var generateJoinQueries = function(include, parentTable) {
          var table         = include.daoFactory.tableName
            , as            = include.as
            , joinQueryItem = ""
            , joinQueries = {
              mainQuery: [],
              subQuery: []
            }
            , attributes
            , association   = include.association
            , through       = include.through
            , joinType      = include.required ? ' INNER JOIN ' : ' LEFT OUTER JOIN '
            , includeWhere  = {}
            , whereOptions  = Utils._.clone(options)

          whereOptions.keysEscaped = true

          if (tableName !== parentTable) {
            as = parentTable+'.'+include.as
          }
          
          if (include.subQuery && subQuery){
            subQueryAs.push(as);
          }

          // includeIgnoreAttributes is used by aggregate functions
          if (options.includeIgnoreAttributes !== false) {
            attributes  = include.attributes.map(function(attr) {
              return self.quoteIdentifier(as) + "." + self.quoteIdentifier(attr) + " " + self.quoteIdentifier(as + "_" + attr)
            })

            if (include.subQuery && subQuery) {
              subQueryAttributes = subQueryAttributes.concat(attributes)
            } else {
              mainAttributes = mainAttributes.concat(attributes)
            }
          }

          if (through) {
            var throughTable      = through.daoFactory.tableName
              , throughAs         = as + "." + through.as
              , throughAttributes = through.attributes.map(function(attr) {
                return self.quoteIdentifier(throughAs) + "." + self.quoteIdentifier(attr) + " " + self.quoteIdentifier(throughAs + "_" + attr)
              })
              , primaryKeysSource = Object.keys(association.source.primaryKeys)
              , tableSource       = parentTable
              , identSource       = association.identifier
              , attrSource        = ((!association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0])
              , where

              , primaryKeysTarget = Object.keys(association.target.primaryKeys)
              , tableTarget       = as
              , identTarget       = association.foreignIdentifier
              , attrTarget        = ((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0])

              , sourceJoinOn
              , targetJoinOn
              , targetWhere

            if (options.includeIgnoreAttributes !== false) {
              // Through includes are always hasMany, so we need to add the attributes to the mainAttributes no matter what (Real join will never be executed in subquery)
              mainAttributes = mainAttributes.concat(throughAttributes)
            }

            // Filter statement for left side of through
            // Used by both join and subquery where
            sourceJoinOn = self.quoteIdentifier(tableSource) + "." + self.quoteIdentifier(attrSource) + " = "
              sourceJoinOn += self.quoteIdentifier(throughAs) + "." + self.quoteIdentifier(identSource)

            // Filter statement for right side of through
            // Used by both join and subquery where
            targetJoinOn = self.quoteIdentifier(tableTarget) + "." + self.quoteIdentifier(attrTarget) + " = "
              targetJoinOn += self.quoteIdentifier(throughAs) + "." + self.quoteIdentifier(identTarget)

            // Generate join SQL for left side of through
            joinQueryItem += joinType + self.quoteIdentifier(throughTable) + " " + self.quoteIdentifier(throughAs) + " ON "
              joinQueryItem += sourceJoinOn

            // Generate join SQL for right side of through
            joinQueryItem += joinType + self.quoteIdentifier(table) + " " + self.quoteIdentifier(as) + " ON "
              joinQueryItem += targetJoinOn


            if (include.where) {
              targetWhere = self.getWhereConditions(include.where, self.sequelize.literal(self.quoteIdentifier(as)), include.daoFactory, whereOptions)
              joinQueryItem += " AND "+ targetWhere
              if (subQuery) {
                if (!options.where) options.where = {}

                // Creating the as-is where for the subQuery, checks that the required association exists
                var _where = "(";
                  _where += "SELECT "+self.quoteIdentifier(identSource)+" FROM " + self.quoteIdentifier(throughTable) + " " + self.quoteIdentifier(throughAs);
                  _where += joinType + self.quoteIdentifier(table) + " " + self.quoteIdentifier(as) + " ON "+targetJoinOn;
                  _where += " WHERE " + sourceJoinOn + " AND " + targetWhere + " AND ROWNUM = 1"
                _where += ")";
                _where += " IS NOT NULL"

                options.where["__"+throughAs] = self.sequelize.asIs(_where)
              }
            }
          } else {
            var primaryKeysLeft = ((association.associationType === 'BelongsTo') ? Object.keys(association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
              , tableLeft       = ((association.associationType === 'BelongsTo') ? as : parentTable)
              , attrLeft        = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])
              , tableRight      = ((association.associationType === 'BelongsTo') ? parentTable : as)
              , attrRight       = association.identifier
              , where

            // Filter statement
            // Used by both join and subquery where

            if (subQuery && !include.subQuery && include.parent.subQuery) {
              where = self.quoteIdentifier(tableLeft + "." + attrLeft) + " = "
            } else {
              where = self.quoteIdentifier(tableLeft) + "." + self.quoteIdentifier(attrLeft) + " = "
            }
            where += self.quoteIdentifier(tableRight) + "." + self.quoteIdentifier(attrRight)

            // Generate join SQL
            joinQueryItem += joinType + self.quoteIdentifier(table) + " " + self.quoteIdentifier(as) + " ON "
              joinQueryItem += where

            if (include.where) {
              joinQueryItem += " AND "+self.getWhereConditions(include.where, self.sequelize.literal(self.quoteIdentifier(as)), include.daoFactory, whereOptions)

              // If its a multi association we need to add a where query to the main where (executed in the subquery)
              if (subQuery && association.isMultiAssociation) {
                if (!options.where) options.where = {}
                // Creating the as-is where for the subQuery, checks that the required association exists
                options.where["__"+as] = self.sequelize.asIs("(SELECT "+self.quoteIdentifier(attrRight)+" FROM " + self.quoteIdentifier(tableRight) + " WHERE " + where + " AND ROWNUM = 1) IS NOT NULL")
              }
            }
          }

          if (include.subQuery && subQuery) {
            joinQueries.subQuery.push(joinQueryItem);
          } else {
            joinQueries.mainQuery.push(joinQueryItem);
          }

          if (include.include) {
            include.include.forEach(function(childInclude) {
              if (childInclude._pseudo) return
              var childJoinQueries = generateJoinQueries(childInclude, as)

              if (childInclude.subQuery && subQuery) {
                joinQueries.subQuery = joinQueries.subQuery.concat(childJoinQueries.subQuery)
              } else {
                joinQueries.mainQuery = joinQueries.mainQuery.concat(childJoinQueries.mainQuery)
              }
            }.bind(this))
          }
          return joinQueries
        }

        // Loop through includes and generate subqueries
        options.include.forEach(function(include) {
          var joinQueries = generateJoinQueries(include, tableName)

          subJoinQueries = subJoinQueries.concat(joinQueries.subQuery)
          mainJoinQueries = mainJoinQueries.concat(joinQueries.mainQuery)
        }.bind(this))
      }

      // If using subQuery select defined subQuery attributes and join subJoinQueries
      if (subQuery) {
        subQueryItems.push("SELECT " + subQueryAttributes.join(', ') + " FROM " + options.table)
        subQueryItems.push(subJoinQueries.join(''))

      // Else do it the reguar way
      } else {
        mainQueryItems.push("SELECT " + mainAttributes.join(', ') + " FROM " + options.table)
        mainQueryItems.push(mainJoinQueries.join(''))
      }

      // Add WHERE to sub or main query
      if (options.hasOwnProperty('where')) {
        options.where = this.getWhereConditions(options.where, tableName, factory, options)
        if (subQuery) {
          subQueryItems.push(" WHERE " + options.where)
        } else {
          mainQueryItems.push(" WHERE " + options.where)
        }
      }

      // Add GROUP BY to sub or main query
      if (options.group) {
        options.group = Array.isArray(options.group) ? options.group.map(function (t) { return this.quote(t, factory) }.bind(this)).join(', ') : options.group
        if (subQuery) {
          subQueryItems.push(" GROUP BY " + options.group)
        } else {
          mainQueryItems.push(" GROUP BY " + options.group)
        }
      }
      
      // Add HAVING to sub or main query
      if (options.hasOwnProperty('having')) {
        options.having = this.getWhereConditions(options.having, tableName, factory, options, false)
        if (subQuery) {
          subQueryItems.push(" HAVING " + options.having)
        } else {
          mainQueryItems.push(" HAVING " + options.having)
        }
      }

      // Add ORDER to sub or main query
      if (options.order) {
        var mainQueryOrder = [];
        var subQueryOrder = [];

        if (Array.isArray(options.order)) {
          options.order.forEach(function (t) {
            var strOrder = this.quote(t, factory)
            var tableName = this.getTableNameOrder(t)
            if (subQuery && !(t[0] instanceof daoFactory) && !(t[0].model instanceof daoFactory)) {
              if(tableName && subQueryAs.indexOf(tableName) > -1){
                subQueryOrder.push(strOrder)
              }              
            }
            
            if(subQuery && tableName !== subQueryAs[0] && subQueryAs.indexOf(tableName) > -1){
              mainQueryOrder.push(this.getSpecificQuoteOrder(t))
            }else{
              mainQueryOrder.push(strOrder)
            }
          }.bind(this))
        } else {
          mainQueryOrder.push(options.order)
        }
        
        if (mainQueryOrder.length) {
          mainQueryItems.push(" ORDER BY " + mainQueryOrder.join(', '))
        }
        if (subQueryOrder.length) {
          subQueryItems.push(" ORDER BY " + subQueryOrder.join(', '))
        }
      }



      // If using subQuery, select attributes from wrapped subQuery and join out join tables
      if (subQuery) {
        query = "SELECT " + mainAttributes.join(', ') + " FROM ("
          query += subQueryItems.join('')
        query += ") "+options.table
        query += mainJoinQueries.join('')
        query += mainQueryItems.join('')
      } else {
        query = mainQueryItems.join('')
      }


      var query = this.addLimitAndOffset(options, query)

      // Add LIMIT, OFFSET to sub or main query
      // if (limitOrder) {
      //   if (subQuery) {
      //     subQueryItems.push(limitOrder)
      //   } else {
      //     mainQueryItems.push(limitOrder)
      //   }
      // }

      //query += ";";

      return query
    },

    oracleDataTypeMapping: function(tableName, attr, dataType) {
      if (Utils._.includes(dataType, 'UUID')) {
        dataType = dataType.replace(/UUID/, 'CHAR(36) BINARY')
      }
      if (Utils._.includes(dataType, 'DATETIME')) {
        dataType = dataType.replace(/DATETIME/, 'TIMESTAMP')
      }
      if (Utils._.includes(dataType, 'VARCHAR') && !Utils._.includes(dataType, 'VARCHAR2')) {
        dataType = dataType.replace(/VARCHAR/, 'VARCHAR2')
      }

      return dataType
    }
  }

  return Utils._.extend(Utils._.clone(require("../abstract/query-generator")), QueryGenerator)
})()