compiler.js 9.59 KB
'use strict';

var _ = require('lodash');
var inherits = require('inherits');
var Oracle_Compiler = require('../../oracle/query/compiler');
var ReturningHelper = require('../utils').ReturningHelper;
var BlobHelper = require('../utils').BlobHelper;

function Oracledb_Compiler(client, builder) {
  Oracle_Compiler.call(this, client, builder);
}
inherits(Oracledb_Compiler, Oracle_Compiler);

_.assign(Oracledb_Compiler.prototype, {
  // Compiles an "insert" query, allowing for multiple
  // inserts using a single query statement.
  insert: function insert() {
    var self = this;
    var outBindPrep = this._prepOutbindings(this.single.insert, this.single.returning);
    var outBinding = outBindPrep.outBinding;
    var returning = outBindPrep.returning;
    var insertValues = outBindPrep.values;

    if (Array.isArray(insertValues) && insertValues.length === 1 && _.isEmpty(insertValues[0])) {
      return this._addReturningToSqlAndConvert('insert into ' + this.tableName + ' (' + this.formatter.wrap(this.single.returning) + ') values (default)', outBinding[0], this.tableName, returning);
    }

    if (_.isEmpty(this.single.insert) && typeof this.single.insert !== 'function') {
      return '';
    }

    var insertData = this._prepInsert(insertValues);

    var sql = {};

    if (_.isString(insertData)) {
      return this._addReturningToSqlAndConvert('insert into ' + this.tableName + ' ' + insertData, outBinding[0], this.tableName, returning);
    }

    if (insertData.values.length === 1) {
      return this._addReturningToSqlAndConvert('insert into ' + this.tableName + ' (' + this.formatter.columnize(insertData.columns) + ') values (' + this.formatter.parameterize(insertData.values[0]) + ')', outBinding[0], this.tableName, returning);
    }

    var insertDefaultsOnly = insertData.columns.length === 0;
    sql.returning = returning;
    sql.sql = 'begin ' + _.map(insertData.values, function (value, index) {
      var parameterizedValues = !insertDefaultsOnly ? self.formatter.parameterize(value, self.client.valueForUndefined) : '';
      var subSql = 'insert into ' + self.tableName;

      if (insertDefaultsOnly) {
        // No columns given so only the default value
        subSql += ' (' + self.formatter.wrap(self.single.returning) + ') values (default)';
      } else {
        subSql += ' (' + self.formatter.columnize(insertData.columns) + ') values (' + parameterizedValues + ')';
      }

      var returningClause = '';
      var intoClause = '';
      var usingClause = '';
      var outClause = '';

      _.each(value, function (val) {
        if (!(val instanceof BlobHelper)) {
          usingClause += ' ?,';
        }
      });
      usingClause = usingClause.slice(0, -1);

      // Build returning and into clauses
      _.each(outBinding[index], function (ret) {
        var columnName = ret.columnName || ret;
        returningClause += '"' + columnName + '",';
        intoClause += ' ?,';
        outClause += ' out ?,';

        // Add Helpers to bindings
        if (ret instanceof BlobHelper) {
          return self.formatter.bindings.push(ret);
        }
        self.formatter.bindings.push(new ReturningHelper(columnName));
      });

      // Strip last comma
      returningClause = returningClause.slice(0, -1);
      intoClause = intoClause.slice(0, -1);
      outClause = outClause.slice(0, -1);

      if (returningClause && intoClause) {
        subSql += ' returning ' + returningClause + ' into' + intoClause;
      }

      // Pre bind position because subSql is an execute immediate parameter
      // later position binding will only convert the ? params
      subSql = self.formatter.client.positionBindings(subSql);
      var parameterizedValuesWithoutDefaultAndBlob = parameterizedValues.replace('DEFAULT, ', '').replace(', DEFAULT', '').replace('EMPTY_BLOB(), ', '').replace(', EMPTY_BLOB()', '');
      return 'execute immediate \'' + subSql.replace(/'/g, "''") + (parameterizedValuesWithoutDefaultAndBlob || value ? '\' using ' : '') + parameterizedValuesWithoutDefaultAndBlob + (parameterizedValuesWithoutDefaultAndBlob && outClause ? ',' : '') + outClause + ';';
    }).join(' ') + 'end;';

    sql.outBinding = outBinding;
    if (returning[0] === '*') {
      returning = returning.slice(0, -1);

      // Generate select statement with special order by
      // to keep the order because 'in (..)' may change the order
      sql.returningSql = function () {
        return 'select * from ' + self.tableName + ' where ROWID in (' + this.outBinding.map(function (v, i) {
          return ':' + (i + 1);
        }).join(', ') + ')' + ' order by case ROWID ' + this.outBinding.map(function (v, i) {
          return 'when CHARTOROWID(:' + (i + 1) + ') then ' + i;
        }).join(' ') + ' end';
      };
    }

    return sql;
  },

  _addReturningToSqlAndConvert: function _addReturningToSqlAndConvert(sql, outBinding, tableName, returning) {
    var self = this;
    var res = {
      sql: sql
    };

    if (!outBinding) {
      return res;
    }
    var returningValues = Array.isArray(outBinding) ? outBinding : [outBinding];
    var returningClause = '';
    var intoClause = '';
    // Build returning and into clauses
    _.each(returningValues, function (ret) {
      var columnName = ret.columnName || ret;
      returningClause += '"' + columnName + '",';
      intoClause += '?,';

      // Add Helpers to bindings
      if (ret instanceof BlobHelper) {
        return self.formatter.bindings.push(ret);
      }
      self.formatter.bindings.push(new ReturningHelper(columnName));
    });
    res.sql = sql;

    // Strip last comma
    returningClause = returningClause.slice(0, -1);
    intoClause = intoClause.slice(0, -1);
    if (returningClause && intoClause) {
      res.sql += ' returning ' + returningClause + ' into ' + intoClause;
    }
    res.outBinding = [outBinding];
    if (returning[0] === '*') {
      res.returningSql = function () {
        return 'select * from ' + self.tableName + ' where ROWID = :1';
      };
    }
    res.returning = returning;

    return res;
  },

  _prepOutbindings: function _prepOutbindings(paramValues, paramReturning) {
    var result = {};
    var params = paramValues || [];
    var returning = paramReturning || [];
    if (!Array.isArray(params) && _.isPlainObject(paramValues)) {
      params = [params];
    }
    // Always wrap returning argument in array
    if (returning && !Array.isArray(returning)) {
      returning = [returning];
    }

    var outBinding = [];
    // Handle Buffer value as Blob
    _.each(params, function (values, index) {
      if (returning[0] === '*') {
        outBinding[index] = ['ROWID'];
      } else {
        outBinding[index] = _.clone(returning);
      }
      _.each(values, function (value, key) {
        if (value instanceof Buffer) {
          values[key] = new BlobHelper(key, value);

          // Delete blob duplicate in returning
          var blobIndex = outBinding[index].indexOf(key);
          if (blobIndex >= 0) {
            outBinding[index].splice(blobIndex, 1);
            values[key].returning = true;
          }
          outBinding[index].push(values[key]);
        }
        if (_.isUndefined(value)) {
          delete params[index][key];
        }
      });
    });
    result.returning = returning;
    result.outBinding = outBinding;
    result.values = params;
    return result;
  },

  update: function update() {
    var self = this;
    var sql = {};
    var outBindPrep = this._prepOutbindings(this.single.update, this.single.returning);
    var outBinding = outBindPrep.outBinding;
    var returning = outBindPrep.returning;

    var updates = this._prepUpdate(this.single.update);
    var where = this.where();

    var returningClause = '';
    var intoClause = '';

    if (_.isEmpty(this.single.update) && typeof this.single.update !== 'function') {
      return '';
    }

    // Build returning and into clauses
    _.each(outBinding, function (out) {
      _.each(out, function (ret) {
        var columnName = ret.columnName || ret;
        returningClause += '"' + columnName + '",';
        intoClause += ' ?,';

        // Add Helpers to bindings
        if (ret instanceof BlobHelper) {
          return self.formatter.bindings.push(ret);
        }
        self.formatter.bindings.push(new ReturningHelper(columnName));
      });
    });
    // Strip last comma
    returningClause = returningClause.slice(0, -1);
    intoClause = intoClause.slice(0, -1);

    sql.outBinding = outBinding;
    sql.returning = returning;
    sql.sql = 'update ' + this.tableName + ' set ' + updates.join(', ') + (where ? ' ' + where : '');
    if (outBinding.length && !_.isEmpty(outBinding[0])) {
      sql.sql += ' returning ' + returningClause + ' into' + intoClause;
    }
    if (returning[0] === '*') {

      sql.returningSql = function () {

        var sql = 'select * from ' + self.tableName;
        var modifiedRowsCount = this.rowsAffected.length || this.rowsAffected;
        var returningSqlIn = ' where ROWID in (';
        var returningSqlOrderBy = ') order by case ROWID ';

        // Needs special order by because in(...) change result order
        for (var i = 0; i < modifiedRowsCount; i++) {
          if (this.returning[0] === '*') {
            returningSqlIn += ':' + (i + 1) + ', ';
            returningSqlOrderBy += 'when CHARTOROWID(:' + (i + 1) + ') then ' + i + ' ';
          }
        }
        if (this.returning[0] === '*') {
          this.returning = this.returning.slice(0, -1);
          returningSqlIn = returningSqlIn.slice(0, -2);
          returningSqlOrderBy = returningSqlOrderBy.slice(0, -1);
        }
        return sql += returningSqlIn + returningSqlOrderBy + ' end';
      };
    }

    return sql;
  }

});

module.exports = Oracledb_Compiler;