Add CookiePatternFilter to push adoption filtering to SQL
The adoptUncategorisedPatterns method loaded all patterns for a banner then filtered in Go. This adds a CookiePatternFilter (match_type + cookie_category_id) and wires it into LoadAllByCookieBannerID so the two targeted loads only fetch the rows they need. Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -531,7 +531,7 @@ func (s *Service) ensureDraftVersionForBanner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var allPatterns coredata.CookiePatterns
|
var allPatterns coredata.CookiePatterns
|
||||||
if err := allPatterns.LoadAllByCookieBannerID(ctx, tx, scope, bannerID); err != nil {
|
if err := allPatterns.LoadAllByCookieBannerID(ctx, tx, scope, bannerID, nil); err != nil {
|
||||||
return nil, fmt.Errorf("cannot load cookie patterns: %w", err)
|
return nil, fmt.Errorf("cannot load cookie patterns: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.gearno.de/kit/log"
|
"go.gearno.de/kit/log"
|
||||||
@@ -85,7 +86,7 @@ func (h *patternAnalysisHandler) Process(ctx context.Context, banner coredata.Co
|
|||||||
scope := coredata.NewScopeFromObjectID(banner.ID)
|
scope := coredata.NewScopeFromObjectID(banner.ID)
|
||||||
|
|
||||||
var patterns coredata.CookiePatterns
|
var patterns coredata.CookiePatterns
|
||||||
if err := patterns.LoadAllByCookieBannerID(ctx, tx, scope, banner.ID); err != nil {
|
if err := patterns.LoadAllByCookieBannerID(ctx, tx, scope, banner.ID, nil); err != nil {
|
||||||
return fmt.Errorf("cannot load patterns: %w", err)
|
return fmt.Errorf("cannot load patterns: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +148,14 @@ func (h *patternAnalysisHandler) Process(ctx context.Context, banner coredata.Co
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
adopted, err := h.adoptUncategorisedPatterns(ctx, tx, scope, banner)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot adopt uncategorised patterns: %w", err)
|
||||||
|
}
|
||||||
|
if adopted {
|
||||||
|
merged = true
|
||||||
|
}
|
||||||
|
|
||||||
if merged {
|
if merged {
|
||||||
if _, err := h.svc.ensureDraftVersionForBanner(ctx, tx, scope, banner.ID); err != nil {
|
if _, err := h.svc.ensureDraftVersionForBanner(ctx, tx, scope, banner.ID); err != nil {
|
||||||
return fmt.Errorf("cannot ensure draft version: %w", err)
|
return fmt.Errorf("cannot ensure draft version: %w", err)
|
||||||
@@ -273,3 +282,85 @@ func mostCommonMaxAge(patterns []*coredata.CookiePattern) *int {
|
|||||||
v := entries[0].k.val
|
v := entries[0].k.val
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *patternAnalysisHandler) adoptUncategorisedPatterns(
|
||||||
|
ctx context.Context,
|
||||||
|
tx pg.Tx,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
banner coredata.CookieBanner,
|
||||||
|
) (bool, error) {
|
||||||
|
var uncategorised coredata.CookieCategory
|
||||||
|
if err := uncategorised.LoadUncategorisedByCookieBannerID(ctx, tx, scope, banner.ID); err != nil {
|
||||||
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("cannot load uncategorised category: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixMatchType := coredata.CookiePatternMatchTypePrefix
|
||||||
|
var prefixPatterns coredata.CookiePatterns
|
||||||
|
if err := prefixPatterns.LoadAllByCookieBannerID(
|
||||||
|
ctx,
|
||||||
|
tx,
|
||||||
|
scope,
|
||||||
|
banner.ID,
|
||||||
|
coredata.NewCookiePatternFilter(&prefixMatchType, nil),
|
||||||
|
); err != nil {
|
||||||
|
return false, fmt.Errorf("cannot load prefix patterns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(prefixPatterns) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(prefixPatterns, func(i, j int) bool {
|
||||||
|
return len(prefixPatterns[i].Pattern) > len(prefixPatterns[j].Pattern)
|
||||||
|
})
|
||||||
|
|
||||||
|
exactMatchType := coredata.CookiePatternMatchTypeExact
|
||||||
|
var uncategorisedExact coredata.CookiePatterns
|
||||||
|
if err := uncategorisedExact.LoadAllByCookieBannerID(
|
||||||
|
ctx,
|
||||||
|
tx,
|
||||||
|
scope,
|
||||||
|
banner.ID,
|
||||||
|
coredata.NewCookiePatternFilter(&exactMatchType, &uncategorised.ID),
|
||||||
|
); err != nil {
|
||||||
|
return false, fmt.Errorf("cannot load uncategorised exact patterns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
adopted := false
|
||||||
|
for _, ep := range uncategorisedExact {
|
||||||
|
var match *coredata.CookiePattern
|
||||||
|
for _, pp := range prefixPatterns {
|
||||||
|
if strings.HasPrefix(ep.Pattern, pp.Pattern) {
|
||||||
|
match = pp
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if match == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var cookies coredata.Cookies
|
||||||
|
if err := cookies.RelinkByCookiePatternID(ctx, tx, scope, ep.ID, match.ID); err != nil {
|
||||||
|
return false, fmt.Errorf("cannot relink cookies from pattern %q: %w", ep.Pattern, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ep.Delete(ctx, tx, scope); err != nil {
|
||||||
|
return false, fmt.Errorf("cannot delete adopted exact pattern %q: %w", ep.Pattern, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
adopted = true
|
||||||
|
h.logger.InfoCtx(
|
||||||
|
ctx,
|
||||||
|
"adopted uncategorised exact pattern into prefix pattern",
|
||||||
|
log.String("exact_pattern", ep.Pattern),
|
||||||
|
log.String("prefix_pattern", match.Pattern),
|
||||||
|
log.String("banner_id", banner.ID.String()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adopted, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -331,6 +331,7 @@ func (cps *CookiePatterns) LoadAllByCookieBannerID(
|
|||||||
conn pg.Querier,
|
conn pg.Querier,
|
||||||
scope Scoper,
|
scope Scoper,
|
||||||
cookieBannerID gid.GID,
|
cookieBannerID gid.GID,
|
||||||
|
filter *CookiePatternFilter,
|
||||||
) error {
|
) error {
|
||||||
q := `
|
q := `
|
||||||
SELECT
|
SELECT
|
||||||
@@ -351,14 +352,16 @@ FROM
|
|||||||
WHERE
|
WHERE
|
||||||
%s
|
%s
|
||||||
AND cookie_banner_id = @cookie_banner_id
|
AND cookie_banner_id = @cookie_banner_id
|
||||||
|
AND %s
|
||||||
ORDER BY
|
ORDER BY
|
||||||
created_at ASC, id ASC;
|
created_at ASC, id ASC;
|
||||||
`
|
`
|
||||||
|
|
||||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
|
||||||
|
|
||||||
args := pgx.StrictNamedArgs{"cookie_banner_id": cookieBannerID}
|
args := pgx.StrictNamedArgs{"cookie_banner_id": cookieBannerID}
|
||||||
maps.Copy(args, scope.SQLArguments())
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
maps.Copy(args, filter.SQLArguments())
|
||||||
|
|
||||||
rows, err := conn.Query(ctx, q, args)
|
rows, err := conn.Query(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
83
pkg/coredata/cookie_pattern_filter.go
Normal file
83
pkg/coredata/cookie_pattern_filter.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package coredata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CookiePatternFilter struct {
|
||||||
|
matchType *CookiePatternMatchType
|
||||||
|
cookieCategoryID *gid.GID
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCookiePatternFilter(
|
||||||
|
matchType *CookiePatternMatchType,
|
||||||
|
cookieCategoryID *gid.GID,
|
||||||
|
) *CookiePatternFilter {
|
||||||
|
return &CookiePatternFilter{
|
||||||
|
matchType: matchType,
|
||||||
|
cookieCategoryID: cookieCategoryID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *CookiePatternFilter) SQLFragment() string {
|
||||||
|
if f == nil {
|
||||||
|
return "TRUE"
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
(
|
||||||
|
CASE
|
||||||
|
WHEN @has_match_type_filter::boolean = false THEN TRUE
|
||||||
|
WHEN @has_match_type_filter::boolean = true THEN
|
||||||
|
match_type = @filter_match_type::cookie_pattern_match_type
|
||||||
|
ELSE TRUE
|
||||||
|
END
|
||||||
|
AND
|
||||||
|
CASE
|
||||||
|
WHEN @has_cookie_category_id_filter::boolean = false THEN TRUE
|
||||||
|
WHEN @has_cookie_category_id_filter::boolean = true THEN
|
||||||
|
cookie_category_id = @filter_cookie_category_id::text
|
||||||
|
ELSE TRUE
|
||||||
|
END
|
||||||
|
)`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *CookiePatternFilter) SQLArguments() pgx.StrictNamedArgs {
|
||||||
|
if f == nil {
|
||||||
|
return pgx.StrictNamedArgs{}
|
||||||
|
}
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"has_match_type_filter": false,
|
||||||
|
"filter_match_type": nil,
|
||||||
|
"has_cookie_category_id_filter": false,
|
||||||
|
"filter_cookie_category_id": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.matchType != nil {
|
||||||
|
args["has_match_type_filter"] = true
|
||||||
|
args["filter_match_type"] = string(*f.matchType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.cookieCategoryID != nil {
|
||||||
|
args["has_cookie_category_id_filter"] = true
|
||||||
|
args["filter_cookie_category_id"] = *f.cookieCategoryID
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user