Sequelize

declaration

Utils

Utils
    var Utils = require("../../utils")
      , util  = require("util")
      , tables = {}
      , primaryKeys = {};
    
    function removeQuotes(s, quoteChar) {
      quoteChar = quoteChar || '"'
      return s.replace(new RegExp(quoteChar, 'g'), '')
    }
    
    function addQuotes(s, quoteChar) {
      quoteChar = quoteChar || '"'
      return quoteChar + removeQuotes(s) + quoteChar
    }
    
    function pgEscape(s) {
      s = Utils.escape(s)
    
      if (typeof s == 'string') {
        // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
        s = s.replace(/\\'/g, "''")
      }
    
      return s
    }
    
    function padInt(i) {
      return (i < 10) ? '0' + i.toString() : i.toString()
    }
    function pgSqlDate(dt) {
      var date = [ dt.getUTCFullYear(), padInt(dt.getUTCMonth()+1), padInt(dt.getUTCDate()) ].join('-')
      var time = [ dt.getUTCHours(), padInt(dt.getUTCMinutes()), padInt(dt.getUTCSeconds())].join(':')
      return date + ' ' + time + '.' + ((dt.getTime() % 1000) * 1000)
    }
    
    function pgDataTypeMapping(tableName, attr, dataType) {
      if (Utils._.includes(dataType, 'PRIMARY KEY')) {
        primaryKeys[tableName].push(attr)
        dataType = dataType.replace(/PRIMARY KEY/, '')
      }
    
      if (Utils._.includes(dataType, 'TINYINT(1)')) {
        dataType = dataType.replace(/TINYINT\(1\)/, 'BOOLEAN')
      }
    
      if (Utils._.includes(dataType, 'DATETIME')) {
        dataType = dataType.replace(/DATETIME/, 'TIMESTAMP')
      }
    
      if (Utils._.includes(dataType, 'SERIAL')) {
        dataType = dataType.replace(/INTEGER/, '')
        dataType = dataType.replace(/NOT NULL/, '')
        tables[tableName][attr] = 'serial'
      }
    
      return dataType
    }
    
    module.exports = (function() {
      var QueryGenerator = {
        options: {},
    
        createTableQuery: function(tableName, attributes, options) {
          options = Utils._.extend({
          }, options || {})
    
          primaryKeys[tableName] = []
          tables[tableName] = {}
    
          var query   = "CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>)"
            , attrStr = []
    
          for (var attr in attributes) {
            var dataType = pgDataTypeMapping(tableName, attr, attributes[attr])
            attrStr.push(addQuotes(attr) + " " + dataType)
          }
    
          var values  = {
            table: addQuotes(tableName),
            attributes: attrStr.join(", "),
          }
    
          var pks = primaryKeys[tableName].map(function(pk){ return addQuotes(pk) }).join(",")
          if (pks.length > 0) {
            values.attributes += ", PRIMARY KEY (" + pks + ")"
          }
    
          return Utils._.template(query)(values).trim() + ";"
        },
    
        dropTableQuery: function(tableName, options) {
          options = options || {}
          var query = "DROP TABLE IF EXISTS <%= table %>;"
          return Utils._.template(query)({table: addQuotes(tableName)})
        },
    
        renameTableQuery: function(before, after) {
          var query = "ALTER TABLE <%= before %> RENAME TO <%= after %>;"
          return Utils._.template(query)({ before: addQuotes(before), after: addQuotes(after) })
        },
    
        showTablesQuery: function() {
          return "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';"
        },
    
        describeTableQuery: function(tableName) {
          var query = 'SELECT column_name as "Field", column_default as "Default", is_nullable as "Null", data_type as "Type" FROM information_schema.columns WHERE table_name = <%= table %>;'
          return Utils._.template(query)({ table: addQuotes(tableName, "'") })
        },
    
        addColumnQuery: function(tableName, attributes) {
          var query      = "ALTER TABLE <%= tableName %> ADD COLUMN <%= attributes %>;"
            , attrString = []
    
          for (var attrName in attributes) {
            var definition = attributes[attrName]
    
            attrString.push(Utils._.template('<%= attrName %> <%= definition %>')({
              attrName: addQuotes(attrName),
              definition: pgDataTypeMapping(tableName, attrName, definition)
            }))
          }
    
          return Utils._.template(query)({ tableName: addQuotes(tableName), attributes: attrString.join(', ') })
        },
    
        removeColumnQuery: function(tableName, attributeName) {
          var query = "ALTER TABLE <%= tableName %> DROP COLUMN <%= attributeName %>;"
          return Utils._.template(query)({ tableName: addQuotes(tableName), attributeName: addQuotes(attributeName) })
        },
    
        changeColumnQuery: function(tableName, attributes) {
          var query = "ALTER TABLE <%= tableName %> ALTER COLUMN <%= query %>;"
            , sql   = []
    
          for (var attributeName in attributes) {
            var attrSql = ''
    
            if (definition.indexOf('NOT NULL') > 0) {
              attrSql += Utils._.template(query)({
                tableName: addQuotes(tableName),
                query: addQuotes(attributeName) + ' SET NOT NULL'
              })
              definition = definition.replace('NOT NULL', '').trim()
            } else {
              attrSql += Utils._.template(query)({
                tableName: addQuotes(tableName),
                query: addQuotes(attributeName) + ' DROP NOT NULL'
              })
            }
    
            attrSql += Utils._.template(query)({
              tableName: addQuotes(tableName),
              query: addQuotes(attributeName) + ' TYPE ' + definition
            })
    
            sql.push(attrSql)
          }
    
          return sql.join('')
        },
    
        renameColumnQuery: function(tableName, attrBefore, attributes) {
          var query      = "ALTER TABLE <%= tableName %> RENAME COLUMN <%= attributes %>;"
          var attrString = []
    
          for (var attributeName in attributes) {
            attrString.push(Utils._.template('<%= before %> TO <%= after %>')({
              before: addQuotes(attrBefore),
              after: addQuotes(attributeName),
            }))
          }
    
          return Utils._.template(query)({ tableName: addQuotes(tableName), attributes: attrString.join(', ') })
        },
    
        selectQuery: function(tableName, options) {
          options = options || {}
          options.table = Array.isArray(tableName) ? tableName.map(function(t){return addQuotes(t);}).join(", ") : addQuotes(tableName)
          options.attributes = options.attributes && options.attributes.map(function(attr){
            if(Array.isArray(attr) && attr.length == 2) {
              return [attr[0], addQuotes(removeQuotes(attr[1], '`'))].join(' as ')
            } else if (attr.indexOf('`') >= 0) {
              return attr.replace(/`/g, '"')
            } else {
              return addQuotes(attr)
            }
          }).join(", ")
    
          options.attributes = options.attributes || '*'
    
          var query = "SELECT <%= attributes %> FROM <%= table %>"
    
          if(options.where) {
            options.where = QueryGenerator.getWhereConditions(options.where)
            query += " WHERE <%= where %>"
          }
          if(options.order) {
            options.order = options.order.replace(/([^ ]+)(.*)/, function(m, g1, g2) { return addQuotes(g1)+g2 })
            query += " ORDER BY <%= order %>"
          }
          if(options.group) {
            options.group = addQuotes(options.group)
            query += " GROUP BY <%= group %>"
          }
          if(options.limit) query += " LIMIT <%= limit %>"
          if(options.offset) query += " OFFSET <%= offset %>"
    
          query += ";"
          return Utils._.template(query)(options)
        },
    
        insertQuery: function(tableName, attrValueHash) {
          attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
    
          var query     = "INSERT INTO <%= table %> (<%= attributes %>) VALUES (<%= values %>) RETURNING *;"
            , returning = []
    
          Utils._.forEach(attrValueHash, function(value, key, hash) {
            if (tables[tableName] && tables[tableName][key]) {
              switch (tables[tableName][key]) {
                case 'serial':
                  delete hash[key]
                  returning.push(key)
                  break
              }
            }
          });
    
          var replacements  = {
            table: addQuotes(tableName),
            attributes: Utils._.keys(attrValueHash).map(function(attr){return addQuotes(attr)}).join(","),
            values: Utils._.values(attrValueHash).map(function(value){
              return pgEscape((value instanceof Date) ? pgSqlDate(value) : value)
            }).join(",")
          }
    
          return Utils._.template(query)(replacements)
        },
    
        updateQuery: function(tableName, attrValueHash, where) {
          attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
    
          var query  = "UPDATE <%= table %> SET <%= values %> WHERE <%= where %>"
            , values = []
    
          for (var key in attrValueHash) {
            var value = attrValueHash[key]
            values.push(addQuotes(key) + "=" + pgEscape((value instanceof Date) ? pgSqlDate(value) : value))
          }
    
          var replacements = {
            table: addQuotes(tableName),
            values: values.join(","),
            where: QueryGenerator.getWhereConditions(where)
          }
    
          return Utils._.template(query)(replacements)
        },
    
        deleteQuery: function(tableName, where, options) {
          options = options || {}
          options.limit = options.limit || 1
    
          primaryKeys[tableName] = primaryKeys[tableName] || [];
    
          var query = "DELETE FROM <%= table %> WHERE <%= primaryKeys %> IN (SELECT <%= primaryKeysSelection %> FROM <%= table %> WHERE <%= where %> LIMIT <%= limit %>)"
    
          var pks;
          if (primaryKeys[tableName] && primaryKeys[tableName].length > 0) {
            pks = primaryKeys[tableName].map(function(pk) { return addQuotes(pk) }).join(',')
          } else {
            pks = addQuotes('id')
          }
    
          var replacements = {
            table: addQuotes(tableName),
            where: QueryGenerator.getWhereConditions(where),
            limit: pgEscape(options.limit),
            primaryKeys: primaryKeys[tableName].length > 1 ? '(' + pks + ')' : pks,
            primaryKeysSelection: pks
          }
    
          return Utils._.template(query)(replacements)
        },
    
        addIndexQuery: function(tableName, attributes, options) {
          var transformedAttributes = attributes.map(function(attribute) {
            if(typeof attribute == 'string')
              return addQuotes(attribute)
            else {
              var result = ""
    
              if(!attribute.attribute)
                throw new Error('The following index attribute has no attribute: ' + util.inspect(attribute))
    
              result += addQuotes(attribute.attribute)
    
              if(attribute.length)
                result += '(' + attribute.length + ')'
    
              if(attribute.order)
                result += ' ' + attribute.order
    
              return result
            }
          })
    
          var onlyAttributeNames = attributes.map(function(attribute) {
            return (typeof attribute == 'string') ? attribute : attribute.attribute
          })
    
          options = Utils._.extend({
            indicesType: null,
            indexName: Utils._.underscored(tableName + '_' + onlyAttributeNames.join('_')),
            parser: null
          }, options || {})
    
          return Utils._.compact([
            "CREATE", options.indicesType, "INDEX", addQuotes(options.indexName),
            (options.indexType ? ('USING ' + options.indexType) : undefined),
            "ON", addQuotes(tableName), '(' + transformedAttributes.join(', ') + ')'
          ]).join(' ')
        },
    
        showIndexQuery: function(tableName, options) {
          var query = "SELECT relname FROM pg_class WHERE oid IN ( SELECT indexrelid FROM pg_index, pg_class WHERE pg_class.relname='<%= tableName %>' AND pg_class.oid=pg_index.indrelid);"
          return Utils._.template(query)({ tableName: tableName });
        },
    
        removeIndexQuery: function(tableName, indexNameOrAttributes) {
          var sql       = "DROP INDEX IF EXISTS <%= indexName %>"
            , indexName = indexNameOrAttributes
    
          if(typeof indexName != 'string')
            indexName = Utils._.underscored(tableName + '_' + indexNameOrAttributes.join('_'))
    
          return Utils._.template(sql)({ tableName: addQuotes(tableName), indexName: addQuotes(indexName) })
        },
    
        getWhereConditions: function(smth) {
          var result = null
    
          if(Utils.isHash(smth))
            result = QueryGenerator.hashToWhereConditions(smth)
          else if(typeof smth == 'number')
            result = '\"id\"' + "=" + pgEscape(smth)
          else if(typeof smth == "string")
            result = smth
          else if(Array.isArray(smth))
            result = Utils.format(smth)
    
          return result
        },
    
        hashToWhereConditions: function(hash) {
          var result = []
    
          for (var key in hash) {
            var value = hash[key]
    
            //handle qualified key names
            var _key   = key.split('.').map(function(col){return addQuotes(col)}).join(".")
              , _value = null
    
            if(Array.isArray(value)) {
              _value = "(" + value.map(function(subValue) {
                return pgEscape(subValue);
              }).join(',') + ")"
    
              result.push([_key, _value].join(" IN "))
            }
            else if ((value) && (typeof value == 'object')) {
              //using as sentinel for join column => value
              _value = value.join.split('.').map(function(col){return addQuotes(col)}).join(".")
              result.push([_key, _value].join("="))
            } else {
              _value = pgEscape(value)
              result.push((_value == 'NULL') ? _key + " IS NULL" : [_key, _value].join("="))
            }
          }
    
          return result.join(' AND ')
        },
    
        attributesToSQL: function(attributes) {
          var result = {}
    
          for (var name in attributes) {
            var dataType = attributes[name]
    
            if(Utils.isHash(dataType)) {
              var template     = "<%= type %>"
                , replacements = { type: dataType.type }
    
              if(dataType.type == 'TINYINT(1)') dataType.type = 'BOOLEAN'
              if(dataType.type == 'DATETIME') dataType.type = 'TIMESTAMP'
    
              if(dataType.hasOwnProperty('allowNull') && (!dataType.allowNull)) template += " NOT NULL"
              if(dataType.autoIncrement) template +=" SERIAL"
              if(dataType.defaultValue != undefined) {
                template += " DEFAULT <%= defaultValue %>"
                replacements.defaultValue = pgEscape(dataType.defaultValue)
              }
              if(dataType.unique) template += " UNIQUE"
              if(dataType.primaryKey) template += " PRIMARY KEY"
    
              result[name] = Utils._.template(template)(replacements)
            } else {
              result[name] = dataType
            }
          }
    
          return result
        },
    
        findAutoIncrementField: function(factory) {
          var fields = []
    
          for (var name in factory.attributes) {
            var definition = factory.attributes[name]
    
            if (definition && (definition.indexOf('SERIAL') > -1)) {
              fields.push(name)
            }
          }
    
          return fields
        },
    
        databaseConnectionUri: function(config) {
          var template = '<%= protocol %>://<%= user %>:<%= password %>@<%= host %><% if(port) { %>:<%= port %><% } %>/<%= database %>';
    
          return Utils._.template(template)({
            user: config.username,
            password: config.password,
            database: config.database,
            host: config.host,
            port: config.port,
            protocol: config.protocol
          })
        }
      }
    
      return Utils._.extend(Utils._.clone(require("../query-generator")), QueryGenerator)
    })()