"use strict";
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
  for (var prop in b || (b = {}))
    if (__hasOwnProp.call(b, prop))
      __defNormalProp(a, prop, b[prop]);
  if (__getOwnPropSymbols)
    for (var prop of __getOwnPropSymbols(b)) {
      if (__propIsEnum.call(b, prop))
        __defNormalProp(a, prop, b[prop]);
    }
  return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
const _ = require("lodash");
const Utils = require("../../utils");
const AbstractQueryGenerator = require("../abstract/query-generator");
const util = require("util");
const Op = require("../../operators");
const JSON_FUNCTION_REGEX = /^\s*((?:[a-z]+_){0,2}jsonb?(?:_[a-z]+){0,2})\([^)]*\)/i;
const JSON_OPERATOR_REGEX = /^\s*(->>?|@>|<@|\?[|&]?|\|{2}|#-)/i;
const TOKEN_CAPTURE_REGEX = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i;
const FOREIGN_KEY_FIELDS = [
  "CONSTRAINT_NAME as constraint_name",
  "CONSTRAINT_NAME as constraintName",
  "CONSTRAINT_SCHEMA as constraintSchema",
  "CONSTRAINT_SCHEMA as constraintCatalog",
  "TABLE_NAME as tableName",
  "TABLE_SCHEMA as tableSchema",
  "TABLE_SCHEMA as tableCatalog",
  "COLUMN_NAME as columnName",
  "REFERENCED_TABLE_SCHEMA as referencedTableSchema",
  "REFERENCED_TABLE_SCHEMA as referencedTableCatalog",
  "REFERENCED_TABLE_NAME as referencedTableName",
  "REFERENCED_COLUMN_NAME as referencedColumnName"
].join(",");
const SNOWFLAKE_RESERVED_WORDS = "account,all,alter,and,any,as,between,by,case,cast,check,column,connect,connections,constraint,create,cross,current,current_date,current_time,current_timestamp,current_user,database,delete,distinct,drop,else,exists,false,following,for,from,full,grant,group,gscluster,having,ilike,in,increment,inner,insert,intersect,into,is,issue,join,lateral,left,like,localtime,localtimestamp,minus,natural,not,null,of,on,or,order,organization,qualify,regexp,revoke,right,rlike,row,rows,sample,schema,select,set,some,start,table,tablesample,then,to,trigger,true,try_cast,union,unique,update,using,values,view,when,whenever,where,with".split(",");
const typeWithoutDefault = /* @__PURE__ */ new Set(["BLOB", "TEXT", "GEOMETRY", "JSON"]);
class SnowflakeQueryGenerator extends AbstractQueryGenerator {
  constructor(options) {
    super(options);
    this.OperatorMap = __spreadProps(__spreadValues({}, this.OperatorMap), {
      [Op.regexp]: "REGEXP",
      [Op.notRegexp]: "NOT REGEXP"
    });
  }
  createDatabaseQuery(databaseName, options) {
    options = __spreadValues({
      charset: null,
      collate: null
    }, options);
    return Utils.joinSQLFragments([
      "CREATE DATABASE IF NOT EXISTS",
      this.quoteIdentifier(databaseName),
      options.charset && `DEFAULT CHARACTER SET ${this.escape(options.charset)}`,
      options.collate && `DEFAULT COLLATE ${this.escape(options.collate)}`,
      ";"
    ]);
  }
  dropDatabaseQuery(databaseName) {
    return `DROP DATABASE IF EXISTS ${this.quoteIdentifier(databaseName)};`;
  }
  createSchema() {
    return "SHOW TABLES";
  }
  showSchemasQuery() {
    return "SHOW TABLES";
  }
  versionQuery() {
    return "SELECT CURRENT_VERSION()";
  }
  createTableQuery(tableName, attributes, options) {
    options = __spreadValues({
      charset: null,
      rowFormat: null
    }, options);
    const primaryKeys = [];
    const foreignKeys = {};
    const attrStr = [];
    for (const attr in attributes) {
      if (!Object.prototype.hasOwnProperty.call(attributes, attr))
        continue;
      const dataType = attributes[attr];
      let match;
      if (dataType.includes("PRIMARY KEY")) {
        primaryKeys.push(attr);
        if (dataType.includes("REFERENCES")) {
          match = dataType.match(/^(.+) (REFERENCES.*)$/);
          attrStr.push(`${this.quoteIdentifier(attr)} ${match[1].replace("PRIMARY KEY", "")}`);
          foreignKeys[attr] = match[2];
        } else {
          attrStr.push(`${this.quoteIdentifier(attr)} ${dataType.replace("PRIMARY KEY", "")}`);
        }
      } else if (dataType.includes("REFERENCES")) {
        match = dataType.match(/^(.+) (REFERENCES.*)$/);
        attrStr.push(`${this.quoteIdentifier(attr)} ${match[1]}`);
        foreignKeys[attr] = match[2];
      } else {
        attrStr.push(`${this.quoteIdentifier(attr)} ${dataType}`);
      }
    }
    const table = this.quoteTable(tableName);
    let attributesClause = attrStr.join(", ");
    const pkString = primaryKeys.map((pk) => this.quoteIdentifier(pk)).join(", ");
    if (options.uniqueKeys) {
      _.each(options.uniqueKeys, (columns, indexName) => {
        if (columns.customIndex) {
          if (typeof indexName !== "string") {
            indexName = `uniq_${tableName}_${columns.fields.join("_")}`;
          }
          attributesClause += `, UNIQUE ${this.quoteIdentifier(indexName)} (${columns.fields.map((field) => this.quoteIdentifier(field)).join(", ")})`;
        }
      });
    }
    if (pkString.length > 0) {
      attributesClause += `, PRIMARY KEY (${pkString})`;
    }
    for (const fkey in foreignKeys) {
      if (Object.prototype.hasOwnProperty.call(foreignKeys, fkey)) {
        attributesClause += `, FOREIGN KEY (${this.quoteIdentifier(fkey)}) ${foreignKeys[fkey]}`;
      }
    }
    return Utils.joinSQLFragments([
      "CREATE TABLE IF NOT EXISTS",
      table,
      `(${attributesClause})`,
      options.comment && typeof options.comment === "string" && `COMMENT ${this.escape(options.comment)}`,
      options.charset && `DEFAULT CHARSET=${options.charset}`,
      options.collate && `COLLATE ${options.collate}`,
      options.rowFormat && `ROW_FORMAT=${options.rowFormat}`,
      ";"
    ]);
  }
  describeTableQuery(tableName, schema, schemaDelimiter) {
    const table = this.quoteTable(this.addSchema({
      tableName,
      _schema: schema,
      _schemaDelimiter: schemaDelimiter
    }));
    return `SHOW FULL COLUMNS FROM ${table};`;
  }
  showTablesQuery(database) {
    return Utils.joinSQLFragments([
      "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'",
      database ? `AND TABLE_SCHEMA = ${this.escape(database)}` : "AND TABLE_SCHEMA NOT IN ( 'INFORMATION_SCHEMA', 'PERFORMANCE_SCHEMA', 'SYS')",
      ";"
    ]);
  }
  tableExistsQuery(table) {
    const tableName = table.tableName || table;
    const schema = table.schema;
    return Utils.joinSQLFragments([
      "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'",
      `AND TABLE_SCHEMA = ${schema !== void 0 ? this.escape(schema) : "CURRENT_SCHEMA()"}`,
      `AND TABLE_NAME = ${this.escape(tableName)}`,
      ";"
    ]);
  }
  addColumnQuery(table, key, dataType) {
    return Utils.joinSQLFragments([
      "ALTER TABLE",
      this.quoteTable(table),
      "ADD",
      this.quoteIdentifier(key),
      this.attributeToSQL(dataType, {
        context: "addColumn",
        tableName: table,
        foreignKey: key
      }),
      ";"
    ]);
  }
  removeColumnQuery(tableName, attributeName) {
    return Utils.joinSQLFragments([
      "ALTER TABLE",
      this.quoteTable(tableName),
      "DROP",
      this.quoteIdentifier(attributeName),
      ";"
    ]);
  }
  changeColumnQuery(tableName, attributes) {
    const query = (...subQuerys) => Utils.joinSQLFragments([
      "ALTER TABLE",
      this.quoteTable(tableName),
      "ALTER COLUMN",
      ...subQuerys,
      ";"
    ]);
    const sql = [];
    for (const attributeName in attributes) {
      let definition = this.dataTypeMapping(tableName, attributeName, attributes[attributeName]);
      const attrSql = [];
      if (definition.includes("NOT NULL")) {
        attrSql.push(query(this.quoteIdentifier(attributeName), "SET NOT NULL"));
        definition = definition.replace("NOT NULL", "").trim();
      } else if (!definition.includes("REFERENCES")) {
        attrSql.push(query(this.quoteIdentifier(attributeName), "DROP NOT NULL"));
      }
      if (definition.includes("DEFAULT")) {
        attrSql.push(query(this.quoteIdentifier(attributeName), "SET DEFAULT", definition.match(/DEFAULT ([^;]+)/)[1]));
        definition = definition.replace(/(DEFAULT[^;]+)/, "").trim();
      } else if (!definition.includes("REFERENCES")) {
        attrSql.push(query(this.quoteIdentifier(attributeName), "DROP DEFAULT"));
      }
      if (definition.match(/UNIQUE;*$/)) {
        definition = definition.replace(/UNIQUE;*$/, "");
        attrSql.push(query("ADD UNIQUE (", this.quoteIdentifier(attributeName), ")").replace("ALTER COLUMN", ""));
      }
      if (definition.includes("REFERENCES")) {
        definition = definition.replace(/.+?(?=REFERENCES)/, "");
        attrSql.push(query("ADD FOREIGN KEY (", this.quoteIdentifier(attributeName), ")", definition).replace("ALTER COLUMN", ""));
      } else {
        attrSql.push(query(this.quoteIdentifier(attributeName), "TYPE", definition));
      }
      sql.push(attrSql.join(""));
    }
    return sql.join("");
  }
  renameColumnQuery(tableName, attrBefore, attributes) {
    const attrString = [];
    for (const attrName in attributes) {
      const definition = attributes[attrName];
      attrString.push(`'${attrBefore}' '${attrName}' ${definition}`);
    }
    return Utils.joinSQLFragments([
      "ALTER TABLE",
      this.quoteTable(tableName),
      "RENAME COLUMN",
      attrString.join(" to "),
      ";"
    ]);
  }
  handleSequelizeMethod(attr, tableName, factory, options, prepend) {
    if (attr instanceof Utils.Json) {
      if (attr.conditions) {
        const conditions = this.parseConditionObject(attr.conditions).map((condition) => `${this.jsonPathExtractionQuery(condition.path[0], _.tail(condition.path))} = '${condition.value}'`);
        return conditions.join(" AND ");
      }
      if (attr.path) {
        let str;
        if (this._checkValidJsonStatement(attr.path)) {
          str = attr.path;
        } else {
          const paths = _.toPath(attr.path);
          const column = paths.shift();
          str = this.jsonPathExtractionQuery(column, paths);
        }
        if (attr.value) {
          str += util.format(" = %s", this.escape(attr.value));
        }
        return str;
      }
    } else if (attr instanceof Utils.Cast) {
      if (/timestamp/i.test(attr.type)) {
        attr.type = "datetime";
      } else if (attr.json && /boolean/i.test(attr.type)) {
        attr.type = "char";
      } else if (/double precision/i.test(attr.type) || /boolean/i.test(attr.type) || /integer/i.test(attr.type)) {
        attr.type = "decimal";
      } else if (/text/i.test(attr.type)) {
        attr.type = "char";
      }
    }
    return super.handleSequelizeMethod(attr, tableName, factory, options, prepend);
  }
  truncateTableQuery(tableName) {
    return Utils.joinSQLFragments([
      "TRUNCATE",
      this.quoteTable(tableName)
    ]);
  }
  deleteQuery(tableName, where, options = {}, model) {
    const table = this.quoteTable(tableName);
    let whereClause = this.getWhereConditions(where, null, model, options);
    const limit = options.limit && ` LIMIT ${this.escape(options.limit)}`;
    let primaryKeys = "";
    let primaryKeysSelection = "";
    if (whereClause) {
      whereClause = `WHERE ${whereClause}`;
    }
    if (limit) {
      if (!model) {
        throw new Error("Cannot LIMIT delete without a model.");
      }
      const pks = Object.values(model.primaryKeys).map((pk) => this.quoteIdentifier(pk.field)).join(",");
      primaryKeys = model.primaryKeyAttributes.length > 1 ? `(${pks})` : pks;
      primaryKeysSelection = pks;
      return Utils.joinSQLFragments([
        "DELETE FROM",
        table,
        "WHERE",
        primaryKeys,
        "IN (SELECT",
        primaryKeysSelection,
        "FROM",
        table,
        whereClause,
        limit,
        ")",
        ";"
      ]);
    }
    return Utils.joinSQLFragments([
      "DELETE FROM",
      table,
      whereClause,
      ";"
    ]);
  }
  showIndexesQuery() {
    return "SELECT '' FROM DUAL";
  }
  showConstraintsQuery(table, constraintName) {
    const tableName = table.tableName || table;
    const schemaName = table.schema;
    return Utils.joinSQLFragments([
      "SELECT CONSTRAINT_CATALOG AS constraintCatalog,",
      "CONSTRAINT_NAME AS constraintName,",
      "CONSTRAINT_SCHEMA AS constraintSchema,",
      "CONSTRAINT_TYPE AS constraintType,",
      "TABLE_NAME AS tableName,",
      "TABLE_SCHEMA AS tableSchema",
      "from INFORMATION_SCHEMA.TABLE_CONSTRAINTS",
      `WHERE table_name='${tableName}'`,
      constraintName && `AND constraint_name = '${constraintName}'`,
      schemaName && `AND TABLE_SCHEMA = '${schemaName}'`,
      ";"
    ]);
  }
  removeIndexQuery(tableName, indexNameOrAttributes) {
    let indexName = indexNameOrAttributes;
    if (typeof indexName !== "string") {
      indexName = Utils.underscore(`${tableName}_${indexNameOrAttributes.join("_")}`);
    }
    return Utils.joinSQLFragments([
      "DROP INDEX",
      this.quoteIdentifier(indexName),
      "ON",
      this.quoteTable(tableName),
      ";"
    ]);
  }
  attributeToSQL(attribute, options) {
    if (!_.isPlainObject(attribute)) {
      attribute = {
        type: attribute
      };
    }
    const attributeString = attribute.type.toString({ escape: this.escape.bind(this) });
    let template = attributeString;
    if (attribute.allowNull === false) {
      template += " NOT NULL";
    }
    if (attribute.autoIncrement) {
      template += " AUTOINCREMENT";
    }
    if (!typeWithoutDefault.has(attributeString) && attribute.type._binary !== true && Utils.defaultValueSchemable(attribute.defaultValue)) {
      template += ` DEFAULT ${this.escape(attribute.defaultValue)}`;
    }
    if (attribute.unique === true) {
      template += " UNIQUE";
    }
    if (attribute.primaryKey) {
      template += " PRIMARY KEY";
    }
    if (attribute.comment) {
      template += ` COMMENT ${this.escape(attribute.comment)}`;
    }
    if (attribute.first) {
      template += " FIRST";
    }
    if (attribute.after) {
      template += ` AFTER ${this.quoteIdentifier(attribute.after)}`;
    }
    if (attribute.references) {
      if (options && options.context === "addColumn" && options.foreignKey) {
        const attrName = this.quoteIdentifier(options.foreignKey);
        const fkName = this.quoteIdentifier(`${options.tableName}_${attrName}_foreign_idx`);
        template += `, ADD CONSTRAINT ${fkName} FOREIGN KEY (${attrName})`;
      }
      template += ` REFERENCES ${this.quoteTable(attribute.references.model)}`;
      if (attribute.references.key) {
        template += ` (${this.quoteIdentifier(attribute.references.key)})`;
      } else {
        template += ` (${this.quoteIdentifier("id")})`;
      }
      if (attribute.onDelete) {
        template += ` ON DELETE ${attribute.onDelete.toUpperCase()}`;
      }
      if (attribute.onUpdate) {
        template += ` ON UPDATE ${attribute.onUpdate.toUpperCase()}`;
      }
    }
    return template;
  }
  attributesToSQL(attributes, options) {
    const result = {};
    for (const key in attributes) {
      const attribute = attributes[key];
      result[attribute.field || key] = this.attributeToSQL(attribute, options);
    }
    return result;
  }
  _checkValidJsonStatement(stmt) {
    if (typeof stmt !== "string") {
      return false;
    }
    let currentIndex = 0;
    let openingBrackets = 0;
    let closingBrackets = 0;
    let hasJsonFunction = false;
    let hasInvalidToken = false;
    while (currentIndex < stmt.length) {
      const string = stmt.substr(currentIndex);
      const functionMatches = JSON_FUNCTION_REGEX.exec(string);
      if (functionMatches) {
        currentIndex += functionMatches[0].indexOf("(");
        hasJsonFunction = true;
        continue;
      }
      const operatorMatches = JSON_OPERATOR_REGEX.exec(string);
      if (operatorMatches) {
        currentIndex += operatorMatches[0].length;
        hasJsonFunction = true;
        continue;
      }
      const tokenMatches = TOKEN_CAPTURE_REGEX.exec(string);
      if (tokenMatches) {
        const capturedToken = tokenMatches[1];
        if (capturedToken === "(") {
          openingBrackets++;
        } else if (capturedToken === ")") {
          closingBrackets++;
        } else if (capturedToken === ";") {
          hasInvalidToken = true;
          break;
        }
        currentIndex += tokenMatches[0].length;
        continue;
      }
      break;
    }
    if (hasJsonFunction && (hasInvalidToken || openingBrackets !== closingBrackets)) {
      throw new Error(`Invalid json statement: ${stmt}`);
    }
    return hasJsonFunction;
  }
  dataTypeMapping(tableName, attr, dataType) {
    if (dataType.includes("PRIMARY KEY")) {
      dataType = dataType.replace("PRIMARY KEY", "");
    }
    if (dataType.includes("SERIAL")) {
      if (dataType.includes("BIGINT")) {
        dataType = dataType.replace("SERIAL", "BIGSERIAL");
        dataType = dataType.replace("BIGINT", "");
      } else if (dataType.includes("SMALLINT")) {
        dataType = dataType.replace("SERIAL", "SMALLSERIAL");
        dataType = dataType.replace("SMALLINT", "");
      } else {
        dataType = dataType.replace("INTEGER", "");
      }
      dataType = dataType.replace("NOT NULL", "");
    }
    return dataType;
  }
  getForeignKeysQuery(table, schemaName) {
    const tableName = table.tableName || table;
    return Utils.joinSQLFragments([
      "SELECT",
      FOREIGN_KEY_FIELDS,
      `FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = '${tableName}'`,
      `AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='${schemaName}'`,
      "AND REFERENCED_TABLE_NAME IS NOT NULL",
      ";"
    ]);
  }
  getForeignKeyQuery(table, columnName) {
    const quotedSchemaName = table.schema ? wrapSingleQuote(table.schema) : "";
    const quotedTableName = wrapSingleQuote(table.tableName || table);
    const quotedColumnName = wrapSingleQuote(columnName);
    return Utils.joinSQLFragments([
      "SELECT",
      FOREIGN_KEY_FIELDS,
      "FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE",
      "WHERE (",
      [
        `REFERENCED_TABLE_NAME = ${quotedTableName}`,
        table.schema && `AND REFERENCED_TABLE_SCHEMA = ${quotedSchemaName}`,
        `AND REFERENCED_COLUMN_NAME = ${quotedColumnName}`
      ],
      ") OR (",
      [
        `TABLE_NAME = ${quotedTableName}`,
        table.schema && `AND TABLE_SCHEMA = ${quotedSchemaName}`,
        `AND COLUMN_NAME = ${quotedColumnName}`,
        "AND REFERENCED_TABLE_NAME IS NOT NULL"
      ],
      ")"
    ]);
  }
  dropForeignKeyQuery(tableName, foreignKey) {
    return Utils.joinSQLFragments([
      "ALTER TABLE",
      this.quoteTable(tableName),
      "DROP FOREIGN KEY",
      this.quoteIdentifier(foreignKey),
      ";"
    ]);
  }
  addLimitAndOffset(options) {
    let fragment = [];
    if (options.offset !== null && options.offset !== void 0 && options.offset !== 0) {
      fragment = fragment.concat([" LIMIT ", this.escape(options.limit), " OFFSET ", this.escape(options.offset)]);
    } else if (options.limit !== null && options.limit !== void 0) {
      fragment = [" LIMIT ", this.escape(options.limit)];
    }
    return fragment.join("");
  }
  quoteIdentifier(identifier, force) {
    const optForceQuote = force || false;
    const optQuoteIdentifiers = this.options.quoteIdentifiers !== false;
    const rawIdentifier = Utils.removeTicks(identifier, '"');
    if (optForceQuote === true || optQuoteIdentifiers !== false || identifier.includes(".") || identifier.includes("->") || SNOWFLAKE_RESERVED_WORDS.includes(rawIdentifier.toLowerCase())) {
      return Utils.addTicks(rawIdentifier, '"');
    }
    return rawIdentifier;
  }
}
function wrapSingleQuote(identifier) {
  return Utils.addTicks(identifier, "'");
}
module.exports = SnowflakeQueryGenerator;
//# sourceMappingURL=query-generator.js.map
