@backstage/plugin-catalog-backend 1.28.1-next.0 → 1.29.0-next.2
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 +58 -0
- package/dist/database/metrics.cjs.js +26 -8
- package/dist/database/metrics.cjs.js.map +1 -1
- package/dist/service/DefaultEntitiesCatalog.cjs.js +178 -178
- package/dist/service/DefaultEntitiesCatalog.cjs.js.map +1 -1
- package/dist/service/createRouter.cjs.js +67 -20
- package/dist/service/createRouter.cjs.js.map +1 -1
- package/dist/service/request/applyEntityFilterToQuery.cjs.js +129 -0
- package/dist/service/request/applyEntityFilterToQuery.cjs.js.map +1 -0
- package/dist/service/request/parseEntityFilterParams.cjs.js +5 -2
- package/dist/service/request/parseEntityFilterParams.cjs.js.map +1 -1
- package/dist/service/util.cjs.js +54 -0
- package/dist/service/util.cjs.js.map +1 -1
- package/package.json +20 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# @backstage/plugin-catalog-backend
|
|
2
2
|
|
|
3
|
+
## 1.29.0-next.2
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c1307b4: Implement `/entities` in terms of `queryEntities` to not run into memory and performance problems on large catalogs
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- dfc8b41: Updated dependency `@opentelemetry/api` to `^1.9.0`.
|
|
12
|
+
- 8013c9c: Perform the by-query count inlined with the main query
|
|
13
|
+
- feba9ee: Use a join based strategy for filtering, when having small page sizes
|
|
14
|
+
- 1fdb48e: Use a faster count method on pg when computing some metrics
|
|
15
|
+
- 0c33465: Implement `/entities/by-name/:kind/:namespace/:name` using `getEntitiesByRefs`
|
|
16
|
+
- d93390d: When parsing filters, do not make redundant `anyOf` and `allOf` nodes when there's only a single entry within them
|
|
17
|
+
- 24ecea8: Avoid extra ordering in by-query when the user doesn't ask for it
|
|
18
|
+
- Updated dependencies
|
|
19
|
+
- @backstage/backend-plugin-api@1.1.0-next.2
|
|
20
|
+
- @backstage/plugin-permission-node@0.8.6-next.2
|
|
21
|
+
- @backstage/backend-openapi-utils@0.4.0-next.2
|
|
22
|
+
- @backstage/errors@1.2.6-next.0
|
|
23
|
+
- @backstage/plugin-catalog-node@1.15.0-next.2
|
|
24
|
+
- @backstage/plugin-events-node@0.4.6-next.2
|
|
25
|
+
- @backstage/plugin-search-backend-module-catalog@0.2.6-next.2
|
|
26
|
+
- @backstage/catalog-client@1.9.0-next.2
|
|
27
|
+
- @backstage/catalog-model@1.7.2-next.0
|
|
28
|
+
- @backstage/config@1.3.1-next.0
|
|
29
|
+
- @backstage/integration@1.16.0-next.1
|
|
30
|
+
- @backstage/types@1.2.0
|
|
31
|
+
- @backstage/plugin-catalog-common@1.1.2-next.0
|
|
32
|
+
- @backstage/plugin-permission-common@0.8.3-next.0
|
|
33
|
+
|
|
34
|
+
## 1.29.0-next.1
|
|
35
|
+
|
|
36
|
+
### Minor Changes
|
|
37
|
+
|
|
38
|
+
- 384e494: Internal updates to generated code.
|
|
39
|
+
- 1d0bc11: Fetch all facets in a single database query
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- e4aab10: Fix a bug where sometimes the `by-query` endpoint could return nulls for entities that were not yet stitched.
|
|
44
|
+
- 5c9cc05: Use native fetch instead of node-fetch
|
|
45
|
+
- Updated dependencies
|
|
46
|
+
- @backstage/plugin-catalog-node@1.15.0-next.1
|
|
47
|
+
- @backstage/catalog-client@1.9.0-next.1
|
|
48
|
+
- @backstage/backend-plugin-api@1.1.0-next.1
|
|
49
|
+
- @backstage/plugin-permission-node@0.8.6-next.1
|
|
50
|
+
- @backstage/plugin-search-backend-module-catalog@0.2.6-next.1
|
|
51
|
+
- @backstage/backend-openapi-utils@0.3.1-next.1
|
|
52
|
+
- @backstage/catalog-model@1.7.1
|
|
53
|
+
- @backstage/config@1.3.0
|
|
54
|
+
- @backstage/errors@1.2.5
|
|
55
|
+
- @backstage/integration@1.16.0-next.0
|
|
56
|
+
- @backstage/types@1.2.0
|
|
57
|
+
- @backstage/plugin-catalog-common@1.1.1
|
|
58
|
+
- @backstage/plugin-events-node@0.4.6-next.1
|
|
59
|
+
- @backstage/plugin-permission-common@0.8.2
|
|
60
|
+
|
|
3
61
|
## 1.28.1-next.0
|
|
4
62
|
|
|
5
63
|
### Patch Changes
|
|
@@ -64,18 +64,36 @@ function initDatabaseMetrics(knex) {
|
|
|
64
64
|
registered_locations: meter.createObservableGauge("catalog_registered_locations_count", {
|
|
65
65
|
description: "Total amount of registered locations in the catalog"
|
|
66
66
|
}).addCallback(async (gauge) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
if (knex.client.config.client === "pg") {
|
|
68
|
+
const total = await knex.raw(`
|
|
69
|
+
SELECT reltuples::bigint AS estimate
|
|
70
|
+
FROM pg_class
|
|
71
|
+
WHERE oid = 'locations'::regclass;
|
|
72
|
+
`);
|
|
73
|
+
gauge.observe(Number(total.rows[0].estimate));
|
|
74
|
+
} else {
|
|
75
|
+
const total = await knex("locations").count({
|
|
76
|
+
count: "*"
|
|
77
|
+
});
|
|
78
|
+
gauge.observe(Number(total[0].count));
|
|
79
|
+
}
|
|
71
80
|
}),
|
|
72
81
|
relations: meter.createObservableGauge("catalog_relations_count", {
|
|
73
82
|
description: "Total amount of relations between entities"
|
|
74
83
|
}).addCallback(async (gauge) => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
if (knex.client.config.client === "pg") {
|
|
85
|
+
const total = await knex.raw(`
|
|
86
|
+
SELECT reltuples::bigint AS estimate
|
|
87
|
+
FROM pg_class
|
|
88
|
+
WHERE oid = 'relations'::regclass;
|
|
89
|
+
`);
|
|
90
|
+
gauge.observe(Number(total.rows[0].estimate));
|
|
91
|
+
} else {
|
|
92
|
+
const total = await knex("relations").count({
|
|
93
|
+
count: "*"
|
|
94
|
+
});
|
|
95
|
+
gauge.observe(Number(total[0].count));
|
|
96
|
+
}
|
|
79
97
|
})
|
|
80
98
|
};
|
|
81
99
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.cjs.js","sources":["../../src/database/metrics.ts"],"sourcesContent":["/*\n * Copyright 2021 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 { Knex } from 'knex';\nimport { createGaugeMetric } from '../util/metrics';\nimport { DbRelationsRow, DbLocationsRow, DbSearchRow } from './tables';\nimport { metrics } from '@opentelemetry/api';\n\nexport function initDatabaseMetrics(knex: Knex) {\n const seenProm = new Set<string>();\n const seen = new Set<string>();\n const meter = metrics.getMeter('default');\n return {\n entities_count_prom: createGaugeMetric({\n name: 'catalog_entities_count',\n help: 'Total amount of entities in the catalog. DEPRECATED: Please use opentelemetry metrics instead.',\n labelNames: ['kind'],\n async collect() {\n const results = await knex<DbSearchRow>('search')\n .where('key', '=', 'kind')\n .whereNotNull('value')\n .select({ kind: 'value', count: knex.raw('count(*)') })\n .groupBy('value');\n\n results.forEach(({ kind, count }) => {\n seenProm.add(kind);\n this.set({ kind }, Number(count));\n });\n\n // Set all the entities that were not seenProm to 0 and delete them from the seenProm set.\n seenProm.forEach(kind => {\n if (!results.some(r => r.kind === kind)) {\n this.set({ kind }, 0);\n seenProm.delete(kind);\n }\n });\n },\n }),\n registered_locations_prom: createGaugeMetric({\n name: 'catalog_registered_locations_count',\n help: 'Total amount of registered locations in the catalog. DEPRECATED: Please use opentelemetry metrics instead.',\n async collect() {\n const total = await knex<DbLocationsRow>('locations').count({\n count: '*',\n });\n this.set(Number(total[0].count));\n },\n }),\n relations_prom: createGaugeMetric({\n name: 'catalog_relations_count',\n help: 'Total amount of relations between entities. DEPRECATED: Please use opentelemetry metrics instead.',\n async collect() {\n const total = await knex<DbRelationsRow>('relations').count({\n count: '*',\n });\n this.set(Number(total[0].count));\n },\n }),\n entities_count: meter\n .createObservableGauge('catalog_entities_count', {\n description: 'Total amount of entities in the catalog',\n })\n .addCallback(async gauge => {\n const results = await knex<DbSearchRow>('search')\n .where('key', '=', 'kind')\n .whereNotNull('value')\n .select({ kind: 'value', count: knex.raw('count(*)') })\n .groupBy('value');\n\n results.forEach(({ kind, count }) => {\n seen.add(kind);\n gauge.observe(Number(count), { kind });\n });\n\n // Set all the entities that were not seen to 0 and delete them from the seen set.\n seen.forEach(kind => {\n if (!results.some(r => r.kind === kind)) {\n gauge.observe(0, { kind });\n seen.delete(kind);\n }\n });\n }),\n registered_locations: meter\n .createObservableGauge('catalog_registered_locations_count', {\n description: 'Total amount of registered locations in the catalog',\n })\n .addCallback(async gauge => {\n const total = await knex<DbLocationsRow>('locations').count({\n
|
|
1
|
+
{"version":3,"file":"metrics.cjs.js","sources":["../../src/database/metrics.ts"],"sourcesContent":["/*\n * Copyright 2021 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 { Knex } from 'knex';\nimport { createGaugeMetric } from '../util/metrics';\nimport { DbRelationsRow, DbLocationsRow, DbSearchRow } from './tables';\nimport { metrics } from '@opentelemetry/api';\n\nexport function initDatabaseMetrics(knex: Knex) {\n const seenProm = new Set<string>();\n const seen = new Set<string>();\n const meter = metrics.getMeter('default');\n return {\n entities_count_prom: createGaugeMetric({\n name: 'catalog_entities_count',\n help: 'Total amount of entities in the catalog. DEPRECATED: Please use opentelemetry metrics instead.',\n labelNames: ['kind'],\n async collect() {\n const results = await knex<DbSearchRow>('search')\n .where('key', '=', 'kind')\n .whereNotNull('value')\n .select({ kind: 'value', count: knex.raw('count(*)') })\n .groupBy('value');\n\n results.forEach(({ kind, count }) => {\n seenProm.add(kind);\n this.set({ kind }, Number(count));\n });\n\n // Set all the entities that were not seenProm to 0 and delete them from the seenProm set.\n seenProm.forEach(kind => {\n if (!results.some(r => r.kind === kind)) {\n this.set({ kind }, 0);\n seenProm.delete(kind);\n }\n });\n },\n }),\n registered_locations_prom: createGaugeMetric({\n name: 'catalog_registered_locations_count',\n help: 'Total amount of registered locations in the catalog. DEPRECATED: Please use opentelemetry metrics instead.',\n async collect() {\n const total = await knex<DbLocationsRow>('locations').count({\n count: '*',\n });\n this.set(Number(total[0].count));\n },\n }),\n relations_prom: createGaugeMetric({\n name: 'catalog_relations_count',\n help: 'Total amount of relations between entities. DEPRECATED: Please use opentelemetry metrics instead.',\n async collect() {\n const total = await knex<DbRelationsRow>('relations').count({\n count: '*',\n });\n this.set(Number(total[0].count));\n },\n }),\n entities_count: meter\n .createObservableGauge('catalog_entities_count', {\n description: 'Total amount of entities in the catalog',\n })\n .addCallback(async gauge => {\n const results = await knex<DbSearchRow>('search')\n .where('key', '=', 'kind')\n .whereNotNull('value')\n .select({ kind: 'value', count: knex.raw('count(*)') })\n .groupBy('value');\n\n results.forEach(({ kind, count }) => {\n seen.add(kind);\n gauge.observe(Number(count), { kind });\n });\n\n // Set all the entities that were not seen to 0 and delete them from the seen set.\n seen.forEach(kind => {\n if (!results.some(r => r.kind === kind)) {\n gauge.observe(0, { kind });\n seen.delete(kind);\n }\n });\n }),\n registered_locations: meter\n .createObservableGauge('catalog_registered_locations_count', {\n description: 'Total amount of registered locations in the catalog',\n })\n .addCallback(async gauge => {\n if (knex.client.config.client === 'pg') {\n // https://stackoverflow.com/questions/7943233/fast-way-to-discover-the-row-count-of-a-table-in-postgresql\n const total = await knex.raw(`\n SELECT reltuples::bigint AS estimate\n FROM pg_class\n WHERE oid = 'locations'::regclass;\n `);\n gauge.observe(Number(total.rows[0].estimate));\n } else {\n const total = await knex<DbLocationsRow>('locations').count({\n count: '*',\n });\n gauge.observe(Number(total[0].count));\n }\n }),\n relations: meter\n .createObservableGauge('catalog_relations_count', {\n description: 'Total amount of relations between entities',\n })\n .addCallback(async gauge => {\n if (knex.client.config.client === 'pg') {\n // https://stackoverflow.com/questions/7943233/fast-way-to-discover-the-row-count-of-a-table-in-postgresql\n const total = await knex.raw(`\n SELECT reltuples::bigint AS estimate\n FROM pg_class\n WHERE oid = 'relations'::regclass;\n `);\n gauge.observe(Number(total.rows[0].estimate));\n } else {\n const total = await knex<DbRelationsRow>('relations').count({\n count: '*',\n });\n gauge.observe(Number(total[0].count));\n }\n }),\n };\n}\n"],"names":["metrics","createGaugeMetric"],"mappings":"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"}
|
|
@@ -5,12 +5,9 @@ var errors = require('@backstage/errors');
|
|
|
5
5
|
var lodash = require('lodash');
|
|
6
6
|
var zod = require('zod');
|
|
7
7
|
var util = require('./util.cjs.js');
|
|
8
|
+
var applyEntityFilterToQuery = require('./request/applyEntityFilterToQuery.cjs.js');
|
|
8
9
|
|
|
9
|
-
const
|
|
10
|
-
field: "metadata.uid",
|
|
11
|
-
order: "asc"
|
|
12
|
-
};
|
|
13
|
-
const DEFAULT_LIMIT = 20;
|
|
10
|
+
const DEFAULT_LIMIT = 200;
|
|
14
11
|
function parsePagination(input) {
|
|
15
12
|
if (!input) {
|
|
16
13
|
return {};
|
|
@@ -46,52 +43,6 @@ function stringifyPagination(input) {
|
|
|
46
43
|
const base64 = Buffer.from(json, "utf8").toString("base64");
|
|
47
44
|
return base64;
|
|
48
45
|
}
|
|
49
|
-
function addCondition(queryBuilder, db, filter, negate = false, entityIdField = "entity_id") {
|
|
50
|
-
const key = filter.key.toLowerCase();
|
|
51
|
-
const values = filter.values?.map((v) => v.toLowerCase());
|
|
52
|
-
const matchQuery = db("search").select("search.entity_id").where({ key }).andWhere(function keyFilter() {
|
|
53
|
-
if (values?.length === 1) {
|
|
54
|
-
this.where({ value: values.at(0) });
|
|
55
|
-
} else if (values) {
|
|
56
|
-
this.andWhere("value", "in", values);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
queryBuilder.andWhere(entityIdField, negate ? "not in" : "in", matchQuery);
|
|
60
|
-
}
|
|
61
|
-
function isEntitiesSearchFilter(filter) {
|
|
62
|
-
return filter.hasOwnProperty("key");
|
|
63
|
-
}
|
|
64
|
-
function isOrEntityFilter(filter) {
|
|
65
|
-
return filter.hasOwnProperty("anyOf");
|
|
66
|
-
}
|
|
67
|
-
function isNegationEntityFilter(filter) {
|
|
68
|
-
return filter.hasOwnProperty("not");
|
|
69
|
-
}
|
|
70
|
-
function parseFilter(filter, query, db, negate = false, entityIdField = "entity_id") {
|
|
71
|
-
if (isNegationEntityFilter(filter)) {
|
|
72
|
-
return parseFilter(filter.not, query, db, !negate, entityIdField);
|
|
73
|
-
}
|
|
74
|
-
if (isEntitiesSearchFilter(filter)) {
|
|
75
|
-
return query.andWhere(function filterFunction() {
|
|
76
|
-
addCondition(this, db, filter, negate, entityIdField);
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
return query[negate ? "andWhereNot" : "andWhere"](function filterFunction() {
|
|
80
|
-
if (isOrEntityFilter(filter)) {
|
|
81
|
-
for (const subFilter of filter.anyOf ?? []) {
|
|
82
|
-
this.orWhere(
|
|
83
|
-
(subQuery) => parseFilter(subFilter, subQuery, db, false, entityIdField)
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
} else {
|
|
87
|
-
for (const subFilter of filter.allOf ?? []) {
|
|
88
|
-
this.andWhere(
|
|
89
|
-
(subQuery) => parseFilter(subFilter, subQuery, db, false, entityIdField)
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
46
|
class DefaultEntitiesCatalog {
|
|
96
47
|
database;
|
|
97
48
|
logger;
|
|
@@ -103,6 +54,7 @@ class DefaultEntitiesCatalog {
|
|
|
103
54
|
}
|
|
104
55
|
async entities(request) {
|
|
105
56
|
const db = this.database;
|
|
57
|
+
const { limit, offset } = parsePagination(request?.pagination);
|
|
106
58
|
let entitiesQuery = db("final_entities").select("final_entities.*");
|
|
107
59
|
request?.order?.forEach(({ field }, index) => {
|
|
108
60
|
const alias = `order_${index}`;
|
|
@@ -115,13 +67,13 @@ class DefaultEntitiesCatalog {
|
|
|
115
67
|
});
|
|
116
68
|
entitiesQuery = entitiesQuery.whereNotNull("final_entities.final_entity");
|
|
117
69
|
if (request?.filter) {
|
|
118
|
-
entitiesQuery =
|
|
119
|
-
request.filter,
|
|
120
|
-
entitiesQuery,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
"
|
|
124
|
-
);
|
|
70
|
+
entitiesQuery = applyEntityFilterToQuery.applyEntityFilterToQuery({
|
|
71
|
+
filter: request.filter,
|
|
72
|
+
targetQuery: entitiesQuery,
|
|
73
|
+
onEntityIdField: "final_entities.entity_id",
|
|
74
|
+
knex: db,
|
|
75
|
+
strategy: limit !== void 0 && limit <= 500 ? "join" : "in"
|
|
76
|
+
});
|
|
125
77
|
}
|
|
126
78
|
request?.order?.forEach(({ order }, index) => {
|
|
127
79
|
if (db.client.config.client === "pg") {
|
|
@@ -140,7 +92,6 @@ class DefaultEntitiesCatalog {
|
|
|
140
92
|
} else {
|
|
141
93
|
entitiesQuery.orderBy("final_entities.entity_id", "asc");
|
|
142
94
|
}
|
|
143
|
-
const { limit, offset } = parsePagination(request?.pagination);
|
|
144
95
|
if (limit !== void 0) {
|
|
145
96
|
entitiesQuery = entitiesQuery.limit(limit + 1);
|
|
146
97
|
}
|
|
@@ -165,17 +116,7 @@ class DefaultEntitiesCatalog {
|
|
|
165
116
|
if (request?.fields) {
|
|
166
117
|
entities = entities.map((e) => request.fields(e));
|
|
167
118
|
}
|
|
168
|
-
|
|
169
|
-
if (entity.relations) {
|
|
170
|
-
for (const relation of entity.relations) {
|
|
171
|
-
if (!relation.targetRef && relation.target) {
|
|
172
|
-
relation.targetRef = catalogModel.stringifyEntityRef(relation.target);
|
|
173
|
-
} else if (!relation.target && relation.targetRef) {
|
|
174
|
-
relation.target = catalogModel.parseEntityRef(relation.targetRef);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
119
|
+
util.expandLegacyCompoundRelationRefsInResponse(entities);
|
|
179
120
|
return {
|
|
180
121
|
entities,
|
|
181
122
|
pageInfo
|
|
@@ -189,13 +130,12 @@ class DefaultEntitiesCatalog {
|
|
|
189
130
|
entity: "final_entities.final_entity"
|
|
190
131
|
}).whereIn("final_entities.entity_ref", chunk);
|
|
191
132
|
if (request?.filter) {
|
|
192
|
-
query =
|
|
193
|
-
request.filter,
|
|
194
|
-
query,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
);
|
|
133
|
+
query = applyEntityFilterToQuery.applyEntityFilterToQuery({
|
|
134
|
+
filter: request.filter,
|
|
135
|
+
targetQuery: query,
|
|
136
|
+
onEntityIdField: "final_entities.entity_id",
|
|
137
|
+
knex: this.database
|
|
138
|
+
});
|
|
199
139
|
}
|
|
200
140
|
for (const row of await query) {
|
|
201
141
|
lookup.set(row.entityRef, row.entity ? JSON.parse(row.entity) : null);
|
|
@@ -208,97 +148,137 @@ class DefaultEntitiesCatalog {
|
|
|
208
148
|
return { items };
|
|
209
149
|
}
|
|
210
150
|
async queryEntities(request) {
|
|
211
|
-
const db = this.database;
|
|
212
151
|
const limit = request.limit ?? DEFAULT_LIMIT;
|
|
213
152
|
const cursor = {
|
|
214
|
-
orderFields: [
|
|
153
|
+
orderFields: [],
|
|
215
154
|
isPrevious: false,
|
|
216
155
|
...parseCursorFromRequest(request)
|
|
217
156
|
};
|
|
157
|
+
const shouldComputeTotalItems = cursor.totalItems === void 0 && !cursor.skipTotalItems;
|
|
218
158
|
const isFetchingBackwards = cursor.isPrevious;
|
|
219
159
|
if (cursor.orderFields.length > 1) {
|
|
220
160
|
this.logger.warn(`Only one sort field is supported, ignoring the rest`);
|
|
221
161
|
}
|
|
222
|
-
const sortField =
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
162
|
+
const sortField = cursor.orderFields.at(0);
|
|
163
|
+
const dbQuery = this.database.with(
|
|
164
|
+
"filtered",
|
|
165
|
+
["entity_id", "final_entity", ...sortField ? ["value"] : []],
|
|
166
|
+
(inner) => {
|
|
167
|
+
inner.from("final_entities").whereNotNull("final_entity");
|
|
168
|
+
if (sortField) {
|
|
169
|
+
inner.leftOuterJoin(
|
|
170
|
+
"search",
|
|
171
|
+
(qb) => qb.on("search.entity_id", "final_entities.entity_id").andOnVal("search.key", sortField.field)
|
|
172
|
+
).select({
|
|
173
|
+
entity_id: "final_entities.entity_id",
|
|
174
|
+
final_entity: "final_entities.final_entity",
|
|
175
|
+
value: "search.value"
|
|
176
|
+
});
|
|
177
|
+
} else {
|
|
178
|
+
inner.select({
|
|
179
|
+
entity_id: "final_entities.entity_id",
|
|
180
|
+
final_entity: "final_entities.final_entity"
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (cursor.filter) {
|
|
184
|
+
applyEntityFilterToQuery.applyEntityFilterToQuery({
|
|
185
|
+
filter: cursor.filter,
|
|
186
|
+
targetQuery: inner,
|
|
187
|
+
onEntityIdField: "final_entities.entity_id",
|
|
188
|
+
knex: this.database,
|
|
189
|
+
strategy: limit <= 500 ? "join" : "in"
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
const normalizedFullTextFilterTerm = cursor.fullTextFilter?.term?.trim();
|
|
193
|
+
const textFilterFields = cursor.fullTextFilter?.fields ?? [
|
|
194
|
+
sortField?.field || "metadata.uid"
|
|
195
|
+
];
|
|
196
|
+
if (normalizedFullTextFilterTerm) {
|
|
197
|
+
if (textFilterFields.length === 1 && textFilterFields[0] === sortField?.field) {
|
|
198
|
+
inner.andWhereRaw(
|
|
199
|
+
"search.value like ?",
|
|
200
|
+
`%${normalizedFullTextFilterTerm.toLocaleLowerCase("en-US")}%`
|
|
201
|
+
);
|
|
202
|
+
} else {
|
|
203
|
+
const matchQuery = this.database("search").select("search.entity_id").whereIn(
|
|
204
|
+
"search.key",
|
|
205
|
+
textFilterFields.map((field) => field.toLocaleLowerCase("en-US"))
|
|
206
|
+
).andWhere(function keyFilter() {
|
|
207
|
+
this.andWhereRaw(
|
|
208
|
+
"search.value like ?",
|
|
209
|
+
`%${normalizedFullTextFilterTerm.toLocaleLowerCase(
|
|
210
|
+
"en-US"
|
|
211
|
+
)}%`
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
inner.andWhere("final_entities.entity_id", "in", matchQuery);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
230
218
|
);
|
|
231
|
-
if (
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
);
|
|
219
|
+
if (shouldComputeTotalItems) {
|
|
220
|
+
dbQuery.with(
|
|
221
|
+
"filtered_count",
|
|
222
|
+
["count"],
|
|
223
|
+
(inner) => inner.from("filtered").count("*", { as: "count" })
|
|
224
|
+
).fromRaw("filtered_count, filtered").select("count", "filtered.*");
|
|
225
|
+
} else {
|
|
226
|
+
dbQuery.from("filtered").select("*");
|
|
239
227
|
}
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
dbQuery.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
this.andWhereRaw(
|
|
254
|
-
"value like ?",
|
|
255
|
-
`%${normalizedFullTextFilterTerm.toLocaleLowerCase("en-US")}%`
|
|
228
|
+
const isOrderingDescending = sortField?.order === "desc";
|
|
229
|
+
if (cursor.orderFieldValues) {
|
|
230
|
+
if (cursor.orderFieldValues.length === 2) {
|
|
231
|
+
const [first, second] = cursor.orderFieldValues;
|
|
232
|
+
dbQuery.andWhere(function nested() {
|
|
233
|
+
this.where(
|
|
234
|
+
"filtered.value",
|
|
235
|
+
isFetchingBackwards !== isOrderingDescending ? "<" : ">",
|
|
236
|
+
first
|
|
237
|
+
).orWhere("filtered.value", "=", first).andWhere(
|
|
238
|
+
"filtered.entity_id",
|
|
239
|
+
isFetchingBackwards !== isOrderingDescending ? "<" : ">",
|
|
240
|
+
second
|
|
256
241
|
);
|
|
257
242
|
});
|
|
258
|
-
|
|
243
|
+
} else if (cursor.orderFieldValues.length === 1) {
|
|
244
|
+
const [first] = cursor.orderFieldValues;
|
|
245
|
+
dbQuery.andWhere("entity_id", isFetchingBackwards ? "<" : ">", first);
|
|
259
246
|
}
|
|
260
247
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
dbQuery.andWhere(function nested() {
|
|
265
|
-
this.where(
|
|
266
|
-
"value",
|
|
267
|
-
isFetchingBackwards !== isOrderingDescending ? "<" : ">",
|
|
268
|
-
prevItemOrderFieldValue
|
|
269
|
-
).orWhere("value", "=", prevItemOrderFieldValue).andWhere(
|
|
270
|
-
"final_entities.entity_id",
|
|
271
|
-
isFetchingBackwards !== isOrderingDescending ? "<" : ">",
|
|
272
|
-
prevItemUid
|
|
273
|
-
);
|
|
274
|
-
});
|
|
248
|
+
let order = sortField?.order ?? "asc";
|
|
249
|
+
if (isFetchingBackwards) {
|
|
250
|
+
order = invertOrder(order);
|
|
275
251
|
}
|
|
276
|
-
if (
|
|
252
|
+
if (this.database.client.config.client === "pg") {
|
|
277
253
|
dbQuery.orderBy([
|
|
254
|
+
...sortField ? [
|
|
255
|
+
{
|
|
256
|
+
column: "filtered.value",
|
|
257
|
+
order,
|
|
258
|
+
nulls: "last"
|
|
259
|
+
}
|
|
260
|
+
] : [],
|
|
278
261
|
{
|
|
279
|
-
column: "
|
|
280
|
-
order
|
|
281
|
-
nulls: "last"
|
|
282
|
-
},
|
|
283
|
-
{
|
|
284
|
-
column: "final_entities.entity_id",
|
|
285
|
-
order: isFetchingBackwards ? invertOrder(sortField.order) : sortField.order
|
|
262
|
+
column: "filtered.entity_id",
|
|
263
|
+
order
|
|
286
264
|
}
|
|
287
265
|
]);
|
|
288
266
|
} else {
|
|
289
267
|
dbQuery.orderBy([
|
|
268
|
+
...sortField ? [
|
|
269
|
+
{
|
|
270
|
+
column: "filtered.value",
|
|
271
|
+
order: void 0,
|
|
272
|
+
nulls: "last"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
column: "filtered.value",
|
|
276
|
+
order
|
|
277
|
+
}
|
|
278
|
+
] : [],
|
|
290
279
|
{
|
|
291
|
-
column: "
|
|
292
|
-
order
|
|
293
|
-
nulls: "last"
|
|
294
|
-
},
|
|
295
|
-
{
|
|
296
|
-
column: "search.value",
|
|
297
|
-
order: isFetchingBackwards ? invertOrder(sortField.order) : sortField.order
|
|
298
|
-
},
|
|
299
|
-
{
|
|
300
|
-
column: "final_entities.entity_id",
|
|
301
|
-
order: isFetchingBackwards ? invertOrder(sortField.order) : sortField.order
|
|
280
|
+
column: "filtered.entity_id",
|
|
281
|
+
order
|
|
302
282
|
}
|
|
303
283
|
]);
|
|
304
284
|
}
|
|
@@ -306,16 +286,17 @@ class DefaultEntitiesCatalog {
|
|
|
306
286
|
dbQuery.offset(request.offset);
|
|
307
287
|
}
|
|
308
288
|
dbQuery.limit(isFetchingBackwards ? limit : limit + 1);
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
289
|
+
const rows = shouldComputeTotalItems || limit > 0 ? await dbQuery : [];
|
|
290
|
+
let totalItems;
|
|
291
|
+
if (cursor.totalItems !== void 0) {
|
|
292
|
+
totalItems = cursor.totalItems;
|
|
293
|
+
} else if (cursor.skipTotalItems) {
|
|
294
|
+
totalItems = 0;
|
|
295
|
+
} else if (rows.length) {
|
|
296
|
+
totalItems = Number(rows[0].count);
|
|
297
|
+
} else {
|
|
298
|
+
totalItems = 0;
|
|
299
|
+
}
|
|
319
300
|
if (isFetchingBackwards) {
|
|
320
301
|
rows.reverse();
|
|
321
302
|
}
|
|
@@ -326,20 +307,20 @@ class DefaultEntitiesCatalog {
|
|
|
326
307
|
const isInitialRequest = cursor.firstSortFieldValues === void 0;
|
|
327
308
|
const firstRow = rows[0];
|
|
328
309
|
const lastRow = rows[rows.length - 1];
|
|
329
|
-
const firstSortFieldValues = cursor.firstSortFieldValues ||
|
|
330
|
-
firstRow?.value,
|
|
331
|
-
firstRow?.entity_id
|
|
332
|
-
];
|
|
310
|
+
const firstSortFieldValues = cursor.firstSortFieldValues || sortFieldsFromRow(firstRow, sortField);
|
|
333
311
|
const nextCursor = hasMoreResults ? {
|
|
334
312
|
...cursor,
|
|
335
|
-
orderFieldValues: sortFieldsFromRow(lastRow),
|
|
313
|
+
orderFieldValues: sortFieldsFromRow(lastRow, sortField),
|
|
336
314
|
firstSortFieldValues,
|
|
337
315
|
isPrevious: false,
|
|
338
316
|
totalItems
|
|
339
317
|
} : void 0;
|
|
340
|
-
const prevCursor = !isInitialRequest && rows.length > 0 && !lodash.isEqual(
|
|
318
|
+
const prevCursor = !isInitialRequest && rows.length > 0 && !lodash.isEqual(
|
|
319
|
+
sortFieldsFromRow(firstRow, sortField),
|
|
320
|
+
cursor.firstSortFieldValues
|
|
321
|
+
) ? {
|
|
341
322
|
...cursor,
|
|
342
|
-
orderFieldValues: sortFieldsFromRow(firstRow),
|
|
323
|
+
orderFieldValues: sortFieldsFromRow(firstRow, sortField),
|
|
343
324
|
firstSortFieldValues: cursor.firstSortFieldValues,
|
|
344
325
|
isPrevious: true,
|
|
345
326
|
totalItems
|
|
@@ -438,17 +419,29 @@ class DefaultEntitiesCatalog {
|
|
|
438
419
|
};
|
|
439
420
|
}
|
|
440
421
|
async facets(request) {
|
|
422
|
+
const query = this.database("search").whereIn(
|
|
423
|
+
"search.key",
|
|
424
|
+
request.facets.map((f) => f.toLocaleLowerCase("en-US"))
|
|
425
|
+
).whereNotNull("search.original_value").select({
|
|
426
|
+
facet: "search.key",
|
|
427
|
+
value: "search.original_value",
|
|
428
|
+
count: this.database.raw("count(*)")
|
|
429
|
+
}).groupBy(["search.key", "search.original_value"]);
|
|
430
|
+
if (request.filter) {
|
|
431
|
+
applyEntityFilterToQuery.applyEntityFilterToQuery({
|
|
432
|
+
filter: request.filter,
|
|
433
|
+
targetQuery: query,
|
|
434
|
+
onEntityIdField: "search.entity_id",
|
|
435
|
+
knex: this.database
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
const rows = await query;
|
|
441
439
|
const facets = {};
|
|
442
|
-
const db = this.database;
|
|
443
440
|
for (const facet of request.facets) {
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const result = await dbQuery;
|
|
449
|
-
facets[facet] = result.map((data) => ({
|
|
450
|
-
value: String(data.value),
|
|
451
|
-
count: Number(data.count)
|
|
441
|
+
const facetLowercase = facet.toLocaleLowerCase("en-US");
|
|
442
|
+
facets[facet] = rows.filter((row) => row.facet === facetLowercase).map((row) => ({
|
|
443
|
+
value: String(row.value),
|
|
444
|
+
count: Number(row.count)
|
|
452
445
|
}));
|
|
453
446
|
}
|
|
454
447
|
return { facets };
|
|
@@ -475,21 +468,28 @@ function parseCursorFromRequest(request) {
|
|
|
475
468
|
if (util.isQueryEntitiesInitialRequest(request)) {
|
|
476
469
|
const {
|
|
477
470
|
filter,
|
|
478
|
-
orderFields: sortFields = [
|
|
479
|
-
fullTextFilter
|
|
471
|
+
orderFields: sortFields = [],
|
|
472
|
+
fullTextFilter,
|
|
473
|
+
skipTotalItems = false
|
|
480
474
|
} = request;
|
|
481
|
-
return { filter, orderFields: sortFields, fullTextFilter };
|
|
475
|
+
return { filter, orderFields: sortFields, fullTextFilter, skipTotalItems };
|
|
482
476
|
}
|
|
483
477
|
if (util.isQueryEntitiesCursorRequest(request)) {
|
|
484
|
-
return
|
|
478
|
+
return {
|
|
479
|
+
...request.cursor,
|
|
480
|
+
// Doesn't matter here
|
|
481
|
+
skipTotalItems: false
|
|
482
|
+
};
|
|
485
483
|
}
|
|
486
|
-
return {
|
|
484
|
+
return {
|
|
485
|
+
skipTotalItems: false
|
|
486
|
+
};
|
|
487
487
|
}
|
|
488
488
|
function invertOrder(order) {
|
|
489
489
|
return order === "asc" ? "desc" : "asc";
|
|
490
490
|
}
|
|
491
|
-
function sortFieldsFromRow(row) {
|
|
492
|
-
return [row
|
|
491
|
+
function sortFieldsFromRow(row, sortField) {
|
|
492
|
+
return sortField ? [row?.value, row?.entity_id] : [row?.entity_id];
|
|
493
493
|
}
|
|
494
494
|
|
|
495
495
|
exports.DefaultEntitiesCatalog = DefaultEntitiesCatalog;
|