Skip to content

Commit

Permalink
fix(mssql): return aggregated error instead of last error (#16188)
Browse files Browse the repository at this point in the history
  • Loading branch information
lohart13 committed Jul 19, 2023
1 parent c081850 commit dea29f7
Show file tree
Hide file tree
Showing 7 changed files with 816 additions and 480 deletions.
7 changes: 6 additions & 1 deletion packages/core/src/dialects/db2/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,15 @@ export class Db2Query extends AbstractQuery {
|| err.message.match(/SQL0530N/)
|| err.message.match(/SQL0531N/);
if (match && match.length > 0) {
const data = err.message.match(/(?:"([\w.]+)")/);
const constraintData = data && data.length > 0 ? data[1] : undefined;
const [, table, constraint] = constraintData.split('.');

return new sequelizeErrors.ForeignKeyConstraintError({
fields: null,
index: match[1],
index: constraint,
cause: err,
table,
});
}

Expand Down
71 changes: 28 additions & 43 deletions packages/core/src/dialects/mssql/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,12 +288,8 @@ export class MsSqlQuery extends AbstractQuery {
formatError(err) {
let match;

// TODO: err can be an AggregateError. When that happens, we must throw an AggregateError too instead of throwing only the second error,
// or we lose important information

match = err.message.match(/Violation of (?:UNIQUE|PRIMARY) KEY constraint '([^']*)'. Cannot insert duplicate key in object '.*'\.(:? The duplicate key value is \((.*)\).)?/s);
match = err.message.match(/Violation of (?:UNIQUE|PRIMARY) KEY constraint '([^']*)'\. Cannot insert duplicate key in object '.*'\.(:? The duplicate key value is \((.*)\).)?/s);
match = match || err.message.match(/Cannot insert duplicate key row in object .* with unique index '(.*)'\.(:? The duplicate key value is \((.*)\).)?/s);

if (match && match.length > 1) {
let fields = {};
const uniqueKey = this.model && this.model.getIndexes().find(index => index.unique && index.name === match[1]);
Expand Down Expand Up @@ -325,65 +321,54 @@ export class MsSqlQuery extends AbstractQuery {
));
});

return new sequelizeErrors.UniqueConstraintError({ message, errors, cause: err, fields });
const uniqueConstraintError = new sequelizeErrors.UniqueConstraintError({ message, errors, cause: err, fields });
if (err.errors?.length > 0) {
return new sequelizeErrors.AggregateError([...err.errors, uniqueConstraintError]);
}

return uniqueConstraintError;
}

match = err.message.match(/Failed on step '(.*)'.Could not create constraint. See previous errors./)
|| err.message.match(/The DELETE statement conflicted with the REFERENCE constraint "(.*)". The conflict occurred in database "(.*)", table "(.*)", column '(.*)'./)
|| err.message.match(/The (?:INSERT|MERGE|UPDATE) statement conflicted with the FOREIGN KEY constraint "(.*)". The conflict occurred in database "(.*)", table "(.*)", column '(.*)'./);
if (match && match.length > 0) {
return new sequelizeErrors.ForeignKeyConstraintError({
fields: null,
match = err.message.match(/The (?:DELETE|INSERT|MERGE|UPDATE) statement conflicted with the (?:FOREIGN KEY|REFERENCE) constraint "(.*)"\. The conflict occurred in database "(.*)", table "(.*)", column '(.*)'\./);
if (match && match.length > 1) {
const fkConstraintError = new sequelizeErrors.ForeignKeyConstraintError({
index: match[1],
cause: err,
table: match[3],
fields: [match[4]],
});
}

if (err.errors) {
for (const error of err.errors) {
match = error.message.match(/Could not create constraint or index. See previous errors./);
if (match && match.length > 0) {
return new sequelizeErrors.ForeignKeyConstraintError({
fields: null,
index: match[1],
cause: error,
});
}
if (err.errors?.length > 0) {
return new sequelizeErrors.AggregateError([...err.errors, fkConstraintError]);
}
}

match = err.message.match(/Could not drop constraint. See previous errors./);
if (match && match.length > 0) {
let constraint = err.sql.match(/(?:constraint|index) \[(.+?)]/i);
constraint = constraint ? constraint[1] : undefined;
let table = err.sql.match(/table \[(.+?)]/i);
table = table ? table[1] : undefined;

return new sequelizeErrors.UnknownConstraintError({
message: match[1],
constraint,
table,
cause: err,
});
return fkConstraintError;
}

if (err.errors) {
for (const error of err.errors) {
match = error.message.match(/Could not drop constraint. See previous errors./);
if (err.errors?.length > 0) {
let firstError;
for (const [index, error] of err.errors.entries()) {
match = error.message.match(/Could not (?:create|drop) constraint(?: or index)?\. See previous errors\./);
if (match && match.length > 0) {
let constraint = err.sql.match(/(?:constraint|index) \[(.+?)]/i);
constraint = constraint ? constraint[1] : undefined;
let table = err.sql.match(/table \[(.+?)]/i);
table = table ? table[1] : undefined;

return new sequelizeErrors.UnknownConstraintError({
message: match[1],
firstError = new sequelizeErrors.UnknownConstraintError({
message: err.errors[index - 1].message,
constraint,
table,
cause: error,
cause: err,
});
}
}

if (firstError) {
return new sequelizeErrors.AggregateError([...err.errors, firstError]);
}

return new sequelizeErrors.AggregateError(err.errors);
}

return new sequelizeErrors.DatabaseError(err);
Expand Down
9 changes: 5 additions & 4 deletions packages/core/src/errors/validation-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,23 +176,24 @@ export class ValidationErrorItem extends Error {
* defaults to "." (fullstop). only used and validated if useTypeAsNS is TRUE.
* @throws {Error} thrown if NSSeparator is found to be invalid.
*/
getValidatorKey(useTypeAsNS: boolean, NSSeparator: string): string {
getValidatorKey(useTypeAsNS: false): string;
getValidatorKey(useTypeAsNS?: true, NSSeparator?: string): string;
getValidatorKey(useTypeAsNS: boolean = true, NSSeparator: string = '.'): string {
const useTANS = useTypeAsNS === undefined || Boolean(useTypeAsNS);
const NSSep = NSSeparator === undefined ? '.' : NSSeparator;

const type = this.origin;
const key = this.validatorKey || this.validatorName;
const useNS = useTANS && type && ValidationErrorItemOrigin[type];

if (useNS && (typeof NSSep !== 'string' || NSSep.length === 0)) {
if (useNS && (typeof NSSeparator !== 'string' || NSSeparator.length === 0)) {
throw new Error('Invalid namespace separator given, must be a non-empty string');
}

if (!(typeof key === 'string' && key.length > 0)) {
return '';
}

return (useNS ? [this.origin, key].join(NSSep) : key).toLowerCase().trim();
return (useNS ? [this.origin, key].join(NSSeparator) : key).toLowerCase().trim();
}
}

Expand Down

0 comments on commit dea29f7

Please sign in to comment.