|
@@ -0,0 +1,280 @@
|
|
|
|
|
+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 <key> <value>%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 <query_conditions>%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 <key> <value>%s Add a new record
|
|
|
|
|
+ %scount <query>%s Count records matching query
|
|
|
|
|
+ %s<query>%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)
|
|
|
|
|
+}
|