directus

Форк
0
/
fields.ts 
812 строк · 24.1 Кб
1
import {
2
	KNEX_TYPES,
3
	REGEX_BETWEEN_PARENS,
4
	DEFAULT_NUMERIC_PRECISION,
5
	DEFAULT_NUMERIC_SCALE,
6
} from '@directus/constants';
7
import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
8
import type { Column, SchemaInspector } from '@directus/schema';
9
import { createInspector } from '@directus/schema';
10
import type { Accountability, Field, FieldMeta, RawField, SchemaOverview, Type } from '@directus/types';
11
import { addFieldFlag, toArray } from '@directus/utils';
12
import type Keyv from 'keyv';
13
import type { Knex } from 'knex';
14
import { isEqual, isNil, merge } from 'lodash-es';
15
import { clearSystemCache, getCache } from '../cache.js';
16
import { ALIAS_TYPES } from '../constants.js';
17
import { translateDatabaseError } from '../database/errors/translate.js';
18
import type { Helpers } from '../database/helpers/index.js';
19
import { getHelpers } from '../database/helpers/index.js';
20
import getDatabase, { getSchemaInspector } from '../database/index.js';
21
import emitter from '../emitter.js';
22
import type { AbstractServiceOptions, ActionEventParams, MutationOptions } from '../types/index.js';
23
import getDefaultValue from '../utils/get-default-value.js';
24
import { getSystemFieldRowsWithAuthProviders } from '../utils/get-field-system-rows.js';
25
import getLocalType from '../utils/get-local-type.js';
26
import { getSchema } from '../utils/get-schema.js';
27
import { sanitizeColumn } from '../utils/sanitize-schema.js';
28
import { shouldClearCache } from '../utils/should-clear-cache.js';
29
import { transaction } from '../utils/transaction.js';
30
import { ItemsService } from './items.js';
31
import { PayloadService } from './payload.js';
32
import { RelationsService } from './relations.js';
33

34
const systemFieldRows = getSystemFieldRowsWithAuthProviders();
35

36
export class FieldsService {
37
	knex: Knex;
38
	helpers: Helpers;
39
	accountability: Accountability | null;
40
	itemsService: ItemsService;
41
	payloadService: PayloadService;
42
	schemaInspector: SchemaInspector;
43
	schema: SchemaOverview;
44
	cache: Keyv<any> | null;
45
	systemCache: Keyv<any>;
46

47
	constructor(options: AbstractServiceOptions) {
48
		this.knex = options.knex || getDatabase();
49
		this.helpers = getHelpers(this.knex);
50
		this.schemaInspector = options.knex ? createInspector(options.knex) : getSchemaInspector();
51
		this.accountability = options.accountability || null;
52
		this.itemsService = new ItemsService('directus_fields', options);
53
		this.payloadService = new PayloadService('directus_fields', options);
54
		this.schema = options.schema;
55

56
		const { cache, systemCache } = getCache();
57

58
		this.cache = cache;
59
		this.systemCache = systemCache;
60
	}
61

62
	private get hasReadAccess() {
63
		return !!this.accountability?.permissions?.find((permission) => {
64
			return permission.collection === 'directus_fields' && permission.action === 'read';
65
		});
66
	}
67

68
	async readAll(collection?: string): Promise<Field[]> {
69
		let fields: FieldMeta[];
70

71
		if (this.accountability && this.accountability.admin !== true && this.hasReadAccess === false) {
72
			throw new ForbiddenError();
73
		}
74

75
		const nonAuthorizedItemsService = new ItemsService('directus_fields', {
76
			knex: this.knex,
77
			schema: this.schema,
78
		});
79

80
		if (collection) {
81
			fields = (await nonAuthorizedItemsService.readByQuery({
82
				filter: { collection: { _eq: collection } },
83
				limit: -1,
84
			})) as FieldMeta[];
85

86
			fields.push(...systemFieldRows.filter((fieldMeta) => fieldMeta.collection === collection));
87
		} else {
88
			fields = (await nonAuthorizedItemsService.readByQuery({ limit: -1 })) as FieldMeta[];
89
			fields.push(...systemFieldRows);
90
		}
91

92
		const columns = (await this.schemaInspector.columnInfo(collection)).map((column) => ({
93
			...column,
94
			default_value: getDefaultValue(
95
				column,
96
				fields.find((field) => field.collection === column.table && field.field === column.name),
97
			),
98
		}));
99

100
		const columnsWithSystem = columns.map((column) => {
101
			const field = fields.find((field) => {
102
				return field.field === column.name && field.collection === column.table;
103
			});
104

105
			const type = getLocalType(column, field);
106

107
			const data = {
108
				collection: column.table,
109
				field: column.name,
110
				type: type,
111
				schema: column,
112
				meta: field || null,
113
			};
114

115
			return data as Field;
116
		});
117

118
		const aliasQuery = this.knex.select<any[]>('*').from('directus_fields');
119

120
		if (collection) {
121
			aliasQuery.andWhere('collection', collection);
122
		}
123

124
		let aliasFields = [...((await this.payloadService.processValues('read', await aliasQuery)) as FieldMeta[])];
125

126
		if (collection) {
127
			aliasFields.push(...systemFieldRows.filter((fieldMeta) => fieldMeta.collection === collection));
128
		} else {
129
			aliasFields.push(...systemFieldRows);
130
		}
131

132
		aliasFields = aliasFields.filter((field) => {
133
			const specials = toArray(field.special);
134

135
			for (const type of ALIAS_TYPES) {
136
				if (specials.includes(type)) return true;
137
			}
138

139
			return false;
140
		});
141

142
		const aliasFieldsAsField = aliasFields.map((field) => {
143
			const type = getLocalType(undefined, field);
144

145
			const data = {
146
				collection: field.collection,
147
				field: field.field,
148
				type,
149
				schema: null,
150
				meta: field,
151
			};
152

153
			return data;
154
		}) as Field[];
155

156
		const knownCollections = Object.keys(this.schema.collections);
157

158
		const result = [...columnsWithSystem, ...aliasFieldsAsField].filter((field) =>
159
			knownCollections.includes(field.collection),
160
		);
161

162
		// Filter the result so we only return the fields you have read access to
163
		if (this.accountability && this.accountability.admin !== true) {
164
			const permissions = this.accountability.permissions!.filter((permission) => {
165
				return permission.action === 'read';
166
			});
167

168
			const allowedFieldsInCollection: Record<string, string[]> = {};
169

170
			permissions.forEach((permission) => {
171
				allowedFieldsInCollection[permission.collection] = permission.fields ?? [];
172
			});
173

174
			if (collection && collection in allowedFieldsInCollection === false) {
175
				throw new ForbiddenError();
176
			}
177

178
			return result.filter((field) => {
179
				if (field.collection in allowedFieldsInCollection === false) return false;
180
				const allowedFields = allowedFieldsInCollection[field.collection]!;
181
				if (allowedFields[0] === '*') return true;
182
				return allowedFields.includes(field.field);
183
			});
184
		}
185

186
		// Update specific database type overrides
187
		for (const field of result) {
188
			if (field.meta?.special?.includes('cast-timestamp')) {
189
				field.type = 'timestamp';
190
			} else if (field.meta?.special?.includes('cast-datetime')) {
191
				field.type = 'dateTime';
192
			}
193

194
			field.type = this.helpers.schema.processFieldType(field);
195
		}
196

197
		return result;
198
	}
199

200
	async readOne(collection: string, field: string): Promise<Record<string, any>> {
201
		if (this.accountability && this.accountability.admin !== true) {
202
			if (this.hasReadAccess === false) {
203
				throw new ForbiddenError();
204
			}
205

206
			const permissions = this.accountability.permissions!.find((permission) => {
207
				return permission.action === 'read' && permission.collection === collection;
208
			});
209

210
			if (!permissions || !permissions.fields) throw new ForbiddenError();
211

212
			if (permissions.fields.includes('*') === false) {
213
				const allowedFields = permissions.fields;
214
				if (allowedFields.includes(field) === false) throw new ForbiddenError();
215
			}
216
		}
217

218
		let column = undefined;
219
		let fieldInfo = await this.knex.select('*').from('directus_fields').where({ collection, field }).first();
220

221
		if (fieldInfo) {
222
			fieldInfo = (await this.payloadService.processValues('read', fieldInfo)) as FieldMeta[];
223
		}
224

225
		fieldInfo =
226
			fieldInfo ||
227
			systemFieldRows.find((fieldMeta) => fieldMeta.collection === collection && fieldMeta.field === field);
228

229
		try {
230
			column = await this.schemaInspector.columnInfo(collection, field);
231
		} catch {
232
			// Do nothing
233
		}
234

235
		if (!column && !fieldInfo) throw new ForbiddenError();
236

237
		const type = getLocalType(column, fieldInfo);
238

239
		const columnWithCastDefaultValue = column
240
			? {
241
					...column,
242
					default_value: getDefaultValue(column, fieldInfo),
243
			  }
244
			: null;
245

246
		const data = {
247
			collection,
248
			field,
249
			type,
250
			meta: fieldInfo || null,
251
			schema: type === 'alias' ? null : columnWithCastDefaultValue,
252
		};
253

254
		return data;
255
	}
256

257
	async createField(
258
		collection: string,
259
		field: Partial<Field> & { field: string; type: Type | null },
260
		table?: Knex.CreateTableBuilder, // allows collection creation to
261
		opts?: MutationOptions,
262
	): Promise<void> {
263
		if (this.accountability && this.accountability.admin !== true) {
264
			throw new ForbiddenError();
265
		}
266

267
		const runPostColumnChange = await this.helpers.schema.preColumnChange();
268
		const nestedActionEvents: ActionEventParams[] = [];
269

270
		try {
271
			const exists =
272
				field.field in this.schema.collections[collection]!.fields ||
273
				isNil(
274
					await this.knex.select('id').from('directus_fields').where({ collection, field: field.field }).first(),
275
				) === false;
276

277
			// Check if field already exists, either as a column, or as a row in directus_fields
278
			if (exists) {
279
				throw new InvalidPayloadError({
280
					reason: `Field "${field.field}" already exists in collection "${collection}"`,
281
				});
282
			}
283

284
			// Add flag for specific database type overrides
285
			const flagToAdd = this.helpers.date.fieldFlagForField(field.type);
286

287
			if (flagToAdd) {
288
				addFieldFlag(field, flagToAdd);
289
			}
290

291
			await transaction(this.knex, async (trx) => {
292
				const itemsService = new ItemsService('directus_fields', {
293
					knex: trx,
294
					accountability: this.accountability,
295
					schema: this.schema,
296
				});
297

298
				const hookAdjustedField =
299
					opts?.emitEvents !== false
300
						? await emitter.emitFilter(
301
								`fields.create`,
302
								field,
303
								{
304
									collection: collection,
305
								},
306
								{
307
									database: trx,
308
									schema: this.schema,
309
									accountability: this.accountability,
310
								},
311
						  )
312
						: field;
313

314
				if (hookAdjustedField.type && ALIAS_TYPES.includes(hookAdjustedField.type) === false) {
315
					if (table) {
316
						this.addColumnToTable(table, hookAdjustedField as Field);
317
					} else {
318
						await trx.schema.alterTable(collection, (table) => {
319
							this.addColumnToTable(table, hookAdjustedField as Field);
320
						});
321
					}
322
				}
323

324
				if (hookAdjustedField.meta) {
325
					const existingSortRecord: Record<'max', number | null> | undefined = await trx
326
						.from('directus_fields')
327
						.where(hookAdjustedField.meta?.group ? { collection, group: hookAdjustedField.meta.group } : { collection })
328
						.max('sort', { as: 'max' })
329
						.first();
330

331
					const newSortValue: number = existingSortRecord?.max ? existingSortRecord.max + 1 : 1;
332

333
					await itemsService.createOne(
334
						{
335
							...merge({ sort: newSortValue }, hookAdjustedField.meta),
336
							collection: collection,
337
							field: hookAdjustedField.field,
338
						},
339
						{ emitEvents: false },
340
					);
341
				}
342

343
				const actionEvent = {
344
					event: 'fields.create',
345
					meta: {
346
						payload: hookAdjustedField,
347
						key: hookAdjustedField.field,
348
						collection: collection,
349
					},
350
					context: {
351
						database: getDatabase(),
352
						schema: this.schema,
353
						accountability: this.accountability,
354
					},
355
				};
356

357
				if (opts?.bypassEmitAction) {
358
					opts.bypassEmitAction(actionEvent);
359
				} else {
360
					nestedActionEvents.push(actionEvent);
361
				}
362
			});
363
		} finally {
364
			if (runPostColumnChange) {
365
				await this.helpers.schema.postColumnChange();
366
			}
367

368
			if (shouldClearCache(this.cache, opts)) {
369
				await this.cache.clear();
370
			}
371

372
			if (opts?.autoPurgeSystemCache !== false) {
373
				await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache });
374
			}
375

376
			if (opts?.emitEvents !== false && nestedActionEvents.length > 0) {
377
				const updatedSchema = await getSchema();
378

379
				for (const nestedActionEvent of nestedActionEvents) {
380
					nestedActionEvent.context.schema = updatedSchema;
381
					emitter.emitAction(nestedActionEvent.event, nestedActionEvent.meta, nestedActionEvent.context);
382
				}
383
			}
384
		}
385
	}
386

387
	async updateField(collection: string, field: RawField, opts?: MutationOptions): Promise<string> {
388
		if (this.accountability && this.accountability.admin !== true) {
389
			throw new ForbiddenError();
390
		}
391

392
		const runPostColumnChange = await this.helpers.schema.preColumnChange();
393
		const nestedActionEvents: ActionEventParams[] = [];
394

395
		// 'type' is required for further checks on schema update
396
		if (field.schema && !field.type) {
397
			const existingType = this.schema.collections[collection]?.fields[field.field]?.type;
398
			if (existingType) field.type = existingType;
399
		}
400

401
		try {
402
			const hookAdjustedField =
403
				opts?.emitEvents !== false
404
					? await emitter.emitFilter(
405
							`fields.update`,
406
							field,
407
							{
408
								keys: [field.field],
409
								collection: collection,
410
							},
411
							{
412
								database: this.knex,
413
								schema: this.schema,
414
								accountability: this.accountability,
415
							},
416
					  )
417
					: field;
418

419
			const record = field.meta
420
				? await this.knex.select('id').from('directus_fields').where({ collection, field: field.field }).first()
421
				: null;
422

423
			if (
424
				hookAdjustedField.type &&
425
				(hookAdjustedField.type === 'alias' ||
426
					this.schema.collections[collection]!.fields[field.field]?.type === 'alias') &&
427
				hookAdjustedField.type !== (this.schema.collections[collection]!.fields[field.field]?.type ?? 'alias')
428
			) {
429
				throw new InvalidPayloadError({ reason: 'Alias type cannot be changed' });
430
			}
431

432
			if (hookAdjustedField.schema) {
433
				const existingColumn = await this.schemaInspector.columnInfo(collection, hookAdjustedField.field);
434

435
				if (hookAdjustedField.schema?.is_nullable === true && existingColumn.is_primary_key) {
436
					throw new InvalidPayloadError({ reason: 'Primary key cannot be null' });
437
				}
438

439
				// Sanitize column only when applying snapshot diff as opts is only passed from /utils/apply-diff.ts
440
				const columnToCompare =
441
					opts?.bypassLimits && opts.autoPurgeSystemCache === false ? sanitizeColumn(existingColumn) : existingColumn;
442

443
				if (!isEqual(columnToCompare, hookAdjustedField.schema)) {
444
					try {
445
						await transaction(this.knex, async (trx) => {
446
							await trx.schema.alterTable(collection, async (table) => {
447
								if (!hookAdjustedField.schema) return;
448
								this.addColumnToTable(table, field, existingColumn);
449
							});
450
						});
451
					} catch (err: any) {
452
						throw await translateDatabaseError(err);
453
					}
454
				}
455
			}
456

457
			if (hookAdjustedField.meta) {
458
				if (record) {
459
					await this.itemsService.updateOne(
460
						record.id,
461
						{
462
							...hookAdjustedField.meta,
463
							collection: collection,
464
							field: hookAdjustedField.field,
465
						},
466
						{ emitEvents: false },
467
					);
468
				} else {
469
					await this.itemsService.createOne(
470
						{
471
							...hookAdjustedField.meta,
472
							collection: collection,
473
							field: hookAdjustedField.field,
474
						},
475
						{ emitEvents: false },
476
					);
477
				}
478
			}
479

480
			const actionEvent = {
481
				event: 'fields.update',
482
				meta: {
483
					payload: hookAdjustedField,
484
					keys: [hookAdjustedField.field],
485
					collection: collection,
486
				},
487
				context: {
488
					database: getDatabase(),
489
					schema: this.schema,
490
					accountability: this.accountability,
491
				},
492
			};
493

494
			if (opts?.bypassEmitAction) {
495
				opts.bypassEmitAction(actionEvent);
496
			} else {
497
				nestedActionEvents.push(actionEvent);
498
			}
499

500
			return field.field;
501
		} finally {
502
			if (runPostColumnChange) {
503
				await this.helpers.schema.postColumnChange();
504
			}
505

506
			if (shouldClearCache(this.cache, opts)) {
507
				await this.cache.clear();
508
			}
509

510
			if (opts?.autoPurgeSystemCache !== false) {
511
				await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache });
512
			}
513

514
			if (opts?.emitEvents !== false && nestedActionEvents.length > 0) {
515
				const updatedSchema = await getSchema();
516

517
				for (const nestedActionEvent of nestedActionEvents) {
518
					nestedActionEvent.context.schema = updatedSchema;
519
					emitter.emitAction(nestedActionEvent.event, nestedActionEvent.meta, nestedActionEvent.context);
520
				}
521
			}
522
		}
523
	}
524

525
	async updateFields(collection: string, fields: RawField[], opts?: MutationOptions): Promise<string[]> {
526
		const nestedActionEvents: ActionEventParams[] = [];
527

528
		try {
529
			const fieldNames = [];
530

531
			for (const field of fields) {
532
				fieldNames.push(
533
					await this.updateField(collection, field, {
534
						autoPurgeCache: false,
535
						autoPurgeSystemCache: false,
536
						bypassEmitAction: (params) => nestedActionEvents.push(params),
537
					}),
538
				);
539
			}
540

541
			return fieldNames;
542
		} finally {
543
			if (shouldClearCache(this.cache, opts)) {
544
				await this.cache.clear();
545
			}
546

547
			if (opts?.autoPurgeSystemCache !== false) {
548
				await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache });
549
			}
550

551
			if (opts?.emitEvents !== false && nestedActionEvents.length > 0) {
552
				const updatedSchema = await getSchema();
553

554
				for (const nestedActionEvent of nestedActionEvents) {
555
					nestedActionEvent.context.schema = updatedSchema;
556
					emitter.emitAction(nestedActionEvent.event, nestedActionEvent.meta, nestedActionEvent.context);
557
				}
558
			}
559
		}
560
	}
561

562
	async deleteField(collection: string, field: string, opts?: MutationOptions): Promise<void> {
563
		if (this.accountability && this.accountability.admin !== true) {
564
			throw new ForbiddenError();
565
		}
566

567
		const runPostColumnChange = await this.helpers.schema.preColumnChange();
568
		const nestedActionEvents: ActionEventParams[] = [];
569

570
		try {
571
			if (opts?.emitEvents !== false) {
572
				await emitter.emitFilter(
573
					'fields.delete',
574
					[field],
575
					{
576
						collection: collection,
577
					},
578
					{
579
						database: this.knex,
580
						schema: this.schema,
581
						accountability: this.accountability,
582
					},
583
				);
584
			}
585

586
			await transaction(this.knex, async (trx) => {
587
				const relations = this.schema.relations.filter((relation) => {
588
					return (
589
						(relation.collection === collection && relation.field === field) ||
590
						(relation.related_collection === collection && relation.meta?.one_field === field)
591
					);
592
				});
593

594
				const relationsService = new RelationsService({
595
					knex: trx,
596
					accountability: this.accountability,
597
					schema: this.schema,
598
				});
599

600
				const fieldsService = new FieldsService({
601
					knex: trx,
602
					accountability: this.accountability,
603
					schema: this.schema,
604
				});
605

606
				for (const relation of relations) {
607
					const isM2O = relation.collection === collection && relation.field === field;
608

609
					// If the current field is a m2o, delete the related o2m if it exists and remove the relationship
610
					if (isM2O) {
611
						await relationsService.deleteOne(collection, field, {
612
							autoPurgeSystemCache: false,
613
							bypassEmitAction: (params) =>
614
								opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
615
						});
616

617
						if (
618
							relation.related_collection &&
619
							relation.meta?.one_field &&
620
							relation.related_collection !== collection &&
621
							relation.meta.one_field !== field
622
						) {
623
							await fieldsService.deleteField(relation.related_collection, relation.meta.one_field, {
624
								autoPurgeCache: false,
625
								autoPurgeSystemCache: false,
626
								bypassEmitAction: (params) =>
627
									opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
628
							});
629
						}
630
					}
631

632
					// If the current field is a o2m, just delete the one field config from the relation
633
					if (!isM2O && relation.meta?.one_field) {
634
						await trx('directus_relations')
635
							.update({ one_field: null })
636
							.where({ many_collection: relation.collection, many_field: relation.field });
637
					}
638
				}
639

640
				// Delete field only after foreign key constraints are removed
641
				if (
642
					this.schema.collections[collection] &&
643
					field in this.schema.collections[collection]!.fields &&
644
					this.schema.collections[collection]!.fields[field]!.alias === false
645
				) {
646
					await trx.schema.table(collection, (table) => {
647
						table.dropColumn(field);
648
					});
649
				}
650

651
				const collectionMeta = await trx
652
					.select('archive_field', 'sort_field')
653
					.from('directus_collections')
654
					.where({ collection })
655
					.first();
656

657
				const collectionMetaUpdates: Record<string, null> = {};
658

659
				if (collectionMeta?.archive_field === field) {
660
					collectionMetaUpdates['archive_field'] = null;
661
				}
662

663
				if (collectionMeta?.sort_field === field) {
664
					collectionMetaUpdates['sort_field'] = null;
665
				}
666

667
				if (Object.keys(collectionMetaUpdates).length > 0) {
668
					await trx('directus_collections').update(collectionMetaUpdates).where({ collection });
669
				}
670

671
				// Cleanup directus_fields
672
				const metaRow = await trx
673
					.select('collection', 'field')
674
					.from('directus_fields')
675
					.where({ collection, field })
676
					.first();
677

678
				if (metaRow) {
679
					// Handle recursive FK constraints
680
					await trx('directus_fields')
681
						.update({ group: null })
682
						.where({ group: metaRow.field, collection: metaRow.collection });
683
				}
684

685
				await trx('directus_fields').delete().where({ collection, field });
686
			});
687

688
			const actionEvent = {
689
				event: 'fields.delete',
690
				meta: {
691
					payload: [field],
692
					collection: collection,
693
				},
694
				context: {
695
					database: this.knex,
696
					schema: this.schema,
697
					accountability: this.accountability,
698
				},
699
			};
700

701
			if (opts?.bypassEmitAction) {
702
				opts.bypassEmitAction(actionEvent);
703
			} else {
704
				nestedActionEvents.push(actionEvent);
705
			}
706
		} finally {
707
			if (runPostColumnChange) {
708
				await this.helpers.schema.postColumnChange();
709
			}
710

711
			if (shouldClearCache(this.cache, opts)) {
712
				await this.cache.clear();
713
			}
714

715
			if (opts?.autoPurgeSystemCache !== false) {
716
				await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache });
717
			}
718

719
			if (opts?.emitEvents !== false && nestedActionEvents.length > 0) {
720
				const updatedSchema = await getSchema();
721

722
				for (const nestedActionEvent of nestedActionEvents) {
723
					nestedActionEvent.context.schema = updatedSchema;
724
					emitter.emitAction(nestedActionEvent.event, nestedActionEvent.meta, nestedActionEvent.context);
725
				}
726
			}
727
		}
728
	}
729

730
	public addColumnToTable(table: Knex.CreateTableBuilder, field: RawField | Field, alter: Column | null = null): void {
731
		let column: Knex.ColumnBuilder;
732

733
		// Don't attempt to add a DB column for alias / corrupt fields
734
		if (field.type === 'alias' || field.type === 'unknown') return;
735

736
		if (field.schema?.has_auto_increment) {
737
			if (field.type === 'bigInteger') {
738
				column = table.bigIncrements(field.field);
739
			} else {
740
				column = table.increments(field.field);
741
			}
742
		} else if (field.type === 'string') {
743
			column = table.string(field.field, field.schema?.max_length ?? undefined);
744
		} else if (['float', 'decimal'].includes(field.type)) {
745
			const type = field.type as 'float' | 'decimal';
746

747
			column = table[type](
748
				field.field,
749
				field.schema?.numeric_precision ?? DEFAULT_NUMERIC_PRECISION,
750
				field.schema?.numeric_scale ?? DEFAULT_NUMERIC_SCALE,
751
			);
752
		} else if (field.type === 'csv') {
753
			column = table.text(field.field);
754
		} else if (field.type === 'hash') {
755
			column = table.string(field.field, 255);
756
		} else if (field.type === 'dateTime') {
757
			column = table.dateTime(field.field, { useTz: false });
758
		} else if (field.type === 'timestamp') {
759
			column = table.timestamp(field.field, { useTz: true });
760
		} else if (field.type.startsWith('geometry')) {
761
			column = this.helpers.st.createColumn(table, field);
762
		} else if (KNEX_TYPES.includes(field.type as (typeof KNEX_TYPES)[number])) {
763
			column = table[field.type as (typeof KNEX_TYPES)[number]](field.field);
764
		} else {
765
			throw new InvalidPayloadError({ reason: `Illegal type passed: "${field.type}"` });
766
		}
767

768
		if (field.schema?.default_value !== undefined) {
769
			if (
770
				typeof field.schema.default_value === 'string' &&
771
				(field.schema.default_value.toLowerCase() === 'now()' || field.schema.default_value === 'CURRENT_TIMESTAMP')
772
			) {
773
				column.defaultTo(this.knex.fn.now());
774
			} else if (
775
				typeof field.schema.default_value === 'string' &&
776
				field.schema.default_value.includes('CURRENT_TIMESTAMP(') &&
777
				field.schema.default_value.includes(')')
778
			) {
779
				const precision = field.schema.default_value.match(REGEX_BETWEEN_PARENS)![1];
780
				column.defaultTo(this.knex.fn.now(Number(precision)));
781
			} else {
782
				column.defaultTo(field.schema.default_value);
783
			}
784
		}
785

786
		if (field.schema?.is_nullable === false) {
787
			if (!alter || alter.is_nullable === true) {
788
				column.notNullable();
789
			}
790
		} else {
791
			if (!alter || alter.is_nullable === false) {
792
				column.nullable();
793
			}
794
		}
795

796
		if (field.schema?.is_primary_key) {
797
			column.primary().notNullable();
798
		} else if (field.schema?.is_unique === true) {
799
			if (!alter || alter.is_unique === false) {
800
				column.unique();
801
			}
802
		} else if (field.schema?.is_unique === false) {
803
			if (alter && alter.is_unique === true) {
804
				table.dropUnique([field.field]);
805
			}
806
		}
807

808
		if (alter) {
809
			column.alter();
810
		}
811
	}
812
}
813

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

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

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

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