directus

Форк
0
171 строка · 5.1 Кб
1
import {
2
	ContainsNullValuesError,
3
	InvalidForeignKeyError,
4
	NotNullViolationError,
5
	RecordNotUniqueError,
6
	ValueOutOfRangeError,
7
	ValueTooLongError,
8
} from '@directus/errors';
9
import type { MySQLError } from './types.js';
10

11
enum MySQLErrorCodes {
12
	UNIQUE_VIOLATION = 'ER_DUP_ENTRY',
13
	NUMERIC_VALUE_OUT_OF_RANGE = 'ER_WARN_DATA_OUT_OF_RANGE',
14
	ER_DATA_TOO_LONG = 'ER_DATA_TOO_LONG',
15
	NOT_NULL_VIOLATION = 'ER_BAD_NULL_ERROR',
16
	FOREIGN_KEY_VIOLATION = 'ER_NO_REFERENCED_ROW_2',
17
	ER_INVALID_USE_OF_NULL = 'ER_INVALID_USE_OF_NULL',
18
	WARN_DATA_TRUNCATED = 'WARN_DATA_TRUNCATED',
19
}
20

21
export function extractError(error: MySQLError): MySQLError | Error {
22
	switch (error.code) {
23
		case MySQLErrorCodes.UNIQUE_VIOLATION:
24
			return uniqueViolation(error);
25
		case MySQLErrorCodes.NUMERIC_VALUE_OUT_OF_RANGE:
26
			return numericValueOutOfRange(error);
27
		case MySQLErrorCodes.ER_DATA_TOO_LONG:
28
			return valueLimitViolation(error);
29
		case MySQLErrorCodes.NOT_NULL_VIOLATION:
30
			return notNullViolation(error);
31
		case MySQLErrorCodes.FOREIGN_KEY_VIOLATION:
32
			return foreignKeyViolation(error);
33
		// Note: MariaDB throws data truncated for null value error
34
		case MySQLErrorCodes.ER_INVALID_USE_OF_NULL:
35
		case MySQLErrorCodes.WARN_DATA_TRUNCATED:
36
			return containsNullValues(error);
37
	}
38

39
	return error;
40
}
41

42
function uniqueViolation(error: MySQLError) {
43
	const betweenQuotes = /'([^']+)'/g;
44
	const matches = error.sqlMessage.match(betweenQuotes);
45

46
	if (!matches) return error;
47

48
	/**
49
	 * MySQL's error doesn't return the field name in the error. In case the field is created through
50
	 * Directus (/ Knex), the key name will be `<collection>_<field>_unique` in which case we can pull
51
	 * the field name from the key name.
52
	 * If the field is the primary key it instead will be `<collection>_PRIMARY` for MySQL 8+
53
	 * and `PRIMARY` for MySQL 5.7 and MariaDB.
54
	 */
55

56
	let collection: string | null;
57
	let indexName: string;
58
	let field = null;
59

60
	if (matches[1]!.includes('.')) {
61
		// MySQL 8+ style error message
62

63
		// In case of primary key matches[1] is `'<collection>.PRIMARY'`
64
		// In case of other field matches[1] is `'<collection>.<collection>_<field>_unique'`
65
		[collection, indexName] = matches[1]!.slice(1, -1).split('.') as [string, string];
66
	} else {
67
		// MySQL 5.7 and MariaDB style error message
68

69
		// In case of primary key matches[1] is `'PRIMARY'`
70
		// In case of other field matches[1] is `'<collection>_<field>_unique'`
71
		indexName = matches[1]!.slice(1, -1);
72
		collection = indexName.includes('_') ? indexName.split('_')[0]! : null;
73
	}
74

75
	if (collection !== null && indexName.startsWith(`${collection}_`) && indexName.endsWith('_unique')) {
76
		field = indexName?.slice(collection.length + 1, -7);
77
	}
78

79
	return new RecordNotUniqueError({
80
		collection,
81
		field,
82
		primaryKey: indexName === 'PRIMARY', // propagate information about primary key violation
83
	});
84
}
85

86
function numericValueOutOfRange(error: MySQLError) {
87
	const betweenTicks = /`([^`]+)`/g;
88
	const betweenQuotes = /'([^']+)'/g;
89

90
	const tickMatches = error.sql.match(betweenTicks);
91
	const quoteMatches = error.sqlMessage.match(betweenQuotes);
92

93
	if (!tickMatches || !quoteMatches) return error;
94

95
	const collection = tickMatches[0]?.slice(1, -1);
96
	const field = quoteMatches[0]?.slice(1, -1);
97

98
	return new ValueOutOfRangeError({
99
		collection,
100
		field,
101
	});
102
}
103

104
function valueLimitViolation(error: MySQLError) {
105
	const betweenTicks = /`([^`]+)`/g;
106
	const betweenQuotes = /'([^']+)'/g;
107

108
	const tickMatches = error.sql.match(betweenTicks);
109
	const quoteMatches = error.sqlMessage.match(betweenQuotes);
110

111
	if (!tickMatches || !quoteMatches) return error;
112

113
	const collection = tickMatches[0]?.slice(1, -1);
114
	const field = quoteMatches[0]?.slice(1, -1);
115

116
	return new ValueTooLongError({
117
		collection,
118
		field,
119
	});
120
}
121

122
function notNullViolation(error: MySQLError) {
123
	const betweenTicks = /`([^`]+)`/g;
124
	const betweenQuotes = /'([^']+)'/g;
125

126
	const tickMatches = error.sql.match(betweenTicks);
127
	const quoteMatches = error.sqlMessage.match(betweenQuotes);
128

129
	if (!tickMatches || !quoteMatches) return error;
130

131
	const collection = tickMatches[0]?.slice(1, -1);
132
	const field = quoteMatches[0]?.slice(1, -1);
133

134
	return new NotNullViolationError({
135
		collection,
136
		field,
137
	});
138
}
139

140
function foreignKeyViolation(error: MySQLError) {
141
	const betweenTicks = /`([^`]+)`/g;
142
	const betweenParens = /\(([^)]+)\)/g;
143

144
	const tickMatches = error.sqlMessage.match(betweenTicks);
145
	const parenMatches = error.sql.match(betweenParens);
146

147
	if (!tickMatches || !parenMatches) return error;
148

149
	const collection = tickMatches[1]!.slice(1, -1)!;
150
	const field = tickMatches[3]!.slice(1, -1)!;
151

152
	return new InvalidForeignKeyError({
153
		collection,
154
		field,
155
	});
156
}
157

158
function containsNullValues(error: MySQLError) {
159
	const betweenTicks = /`([^`]+)`/g;
160

161
	// Normally, we shouldn't read from the executed SQL. In this case, we're altering a single
162
	// column, so we shouldn't have the problem where multiple columns are altered at the same time
163
	const tickMatches = error.sql.match(betweenTicks);
164

165
	if (!tickMatches) return error;
166

167
	const collection = tickMatches[0]!.slice(1, -1)!;
168
	const field = tickMatches[1]!.slice(1, -1)!;
169

170
	return new ContainsNullValuesError({ collection, field });
171
}
172

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.