"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 Utils = require("../../utils");
const Transaction = require("../../transaction");
const _ = require("lodash");
const MySqlQueryGenerator = require("../mysql/query-generator");
const AbstractQueryGenerator = require("../abstract/query-generator");
class SQLiteQueryGenerator extends MySqlQueryGenerator {
  createSchema() {
    return "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
  }
  showSchemasQuery() {
    return "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
  }
  versionQuery() {
    return "SELECT sqlite_version() as `version`";
  }
  createTableQuery(tableName, attributes, options) {
    options = options || {};
    const primaryKeys = [];
    const needsMultiplePrimaryKeys = Object.values(attributes).filter((definition) => definition.includes("PRIMARY KEY")).length > 1;
    const attrArray = [];
    for (const attr in attributes) {
      if (Object.prototype.hasOwnProperty.call(attributes, attr)) {
        const dataType = attributes[attr];
        const containsAutoIncrement = dataType.includes("AUTOINCREMENT");
        let dataTypeString = dataType;
        if (dataType.includes("PRIMARY KEY")) {
          if (dataType.includes("INT")) {
            dataTypeString = containsAutoIncrement ? "INTEGER PRIMARY KEY AUTOINCREMENT" : "INTEGER PRIMARY KEY";
            if (dataType.includes(" REFERENCES")) {
              dataTypeString += dataType.substr(dataType.indexOf(" REFERENCES"));
            }
          }
          if (needsMultiplePrimaryKeys) {
            primaryKeys.push(attr);
            if (dataType.includes("NOT NULL")) {
              dataTypeString = dataType.replace(" PRIMARY KEY", "");
            } else {
              dataTypeString = dataType.replace("PRIMARY KEY", "NOT NULL");
            }
          }
        }
        attrArray.push(`${this.quoteIdentifier(attr)} ${dataTypeString}`);
      }
    }
    const table = this.quoteTable(tableName);
    let attrStr = attrArray.join(", ");
    const pkString = primaryKeys.map((pk) => this.quoteIdentifier(pk)).join(", ");
    if (options.uniqueKeys) {
      _.each(options.uniqueKeys, (columns) => {
        if (columns.customIndex) {
          attrStr += `, UNIQUE (${columns.fields.map((field) => this.quoteIdentifier(field)).join(", ")})`;
        }
      });
    }
    if (pkString.length > 0) {
      attrStr += `, PRIMARY KEY (${pkString})`;
    }
    const sql = `CREATE TABLE IF NOT EXISTS ${table} (${attrStr});`;
    return this.replaceBooleanDefaults(sql);
  }
  booleanValue(value) {
    return value ? 1 : 0;
  }
  _checkValidJsonStatement(stmt) {
    if (typeof stmt !== "string") {
      return false;
    }
    const jsonFunctionRegex = /^\s*(json(?:_[a-z]+){0,2})\([^)]*\)/i;
    const tokenCaptureRegex = /^\s*((?:([`"'])(?:(?!\2).|\2{2})*\2)|[\w\d\s]+|[().,;+-])/i;
    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 = jsonFunctionRegex.exec(string);
      if (functionMatches) {
        currentIndex += functionMatches[0].indexOf("(");
        hasJsonFunction = true;
        continue;
      }
      const tokenMatches = tokenCaptureRegex.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;
    }
    hasInvalidToken |= openingBrackets !== closingBrackets;
    if (hasJsonFunction && hasInvalidToken) {
      throw new Error(`Invalid json statement: ${stmt}`);
    }
    return hasJsonFunction;
  }
  _toJSONValue(value) {
    if (value instanceof Date) {
      return value.toISOString();
    }
    if (Array.isArray(value) && value[0] instanceof Date) {
      return value.map((val) => val.toISOString());
    }
    return value;
  }
  handleSequelizeMethod(smth, tableName, factory, options, prepend) {
    if (smth instanceof Utils.Json) {
      return super.handleSequelizeMethod(smth, tableName, factory, options, prepend);
    }
    if (smth instanceof Utils.Cast) {
      if (/timestamp/i.test(smth.type)) {
        smth.type = "datetime";
      }
    }
    return AbstractQueryGenerator.prototype.handleSequelizeMethod.call(this, smth, tableName, factory, options, prepend);
  }
  addColumnQuery(table, key, dataType) {
    const attributes = {};
    attributes[key] = dataType;
    const fields = this.attributesToSQL(attributes, { context: "addColumn" });
    const attribute = `${this.quoteIdentifier(key)} ${fields[key]}`;
    const sql = `ALTER TABLE ${this.quoteTable(table)} ADD ${attribute};`;
    return this.replaceBooleanDefaults(sql);
  }
  showTablesQuery() {
    return "SELECT name FROM `sqlite_master` WHERE type='table' and name!='sqlite_sequence';";
  }
  updateQuery(tableName, attrValueHash, where, options, attributes) {
    options = options || {};
    _.defaults(options, this.options);
    attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options);
    const modelAttributeMap = {};
    const values = [];
    const bind = [];
    const bindParam = options.bindParam || this.bindParam(bind);
    if (attributes) {
      _.each(attributes, (attribute, key) => {
        modelAttributeMap[key] = attribute;
        if (attribute.field) {
          modelAttributeMap[attribute.field] = attribute;
        }
      });
    }
    for (const key in attrValueHash) {
      const value = attrValueHash[key];
      if (value instanceof Utils.SequelizeMethod || options.bindParam === false) {
        values.push(`${this.quoteIdentifier(key)}=${this.escape(value, modelAttributeMap && modelAttributeMap[key] || void 0, { context: "UPDATE" })}`);
      } else {
        values.push(`${this.quoteIdentifier(key)}=${this.format(value, modelAttributeMap && modelAttributeMap[key] || void 0, { context: "UPDATE" }, bindParam)}`);
      }
    }
    let query;
    const whereOptions = __spreadProps(__spreadValues({}, options), { bindParam });
    if (options.limit) {
      query = `UPDATE ${this.quoteTable(tableName)} SET ${values.join(",")} WHERE rowid IN (SELECT rowid FROM ${this.quoteTable(tableName)} ${this.whereQuery(where, whereOptions)} LIMIT ${this.escape(options.limit)})`;
    } else {
      query = `UPDATE ${this.quoteTable(tableName)} SET ${values.join(",")} ${this.whereQuery(where, whereOptions)}`;
    }
    return { query, bind };
  }
  truncateTableQuery(tableName, options = {}) {
    return [
      `DELETE FROM ${this.quoteTable(tableName)}`,
      options.restartIdentity ? `; DELETE FROM ${this.quoteTable("sqlite_sequence")} WHERE ${this.quoteIdentifier("name")} = ${Utils.addTicks(Utils.removeTicks(this.quoteTable(tableName), "`"), "'")};` : ""
    ].join("");
  }
  deleteQuery(tableName, where, options = {}, model) {
    _.defaults(options, this.options);
    let whereClause = this.getWhereConditions(where, null, model, options);
    if (whereClause) {
      whereClause = `WHERE ${whereClause}`;
    }
    if (options.limit) {
      whereClause = `WHERE rowid IN (SELECT rowid FROM ${this.quoteTable(tableName)} ${whereClause} LIMIT ${this.escape(options.limit)})`;
    }
    return `DELETE FROM ${this.quoteTable(tableName)} ${whereClause}`;
  }
  attributesToSQL(attributes) {
    const result = {};
    for (const name in attributes) {
      const dataType = attributes[name];
      const fieldName = dataType.field || name;
      if (_.isObject(dataType)) {
        let sql = dataType.type.toString();
        if (Object.prototype.hasOwnProperty.call(dataType, "allowNull") && !dataType.allowNull) {
          sql += " NOT NULL";
        }
        if (Utils.defaultValueSchemable(dataType.defaultValue)) {
          sql += ` DEFAULT ${this.escape(dataType.defaultValue, dataType)}`;
        }
        if (dataType.unique === true) {
          sql += " UNIQUE";
        }
        if (dataType.primaryKey) {
          sql += " PRIMARY KEY";
          if (dataType.autoIncrement) {
            sql += " AUTOINCREMENT";
          }
        }
        if (dataType.references) {
          const referencesTable = this.quoteTable(dataType.references.model);
          let referencesKey;
          if (dataType.references.key) {
            referencesKey = this.quoteIdentifier(dataType.references.key);
          } else {
            referencesKey = this.quoteIdentifier("id");
          }
          sql += ` REFERENCES ${referencesTable} (${referencesKey})`;
          if (dataType.onDelete) {
            sql += ` ON DELETE ${dataType.onDelete.toUpperCase()}`;
          }
          if (dataType.onUpdate) {
            sql += ` ON UPDATE ${dataType.onUpdate.toUpperCase()}`;
          }
        }
        result[fieldName] = sql;
      } else {
        result[fieldName] = dataType;
      }
    }
    return result;
  }
  showIndexesQuery(tableName) {
    return `PRAGMA INDEX_LIST(${this.quoteTable(tableName)})`;
  }
  showConstraintsQuery(tableName, constraintName) {
    let sql = `SELECT sql FROM sqlite_master WHERE tbl_name='${tableName}'`;
    if (constraintName) {
      sql += ` AND sql LIKE '%${constraintName}%'`;
    }
    return `${sql};`;
  }
  removeIndexQuery(tableName, indexNameOrAttributes) {
    let indexName = indexNameOrAttributes;
    if (typeof indexName !== "string") {
      indexName = Utils.underscore(`${tableName}_${indexNameOrAttributes.join("_")}`);
    }
    return `DROP INDEX IF EXISTS ${this.quoteIdentifier(indexName)}`;
  }
  describeTableQuery(tableName, schema, schemaDelimiter) {
    const table = {
      _schema: schema,
      _schemaDelimiter: schemaDelimiter,
      tableName
    };
    return `PRAGMA TABLE_INFO(${this.quoteTable(this.addSchema(table))});`;
  }
  describeCreateTableQuery(tableName) {
    return `SELECT sql FROM sqlite_master WHERE tbl_name='${tableName}';`;
  }
  removeColumnQuery(tableName, attributes) {
    attributes = this.attributesToSQL(attributes);
    let backupTableName;
    if (typeof tableName === "object") {
      backupTableName = {
        tableName: `${tableName.tableName}_backup`,
        schema: tableName.schema
      };
    } else {
      backupTableName = `${tableName}_backup`;
    }
    const quotedTableName = this.quoteTable(tableName);
    const quotedBackupTableName = this.quoteTable(backupTableName);
    const attributeNames = Object.keys(attributes).map((attr) => this.quoteIdentifier(attr)).join(", ");
    return `${this.createTableQuery(backupTableName, attributes)}INSERT INTO ${quotedBackupTableName} SELECT ${attributeNames} FROM ${quotedTableName};DROP TABLE ${quotedTableName};${this.createTableQuery(tableName, attributes)}INSERT INTO ${quotedTableName} SELECT ${attributeNames} FROM ${quotedBackupTableName};DROP TABLE ${quotedBackupTableName};`;
  }
  _alterConstraintQuery(tableName, attributes, createTableSql) {
    let backupTableName;
    attributes = this.attributesToSQL(attributes);
    if (typeof tableName === "object") {
      backupTableName = {
        tableName: `${tableName.tableName}_backup`,
        schema: tableName.schema
      };
    } else {
      backupTableName = `${tableName}_backup`;
    }
    const quotedTableName = this.quoteTable(tableName);
    const quotedBackupTableName = this.quoteTable(backupTableName);
    const attributeNames = Object.keys(attributes).map((attr) => this.quoteIdentifier(attr)).join(", ");
    return `${createTableSql.replace(`CREATE TABLE ${quotedTableName}`, `CREATE TABLE ${quotedBackupTableName}`).replace(`CREATE TABLE ${quotedTableName.replace(/`/g, '"')}`, `CREATE TABLE ${quotedBackupTableName}`)}INSERT INTO ${quotedBackupTableName} SELECT ${attributeNames} FROM ${quotedTableName};DROP TABLE ${quotedTableName};ALTER TABLE ${quotedBackupTableName} RENAME TO ${quotedTableName};`;
  }
  renameColumnQuery(tableName, attrNameBefore, attrNameAfter, attributes) {
    let backupTableName;
    attributes = this.attributesToSQL(attributes);
    if (typeof tableName === "object") {
      backupTableName = {
        tableName: `${tableName.tableName}_backup`,
        schema: tableName.schema
      };
    } else {
      backupTableName = `${tableName}_backup`;
    }
    const quotedTableName = this.quoteTable(tableName);
    const quotedBackupTableName = this.quoteTable(backupTableName);
    const attributeNamesImport = Object.keys(attributes).map((attr) => attrNameAfter === attr ? `${this.quoteIdentifier(attrNameBefore)} AS ${this.quoteIdentifier(attr)}` : this.quoteIdentifier(attr)).join(", ");
    const attributeNamesExport = Object.keys(attributes).map((attr) => this.quoteIdentifier(attr)).join(", ");
    return `${this.createTableQuery(backupTableName, attributes)}INSERT INTO ${quotedBackupTableName} SELECT ${attributeNamesImport} FROM ${quotedTableName};DROP TABLE ${quotedTableName};${this.createTableQuery(tableName, attributes)}INSERT INTO ${quotedTableName} SELECT ${attributeNamesExport} FROM ${quotedBackupTableName};DROP TABLE ${quotedBackupTableName};`;
  }
  startTransactionQuery(transaction) {
    if (transaction.parent) {
      return `SAVEPOINT ${this.quoteIdentifier(transaction.name)};`;
    }
    return `BEGIN ${transaction.options.type} TRANSACTION;`;
  }
  setIsolationLevelQuery(value) {
    switch (value) {
      case Transaction.ISOLATION_LEVELS.REPEATABLE_READ:
        return "-- SQLite is not able to choose the isolation level REPEATABLE READ.";
      case Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED:
        return "PRAGMA read_uncommitted = ON;";
      case Transaction.ISOLATION_LEVELS.READ_COMMITTED:
        return "PRAGMA read_uncommitted = OFF;";
      case Transaction.ISOLATION_LEVELS.SERIALIZABLE:
        return "-- SQLite's default isolation level is SERIALIZABLE. Nothing to do.";
      default:
        throw new Error(`Unknown isolation level: ${value}`);
    }
  }
  replaceBooleanDefaults(sql) {
    return sql.replace(/DEFAULT '?false'?/g, "DEFAULT 0").replace(/DEFAULT '?true'?/g, "DEFAULT 1");
  }
  getForeignKeysQuery(tableName) {
    return `PRAGMA foreign_key_list(${this.quoteTable(this.addSchema(tableName))})`;
  }
  tableExistsQuery(tableName) {
    return `SELECT name FROM sqlite_master WHERE type='table' AND name=${this.escape(this.addSchema(tableName))};`;
  }
  quoteIdentifier(identifier, force) {
    return Utils.addTicks(Utils.removeTicks(identifier, "`"), "`");
  }
}
module.exports = SQLiteQueryGenerator;
//# sourceMappingURL=query-generator.js.map
