package main import ( "bufio" "fmt" "math/rand" "os" "strings" "time" "unicode/utf8" "igit.com/xbase/raft/db" ) const DataDir = "cli_data" // ANSI Colors const ( ColorReset = "\033[0m" ColorRed = "\033[31m" ColorGreen = "\033[32m" ColorYellow = "\033[33m" ColorBlue = "\033[34m" ColorPurple = "\033[35m" ColorCyan = "\033[36m" ColorGray = "\033[37m" ColorBold = "\033[1m" ) func main() { // Initialize DB e, err := db.NewEngine(DataDir) if err != nil { fmt.Printf("%sError initializing DB: %v%s\n", ColorRed, err, ColorReset) os.Exit(1) } defer e.Close() printWelcome() // Auto-seed if empty res, _ := e.Query("LIMIT 1") if len(res) == 0 { fmt.Printf("%s[!] Database appears empty. Seeding 1000 records...%s\n", ColorYellow, ColorReset) seedData(e) } else { fmt.Printf("%s[+] Database loaded. Ready for queries.%s\n", ColorGreen, ColorReset) } scanner := bufio.NewScanner(os.Stdin) for { fmt.Printf("\n%sraftdb%s> ", ColorBlue, ColorReset) if !scanner.Scan() { break } line := scanner.Text() line = strings.TrimSpace(line) if line == "" { continue } parts := strings.Fields(line) cmd := strings.ToLower(parts[0]) switch cmd { case "exit", "quit": fmt.Printf("%sBye!%s\n", ColorGreen, ColorReset) return case "help": printHelp() case "seed": seedData(e) case "add": handleAdd(e, parts) case "count": handleCount(e, line) default: handleQuery(e, line) } } } func handleAdd(e *db.Engine, parts []string) { if len(parts) < 3 { fmt.Printf("%sUsage: add %s\n", ColorRed, ColorReset) return } key := parts[1] val := strings.Join(parts[2:], " ") idx := uint64(time.Now().UnixNano()) start := time.Now() err := e.Set(key, val, idx) duration := time.Since(start) if err != nil { fmt.Printf("%sError adding data: %v%s\n", ColorRed, err, ColorReset) } else { fmt.Printf("%sQuery OK, 1 row affected (%v)%s\n", ColorGreen, duration, ColorReset) } } func handleCount(e *db.Engine, line string) { // Format: count key like "..." // Remove "count" from start query := strings.TrimSpace(line[5:]) if query == "" { fmt.Printf("%sUsage: count %s\n", ColorRed, ColorReset) return } start := time.Now() // Use Query to get results then count // Note: If Engine had a Count() method, we would use it here. results, err := e.Query(query) duration := time.Since(start) if err != nil { fmt.Printf("%sError: %v%s\n", ColorRed, err, ColorReset) return } fmt.Printf("%s+-------+%s\n", ColorYellow, ColorReset) fmt.Printf("%s| COUNT |%s\n", ColorYellow, ColorReset) fmt.Printf("%s+-------+%s\n", ColorYellow, ColorReset) fmt.Printf("| %-5d |\n", len(results)) fmt.Printf("%s+-------+%s\n", ColorYellow, ColorReset) fmt.Printf("%s1 row in set (%v)%s\n", ColorGreen, duration, ColorReset) } func handleQuery(e *db.Engine, query string) { start := time.Now() results, err := e.Query(query) duration := time.Since(start) if err != nil { fmt.Printf("%sError executing query: %v%s\n", ColorRed, err, ColorReset) return } if len(results) == 0 { fmt.Printf("%sEmpty set (%v)%s\n", ColorGray, duration, ColorReset) return } // Calculate column widths maxKey := 3 maxVal := 5 maxIdx := 12 for _, r := range results { if len(r.Key) > maxKey { maxKey = len(r.Key) } // Truncate value for display if too long vLen := utf8.RuneCountInString(r.Value) if vLen > 60 { vLen = 60 } if vLen > maxVal { maxVal = vLen } } if maxKey > 40 { maxKey = 40 } // Hard limit for key display // Print Table Header printSeparator(maxKey, maxVal, maxIdx) fmt.Printf("| %-*s | %-*s | %-*s |\n", maxKey, "KEY", maxVal, "VALUE", maxIdx, "COMMIT_INDEX") printSeparator(maxKey, maxVal, maxIdx) // Print Rows for _, r := range results { k := r.Key if utf8.RuneCountInString(k) > maxKey { k = k[:maxKey-3] + "..." } v := r.Value if utf8.RuneCountInString(v) > maxVal { v = v[:maxVal-3] + "..." } fmt.Printf("| %-*s | %-*s | %-*d |\n", maxKey, k, maxVal, v, maxIdx, r.CommitIndex) } printSeparator(maxKey, maxVal, maxIdx) fmt.Printf("%s%d rows in set (%v)%s\n", ColorGreen, len(results), duration, ColorReset) } func printSeparator(k, v, i int) { fmt.Printf("%s+%s+%s+%s+%s\n", ColorYellow, strings.Repeat("-", k+2), strings.Repeat("-", v+2), strings.Repeat("-", i+2), ColorReset) } func seedData(e *db.Engine) { fmt.Println("Seeding 1000 records...") start := time.Now() prefixes := []string{"user", "product", "log", "config"} for i := 0; i < 1000; i++ { prefix := prefixes[rand.Intn(len(prefixes))] key := fmt.Sprintf("%s:%d", prefix, i) var val string switch prefix { case "user": val = fmt.Sprintf("name=User%d;role=%s;active=true", i, randomRole()) case "product": val = fmt.Sprintf("title=Item%d;price=%d;desc=%s", i, rand.Intn(1000), randomDesc()) case "log": val = fmt.Sprintf("level=%s;msg=Something happened at %d", randomLevel(), i) case "config": val = fmt.Sprintf("setting=%d;enabled=%v", i, rand.Intn(2) == 1) } e.Set(key, val, uint64(i+1)) } fmt.Printf("Seeded 1000 records in %v\n", time.Since(start)) } func randomRole() string { roles := []string{"admin", "editor", "viewer", "guest"} return roles[rand.Intn(len(roles))] } func randomLevel() string { levels := []string{"INFO", "WARN", "ERROR", "DEBUG"} return levels[rand.Intn(len(levels))] } func randomDesc() string { adjectives := []string{"Great", "Awesome", "Standard", "Basic", "Premium"} nouns := []string{"Widget", "Tool", "Gadget", "Device"} return fmt.Sprintf("%s %s", adjectives[rand.Intn(len(adjectives))], nouns[rand.Intn(len(nouns))]) } func printWelcome() { fmt.Printf(`%s ____ ______ ____ ____ / __ \____ _/ __/ /_ / __ \/ __ ) / /_/ / __ '/ /_/ __// / / / __ | / _, _/ /_/ / __/ /_ / /_/ / /_/ / /_/ |_|\__,_/_/ \__//_____/_____/ %sWelcome to RaftDB CLI Monitor.%s Type 'help' for commands. `, ColorCyan, ColorBold, ColorReset) } func printHelp() { fmt.Printf(` %sCommands:%s %shelp%s Show this help %sseed%s Insert 1000 random records %sadd %s Add a new record %scount %s Count records matching query %s%s Execute a RaftDB query %sexit / quit%s Exit the CLI %sQuery Examples:%s key like "user:*" Find all users key = "user:10" Find specific user value like "*admin*" Find full-text matches (admin role) value like "*ERROR*" Find error logs key like "product:*" LIMIT 5 List 5 products key like "log:*" OFFSET 10 LIMIT 5 Pagination example %sCount Examples:%s count key like "user:*" Count total users count value like "*admin*" Count admins `, ColorYellow, ColorReset, ColorGreen, ColorReset, ColorGreen, ColorReset, ColorGreen, ColorReset, ColorGreen, ColorReset, ColorGreen, ColorReset, ColorGreen, ColorReset, ColorYellow, ColorReset, ColorYellow, ColorReset) }