id_interleaved.go

 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}