mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Fix diff validation and apply (#18048)
Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com> Co-authored-by: ian <licitdev@gmail.com>
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
SnapshotField,
|
||||
} from '../types/index.js';
|
||||
import { getSchema } from './get-schema.js';
|
||||
import { getHelpers } from '../database/helpers/index.js';
|
||||
|
||||
type CollectionDelta = {
|
||||
collection: string;
|
||||
@@ -32,6 +33,7 @@ export async function applyDiff(
|
||||
options?: { database?: Knex; schema?: SchemaOverview }
|
||||
): Promise<void> {
|
||||
const database = options?.database ?? getDatabase();
|
||||
const helpers = getHelpers(database);
|
||||
const schema = options?.schema ?? (await getSchema({ database, bypassCache: true }));
|
||||
|
||||
const nestedActionEvents: ActionEventParams[] = [];
|
||||
@@ -41,6 +43,8 @@ export async function applyDiff(
|
||||
bypassLimits: true,
|
||||
};
|
||||
|
||||
const runPostColumnChange = await helpers.schema.preColumnChange();
|
||||
|
||||
await database.transaction(async (trx) => {
|
||||
const collectionsService = new CollectionsService({ knex: trx, schema });
|
||||
|
||||
@@ -301,6 +305,10 @@ export async function applyDiff(
|
||||
}
|
||||
});
|
||||
|
||||
if (runPostColumnChange) {
|
||||
await helpers.schema.postColumnChange();
|
||||
}
|
||||
|
||||
await clearSystemCache();
|
||||
|
||||
if (nestedActionEvents.length > 0) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Column } from '@directus/schema';
|
||||
import type { Field, Relation } from '@directus/types';
|
||||
import { pick } from 'lodash-es';
|
||||
import type { Collection } from '../types/index.js';
|
||||
@@ -50,6 +51,26 @@ export function sanitizeField(field: Field | undefined, sanitizeAllSchema = fals
|
||||
return pick(field, pickedPaths);
|
||||
}
|
||||
|
||||
export function sanitizeColumn(column: Column) {
|
||||
return pick(column, [
|
||||
'name',
|
||||
'table',
|
||||
'data_type',
|
||||
'default_value',
|
||||
'max_length',
|
||||
'numeric_precision',
|
||||
'numeric_scale',
|
||||
'is_nullable',
|
||||
'is_unique',
|
||||
'is_primary_key',
|
||||
'is_generated',
|
||||
'generation_expression',
|
||||
'has_auto_increment',
|
||||
'foreign_key_table',
|
||||
'foreign_key_column',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick certain database vendor specific relation properties that should be compared when performing diff
|
||||
*
|
||||
|
||||
@@ -131,6 +131,168 @@ describe('should throw accurate error', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should not throw error for diffs with varying types of lhs/rhs', () => {
|
||||
const diff: any = {
|
||||
hash: 'abc',
|
||||
diff: {
|
||||
collections: [
|
||||
{
|
||||
collection: 'a',
|
||||
diff: [
|
||||
{
|
||||
kind: 'E',
|
||||
path: ['meta', 'color'],
|
||||
lhs: null,
|
||||
rhs: '#6644FF',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
collection: 'a',
|
||||
diff: [
|
||||
{
|
||||
kind: 'A',
|
||||
path: ['meta', 'translations'],
|
||||
index: 1,
|
||||
item: {
|
||||
kind: 'N',
|
||||
rhs: {
|
||||
language: 'de-DE',
|
||||
translation: 'Collection A de-DE',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
collection: 'b',
|
||||
diff: [
|
||||
{
|
||||
kind: 'E',
|
||||
path: ['meta', 'translations', 1, 'language'],
|
||||
lhs: 'es-ES',
|
||||
rhs: 'nl-NL',
|
||||
},
|
||||
{
|
||||
kind: 'E',
|
||||
path: ['meta', 'translations', 1, 'translation'],
|
||||
lhs: 'nombre',
|
||||
rhs: 'naam',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
collection: 'a',
|
||||
field: 'new_field',
|
||||
diff: [
|
||||
{
|
||||
kind: 'N',
|
||||
rhs: {
|
||||
collection: 'a',
|
||||
field: 'new_field',
|
||||
type: 'string',
|
||||
meta: {},
|
||||
schema: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
collection: 'a',
|
||||
field: 'update_field',
|
||||
diff: [
|
||||
{
|
||||
kind: 'E',
|
||||
path: ['meta', 'options'],
|
||||
lhs: {
|
||||
iconLeft: 'check_circle',
|
||||
},
|
||||
rhs: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
collection: 'a',
|
||||
field: 'delete_field',
|
||||
diff: [
|
||||
{
|
||||
kind: 'D',
|
||||
lhs: {
|
||||
collection: 'a',
|
||||
field: 'delete_field',
|
||||
type: 'string',
|
||||
meta: {},
|
||||
schema: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
relations: [
|
||||
{
|
||||
collection: 'a',
|
||||
field: 'm2o',
|
||||
related_collection: 'b',
|
||||
diff: [
|
||||
{
|
||||
kind: 'E',
|
||||
path: ['schema', 'on_delete'],
|
||||
lhs: 'SET NULL',
|
||||
rhs: 'CASCADE',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const snapshot = { hash: 'abc' } as SnapshotWithHash;
|
||||
|
||||
expect(() => validateApplyDiff(diff, snapshot)).not.toThrow();
|
||||
});
|
||||
|
||||
test('should not throw error for relation diff with null related_collection (applicable for M2A junction tables)', () => {
|
||||
const diff: any = {
|
||||
hash: 'abc',
|
||||
diff: {
|
||||
collections: [],
|
||||
fields: [],
|
||||
relations: [
|
||||
{
|
||||
collection: 'pages_blocks',
|
||||
field: 'item',
|
||||
related_collection: null,
|
||||
diff: [
|
||||
{
|
||||
kind: 'N',
|
||||
rhs: {
|
||||
collection: 'pages_blocks',
|
||||
field: 'item',
|
||||
related_collection: null,
|
||||
meta: {
|
||||
junction_field: 'pages_id',
|
||||
many_collection: 'pages_blocks',
|
||||
many_field: 'item',
|
||||
one_allowed_collections: ['a', 'b'],
|
||||
one_collection: null,
|
||||
one_collection_field: 'collection',
|
||||
one_deselect_action: 'nullify',
|
||||
one_field: null,
|
||||
sort_field: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const snapshot = { hash: 'abc' } as SnapshotWithHash;
|
||||
|
||||
expect(() => validateApplyDiff(diff, snapshot)).not.toThrow();
|
||||
});
|
||||
|
||||
test('should detect empty diff', () => {
|
||||
const diff = {
|
||||
hash: 'abc',
|
||||
|
||||
@@ -6,12 +6,16 @@ const deepDiffSchema = Joi.object({
|
||||
kind: Joi.string()
|
||||
.valid(...Object.values(DiffKind))
|
||||
.required(),
|
||||
path: Joi.array().items(Joi.string()),
|
||||
lhs: Joi.object().when('kind', { is: [DiffKind.DELETE, DiffKind.EDIT], then: Joi.required() }),
|
||||
rhs: Joi.object().when('kind', { is: [DiffKind.NEW, DiffKind.EDIT], then: Joi.required() }),
|
||||
path: Joi.array().items(Joi.alternatives().try(Joi.string(), Joi.number())),
|
||||
lhs: Joi.any().when('kind', { is: [DiffKind.NEW, DiffKind.ARRAY], then: Joi.optional(), otherwise: Joi.required() }),
|
||||
rhs: Joi.any().when('kind', {
|
||||
is: [DiffKind.DELETE, DiffKind.ARRAY],
|
||||
then: Joi.optional(),
|
||||
otherwise: Joi.required(),
|
||||
}),
|
||||
index: Joi.number().when('kind', { is: DiffKind.ARRAY, then: Joi.required() }),
|
||||
item: Joi.link('/').when('kind', { is: DiffKind.ARRAY, then: Joi.required() }),
|
||||
});
|
||||
item: Joi.link('#deepdiff').when('kind', { is: DiffKind.ARRAY, then: Joi.required() }),
|
||||
}).id('deepdiff');
|
||||
|
||||
const applyJoiSchema = Joi.object({
|
||||
hash: Joi.string().required(),
|
||||
|
||||
Reference in New Issue
Block a user