"use strict"; const _ = require("lodash"); const Utils = require("../../utils"); const AbstractQuery = require("../abstract/query"); const QueryTypes = require("../../query-types"); const sequelizeErrors = require("../../errors"); const parserStore = require("../parserStore")("sqlite"); const { logger } = require("../../utils/logger"); const debug = logger.debugContext("sql:sqlite"); function stringifyIfBigint(value) { if (typeof value === "bigint") { return value.toString(); } return value; } class Query extends AbstractQuery { getInsertIdField() { return "lastID"; } static formatBindParameters(sql, values, dialect) { let bindParam; if (Array.isArray(values)) { bindParam = {}; values.forEach((v, i) => { bindParam[`$${i + 1}`] = v; }); sql = AbstractQuery.formatBindParameters(sql, values, dialect, { skipValueReplace: true })[0]; } else { bindParam = {}; if (typeof values === "object") { for (const k of Object.keys(values)) { bindParam[`$${k}`] = values[k]; } } sql = AbstractQuery.formatBindParameters(sql, values, dialect, { skipValueReplace: true })[0]; } return [sql, bindParam]; } _collectModels(include, prefix) { const ret = {}; if (include) { for (const _include of include) { let key; if (!prefix) { key = _include.as; } else { key = `${prefix}.${_include.as}`; } ret[key] = _include.model; if (_include.include) { _.merge(ret, this._collectModels(_include.include, key)); } } } return ret; } _handleQueryResponse(metaData, columnTypes, err, results, errStack) { if (err) { err.sql = this.sql; throw this.formatError(err, errStack); } let result = this.instance; if (this.isInsertQuery(results, metaData) || this.isUpsertQuery()) { this.handleInsertQuery(results, metaData); if (!this.instance) { if (metaData.constructor.name === "Statement" && this.model && this.model.autoIncrementAttribute && this.model.autoIncrementAttribute === this.model.primaryKeyAttribute && this.model.rawAttributes[this.model.primaryKeyAttribute]) { const startId = metaData[this.getInsertIdField()] - metaData.changes + 1; result = []; for (let i = startId; i < startId + metaData.changes; i++) { result.push({ [this.model.rawAttributes[this.model.primaryKeyAttribute].field]: i }); } } else { result = metaData[this.getInsertIdField()]; } } } if (this.isShowTablesQuery()) { return results.map((row) => row.name); } if (this.isShowConstraintsQuery()) { result = results; if (results && results[0] && results[0].sql) { result = this.parseConstraintsFromSql(results[0].sql); } return result; } if (this.isSelectQuery()) { if (this.options.raw) { return this.handleSelectQuery(results); } const prefixes = this._collectModels(this.options.include); results = results.map((result2) => { return _.mapValues(result2, (value, name) => { let model; if (name.includes(".")) { const lastind = name.lastIndexOf("."); model = prefixes[name.substr(0, lastind)]; name = name.substr(lastind + 1); } else { model = this.options.model; } const tableName = model.getTableName().toString().replace(/`/g, ""); const tableTypes = columnTypes[tableName] || {}; if (tableTypes && !(name in tableTypes)) { _.forOwn(model.rawAttributes, (attribute, key) => { if (name === key && attribute.field) { name = attribute.field; return false; } }); } return Object.prototype.hasOwnProperty.call(tableTypes, name) ? this.applyParsers(tableTypes[name], value) : value; }); }); return this.handleSelectQuery(results); } if (this.isShowOrDescribeQuery()) { return results; } if (this.sql.includes("PRAGMA INDEX_LIST")) { return this.handleShowIndexesQuery(results); } if (this.sql.includes("PRAGMA INDEX_INFO")) { return results; } if (this.sql.includes("PRAGMA TABLE_INFO")) { result = {}; let defaultValue; for (const _result of results) { if (_result.dflt_value === null) { defaultValue = void 0; } else if (_result.dflt_value === "NULL") { defaultValue = null; } else { defaultValue = _result.dflt_value; } result[_result.name] = { type: _result.type, allowNull: _result.notnull === 0, defaultValue, primaryKey: _result.pk !== 0 }; if (result[_result.name].type === "TINYINT(1)") { result[_result.name].defaultValue = { "0": false, "1": true }[result[_result.name].defaultValue]; } if (typeof result[_result.name].defaultValue === "string") { result[_result.name].defaultValue = result[_result.name].defaultValue.replace(/'/g, ""); } } return result; } if (this.sql.includes("PRAGMA foreign_keys;")) { return results[0]; } if (this.sql.includes("PRAGMA foreign_keys")) { return results; } if (this.sql.includes("PRAGMA foreign_key_list")) { return results; } if ([QueryTypes.BULKUPDATE, QueryTypes.BULKDELETE].includes(this.options.type)) { return metaData.changes; } if (this.options.type === QueryTypes.VERSION) { return results[0].version; } if (this.options.type === QueryTypes.RAW) { return [results, metaData]; } if (this.isUpsertQuery()) { return [result, null]; } if (this.isUpdateQuery() || this.isInsertQuery()) { return [result, metaData.changes]; } return result; } async run(sql, parameters) { const conn = this.connection; this.sql = sql; const method = this.getDatabaseMethod(); const complete = this._logQuery(sql, debug, parameters); return new Promise((resolve, reject) => conn.serialize(async () => { const columnTypes = {}; const errForStack = new Error(); const executeSql = () => { if (sql.startsWith("-- ")) { return resolve(); } const query = this; function afterExecute(executionError, results) { try { complete(); resolve(query._handleQueryResponse(this, columnTypes, executionError, results, errForStack.stack)); return; } catch (error) { reject(error); } } if (!parameters) parameters = []; if (_.isPlainObject(parameters)) { const newParameters = Object.create(null); for (const key of Object.keys(parameters)) { newParameters[`${key}`] = stringifyIfBigint(parameters[key]); } parameters = newParameters; } else { parameters = parameters.map(stringifyIfBigint); } conn[method](sql, parameters, afterExecute); return null; }; if (this.getDatabaseMethod() === "all") { let tableNames = []; if (this.options && this.options.tableNames) { tableNames = this.options.tableNames; } else if (/FROM `(.*?)`/i.exec(this.sql)) { tableNames.push(/FROM `(.*?)`/i.exec(this.sql)[1]); } tableNames = tableNames.filter((tableName) => !(tableName in columnTypes) && tableName !== "sqlite_master"); if (!tableNames.length) { return executeSql(); } await Promise.all(tableNames.map((tableName) => new Promise((resolve2) => { tableName = tableName.replace(/`/g, ""); columnTypes[tableName] = {}; conn.all(`PRAGMA table_info(\`${tableName}\`)`, (err, results) => { if (!err) { for (const result of results) { columnTypes[tableName][result.name] = result.type; } } resolve2(); }); }))); } return executeSql(); })); } parseConstraintsFromSql(sql) { let constraints = sql.split("CONSTRAINT "); let referenceTableName, referenceTableKeys, updateAction, deleteAction; constraints.splice(0, 1); constraints = constraints.map((constraintSql) => { if (constraintSql.includes("REFERENCES")) { updateAction = constraintSql.match(/ON UPDATE (CASCADE|SET NULL|RESTRICT|NO ACTION|SET DEFAULT){1}/); deleteAction = constraintSql.match(/ON DELETE (CASCADE|SET NULL|RESTRICT|NO ACTION|SET DEFAULT){1}/); if (updateAction) { updateAction = updateAction[1]; } if (deleteAction) { deleteAction = deleteAction[1]; } const referencesRegex = /REFERENCES.+\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)/; const referenceConditions = constraintSql.match(referencesRegex)[0].split(" "); referenceTableName = Utils.removeTicks(referenceConditions[1]); let columnNames = referenceConditions[2]; columnNames = columnNames.replace(/\(|\)/g, "").split(", "); referenceTableKeys = columnNames.map((column) => Utils.removeTicks(column)); } const constraintCondition = constraintSql.match(/\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)/)[0]; constraintSql = constraintSql.replace(/\(.+\)/, ""); const constraint = constraintSql.split(" "); if (["PRIMARY", "FOREIGN"].includes(constraint[1])) { constraint[1] += " KEY"; } return { constraintName: Utils.removeTicks(constraint[0]), constraintType: constraint[1], updateAction, deleteAction, sql: sql.replace(/"/g, "`"), constraintCondition, referenceTableName, referenceTableKeys }; }); return constraints; } applyParsers(type, value) { if (type.includes("(")) { type = type.substr(0, type.indexOf("(")); } type = type.replace("UNSIGNED", "").replace("ZEROFILL", ""); type = type.trim().toUpperCase(); const parse = parserStore.get(type); if (value !== null && parse) { return parse(value, { timezone: this.sequelize.options.timezone }); } return value; } formatError(err, errStack) { switch (err.code) { case "SQLITE_CONSTRAINT_UNIQUE": case "SQLITE_CONSTRAINT_PRIMARYKEY": case "SQLITE_CONSTRAINT_TRIGGER": case "SQLITE_CONSTRAINT_FOREIGNKEY": case "SQLITE_CONSTRAINT": { if (err.message.includes("FOREIGN KEY constraint failed")) { return new sequelizeErrors.ForeignKeyConstraintError({ parent: err, stack: errStack }); } let fields = []; let match = err.message.match(/columns (.*?) are/); if (match !== null && match.length >= 2) { fields = match[1].split(", "); } else { match = err.message.match(/UNIQUE constraint failed: (.*)/); if (match !== null && match.length >= 2) { fields = match[1].split(", ").map((columnWithTable) => columnWithTable.split(".")[1]); } } const errors = []; let message = "Validation error"; for (const field of fields) { errors.push(new sequelizeErrors.ValidationErrorItem(this.getUniqueConstraintErrorMessage(field), "unique violation", field, this.instance && this.instance[field], this.instance, "not_unique")); } if (this.model) { _.forOwn(this.model.uniqueKeys, (constraint) => { if (_.isEqual(constraint.fields, fields) && !!constraint.msg) { message = constraint.msg; return false; } }); } return new sequelizeErrors.UniqueConstraintError({ message, errors, parent: err, fields, stack: errStack }); } case "SQLITE_BUSY": return new sequelizeErrors.TimeoutError(err, { stack: errStack }); default: return new sequelizeErrors.DatabaseError(err, { stack: errStack }); } } async handleShowIndexesQuery(data) { return Promise.all(data.reverse().map(async (item) => { item.fields = []; item.primary = false; item.unique = !!item.unique; item.constraintName = item.name; const columns = await this.run(`PRAGMA INDEX_INFO(\`${item.name}\`)`); for (const column of columns) { item.fields[column.seqno] = { attribute: column.name, length: void 0, order: void 0 }; } return item; })); } getDatabaseMethod() { if (this.isInsertQuery() || this.isUpdateQuery() || this.isUpsertQuery() || this.isBulkUpdateQuery() || this.sql.toLowerCase().includes("CREATE TEMPORARY TABLE".toLowerCase()) || this.options.type === QueryTypes.BULKDELETE) { return "run"; } return "all"; } } module.exports = Query; module.exports.Query = Query; module.exports.default = Query; //# sourceMappingURL=query.js.map