365 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			365 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| "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
 |