@@ -360,6 +360,14 @@ func (impl AppListingRepositoryQueryBuilder) buildTagFiltersWhereConditionOR(tag
360360 return " and (" + strings .Join (clauses , " OR " ) + ") " , queryParams
361361}
362362
363+ // buildTagFilterPredicate converts one UI tag filter row into a SQL predicate.
364+ // Operator behavior (all case-sensitive):
365+ // - EQUALS: key exists with exact value match.
366+ // - DOES_NOT_EQUAL: key exists with at least one value different from target.
367+ // - CONTAINS: key exists with at least one value containing target substring.
368+ // - DOES_NOT_CONTAIN: key exists with at least one value not containing target substring.
369+ // - EXISTS: key exists.
370+ // - DOES_NOT_EXIST: key does not exist.
363371func (impl AppListingRepositoryQueryBuilder ) buildTagFilterPredicate (tagFilter TagFilter ) (string , []interface {}) {
364372 value := ""
365373 if tagFilter .Value != nil {
@@ -370,15 +378,17 @@ func (impl AppListingRepositoryQueryBuilder) buildTagFilterPredicate(tagFilter T
370378 return "EXISTS (SELECT 1 FROM app_label al WHERE al.app_id = a.id and al.key = ? and al.value = ?)" ,
371379 []interface {}{tagFilter .Key , value }
372380 case TagFilterOperatorDoesNotEqual :
373- // NOT EXISTS intentionally includes apps where the key is missing.
374- return "NOT EXISTS (SELECT 1 FROM app_label al WHERE al.app_id = a.id and al.key = ? and al.value = ?)" ,
381+ // Best-practice semantics for multi-value keys:
382+ // include app when key exists and at least one value is different from target.
383+ return "EXISTS (SELECT 1 FROM app_label al WHERE al.app_id = a.id and al.key = ? and al.value <> ?)" ,
375384 []interface {}{tagFilter .Key , value }
376385 case TagFilterOperatorContains :
377386 return "EXISTS (SELECT 1 FROM app_label al WHERE al.app_id = a.id and al.key = ? and al.value LIKE ? ESCAPE '\\ ')" ,
378387 []interface {}{tagFilter .Key , buildContainsPattern (value )}
379388 case TagFilterOperatorDoesNotContain :
380- // NOT EXISTS intentionally includes apps where the key is missing.
381- return "NOT EXISTS (SELECT 1 FROM app_label al WHERE al.app_id = a.id and al.key = ? and al.value LIKE ? ESCAPE '\\ ')" ,
389+ // Best-practice semantics for multi-value keys:
390+ // include app when key exists and at least one value does not contain target.
391+ return "EXISTS (SELECT 1 FROM app_label al WHERE al.app_id = a.id and al.key = ? and al.value NOT LIKE ? ESCAPE '\\ ')" ,
382392 []interface {}{tagFilter .Key , buildContainsPattern (value )}
383393 case TagFilterOperatorExists :
384394 return "EXISTS (SELECT 1 FROM app_label al WHERE al.app_id = a.id and al.key = ?)" ,
0 commit comments