1package entity
2
3import (
4 "strings"
5)
6
7// CombineIds compute a merged Id holding information from both the primary Id
8// and the secondary Id.
9//
10// This allow to later find efficiently a secondary element because we can access
11// the primary one directly instead of searching for a primary that has a
12// secondary matching the Id.
13//
14// An example usage is Comment in a Bug. The interleaved Id will hold part of the
15// Bug Id and part of the Comment Id.
16//
17// To allow the use of an arbitrary length prefix of this Id, Ids from primary
18// and secondary are interleaved with this irregular pattern to give the
19// best chance to find the secondary even with a 7 character prefix.
20//
21// Format is: PSPSPSPPPSPPPPSPPPPSPPPPSPPPPSPPPPSPPPPSPPPPSPPPPSPPPPSPPPPSPPPP
22//
23// A complete interleaved Id hold 50 characters for the primary and 14 for the
24// secondary, which give a key space of 36^50 for the primary (~6 * 10^77) and
25// 36^14 for the secondary (~6 * 10^21). This asymmetry assume a reasonable number
26// of secondary within a primary Entity, while still allowing for a vast key space
27// for the primary (that is, a globally merged database) with a low risk of collision.
28//
29// Here is the breakdown of several common prefix length:
30//
31// 5: 3P, 2S
32// 7: 4P, 3S
33// 10: 6P, 4S
34// 16: 11P, 5S
35func CombineIds(primary Id, secondary Id) Id {
36 var id strings.Builder
37
38 for i := 0; i < idLength; i++ {
39 switch {
40 default:
41 id.WriteByte(primary[0])
42 primary = primary[1:]
43 case i == 1, i == 3, i == 5, i == 9, i >= 10 && i%5 == 4:
44 id.WriteByte(secondary[0])
45 secondary = secondary[1:]
46 }
47 }
48
49 return Id(id.String())
50}
51
52// SeparateIds extract primary and secondary prefix from an arbitrary length prefix
53// of an Id created with CombineIds.
54func SeparateIds(prefix string) (primaryPrefix string, secondaryPrefix string) {
55 var primary strings.Builder
56 var secondary strings.Builder
57
58 for i, r := range prefix {
59 switch {
60 default:
61 primary.WriteRune(r)
62 case i == 1, i == 3, i == 5, i == 9, i >= 10 && i%5 == 4:
63 secondary.WriteRune(r)
64 }
65 }
66
67 return primary.String(), secondary.String()
68}