├── .gitignore ├── .gitmodules ├── .goreleaser.yaml ├── .vscode └── launch.json ├── LICENSE ├── Makefile ├── README.md ├── VERSION ├── bench └── main.go ├── cmd ├── bench.go └── root.go ├── go.mod ├── go.sum ├── install.sh ├── ironhawk ├── bench.go ├── main.go └── parse_args_test.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | 27 | # IDE files 28 | .idea/ 29 | 30 | # Generated binaries 31 | dicedb-cli 32 | dist/ 33 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DiceDB/dicedb-cli/7fb7b587959916fd35222d5050317ae366ebc032/.gitmodules -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | project_name: dicedb-cli 4 | before: 5 | hooks: 6 | - go mod tidy 7 | - go generate ./... 8 | 9 | builds: 10 | - env: 11 | - CGO_ENABLED=0 12 | goos: 13 | - linux 14 | - windows 15 | - darwin 16 | goarch: 17 | - amd64 18 | - arm 19 | - arm64 20 | binary: dicedb-cli 21 | 22 | archives: 23 | - format: tar.gz 24 | name_template: "{{ .ProjectName }}_{{ .Version }}_{{ tolower .Os }}_{{ tolower .Arch }}" 25 | format_overrides: 26 | - goos: windows 27 | format: zip 28 | 29 | changelog: 30 | sort: asc 31 | filters: 32 | exclude: 33 | - "^docs:" 34 | - "^test:" 35 | 36 | checksum: 37 | name_template: "checksums.txt" 38 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch dicedb-cli", 6 | "type": "go", 7 | "request": "launch", 8 | "mode": "auto", 9 | "program": "main.go", 10 | "console": "integratedTerminal" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022-present, DiceDB contributors 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := $(shell cat VERSION) 2 | 3 | run: 4 | go run main.go 5 | 6 | build: 7 | go build -o ./dicedb-cli 8 | 9 | check-golangci-lint: 10 | @if ! command -v golangci-lint > /dev/null || ! golangci-lint version | grep -q "$(GOLANGCI_LINT_VERSION)"; then \ 11 | echo "Required golangci-lint version $(GOLANGCI_LINT_VERSION) not found."; \ 12 | echo "Please install golangci-lint version $(GOLANGCI_LINT_VERSION) with the following command:"; \ 13 | echo "curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.60.1"; \ 14 | exit 1; \ 15 | fi 16 | 17 | lint: check-golangci-lint 18 | golangci-lint run ./... 19 | 20 | release: 21 | git tag -a $(VERSION) -m "release $(VERSION)" 22 | git push origin $(VERSION) 23 | goreleaser release --clean 24 | 25 | generate: 26 | protoc --go_out=. --go-grpc_out=. protos/cmd.proto 27 | 28 | bench: 29 | go run main.go bench --num-connections=4 --engine=ironhawk 30 | 31 | clean: 32 | rm -f ./dicedb-cli 33 | go clean -modcache -cache -testcache 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DiceDB CLI 2 | 3 | This is a command line interface for [DiceDB](https://dicedb.io). 4 | 5 | ## Get Started 6 | 7 | ### Using cURL 8 | 9 | The best way to connect to DiceDB is using [DiceDB CLI](https://github.com/dicedb/dicedb-cli) and you can install it by running the following command 10 | 11 | ```bash 12 | $ sudo su 13 | $ curl -sL https://raw.githubusercontent.com/dicedb/dicedb-cli/refs/heads/master/install.sh | sh 14 | ``` 15 | 16 | If you are working on unsupported OS (as per above script), you can always follow the installation instructions mentioned in the [dicedb/cli](https://github.com/dicedb/dicedb-cli) repository. 17 | 18 | ### Building from source 19 | 20 | ```sh 21 | $ git clone https://github.com/dicedb/dicedb-cli 22 | $ cd dicedb-cli 23 | $ make build 24 | ``` 25 | 26 | The above command will create a binary `dicedb-cli`. Execute the binary will 27 | start the CLI and will try to connect to the DiceDB server. 28 | 29 | ## Usage 30 | 31 | Run the executable to start the interactive prompt (REPL) 32 | 33 | ```bash 34 | $ dicedb-cli 35 | ``` 36 | 37 | You should see 38 | 39 | ```sh 40 | localhost:7379> 41 | ``` 42 | 43 | To connect to some other host or port, you can pass the flags `--host` and `--port` with apt parameters. 44 | You can also get all available parameters by firing 45 | 46 | ```sh 47 | $ dicedb-cli --help 48 | ``` 49 | 50 | ## Firing commands 51 | 52 | You can execute any DiceDB command directly: 53 | 54 | ```bash 55 | localhost:7379> SET k1 v1 56 | OK OK 57 | localhost:7379> GET k1 58 | OK "v1" 59 | localhost:7379> DEL k1 60 | OK 1 61 | ``` 62 | 63 | You can find all available commands at [dicedb.io/docs](https://dicedb.io/docs). 64 | 65 | ## License 66 | 67 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 68 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | v1.0.8 -------------------------------------------------------------------------------- /bench/main.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func Benchmark(parallelism int, dicedbBenchFunc func(b *testing.B)) { 9 | nsPerOpChan := make(chan float64, parallelism) 10 | allocsPerOpChan := make(chan int64, parallelism) 11 | bytesPerOpChan := make(chan int64, parallelism) 12 | throughputChan := make(chan float64, parallelism) 13 | 14 | for i := 0; i < parallelism; i++ { 15 | go func() { 16 | results := testing.Benchmark(dicedbBenchFunc) 17 | nsPerOpChan <- float64(results.NsPerOp()) 18 | allocsPerOpChan <- results.AllocsPerOp() 19 | bytesPerOpChan <- results.AllocedBytesPerOp() 20 | throughputChan <- float64(1e9) / float64(results.NsPerOp()) 21 | }() 22 | } 23 | 24 | var totalNsPerOp, totalThroughput float64 25 | var totalAllocsPerOp, totalBytesPerOp int64 26 | 27 | for i := 0; i < parallelism; i++ { 28 | totalNsPerOp += <-nsPerOpChan 29 | totalAllocsPerOp += <-allocsPerOpChan 30 | totalBytesPerOp += <-bytesPerOpChan 31 | totalThroughput += <-throughputChan 32 | } 33 | 34 | avgNsPerOp := totalNsPerOp / float64(parallelism) 35 | avgAllocsPerOp := totalAllocsPerOp / int64(parallelism) 36 | avgBytesPerOp := totalBytesPerOp / int64(parallelism) 37 | 38 | fmt.Printf("parallelism: %d\n", parallelism) 39 | fmt.Printf("avg ns/op: %.2f\n", avgNsPerOp) 40 | fmt.Printf("avg allocs/op: %d\n", avgAllocsPerOp) 41 | fmt.Printf("avg bytes/op: %d\n", avgBytesPerOp) 42 | fmt.Printf("total throughput: %.2f ops/sec\n", totalThroughput) 43 | } 44 | -------------------------------------------------------------------------------- /cmd/bench.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/dicedb/dicedb-cli/ironhawk" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var benchCmd = &cobra.Command{ 9 | Use: "bench", 10 | Short: "quickly benchmark the performance of DiceDB", 11 | Run: func(cmd *cobra.Command, args []string) { 12 | numConns, _ := cmd.Flags().GetInt("num-connections") 13 | ironhawk.Benchmark(numConns) 14 | }, 15 | } 16 | 17 | func init() { 18 | benchCmd.Flags().Int("num-connections", 4, "number of connections in parallel to fire the requests") 19 | rootCmd.AddCommand(benchCmd) 20 | } 21 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/dicedb/dicedb-cli/ironhawk" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var rootCmd = &cobra.Command{ 12 | Use: "dicedb-cli", 13 | Short: "Command line interface for DiceDB", 14 | Run: func(cmd *cobra.Command, args []string) { 15 | host, _ := cmd.Flags().GetString("host") 16 | port, _ := cmd.Flags().GetInt("port") 17 | ironhawk.Run(host, port) 18 | }, 19 | } 20 | 21 | func init() { 22 | rootCmd.PersistentFlags().String("host", "localhost", "hostname or ip address of the DiceDB server") 23 | rootCmd.PersistentFlags().Int("port", 7379, "port number of the DiceDB server") 24 | } 25 | 26 | func Execute() { 27 | if err := rootCmd.Execute(); err != nil { 28 | fmt.Println(err) 29 | os.Exit(1) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dicedb/dicedb-cli 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/chzyer/readline v1.5.1 7 | github.com/dicedb/dicedb-go v1.0.11 8 | github.com/fatih/color v1.18.0 9 | github.com/spf13/cobra v1.8.1 10 | google.golang.org/protobuf v1.36.6 11 | ) 12 | 13 | require ( 14 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 15 | github.com/google/uuid v1.6.0 // indirect 16 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 17 | github.com/mattn/go-colorable v0.1.13 // indirect 18 | github.com/mattn/go-isatty v0.0.20 // indirect 19 | github.com/spf13/pflag v1.0.5 // indirect 20 | golang.org/x/sys v0.25.0 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= 2 | github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= 3 | github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= 4 | github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= 5 | github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= 6 | github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= 7 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 8 | github.com/dicedb/dicedb-go v1.0.11 h1:3bLx9HaQXVlXpMOxN16mrDBrHOZB08JDtuXg/nTZimU= 9 | github.com/dicedb/dicedb-go v1.0.11/go.mod h1:V1fiCJnPfSObKWrOJ/zrhHEGlLwT9k3pKCto3sz1oW8= 10 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 11 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 12 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 13 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 14 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 15 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 16 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 17 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 18 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 19 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 20 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 21 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 22 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 23 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 24 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 25 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 26 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 27 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 28 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 29 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 30 | go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs= 31 | go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= 32 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 33 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 34 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 35 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= 36 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 37 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 38 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 39 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 40 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 41 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 42 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 43 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | set -e 3 | 4 | REPO="dicedb/dicedb-cli" 5 | LATEST_RELEASE=$(curl -s https://api.github.com/repos/$REPO/releases/latest | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/') 6 | VERSION=$(echo $LATEST_RELEASE | sed 's/^v//') 7 | 8 | # Detect the operating system and architecture 9 | OS=$(uname -s) 10 | ARCH=$(uname -m) 11 | 12 | # Convert OS/ARCH to the naming convention used in releases 13 | case $OS in 14 | Linux) OS="linux" ;; 15 | Darwin) OS="darwin" ;; 16 | CYGWIN*|MINGW32*|MSYS*|MINGW*) OS="windows" ;; 17 | *) echo "OS not supported"; exit 1 ;; 18 | esac 19 | 20 | case $ARCH in 21 | x86_64) ARCH="amd64" ;; 22 | arm64|aarch64) ARCH="arm64" ;; 23 | *) echo "Architecture not supported"; exit 1 ;; 24 | esac 25 | 26 | BINARY="dicedb-cli_${VERSION}_${OS}_${ARCH}.tar.gz" 27 | URL="https://github.com/$REPO/releases/download/$LATEST_RELEASE/$BINARY" 28 | 29 | echo "Downloading $BINARY..." 30 | curl -L $URL -o /tmp/$BINARY 31 | 32 | # Extract and move to /usr/local/bin 33 | tar -xzf /tmp/$BINARY -C /tmp 34 | chmod 777 /tmp/dicedb-cli 35 | 36 | DICEDB_DIR=/usr/local/dicedb 37 | DICEDB_BIN_DIR=$DICEDB_DIR/bin 38 | 39 | if [ ! -d "$DICEDB_DIR" ]; then 40 | sudo mkdir -p $DICEDB_DIR 41 | fi 42 | 43 | if [ ! -d "$DICEDB_BIN_DIR" ]; then 44 | sudo mkdir -p $DICEDB_BIN_DIR 45 | sudo chmod 777 $DICEDB_BIN_DIR 46 | fi 47 | 48 | mv /tmp/dicedb-cli $DICEDB_BIN_DIR 49 | sudo ln -sf $DICEDB_BIN_DIR/dicedb-cli /usr/local/bin/dicedb-cli 50 | 51 | echo "\n 52 | ██████╗ ██╗ ██████╗███████╗██████╗ ██████╗ 53 | ██╔══██╗██║██╔════╝██╔════╝██╔══██╗██╔══██╗ 54 | ██║ ██║██║██║ █████╗ ██║ ██║██████╔╝ 55 | ██║ ██║██║██║ ██╔══╝ ██║ ██║██╔══██╗ 56 | ██████╔╝██║╚██████╗███████╗██████╔╝██████╔╝ 57 | ╚═════╝ ╚═╝ ╚═════╝╚══════╝╚═════╝ ╚═════╝ 58 | " 59 | echo "> if you get 'command not found' error, add '/usr/local/bin' to your 'PATH' variable." 60 | echo "\nDiceDB CLI installation complete ✓" 61 | -------------------------------------------------------------------------------- /ironhawk/bench.go: -------------------------------------------------------------------------------- 1 | package ironhawk 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/dicedb/dicedb-cli/bench" 8 | "github.com/dicedb/dicedb-go" 9 | "github.com/dicedb/dicedb-go/wire" 10 | ) 11 | 12 | func benchmarkCommand(b *testing.B) { 13 | client, err := dicedb.NewClient("localhost", 7379) 14 | if err != nil { 15 | b.Fatal("Failed to create connection") 16 | } 17 | defer client.Close() 18 | 19 | cmds := make([]*wire.Command, 1000) 20 | for i := 0; i < 1000; i++ { 21 | cmds[i] = &wire.Command{ 22 | Cmd: "GET", 23 | Args: []string{fmt.Sprintf("key-%d", i)}, 24 | } 25 | } 26 | 27 | b.ResetTimer() 28 | for i := 0; i < b.N; i++ { 29 | _ = client.Fire(cmds[i%1000]) 30 | } 31 | } 32 | 33 | func Benchmark(parallelism int) { 34 | bench.Benchmark(parallelism, benchmarkCommand) 35 | } 36 | -------------------------------------------------------------------------------- /ironhawk/main.go: -------------------------------------------------------------------------------- 1 | package ironhawk 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "strings" 10 | 11 | "github.com/chzyer/readline" 12 | "github.com/dicedb/dicedb-go" 13 | "github.com/dicedb/dicedb-go/wire" 14 | "github.com/fatih/color" 15 | "github.com/google/shlex" 16 | "google.golang.org/protobuf/encoding/protojson" 17 | ) 18 | 19 | var ( 20 | boldRed = color.New(color.FgRed, color.Bold).SprintFunc() 21 | boldBlue = color.New(color.FgBlue, color.Bold).SprintFunc() 22 | boldGreen = color.New(color.FgGreen, color.Bold).SprintFunc() 23 | ) 24 | 25 | func Run(host string, port int) { 26 | client, err := dicedb.NewClient(host, port) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | defer client.Close() 31 | 32 | rl, err := readline.NewEx(&readline.Config{ 33 | Prompt: fmt.Sprintf("%s:%s> ", boldBlue(host), boldBlue(port)), 34 | HistoryFile: os.ExpandEnv("$HOME/.dicedb_history"), 35 | }) 36 | if err != nil { 37 | fmt.Printf("%s failed to initialize readline: %v\n", boldRed("ERR"), err) 38 | return 39 | } 40 | defer rl.Close() 41 | 42 | // Signal handling 43 | sigChan := make(chan os.Signal, 1) 44 | signal.Notify(sigChan, os.Interrupt) 45 | 46 | watchModeSignal := make(chan bool, 1) 47 | sigChanWatchMode := make(chan os.Signal, 1) 48 | 49 | // Handling interrupts in a goroutine 50 | go func() { 51 | for sig := range sigChan { 52 | select { 53 | // When in watch mode, capture the signal and send it to 54 | // the signal channel for watch mode 55 | case <-watchModeSignal: 56 | // Instead of exiting the REPL, send the signal to the 57 | // watch mode signal channel 58 | sigChanWatchMode <- sig 59 | default: 60 | // when not in watch mode, exit the REPL 61 | fmt.Println("\nreceived interrupt. exiting...") 62 | os.Exit(0) 63 | } 64 | } 65 | }() 66 | 67 | for { 68 | input, err := rl.Readline() 69 | if err != nil { // io.EOF, readline.ErrInterrupt 70 | break 71 | } 72 | input = strings.TrimSpace(input) 73 | 74 | if input == "exit" { 75 | return 76 | } 77 | 78 | if input == "" { 79 | continue 80 | } 81 | 82 | args := parseArgs(input) 83 | if len(args) == 0 { 84 | continue 85 | } 86 | 87 | c := &wire.Command{ 88 | Cmd: strings.ToUpper(args[0]), 89 | Args: args[1:], 90 | } 91 | 92 | resp := client.Fire(c) 93 | if resp.Status == wire.Status_ERR { 94 | renderResponse(resp) 95 | continue 96 | } 97 | 98 | if strings.HasSuffix(strings.ToUpper(args[0]), ".WATCH") { 99 | fmt.Println("entered the watch mode for", c.Cmd, strings.Join(c.Args, " ")) 100 | 101 | // Send a signal to the primary Signal handler goroutine 102 | // that the watch mode has been entered 103 | watchModeSignal <- true 104 | 105 | // Get the watch channel and start watching for changes 106 | ch, err := client.WatchCh() 107 | if err != nil { 108 | fmt.Println("error watching:", err) 109 | continue 110 | } 111 | 112 | // Start watching for changes 113 | // until the user exits the watch mode 114 | shouldExitWatchMode := false 115 | for !shouldExitWatchMode { 116 | select { 117 | // If the user sends a signal Ctrl+C, 118 | // It is captured by the signal handler goroutine 119 | // and then sent to the watch mode signal channel 120 | // which will set the shouldExitWatchMode flag to true 121 | case <-sigChanWatchMode: 122 | fmt.Println("exiting the watch mode. back to command mode") 123 | shouldExitWatchMode = true 124 | case resp := <-ch: 125 | // If we get any response over the watch channel, 126 | // render the response 127 | renderResponse(resp) 128 | } 129 | } 130 | } else { 131 | // If the command is not a watch command, render the response 132 | // and continue to the next command in REPL 133 | renderResponse(resp) 134 | } 135 | } 136 | } 137 | 138 | func printZElement(e *wire.ZElement) { 139 | fmt.Printf("%d) %d, %s\n", e.Rank, e.Score, e.Member) 140 | } 141 | 142 | func printGEOElement(index int, e *wire.GEOElement) { 143 | fmt.Printf("%d) %d, %f, (%f, %f), %s\n", index, e.Hash, e.Distance, e.Coords.Longitude, e.Coords.Latitude, e.Member) 144 | } 145 | 146 | func renderResponse(resp *wire.Result) { 147 | if resp.Status == wire.Status_ERR { 148 | fmt.Printf("%s %s\n", boldRed("ERR"), resp.Message) 149 | return 150 | } 151 | 152 | fmt.Printf("%s ", boldGreen(resp.Message)) 153 | if resp.Fingerprint64 != 0 { 154 | fmt.Printf("[fingerprint=%d] ", resp.Fingerprint64) 155 | } 156 | 157 | switch resp.Response.(type) { 158 | case *wire.Result_GETRes: 159 | fmt.Printf("\"%s\"\n", resp.GetGETRes().Value) 160 | case *wire.Result_GETDELRes: 161 | fmt.Printf("\"%s\"\n", resp.GetGETDELRes().Value) 162 | case *wire.Result_SETRes: 163 | fmt.Printf("\n") 164 | case *wire.Result_FLUSHDBRes: 165 | fmt.Printf("\n") 166 | case *wire.Result_DELRes: 167 | fmt.Printf("%d\n", resp.GetDELRes().Count) 168 | case *wire.Result_DECRRes: 169 | fmt.Printf("%d\n", resp.GetDECRRes().Value) 170 | case *wire.Result_INCRRes: 171 | fmt.Printf("%d\n", resp.GetINCRRes().Value) 172 | case *wire.Result_DECRBYRes: 173 | fmt.Printf("%d\n", resp.GetDECRBYRes().Value) 174 | case *wire.Result_INCRBYRes: 175 | fmt.Printf("%d\n", resp.GetINCRBYRes().Value) 176 | case *wire.Result_ECHORes: 177 | fmt.Printf("%s\n", resp.GetECHORes().Message) 178 | case *wire.Result_EXISTSRes: 179 | fmt.Printf("%d\n", resp.GetEXISTSRes().Count) 180 | case *wire.Result_EXPIRERes: 181 | fmt.Printf("%v\n", resp.GetEXPIRERes().IsChanged) 182 | case *wire.Result_EXPIREATRes: 183 | fmt.Printf("%v\n", resp.GetEXPIREATRes().IsChanged) 184 | case *wire.Result_EXPIRETIMERes: 185 | fmt.Printf("%d\n", resp.GetEXPIRETIMERes().UnixSec) 186 | case *wire.Result_TTLRes: 187 | fmt.Printf("%d\n", resp.GetTTLRes().Seconds) 188 | case *wire.Result_GETEXRes: 189 | fmt.Printf("\"%s\"\n", resp.GetGETEXRes().Value) 190 | case *wire.Result_GETSETRes: 191 | fmt.Printf("\"%s\"\n", resp.GetGETSETRes().Value) 192 | case *wire.Result_HANDSHAKERes: 193 | fmt.Printf("\n") 194 | case *wire.Result_HGETRes: 195 | fmt.Printf("\"%s\"\n", resp.GetHGETRes().Value) 196 | case *wire.Result_HSETRes: 197 | fmt.Printf("%d\n", resp.GetHSETRes().Count) 198 | case *wire.Result_HGETALLRes: 199 | fmt.Printf("\n") 200 | for i, e := range resp.GetHGETALLRes().Elements { 201 | fmt.Printf("%d) %s=\"%s\"\n", i, e.Key, e.Value) 202 | } 203 | case *wire.Result_KEYSRes: 204 | fmt.Printf("\n") 205 | for i, key := range resp.GetKEYSRes().Keys { 206 | fmt.Printf("%d) %s\n", i, key) 207 | } 208 | case *wire.Result_PINGRes: 209 | fmt.Printf("\"%s\"\n", resp.GetPINGRes().Message) 210 | case *wire.Result_TYPERes: 211 | fmt.Printf("%s\n", resp.GetTYPERes().Type) 212 | case *wire.Result_ZADDRes: 213 | fmt.Printf("%d\n", resp.GetZADDRes().Count) 214 | case *wire.Result_ZCOUNTRes: 215 | fmt.Printf("%d\n", resp.GetZCOUNTRes().Count) 216 | case *wire.Result_ZRANGERes: 217 | fmt.Printf("\n") 218 | for _, e := range resp.GetZRANGERes().Elements { 219 | printZElement(e) 220 | } 221 | case *wire.Result_ZPOPMAXRes: 222 | fmt.Printf("\n") 223 | for _, e := range resp.GetZPOPMAXRes().Elements { 224 | printZElement(e) 225 | } 226 | case *wire.Result_ZPOPMINRes: 227 | fmt.Printf("\n") 228 | for _, e := range resp.GetZPOPMINRes().Elements { 229 | printZElement(e) 230 | } 231 | case *wire.Result_ZREMRes: 232 | fmt.Printf("%d\n", resp.GetZREMRes().Count) 233 | case *wire.Result_ZCARDRes: 234 | fmt.Printf("%d\n", resp.GetZCARDRes().Count) 235 | case *wire.Result_ZRANKRes: 236 | printZElement(resp.GetZRANKRes().Element) 237 | case *wire.Result_GETWATCHRes: 238 | fmt.Printf("\n") 239 | case *wire.Result_HGETWATCHRes: 240 | fmt.Printf("\n") 241 | case *wire.Result_HGETALLWATCHRes: 242 | fmt.Printf("\n") 243 | case *wire.Result_ZRANGEWATCHRes: 244 | fmt.Printf("\n") 245 | case *wire.Result_ZCARDWATCHRes: 246 | fmt.Printf("\n") 247 | case *wire.Result_ZCOUNTWATCHRes: 248 | fmt.Printf("\n") 249 | case *wire.Result_ZRANKWATCHRes: 250 | fmt.Printf("\n") 251 | case *wire.Result_UNWATCHRes: 252 | fmt.Printf("\n") 253 | case *wire.Result_GEOADDRes: 254 | fmt.Printf("%d\n", resp.GetGEOADDRes().Count) 255 | case *wire.Result_GEODISTRes: 256 | fmt.Printf("%f\n", resp.GetGEODISTRes().Distance) 257 | case *wire.Result_GEOSEARCHRes: 258 | fmt.Printf("\n") 259 | for i, e := range resp.GetGEOSEARCHRes().Elements { 260 | printGEOElement(i, e) 261 | } 262 | case *wire.Result_GEOHASHRes: 263 | fmt.Printf("\n") 264 | for i, hash := range resp.GetGEOHASHRes().Hashes { 265 | if len(hash) == 0 { 266 | hash = "nil" 267 | } 268 | fmt.Printf("%d) %s\n", i, hash) 269 | } 270 | case *wire.Result_GEOPOSRes: 271 | fmt.Printf("\n") 272 | for i, coord := range resp.GetGEOPOSRes().Coords { 273 | if coord.Latitude == 0 || coord.Longitude == 0 { 274 | fmt.Printf("%d) (nil)\n", i) 275 | } else { 276 | fmt.Printf("%d) %f, %f\n", i, coord.Longitude, coord.Latitude) 277 | } 278 | } 279 | default: 280 | fmt.Println("note: this response is JSON serialized version of the response because it is not supported by this version of the CLI. You can upgrade the CLI to the latest version to get a formatted response.") 281 | b, err := protojson.Marshal(resp) 282 | if err != nil { 283 | log.Fatalf("failed to marshal to JSON: %v", err) 284 | } 285 | 286 | var m map[string]interface{} 287 | _ = json.Unmarshal(b, &m) 288 | 289 | nb, _ := json.MarshalIndent(m, "", " ") 290 | fmt.Println(string(nb)) 291 | } 292 | } 293 | 294 | func parseArgs(input string) []string { 295 | args, err := shlex.Split(input) 296 | if err != nil { 297 | fmt.Printf("%s failed to parse command: %v\n", boldRed("ERR"), err) 298 | return []string{} 299 | } 300 | return args 301 | } 302 | -------------------------------------------------------------------------------- /ironhawk/parse_args_test.go: -------------------------------------------------------------------------------- 1 | package ironhawk 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestParseArgs_EdgeCases(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | input string 12 | expected []string 13 | }{ 14 | { 15 | name: "Simple command with double quotes", 16 | input: `SET key "hello world"`, 17 | expected: []string{"SET", "key", "hello world"}, 18 | }, 19 | { 20 | name: "Escaped quote inside string", 21 | input: `ECHO "She said \"hello\""`, 22 | expected: []string{"ECHO", `She said "hello"`}, 23 | }, 24 | { 25 | name: "Single quote inside double quotes", 26 | input: `ECHO "It\'s a test"`, 27 | expected: []string{"ECHO", `It's a test`}, 28 | }, 29 | { 30 | name: "Double quote inside single quotes", 31 | input: `ECHO 'She said "hi"'`, 32 | expected: []string{"ECHO", `She said "hi"`}, 33 | }, 34 | { 35 | name: "Unterminated quote (should fail)", 36 | input: `SET key "unterminated value`, 37 | expected: []string{"SET", "key", "unterminated value"}, 38 | }, 39 | { 40 | name: "Nested quotes", 41 | input: `CMD "value with 'nested quote'"`, 42 | expected: []string{"CMD", `value with 'nested quote'`}, 43 | }, 44 | { 45 | name: "Empty quoted argument", 46 | input: `SET key ""`, 47 | expected: []string{"SET", "key", ""}, 48 | }, 49 | { 50 | name: "Trailing space", 51 | input: `ECHO "hello world" `, 52 | expected: []string{"ECHO", "hello world"}, 53 | }, 54 | { 55 | name: "Special characters in value", 56 | input: `SET key "!@#$%^&*()"`, 57 | expected: []string{"SET", "key", "!@#$%^&*()"}, 58 | }, 59 | { 60 | name: "Argument with escaped backslash", 61 | input: `ECHO "C:\\Program Files\\App"`, 62 | expected: []string{"ECHO", `C:\Program Files\App`}, 63 | }, 64 | { 65 | name: "Multiple spaces between arguments", 66 | input: `SET key "hello"`, 67 | expected: []string{"SET", "key", "hello"}, 68 | }, 69 | { 70 | name: "Command with tab separators", 71 | input: "SET\tkey\t\"value with tab\"", 72 | expected: []string{"SET", "key", "value with tab"}, 73 | }, 74 | { 75 | name: "List-like input", 76 | input: `RPUSH list "item1" "item2" "item3"`, 77 | expected: []string{"RPUSH", "list", "item1", "item2", "item3"}, 78 | }, 79 | { 80 | name: "Command with newline in input", 81 | input: "SET key \"value with \n newline\"", 82 | expected: []string{"SET", "key", "value with \n newline"}, 83 | }, 84 | } 85 | 86 | for _, tc := range tests { 87 | t.Run(tc.name, func(t *testing.T) { 88 | result := parseArgs(tc.input) 89 | if !reflect.DeepEqual(result, tc.expected) { 90 | t.Errorf("parseArgs(%q) = %#v; expected %#v", tc.input, result, tc.expected) 91 | } 92 | }) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/dicedb/dicedb-cli/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | --------------------------------------------------------------------------------