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:
Azri Kahar
2023-04-10 23:53:51 +08:00
committed by GitHub
parent 98335f248f
commit dd3202ce38
7 changed files with 448 additions and 6 deletions

View File

@@ -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) {

View File

@@ -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
*

View File

@@ -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',

View File

@@ -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(),