2
ContainsNullValuesError,
3
InvalidForeignKeyError,
8
} from '@directus/errors';
10
import getDatabase from '../../index.js';
11
import type { MSSQLError } from './types.js';
14
FOREIGN_KEY_VIOLATION = 547,
15
NOT_NULL_VIOLATION = 515,
16
NUMERIC_VALUE_OUT_OF_RANGE = 220,
17
UNIQUE_VIOLATION = 2601, // or 2627
18
VALUE_LIMIT_VIOLATION = 2628,
21
export async function extractError(error: MSSQLError): Promise<MSSQLError | Error> {
22
switch (error.number) {
23
case MSSQLErrorCodes.UNIQUE_VIOLATION:
25
return await uniqueViolation(error);
26
case MSSQLErrorCodes.NUMERIC_VALUE_OUT_OF_RANGE:
27
return numericValueOutOfRange(error);
28
case MSSQLErrorCodes.VALUE_LIMIT_VIOLATION:
29
return valueLimitViolation(error);
30
case MSSQLErrorCodes.NOT_NULL_VIOLATION:
31
return notNullViolation(error);
32
case MSSQLErrorCodes.FOREIGN_KEY_VIOLATION:
33
return foreignKeyViolation(error);
39
async function uniqueViolation(error: MSSQLError) {
42
* SQL Server doesn't return the name of the offending column when a unique constraint is thrown:
44
* insert into [articles] ([unique]) values (@p0)
45
* - Violation of UNIQUE KEY constraint 'UQ__articles__5A062640242004EB'.
46
* Cannot insert duplicate key in object 'dbo.articles'. The duplicate key value is (rijk).
48
* While it's not ideal, the best next thing we can do is extract the column name from
49
* information_schema when this happens
52
const betweenQuotes = /'([^']+)'/g;
53
const betweenParens = /\(([^)]+)\)/g;
55
const quoteMatches = error.message.match(betweenQuotes);
56
const parenMatches = error.message.match(betweenParens);
58
if (!quoteMatches || !parenMatches) return error;
60
const keyName = quoteMatches[1]!.slice(1, -1);
62
let collection = quoteMatches[0]!.slice(1, -1);
63
let field: string | null = null;
66
const database = getDatabase();
68
const constraintUsage = await database
69
.select('sys.columns.name as field', database.raw('OBJECT_NAME(??) as collection', ['sys.columns.object_id']))
71
.innerJoin('sys.index_columns', (join) => {
73
.on('sys.indexes.object_id', '=', 'sys.index_columns.object_id')
74
.andOn('sys.indexes.index_id', '=', 'sys.index_columns.index_id');
76
.innerJoin('sys.columns', (join) => {
78
.on('sys.index_columns.object_id', '=', 'sys.columns.object_id')
79
.andOn('sys.index_columns.column_id', '=', 'sys.columns.column_id');
81
.where('sys.indexes.name', '=', keyName)
84
collection = constraintUsage?.collection;
85
field = constraintUsage?.field;
88
return new RecordNotUniqueError({
94
function numericValueOutOfRange(error: MSSQLError) {
95
const betweenBrackets = /\[([^\]]+)\]/g;
97
const bracketMatches = error.message.match(betweenBrackets);
99
if (!bracketMatches) return error;
101
const collection = bracketMatches[0].slice(1, -1);
105
* MS SQL Doesn't return the offending column name in the error, nor any other identifying information
106
* we can use to extract the column name :(
108
* insert into [test1] ([small]) values (@p0)
109
* - Arithmetic overflow error for data type tinyint, value = 50000.
114
return new ValueOutOfRangeError({
120
function valueLimitViolation(error: MSSQLError) {
121
const betweenBrackets = /\[([^\]]+)\]/g;
122
const betweenQuotes = /'([^']+)'/g;
124
const bracketMatches = error.message.match(betweenBrackets);
125
const quoteMatches = error.message.match(betweenQuotes);
127
if (!bracketMatches || !quoteMatches) return error;
129
const collection = bracketMatches[0].slice(1, -1);
130
const field = quoteMatches[1]!.slice(1, -1);
132
return new ValueTooLongError({
138
function notNullViolation(error: MSSQLError) {
139
const betweenBrackets = /\[([^\]]+)\]/g;
140
const betweenQuotes = /'([^']+)'/g;
142
const bracketMatches = error.message.match(betweenBrackets);
143
const quoteMatches = error.message.match(betweenQuotes);
145
if (!bracketMatches || !quoteMatches) return error;
147
const collection = bracketMatches[0].slice(1, -1);
148
const field = quoteMatches[0].slice(1, -1);
150
if (error.message.includes('Cannot insert the value NULL into column')) {
151
return new ContainsNullValuesError({ collection, field });
154
return new NotNullViolationError({
160
function foreignKeyViolation(error: MSSQLError) {
161
const betweenUnderscores = /__(.+)__/g;
162
const betweenParens = /\(([^)]+)\)/g;
165
// Seeing that MS SQL doesn't return the offending column name, we have to extract it from the
166
// foreign key constraint name as generated by the database. This'll probably fail if you have
167
// custom names for whatever reason.
169
const underscoreMatches = error.message.match(betweenUnderscores);
170
const parenMatches = error.message.match(betweenParens);
172
if (!underscoreMatches || !parenMatches) return error;
174
const underscoreParts = underscoreMatches[0].split('__');
176
const collection = underscoreParts[1]!;
177
const field = underscoreParts[2]!;
179
return new InvalidForeignKeyError({