mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Ask for value when changing nullable to not-nullable (#5400)
* Add ContainsNullValues exception abstraction * Add dialog for null values when disabling non-null Fixes #2934 * Add translation for CONTAINS_NULL_VALUE error * Make dialog title translated
This commit is contained in:
12
api/src/exceptions/database/contains-null-values.ts
Normal file
12
api/src/exceptions/database/contains-null-values.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { BaseException } from '../base';
|
||||
|
||||
type Exceptions = {
|
||||
collection: string;
|
||||
field: string;
|
||||
};
|
||||
|
||||
export class ContainsNullValuesException extends BaseException {
|
||||
constructor(field: string, exceptions?: Exceptions) {
|
||||
super(`Field "${field}" contains null values.`, 400, 'CONTAINS_NULL_VALUES', exceptions);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import database from '../../../database';
|
||||
import { ContainsNullValuesException } from '../contains-null-values';
|
||||
import { InvalidForeignKeyException } from '../invalid-foreign-key';
|
||||
import { NotNullViolationException } from '../not-null-violation';
|
||||
import { RecordNotUniqueException } from '../record-not-unique';
|
||||
@@ -134,6 +135,10 @@ function notNullViolation(error: MSSQLError) {
|
||||
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 ContainsNullValuesException(field, { collection, field });
|
||||
}
|
||||
|
||||
return new NotNullViolationException(field, {
|
||||
collection,
|
||||
field,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ContainsNullValuesException } from '../contains-null-values';
|
||||
import { InvalidForeignKeyException } from '../invalid-foreign-key';
|
||||
import { NotNullViolationException } from '../not-null-violation';
|
||||
import { RecordNotUniqueException } from '../record-not-unique';
|
||||
@@ -11,6 +12,8 @@ enum MySQLErrorCodes {
|
||||
ER_DATA_TOO_LONG = 'ER_DATA_TOO_LONG',
|
||||
NOT_NULL_VIOLATION = 'ER_BAD_NULL_ERROR',
|
||||
FOREIGN_KEY_VIOLATION = 'ER_NO_REFERENCED_ROW_2',
|
||||
ER_INVALID_USE_OF_NULL = 'ER_INVALID_USE_OF_NULL',
|
||||
WARN_DATA_TRUNCATED = 'WARN_DATA_TRUNCATED',
|
||||
}
|
||||
|
||||
export function extractError(error: MySQLError): MySQLError | Error {
|
||||
@@ -25,7 +28,12 @@ export function extractError(error: MySQLError): MySQLError | Error {
|
||||
return notNullViolation(error);
|
||||
case MySQLErrorCodes.FOREIGN_KEY_VIOLATION:
|
||||
return foreignKeyViolation(error);
|
||||
// Note: MariaDB throws data truncated for null value error
|
||||
case MySQLErrorCodes.ER_INVALID_USE_OF_NULL:
|
||||
case MySQLErrorCodes.WARN_DATA_TRUNCATED:
|
||||
return containsNullValues(error);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -132,3 +140,17 @@ function foreignKeyViolation(error: MySQLError) {
|
||||
invalid,
|
||||
});
|
||||
}
|
||||
|
||||
function containsNullValues(error: MySQLError) {
|
||||
const betweenTicks = /`([^`]+)`/g;
|
||||
|
||||
// Normally, we shouldn't read from the executed SQL. In this case, we're altering a single
|
||||
// column, so we shouldn't have the problem where multiple columns are altered at the same time
|
||||
const tickMatches = error.sql.match(betweenTicks);
|
||||
|
||||
if (!tickMatches) return error;
|
||||
|
||||
const field = tickMatches[1].slice(1, -1);
|
||||
|
||||
return new ContainsNullValuesException(field);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,28 @@
|
||||
export function extractError(error: Error): Error {
|
||||
return error;
|
||||
import { ContainsNullValuesException } from '../contains-null-values';
|
||||
import { OracleError } from './types';
|
||||
|
||||
enum OracleErrorCodes {
|
||||
'CONTAINS_NULL_VALUES' = 2296,
|
||||
// @TODO extend with other errors
|
||||
}
|
||||
|
||||
export function extractError(error: OracleError): OracleError | Error {
|
||||
switch (error.errorNum) {
|
||||
case OracleErrorCodes.CONTAINS_NULL_VALUES:
|
||||
return containsNullValues(error);
|
||||
default:
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
function containsNullValues(error: OracleError): OracleError | ContainsNullValuesException {
|
||||
const betweenQuotes = /"([^"]+)"/g;
|
||||
const matches = error.message.match(betweenQuotes);
|
||||
|
||||
if (!matches) return error;
|
||||
|
||||
const collection = matches[0].slice(1, -1);
|
||||
const field = matches[1].slice(1, -1);
|
||||
|
||||
return new ContainsNullValuesException(field, { collection, field });
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ContainsNullValuesException } from '../contains-null-values';
|
||||
import { InvalidForeignKeyException } from '../invalid-foreign-key';
|
||||
import { NotNullViolationException } from '../not-null-violation';
|
||||
import { RecordNotUniqueException } from '../record-not-unique';
|
||||
@@ -88,9 +89,12 @@ function valueLimitViolation(error: PostgresError) {
|
||||
|
||||
function notNullViolation(error: PostgresError) {
|
||||
const { table, column } = error;
|
||||
|
||||
if (!column) return error;
|
||||
|
||||
if (error.message.endsWith('contains null values')) {
|
||||
return new ContainsNullValuesException(column, { collection: table, field: column });
|
||||
}
|
||||
|
||||
return new NotNullViolationException(column, {
|
||||
collection: table,
|
||||
field: column,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ContainsNullValuesException } from '../contains-null-values';
|
||||
import { InvalidForeignKeyException } from '../invalid-foreign-key';
|
||||
import { NotNullViolationException } from '../not-null-violation';
|
||||
import { RecordNotUniqueException } from '../record-not-unique';
|
||||
@@ -41,6 +42,16 @@ function notNullConstraint(error: SQLiteError) {
|
||||
const [table, column] = errorParts[errorParts.length - 1].split('.');
|
||||
|
||||
if (table && column) {
|
||||
// Now this gets a little finicky... SQLite doesn't have any native ALTER, so Knex implements
|
||||
// it by creating a new table, and then copying the data over. That also means we'll never get
|
||||
// a ContainsNullValues constraint error, as there is no ALTER. HOWEVER, we can hack around
|
||||
// that by checking for the collection name, as Knex's alter default template name will always
|
||||
// start with _knex_temp. The best we can do in this case is check for that, and use it to
|
||||
// decide between NotNullViolation and ContainsNullValues
|
||||
if (table.startsWith('_knex_temp_alter')) {
|
||||
return new ContainsNullValuesException(column);
|
||||
}
|
||||
|
||||
return new NotNullViolationException(column, {
|
||||
collection: table,
|
||||
field: column,
|
||||
|
||||
@@ -31,10 +31,16 @@ export type PostgresError = {
|
||||
constraint?: string;
|
||||
};
|
||||
|
||||
export type OracleError = {
|
||||
message: string;
|
||||
errorNum: number;
|
||||
offset: number;
|
||||
};
|
||||
|
||||
export type SQLiteError = {
|
||||
message: string;
|
||||
errno: number;
|
||||
code: string;
|
||||
};
|
||||
|
||||
export type SQLError = MSSQLError & MySQLError & PostgresError & SQLiteError & Error;
|
||||
export type SQLError = MSSQLError & MySQLError & PostgresError & SQLiteError & OracleError & Error;
|
||||
|
||||
Reference in New Issue
Block a user