@@ -177,51 +177,68 @@ func (ff *FlatFilter) ToDefaultFilter() *FlatFilter {
177177 return defaultFilter
178178}
179179
180+ // MatchingFilter returns the filter the event belongs to, the charge's default bucket when none
181+ // matches.
182+ //
183+ // NOTE: This must return the same filter as the Ruby ChargeFilters::EventMatchingService, which
184+ // does the same job for organizations on the Postgres events store. Both the usage cache key and
185+ // the enriched event are built from it, so picking another one leaves the usage cache expiring a
186+ // key the usage reader never wrote to.
180187func MatchingFilter (filters []FlatFilter , event * EnrichedEvent ) * FlatFilter {
181- // Multiple filters are present, identify the best match
182- if len (filters ) > 1 {
183- // First select all matching filters
184- matchingFilters := make ([]FlatFilter , 0 )
185- for _ , filter := range filters {
186- if filter .HasFilters () && filter .IsMatchingEvent (event ).Value () {
187- matchingFilters = append (matchingFilters , filter )
188- }
188+ var bestFilter * FlatFilter
189+
190+ for i := range filters {
191+ filter := & filters [i ]
192+ if ! filter .HasFilters () || ! filter .IsMatchingEvent (event ).Value () {
193+ continue
189194 }
190195
191- // No filters matches the event
192- if len (matchingFilters ) == 0 {
193- // Return the charge's default bucket
194- return filters [0 ].ToDefaultFilter ()
195-
196- } else {
197- // NOTE: Multiple filters match the event (parent/child filters),
198- // We must take only the one matching the most properties
199- var bestFilter * FlatFilter
200- for _ , filter := range matchingFilters {
201- if bestFilter == nil {
202- bestFilter = & filter
203- continue
204- }
205-
206- if len (filter .Filters .Keys ()) > len (bestFilter .Filters .Keys ()) {
207- bestFilter = & filter
208- }
209- }
210-
211- // Return the best match
212- return bestFilter
196+ if bestFilter == nil || filter .isBetterMatchThan (bestFilter ) {
197+ bestFilter = filter
213198 }
199+ }
214200
215- } else {
216- filter := filters [0 ]
201+ // No filter matches the event, it falls into the charge's default bucket
202+ if bestFilter == nil {
203+ return filters [0 ].ToDefaultFilter ()
204+ }
217205
218- // Check if the only filter is matching the event
219- if filter .HasFilters () && filter .IsMatchingEvent (event ).Value () {
220- // Return the only matching filter
221- return & filter
222- } else {
223- // Otherwise, return the charge's default bucket
224- return filter .ToDefaultFilter ()
225- }
206+ return bestFilter
207+ }
208+
209+ // isBetterMatchThan reports whether ff must be preferred over other: it matches more of the event
210+ // properties, or as many but is older.
211+ //
212+ // NOTE: Ruby takes the first filter matching the most properties out of charge.filters, ordered by
213+ // the ChargeFilter default scope (order(updated_at: :asc)), hence the tie-break on
214+ // ChargeFilterUpdatedAt. The one on ChargeFilterID has no Ruby counterpart (Postgres resolves an
215+ // ORDER BY tie arbitrarily), it only keeps us deterministic when two filters share a timestamp,
216+ // rather than falling back to the order the filters were read in.
217+ func (ff * FlatFilter ) isBetterMatchThan (other * FlatFilter ) bool {
218+ if keys , otherKeys := len (ff .Filters .Keys ()), len (other .Filters .Keys ()); keys != otherKeys {
219+ return keys > otherKeys
220+ }
221+
222+ updatedAt , otherUpdatedAt := ff .chargeFilterUpdatedAt (), other .chargeFilterUpdatedAt ()
223+ if ! updatedAt .Equal (otherUpdatedAt ) {
224+ return updatedAt .Before (otherUpdatedAt )
226225 }
226+
227+ return ff .chargeFilterID () < other .chargeFilterID ()
228+ }
229+
230+ func (ff * FlatFilter ) chargeFilterUpdatedAt () time.Time {
231+ if ff .ChargeFilterUpdatedAt == nil {
232+ return time.Time {}
233+ }
234+
235+ return * ff .ChargeFilterUpdatedAt
236+ }
237+
238+ func (ff * FlatFilter ) chargeFilterID () string {
239+ if ff .ChargeFilterID == nil {
240+ return ""
241+ }
242+
243+ return * ff .ChargeFilterID
227244}
0 commit comments