@directus/api
Version:
Directus is a real-time API and App dashboard for managing SQL database content
155 lines (154 loc) • 7.17 kB
JavaScript
import { ContainsNullValuesError, InvalidForeignKeyError, NotNullViolationError, RecordNotUniqueError, ValueOutOfRangeError, ValueTooLongError, } from '@directus/errors';
import getDatabase from '../../index.js';
var MSSQLErrorCodes;
(function (MSSQLErrorCodes) {
MSSQLErrorCodes[MSSQLErrorCodes["FOREIGN_KEY_VIOLATION"] = 547] = "FOREIGN_KEY_VIOLATION";
MSSQLErrorCodes[MSSQLErrorCodes["NOT_NULL_VIOLATION"] = 515] = "NOT_NULL_VIOLATION";
MSSQLErrorCodes[MSSQLErrorCodes["NUMERIC_VALUE_OUT_OF_RANGE"] = 220] = "NUMERIC_VALUE_OUT_OF_RANGE";
MSSQLErrorCodes[MSSQLErrorCodes["UNIQUE_VIOLATION_INDEX"] = 2601] = "UNIQUE_VIOLATION_INDEX";
MSSQLErrorCodes[MSSQLErrorCodes["UNIQUE_VIOLATION_CONSTRAINT"] = 2627] = "UNIQUE_VIOLATION_CONSTRAINT";
MSSQLErrorCodes[MSSQLErrorCodes["VALUE_LIMIT_VIOLATION"] = 2628] = "VALUE_LIMIT_VIOLATION";
})(MSSQLErrorCodes || (MSSQLErrorCodes = {}));
export async function extractError(error, data) {
switch (error.number) {
case MSSQLErrorCodes.UNIQUE_VIOLATION_CONSTRAINT:
case MSSQLErrorCodes.UNIQUE_VIOLATION_INDEX:
return await uniqueViolation(error);
case MSSQLErrorCodes.NUMERIC_VALUE_OUT_OF_RANGE:
return numericValueOutOfRange();
case MSSQLErrorCodes.VALUE_LIMIT_VIOLATION:
return valueLimitViolation();
case MSSQLErrorCodes.NOT_NULL_VIOLATION:
return notNullViolation();
case MSSQLErrorCodes.FOREIGN_KEY_VIOLATION:
return foreignKeyViolation();
}
return error;
async function uniqueViolation(error) {
/**
* NOTE:
* SQL Server doesn't return the name of the offending column when a unique error is thrown:
*
* Constraint:
* insert into [articles] ([unique]) values (@p0)
* - Violation of UNIQUE KEY constraint 'unique_contraint_name'. Cannot insert duplicate key in object 'dbo.article'.
* The duplicate key value is (rijk).
*
* Index:
* insert into [articles] ([unique]) values (@p0)
* - Cannot insert duplicate key row in object 'dbo.articles' with unique index 'unique_index_name'.
* The duplicate key value is (rijk).
*
* While it's not ideal, the best next thing we can do is extract the column name from
* information_schema when this happens
*/
const betweenQuotes = /'([^']+)'/g;
const betweenParens = /\(([^)]+)\)/g;
const quoteMatches = error.message.match(betweenQuotes);
const parenMatches = error.message.match(betweenParens);
if (!quoteMatches || !parenMatches)
return error;
const [keyNameMatchIndex, collectionNameMatchIndex] = error.number === MSSQLErrorCodes.UNIQUE_VIOLATION_INDEX ? [1, 0] : [0, 1];
const keyName = quoteMatches[keyNameMatchIndex].slice(1, -1);
let collection = quoteMatches[collectionNameMatchIndex].slice(1, -1);
let field = null;
if (keyName) {
const database = getDatabase();
const constraintUsage = await database
.select('sys.columns.name as field', database.raw('OBJECT_NAME(??) as collection', ['sys.columns.object_id']))
.from('sys.indexes')
.innerJoin('sys.index_columns', (join) => {
join
.on('sys.indexes.object_id', '=', 'sys.index_columns.object_id')
.andOn('sys.indexes.index_id', '=', 'sys.index_columns.index_id');
})
.innerJoin('sys.columns', (join) => {
join
.on('sys.index_columns.object_id', '=', 'sys.columns.object_id')
.andOn('sys.index_columns.column_id', '=', 'sys.columns.column_id');
})
.where('sys.indexes.name', '=', keyName)
.first();
collection = constraintUsage?.collection;
field = constraintUsage?.field;
}
return new RecordNotUniqueError({
collection,
field,
value: field ? data[field] : null,
});
}
function numericValueOutOfRange() {
const betweenBrackets = /\[([^\]]+)\]/g;
const bracketMatches = error.message.match(betweenBrackets);
if (!bracketMatches)
return error;
const collection = bracketMatches[0].slice(1, -1);
/**
* NOTE
* MS SQL Doesn't return the offending column name in the error, nor any other identifying information
* we can use to extract the column name :(
*
* insert into [test1] ([small]) values (@p0)
* - Arithmetic overflow error for data type tinyint, value = 50000.
*/
const field = null;
return new ValueOutOfRangeError({
collection,
field,
value: field ? data[field] : null,
});
}
function valueLimitViolation() {
const betweenBrackets = /\[([^\]]+)\]/g;
const betweenQuotes = /'([^']+)'/g;
const bracketMatches = error.message.match(betweenBrackets);
const quoteMatches = error.message.match(betweenQuotes);
if (!bracketMatches || !quoteMatches)
return error;
const collection = bracketMatches[0].slice(1, -1);
const field = quoteMatches[1].slice(1, -1);
return new ValueTooLongError({
collection,
field,
value: field ? data[field] : null,
});
}
function notNullViolation() {
const betweenBrackets = /\[([^\]]+)\]/g;
const betweenQuotes = /'([^']+)'/g;
const bracketMatches = error.message.match(betweenBrackets);
const quoteMatches = error.message.match(betweenQuotes);
if (!bracketMatches || !quoteMatches)
return error;
const collection = bracketMatches[0].slice(1, -1);
const field = quoteMatches[0].slice(1, -1);
if (error.message.includes('Cannot insert the value NULL into column')) {
return new ContainsNullValuesError({ collection, field });
}
return new NotNullViolationError({
collection,
field,
});
}
function foreignKeyViolation() {
const betweenUnderscores = /__(.+)__/g;
const betweenParens = /\(([^)]+)\)/g;
// NOTE:
// Seeing that MS SQL doesn't return the offending column name, we have to extract it from the
// foreign key constraint name as generated by the database. This'll probably fail if you have
// custom names for whatever reason.
const underscoreMatches = error.message.match(betweenUnderscores);
const parenMatches = error.message.match(betweenParens);
if (!underscoreMatches || !parenMatches)
return error;
const underscoreParts = underscoreMatches[0].split('__');
const collection = underscoreParts[1];
const field = underscoreParts[2];
return new InvalidForeignKeyError({
collection,
field,
value: field ? data[field] : null,
});
}
}