Passed
Push — master ( d0e8fc...fcd060 )
by Tolga
01:37
created

snapshot.Token.createFinalSnapshot   F

Complexity

Conditions 17

Size

Total Lines 81
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 45
nop 0
dl 0
loc 81
rs 1.8
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like snapshot.Token.createFinalSnapshot often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
package snapshot
2
3
import (
4
	"encoding/base64"
5
	"encoding/binary"
6
	"fmt"
7
	"slices"
8
	"strconv"
9
	"strings"
10
11
	"github.com/jackc/pgtype"
12
13
	"github.com/Permify/permify/pkg/database/postgres"
14
	"github.com/Permify/permify/pkg/token"
15
)
16
17
type (
18
	// Token - Structure for Token
19
	Token struct {
20
		Value    postgres.XID8
21
		Snapshot string
22
	}
23
	// EncodedToken - Structure for EncodedToken
24
	EncodedToken struct {
25
		Value string
26
	}
27
)
28
29
// NewToken creates a new snapshot token with proper MVCC visibility.
30
// It automatically processes the snapshot to ensure each transaction has a unique snapshot.
31
func NewToken(value postgres.XID8, snapshot string) token.SnapToken {
32
	t := Token{
33
		Value:    value,
34
		Snapshot: snapshot,
35
	}
36
	t.Snapshot = t.createFinalSnapshot()
37
	return t
38
}
39
40
// Encode - Encodes the token to a string
41
func (t Token) Encode() token.EncodedSnapToken {
42
	if t.Snapshot != "" {
43
		// New format: "xid:snapshot" as single base64
44
		combined := fmt.Sprintf("%d:%s", t.Value.Uint, t.Snapshot)
45
		encoded := base64.StdEncoding.EncodeToString([]byte(combined))
46
		return EncodedToken{
47
			Value: encoded,
48
		}
49
	}
50
51
	// Legacy format: binary encoded xid (for backward compatibility)
52
	b := make([]byte, 8)
53
	binary.LittleEndian.PutUint64(b, t.Value.Uint)
54
	valueEncoded := base64.StdEncoding.EncodeToString(b)
55
56
	return EncodedToken{
57
		Value: valueEncoded,
58
	}
59
}
60
61
// Eg snapshot is equal to given snapshot
62
func (t Token) Eg(token token.SnapToken) bool {
63
	ct, ok := token.(Token)
64
	return ok && t.Value.Uint == ct.Value.Uint
65
}
66
67
// Gt snapshot is greater than given snapshot
68
func (t Token) Gt(token token.SnapToken) bool {
69
	ct, ok := token.(Token)
70
	return ok && t.Value.Uint > ct.Value.Uint
71
}
72
73
// Lt snapshot is less than given snapshot
74
func (t Token) Lt(token token.SnapToken) bool {
75
	ct, ok := token.(Token)
76
	return ok && t.Value.Uint < ct.Value.Uint
77
}
78
79
// Decode decodes the token from a string
80
func (t EncodedToken) Decode() (token.SnapToken, error) {
81
	// Decode the base64 string
82
	b, err := base64.StdEncoding.DecodeString(t.Value)
83
	if err != nil {
84
		return nil, err
85
	}
86
87
	// Check if it's legacy binary format (8 bytes)
88
	if len(b) == 8 {
89
		// Legacy format: binary encoded xid
90
		return Token{
91
			Value: postgres.XID8{
92
				Uint:   binary.LittleEndian.Uint64(b),
93
				Status: pgtype.Present,
94
			},
95
			Snapshot: "",
96
		}, nil
97
	}
98
99
	// New format: "xid:snapshot" as string
100
	decodedStr := string(b)
101
	parts := strings.Split(decodedStr, ":")
102
	if len(parts) >= 2 {
103
		// New format: "xid:snapshot"
104
		xidStr := parts[0]
105
		snapshot := strings.Join(parts[1:], ":") // Rejoin in case snapshot contains colons
106
107
		xid, err := strconv.ParseUint(xidStr, 10, 64)
108
		if err != nil {
109
			return nil, err
110
		}
111
112
		return Token{
113
			Value: postgres.XID8{
114
				Uint:   xid,
115
				Status: pgtype.Present,
116
			},
117
			Snapshot: snapshot,
118
		}, nil
119
	}
120
121
	// This should never happen with current formats, but handle gracefully
122
	return nil, fmt.Errorf("invalid token format")
123
}
124
125
// Decode decodes the token from a string
126
func (t EncodedToken) String() string {
127
	return t.Value
128
}
129
130
// createFinalSnapshot creates a final snapshot by adding the current transaction ID to the XIP list.
131
// This ensures each concurrent transaction gets a unique snapshot for proper MVCC visibility.
132
func (t Token) createFinalSnapshot() string {
133
	if t.Snapshot == "" {
134
		return ""
135
	}
136
137
	parts := strings.SplitN(strings.TrimSpace(t.Snapshot), ":", 3)
138
	if len(parts) < 2 {
139
		return t.Snapshot
140
	}
141
142
	xmin, err := strconv.ParseUint(parts[0], 10, 64)
143
	if err != nil {
144
		return t.Snapshot
145
	}
146
	xmax, err := strconv.ParseUint(parts[1], 10, 64)
147
	if err != nil {
148
		return t.Snapshot
149
	}
150
151
	txid := t.Value.Uint
152
153
	// Parse existing XIPs
154
	xipList := []uint64{}
155
	if len(parts) == 3 && parts[2] != "" {
156
		for _, xipStr := range strings.Split(parts[2], ",") {
157
			xipStr = strings.TrimSpace(xipStr)
158
			if xipStr == "" {
159
				continue
160
			}
161
			xip, err := strconv.ParseUint(xipStr, 10, 64)
162
			if err != nil {
163
				return t.Snapshot
164
			}
165
			xipList = append(xipList, xip)
166
		}
167
	}
168
169
	// Sort XIPs to ensure deterministic snapshot encoding and maintain PostgreSQL invariants
170
	// Per PostgreSQL semantics, xip[] must contain only XIDs with xmin ≤ xip < xmax
171
	slices.Sort(xipList)
172
173
	// Add current txid to make snapshot unique for this transaction
174
	// Insert in sorted order to maintain consistency
175
	inserted := false
176
	for i, xip := range xipList {
177
		if xip == txid {
178
			// Already in list, don't add again
179
			inserted = true
180
			break
181
		}
182
		if xip > txid {
183
			// Insert at position i
184
			xipList = append(xipList[:i], append([]uint64{txid}, xipList[i:]...)...)
185
			inserted = true
186
			break
187
		}
188
	}
189
	if !inserted {
190
		// Append to end
191
		xipList = append(xipList, txid)
192
	}
193
194
	// Adjust xmax if necessary
195
	newXmax := xmax
196
	if txid >= newXmax {
197
		newXmax = txid + 1
198
	}
199
200
	// Adjust xmin if current txid is smaller
201
	newXmin := xmin
202
	if txid < newXmin {
203
		newXmin = txid
204
	}
205
206
	// Build the result snapshot string efficiently using strings.Builder
207
	var xipStrs []string
208
	for _, xip := range xipList {
209
		xipStrs = append(xipStrs, fmt.Sprintf("%d", xip))
210
	}
211
212
	return fmt.Sprintf("%d:%d:%s", newXmin, newXmax, strings.Join(xipStrs, ","))
213
}
214