UNPKG

sequelize

Version:

Sequelize is a promise-based Node.js ORM tool for Postgres, MySQL, MariaDB, SQLite, Microsoft SQL Server, Amazon Redshift and Snowflake’s Data Cloud. It features solid transaction support, relations, eager and lazy loading, read replication and more.

365 lines (364 loc) 13.3 kB
"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