"use strict";
const AbstractQuery = require("../abstract/query");
const QueryTypes = require("../../query-types");
const sequelizeErrors = require("../../errors");
const _ = require("lodash");
const { logger } = require("../../utils/logger");
const debug = logger.debugContext("sql:pg");
class Query extends AbstractQuery {
  static formatBindParameters(sql, values, dialect) {
    const stringReplaceFunc = (value) => typeof value === "string" ? value.replace(/\0/g, "\\0") : value;
    let bindParam;
    if (Array.isArray(values)) {
      bindParam = values.map(stringReplaceFunc);
      sql = AbstractQuery.formatBindParameters(sql, values, dialect, { skipValueReplace: true })[0];
    } else {
      bindParam = [];
      let i = 0;
      const seen = {};
      const replacementFunc = (match, key, values2) => {
        if (seen[key] !== void 0) {
          return seen[key];
        }
        if (values2[key] !== void 0) {
          i = i + 1;
          bindParam.push(stringReplaceFunc(values2[key]));
          seen[key] = `$${i}`;
          return `$${i}`;
        }
        return void 0;
      };
      sql = AbstractQuery.formatBindParameters(sql, values, dialect, replacementFunc)[0];
    }
    return [sql, bindParam];
  }
  async run(sql, parameters) {
    const { connection } = this;
    if (!_.isEmpty(this.options.searchPath)) {
      sql = this.sequelize.getQueryInterface().queryGenerator.setSearchPath(this.options.searchPath) + sql;
    }
    if (this.sequelize.options.minifyAliases && this.options.includeAliases) {
      _.toPairs(this.options.includeAliases).sort((a, b) => b[1].length - a[1].length).forEach(([alias, original]) => {
        const reg = new RegExp(_.escapeRegExp(original), "g");
        sql = sql.replace(reg, alias);
      });
    }
    this.sql = sql;
    const query = parameters && parameters.length ? new Promise((resolve, reject) => connection.query(sql, parameters, (error, result) => error ? reject(error) : resolve(result))) : new Promise((resolve, reject) => connection.query(sql, (error, result) => error ? reject(error) : resolve(result)));
    const complete = this._logQuery(sql, debug, parameters);
    let queryResult;
    const errForStack = new Error();
    try {
      queryResult = await query;
    } catch (error) {
      if (error.code === "ECONNRESET" || /Unable to set non-blocking to true/i.test(error) || /SSL SYSCALL error: EOF detected/i.test(error) || /Local: Authentication failure/i.test(error)) {
        connection._invalid = true;
      }
      error.sql = sql;
      error.parameters = parameters;
      throw this.formatError(error, errForStack.stack);
    }
    complete();
    let rows = Array.isArray(queryResult) ? queryResult.reduce((allRows, r) => allRows.concat(r.rows || []), []) : queryResult.rows;
    const rowCount = Array.isArray(queryResult) ? queryResult.reduce((count, r) => Number.isFinite(r.rowCount) ? count + r.rowCount : count, 0) : queryResult.rowCount || 0;
    if (this.sequelize.options.minifyAliases && this.options.aliasesMapping) {
      rows = rows.map((row) => _.toPairs(row).reduce((acc, [key, value]) => {
        const mapping = this.options.aliasesMapping.get(key);
        acc[mapping || key] = value;
        return acc;
      }, {}));
    }
    const isTableNameQuery = sql.startsWith("SELECT table_name FROM information_schema.tables");
    const isRelNameQuery = sql.startsWith("SELECT relname FROM pg_class WHERE oid IN");
    if (isRelNameQuery) {
      return rows.map((row) => ({
        name: row.relname,
        tableName: row.relname.split("_")[0]
      }));
    }
    if (isTableNameQuery) {
      return rows.map((row) => Object.values(row));
    }
    if (rows[0] && rows[0].sequelize_caught_exception !== void 0) {
      if (rows[0].sequelize_caught_exception !== null) {
        throw this.formatError({
          sql,
          parameters,
          code: "23505",
          detail: rows[0].sequelize_caught_exception
        });
      }
      for (const row of rows) {
        delete row.sequelize_caught_exception;
      }
    }
    if (this.isShowIndexesQuery()) {
      for (const row of rows) {
        const attributes = /ON .*? (?:USING .*?\s)?\(([^]*)\)/gi.exec(row.definition)[1].split(",");
        const columns = _.zipObject(row.column_indexes, this.sequelize.getQueryInterface().queryGenerator.fromArray(row.column_names));
        delete row.column_indexes;
        delete row.column_names;
        let field;
        let attribute;
        row.fields = row.indkey.split(" ").map((indKey, index) => {
          field = columns[indKey];
          if (!field) {
            return null;
          }
          attribute = attributes[index];
          return {
            attribute: field,
            collate: attribute.match(/COLLATE "(.*?)"/) ? /COLLATE "(.*?)"/.exec(attribute)[1] : void 0,
            order: attribute.includes("DESC") ? "DESC" : attribute.includes("ASC") ? "ASC" : void 0,
            length: void 0
          };
        }).filter((n) => n !== null);
        delete row.columns;
      }
      return rows;
    }
    if (this.isForeignKeysQuery()) {
      const result = [];
      for (const row of rows) {
        let defParts;
        if (row.condef !== void 0 && (defParts = row.condef.match(/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?( ON (UPDATE|DELETE) (CASCADE|RESTRICT))?/))) {
          row.id = row.constraint_name;
          row.table = defParts[2];
          row.from = defParts[1];
          row.to = defParts[3];
          let i;
          for (i = 5; i <= 8; i += 3) {
            if (/(UPDATE|DELETE)/.test(defParts[i])) {
              row[`on_${defParts[i].toLowerCase()}`] = defParts[i + 1];
            }
          }
        }
        result.push(row);
      }
      return result;
    }
    if (this.isSelectQuery()) {
      let result = rows;
      if (this.options.raw === false && this.sequelize.options.quoteIdentifiers === false) {
        const attrsMap = _.reduce(this.model.rawAttributes, (m, v, k) => {
          m[k.toLowerCase()] = k;
          return m;
        }, {});
        result = rows.map((row) => {
          return _.mapKeys(row, (value, key) => {
            const targetAttr = attrsMap[key];
            if (typeof targetAttr === "string" && targetAttr !== key) {
              return targetAttr;
            }
            return key;
          });
        });
      }
      return this.handleSelectQuery(result);
    }
    if (QueryTypes.DESCRIBE === this.options.type) {
      const result = {};
      for (const row of rows) {
        result[row.Field] = {
          type: row.Type.toUpperCase(),
          allowNull: row.Null === "YES",
          defaultValue: row.Default,
          comment: row.Comment,
          special: row.special ? this.sequelize.getQueryInterface().queryGenerator.fromArray(row.special) : [],
          primaryKey: row.Constraint === "PRIMARY KEY"
        };
        if (result[row.Field].type === "BOOLEAN") {
          result[row.Field].defaultValue = { "false": false, "true": true }[result[row.Field].defaultValue];
          if (result[row.Field].defaultValue === void 0) {
            result[row.Field].defaultValue = null;
          }
        }
        if (typeof result[row.Field].defaultValue === "string") {
          result[row.Field].defaultValue = result[row.Field].defaultValue.replace(/'/g, "");
          if (result[row.Field].defaultValue.includes("::")) {
            const split = result[row.Field].defaultValue.split("::");
            if (split[1].toLowerCase() !== "regclass)") {
              result[row.Field].defaultValue = split[0];
            }
          }
        }
      }
      return result;
    }
    if (this.isVersionQuery()) {
      return rows[0].server_version;
    }
    if (this.isShowOrDescribeQuery()) {
      return rows;
    }
    if (QueryTypes.BULKUPDATE === this.options.type) {
      if (!this.options.returning) {
        return parseInt(rowCount, 10);
      }
      return this.handleSelectQuery(rows);
    }
    if (QueryTypes.BULKDELETE === this.options.type) {
      return parseInt(rowCount, 10);
    }
    if (this.isInsertQuery() || this.isUpdateQuery() || this.isUpsertQuery()) {
      if (this.instance && this.instance.dataValues) {
        if (this.isInsertQuery() && !this.isUpsertQuery() && rowCount === 0) {
          throw new sequelizeErrors.EmptyResultError();
        }
        for (const key in rows[0]) {
          if (Object.prototype.hasOwnProperty.call(rows[0], key)) {
            const record = rows[0][key];
            const attr = _.find(this.model.rawAttributes, (attribute) => attribute.fieldName === key || attribute.field === key);
            this.instance.dataValues[attr && attr.fieldName || key] = record;
          }
        }
      }
      if (this.isUpsertQuery()) {
        return [
          this.instance,
          null
        ];
      }
      return [
        this.instance || rows && (this.options.plain && rows[0] || rows) || void 0,
        rowCount
      ];
    }
    if (this.isRawQuery()) {
      return [rows, queryResult];
    }
    return rows;
  }
  formatError(err, errStack) {
    let match;
    let table;
    let index;
    let fields;
    let errors;
    let message;
    const code = err.code || err.sqlState;
    const errMessage = err.message || err.messagePrimary;
    const errDetail = err.detail || err.messageDetail;
    switch (code) {
      case "23503":
        index = errMessage.match(/violates foreign key constraint "(.+?)"/);
        index = index ? index[1] : void 0;
        table = errMessage.match(/on table "(.+?)"/);
        table = table ? table[1] : void 0;
        return new sequelizeErrors.ForeignKeyConstraintError({
          message: errMessage,
          fields: null,
          index,
          table,
          parent: err,
          stack: errStack
        });
      case "23505":
        if (errDetail && (match = errDetail.replace(/"/g, "").match(/Key \((.*?)\)=\((.*?)\)/))) {
          fields = _.zipObject(match[1].split(", "), match[2].split(", "));
          errors = [];
          message = "Validation error";
          _.forOwn(fields, (value, field) => {
            errors.push(new sequelizeErrors.ValidationErrorItem(this.getUniqueConstraintErrorMessage(field), "unique violation", field, value, this.instance, "not_unique"));
          });
          if (this.model && this.model.uniqueKeys) {
            _.forOwn(this.model.uniqueKeys, (constraint) => {
              if (_.isEqual(constraint.fields, Object.keys(fields)) && !!constraint.msg) {
                message = constraint.msg;
                return false;
              }
            });
          }
          return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields, stack: errStack });
        }
        return new sequelizeErrors.UniqueConstraintError({
          message: errMessage,
          parent: err,
          stack: errStack
        });
      case "23P01":
        match = errDetail.match(/Key \((.*?)\)=\((.*?)\)/);
        if (match) {
          fields = _.zipObject(match[1].split(", "), match[2].split(", "));
        }
        message = "Exclusion constraint error";
        return new sequelizeErrors.ExclusionConstraintError({
          message,
          constraint: err.constraint,
          fields,
          table: err.table,
          parent: err,
          stack: errStack
        });
      case "42704":
        if (err.sql && /(CONSTRAINT|INDEX)/gi.test(err.sql)) {
          message = "Unknown constraint error";
          index = errMessage.match(/(?:constraint|index) "(.+?)"/i);
          index = index ? index[1] : void 0;
          table = errMessage.match(/relation "(.+?)"/i);
          table = table ? table[1] : void 0;
          throw new sequelizeErrors.UnknownConstraintError({
            message,
            constraint: index,
            fields,
            table,
            parent: err,
            stack: errStack
          });
        }
      default:
        return new sequelizeErrors.DatabaseError(err, { stack: errStack });
    }
  }
  isForeignKeysQuery() {
    return /SELECT conname as constraint_name, pg_catalog\.pg_get_constraintdef\(r\.oid, true\) as condef FROM pg_catalog\.pg_constraint r WHERE r\.conrelid = \(SELECT oid FROM pg_class WHERE relname = '.*' LIMIT 1\) AND r\.contype = 'f' ORDER BY 1;/.test(this.sql);
  }
  getInsertIdField() {
    return "id";
  }
}
module.exports = Query;
module.exports.Query = Query;
module.exports.default = Query;
//# sourceMappingURL=query.js.map
