Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pt : 2 done with minimal cli and db #1

Merged
merged 1 commit into from
Mar 22, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions database.log
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
2025-03-22T17:36:54+05:30 | SET | user1 | Alice
2025-03-22T17:36:54+05:30 | SET | user2 | Bob
2025-03-22T17:36:54+05:30 | DELETE | user1 |
2025-03-22T18:10:12+05:30|SET|name|Mrinal
2025-03-22T18:10:15+05:30|SET|age|20
2025-03-22T18:10:21+05:30|SET|lastName|Pramanick
2025-03-22T18:10:27+05:30|DELETE|name|
30 changes: 23 additions & 7 deletions db/db.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
package db

import (
"bufio"
"os"
"bufio"
"fmt"
"os"
"sync"
"time"
"sync"
)

// DB represents the database

type DB struct {
store map[string]string
logFile *os.File
logWriter *bufio.Writer
mu sync.RWMutex
}

// NewDB creates a new database instance

func NewDB(logPath string) (*DB, error) {
file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
@@ -58,15 +59,15 @@ func (db *DB) Set(key, value string) error {
// Get retrieves a value by key
func (db *DB) Get(key string) (string, bool) {
db.mu.RLock()
defer db.mu.RUnlock() // This is correct for read operations
defer db.mu.RUnlock()

value, exists := db.store[key]
return value, exists
}

// Delete removes a key-value pair
func (db *DB) Delete(key string) error {
db.mu.Lock() // Changed from RLock to Lock since we're modifying
db.mu.Lock()
defer db.mu.Unlock()

op := Operation{
@@ -83,6 +84,21 @@ func (db *DB) Delete(key string) error {
return nil
}

// List prints all key-value pairs in the store
func (db *DB) List() {
db.mu.RLock()
defer db.mu.RUnlock()

if len(db.store) == 0 {
fmt.Println("Store is empty")
return
}

for key, value := range db.store {
fmt.Printf("%s: %s\n", key, value)
}
}

// Close cleans up resources
func (db *DB) Close() error {
db.mu.Lock()
124 changes: 84 additions & 40 deletions db/log.go
Original file line number Diff line number Diff line change
@@ -1,51 +1,95 @@
package db

import (
"bufio"
"fmt"
"strings"
"time"
"bufio"
"fmt"
// "os"
"strings"
"time"
)

func (db *DB) writeLog(op Operation) error {
logEntry := fmt.Sprintf("%s | %s | %s | %s \n", op.Timestamp.Format(time.RFC3339), op.Command, op.Key, op.Value)
logEntry := fmt.Sprintf("%s|%s|%s|%s\n",
op.Timestamp.Format(time.RFC3339),
op.Command,
op.Key,
op.Value)

if _, err := db.logWriter.WriteString(logEntry); err != nil {
return err
}
return db.logWriter.Flush()
if _, err := db.logWriter.WriteString(logEntry); err != nil {
return err
}
return db.logWriter.Flush()
}

func (db *DB) replayLog() error {
if _, err := db.logFile.Seek(0, 0); err != nil {
return err
}

scanner := bufio.NewScanner(db.logFile)
for scanner.Scan(){
line := scanner.Text()
parts := strings.Split(line, "|")
if len(parts) < 3 {
continue
}

timestamp, command, key := parts[0], parts[1], parts[2]
value := ""
if len(parts) > 3 {
value = parts[3]
}

if _, err := time.Parse(time.RFC3339, timestamp); err != nil {
continue
}

switch command {
case "SET":
db.store[key] = value
case "DELETE":
delete(db.store, key)
}
}

return scanner.Err()
if _, err := db.logFile.Seek(0, 0); err != nil {
return err
}

fmt.Println("Replaying log entries:")
scanner := bufio.NewScanner(db.logFile)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Split(line, "|")
if len(parts) < 3 {
continue
}

timestamp, command, key := parts[0], parts[1], parts[2]
value := ""
if len(parts) > 3 {
value = parts[3]
}

if _, err := time.Parse(time.RFC3339, timestamp); err != nil {
continue
}

fmt.Printf(" %s %s %s %s\n", timestamp, command, key, value)
switch command {
case "SET":
db.store[key] = value
case "DELETE":
delete(db.store, key)
}
}

return scanner.Err()
}

// ListLogEntries returns all log entries for display
func (db *DB) ListLogEntries() ([]Operation, error) {
if _, err := db.logFile.Seek(0, 0); err != nil {
return nil, err
}

var entries []Operation
scanner := bufio.NewScanner(db.logFile)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Split(line, "|")
if len(parts) < 3 {
continue
}

timestamp, command, key := parts[0], parts[1], parts[2]
value := ""
if len(parts) > 3 {
value = parts[3]
}

ts, err := time.Parse(time.RFC3339, timestamp)
if err != nil {
continue
}

entries = append(entries, Operation{
Timestamp: ts,
Command: command,
Key: key,
Value: value,
})
}

return entries, scanner.Err()
}
91 changes: 82 additions & 9 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package main

import (
"bufio"
"fmt"
"time"
"os"
"strings"

"github.com/mrinalxdev/go-rep/db"
)
@@ -14,15 +18,84 @@ func main() {
}
defer database.Close()

// Example usage
database.Set("user1", "Alice")
database.Set("user2", "Bob")
database.Delete("user1")
fmt.Println("\nSimple Key-Value DB with Replay System")
fmt.Println("Commands: set <key> <value>, get <key>, delete <key>, list, log, exit")

if value, exists := database.Get("user2"); exists {
fmt.Printf("user2: %s\n", value)
}
if _, exists := database.Get("user1"); !exists {
fmt.Println("user1: not found")
scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Print("> ")
if !scanner.Scan() {
break
}
input := strings.TrimSpace(scanner.Text())
if input == "" {
continue
}

parts := strings.Split(input, " ")
command := strings.ToLower(parts[0])

switch command {
case "set":
if len(parts) != 3 {
fmt.Println("Usage: set <key> <value>")
continue
}
err := database.Set(parts[1], parts[2])
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Println("OK")
}

case "get":
if len(parts) != 2 {
fmt.Println("Usage: get <key>")
continue
}
value, exists := database.Get(parts[1])
if exists {
fmt.Printf("%s\n", value)
} else {
fmt.Println("Not found")
}

case "delete":
if len(parts) != 2 {
fmt.Println("Usage: delete <key>")
continue
}
err := database.Delete(parts[1])
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Println("OK")
}

case "list":
database.List() // Use the new public method

case "log":
entries, err := database.ListLogEntries()
if err != nil {
fmt.Printf("Error reading log: %v\n", err)
continue
}
fmt.Println("Log entries:")
for _, entry := range entries {
fmt.Printf(" %s %s %s %s\n",
entry.Timestamp.Format(time.RFC3339),
entry.Command,
entry.Key,
entry.Value)
}

case "exit":
fmt.Println("Goodbye!")
return

default:
fmt.Println("Unknown command. Available: set, get, delete, list, log, exit")
}
}
}