diff --git a/.changeset/dirty-rings-greet.md b/.changeset/dirty-rings-greet.md new file mode 100644 index 0000000000..53e61ab2b3 --- /dev/null +++ b/.changeset/dirty-rings-greet.md @@ -0,0 +1,5 @@ +--- +'@directus/api': minor +--- + +Enabled caching of field information as part of schema caching diff --git a/api/src/services/fields.ts b/api/src/services/fields.ts index b4fac53d08..bbbc5048c8 100644 --- a/api/src/services/fields.ts +++ b/api/src/services/fields.ts @@ -12,7 +12,7 @@ import { addFieldFlag, toArray } from '@directus/utils'; import type Keyv from 'keyv'; import type { Knex } from 'knex'; import { isEqual, isNil, merge } from 'lodash-es'; -import { clearSystemCache, getCache } from '../cache.js'; +import { clearSystemCache, getCache, getCacheValue, setCacheValue } from '../cache.js'; import { ALIAS_TYPES } from '../constants.js'; import { translateDatabaseError } from '../database/errors/translate.js'; import type { Helpers } from '../database/helpers/index.js'; @@ -30,8 +30,10 @@ import { transaction } from '../utils/transaction.js'; import { ItemsService } from './items.js'; import { PayloadService } from './payload.js'; import { RelationsService } from './relations.js'; +import { useEnv } from '@directus/env'; const systemFieldRows = getSystemFieldRowsWithAuthProviders(); +const env = useEnv(); export class FieldsService { knex: Knex; @@ -43,6 +45,7 @@ export class FieldsService { schema: SchemaOverview; cache: Keyv | null; systemCache: Keyv; + schemaCache: Keyv; constructor(options: AbstractServiceOptions) { this.knex = options.knex || getDatabase(); @@ -53,10 +56,11 @@ export class FieldsService { this.payloadService = new PayloadService('directus_fields', options); this.schema = options.schema; - const { cache, systemCache } = getCache(); + const { cache, systemCache, localSchemaCache } = getCache(); this.cache = cache; this.systemCache = systemCache; + this.schemaCache = localSchemaCache; } private get hasReadAccess() { @@ -65,6 +69,36 @@ export class FieldsService { }); } + async columnInfo(collection?: string): Promise; + async columnInfo(collection: string, field: string): Promise; + async columnInfo(collection?: string, field?: string): Promise { + const schemaCacheIsEnabled = Boolean(env['CACHE_SCHEMA']); + + let columnInfo: Column[] | null = null; + + if (schemaCacheIsEnabled) { + columnInfo = await getCacheValue(this.schemaCache, 'columnInfo'); + } + + if (!columnInfo) { + columnInfo = await this.schemaInspector.columnInfo(); + + if (schemaCacheIsEnabled) { + setCacheValue(this.schemaCache, 'columnInfo', columnInfo); + } + } + + if (collection) { + columnInfo = columnInfo.filter((column) => column.table === collection); + } + + if (field) { + return columnInfo.find((column) => column.name === field) as Column; + } + + return columnInfo; + } + async readAll(collection?: string): Promise { let fields: FieldMeta[]; @@ -89,7 +123,7 @@ export class FieldsService { fields.push(...systemFieldRows); } - const columns = (await this.schemaInspector.columnInfo(collection)).map((column) => ({ + const columns = (await this.columnInfo(collection)).map((column) => ({ ...column, default_value: getDefaultValue( column, @@ -227,7 +261,7 @@ export class FieldsService { systemFieldRows.find((fieldMeta) => fieldMeta.collection === collection && fieldMeta.field === field); try { - column = await this.schemaInspector.columnInfo(collection, field); + column = await this.columnInfo(collection, field); } catch { // Do nothing } @@ -430,7 +464,7 @@ export class FieldsService { } if (hookAdjustedField.schema) { - const existingColumn = await this.schemaInspector.columnInfo(collection, hookAdjustedField.field); + const existingColumn = await this.columnInfo(collection, hookAdjustedField.field); if (hookAdjustedField.schema?.is_nullable === true && existingColumn.is_primary_key) { throw new InvalidPayloadError({ reason: 'Primary key cannot be null' });