Passed
Pull Request — master (#2577)
by Tolga
02:57
created

snapshot.Token.createFinalSnapshot   F

Complexity

Conditions 17

Size

Total Lines 77
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

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