main.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "math/rand"
  6. "os"
  7. "strings"
  8. "time"
  9. "unicode/utf8"
  10. "igit.com/xbase/raft/db"
  11. )
  12. const DataDir = "cli_data"
  13. // ANSI Colors
  14. const (
  15. ColorReset = "\033[0m"
  16. ColorRed = "\033[31m"
  17. ColorGreen = "\033[32m"
  18. ColorYellow = "\033[33m"
  19. ColorBlue = "\033[34m"
  20. ColorPurple = "\033[35m"
  21. ColorCyan = "\033[36m"
  22. ColorGray = "\033[37m"
  23. ColorBold = "\033[1m"
  24. )
  25. func main() {
  26. // Initialize DB
  27. e, err := db.NewEngine(DataDir)
  28. if err != nil {
  29. fmt.Printf("%sError initializing DB: %v%s\n", ColorRed, err, ColorReset)
  30. os.Exit(1)
  31. }
  32. defer e.Close()
  33. printWelcome()
  34. // Auto-seed if empty
  35. res, _ := e.Query("LIMIT 1")
  36. if len(res) == 0 {
  37. fmt.Printf("%s[!] Database appears empty. Seeding 1000 records...%s\n", ColorYellow, ColorReset)
  38. seedData(e)
  39. } else {
  40. fmt.Printf("%s[+] Database loaded. Ready for queries.%s\n", ColorGreen, ColorReset)
  41. }
  42. scanner := bufio.NewScanner(os.Stdin)
  43. for {
  44. fmt.Printf("\n%sraftdb%s> ", ColorBlue, ColorReset)
  45. if !scanner.Scan() {
  46. break
  47. }
  48. line := scanner.Text()
  49. line = strings.TrimSpace(line)
  50. if line == "" {
  51. continue
  52. }
  53. parts := strings.Fields(line)
  54. cmd := strings.ToLower(parts[0])
  55. switch cmd {
  56. case "exit", "quit":
  57. fmt.Printf("%sBye!%s\n", ColorGreen, ColorReset)
  58. return
  59. case "help":
  60. printHelp()
  61. case "seed":
  62. seedData(e)
  63. case "add":
  64. handleAdd(e, parts)
  65. case "count":
  66. handleCount(e, line)
  67. default:
  68. handleQuery(e, line)
  69. }
  70. }
  71. }
  72. func handleAdd(e *db.Engine, parts []string) {
  73. if len(parts) < 3 {
  74. fmt.Printf("%sUsage: add <key> <value>%s\n", ColorRed, ColorReset)
  75. return
  76. }
  77. key := parts[1]
  78. val := strings.Join(parts[2:], " ")
  79. idx := uint64(time.Now().UnixNano())
  80. start := time.Now()
  81. err := e.Set(key, val, idx)
  82. duration := time.Since(start)
  83. if err != nil {
  84. fmt.Printf("%sError adding data: %v%s\n", ColorRed, err, ColorReset)
  85. } else {
  86. fmt.Printf("%sQuery OK, 1 row affected (%v)%s\n", ColorGreen, duration, ColorReset)
  87. }
  88. }
  89. func handleCount(e *db.Engine, line string) {
  90. // Format: count key like "..."
  91. // Remove "count" from start
  92. query := strings.TrimSpace(line[5:])
  93. if query == "" {
  94. fmt.Printf("%sUsage: count <query_conditions>%s\n", ColorRed, ColorReset)
  95. return
  96. }
  97. start := time.Now()
  98. // Use Query to get results then count
  99. // Note: If Engine had a Count() method, we would use it here.
  100. results, err := e.Query(query)
  101. duration := time.Since(start)
  102. if err != nil {
  103. fmt.Printf("%sError: %v%s\n", ColorRed, err, ColorReset)
  104. return
  105. }
  106. fmt.Printf("%s+-------+%s\n", ColorYellow, ColorReset)
  107. fmt.Printf("%s| COUNT |%s\n", ColorYellow, ColorReset)
  108. fmt.Printf("%s+-------+%s\n", ColorYellow, ColorReset)
  109. fmt.Printf("| %-5d |\n", len(results))
  110. fmt.Printf("%s+-------+%s\n", ColorYellow, ColorReset)
  111. fmt.Printf("%s1 row in set (%v)%s\n", ColorGreen, duration, ColorReset)
  112. }
  113. func handleQuery(e *db.Engine, query string) {
  114. start := time.Now()
  115. results, err := e.Query(query)
  116. duration := time.Since(start)
  117. if err != nil {
  118. fmt.Printf("%sError executing query: %v%s\n", ColorRed, err, ColorReset)
  119. return
  120. }
  121. if len(results) == 0 {
  122. fmt.Printf("%sEmpty set (%v)%s\n", ColorGray, duration, ColorReset)
  123. return
  124. }
  125. // Calculate column widths
  126. maxKey := 3
  127. maxVal := 5
  128. maxIdx := 12
  129. for _, r := range results {
  130. if len(r.Key) > maxKey {
  131. maxKey = len(r.Key)
  132. }
  133. // Truncate value for display if too long
  134. vLen := utf8.RuneCountInString(r.Value)
  135. if vLen > 60 {
  136. vLen = 60
  137. }
  138. if vLen > maxVal {
  139. maxVal = vLen
  140. }
  141. }
  142. if maxKey > 40 { maxKey = 40 } // Hard limit for key display
  143. // Print Table Header
  144. printSeparator(maxKey, maxVal, maxIdx)
  145. fmt.Printf("| %-*s | %-*s | %-*s |\n", maxKey, "KEY", maxVal, "VALUE", maxIdx, "COMMIT_INDEX")
  146. printSeparator(maxKey, maxVal, maxIdx)
  147. // Print Rows
  148. for _, r := range results {
  149. k := r.Key
  150. if utf8.RuneCountInString(k) > maxKey {
  151. k = k[:maxKey-3] + "..."
  152. }
  153. v := r.Value
  154. if utf8.RuneCountInString(v) > maxVal {
  155. v = v[:maxVal-3] + "..."
  156. }
  157. fmt.Printf("| %-*s | %-*s | %-*d |\n", maxKey, k, maxVal, v, maxIdx, r.CommitIndex)
  158. }
  159. printSeparator(maxKey, maxVal, maxIdx)
  160. fmt.Printf("%s%d rows in set (%v)%s\n", ColorGreen, len(results), duration, ColorReset)
  161. }
  162. func printSeparator(k, v, i int) {
  163. fmt.Printf("%s+%s+%s+%s+%s\n", ColorYellow, strings.Repeat("-", k+2), strings.Repeat("-", v+2), strings.Repeat("-", i+2), ColorReset)
  164. }
  165. func seedData(e *db.Engine) {
  166. fmt.Println("Seeding 1000 records...")
  167. start := time.Now()
  168. prefixes := []string{"user", "product", "log", "config"}
  169. for i := 0; i < 1000; i++ {
  170. prefix := prefixes[rand.Intn(len(prefixes))]
  171. key := fmt.Sprintf("%s:%d", prefix, i)
  172. var val string
  173. switch prefix {
  174. case "user":
  175. val = fmt.Sprintf("name=User%d;role=%s;active=true", i, randomRole())
  176. case "product":
  177. val = fmt.Sprintf("title=Item%d;price=%d;desc=%s", i, rand.Intn(1000), randomDesc())
  178. case "log":
  179. val = fmt.Sprintf("level=%s;msg=Something happened at %d", randomLevel(), i)
  180. case "config":
  181. val = fmt.Sprintf("setting=%d;enabled=%v", i, rand.Intn(2) == 1)
  182. }
  183. e.Set(key, val, uint64(i+1))
  184. }
  185. fmt.Printf("Seeded 1000 records in %v\n", time.Since(start))
  186. }
  187. func randomRole() string {
  188. roles := []string{"admin", "editor", "viewer", "guest"}
  189. return roles[rand.Intn(len(roles))]
  190. }
  191. func randomLevel() string {
  192. levels := []string{"INFO", "WARN", "ERROR", "DEBUG"}
  193. return levels[rand.Intn(len(levels))]
  194. }
  195. func randomDesc() string {
  196. adjectives := []string{"Great", "Awesome", "Standard", "Basic", "Premium"}
  197. nouns := []string{"Widget", "Tool", "Gadget", "Device"}
  198. return fmt.Sprintf("%s %s", adjectives[rand.Intn(len(adjectives))], nouns[rand.Intn(len(nouns))])
  199. }
  200. func printWelcome() {
  201. fmt.Printf(`%s
  202. ____ ______ ____ ____
  203. / __ \____ _/ __/ /_ / __ \/ __ )
  204. / /_/ / __ '/ /_/ __// / / / __ |
  205. / _, _/ /_/ / __/ /_ / /_/ / /_/ /
  206. /_/ |_|\__,_/_/ \__//_____/_____/
  207. %sWelcome to RaftDB CLI Monitor.%s
  208. Type 'help' for commands.
  209. `, ColorCyan, ColorBold, ColorReset)
  210. }
  211. func printHelp() {
  212. fmt.Printf(`
  213. %sCommands:%s
  214. %shelp%s Show this help
  215. %sseed%s Insert 1000 random records
  216. %sadd <key> <value>%s Add a new record
  217. %scount <query>%s Count records matching query
  218. %s<query>%s Execute a RaftDB query
  219. %sexit / quit%s Exit the CLI
  220. %sQuery Examples:%s
  221. key like "user:*" Find all users
  222. key = "user:10" Find specific user
  223. value like "*admin*" Find full-text matches (admin role)
  224. value like "*ERROR*" Find error logs
  225. key like "product:*" LIMIT 5 List 5 products
  226. key like "log:*" OFFSET 10 LIMIT 5 Pagination example
  227. %sCount Examples:%s
  228. count key like "user:*" Count total users
  229. count value like "*admin*" Count admins
  230. `, ColorYellow, ColorReset,
  231. ColorGreen, ColorReset,
  232. ColorGreen, ColorReset,
  233. ColorGreen, ColorReset,
  234. ColorGreen, ColorReset,
  235. ColorGreen, ColorReset,
  236. ColorGreen, ColorReset,
  237. ColorYellow, ColorReset,
  238. ColorYellow, ColorReset)
  239. }