You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

349 lines
12 KiB

"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");
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 = [];
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