@backstage/plugin-catalog-backend 3.6.0 → 3.6.1-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @backstage/plugin-catalog-backend
2
2
 
3
+ ## 3.6.1-next.0
4
+
5
+ ### Patch Changes
6
+
7
+ - b33f845: Fixed several database migration `down` functions that were not properly reversible, causing the SQL report to show warnings:
8
+
9
+ - `20241003170511_alter_target_in_locations.js`: both `up` and `down` now include `.notNullable()` when altering the `locations.target` column, preventing the `NOT NULL` constraint from being accidentally dropped when widening the column type from `varchar(255)` to `text`.
10
+ - `20220116144621_remove_legacy.js`: the `down` function now properly recreates the three dropped legacy tables (`entities`, `entities_search`, `entities_relations`) with correct columns and indices.
11
+ - `20210302150147_refresh_state.js`: the `down` function now drops dependent tables in the correct order (avoiding a FK constraint violation) and fixes a typo where the table was referred to as `references` instead of `refresh_state_references`.
12
+ - `20201005122705_add_entity_full_name.js`: the `down` function now drops the `full_name` column from `entities` (not `entities_search`), and restores the `entities_unique_name` index with the correct column order `(kind, name, namespace)`.
13
+ - `20200702153613_entities.js`: the `down` function now uses `table.integer('generation')` instead of `table.string('generation')`, restoring the correct column type.
14
+
15
+ - cf195de: Fixed a performance regression in the `/entity-facets` endpoint when filters or permission conditions are applied, by routing the EXISTS-based filter through `final_entities` instead of correlating against the much larger `search` table.
16
+ - 744fa1f: Removed duplicated entries that appeared in both `dependencies` and `devDependencies`.
17
+ - Updated dependencies
18
+ - @backstage/errors@1.3.1-next.0
19
+ - @backstage/integration@2.0.2-next.0
20
+ - @backstage/backend-openapi-utils@0.6.9-next.0
21
+ - @backstage/backend-plugin-api@1.9.1-next.0
22
+ - @backstage/catalog-client@1.15.1-next.0
23
+ - @backstage/catalog-model@1.8.1-next.0
24
+ - @backstage/config@1.3.8-next.0
25
+ - @backstage/filter-predicates@0.1.3-next.0
26
+ - @backstage/plugin-catalog-node@2.2.1-next.0
27
+ - @backstage/plugin-events-node@0.4.22-next.0
28
+ - @backstage/plugin-permission-common@0.9.9-next.0
29
+ - @backstage/plugin-permission-node@0.10.13-next.0
30
+ - @backstage/types@1.2.2
31
+ - @backstage/plugin-catalog-common@1.1.10-next.0
32
+
3
33
  ## 3.6.0
4
34
 
5
35
  ### Minor Changes
@@ -441,13 +441,15 @@ class DefaultEntitiesCatalog {
441
441
  count: this.database.raw("count(DISTINCT search.entity_id)")
442
442
  }).groupBy(["search.key", "search.original_value"]);
443
443
  if (request.filter || request.query) {
444
+ const entityIdSubquery = this.database("final_entities").select("final_entities.entity_id").whereNotNull("final_entities.final_entity");
444
445
  applyEntityFilterToQuery.applyEntityFilterToQuery({
445
446
  filter: request.filter,
446
447
  query: request.query,
447
- targetQuery: query,
448
- onEntityIdField: "search.entity_id",
448
+ targetQuery: entityIdSubquery,
449
+ onEntityIdField: "final_entities.entity_id",
449
450
  knex: this.database
450
451
  });
452
+ query.whereIn("search.entity_id", entityIdSubquery);
451
453
  }
452
454
  const rows = await query;
453
455
  const facets = {};
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultEntitiesCatalog.cjs.js","sources":["../../src/service/DefaultEntitiesCatalog.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity, stringifyEntityRef } from '@backstage/catalog-model';\nimport { InputError, NotFoundError } from '@backstage/errors';\nimport { Knex } from 'knex';\nimport { chunk as lodashChunk, isEqual } from 'lodash';\nimport {\n Cursor,\n EntitiesBatchRequest,\n EntitiesBatchResponse,\n EntitiesCatalog,\n EntitiesRequest,\n EntitiesResponse,\n EntityAncestryResponse,\n EntityFacetsRequest,\n EntityFacetsResponse,\n EntityOrder,\n EntityPagination,\n QueryEntitiesRequest,\n QueryEntitiesResponse,\n} from '../catalog/types';\nimport {\n DbFinalEntitiesRow,\n DbPageInfo,\n DbRefreshStateReferencesRow,\n DbRefreshStateRow,\n DbRelationsRow,\n DbSearchRow,\n} from '../database/tables';\nimport { Stitcher } from '../stitching/types';\n\nimport {\n expandLegacyCompoundRelationsInEntity,\n isQueryEntitiesCursorRequest,\n isQueryEntitiesInitialRequest,\n} from './util';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { applyEntityFilterToQuery } from './request/applyEntityFilterToQuery';\nimport { processRawEntitiesResult } from './response';\n\nconst DEFAULT_LIMIT = 200;\n\nfunction parsePagination(input?: EntityPagination): EntityPagination {\n if (!input) {\n return {};\n }\n\n let { limit, offset } = input;\n\n if (input.after === undefined) {\n return { limit, offset };\n }\n\n let cursor;\n try {\n const json = Buffer.from(input.after, 'base64').toString('utf8');\n cursor = JSON.parse(json);\n } catch {\n throw new InputError('Malformed after cursor, could not be parsed');\n }\n\n if (cursor.limit !== undefined) {\n if (!Number.isInteger(cursor.limit)) {\n throw new InputError('Malformed after cursor, limit was not an number');\n }\n limit = cursor.limit;\n }\n\n if (cursor.offset !== undefined) {\n if (!Number.isInteger(cursor.offset)) {\n throw new InputError('Malformed after cursor, offset was not a number');\n }\n offset = cursor.offset;\n }\n\n return { limit, offset };\n}\n\nfunction stringifyPagination(\n input: Required<Omit<EntityPagination, 'after'>>,\n): string {\n const { limit, offset } = input;\n const json = JSON.stringify({ limit, offset });\n const base64 = Buffer.from(json, 'utf8').toString('base64');\n return base64;\n}\n\nexport class DefaultEntitiesCatalog implements EntitiesCatalog {\n private readonly database: Knex;\n private readonly logger: LoggerService;\n private readonly stitcher: Stitcher;\n private readonly enableRelationsCompatibility: boolean;\n\n constructor(options: {\n database: Knex;\n logger: LoggerService;\n stitcher: Stitcher;\n enableRelationsCompatibility?: boolean;\n }) {\n this.database = options.database;\n this.logger = options.logger;\n this.stitcher = options.stitcher;\n this.enableRelationsCompatibility = Boolean(\n options.enableRelationsCompatibility,\n );\n }\n\n async entities(request?: EntitiesRequest): Promise<EntitiesResponse> {\n const db = this.database;\n const { limit, offset } = parsePagination(request?.pagination);\n\n let entitiesQuery =\n db<DbFinalEntitiesRow>('final_entities').select('final_entities.*');\n\n request?.order?.forEach(({ field }, index) => {\n const alias = `order_${index}`;\n entitiesQuery = entitiesQuery.leftOuterJoin(\n { [alias]: 'search' },\n function search(inner) {\n inner\n .on(`${alias}.entity_id`, 'final_entities.entity_id')\n .andOn(`${alias}.key`, db.raw('?', [field]));\n },\n );\n });\n\n entitiesQuery = entitiesQuery.whereNotNull('final_entities.final_entity');\n\n if (request?.filter) {\n entitiesQuery = applyEntityFilterToQuery({\n filter: request.filter,\n targetQuery: entitiesQuery,\n onEntityIdField: 'final_entities.entity_id',\n knex: db,\n });\n }\n\n request?.order?.forEach(({ order }, index) => {\n if (db.client.config.client === 'pg') {\n // pg correctly orders by the column value and handling nulls in one go\n entitiesQuery = entitiesQuery.orderBy([\n { column: `order_${index}.value`, order, nulls: 'last' },\n ]);\n } else {\n // sqlite and mysql translate the above statement ONLY into \"order by (value is null) asc\"\n // no matter what the order is, for some reason, so we have to manually add back the statement\n // that translates to \"order by value <order>\" while avoiding to give an order\n entitiesQuery = entitiesQuery.orderBy([\n { column: `order_${index}.value`, order: undefined, nulls: 'last' },\n { column: `order_${index}.value`, order },\n ]);\n }\n });\n\n if (!request?.order) {\n entitiesQuery = entitiesQuery.orderBy('final_entities.entity_ref', 'asc'); // default sort\n } else {\n entitiesQuery.orderBy('final_entities.entity_id', 'asc'); // stable sort\n }\n\n if (limit !== undefined) {\n entitiesQuery = entitiesQuery.limit(limit + 1);\n }\n if (offset !== undefined) {\n entitiesQuery = entitiesQuery.offset(offset);\n }\n\n let rows = await entitiesQuery;\n let pageInfo: DbPageInfo;\n if (limit === undefined || rows.length <= limit) {\n pageInfo = { hasNextPage: false };\n } else {\n rows = rows.slice(0, -1);\n pageInfo = {\n hasNextPage: true,\n endCursor: stringifyPagination({\n limit,\n offset: (offset ?? 0) + limit,\n }),\n };\n }\n\n return {\n entities: processRawEntitiesResult(\n rows.map(r => r.final_entity!),\n this.enableRelationsCompatibility\n ? e => {\n expandLegacyCompoundRelationsInEntity(e);\n if (request?.fields) {\n return request.fields(e);\n }\n return e;\n }\n : request?.fields,\n ),\n pageInfo,\n };\n }\n\n async entitiesBatch(\n request: EntitiesBatchRequest,\n ): Promise<EntitiesBatchResponse> {\n const lookup = new Map<string, string>();\n\n for (const chunk of lodashChunk(request.entityRefs, 200)) {\n let query = this.database<DbFinalEntitiesRow>('final_entities')\n .select({\n entityRef: 'final_entities.entity_ref',\n entity: 'final_entities.final_entity',\n })\n .whereIn('final_entities.entity_ref', chunk);\n\n if (request?.filter || request?.query) {\n query = applyEntityFilterToQuery({\n filter: request.filter,\n query: request.query,\n targetQuery: query,\n onEntityIdField: 'final_entities.entity_id',\n knex: this.database,\n });\n }\n\n for (const row of await query) {\n lookup.set(row.entityRef, row.entity ? row.entity : null);\n }\n }\n\n const items = request.entityRefs.map(ref => lookup.get(ref) ?? null);\n\n return { items: processRawEntitiesResult(items, request.fields) };\n }\n\n async queryEntities(\n request: QueryEntitiesRequest,\n ): Promise<QueryEntitiesResponse> {\n const limit = request.limit ?? DEFAULT_LIMIT;\n\n const cursor: Omit<Cursor, 'orderFieldValues'> & {\n orderFieldValues?: (string | null)[];\n skipTotalItems: boolean;\n } = {\n orderFields: [],\n isPrevious: false,\n ...parseCursorFromRequest(request),\n };\n\n // For performance reasons we invoke the count query only on the first\n // request. The result is then embedded into the cursor for subsequent\n // requests. Threfore this can be undefined here, but will then get\n // populated further down.\n const shouldComputeTotalItems =\n cursor.totalItems === undefined && !cursor.skipTotalItems;\n const isFetchingBackwards = cursor.isPrevious;\n\n if (cursor.orderFields.length > 1) {\n this.logger.warn(`Only one sort field is supported, ignoring the rest`);\n }\n\n const sortField = cursor.orderFields.at(0);\n\n // The first part of the query builder is a subquery that applies all of the\n // filtering.\n const dbQuery = this.database.with(\n 'filtered',\n ['entity_id', 'final_entity', ...(sortField ? ['value'] : [])],\n inner => {\n inner\n .from<DbFinalEntitiesRow>('final_entities')\n .whereNotNull('final_entity');\n\n if (sortField) {\n inner\n .distinct()\n .leftOuterJoin('search', qb =>\n qb\n .on('search.entity_id', 'final_entities.entity_id')\n .andOnVal('search.key', sortField.field),\n )\n .select({\n entity_id: 'final_entities.entity_id',\n final_entity: 'final_entities.final_entity',\n value: 'search.value',\n });\n } else {\n inner.select({\n entity_id: 'final_entities.entity_id',\n final_entity: 'final_entities.final_entity',\n });\n }\n\n // Add regular filters and/or predicate query, if given\n if (cursor.filter || cursor.query) {\n applyEntityFilterToQuery({\n filter: cursor.filter,\n query: cursor.query,\n targetQuery: inner,\n onEntityIdField: 'final_entities.entity_id',\n knex: this.database,\n });\n }\n\n // Add full text search filters, if given\n const normalizedFullTextFilterTerm =\n cursor.fullTextFilter?.term?.trim();\n const textFilterFields = cursor.fullTextFilter?.fields ?? [\n sortField?.field || 'metadata.uid',\n ];\n if (normalizedFullTextFilterTerm) {\n if (\n textFilterFields.length === 1 &&\n textFilterFields[0] === sortField?.field\n ) {\n // If there is one item, apply the like query to the top level query which is already\n // filtered based on the singular sortField.\n inner.andWhereRaw(\n 'search.value like ?',\n `%${normalizedFullTextFilterTerm.toLocaleLowerCase('en-US')}%`,\n );\n } else {\n const matchQuery = this.database<DbSearchRow>('search')\n .select('search.entity_id')\n // textFilterFields must be lowercased to match searchable keys in database, i.e. spec.profile.displayName -> spec.profile.displayname\n .whereIn(\n 'search.key',\n textFilterFields.map(field => field.toLocaleLowerCase('en-US')),\n )\n .andWhere(function keyFilter() {\n this.andWhereRaw(\n 'search.value like ?',\n `%${normalizedFullTextFilterTerm.toLocaleLowerCase(\n 'en-US',\n )}%`,\n );\n });\n inner.andWhere('final_entities.entity_id', 'in', matchQuery);\n }\n }\n },\n );\n\n // Only pay the cost of counting the number of items if needed\n if (shouldComputeTotalItems) {\n // Note the intentional cross join here. The filtered_count dataset is\n // always exactly one row, so it won't grow the result unnecessarily. But\n // it's also important that there IS at least one row, because even if the\n // filtered dataset is empty, we still want to know the total number of\n // items.\n dbQuery\n .with('filtered_count', ['count'], inner =>\n inner.from('filtered').count('*', { as: 'count' }),\n )\n .fromRaw('filtered_count, filtered')\n .select('count', 'filtered.*');\n } else {\n dbQuery.from('filtered').select('*');\n }\n\n const isOrderingDescending = sortField?.order === 'desc';\n\n // Move forward (or backward) in the set to the correct cursor position\n if (cursor.orderFieldValues) {\n if (cursor.orderFieldValues.length === 2) {\n // The first will be the sortField value, the second the entity_id\n const [first, second] = cursor.orderFieldValues;\n dbQuery.andWhere(function nested() {\n this.where(\n 'filtered.value',\n isFetchingBackwards !== isOrderingDescending ? '<' : '>',\n first,\n )\n .orWhere('filtered.value', '=', first)\n .andWhere(\n 'filtered.entity_id',\n isFetchingBackwards !== isOrderingDescending ? '<' : '>',\n second,\n );\n });\n } else if (cursor.orderFieldValues.length === 1) {\n // This will be the entity_id\n const [first] = cursor.orderFieldValues;\n dbQuery.andWhere('entity_id', isFetchingBackwards ? '<' : '>', first);\n }\n }\n\n // Add the ordering\n let order = sortField?.order ?? 'asc';\n if (isFetchingBackwards) {\n order = invertOrder(order);\n }\n if (this.database.client.config.client === 'pg') {\n // pg correctly orders by the column value and handling nulls in one go\n dbQuery.orderBy([\n ...(sortField\n ? [\n {\n column: 'filtered.value',\n order,\n nulls: 'last',\n },\n ]\n : []),\n {\n column: 'filtered.entity_id',\n order,\n },\n ]);\n } else {\n // sqlite and mysql translate the above statement ONLY into \"order by (value is null) asc\"\n // no matter what the order is, for some reason, so we have to manually add back the statement\n // that translates to \"order by value <order>\" while avoiding to give an order\n dbQuery.orderBy([\n ...(sortField\n ? [\n {\n column: 'filtered.value',\n order: undefined,\n nulls: 'last',\n },\n {\n column: 'filtered.value',\n order,\n },\n ]\n : []),\n {\n column: 'filtered.entity_id',\n order,\n },\n ]);\n }\n\n // Apply a manually set initial offset\n if (\n isQueryEntitiesInitialRequest(request) &&\n request.offset !== undefined\n ) {\n dbQuery.offset(request.offset);\n }\n // fetch an extra item to check if there are more items.\n dbQuery.limit(isFetchingBackwards ? limit : limit + 1);\n\n const rows = shouldComputeTotalItems || limit > 0 ? await dbQuery : [];\n\n let totalItems: number;\n if (cursor.totalItems !== undefined) {\n totalItems = cursor.totalItems;\n } else if (cursor.skipTotalItems) {\n totalItems = 0;\n } else if (rows.length) {\n totalItems = Number(rows[0].count);\n } else {\n totalItems = 0;\n }\n\n if (isFetchingBackwards) {\n rows.reverse();\n }\n const hasMoreResults =\n limit > 0 && (isFetchingBackwards || rows.length > limit);\n\n // discard the extra item only when fetching forward.\n if (rows.length > limit) {\n rows.length -= 1;\n }\n\n const isInitialRequest = cursor.firstSortFieldValues === undefined;\n\n const firstRow = rows[0];\n const lastRow = rows[rows.length - 1];\n\n const firstSortFieldValues =\n cursor.firstSortFieldValues || sortFieldsFromRow(firstRow, sortField);\n\n const nextCursor: Cursor | undefined = hasMoreResults\n ? {\n ...cursor,\n orderFieldValues: sortFieldsFromRow(lastRow, sortField),\n firstSortFieldValues,\n isPrevious: false,\n totalItems,\n }\n : undefined;\n\n const prevCursor: Cursor | undefined =\n !isInitialRequest &&\n rows.length > 0 &&\n !isEqual(\n sortFieldsFromRow(firstRow, sortField),\n cursor.firstSortFieldValues,\n )\n ? {\n ...cursor,\n orderFieldValues: sortFieldsFromRow(firstRow, sortField),\n firstSortFieldValues: cursor.firstSortFieldValues,\n isPrevious: true,\n totalItems,\n }\n : undefined;\n\n return {\n items: processRawEntitiesResult(\n rows.map(r => r.final_entity!),\n request.fields,\n ),\n pageInfo: {\n ...(!!prevCursor && { prevCursor }),\n ...(!!nextCursor && { nextCursor }),\n },\n totalItems,\n };\n }\n\n async removeEntityByUid(uid: string): Promise<void> {\n const relationPeerRefs = await this.database.transaction(async tx => {\n const dbConfig = tx.client.config;\n\n // Clear the hashed state of the immediate parents of the deleted entity.\n // This makes sure that when they get reprocessed, their output is written\n // down again. The reason for wanting to do this, is that if the user\n // deletes entities that ARE still emitted by the parent, the parent\n // processing will still generate the same output hash as always, which\n // means it'll never try to write down the children again (it assumes that\n // they already exist). This means that without the code below, the database\n // never \"heals\" from accidental deletes.\n if (dbConfig.client.includes('mysql')) {\n // MySQL doesn't support the syntax we need to do this in a single query,\n // http://dev.mysql.com/doc/refman/5.6/en/update.html\n const results = await tx<DbRefreshStateRow>('refresh_state')\n .select('entity_id')\n .whereIn('entity_ref', function parents(builder) {\n return builder\n .from<DbRefreshStateRow>('refresh_state')\n .innerJoin<DbRefreshStateReferencesRow>(\n 'refresh_state_references',\n {\n 'refresh_state_references.target_entity_ref':\n 'refresh_state.entity_ref',\n },\n )\n .where('refresh_state.entity_id', '=', uid)\n .select('refresh_state_references.source_entity_ref');\n });\n await tx<DbRefreshStateRow>('refresh_state')\n .update({\n result_hash: 'child-was-deleted',\n next_update_at: tx.fn.now(),\n })\n .whereIn(\n 'entity_id',\n results.map(key => key.entity_id),\n );\n } else {\n await tx<DbRefreshStateRow>('refresh_state')\n .update({\n result_hash: 'child-was-deleted',\n next_update_at: tx.fn.now(),\n })\n .whereIn('entity_ref', function parents(builder) {\n return builder\n .from<DbRefreshStateRow>('refresh_state')\n .innerJoin<DbRefreshStateReferencesRow>(\n 'refresh_state_references',\n {\n 'refresh_state_references.target_entity_ref':\n 'refresh_state.entity_ref',\n },\n )\n .where('refresh_state.entity_id', '=', uid)\n .select('refresh_state_references.source_entity_ref');\n });\n }\n\n const relationPeers = await tx\n .from<DbRelationsRow>('relations')\n .innerJoin<DbRefreshStateRow>('refresh_state', {\n 'refresh_state.entity_ref': 'relations.target_entity_ref',\n })\n .where('relations.originating_entity_id', '=', uid)\n .andWhere('refresh_state.entity_id', '!=', uid)\n .select({ ref: 'relations.target_entity_ref' })\n .union(other =>\n other\n .from<DbRelationsRow>('relations')\n .innerJoin<DbRefreshStateRow>('refresh_state', {\n 'refresh_state.entity_ref': 'relations.source_entity_ref',\n })\n .where('relations.originating_entity_id', '=', uid)\n .andWhere('refresh_state.entity_id', '!=', uid)\n .select({ ref: 'relations.source_entity_ref' }),\n );\n\n await tx<DbRefreshStateRow>('refresh_state')\n .where('entity_id', uid)\n .delete();\n\n return new Set(relationPeers.map(p => p.ref));\n });\n\n if (relationPeerRefs.size > 0) {\n await this.stitcher.stitch({\n entityRefs: relationPeerRefs,\n });\n }\n }\n\n async entityAncestry(rootRef: string): Promise<EntityAncestryResponse> {\n const [rootRow] = await this.database<DbFinalEntitiesRow>('final_entities')\n .where('final_entities.entity_ref', '=', rootRef)\n .select({\n entityJson: 'final_entities.final_entity',\n });\n\n if (!rootRow) {\n throw new NotFoundError(`No such entity ${rootRef}`);\n }\n\n const rootEntity = JSON.parse(rootRow.entityJson) as Entity;\n const seenEntityRefs = new Set<string>();\n const todo = new Array<Entity>();\n const items = new Array<{ entity: Entity; parentEntityRefs: string[] }>();\n\n for (\n let current: Entity | undefined = rootEntity;\n current;\n current = todo.pop()\n ) {\n const currentRef = stringifyEntityRef(current);\n seenEntityRefs.add(currentRef);\n\n const parentRows = await this.database<DbRefreshStateReferencesRow>(\n 'refresh_state_references',\n )\n .innerJoin<DbFinalEntitiesRow>('final_entities', {\n 'refresh_state_references.source_entity_ref':\n 'final_entities.entity_ref',\n })\n .where('refresh_state_references.target_entity_ref', '=', currentRef)\n .select({\n parentEntityRef: 'final_entities.entity_ref',\n parentEntityJson: 'final_entities.final_entity',\n });\n\n const parentRefs: string[] = [];\n for (const { parentEntityRef, parentEntityJson } of parentRows) {\n parentRefs.push(parentEntityRef);\n if (!seenEntityRefs.has(parentEntityRef)) {\n seenEntityRefs.add(parentEntityRef);\n todo.push(JSON.parse(parentEntityJson));\n }\n }\n\n items.push({\n entity: current,\n parentEntityRefs: parentRefs,\n });\n }\n\n return {\n rootEntityRef: stringifyEntityRef(rootEntity),\n items,\n };\n }\n\n async facets(request: EntityFacetsRequest): Promise<EntityFacetsResponse> {\n const query = this.database<DbSearchRow>('search')\n .whereIn(\n 'search.key',\n request.facets.map(f => f.toLocaleLowerCase('en-US')),\n )\n .whereNotNull('search.original_value')\n .select({\n facet: 'search.key',\n value: 'search.original_value',\n count: this.database.raw('count(DISTINCT search.entity_id)'),\n })\n .groupBy(['search.key', 'search.original_value']);\n\n if (request.filter || request.query) {\n applyEntityFilterToQuery({\n filter: request.filter,\n query: request.query,\n targetQuery: query,\n onEntityIdField: 'search.entity_id',\n knex: this.database,\n });\n }\n\n const rows = await query;\n\n const facets: EntityFacetsResponse['facets'] = {};\n for (const facet of request.facets) {\n const facetLowercase = facet.toLocaleLowerCase('en-US');\n facets[facet] = rows\n .filter(row => row.facet === facetLowercase)\n .map(row => ({\n value: String(row.value),\n count: Number(row.count),\n }));\n }\n\n return { facets };\n }\n}\n\nfunction parseCursorFromRequest(\n request?: QueryEntitiesRequest,\n): Partial<Cursor> & { skipTotalItems: boolean } {\n if (isQueryEntitiesInitialRequest(request)) {\n const {\n filter,\n query,\n orderFields: sortFields = [],\n fullTextFilter,\n skipTotalItems = false,\n } = request;\n return {\n filter,\n query,\n orderFields: sortFields,\n fullTextFilter,\n skipTotalItems,\n };\n }\n if (isQueryEntitiesCursorRequest(request)) {\n return {\n ...request.cursor,\n // Doesn't matter here\n skipTotalItems: false,\n };\n }\n return {\n skipTotalItems: false,\n };\n}\n\nfunction invertOrder(order: EntityOrder['order']) {\n return order === 'asc' ? 'desc' : 'asc';\n}\n\nfunction sortFieldsFromRow(\n row: DbSearchRow & DbFinalEntitiesRow,\n sortField?: EntityOrder | undefined,\n) {\n return sortField ? [row?.value, row?.entity_id] : [row?.entity_id];\n}\n"],"names":["InputError","applyEntityFilterToQuery","processRawEntitiesResult","expandLegacyCompoundRelationsInEntity","lodashChunk","isQueryEntitiesInitialRequest","isEqual","NotFoundError","stringifyEntityRef","isQueryEntitiesCursorRequest"],"mappings":"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"}
1
+ {"version":3,"file":"DefaultEntitiesCatalog.cjs.js","sources":["../../src/service/DefaultEntitiesCatalog.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity, stringifyEntityRef } from '@backstage/catalog-model';\nimport { InputError, NotFoundError } from '@backstage/errors';\nimport { Knex } from 'knex';\nimport { chunk as lodashChunk, isEqual } from 'lodash';\nimport {\n Cursor,\n EntitiesBatchRequest,\n EntitiesBatchResponse,\n EntitiesCatalog,\n EntitiesRequest,\n EntitiesResponse,\n EntityAncestryResponse,\n EntityFacetsRequest,\n EntityFacetsResponse,\n EntityOrder,\n EntityPagination,\n QueryEntitiesRequest,\n QueryEntitiesResponse,\n} from '../catalog/types';\nimport {\n DbFinalEntitiesRow,\n DbPageInfo,\n DbRefreshStateReferencesRow,\n DbRefreshStateRow,\n DbRelationsRow,\n DbSearchRow,\n} from '../database/tables';\nimport { Stitcher } from '../stitching/types';\n\nimport {\n expandLegacyCompoundRelationsInEntity,\n isQueryEntitiesCursorRequest,\n isQueryEntitiesInitialRequest,\n} from './util';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { applyEntityFilterToQuery } from './request/applyEntityFilterToQuery';\nimport { processRawEntitiesResult } from './response';\n\nconst DEFAULT_LIMIT = 200;\n\nfunction parsePagination(input?: EntityPagination): EntityPagination {\n if (!input) {\n return {};\n }\n\n let { limit, offset } = input;\n\n if (input.after === undefined) {\n return { limit, offset };\n }\n\n let cursor;\n try {\n const json = Buffer.from(input.after, 'base64').toString('utf8');\n cursor = JSON.parse(json);\n } catch {\n throw new InputError('Malformed after cursor, could not be parsed');\n }\n\n if (cursor.limit !== undefined) {\n if (!Number.isInteger(cursor.limit)) {\n throw new InputError('Malformed after cursor, limit was not an number');\n }\n limit = cursor.limit;\n }\n\n if (cursor.offset !== undefined) {\n if (!Number.isInteger(cursor.offset)) {\n throw new InputError('Malformed after cursor, offset was not a number');\n }\n offset = cursor.offset;\n }\n\n return { limit, offset };\n}\n\nfunction stringifyPagination(\n input: Required<Omit<EntityPagination, 'after'>>,\n): string {\n const { limit, offset } = input;\n const json = JSON.stringify({ limit, offset });\n const base64 = Buffer.from(json, 'utf8').toString('base64');\n return base64;\n}\n\nexport class DefaultEntitiesCatalog implements EntitiesCatalog {\n private readonly database: Knex;\n private readonly logger: LoggerService;\n private readonly stitcher: Stitcher;\n private readonly enableRelationsCompatibility: boolean;\n\n constructor(options: {\n database: Knex;\n logger: LoggerService;\n stitcher: Stitcher;\n enableRelationsCompatibility?: boolean;\n }) {\n this.database = options.database;\n this.logger = options.logger;\n this.stitcher = options.stitcher;\n this.enableRelationsCompatibility = Boolean(\n options.enableRelationsCompatibility,\n );\n }\n\n async entities(request?: EntitiesRequest): Promise<EntitiesResponse> {\n const db = this.database;\n const { limit, offset } = parsePagination(request?.pagination);\n\n let entitiesQuery =\n db<DbFinalEntitiesRow>('final_entities').select('final_entities.*');\n\n request?.order?.forEach(({ field }, index) => {\n const alias = `order_${index}`;\n entitiesQuery = entitiesQuery.leftOuterJoin(\n { [alias]: 'search' },\n function search(inner) {\n inner\n .on(`${alias}.entity_id`, 'final_entities.entity_id')\n .andOn(`${alias}.key`, db.raw('?', [field]));\n },\n );\n });\n\n entitiesQuery = entitiesQuery.whereNotNull('final_entities.final_entity');\n\n if (request?.filter) {\n entitiesQuery = applyEntityFilterToQuery({\n filter: request.filter,\n targetQuery: entitiesQuery,\n onEntityIdField: 'final_entities.entity_id',\n knex: db,\n });\n }\n\n request?.order?.forEach(({ order }, index) => {\n if (db.client.config.client === 'pg') {\n // pg correctly orders by the column value and handling nulls in one go\n entitiesQuery = entitiesQuery.orderBy([\n { column: `order_${index}.value`, order, nulls: 'last' },\n ]);\n } else {\n // sqlite and mysql translate the above statement ONLY into \"order by (value is null) asc\"\n // no matter what the order is, for some reason, so we have to manually add back the statement\n // that translates to \"order by value <order>\" while avoiding to give an order\n entitiesQuery = entitiesQuery.orderBy([\n { column: `order_${index}.value`, order: undefined, nulls: 'last' },\n { column: `order_${index}.value`, order },\n ]);\n }\n });\n\n if (!request?.order) {\n entitiesQuery = entitiesQuery.orderBy('final_entities.entity_ref', 'asc'); // default sort\n } else {\n entitiesQuery.orderBy('final_entities.entity_id', 'asc'); // stable sort\n }\n\n if (limit !== undefined) {\n entitiesQuery = entitiesQuery.limit(limit + 1);\n }\n if (offset !== undefined) {\n entitiesQuery = entitiesQuery.offset(offset);\n }\n\n let rows = await entitiesQuery;\n let pageInfo: DbPageInfo;\n if (limit === undefined || rows.length <= limit) {\n pageInfo = { hasNextPage: false };\n } else {\n rows = rows.slice(0, -1);\n pageInfo = {\n hasNextPage: true,\n endCursor: stringifyPagination({\n limit,\n offset: (offset ?? 0) + limit,\n }),\n };\n }\n\n return {\n entities: processRawEntitiesResult(\n rows.map(r => r.final_entity!),\n this.enableRelationsCompatibility\n ? e => {\n expandLegacyCompoundRelationsInEntity(e);\n if (request?.fields) {\n return request.fields(e);\n }\n return e;\n }\n : request?.fields,\n ),\n pageInfo,\n };\n }\n\n async entitiesBatch(\n request: EntitiesBatchRequest,\n ): Promise<EntitiesBatchResponse> {\n const lookup = new Map<string, string>();\n\n for (const chunk of lodashChunk(request.entityRefs, 200)) {\n let query = this.database<DbFinalEntitiesRow>('final_entities')\n .select({\n entityRef: 'final_entities.entity_ref',\n entity: 'final_entities.final_entity',\n })\n .whereIn('final_entities.entity_ref', chunk);\n\n if (request?.filter || request?.query) {\n query = applyEntityFilterToQuery({\n filter: request.filter,\n query: request.query,\n targetQuery: query,\n onEntityIdField: 'final_entities.entity_id',\n knex: this.database,\n });\n }\n\n for (const row of await query) {\n lookup.set(row.entityRef, row.entity ? row.entity : null);\n }\n }\n\n const items = request.entityRefs.map(ref => lookup.get(ref) ?? null);\n\n return { items: processRawEntitiesResult(items, request.fields) };\n }\n\n async queryEntities(\n request: QueryEntitiesRequest,\n ): Promise<QueryEntitiesResponse> {\n const limit = request.limit ?? DEFAULT_LIMIT;\n\n const cursor: Omit<Cursor, 'orderFieldValues'> & {\n orderFieldValues?: (string | null)[];\n skipTotalItems: boolean;\n } = {\n orderFields: [],\n isPrevious: false,\n ...parseCursorFromRequest(request),\n };\n\n // For performance reasons we invoke the count query only on the first\n // request. The result is then embedded into the cursor for subsequent\n // requests. Threfore this can be undefined here, but will then get\n // populated further down.\n const shouldComputeTotalItems =\n cursor.totalItems === undefined && !cursor.skipTotalItems;\n const isFetchingBackwards = cursor.isPrevious;\n\n if (cursor.orderFields.length > 1) {\n this.logger.warn(`Only one sort field is supported, ignoring the rest`);\n }\n\n const sortField = cursor.orderFields.at(0);\n\n // The first part of the query builder is a subquery that applies all of the\n // filtering.\n const dbQuery = this.database.with(\n 'filtered',\n ['entity_id', 'final_entity', ...(sortField ? ['value'] : [])],\n inner => {\n inner\n .from<DbFinalEntitiesRow>('final_entities')\n .whereNotNull('final_entity');\n\n if (sortField) {\n inner\n .distinct()\n .leftOuterJoin('search', qb =>\n qb\n .on('search.entity_id', 'final_entities.entity_id')\n .andOnVal('search.key', sortField.field),\n )\n .select({\n entity_id: 'final_entities.entity_id',\n final_entity: 'final_entities.final_entity',\n value: 'search.value',\n });\n } else {\n inner.select({\n entity_id: 'final_entities.entity_id',\n final_entity: 'final_entities.final_entity',\n });\n }\n\n // Add regular filters and/or predicate query, if given\n if (cursor.filter || cursor.query) {\n applyEntityFilterToQuery({\n filter: cursor.filter,\n query: cursor.query,\n targetQuery: inner,\n onEntityIdField: 'final_entities.entity_id',\n knex: this.database,\n });\n }\n\n // Add full text search filters, if given\n const normalizedFullTextFilterTerm =\n cursor.fullTextFilter?.term?.trim();\n const textFilterFields = cursor.fullTextFilter?.fields ?? [\n sortField?.field || 'metadata.uid',\n ];\n if (normalizedFullTextFilterTerm) {\n if (\n textFilterFields.length === 1 &&\n textFilterFields[0] === sortField?.field\n ) {\n // If there is one item, apply the like query to the top level query which is already\n // filtered based on the singular sortField.\n inner.andWhereRaw(\n 'search.value like ?',\n `%${normalizedFullTextFilterTerm.toLocaleLowerCase('en-US')}%`,\n );\n } else {\n const matchQuery = this.database<DbSearchRow>('search')\n .select('search.entity_id')\n // textFilterFields must be lowercased to match searchable keys in database, i.e. spec.profile.displayName -> spec.profile.displayname\n .whereIn(\n 'search.key',\n textFilterFields.map(field => field.toLocaleLowerCase('en-US')),\n )\n .andWhere(function keyFilter() {\n this.andWhereRaw(\n 'search.value like ?',\n `%${normalizedFullTextFilterTerm.toLocaleLowerCase(\n 'en-US',\n )}%`,\n );\n });\n inner.andWhere('final_entities.entity_id', 'in', matchQuery);\n }\n }\n },\n );\n\n // Only pay the cost of counting the number of items if needed\n if (shouldComputeTotalItems) {\n // Note the intentional cross join here. The filtered_count dataset is\n // always exactly one row, so it won't grow the result unnecessarily. But\n // it's also important that there IS at least one row, because even if the\n // filtered dataset is empty, we still want to know the total number of\n // items.\n dbQuery\n .with('filtered_count', ['count'], inner =>\n inner.from('filtered').count('*', { as: 'count' }),\n )\n .fromRaw('filtered_count, filtered')\n .select('count', 'filtered.*');\n } else {\n dbQuery.from('filtered').select('*');\n }\n\n const isOrderingDescending = sortField?.order === 'desc';\n\n // Move forward (or backward) in the set to the correct cursor position\n if (cursor.orderFieldValues) {\n if (cursor.orderFieldValues.length === 2) {\n // The first will be the sortField value, the second the entity_id\n const [first, second] = cursor.orderFieldValues;\n dbQuery.andWhere(function nested() {\n this.where(\n 'filtered.value',\n isFetchingBackwards !== isOrderingDescending ? '<' : '>',\n first,\n )\n .orWhere('filtered.value', '=', first)\n .andWhere(\n 'filtered.entity_id',\n isFetchingBackwards !== isOrderingDescending ? '<' : '>',\n second,\n );\n });\n } else if (cursor.orderFieldValues.length === 1) {\n // This will be the entity_id\n const [first] = cursor.orderFieldValues;\n dbQuery.andWhere('entity_id', isFetchingBackwards ? '<' : '>', first);\n }\n }\n\n // Add the ordering\n let order = sortField?.order ?? 'asc';\n if (isFetchingBackwards) {\n order = invertOrder(order);\n }\n if (this.database.client.config.client === 'pg') {\n // pg correctly orders by the column value and handling nulls in one go\n dbQuery.orderBy([\n ...(sortField\n ? [\n {\n column: 'filtered.value',\n order,\n nulls: 'last',\n },\n ]\n : []),\n {\n column: 'filtered.entity_id',\n order,\n },\n ]);\n } else {\n // sqlite and mysql translate the above statement ONLY into \"order by (value is null) asc\"\n // no matter what the order is, for some reason, so we have to manually add back the statement\n // that translates to \"order by value <order>\" while avoiding to give an order\n dbQuery.orderBy([\n ...(sortField\n ? [\n {\n column: 'filtered.value',\n order: undefined,\n nulls: 'last',\n },\n {\n column: 'filtered.value',\n order,\n },\n ]\n : []),\n {\n column: 'filtered.entity_id',\n order,\n },\n ]);\n }\n\n // Apply a manually set initial offset\n if (\n isQueryEntitiesInitialRequest(request) &&\n request.offset !== undefined\n ) {\n dbQuery.offset(request.offset);\n }\n // fetch an extra item to check if there are more items.\n dbQuery.limit(isFetchingBackwards ? limit : limit + 1);\n\n const rows = shouldComputeTotalItems || limit > 0 ? await dbQuery : [];\n\n let totalItems: number;\n if (cursor.totalItems !== undefined) {\n totalItems = cursor.totalItems;\n } else if (cursor.skipTotalItems) {\n totalItems = 0;\n } else if (rows.length) {\n totalItems = Number(rows[0].count);\n } else {\n totalItems = 0;\n }\n\n if (isFetchingBackwards) {\n rows.reverse();\n }\n const hasMoreResults =\n limit > 0 && (isFetchingBackwards || rows.length > limit);\n\n // discard the extra item only when fetching forward.\n if (rows.length > limit) {\n rows.length -= 1;\n }\n\n const isInitialRequest = cursor.firstSortFieldValues === undefined;\n\n const firstRow = rows[0];\n const lastRow = rows[rows.length - 1];\n\n const firstSortFieldValues =\n cursor.firstSortFieldValues || sortFieldsFromRow(firstRow, sortField);\n\n const nextCursor: Cursor | undefined = hasMoreResults\n ? {\n ...cursor,\n orderFieldValues: sortFieldsFromRow(lastRow, sortField),\n firstSortFieldValues,\n isPrevious: false,\n totalItems,\n }\n : undefined;\n\n const prevCursor: Cursor | undefined =\n !isInitialRequest &&\n rows.length > 0 &&\n !isEqual(\n sortFieldsFromRow(firstRow, sortField),\n cursor.firstSortFieldValues,\n )\n ? {\n ...cursor,\n orderFieldValues: sortFieldsFromRow(firstRow, sortField),\n firstSortFieldValues: cursor.firstSortFieldValues,\n isPrevious: true,\n totalItems,\n }\n : undefined;\n\n return {\n items: processRawEntitiesResult(\n rows.map(r => r.final_entity!),\n request.fields,\n ),\n pageInfo: {\n ...(!!prevCursor && { prevCursor }),\n ...(!!nextCursor && { nextCursor }),\n },\n totalItems,\n };\n }\n\n async removeEntityByUid(uid: string): Promise<void> {\n const relationPeerRefs = await this.database.transaction(async tx => {\n const dbConfig = tx.client.config;\n\n // Clear the hashed state of the immediate parents of the deleted entity.\n // This makes sure that when they get reprocessed, their output is written\n // down again. The reason for wanting to do this, is that if the user\n // deletes entities that ARE still emitted by the parent, the parent\n // processing will still generate the same output hash as always, which\n // means it'll never try to write down the children again (it assumes that\n // they already exist). This means that without the code below, the database\n // never \"heals\" from accidental deletes.\n if (dbConfig.client.includes('mysql')) {\n // MySQL doesn't support the syntax we need to do this in a single query,\n // http://dev.mysql.com/doc/refman/5.6/en/update.html\n const results = await tx<DbRefreshStateRow>('refresh_state')\n .select('entity_id')\n .whereIn('entity_ref', function parents(builder) {\n return builder\n .from<DbRefreshStateRow>('refresh_state')\n .innerJoin<DbRefreshStateReferencesRow>(\n 'refresh_state_references',\n {\n 'refresh_state_references.target_entity_ref':\n 'refresh_state.entity_ref',\n },\n )\n .where('refresh_state.entity_id', '=', uid)\n .select('refresh_state_references.source_entity_ref');\n });\n await tx<DbRefreshStateRow>('refresh_state')\n .update({\n result_hash: 'child-was-deleted',\n next_update_at: tx.fn.now(),\n })\n .whereIn(\n 'entity_id',\n results.map(key => key.entity_id),\n );\n } else {\n await tx<DbRefreshStateRow>('refresh_state')\n .update({\n result_hash: 'child-was-deleted',\n next_update_at: tx.fn.now(),\n })\n .whereIn('entity_ref', function parents(builder) {\n return builder\n .from<DbRefreshStateRow>('refresh_state')\n .innerJoin<DbRefreshStateReferencesRow>(\n 'refresh_state_references',\n {\n 'refresh_state_references.target_entity_ref':\n 'refresh_state.entity_ref',\n },\n )\n .where('refresh_state.entity_id', '=', uid)\n .select('refresh_state_references.source_entity_ref');\n });\n }\n\n const relationPeers = await tx\n .from<DbRelationsRow>('relations')\n .innerJoin<DbRefreshStateRow>('refresh_state', {\n 'refresh_state.entity_ref': 'relations.target_entity_ref',\n })\n .where('relations.originating_entity_id', '=', uid)\n .andWhere('refresh_state.entity_id', '!=', uid)\n .select({ ref: 'relations.target_entity_ref' })\n .union(other =>\n other\n .from<DbRelationsRow>('relations')\n .innerJoin<DbRefreshStateRow>('refresh_state', {\n 'refresh_state.entity_ref': 'relations.source_entity_ref',\n })\n .where('relations.originating_entity_id', '=', uid)\n .andWhere('refresh_state.entity_id', '!=', uid)\n .select({ ref: 'relations.source_entity_ref' }),\n );\n\n await tx<DbRefreshStateRow>('refresh_state')\n .where('entity_id', uid)\n .delete();\n\n return new Set(relationPeers.map(p => p.ref));\n });\n\n if (relationPeerRefs.size > 0) {\n await this.stitcher.stitch({\n entityRefs: relationPeerRefs,\n });\n }\n }\n\n async entityAncestry(rootRef: string): Promise<EntityAncestryResponse> {\n const [rootRow] = await this.database<DbFinalEntitiesRow>('final_entities')\n .where('final_entities.entity_ref', '=', rootRef)\n .select({\n entityJson: 'final_entities.final_entity',\n });\n\n if (!rootRow) {\n throw new NotFoundError(`No such entity ${rootRef}`);\n }\n\n const rootEntity = JSON.parse(rootRow.entityJson) as Entity;\n const seenEntityRefs = new Set<string>();\n const todo = new Array<Entity>();\n const items = new Array<{ entity: Entity; parentEntityRefs: string[] }>();\n\n for (\n let current: Entity | undefined = rootEntity;\n current;\n current = todo.pop()\n ) {\n const currentRef = stringifyEntityRef(current);\n seenEntityRefs.add(currentRef);\n\n const parentRows = await this.database<DbRefreshStateReferencesRow>(\n 'refresh_state_references',\n )\n .innerJoin<DbFinalEntitiesRow>('final_entities', {\n 'refresh_state_references.source_entity_ref':\n 'final_entities.entity_ref',\n })\n .where('refresh_state_references.target_entity_ref', '=', currentRef)\n .select({\n parentEntityRef: 'final_entities.entity_ref',\n parentEntityJson: 'final_entities.final_entity',\n });\n\n const parentRefs: string[] = [];\n for (const { parentEntityRef, parentEntityJson } of parentRows) {\n parentRefs.push(parentEntityRef);\n if (!seenEntityRefs.has(parentEntityRef)) {\n seenEntityRefs.add(parentEntityRef);\n todo.push(JSON.parse(parentEntityJson));\n }\n }\n\n items.push({\n entity: current,\n parentEntityRefs: parentRefs,\n });\n }\n\n return {\n rootEntityRef: stringifyEntityRef(rootEntity),\n items,\n };\n }\n\n async facets(request: EntityFacetsRequest): Promise<EntityFacetsResponse> {\n const query = this.database<DbSearchRow>('search')\n .whereIn(\n 'search.key',\n request.facets.map(f => f.toLocaleLowerCase('en-US')),\n )\n .whereNotNull('search.original_value')\n .select({\n facet: 'search.key',\n value: 'search.original_value',\n count: this.database.raw('count(DISTINCT search.entity_id)'),\n })\n .groupBy(['search.key', 'search.original_value']);\n\n if (request.filter || request.query) {\n // Build a subquery that finds matching entity IDs via\n // final_entities, so that the EXISTS-based filters correlate\n // against one-row-per-entity rather than the much larger search\n // table. The whereNotNull guard on final_entity excludes\n // not-yet-stitched (or future tombstoned) entities.\n const entityIdSubquery = this.database('final_entities')\n .select('final_entities.entity_id')\n .whereNotNull('final_entities.final_entity');\n\n applyEntityFilterToQuery({\n filter: request.filter,\n query: request.query,\n targetQuery: entityIdSubquery,\n onEntityIdField: 'final_entities.entity_id',\n knex: this.database,\n });\n\n query.whereIn('search.entity_id', entityIdSubquery);\n }\n\n const rows = await query;\n\n const facets: EntityFacetsResponse['facets'] = {};\n for (const facet of request.facets) {\n const facetLowercase = facet.toLocaleLowerCase('en-US');\n facets[facet] = rows\n .filter(row => row.facet === facetLowercase)\n .map(row => ({\n value: String(row.value),\n count: Number(row.count),\n }));\n }\n\n return { facets };\n }\n}\n\nfunction parseCursorFromRequest(\n request?: QueryEntitiesRequest,\n): Partial<Cursor> & { skipTotalItems: boolean } {\n if (isQueryEntitiesInitialRequest(request)) {\n const {\n filter,\n query,\n orderFields: sortFields = [],\n fullTextFilter,\n skipTotalItems = false,\n } = request;\n return {\n filter,\n query,\n orderFields: sortFields,\n fullTextFilter,\n skipTotalItems,\n };\n }\n if (isQueryEntitiesCursorRequest(request)) {\n return {\n ...request.cursor,\n // Doesn't matter here\n skipTotalItems: false,\n };\n }\n return {\n skipTotalItems: false,\n };\n}\n\nfunction invertOrder(order: EntityOrder['order']) {\n return order === 'asc' ? 'desc' : 'asc';\n}\n\nfunction sortFieldsFromRow(\n row: DbSearchRow & DbFinalEntitiesRow,\n sortField?: EntityOrder | undefined,\n) {\n return sortField ? [row?.value, row?.entity_id] : [row?.entity_id];\n}\n"],"names":["InputError","applyEntityFilterToQuery","processRawEntitiesResult","expandLegacyCompoundRelationsInEntity","lodashChunk","isQueryEntitiesInitialRequest","isEqual","NotFoundError","stringifyEntityRef","isQueryEntitiesCursorRequest"],"mappings":"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"}
@@ -169,7 +169,7 @@ exports.down = async function down(knex) {
169
169
  'An opaque string that changes for each update operation to any part of the entity, including metadata.',
170
170
  );
171
171
  table
172
- .string('generation')
172
+ .integer('generation')
173
173
  .notNullable()
174
174
  .unsigned()
175
175
  .comment(
@@ -52,12 +52,12 @@ exports.down = async function down(knex) {
52
52
  await knex.schema.alterTable('entities', table => {
53
53
  // https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta
54
54
  table.dropUnique([], 'entities_unique_full_name');
55
- table.unique(['kind', 'namespace', 'name'], {
55
+ table.unique(['kind', 'name', 'namespace'], {
56
56
  indexName: 'entities_unique_name',
57
57
  });
58
58
  });
59
59
 
60
- await knex.schema.alterTable('entities_search', table => {
60
+ await knex.schema.alterTable('entities', table => {
61
61
  table.dropColumn('full_name');
62
62
  });
63
63
  };
@@ -175,33 +175,10 @@ exports.up = async function up(knex) {
175
175
  * @param {import('knex').Knex} knex
176
176
  */
177
177
  exports.down = async function down(knex) {
178
- await knex.schema.alterTable('refresh_state_references', table => {
179
- table.dropIndex([], 'refresh_state_references_source_key_idx');
180
- table.dropIndex([], 'refresh_state_references_source_entity_ref_idx');
181
- table.dropIndex([], 'refresh_state_references_target_entity_ref_idx');
182
- });
183
- await knex.schema.alterTable('refresh_state', table => {
184
- table.dropUnique([], 'refresh_state_entity_ref_uniq');
185
- table.dropIndex([], 'refresh_state_entity_id_idx');
186
- table.dropIndex([], 'refresh_state_entity_ref_idx');
187
- table.dropIndex([], 'refresh_state_next_update_at_idx');
188
- });
189
- await knex.schema.alterTable('final_entities', table => {
190
- table.dropIndex([], 'final_entities_entity_id_idx');
191
- });
192
- await knex.schema.alterTable('relations', table => {
193
- table.index('source_entity_ref', 'relations_source_entity_ref_idx');
194
- table.index('originating_entity_id', 'relations_source_entity_id_idx');
195
- });
196
- await knex.schema.alterTable('search', table => {
197
- table.dropIndex([], 'search_entity_id_idx');
198
- table.dropIndex([], 'search_key_idx');
199
- table.dropIndex([], 'search_value_idx');
200
- });
201
-
178
+ // Drop tables that have FK constraints referencing refresh_state first.
179
+ await knex.schema.dropTable('refresh_state_references');
202
180
  await knex.schema.dropTable('search');
203
181
  await knex.schema.dropTable('final_entities');
204
182
  await knex.schema.dropTable('relations');
205
- await knex.schema.dropTable('references');
206
183
  await knex.schema.dropTable('refresh_state');
207
184
  };
@@ -25,4 +25,89 @@ exports.up = async function up(knex) {
25
25
  await knex.schema.dropTable('entities');
26
26
  };
27
27
 
28
- exports.down = async function down() {};
28
+ /**
29
+ * @param {import('knex').Knex} knex
30
+ */
31
+ exports.down = async function down(knex) {
32
+ await knex.schema.createTable('entities', table => {
33
+ table.comment('All entities currently stored in the catalog');
34
+ table.uuid('id').primary().comment('Auto-generated ID of the entity');
35
+ table
36
+ .uuid('location_id')
37
+ .references('id')
38
+ .inTable('locations')
39
+ .nullable()
40
+ .comment('The location that originated the entity');
41
+ table
42
+ .string('etag')
43
+ .notNullable()
44
+ .comment(
45
+ 'An opaque string that changes for each update operation to any part of the entity, including metadata.',
46
+ );
47
+ table
48
+ .integer('generation')
49
+ .notNullable()
50
+ .unsigned()
51
+ .comment(
52
+ 'A positive nonzero number that indicates the current generation of data for this entity; the value is incremented each time the spec changes.',
53
+ );
54
+ table
55
+ .string('full_name')
56
+ .notNullable()
57
+ .comment('The full name of the entity');
58
+ table
59
+ .text('data')
60
+ .notNullable()
61
+ .comment('The entire JSON data blob of the entity');
62
+ table.unique(['full_name'], { indexName: 'entities_unique_full_name' });
63
+ table.index('location_id', 'entity_location_id_idx');
64
+ });
65
+
66
+ await knex.schema.createTable('entities_search', table => {
67
+ table.comment(
68
+ 'Flattened key-values from the entities, used for quick filtering',
69
+ );
70
+ table
71
+ .uuid('entity_id')
72
+ .references('id')
73
+ .inTable('entities')
74
+ .onDelete('CASCADE')
75
+ .comment('The entity that matches this key/value');
76
+ table
77
+ .string('key')
78
+ .notNullable()
79
+ .comment('A key that occurs in the entity');
80
+ table
81
+ .string('value')
82
+ .nullable()
83
+ .comment('The corresponding value to match on');
84
+ table.index(['key'], 'entities_search_key');
85
+ table.index(['value'], 'entities_search_value');
86
+ table.index(['entity_id'], 'entity_id_idx');
87
+ });
88
+
89
+ await knex.schema.createTable('entities_relations', table => {
90
+ table.comment('All relations between entities in the catalog');
91
+ table
92
+ .uuid('originating_entity_id')
93
+ .references('id')
94
+ .inTable('entities')
95
+ .onDelete('CASCADE')
96
+ .notNullable()
97
+ .comment('The entity that provided the relation');
98
+ table
99
+ .string('source_full_name')
100
+ .notNullable()
101
+ .comment('The full name of the source entity of the relation');
102
+ table
103
+ .string('type')
104
+ .notNullable()
105
+ .comment('The type of the relation between the entities');
106
+ table
107
+ .string('target_full_name')
108
+ .notNullable()
109
+ .comment('The full name of the target entity of the relation');
110
+ table.index('source_full_name', 'source_full_name_idx');
111
+ table.index('originating_entity_id', 'originating_entity_id_idx');
112
+ });
113
+ };
@@ -22,7 +22,7 @@
22
22
  */
23
23
  exports.up = async function up(knex) {
24
24
  await knex.schema.alterTable('locations', table => {
25
- table.text('target').alter();
25
+ table.text('target').notNullable().alter();
26
26
  });
27
27
  };
28
28
 
@@ -42,6 +42,6 @@ exports.down = async function down(knex) {
42
42
  }
43
43
 
44
44
  await knex.schema.alterTable('locations', table => {
45
- table.string('target').alter();
45
+ table.string('target').notNullable().alter();
46
46
  });
47
47
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-catalog-backend",
3
- "version": "3.6.0",
3
+ "version": "3.6.1-next.0",
4
4
  "description": "The Backstage backend plugin that provides the Backstage catalog",
5
5
  "backstage": {
6
6
  "role": "backend-plugin",
@@ -76,20 +76,20 @@
76
76
  "test": "backstage-cli package test"
77
77
  },
78
78
  "dependencies": {
79
- "@backstage/backend-openapi-utils": "^0.6.8",
80
- "@backstage/backend-plugin-api": "^1.9.0",
81
- "@backstage/catalog-client": "^1.15.0",
82
- "@backstage/catalog-model": "^1.8.0",
83
- "@backstage/config": "^1.3.7",
84
- "@backstage/errors": "^1.3.0",
85
- "@backstage/filter-predicates": "^0.1.2",
86
- "@backstage/integration": "^2.0.1",
87
- "@backstage/plugin-catalog-common": "^1.1.9",
88
- "@backstage/plugin-catalog-node": "^2.2.0",
89
- "@backstage/plugin-events-node": "^0.4.21",
90
- "@backstage/plugin-permission-common": "^0.9.8",
91
- "@backstage/plugin-permission-node": "^0.10.12",
92
- "@backstage/types": "^1.2.2",
79
+ "@backstage/backend-openapi-utils": "0.6.9-next.0",
80
+ "@backstage/backend-plugin-api": "1.9.1-next.0",
81
+ "@backstage/catalog-client": "1.15.1-next.0",
82
+ "@backstage/catalog-model": "1.8.1-next.0",
83
+ "@backstage/config": "1.3.8-next.0",
84
+ "@backstage/errors": "1.3.1-next.0",
85
+ "@backstage/filter-predicates": "0.1.3-next.0",
86
+ "@backstage/integration": "2.0.2-next.0",
87
+ "@backstage/plugin-catalog-common": "1.1.10-next.0",
88
+ "@backstage/plugin-catalog-node": "2.2.1-next.0",
89
+ "@backstage/plugin-events-node": "0.4.22-next.0",
90
+ "@backstage/plugin-permission-common": "0.9.9-next.0",
91
+ "@backstage/plugin-permission-node": "0.10.13-next.0",
92
+ "@backstage/types": "1.2.2",
93
93
  "@opentelemetry/api": "^1.9.0",
94
94
  "ajv": "^8.10.0",
95
95
  "ajv-errors": "^3.0.0",
@@ -113,20 +113,18 @@
113
113
  "zod-validation-error": "^4.0.2"
114
114
  },
115
115
  "devDependencies": {
116
- "@backstage/backend-defaults": "^0.17.0",
117
- "@backstage/backend-test-utils": "^1.11.2",
118
- "@backstage/cli": "^0.36.1",
119
- "@backstage/plugin-catalog-backend-module-logs": "^0.1.21",
120
- "@backstage/plugin-permission-common": "^0.9.8",
121
- "@backstage/plugin-scaffolder-common": "^2.1.0",
122
- "@backstage/repo-tools": "^0.17.1",
116
+ "@backstage/backend-defaults": "0.17.1-next.0",
117
+ "@backstage/backend-test-utils": "1.11.3-next.0",
118
+ "@backstage/cli": "0.36.2-next.0",
119
+ "@backstage/plugin-catalog-backend-module-logs": "0.1.22-next.0",
120
+ "@backstage/plugin-scaffolder-common": "2.1.1-next.0",
121
+ "@backstage/repo-tools": "0.17.2-next.0",
123
122
  "@types/core-js": "^2.5.4",
124
123
  "@types/express": "^4.17.6",
125
124
  "@types/git-url-parse": "^9.0.0",
126
125
  "@types/lodash": "^4.14.151",
127
126
  "@types/supertest": "^2.0.8",
128
127
  "better-sqlite3": "^12.0.0",
129
- "luxon": "^3.0.0",
130
128
  "msw": "^2.0.0",
131
129
  "supertest": "^7.0.0",
132
130
  "wait-for-expect": "^4.0.0",