diff --git a/plugins/catalog-backend/src/database/CommonDatabase.ts b/plugins/catalog-backend/src/database/CommonDatabase.ts index 563fd24c80..dca5072b30 100644 --- a/plugins/catalog-backend/src/database/CommonDatabase.ts +++ b/plugins/catalog-backend/src/database/CommonDatabase.ts @@ -219,64 +219,62 @@ export class CommonDatabase implements Database { let entitiesQuery = tx('entities'); - if (filters && Object.keys(filters).length) { - for (const [matchKey, matchVal] of Object.entries(filters)) { - const key = matchKey.toLowerCase().replace(/[*]/g, '%'); - const keyOp = key.includes('*') ? 'like' : '='; - const values = Array.isArray(matchVal) ? matchVal : [matchVal]; + for (const [matchKey, matchVal] of Object.entries(filters ?? {})) { + const key = matchKey.toLowerCase().replace(/[*]/g, '%'); + const keyOp = key.includes('%') ? 'like' : '='; + const values = Array.isArray(matchVal) ? matchVal : [matchVal]; - let matchNulls = false; - const matchIn: string[] = []; - const matchLike: string[] = []; + let matchNulls = false; + const matchIn: string[] = []; + const matchLike: string[] = []; - for (const value of values) { - if (!value) { - matchNulls = true; - } else if (value.includes('*')) { - matchLike.push(value.toLowerCase().replace(/[*]/g, '%')); - } else { - matchIn.push(value.toLowerCase()); - } + for (const value of values) { + if (!value) { + matchNulls = true; + } else if (value.includes('*')) { + matchLike.push(value.toLowerCase().replace(/[*]/g, '%')); + } else { + matchIn.push(value.toLowerCase()); } - - // NOTE(freben): This used to be a set of OUTER JOIN, which may seem to - // make a lot of sense. However, it had abysmal performance on sqlite - // when datasets grew large, so we're using IN instead. - const matchQuery = tx('entities_search') - .select('entity_id') - .where(function keyFilter() { - this.andWhere('key', keyOp, key); - this.andWhere(function valueFilter() { - if (matchIn.length === 1) { - this.orWhere({ value: matchIn[0] }); - } else if (matchIn.length > 1) { - this.orWhereIn('value', matchIn); - } - if (matchLike.length) { - for (const x of matchLike) { - this.orWhere('value', 'like', tx.raw('?', [x])); - } - } - if (matchNulls) { - // Match explicit nulls, and then handle absence separately below - this.orWhereNull('value'); - } - }); - }); - - // Handle absence as nulls as well - entitiesQuery = entitiesQuery.andWhere(function match() { - this.whereIn('id', matchQuery); - if (matchNulls) { - this.orWhereNotIn( - 'id', - tx('entities_search') - .select('entity_id') - .where('key', keyOp, key), - ); - } - }); } + + // NOTE(freben): This used to be a set of OUTER JOIN, which may seem to + // make a lot of sense. However, it had abysmal performance on sqlite + // when datasets grew large, so we're using IN instead. + const matchQuery = tx('entities_search') + .select('entity_id') + .where(function keyFilter() { + this.andWhere('key', keyOp, key); + this.andWhere(function valueFilter() { + if (matchIn.length === 1) { + this.orWhere({ value: matchIn[0] }); + } else if (matchIn.length > 1) { + this.orWhereIn('value', matchIn); + } + if (matchLike.length) { + for (const x of matchLike) { + this.orWhere('value', 'like', tx.raw('?', [x])); + } + } + if (matchNulls) { + // Match explicit nulls, and then handle absence separately below + this.orWhereNull('value'); + } + }); + }); + + // Handle absence as nulls as well + entitiesQuery = entitiesQuery.andWhere(function match() { + this.whereIn('id', matchQuery); + if (matchNulls) { + this.orWhereNotIn( + 'id', + tx('entities_search') + .select('entity_id') + .where('key', keyOp, key), + ); + } + }); } const rows = await entitiesQuery diff --git a/plugins/catalog-backend/src/service/router.ts b/plugins/catalog-backend/src/service/router.ts index 94dc76ab9d..a262e9b5f4 100644 --- a/plugins/catalog-backend/src/service/router.ts +++ b/plugins/catalog-backend/src/service/router.ts @@ -123,7 +123,7 @@ export async function createRouter( function translateQueryToEntityFilters( request: express.Request, ): EntityFilters { - const filters: EntityFilters = {}; + const filters: Record = {}; for (const [key, valueOrValues] of Object.entries(request.query)) { const values = Array.isArray(valueOrValues) @@ -134,12 +134,7 @@ function translateQueryToEntityFilters( throw new InputError('Complex query parameters are not supported'); } - // This one always emits arrays - let matchers = filters[key] as (string | null)[]; - if (!matchers) { - matchers = []; - filters[key] = matchers; - } + const matchers = key in filters ? filters[key] : (filters[key] = []); matchers.push(...(values.map(v => v || null) as (string | null)[])); }