| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- 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)
- }
|