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

freeeve / tinykvs / 21101309417

17 Jan 2026 09:42PM UTC coverage: 68.627% (-4.3%) from 72.975%
21101309417

push

github

freeeve
feat: add ORDER BY support and box-style table formatting

New features:
- ORDER BY support in SQL queries (ASC/DESC, multiple columns)
- Box-style table formatting with Unicode borders
- GitHub Actions CI for multi-platform releases (linux/darwin/windows × amd64/arm64)
- Build script with ldflags for version embedding

Improvements:
- Split shell.go into logical modules (shell_select.go, shell_write.go,
  shell_csv.go, shell_sql.go, shell_sort.go)
- Version string no longer shows redundant commit hash
- Reorganized tests into corresponding test files
- Added batch_parallel.go for parallel batch operations
- Test coverage improvements

975 of 1677 new or added lines in 11 files covered. (58.14%)

8 existing lines in 3 files now uncovered.

5180 of 7548 relevant lines covered (68.63%)

430934.64 hits per line

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

39.53
/cmd/tinykvs/shell_sql.go
1
package main
2

3
import (
4
        "encoding/hex"
5
        "strconv"
6
        "strings"
7

8
        "github.com/blastrain/vitess-sqlparser/sqlparser"
9
)
10

11
// preprocessFunctions expands SQL function calls to hex literals.
12
// Supported functions:
13
//   - uint64_be(n) → 8-byte big-endian encoding of n
14
//   - uint64_le(n) → 8-byte little-endian encoding of n
15
//   - uint32_be(n) → 4-byte big-endian encoding of n
16
//   - uint32_le(n) → 4-byte little-endian encoding of n
17
//   - byte(n) → single byte (0-255)
18
//   - fnv64(s) → FNV-1a 64-bit hash of string s
19
//
20
// Concatenation with || operator:
21
//   - x'14' || uint64_be(28708) → x'140000000000007024'
22
//
23
// Examples:
24
//   - k STARTS WITH x'10' || uint64_be(7341141)
25
//   - k STARTS WITH byte(0x14) || uint64_be(28708) || fnv64('library-123')
26
func preprocessFunctions(sql string) string {
292✔
27
        // First expand individual functions to x'...' literals
292✔
28
        sql = expandFunction(sql, "uint64_be", func(arg string) ([]byte, bool) {
292✔
NEW
29
                val, err := parseUint(arg)
×
NEW
30
                if err != nil {
×
NEW
31
                        return nil, false
×
NEW
32
                }
×
NEW
33
                b := make([]byte, 8)
×
NEW
34
                for i := 7; i >= 0; i-- {
×
NEW
35
                        b[7-i] = byte(val >> (i * 8))
×
NEW
36
                }
×
NEW
37
                return b, true
×
38
        })
39

40
        sql = expandFunction(sql, "uint64_le", func(arg string) ([]byte, bool) {
292✔
NEW
41
                val, err := parseUint(arg)
×
NEW
42
                if err != nil {
×
NEW
43
                        return nil, false
×
NEW
44
                }
×
NEW
45
                b := make([]byte, 8)
×
NEW
46
                for i := 0; i < 8; i++ {
×
NEW
47
                        b[i] = byte(val >> (i * 8))
×
NEW
48
                }
×
NEW
49
                return b, true
×
50
        })
51

52
        sql = expandFunction(sql, "uint32_be", func(arg string) ([]byte, bool) {
292✔
NEW
53
                val, err := parseUint(arg)
×
NEW
54
                if err != nil {
×
NEW
55
                        return nil, false
×
NEW
56
                }
×
NEW
57
                b := make([]byte, 4)
×
NEW
58
                for i := 3; i >= 0; i-- {
×
NEW
59
                        b[3-i] = byte(val >> (i * 8))
×
NEW
60
                }
×
NEW
61
                return b, true
×
62
        })
63

64
        sql = expandFunction(sql, "uint32_le", func(arg string) ([]byte, bool) {
292✔
NEW
65
                val, err := parseUint(arg)
×
NEW
66
                if err != nil {
×
NEW
67
                        return nil, false
×
NEW
68
                }
×
NEW
69
                b := make([]byte, 4)
×
NEW
70
                for i := 0; i < 4; i++ {
×
NEW
71
                        b[i] = byte(val >> (i * 8))
×
NEW
72
                }
×
NEW
73
                return b, true
×
74
        })
75

76
        sql = expandFunction(sql, "byte", func(arg string) ([]byte, bool) {
292✔
NEW
77
                val, err := parseUint(arg)
×
NEW
78
                if err != nil || val > 255 {
×
NEW
79
                        return nil, false
×
NEW
80
                }
×
NEW
81
                return []byte{byte(val)}, true
×
82
        })
83

84
        sql = expandFunction(sql, "fnv64", func(arg string) ([]byte, bool) {
292✔
NEW
85
                // Strip quotes if present
×
NEW
86
                s := strings.Trim(arg, "'\"")
×
NEW
87
                h := fnv64a(s)
×
NEW
88
                b := make([]byte, 8)
×
NEW
89
                for i := 7; i >= 0; i-- {
×
NEW
90
                        b[7-i] = byte(h >> (i * 8))
×
NEW
91
                }
×
NEW
92
                return b, true
×
93
        })
94

95
        // Now concatenate adjacent x'...' || x'...' literals
96
        sql = concatenateHexLiterals(sql)
292✔
97

292✔
98
        return sql
292✔
99
}
100

101
// parseUint parses a uint64 from decimal or hex (0x...) string
NEW
102
func parseUint(s string) (uint64, error) {
×
NEW
103
        s = strings.TrimSpace(s)
×
NEW
104
        if strings.HasPrefix(strings.ToLower(s), "0x") {
×
NEW
105
                return strconv.ParseUint(s[2:], 16, 64)
×
NEW
106
        }
×
NEW
107
        return strconv.ParseUint(s, 10, 64)
×
108
}
109

110
// fnv64a computes FNV-1a 64-bit hash
NEW
111
func fnv64a(s string) uint64 {
×
NEW
112
        const offset64 = 14695981039346656037
×
NEW
113
        const prime64 = 1099511628211
×
NEW
114
        h := uint64(offset64)
×
NEW
115
        for i := 0; i < len(s); i++ {
×
NEW
116
                h ^= uint64(s[i])
×
NEW
117
                h *= prime64
×
NEW
118
        }
×
NEW
119
        return h
×
120
}
121

122
// expandFunction finds and expands a single-argument function call to hex literal
123
func expandFunction(sql, funcName string, encode func(string) ([]byte, bool)) string {
1,752✔
124
        for {
3,504✔
125
                lower := strings.ToLower(sql)
1,752✔
126
                idx := strings.Index(lower, strings.ToLower(funcName)+"(")
1,752✔
127
                if idx == -1 {
3,504✔
128
                        return sql
1,752✔
129
                }
1,752✔
130

NEW
131
                start := idx + len(funcName) + 1
×
NEW
132
                depth := 1
×
NEW
133
                end := start
×
NEW
134
                for end < len(sql) && depth > 0 {
×
NEW
135
                        if sql[end] == '(' {
×
NEW
136
                                depth++
×
NEW
137
                        } else if sql[end] == ')' {
×
NEW
138
                                depth--
×
NEW
139
                        }
×
NEW
140
                        end++
×
141
                }
NEW
142
                if depth != 0 {
×
NEW
143
                        return sql
×
NEW
144
                }
×
145

NEW
146
                arg := strings.TrimSpace(sql[start : end-1])
×
NEW
147
                bytes, ok := encode(arg)
×
NEW
148
                if !ok {
×
NEW
149
                        return sql
×
NEW
150
                }
×
151

NEW
152
                hexStr := hex.EncodeToString(bytes)
×
NEW
153
                sql = sql[:idx] + "x'" + hexStr + "'" + sql[end:]
×
154
        }
155
}
156

157
// concatenateHexLiterals joins adjacent hex literals: x'ab' || x'cd' → x'abcd'
158
func concatenateHexLiterals(sql string) string {
292✔
159
        for {
584✔
160
                // Find pattern: x'...' followed by optional whitespace, ||, optional whitespace, x'...'
292✔
161
                idx := strings.Index(sql, "' ||")
292✔
162
                if idx == -1 {
584✔
163
                        return sql
292✔
164
                }
292✔
165

166
                // Check if there's x' before the closing quote
NEW
167
                startQuote := strings.LastIndex(sql[:idx], "x'")
×
NEW
168
                if startQuote == -1 {
×
NEW
169
                        // Try next occurrence
×
NEW
170
                        sql = sql[:idx] + "'\x00||" + sql[idx+4:] // Mark as processed
×
NEW
171
                        continue
×
172
                }
173

174
                // Get the first hex value
NEW
175
                hex1 := sql[startQuote+2 : idx]
×
NEW
176

×
NEW
177
                // Find x' after ||
×
NEW
178
                afterPipe := sql[idx+4:]
×
NEW
179
                afterPipe = strings.TrimSpace(afterPipe)
×
NEW
180
                if !strings.HasPrefix(strings.ToLower(afterPipe), "x'") {
×
NEW
181
                        sql = sql[:idx] + "'\x00||" + sql[idx+4:]
×
NEW
182
                        continue
×
183
                }
184

185
                // Find end of second hex literal
NEW
186
                endQuote := strings.Index(afterPipe[2:], "'")
×
NEW
187
                if endQuote == -1 {
×
NEW
188
                        return sql
×
NEW
189
                }
×
NEW
190
                hex2 := afterPipe[2 : 2+endQuote]
×
NEW
191

×
NEW
192
                // Calculate where the second literal ends in original string
×
NEW
193
                afterPipeStart := idx + 4 + (len(sql[idx+4:]) - len(afterPipe))
×
NEW
194
                secondLiteralEnd := afterPipeStart + 2 + endQuote + 1
×
NEW
195

×
NEW
196
                // Replace both literals with concatenated version
×
NEW
197
                sql = sql[:startQuote] + "x'" + hex1 + hex2 + "'" + sql[secondLiteralEnd:]
×
198
        }
199
}
200

201
// preprocessStartsWith converts "STARTS WITH" syntax to LIKE syntax before SQL parsing.
202
// Converts:
203
//   - k STARTS WITH x'14' → k LIKE '$$HEX$$14%'
204
//   - k STARTS WITH '14' → k LIKE '14%'
205
func preprocessStartsWith(sql string) string {
292✔
206
        // Case-insensitive match for "STARTS WITH"
292✔
207
        lower := strings.ToLower(sql)
292✔
208
        idx := strings.Index(lower, "starts with")
292✔
209
        if idx == -1 {
584✔
210
                return sql
292✔
211
        }
292✔
212

213
        // Find what comes after "starts with"
NEW
214
        afterIdx := idx + len("starts with")
×
NEW
215
        after := strings.TrimSpace(sql[afterIdx:])
×
NEW
216

×
NEW
217
        // Check if it's a hex literal x'...' or X'...'
×
NEW
218
        if len(after) >= 4 && (after[0] == 'x' || after[0] == 'X') && after[1] == '\'' {
×
NEW
219
                // Find closing quote
×
NEW
220
                endQuote := strings.Index(after[2:], "'")
×
NEW
221
                if endQuote != -1 {
×
NEW
222
                        hexVal := after[2 : 2+endQuote]
×
NEW
223
                        rest := after[2+endQuote+1:]
×
NEW
224
                        // Convert to: LIKE '$$HEX$$<hexval>%'
×
NEW
225
                        return sql[:idx] + "LIKE '$$HEX$$" + hexVal + "%'" + rest
×
NEW
226
                }
×
NEW
227
        } else if len(after) >= 2 && after[0] == '\'' {
×
NEW
228
                // String literal '...'
×
NEW
229
                endQuote := strings.Index(after[1:], "'")
×
NEW
230
                if endQuote != -1 {
×
NEW
231
                        strVal := after[1 : 1+endQuote]
×
NEW
232
                        rest := after[1+endQuote+1:]
×
NEW
233
                        // Convert to: LIKE '<strval>%'
×
NEW
234
                        return sql[:idx] + "LIKE '" + strVal + "%'" + rest
×
NEW
235
                }
×
236
        }
237

NEW
238
        return sql
×
239
}
240

241
func extractValue(expr sqlparser.Expr) string {
195✔
242
        switch v := expr.(type) {
195✔
243
        case *sqlparser.SQLVal:
195✔
244
                switch v.Type {
195✔
245
                case sqlparser.StrVal:
194✔
246
                        return string(v.Val)
194✔
247
                case sqlparser.HexVal:
1✔
248
                        // x'deadbeef'
1✔
249
                        decoded, _ := hexDecode(string(v.Val))
1✔
250
                        return string(decoded)
1✔
NEW
251
                case sqlparser.IntVal:
×
NEW
252
                        return string(v.Val)
×
253
                }
254
        }
NEW
255
        return ""
×
256
}
257

258
// extractValueForLike extracts a value, with special handling for hex LIKE patterns.
259
// Supports:
260
//   - '0x14%' syntax - string starting with 0x followed by hex bytes and %
261
//   - '$$HEX$$14%' syntax - preprocessed from STARTS WITH x'14'
262
func extractValueForLike(expr sqlparser.Expr, operator string) (string, bool) {
93✔
263
        switch v := expr.(type) {
93✔
264
        case *sqlparser.SQLVal:
93✔
265
                switch v.Type {
93✔
266
                case sqlparser.StrVal:
90✔
267
                        s := string(v.Val)
90✔
268
                        if operator == "like" {
106✔
269
                                // Support '$$HEX$$14%' from preprocessed STARTS WITH x'14'
16✔
270
                                if strings.HasPrefix(s, "$$HEX$$") {
16✔
NEW
271
                                        hexPart := s[7:] // skip $$HEX$$
×
NEW
272
                                        if strings.HasSuffix(hexPart, "%") {
×
NEW
273
                                                hexPart = hexPart[:len(hexPart)-1]
×
NEW
274
                                                decoded, err := hex.DecodeString(hexPart)
×
NEW
275
                                                if err != nil {
×
NEW
276
                                                        return s, false
×
NEW
277
                                                }
×
NEW
278
                                                return string(decoded), true
×
279
                                        }
280
                                }
281
                                // Support '0x14%' or '0X14%' syntax for hex prefix matching
282
                                if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") {
16✔
NEW
283
                                        hexPart := s[2:]
×
NEW
284
                                        if strings.HasSuffix(hexPart, "%") {
×
NEW
285
                                                hexPart = hexPart[:len(hexPart)-1]
×
NEW
286
                                                decoded, err := hex.DecodeString(hexPart)
×
NEW
287
                                                if err != nil {
×
NEW
288
                                                        return s, false
×
NEW
289
                                                }
×
NEW
290
                                                return string(decoded), true
×
291
                                        }
292
                                }
293
                        }
294
                        return s, false
90✔
295
                case sqlparser.HexVal:
1✔
296
                        decoded, _ := hexDecode(string(v.Val))
1✔
297
                        return string(decoded), false
1✔
298
                case sqlparser.IntVal:
2✔
299
                        return string(v.Val), false
2✔
300
                }
301
        }
NEW
302
        return "", false
×
303
}
304

305
func hexDecode(s string) ([]byte, error) {
12✔
306
        // Remove any spaces
12✔
307
        s = strings.ReplaceAll(s, " ", "")
12✔
308
        result := make([]byte, len(s)/2)
12✔
309
        for i := 0; i < len(s)/2; i++ {
84✔
310
                b, err := strconv.ParseUint(s[i*2:i*2+2], 16, 8)
72✔
311
                if err != nil {
73✔
312
                        return nil, err
1✔
313
                }
1✔
314
                result[i] = byte(b)
71✔
315
        }
316
        return result, nil
11✔
317
}
318

319
// extractValueAndType returns the value string and whether it was a hex value
320
func extractValueAndType(expr sqlparser.Expr) (value string, hexBytes []byte, isHex bool) {
179✔
321
        switch v := expr.(type) {
179✔
322
        case *sqlparser.SQLVal:
179✔
323
                switch v.Type {
179✔
324
                case sqlparser.StrVal:
170✔
325
                        return string(v.Val), nil, false
170✔
326
                case sqlparser.HexVal:
6✔
327
                        decoded, _ := hexDecode(string(v.Val))
6✔
328
                        return string(decoded), decoded, true
6✔
329
                case sqlparser.IntVal:
3✔
330
                        return string(v.Val), nil, false
3✔
331
                }
332
        }
NEW
333
        return "", nil, false
×
334
}
335

336
// isMsgpackMap checks if data starts with a msgpack map marker
337
func isMsgpackMap(data []byte) bool {
13✔
338
        if len(data) == 0 {
14✔
339
                return false
1✔
340
        }
1✔
341
        b := data[0]
12✔
342
        // fixmap (0x80-0x8f), map16 (0xde), map32 (0xdf)
12✔
343
        return (b >= 0x80 && b <= 0x8f) || b == 0xde || b == 0xdf
12✔
344
}
345

346
// extractNestedField extracts a nested field value from a record using a dotted path.
347
// For example, path "address.city" extracts record["address"]["city"].
348
func extractNestedField(record map[string]any, path string) (any, bool) {
75✔
349
        parts := strings.Split(path, ".")
75✔
350
        var current any = record
75✔
351
        for _, part := range parts {
172✔
352
                m, ok := current.(map[string]any)
97✔
353
                if !ok {
98✔
354
                        return nil, false
1✔
355
                }
1✔
356
                current, ok = m[part]
96✔
357
                if !ok {
102✔
358
                        return nil, false
6✔
359
                }
6✔
360
        }
361
        return current, true
68✔
362
}
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