arrow functions and simplify

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2026-02-20 11:30:45 +01:00
parent fa4e24d752
commit b218fb07df
2 changed files with 141 additions and 142 deletions
@@ -116,6 +116,16 @@ describe.each(databases.eachSupportedId())(
);
}
it('matches everything for empty field expression', async () => {
await expect(query({})).resolves.toEqual([
'api-d',
'bare-e',
'service-a',
'service-b',
'website-c',
]);
});
it('filters by direct field value', async () => {
await expect(query({ kind: 'component' })).resolves.toEqual([
'bare-e',
@@ -140,6 +150,10 @@ describe.each(databases.eachSupportedId())(
).resolves.toEqual(['service-a', 'service-b']);
});
it('returns nothing for empty $all', async () => {
await expect(query({ $all: [] })).resolves.toEqual([]);
});
it('filters with $any', async () => {
await expect(
query({
@@ -148,6 +162,10 @@ describe.each(databases.eachSupportedId())(
).resolves.toEqual(['service-a', 'service-b', 'website-c']);
});
it('returns nothing for empty $any', async () => {
await expect(query({ $any: [] })).resolves.toEqual([]);
});
it('filters with $not', async () => {
await expect(
query({
@@ -23,24 +23,6 @@ import { InputError } from '@backstage/errors';
import { Knex } from 'knex';
import { DbSearchRow } from '../../database/tables';
function isAllPredicate(
filter: FilterPredicate,
): filter is { $all: FilterPredicate[] } {
return typeof filter === 'object' && filter !== null && '$all' in filter;
}
function isAnyPredicate(
filter: FilterPredicate,
): filter is { $any: FilterPredicate[] } {
return typeof filter === 'object' && filter !== null && '$any' in filter;
}
function isNotPredicate(
filter: FilterPredicate,
): filter is { $not: FilterPredicate } {
return typeof filter === 'object' && filter !== null && '$not' in filter;
}
function isPrimitive(value: unknown): value is FilterPredicatePrimitive {
return (
typeof value === 'string' ||
@@ -49,30 +31,8 @@ function isPrimitive(value: unknown): value is FilterPredicatePrimitive {
);
}
function isExistsValue(
value: FilterPredicateValue,
): value is { $exists: boolean } {
return typeof value === 'object' && value !== null && '$exists' in value;
}
function isInValue(
value: FilterPredicateValue,
): value is { $in: FilterPredicatePrimitive[] } {
return typeof value === 'object' && value !== null && '$in' in value;
}
function isHasPrefixValue(
value: FilterPredicateValue,
): value is { $hasPrefix: string } {
return typeof value === 'object' && value !== null && '$hasPrefix' in value;
}
function isFieldExpression(filter: FilterPredicate): boolean {
if (typeof filter !== 'object' || filter === null) {
return false;
}
// Not a logical operator
return !('$all' in filter || '$any' in filter || '$not' in filter);
function isObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
/**
@@ -114,7 +74,6 @@ function isFieldExpression(filter: FilterPredicate): boolean {
* ORDER BY final_entities.entity_ref ASC;
* ```
*/
export function applyPredicateEntityFilterToQuery(options: {
filter: FilterPredicate;
targetQuery: Knex.QueryBuilder;
@@ -122,53 +81,9 @@ export function applyPredicateEntityFilterToQuery(options: {
knex: Knex;
}): Knex.QueryBuilder {
const { filter, targetQuery, onEntityIdField, knex } = options;
// Handle $not
if (isNotPredicate(filter)) {
return targetQuery.andWhereNot(subQuery =>
applyPredicateEntityFilterToQuery({
filter: filter.$not,
targetQuery: subQuery,
onEntityIdField,
knex,
}),
);
}
// Handle $all (AND)
if (isAllPredicate(filter)) {
return targetQuery.andWhere(function allFilter() {
for (const subFilter of filter.$all) {
this.andWhere(subQuery =>
applyPredicateEntityFilterToQuery({
filter: subFilter,
targetQuery: subQuery,
onEntityIdField,
knex,
}),
);
}
});
}
// Handle $any (OR)
if (isAnyPredicate(filter)) {
return targetQuery.andWhere(function anyFilter() {
for (const subFilter of filter.$any) {
this.orWhere(subQuery =>
applyPredicateEntityFilterToQuery({
filter: subFilter,
targetQuery: subQuery,
onEntityIdField,
knex,
}),
);
}
});
}
// Reject primitives at the top level. Matching by value without specifying
// a field key is ambiguous and should not be allowed.
if (isPrimitive(filter)) {
// We do not support top-level primitives; all matching happens through objects
if (!filter || typeof filter !== 'object' || Array.isArray(filter)) {
throw new InputError(
`Invalid filter predicate: top-level primitive values are not supported. ` +
`Wrap the value in a field expression, e.g. { "kind": ${JSON.stringify(
@@ -177,62 +92,128 @@ export function applyPredicateEntityFilterToQuery(options: {
);
}
// Handle field expressions like { "kind": "component" } or { "spec.type": { "$in": ["service", "website"] } }
if (isFieldExpression(filter)) {
return targetQuery.andWhere(function fieldFilter() {
for (const [key, value] of Object.entries(filter)) {
const normalizedKey = key.toLocaleLowerCase('en-US');
if ('$not' in filter) {
return targetQuery.andWhereNot(inner =>
applyPredicateEntityFilterToQuery({
filter: filter.$not,
targetQuery: inner,
onEntityIdField,
knex,
}),
);
}
if (isExistsValue(value)) {
// Handle $exists
const existsQuery = knex<DbSearchRow>('search')
.select('search.entity_id')
.where({ key: normalizedKey });
if (value.$exists) {
this.andWhere(onEntityIdField, 'in', existsQuery);
} else {
this.andWhere(onEntityIdField, 'not in', existsQuery);
}
} else if (isInValue(value)) {
// Handle $in
const values = value.$in.map(v =>
String(v).toLocaleLowerCase('en-US'),
);
const matchQuery = knex<DbSearchRow>('search')
.select('search.entity_id')
.where({ key: normalizedKey })
.whereIn('value', values);
this.andWhere(onEntityIdField, 'in', matchQuery);
} else if (isHasPrefixValue(value)) {
// Handle $hasPrefix
const prefix = value.$hasPrefix.toLocaleLowerCase('en-US');
const escaped = prefix.replace(/[%_\\]/g, c => `\\${c}`);
const matchQuery = knex<DbSearchRow>('search')
.select('search.entity_id')
.where({ key: normalizedKey })
.andWhereRaw('?? like ? escape ?', ['value', `${escaped}%`, '\\']);
this.andWhere(onEntityIdField, 'in', matchQuery);
} else if (isPrimitive(value)) {
// Handle direct value match
const matchQuery = knex<DbSearchRow>('search')
.select('search.entity_id')
.where({
key: normalizedKey,
value: String(value).toLocaleLowerCase('en-US'),
});
this.andWhere(onEntityIdField, 'in', matchQuery);
} else {
// Reject unsupported/invalid predicate values
throw new InputError(
`Invalid filter predicate value for field "${key}": expected a primitive value, $exists, $in, or $hasPrefix operator, but got ${JSON.stringify(
value,
)}`,
);
}
if ('$all' in filter) {
if (filter.$all.length === 0) {
return targetQuery.andWhereRaw('1 = 0');
}
return targetQuery.andWhere(outer => {
for (const subFilter of filter.$all) {
outer.andWhere(inner =>
applyPredicateEntityFilterToQuery({
filter: subFilter,
targetQuery: inner,
onEntityIdField,
knex,
}),
);
}
});
}
return targetQuery;
if ('$any' in filter) {
if (filter.$any.length === 0) {
return targetQuery.andWhereRaw('1 = 0');
}
return targetQuery.andWhere(outer => {
for (const subFilter of filter.$any) {
outer.orWhere(inner =>
applyPredicateEntityFilterToQuery({
filter: subFilter,
targetQuery: inner,
onEntityIdField,
knex,
}),
);
}
});
}
// Treat the filter as a field expression like { "kind": "component" } or { "spec.type": { "$in": ["service", "website"] } }
if (Object.keys(filter).length === 0) {
return targetQuery;
}
return targetQuery.andWhere(inner => {
for (const [keyAnyCase, value] of Object.entries(filter)) {
applyFieldCondition({
key: keyAnyCase.toLocaleLowerCase('en-US'),
value,
targetQuery: inner,
onEntityIdField,
knex,
});
}
});
}
/**
* Applies a single { key: value } filter to the target query.
*/
function applyFieldCondition(options: {
key: string;
value: FilterPredicateValue;
targetQuery: Knex.QueryBuilder;
onEntityIdField: string;
knex: Knex;
}): Knex.QueryBuilder {
const { key, value, targetQuery, onEntityIdField, knex } = options;
if (isPrimitive(value)) {
const matchQuery = knex<DbSearchRow>('search')
.select('search.entity_id')
.where({
key,
value: String(value).toLocaleLowerCase('en-US'),
});
return targetQuery.andWhere(onEntityIdField, 'in', matchQuery);
}
if (isObject(value)) {
if ('$exists' in value) {
const existsQuery = knex<DbSearchRow>('search')
.select('search.entity_id')
.where({ key });
if (value.$exists) {
return targetQuery.andWhere(onEntityIdField, 'in', existsQuery);
}
return targetQuery.andWhere(onEntityIdField, 'not in', existsQuery);
}
if ('$in' in value) {
const values = (value.$in as FilterPredicatePrimitive[]).map(v =>
String(v).toLocaleLowerCase('en-US'),
);
const matchQuery = knex<DbSearchRow>('search')
.select('search.entity_id')
.where({ key })
.whereIn('value', values);
return targetQuery.andWhere(onEntityIdField, 'in', matchQuery);
}
if ('$hasPrefix' in value) {
const prefix = (value.$hasPrefix as string).toLocaleLowerCase('en-US');
const escaped = prefix.replace(/[%_\\]/g, c => `\\${c}`);
const matchQuery = knex<DbSearchRow>('search')
.select('search.entity_id')
.where({ key })
.andWhereRaw('?? like ? escape ?', ['value', `${escaped}%`, '\\']);
return targetQuery.andWhere(onEntityIdField, 'in', matchQuery);
}
}
throw new InputError(
`Invalid filter predicate value for field "${key}": expected a primitive value, $exists, $in, or $hasPrefix operator, but got ${JSON.stringify(
value,
)}`,
);
}