UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

147 lines (146 loc) 6.57 kB
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"] = 2601] = "UNIQUE_VIOLATION"; MSSQLErrorCodes[MSSQLErrorCodes["VALUE_LIMIT_VIOLATION"] = 2628] = "VALUE_LIMIT_VIOLATION"; })(MSSQLErrorCodes || (MSSQLErrorCodes = {})); export async function extractError(error, data) { switch (error.number) { case MSSQLErrorCodes.UNIQUE_VIOLATION: case 2627: return await uniqueViolation(); 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() { /** * NOTE: * SQL Server doesn't return the name of the offending column when a unique constraint is thrown: * * insert into [articles] ([unique]) values (@p0) * - Violation of UNIQUE KEY constraint 'UQ__articles__5A062640242004EB'. * Cannot insert duplicate key in object 'dbo.articles'. 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 keyName = quoteMatches[1].slice(1, -1); let collection = quoteMatches[0].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, }); } }