• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

freeeve / tinykvs / 21180415886

20 Jan 2026 05:06PM UTC coverage: 70.81% (-0.1%) from 70.948%
21180415886

push

github

freeeve
fix(compaction): add SSTable reference counting to prevent use-after-close

Add reference counting to SSTables to prevent concurrent readers from
encountering "file already closed" errors during compaction.

Changes:
- Add refs and markedForRemoval fields to SSTable struct
- Add IncRef/DecRef/MarkForRemoval methods for safe lifecycle management
- Update reader.Get to hold refs while accessing SSTables
- Update ScanPrefix/ScanRange scanners to track and release refs
- Replace direct Close+Remove with MarkForRemoval in compaction

Fixes TestConcurrentReadsDuringCompaction race condition.

50 of 53 new or added lines in 3 files covered. (94.34%)

760 existing lines in 12 files now uncovered.

5594 of 7900 relevant lines covered (70.81%)

405174.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.93
/cmd/tinykvs/shell_sort.go
1
package main
2

3
import (
4
        "sort"
5
        "strconv"
6
        "strings"
7
)
8

9
// SortOrder represents a single ORDER BY column
10
type SortOrder struct {
11
        Field      string // "k" for key, or field name like "ttl", "v.name"
12
        Descending bool
13
}
14

15
// SortableRows wraps rows for sorting with ORDER BY
16
type SortableRows struct {
17
        Headers []string
18
        Rows    [][]string
19
        Orders  []SortOrder
20
}
21

22
func (s *SortableRows) Len() int {
10✔
23
        return len(s.Rows)
10✔
24
}
10✔
25

26
func (s *SortableRows) Swap(i, j int) {
7✔
27
        s.Rows[i], s.Rows[j] = s.Rows[j], s.Rows[i]
7✔
28
}
7✔
29

30
func (s *SortableRows) Less(i, j int) bool {
22✔
31
        for _, order := range s.Orders {
46✔
32
                colIdx := s.findColumn(order.Field)
24✔
33
                if colIdx < 0 {
36✔
34
                        continue
12✔
35
                }
36

37
                valI := s.Rows[i][colIdx]
12✔
38
                valJ := s.Rows[j][colIdx]
12✔
39

12✔
40
                cmp := compareValues(valI, valJ)
12✔
41
                if cmp == 0 {
12✔
42
                        continue
×
43
                }
44

45
                if order.Descending {
18✔
46
                        return cmp > 0
6✔
47
                }
6✔
48
                return cmp < 0
6✔
49
        }
50
        return false
10✔
51
}
52

53
func (s *SortableRows) findColumn(field string) int {
24✔
54
        // Normalize field name
24✔
55
        field = strings.TrimPrefix(field, "v.")
24✔
56
        field = strings.ToLower(field)
24✔
57

24✔
58
        for i, h := range s.Headers {
64✔
59
                if strings.ToLower(h) == field {
52✔
60
                        return i
12✔
61
                }
12✔
62
        }
63
        return -1
12✔
64
}
65

66
// compareValues compares two string values, attempting numeric comparison first
67
func compareValues(a, b string) int {
12✔
68
        // Try numeric comparison first
12✔
69
        numA, errA := strconv.ParseFloat(a, 64)
12✔
70
        numB, errB := strconv.ParseFloat(b, 64)
12✔
71
        if errA == nil && errB == nil {
14✔
72
                if numA < numB {
3✔
73
                        return -1
1✔
74
                } else if numA > numB {
3✔
75
                        return 1
1✔
76
                }
1✔
77
                return 0
×
78
        }
79

80
        // Fall back to string comparison
81
        if a < b {
10✔
82
                return -1
×
83
        } else if a > b {
20✔
84
                return 1
10✔
85
        }
10✔
86
        return 0
×
87
}
88

89
// SortRows sorts rows according to the ORDER BY clauses
90
func SortRows(headers []string, rows [][]string, orders []SortOrder) {
10✔
91
        if len(orders) == 0 || len(rows) == 0 {
10✔
92
                return
×
93
        }
×
94

95
        sortable := &SortableRows{
10✔
96
                Headers: headers,
10✔
97
                Rows:    rows,
10✔
98
                Orders:  orders,
10✔
99
        }
10✔
100
        sort.Stable(sortable)
10✔
101
}
102

103
// ParseOrderBy extracts ORDER BY clauses from SQL
104
// Returns the modified SQL (with ORDER BY removed) and the sort orders
105
func ParseOrderBy(sql string) (string, []SortOrder) {
423✔
106
        lower := strings.ToLower(sql)
423✔
107
        idx := strings.LastIndex(lower, " order by ")
423✔
108
        if idx == -1 {
835✔
109
                return sql, nil
412✔
110
        }
412✔
111

112
        // Bounds check - index from lowercase string must be valid for original
113
        startIdx := idx + len(" order by ")
11✔
114
        if startIdx > len(sql) || idx > len(sql) {
12✔
115
                return sql, nil
1✔
116
        }
1✔
117

118
        orderPart := sql[startIdx:]
10✔
119
        sql = sql[:idx]
10✔
120

10✔
121
        // Remove any trailing LIMIT clause from orderPart
10✔
122
        // Use case-insensitive search - note: index found in lowercased string may not
10✔
123
        // be valid for original string if unicode characters change length when lowercased
10✔
124
        lowerOrderPart := strings.ToLower(orderPart)
10✔
125
        limitIdx := strings.Index(lowerOrderPart, " limit ")
10✔
126
        if limitIdx != -1 && limitIdx < len(orderPart) {
12✔
127
                // Put LIMIT back on the main SQL
2✔
128
                sql = sql + orderPart[limitIdx:]
2✔
129
                orderPart = orderPart[:limitIdx]
2✔
130
        }
2✔
131

132
        // Parse order columns
133
        var orders []SortOrder
10✔
134
        parts := strings.Split(orderPart, ",")
10✔
135
        for _, part := range parts {
21✔
136
                part = strings.TrimSpace(part)
11✔
137
                if part == "" {
11✔
UNCOV
138
                        continue
×
139
                }
140

141
                order := SortOrder{}
11✔
142
                tokens := strings.Fields(part)
11✔
143
                if len(tokens) >= 1 {
22✔
144
                        order.Field = tokens[0]
11✔
145
                }
11✔
146
                if len(tokens) >= 2 {
19✔
147
                        if strings.ToUpper(tokens[1]) == "DESC" {
14✔
148
                                order.Descending = true
6✔
149
                        }
6✔
150
                }
151
                orders = append(orders, order)
11✔
152
        }
153

154
        return sql, orders
10✔
155
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc