1
0
Fork 0

Adding upstream version 1.9.1.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-09 16:57:44 +01:00
parent 2bf0435a35
commit 031879240c
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
356 changed files with 26924 additions and 0 deletions

9
.bumpversion.cfg Normal file
View file

@ -0,0 +1,9 @@
[bumpversion]
current_version = 1.9.1
commit = True
tag = True
[bumpversion:file:iredis/__init__.py]
[bumpversion:file:pyproject.toml]

5
.flake8 Normal file
View file

@ -0,0 +1,5 @@
[flake8]
ignore = D203,W503,W605,C901,E203
exclude = .git,__pycache__,build,dist,venv
max-complexity = 14
max-line-length = 120

2
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,2 @@
patreon: laixintao
custom: ["https://www.kawabangga.com/sponsor", "http://paypal.me/laixintao"]

152
.github/workflows/release.yaml vendored Normal file
View file

@ -0,0 +1,152 @@
name: Release
on:
push:
tags:
- v*
jobs:
release-pypi:
name: release-pypi
runs-on: ubuntu-16.04
# FIXME
# help test shouldn't depends on this to run
services:
redis:
image: redis:5
ports:
- 6379:6379
options: --entrypoint redis-server
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v1
with:
python-version: 3.7
architecture: 'x64'
- name: Cache venv
uses: actions/cache@v1
with:
path: venv
# Look to see if there is a cache hit for the corresponding requirements file
key: ubuntu-16.04-poetryenv-${{ hashFiles('poetry.lock') }}
- name: Install Dependencies
run: |
python3 -m venv venv
. venv/bin/activate
pip install -U pip
pip install poetry
poetry install
python -c "import sys; print(sys.version)"
pip list
- name: Poetry Build
run: |
. venv/bin/activate
poetry build
- name: Test Build
run: |
python3 -m venv fresh_env
. fresh_env/bin/activate
pip install dist/*.whl
iredis -h
iredis help GET
- name: Upload to Pypi
env:
PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: |
. venv/bin/activate
poetry publish --username __token__ --password ${PASSWORD}
release-binary:
name: Release Executable Binary.
runs-on: ubuntu-16.04
# FIXME
# help test shouldn't depends on this to run
services:
redis:
image: redis
ports:
- 6379:6379
options: --entrypoint redis-server
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v1
with:
python-version: 3.7
architecture: 'x64'
- name: Cache venv
uses: actions/cache@v1
with:
path: venv
# Look to see if there is a cache hit for the corresponding requirements file
key: ubuntu-16.04-poetryenv-${{ hashFiles('poetry.lock') }}
- name: Install Dependencies
run: |
python3 -m venv venv
. venv/bin/activate
pip install -U pip
pip install poetry
poetry install
python -c "import sys; print(sys.version)"
pip list
- name: Poetry Build
run: |
. venv/bin/activate
poetry build
- name: Test Build
run: |
python3 -m venv fresh_env
. fresh_env/bin/activate
pip install dist/*.whl
iredis -h
iredis help GET
- name: Cache cargo registry
uses: actions/cache@v1
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry
- name: Executable Build
run: |
# pyoxidizer doesn't know the wheel path, and it doesn't support passing env vars
export WHEEL_PATH=`ls ./dist/iredis*.whl`
envsubst '$WHEEL_PATH' < pyoxidizer.template.bzl > pyoxidizer.bzl
cargo install pyoxidizer --vers 0.6.0
pyoxidizer build --release install
cd ./build/x86*/release/install
tar -zcf ../../../iredis.tar.gz lib/ iredis
cd -
- name: Test Executable
run: |
./build/x86*/release/install/iredis -h
./build/x86*/release/install/iredis help GET
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./build/iredis.tar.gz
asset_name: iredis.tar.gz
asset_content_type: application/gzip

81
.github/workflows/test.yaml vendored Normal file
View file

@ -0,0 +1,81 @@
name: Test
on:
pull_request:
push:
branches:
- master
jobs:
test:
name: Pytest
strategy:
matrix:
os: [ubuntu-16.04]
python: ['3.6', '3.7', '3.8']
redis: [5, 6]
runs-on: ${{ matrix.os }}
services:
redis:
image: redis:${{ matrix.redis }}
ports:
- 6379:6379
options: --entrypoint redis-server
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python }}
architecture: 'x64'
- name: Cache venv
uses: actions/cache@v1
with:
path: venv
# Look to see if there is a cache hit for the corresponding requirements file
key: ${{ matrix.os }}-poetryenv-${{ hashFiles('poetry.lock') }}
- name: Install Dependencies
run: |
python3 -m venv venv
. venv/bin/activate
pip install -U pip
pip install poetry
poetry install
python -c "import sys; print(sys.version)"
pip list
- name: Pytest
env:
REDIS_VERSION: ${{ matrix.redis }}
run: |
. venv/bin/activate
pytest
lint:
name: flake8 & black
runs-on: ubuntu-16.04
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v1
with:
python-version: 3.7
architecture: 'x64'
- name: Cache venv
uses: actions/cache@v1
with:
path: venv
# Look to see if there is a cache hit for the corresponding requirements file
key: lintenv-v2
- name: Install Dependencies
run: |
python3 -m venv venv
. venv/bin/activate
pip install -U pip flake8 black
- name: Flake8 test
run: |
. venv/bin/activate
flake8 .
- name: Black test
run: |
. venv/bin/activate
black --check .

108
.gitignore vendored Normal file
View file

@ -0,0 +1,108 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
*.aof
# IDE
.vscode

6
.gitmodules vendored Normal file
View file

@ -0,0 +1,6 @@
[submodule "iredis/data/redis-doc"]
path = iredis/data/redis-doc
url = https://github.com/antirez/redis-doc.git
[submodule "redis-doc"]
path = redis-doc
url = https://github.com/antirez/redis-doc.git

218
CHANGELOG.md Normal file
View file

@ -0,0 +1,218 @@
### 1.9.1
- Feature: support auto-reissue command to another Redis server, when got a
"MOVED" error in redis cluster.
## 1.9
- Feature: Support `LPOS` command.
- Doc: Update docs in `HELP` command update to date.
## 1.8
- Feature: Fully support Redis6!
- Support `STRALGO` command.
- `MIGRATE` command now support `AUTH2`.
- DISABLE `hello` command, IRedis not support RESP3.
### 1.7.4
- Bugfix: Lock wcwidth's version on `1.9.0`. Fix binary build.
### 1.7.3
- Bugfix: IRedis can be suspended by <kbd>Ctrl</kbd> + <kbd>Z</kbd>. (Thanks
[wooden-robot])
- Bugfix: Press <kbd>Enter</kbd> when completion is open will not execute
commands. (Thanks [wooden-robot])
- Feature: `AUTH` command is now compatible with both Redis 5 and Redis 6.
- Redis6 support: `CLIENT KILL` support kill by `USER`, `XINFO` command support
`FULL` option.
### 1.7.2
- Feature: Support `ACL` ( [#340](https://github.com/laixintao/iredis/pull/343)
).
- Bugfix: Include tests in source distribution.
### 1.7.1
- Bugfix: `command in` considered as an invalid input case, due to matched with
`command`'s syntax, and `in` as an extra args. Fixed by falling back to
default grammar if there are ambiguous commands that can match.
## 1.7
- Update: Builtin doc was updated with latest
redis-doc(dd4159397f115d53423c21337eedb04d3258d291).
- Feature: New command support: `CLIENT GETREDIR`, `CLIENT TRACKING` and
- Test: IRedis now was tested in both Redis 5 and Redis 6.
- Bugfix: Fix exception when transaction fails. (Thanks [brianmaissy])
- Bugfix: Merging multiple spaces bug, e.g. `set foo "hello world"` will result
in sending `set foo "hello world"` to redis-server. `CLIENT CACHING`.
- Bugfix: `--url` options is ignored, but don't worry, it is fixed now by
[otms61].
### 1.6.2
- Bugfix: `INFO` command accepts `section` now.
- Bugfix: refused to start when can not create connection.
### 1.6.1
- Bugfix: Dangerous command will still run even user canceled.
## 1.6
- Feature: support pager. You can disable it using `--no-pager` or in your
`iredisrc`, or change the pager behavior by setting `pager` in `iredisrc`.
## 1.5
- Bugfix: PEEK command do not use MEMORY USAGE before redis version 4.0.
- Feature: Support disable shell pipeline feature in iredisrc. (Thanks
[wooden-robot])
### 1.4.3
- Support `LOLWUT` command of Redis 6 version.
### 1.4.2
- Password for `AUTH` command will be hidden as `*`.
### 1.4.1
- This is a test release, nothing new.
## 1.4.0
- Bugfix: Fix PyOxidizer binary build, by locking the importlib_resources
version.
### 1.3.1
- Bugfix: Fix PyOxidizer binary build.
- Feature: Completer for HELP command.
- Bugfix: Lowercase for `--newbie` mode.
- Bugfix: Bottom hint for IRedis builtin commands.
## 1.3.0
- Catch up with redis-doc: d19fb20..6927ef0:
- `SET` command support `KEEPTTL` option.
- `LPUSHX` accepts multiple elements.
- Add commands support for:
- CLUSTER BUMPEPOCH
- CLUSTER FLUSHSLOTS
- CLUSTER MYID
- MODULE LIST
- MODULE LOAD
- MODULE UNLOAD
- PSYNC
- LATENCY DOCTOR
- LATENCY GRAPH
- LATENCY HISTORY
- LATENCY LATEST
- LATENCY RESET
- LATENCY HELP
## 1.2.0
- Feature: Peek command now displays more friendly, before each "info" will take
one line, now type/encoding/ttl/memory usage will display in one line, makes
the result looks more clear.
- Support DSN. (Thanks [lyqscmy]).
- Support URL.
- Support socket connection.
### 1.1.2
- Feature: support history location config.
### 1.1.1
- This release is for testing the binary build, nothing else changed.
## 1.1
- Feature: Package into a single binary with PyOxidizer (thanks [Mac Chaffee])
### 1.0.5
- Feature: <kbd>Ctrl - X</kbd> then <kbd>Ctrl -E</kbd> to open an editor to edit
command.
- Feature: Support `completion_casing` config.
### 1.0.4
- Bugfix: command completions when a command is substring of another command.
[issue#198](https://github.com/laixintao/iredis/issues/198)
### 1.0.3
- Feature: Support `bitfield` command, and a new completer for int type.
### 1.0.2
- Internal: Migrate CI from travis and circleci to github action.
### 1.0.1
- Bugfix: Fix info command decode error on
decode=utf-8 #[266](https://github.com/laixintao/iredis/pull/266)
# 1.0
- Feature: Support `EXIT` to exit iredis REPL.
- Feature: Support `CLEAR` to clear screen.
- Feature: Support config log location in iredisrc file, default to None.
### 0.9.1
- Feature: Support `PEEK` Command.
## 0.9
- Refactor: split completer update and response render; Move cli tests to travis
ci. (Thanks: [ruohan.chen])
- Support stream commands. _ Timestamp completer support. _ Stream command
renders and lexers.
- Bugfix: When response is None,
`iredis.completers.udpate_completer_for_responase` will raise Exception.
### 0.8.12
- Bugfix: Multi spaces between commands can be recongnised as correct commands
now.
- Feature: Warning on dangerous command.
### 0.8.11
- Bugfix: Fix HELP command can not render markdown with a `<h3>` header.
- Bugfix: Pipeline using a builtin Python API.
### 0.8.10
- Bugfix: previous version of iredis didn't package redis-doc correctly.
- Feature: prompt for dangerous commands.
### 0.8.9
- Support config files.
### 0.8.8
- Bugfix: pipeline in iredis can run shell command include pipes. thanks to
[Wooden-Robot].
### 0.8.7
- Support connect shell utilities with pipeline
[wooden-robot]: https://github.com/Wooden-Robot
[ruohan.chen]: https://github.com/crhan
[mac chaffee]: https://github.com/mac-chaffee
[lyqscmy]: https://github.com/lyqscmy
[brianmaissy]: https://github.com/brianmaissy
[otms61]: https://github.com/otms61

18
Dockerfile Normal file
View file

@ -0,0 +1,18 @@
FROM python:3
WORKDIR /iredis
COPY README.md poetry.lock pyproject.toml ./
COPY iredis ./iredis
RUN apt-get update && apt-get install -y --allow-unauthenticated \
redis-server && \
rm -rf /var/lib/apt/lists/*
RUN python3 -m venv iredis_env && \
. iredis_env/bin/activate && \
pip install poetry && \
poetry install --no-dev && \
rm -rf ~/.cache
CMD ["sh","-c","redis-server --daemonize yes && . iredis_env/bin/activate && iredis"]

10
LICENSE Normal file
View file

@ -0,0 +1,10 @@
Copyright (c) 2019, laixintao
All rights reserved.
IRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* IRedistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* IRedistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of IRedis nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

228
README.md Normal file
View file

@ -0,0 +1,228 @@
<p align="center">
<img width="100" height="100" src="https://raw.githubusercontent.com/laixintao/iredis/master/docs/assets/logo.png" />
</p>
<h3 align="center">Interactive Redis: A Cli for Redis with AutoCompletion and Syntax Highlighting.</h4>
<p align="center">
<a href="https://github.com/laixintao/iredis/actions"><img src="https://github.com/laixintao/iredis/workflows/Test/badge.svg" alt="Github Action"></a>
<a href="https://badge.fury.io/py/iredis"><img src="https://badge.fury.io/py/iredis.svg" alt="PyPI version"></a>
<img src="https://badgen.net/badge/python/3.6%20|%203.7%20|%203.8/" alt="Python version">
<a href="https://pepy.tech/project/iredis"><img src="https://pepy.tech/badge/iredis" alt="Download stats"></a>
<a href="https://t.me/iredis_users"><img src="https://badgen.net/badge/icon/join?icon=telegram&amp;label=usergroup" alt="Chat on telegram"></a>
<a href="https://console.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/laixintao/iredis&amp;cloudshell_print=docs/cloudshell/run-in-docker.txt"><img src="https://badgen.net/badge/run/GoogleCloudShell/blue?icon=terminal" alt="Open in Cloud Shell"></a>
</p>
<p align="center">
<img src="./docs/assets/demo.svg" alt="demo">
</p>
IRedis is a terminal client for redis with auto-completion and syntax
highlighting. IRedis lets you type Redis commands smoothly, and displays results
in a user-friendly format.
IRedis is an alternative for redis-cli. In most cases, IRedis behaves exactly
the same as redis-cli. Besides, it is safer to use IRedis on production servers
than redis-cli: IRedis will prevent accidentally running dangerous commands,
like `KEYS *` (see
[Redis docs / Latency generated by slow commands](https://redis.io/topics/latency#latency-generated-by-slow-commands)).
## Features
- Advanced code completion. If you run command `KEYS` then run `DEL`, IRedis
will auto-complete your command based on `KEYS` result.
- Command validation. IRedis will validate command while you are typing, and
highlight errors. E.g. try `CLUSTER MEET IP PORT`, IRedis will validate IP and
PORT for you.
- Command highlighting, fully based on redis grammar. Any valid command in
IRedis shell is a valid redis command.
- Human-friendly result display.
- _pipeline_ feature, you can use your favorite shell tools to parse redis'
response, like `get json | jq .`.
- Support pager for long output.
- Support connection via URL, `iredis --url redis://example.com:6379/1`.
- Store server configuration: `iredis -d prod-redis` (see [dsn](#using-dsn) for
more).
- `peek` command to check the key's type then automatically call
`get`/`lrange`/`sscan`, etc, depending on types. You don't need to call the
`type` command then type another command to get the value. `peek` will also
display the key's length and memory usage.
- <kbd>Ctrl</kbd> + <kbd>C</kbd> to cancel the current typed command, this won't
exit IRedis, exactly like bash behaviour. Use <kbd>Ctrl</kbd> + <kbd>D</kbd>
to send a EOF to exit IRedis.
- <kbd>Ctrl</kbd> + <kbd>R</kbd> to open **reverse-i-search** to search through
your command history.
- Auto suggestions. (Like [fish shell](http://fishshell.com/).)
- Support `--encode=utf-8`, to decode Redis' bytes responses.
- Command hint on bottom, include command syntax, supported redis version, and
time complexity.
- Official docs with built-in `HELP` command, try `HELP SET`!
- Written in pure Python, but IRedis was packaged into a single binary with
[PyOxidizer](https://github.com/indygreg/PyOxidizer), you can use cURL to
download and run, it just works, even you don't have a Python interpreter.
- Hide password for `AUTH` command.
- Says "Goodbye!" to you when you exit!
- For full features, please see: [iredis.io](https://www.iredis.io)
## Install
Install via pip:
```
pip install iredis
```
[pipx](https://github.com/pipxproject/pipx) is recommended:
```
pipx install iredis
```
Or you can download the executable binary with cURL(or wget), untar, then run.
It is especially useful when you don't have a python interpreter(E.g. the
[official Redis docker image](https://hub.docker.com/_/redis/) which doesn't
have Python installed.):
```
wget https://github.com/laixintao/iredis/releases/latest/download/iredis.tar.gz \
&& tar -xzf iredis.tar.gz \
&& ./iredis
```
(Check the [release page](https://github.com/laixintao/iredis/releases) if you
want to download an old version of IRedis.)
## Usage
Once you install IRedis, you will know how to use it. Just remember, IRedis
supports similar options like redis-cli, like `-h` for redis-server's host and
`-p` for port.
```
$ iredis --help
```
### Using DSN
IRedis support storing server configuration in config file. Here is a DSN
config:
```
[alias_dsn]
dev=redis://localhost:6379/4
staging=redis://username:password@staging-redis.example.com:6379/1
```
Put this in your `iredisrc` then connect via `iredis -d staging` or
`iredis -d dev`.
### Configuration
IRedis supports config files. Command-line options will always take precedence
over config. Configuration resolution from highest to lowest precedence is:
- _Options from command line_
- `$PWD/.iredisrc`
- `~/.iredisrc` (this path can be changed with `iredis --iredisrc $YOUR_PATH`)
- `/etc/iredisrc`
- default config in IRedis package.
You can copy the _self-explained_ default config here:
https://raw.githubusercontent.com/laixintao/iredis/master/iredis/data/iredisrc
And then make your own changes.
(If you are using an old versions of IRedis, please use the config file below,
and change the version in URL):
https://raw.githubusercontent.com/laixintao/iredis/v1.0.4/iredis/data/iredisrc
### Keys
IRedis support unix/readline-style REPL keyboard shortcuts, which means keys
like <kbd>Ctrl</kbd> + <kbd>F</kbd> to forward work.
Also:
- <kbd>Ctrl</kbd> + <kbd>F</kbd> (i.e. EOF) to exit; you can also use the `exit`
command.
- <kbd>Ctrl</kbd> + <kbd>L</kbd> to clear screen; you can also use the `clear`
command.
- <kbd>Ctrl</kbd> + <kbd>X</kbd> <kbd>Ctrl</kbd> + <kbd>E</kbd> to open an
editor to edit command, or <kbd>V</kbd> in vi-mode.
## Development
### Release Strategy
IRedis is built and released by CircleCI. Whenever a tag is pushed to the
`master` branch, a new release is built and uploaded to pypi.org, it's very
convenient.
Thus, we release as often as possible, so that users can always enjoy the new
features and bugfixes quickly. Any bugfix or new feature will get at least a
patch release, whereas big features will get a minor release.
### Setup Environment
IRedis favors [poetry](https://github.com/sdispater/poetry) as package
management tool. To setup a develop environment on your computer:
First, install poetry (you can do it in a python's virtualenv):
```
pip install poetry
```
Then run (which is similar to `pip install -e .`):
```
poetry install
```
**Be careful running testcases locally, it may flush you db!!!**
### Development Logs
This is a command-line tool, so we don't write logs to stdout.
You can `tail -f ~/.iredis.log` to see logs, the log is pretty clear, you can
see what actually happens from log files.
### Catch Up with Latest Redis-doc
IRedis use a git submodule to track current-up-to-date redis-doc version. To
catch up with latest:
1. Git pull in redis-doc
2. Copy doc files to `/data`: `cp -r redis-doc/commands* iredis/data`
3. Prettier
markdown`prettier --prose-wrap always iredis/data/commands/*.md --write`
4. Check the diff, update IRedis' code if needed.
## Related Projects
- [redis-tui](https://github.com/mylxsw/redis-tui)
If you like iredis, you may also like other cli tools by
[dbcli](https://www.dbcli.com/):
- [pgcli](https://www.pgcli.com) - Postgres Client with Auto-completion and
Syntax Highlighting
- [mycli](https://www.mycli.net) - MySQL/MariaDB/Percona Client with
Auto-completion and Syntax Highlighting
- [litecli](https://litecli.com) - SQLite Client with Auto-completion and Syntax
Highlighting
- [mssql-cli](https://github.com/dbcli/mssql-cli) - Microsoft SQL Server Client
with Auto-completion and Syntax Highlighting
- [athenacli](https://github.com/dbcli/athenacli) - AWS Athena Client with
Auto-completion and Syntax Highlighting
- [vcli](https://github.com/dbcli/vcli) - VerticaDB client
- [iredis](https://github.com/laixintao/iredis/) - Client for Redis with
AutoCompletion and Syntax Highlighting
IRedis is build on the top of
[prompt_toolkit](https://github.com/jonathanslenders/python-prompt-toolkit), a
Python library (by [Jonathan Slenders](https://twitter.com/jonathan_s)) for
building rich commandline applications.

294
docs/assets/color.css Normal file
View file

@ -0,0 +1,294 @@
" Vim color file
"
" Author: Tomas Restrepo <tomas@winterdom.com>
" https://github.com/tomasr/molokai
"
" Note: Based on the Monokai theme for TextMate
" by Wimer Hazenberg and its darker variant
" by Hamish Stuart Macpherson
"
hi clear
if version > 580
" no guarantees for version 5.8 and below, but this makes it stop
" complaining
hi clear
if exists("syntax_on")
syntax reset
endif
endif
let g:colors_name="molokai"
if exists("g:molokai_original")
let s:molokai_original = g:molokai_original
else
let s:molokai_original = 0
endif
hi Boolean guifg=#AE81FF
hi Character guifg=#E6DB74
hi Number guifg=#AE81FF
hi String guifg=#E6DB74
hi Conditional guifg=#F92672 gui=bold
hi Constant guifg=#AE81FF gui=bold
hi Cursor guifg=#000000 guibg=#F8F8F0
hi iCursor guifg=#000000 guibg=#F8F8F0
hi Debug guifg=#BCA3A3 gui=bold
hi Define guifg=#66D9EF
hi Delimiter guifg=#8F8F8F
hi DiffAdd guibg=#13354A
hi DiffChange guifg=#89807D guibg=#4C4745
hi DiffDelete guifg=#960050 guibg=#1E0010
hi DiffText guibg=#4C4745 gui=italic,bold
hi Directory guifg=#A6E22E gui=bold
hi Error guifg=#E6DB74
guibg=#1E0010
hi ErrorMsg guifg=#F92672
guibg=#232526 gui=bold
hi Exception guifg=#A6E22E gui=bold
hi Float guifg=#AE81FF
hi FoldColumn guifg=#465457 guibg=#000000
hi Folded guifg=#465457 guibg=#000000
hi Function guifg=#A6E22E
hi Identifier guifg=#FD971F
hi Ignore guifg=#808080 guibg=bg
hi IncSearch guifg=#C4BE89 guibg=#000000
hi Keyword guifg=#F92672 gui=bold
hi Label guifg=#E6DB74 gui=none
hi Macro guifg=#C4BE89 gui=italic
hi SpecialKey guifg=#66D9EF gui=italic
hi MatchParen guifg=#000000 guibg=#FD971F gui=bold
hi ModeMsg guifg=#E6DB74
hi MoreMsg guifg=#E6DB74
hi Operator guifg=#F92672
" complete menu
hi Pmenu guifg=#66D9EF guibg=#000000
hi PmenuSel guibg=#808080
hi PmenuSbar guibg=#080808
hi PmenuThumb guifg=#66D9EF
hi PreCondit guifg=#A6E22E gui=bold
hi PreProc guifg=#A6E22E
hi Question guifg=#66D9EF
hi Repeat guifg=#F92672 gui=bold
hi Search guifg=#000000 guibg=#FFE792
" marks
hi SignColumn guifg=#A6E22E guibg=#232526
hi SpecialChar guifg=#F92672 gui=bold
hi SpecialComment guifg=#7E8E91 gui=bold
hi Special guifg=#66D9EF guibg=bg gui=italic
if has("spell")
hi SpellBad guisp=#FF0000 gui=undercurl
hi SpellCap guisp=#7070F0 gui=undercurl
hi SpellLocal guisp=#70F0F0 gui=undercurl
hi SpellRare guisp=#FFFFFF gui=undercurl
endif
hi Statement guifg=#F92672 gui=bold
hi StatusLine guifg=#455354 guibg=fg
hi StatusLineNC guifg=#808080 guibg=#080808
hi StorageClass guifg=#FD971F gui=italic
hi Structure guifg=#66D9EF
hi Tag guifg=#F92672 gui=italic
hi Title guifg=#ef5939
hi Todo guifg=#FFFFFF guibg=bg gui=bold
hi Typedef guifg=#66D9EF
hi Type guifg=#66D9EF gui=none
hi Underlined guifg=#808080 gui=underline
hi VertSplit guifg=#808080 guibg=#080808 gui=bold
hi VisualNOS guibg=#403D3D
hi Visual guibg=#403D3D
hi WarningMsg guifg=#FFFFFF guibg=#333333 gui=bold
hi WildMenu guifg=#66D9EF guibg=#000000
hi TabLineFill guifg=#1B1D1E guibg=#1B1D1E
hi TabLine guibg=#1B1D1E guifg=#808080 gui=none
if s:molokai_original == 1
hi Normal guifg=#F8F8F2 guibg=#272822
hi Comment guifg=#75715E
hi CursorLine guibg=#3E3D32
hi CursorLineNr guifg=#FD971F gui=none
hi CursorColumn guibg=#3E3D32
hi ColorColumn guibg=#3B3A32
hi LineNr guifg=#BCBCBC guibg=#3B3A32
hi NonText guifg=#75715E
hi SpecialKey guifg=#75715E
else
hi Normal guifg=#F8F8F2 guibg=#1B1D1E
hi Comment guifg=#7E8E91
hi CursorLine guibg=#293739
hi CursorLineNr guifg=#FD971F gui=none
hi CursorColumn guibg=#293739
hi ColorColumn guibg=#232526
hi LineNr guifg=#465457 guibg=#232526
hi NonText guifg=#465457
hi SpecialKey guifg=#465457
end
"
" Support for 256-color terminal
"
if &t_Co > 255
if s:molokai_original == 1
hi Normal ctermbg=234
hi CursorLine ctermbg=235 cterm=none
hi CursorLineNr ctermfg=208 cterm=none
else
hi Normal ctermfg=252 ctermbg=233
hi CursorLine ctermbg=234 cterm=none
hi CursorLineNr ctermfg=208 cterm=none
endif
hi Boolean ctermfg=135
hi Character ctermfg=144
hi Number ctermfg=135
hi String ctermfg=144
hi Conditional ctermfg=161 cterm=bold
hi Constant ctermfg=135 cterm=bold
hi Cursor ctermfg=16 ctermbg=253
hi Debug ctermfg=225 cterm=bold
hi Define ctermfg=81
hi Delimiter ctermfg=241
hi DiffAdd ctermbg=24
hi DiffChange ctermfg=181 ctermbg=239
hi DiffDelete ctermfg=162 ctermbg=53
hi DiffText ctermbg=102 cterm=bold
hi Directory ctermfg=118 cterm=bold
hi Error ctermfg=219 ctermbg=89
hi ErrorMsg ctermfg=199 ctermbg=16 cterm=bold
hi Exception ctermfg=118 cterm=bold
hi Float ctermfg=135
hi FoldColumn ctermfg=67 ctermbg=16
hi Folded ctermfg=67 ctermbg=16
hi Function ctermfg=118
hi Identifier ctermfg=208 cterm=none
hi Ignore ctermfg=244 ctermbg=232
hi IncSearch ctermfg=193 ctermbg=16
hi keyword ctermfg=161 cterm=bold
hi Label ctermfg=229 cterm=none
hi Macro ctermfg=193
hi SpecialKey ctermfg=81
hi MatchParen ctermfg=233 ctermbg=208 cterm=bold
hi ModeMsg ctermfg=229
hi MoreMsg ctermfg=229
hi Operator ctermfg=161
" complete menu
hi Pmenu ctermfg=81 ctermbg=16
hi PmenuSel ctermfg=255 ctermbg=242
hi PmenuSbar ctermbg=232
hi PmenuThumb ctermfg=81
hi PreCondit ctermfg=118 cterm=bold
hi PreProc ctermfg=118
hi Question ctermfg=81
hi Repeat ctermfg=161 cterm=bold
hi Search ctermfg=0 ctermbg=222 cterm=NONE
" marks column
hi SignColumn ctermfg=118 ctermbg=235
hi SpecialChar ctermfg=161 cterm=bold
hi SpecialComment ctermfg=245 cterm=bold
hi Special ctermfg=81
if has("spell")
hi SpellBad ctermbg=52
hi SpellCap ctermbg=17
hi SpellLocal ctermbg=17
hi SpellRare ctermfg=none ctermbg=none cterm=reverse
endif
hi Statement ctermfg=161 cterm=bold
hi StatusLine ctermfg=238 ctermbg=253
hi StatusLineNC ctermfg=244 ctermbg=232
hi StorageClass ctermfg=208
hi Structure ctermfg=81
hi Tag ctermfg=161
hi Title ctermfg=166
hi Todo ctermfg=231 ctermbg=232 cterm=bold
hi Typedef ctermfg=81
hi Type ctermfg=81 cterm=none
hi Underlined ctermfg=244 cterm=underline
hi VertSplit ctermfg=244 ctermbg=232 cterm=bold
hi VisualNOS ctermbg=238
hi Visual ctermbg=235
hi WarningMsg ctermfg=231 ctermbg=238 cterm=bold
hi WildMenu ctermfg=81 ctermbg=16
hi Comment ctermfg=59
hi CursorColumn ctermbg=236
hi ColorColumn ctermbg=236
hi LineNr ctermfg=250 ctermbg=236
hi NonText ctermfg=59
hi SpecialKey ctermfg=59
if exists("g:rehash256") && g:rehash256 == 1
hi Normal ctermfg=252 ctermbg=234
hi CursorLine ctermbg=236 cterm=none
hi CursorLineNr ctermfg=208 cterm=none
hi Boolean ctermfg=141
hi Character ctermfg=222
hi Number ctermfg=141
hi String ctermfg=222
hi Conditional ctermfg=197 cterm=bold
hi Constant ctermfg=141 cterm=bold
hi DiffDelete ctermfg=125 ctermbg=233
hi Directory ctermfg=154 cterm=bold
hi Error ctermfg=222 ctermbg=233
hi Exception ctermfg=154 cterm=bold
hi Float ctermfg=141
hi Function ctermfg=154
hi Identifier ctermfg=208
hi Keyword ctermfg=197 cterm=bold
hi Operator ctermfg=197
hi PreCondit ctermfg=154 cterm=bold
hi PreProc ctermfg=154
hi Repeat ctermfg=197 cterm=bold
hi Statement ctermfg=197 cterm=bold
hi Tag ctermfg=197
hi Title ctermfg=203
hi Visual ctermbg=238
hi Comment ctermfg=244
hi LineNr ctermfg=239 ctermbg=235
hi NonText ctermfg=239
hi SpecialKey ctermfg=239
endif
end
" Must be at the end, because of ctermbg=234 bug.
" https://groups.google.com/forum/#!msg/vim_dev/afPqwAFNdrU/nqh6tOM87QUJ
set background=dark
[colors]
bottom-toolbar = 'bg:#222222 #aaaaaa'
bottom-toolbar.off = 'bg:#222222 #888888'
bottom-toolbar.on = 'bg:#222222 #ffffff'
bottom-toolbar.transaction.valid = 'bg:#222222 #00ff5f bold'
bottom-toolbar.transaction.failed = 'bg:#222222 #ff005f bold'
# style classes for colored table output
output.header = "#00ff5f bold"
output.odd-row = ""
output.even-row = ""

386
docs/assets/demo.cast Normal file
View file

@ -0,0 +1,386 @@
{"version": 2, "width": 66, "height": 20}
[0.0, "o", "\u001b]1337;RemoteHost=laixintao@Chico.local\u0007\u001b]1337;CurrentDir=/Users/laixintao\u0007\u001b]1337;ShellIntegrationVersion=6;shell=zsh\u0007"]
[0.034194, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[0.054034, "o", "\u001b]133;D;0\u0007\u001b]1337;RemoteHost=laixintao@Chico.local\u0007\u001b]1337;CurrentDir=/Users/laixintao\u0007"]
[0.05708, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b]133;A\u0007$ \u001b]133;B\u0007\u001b[K"]
[0.057424, "o", "\u001b[?1h\u001b="]
[0.057603, "o", "\u001b[?2004h"]
[1.404776, "o", "i"]
[1.709882, "o", "\bir"]
[1.805422, "o", "e"]
[1.957047, "o", "d"]
[2.073349, "o", "i"]
[2.170964, "o", "s"]
[2.308611, "o", "\u001b[?1l\u001b>"]
[2.308783, "o", "\u001b[?2004l\r\r\n"]
[2.311011, "o", "\u001b]133;C;\u0007"]
[2.589501, "o", "\u001b[0m\u001b[?7h\u001b[0miredis 0.8.0\r\r\nredis-server 5.0.6 \r\r\nHome: https://iredis.io\r\r\nIssues: https://iredis.io/issues\u001b[0m\u001b[0m"]
[2.591596, "o", "\u001b[0m\u001b[?7h\u001b[0m\r\r\n\u001b[0m"]
[2.592274, "o", "\u001b[?1l"]
[2.592372, "o", "\u001b[6n"]
[2.599827, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \r\u001b[65C \r\u001b[7A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[2.617441, "o", "\u001b[?25l\u001b[?7l\u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \r\u001b[65C \u001b[0m\r\r\n\u001b[0;38;5;248;48;5;235mCtrl-D to exit; \r\u001b[65C \r\u001b[14A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[3.516857, "o", "\u001b[?25l\u001b[?7l\u001b[0mk \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m KEYS \u001b[0;38;5;16;48;5;238m \u001b[A\u001b[8D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[3.522751, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241meys *\u001b[0m \u001b[6D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[3.626927, "o", "\u001b[?25l\u001b[?7l\u001b[0me \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m KEYS\u001b[C \u001b[0;38;5;16;48;5;238m \u001b[A\u001b[8D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[3.632562, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mys *\u001b[0m \u001b[5D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[3.750562, "o", "\u001b[?25l\u001b[?7l\u001b[0my \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m KEYS\u001b[C \u001b[0;38;5;16;48;5;238m \u001b[A\u001b[8D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[3.755718, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241ms *\u001b[0m \u001b[4D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[3.859462, "o", "\u001b[?25l\u001b[?7l\u001b[3D\u001b[0;38;5;28;1mkeys\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m KEYS\u001b[C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;167;48;5;235;1m(generic) \u001b[0;38;5;28;48;5;235;1mKEYS\u001b[0;38;5;71;48;5;235;1m pattern\u001b[0;38;5;136;48;5;235m since: 1.0.0\u001b[0;38;5;241;48;5;235m complexity:O(N) with N bein\r\u001b[65Cg\u001b[14A\r\u001b[20C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[3.864549, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241m *\u001b[0m \u001b[3D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[4.112384, "o", "\u001b[?25l\u001b[?7l\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[A\u001b[20C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[4.121805, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241m*\u001b[0m \u001b[2D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[4.49726, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71;1m*\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[4.503062, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[4.796083, "o", "\u001b[?25l\u001b[?7l\u001b[22D\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0;38;5;28;1mkeys\u001b[0m \u001b[0;38;5;71;1m*\u001b[0m \r\u001b[65C \r\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
[4.80137, "o", "\u001b[0m\u001b[?7h\u001b[0m 1)\u001b[0m \u001b[0;38;5;71m\"myzset\"\u001b[0m\r\r\n\u001b[0m 2)\u001b[0m \u001b[0;38;5;71m\"mylist1\"\u001b[0m\r\r\n\u001b[0m 3)\u001b[0m \u001b[0;38;5;71m\"abc\"\u001b[0m\r\r\n\u001b[0m 4)\u001b[0m \u001b[0;38;5;71m\"a\"\u001b[0m\r\r\n\u001b[0m 5)\u001b[0m \u001b[0;38;5;71m\"mstream\"\u001b[0m\r\r\n\u001b[0m 6)\u001b[0m \u001b[0;38;5;71m\"testKeyDB2\"\u001b[0m\r\r\n\u001b[0m 7)\u001b[0m \u001b[0;38;5;71m\"list:restaurant\"\u001b[0m\r\r\n\u001b[0m 8)\u001b[0m \u001b[0;38;5;71m\"Sicily\"\u001b[0m\r\r\n\u001b[0m 9)\u001b[0m \u001b[0;38;5;71m\"cars\"\u001b[0m\r\r\n\u001b[0m10)\u001b[0m \u001b[0;38;5;71m\"hash1\"\u001b[0m\r\r\n\u001b[0m11)\u001b[0m \u001b[0;38;5;71m\"list:buildings\"\u001b[0m\r\r\n\u001b[0m12)\u001b[0m \u001b[0;38;5;71m\"hash3\"\u001b[0m\r\r\n\u001b[0m13)\u001b[0m \u001b[0;38;5;71m\"fooset\"\u001b[0m\r\r\n\u001b[0m14)\u001b[0m \u001b[0;38;5;71m\"foo\"\u001b[0m\r\r\n\u001b[0m15)\u001b[0m \u001b[0;38;5;71m\"myset\"\u001b[0m\r\r\n\u001b[0m16)\u001b[0m \u001b[0;38;5;71m\"hash2\"\u001b[0m\r\r\n\u001b[0m17)\u001b[0m \u001b[0;38;5;71m\"list:animals\"\u001b[0m\r\r\n\u001b[0m18)\u001b[0m \u001b[0;38;5;71m\"af\"\u001b[0m\r\r\n\u001b[0m19)\u001b[0m \u001b[0;38;5;71m\"somestream\"\u001b[0m\r\r\n\u001b[0m20)\u001b[0m \u001b[0;38;5;71m\"kkk\"\u001b[0m\u001b[0m"]
[4.803285, "o", "\u001b[0m\u001b[?7h\u001b[0m\r\r\n\u001b[0m"]
[4.804015, "o", "\u001b[?1l"]
[4.804128, "o", "\u001b[6n"]
[4.807349, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \r\u001b[65C \r\u001b[7A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[4.812015, "o", "\u001b[?25l\u001b[?7l\u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;248;48;5;235mCtrl-D to exit; \r\u001b[65C \r\u001b[8A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[5.558691, "o", "\u001b[?25l\u001b[?7l\u001b[0mt \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m TTL \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m TIME \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m TYPE \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m TOUCH \u001b[0;38;5;16;48;5;238m \u001b[4A\u001b[8D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[5.562825, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mype myset\u001b[0m \u001b[10D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[5.802861, "o", "\u001b[?25l\u001b[?7l\u001b[0my \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m TYPE \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[4A\u001b[17C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[5.807952, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mpe myset\u001b[0m \u001b[9D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[6.011176, "o", "\u001b[?25l\u001b[?7l\u001b[0mp \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m TYPE\u001b[C \u001b[0;38;5;16;48;5;238m \u001b[A\u001b[8D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[6.01494, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241me myset\u001b[0m \u001b[8D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[6.1425, "o", "\u001b[?25l\u001b[?7l\u001b[3D\u001b[0;38;5;28;1mtype\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m TYPE\u001b[C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;167;48;5;235;1m(generic) \u001b[0;38;5;28;48;5;235;1mTYPE\u001b[0;38;5;71;48;5;235m key\u001b[0;38;5;136;48;5;235m since: 1.0.0\u001b[0;38;5;241;48;5;235m complexity:O(1)\u001b[8A\u001b[29D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[6.14741, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241m myset\u001b[0m \u001b[7D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[6.244466, "o", "\u001b[?25l\u001b[?7l\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m kkk \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m somestream \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m af \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m list:animals \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m hash2 \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m myset \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m foo \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[6.253095, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mmyset\u001b[0m \u001b[6D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[6.712731, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mm\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mm\u001b[0;38;5;238;48;5;30myset\u001b[8C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mm\u001b[0;38;5;238;48;5;30mstream\u001b[0;38;5;231;48;5;30m \u001b[4C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mm\u001b[0;38;5;238;48;5;30mylist1\u001b[6C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mm\u001b[0;38;5;238;48;5;30myzset\u001b[0;38;5;231;48;5;30m \u001b[2C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mso\u001b[0;38;5;231;48;5;30;1;4mm\u001b[0;38;5;238;48;5;30mestream\u001b[3C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mlist:ani\u001b[0;38;5;231;48;5;30;1;4mm\u001b[0;38;5;238;48;5;30mals\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[7A\u001b[17D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[6.718052, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241myset\u001b[0m \u001b[5D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[9.031011, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71my\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[22C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mmy\u001b[0;38;5;238;48;5;30mset\u001b[3C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[22C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mmy\u001b[0;38;5;238;48;5;30mlist1\u001b[C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[22C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mmy\u001b[0;38;5;238;48;5;30mzset\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[6A\u001b[22C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[9.048076, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mset\u001b[0m \u001b[4D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[9.337635, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mset\u001b[0m\r\r\n\u001b[23C\u001b[0;38;5;16;48;5;231m \u001b[0;38;5;16;48;5;231;4mmy\u001b[0;48;5;231mset\u001b[0;38;5;16;48;5;231m \u001b[A\u001b[6D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[9.341531, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[9.587591, "o", "\u001b[?25l\u001b[?7l\u001b[26D\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0;38;5;28;1mtype\u001b[0m \u001b[0;38;5;71mmyset\u001b[0m \r\u001b[65C \r\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
[9.592731, "o", "\u001b[0m\u001b[?7h\u001b[0m\"set\"\u001b[0m\u001b[0m"]
[9.595501, "o", "\u001b[0m\u001b[?7h\u001b[0m\r\r\n\u001b[0m"]
[9.596128, "o", "\u001b[?1l"]
[9.59625, "o", "\u001b[6n"]
[9.598996, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \r\u001b[65C \r\u001b[7A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[9.602329, "o", "\u001b[?25l\u001b[?7l\u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;248;48;5;235mCtrl-D to exit; \r\u001b[65C \r\u001b[8A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[10.630148, "o", "\u001b[?25l\u001b[?7l\u001b[0ms \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m SET \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m SREM \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m SPOP \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m SADD \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m SYNC \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m SAVE \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m SORT \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[16D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[10.635184, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241met foo bar\u001b[0m \u001b[11D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[11.02782, "o", "\u001b[?25l\u001b[?7l\u001b[0mm \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m SMOVE\u001b[4C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m SMEMBERS \u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[7A\u001b[15D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[11.031124, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241members fooset\u001b[0m \u001b[14D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[11.103554, "o", "\u001b[?25l\u001b[?7l\u001b[0me \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m SMEMBERS \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[2A\u001b[18C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[11.107347, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mmbers fooset\u001b[0m \u001b[13D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[11.198052, "o", "\u001b[?25l\u001b[?7l\u001b[0mm \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m SMEMBERS \u001b[0;38;5;16;48;5;238m \u001b[A\u001b[11D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[11.201012, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mbers fooset\u001b[0m \u001b[12D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[11.568186, "o", "\u001b[?25l\u001b[?7l\u001b[4D\u001b[0;38;5;28;1mSMEMBERS\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[20C\u001b[0;38;5;16;48;5;231m SMEMBERS \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;167;48;5;235;1m(set) \u001b[0;38;5;28;48;5;235;1mSMEMBERS\u001b[0;38;5;71;48;5;235m key\u001b[0;38;5;136;48;5;235m since: 1.0.0\u001b[0;38;5;241;48;5;235m complexity:O(N) where N is the \r\u001b[65Cs\u001b[8A\r\u001b[24C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[11.572482, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[11.824354, "o", "\u001b[?25l\u001b[?7l\u001b[C\u001b[0m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m myset \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[25C\u001b[0;38;5;231;48;5;30m kkk \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[25C\u001b[0;38;5;231;48;5;30m somestream \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[25C\u001b[0;38;5;231;48;5;30m af \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[25C\u001b[0;38;5;231;48;5;30m list:animals \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[25C\u001b[0;38;5;231;48;5;30m hash2 \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[25C\u001b[0;38;5;231;48;5;30m foo \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[11.828823, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mmyset\u001b[0m \u001b[6D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[12.363643, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mm\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[25C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mm\u001b[0;38;5;238;48;5;30myset\u001b[8C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[25C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mm\u001b[0;38;5;238;48;5;30mstream\u001b[6C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[25C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mm\u001b[0;38;5;238;48;5;30mylist1\u001b[0;38;5;231;48;5;30m \u001b[4C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[25C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mm\u001b[0;38;5;238;48;5;30myzset\u001b[7C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[25C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mso\u001b[0;38;5;231;48;5;30;1;4mm\u001b[0;38;5;238;48;5;30mestream\u001b[0;38;5;231;48;5;30m \u001b[2C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[25C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mlist:ani\u001b[0;38;5;231;48;5;30;1;4mm\u001b[0;38;5;238;48;5;30mals\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[25C\u001b[0m \u001b[7A\u001b[17D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[12.368073, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241myset\u001b[0m \u001b[5D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[12.639026, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71my\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[26C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mmy\u001b[0;38;5;238;48;5;30mset\u001b[3C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[26C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mmy\u001b[0;38;5;238;48;5;30mlist1\u001b[C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[26C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mmy\u001b[0;38;5;238;48;5;30mzset\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[6A\u001b[26C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[12.642963, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mset\u001b[0m \u001b[4D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[12.84344, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mset\u001b[0m\r\r\n\u001b[27C\u001b[0;38;5;16;48;5;231m \u001b[0;38;5;16;48;5;231;4mmy\u001b[0;48;5;231mset\u001b[0;38;5;16;48;5;231m \u001b[A\u001b[6D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[12.847415, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[13.250149, "o", "\u001b[?25l\u001b[?7l\u001b[30D\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0;38;5;28;1mSMEMBERS\u001b[0m \u001b[0;38;5;71mmyset\u001b[0m \r\u001b[65C \r\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
[13.255777, "o", "\u001b[0m\u001b[?7h\u001b[0m1)\u001b[0m \u001b[0;38;5;208m\"foo\"\u001b[0m\r\r\n\u001b[0m2)\u001b[0m \u001b[0;38;5;208m\"world\"\u001b[0m\r\r\n\u001b[0m3)\u001b[0m \u001b[0;38;5;208m\"bar\"\u001b[0m\r\r\n\u001b[0m4)\u001b[0m \u001b[0;38;5;208m\"hello\"\u001b[0m\u001b[0m"]
[13.257576, "o", "\u001b[0m\u001b[?7h\u001b[0m\r\r\n\u001b[0m"]
[13.25837, "o", "\u001b[?1l\u001b[6n"]
[13.260811, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \r\u001b[65C \r\u001b[7A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[13.264088, "o", "\u001b[?25l\u001b[?7l\u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;248;48;5;235mCtrl-D to exit; \r\u001b[65C \r\u001b[8A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[15.018012, "o", "\u001b[?25l\u001b[?7l\u001b[0mt \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m TTL \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m TIME \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m TYPE \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m TOUCH \u001b[0;38;5;16;48;5;238m \u001b[4A\u001b[8D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[15.021342, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mype myset\u001b[0m \u001b[10D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[15.136615, "o", "\u001b[?25l\u001b[?7l\u001b[0my \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m TYPE \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[4A\u001b[17C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[15.139872, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mpe myset\u001b[0m \u001b[9D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[15.301704, "o", "\u001b[?25l\u001b[?7l\u001b[0mp \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m TYPE\u001b[C \u001b[0;38;5;16;48;5;238m \u001b[A\u001b[8D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[15.305109, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241me myset\u001b[0m \u001b[8D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[15.381888, "o", "\u001b[?25l\u001b[?7l\u001b[3D\u001b[0;38;5;28;1mtype\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m TYPE\u001b[C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;167;48;5;235;1m(generic) \u001b[0;38;5;28;48;5;235;1mTYPE\u001b[0;38;5;71;48;5;235m key\u001b[0;38;5;136;48;5;235m since: 1.0.0\u001b[0;38;5;241;48;5;235m complexity:O(1)\u001b[8A\u001b[29D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[15.387144, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241m myset\u001b[0m \u001b[7D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[15.493325, "o", "\u001b[?25l\u001b[?7l\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m myset \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m kkk \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m somestream \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m af \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m list:animals \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m hash2 \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m foo \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[15.498349, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mmyset\u001b[0m \u001b[6D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[15.792526, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mc\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mc\u001b[0;38;5;238;48;5;30mars\u001b[3C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mSi\u001b[0;38;5;231;48;5;30;1;4mc\u001b[0;38;5;238;48;5;30mily\u001b[C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mab\u001b[0;38;5;231;48;5;30;1;4mc\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[7A\u001b[17D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[15.797662, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mars\u001b[0m \u001b[4D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[15.880584, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71ma\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[22C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mca\u001b[0;38;5;238;48;5;30mrs\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[3A\u001b[22C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[15.885051, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mrs\u001b[0m \u001b[3D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[16.48149, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mrs\u001b[0m\r\r\n\u001b[23C\u001b[0;38;5;16;48;5;231m \u001b[0;38;5;16;48;5;231;4mca\u001b[0;48;5;231mrs\u001b[0;38;5;16;48;5;231m \u001b[A\u001b[5D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[16.486215, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[16.848883, "o", "\u001b[?25l\u001b[?7l\u001b[25D\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0;38;5;28;1mtype\u001b[0m \u001b[0;38;5;71mcars\u001b[0m \r\u001b[65C \r\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
[16.852972, "o", "\u001b[0m\u001b[?7h\u001b[0m\"zset\"\u001b[0m\u001b[0m"]
[16.854529, "o", "\u001b[0m\u001b[?7h\u001b[0m\r\r\n\u001b[0m"]
[16.855119, "o", "\u001b[?1l\u001b[6n"]
[16.858567, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \r\u001b[65C \r\u001b[7A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[16.862469, "o", "\u001b[?25l\u001b[?7l\u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;248;48;5;235mCtrl-D to exit; \r\u001b[65C \r\u001b[8A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[17.715952, "o", "\u001b[?25l\u001b[?7l\u001b[0mz \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m ZREM \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m ZADD \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m ZSCAN \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m ZRANK \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m ZCARD \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m ZSCORE \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m ZRANGE \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[19D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[17.72013, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mscan kkk 0\u001b[0m \u001b[11D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[17.977719, "o", "\u001b[?25l\u001b[?7l\u001b[0ms \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m ZSCAN\u001b[2C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m ZSCORE \u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[7A\u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[17.981853, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mcan kkk 0\u001b[0m \u001b[10D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[18.172029, "o", "\u001b[?25l\u001b[?7l\u001b[0mc \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m ZSCAN\u001b[C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m ZSCORE \u001b[0;38;5;16;48;5;238m \u001b[2A\u001b[9D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[18.176161, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241man kkk 0\u001b[0m \u001b[9D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[18.555927, "o", "\u001b[?25l\u001b[?7l\u001b[3D\u001b[0;38;5;28;1mZSCAN\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[19C\u001b[0;38;5;16;48;5;231m ZSCAN \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;167;48;5;235;1m(sorted_set) \u001b[0;38;5;28;48;5;235;1mZSCAN\u001b[0;38;5;71;48;5;235m key\u001b[0;38;5;141;48;5;235m cursor\u001b[0;38;5;28;48;5;235;1m [MATCH\u001b[0;38;5;71;48;5;235;1m pattern\u001b[0;38;5;28;48;5;235;1m] [COUNT\u001b[0;38;5;141;48;5;235m count\u001b[0;38;5;28;48;5;235;1m]\u001b[0;38;5;136;48;5;235m sin\r\u001b[65Cc\u001b[8A\r\u001b[21C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[18.5603, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[18.890406, "o", "\u001b[?25l\u001b[?7l\u001b[C\u001b[0m \u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m cars \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m myset \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[22C\u001b[0;38;5;231;48;5;30m kkk \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[22C\u001b[0;38;5;231;48;5;30m somestream \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[22C\u001b[0;38;5;231;48;5;30m af \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[22C\u001b[0;38;5;231;48;5;30m list:animals \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[22C\u001b[0;38;5;231;48;5;30m hash2 \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[18.895303, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mcars 0\u001b[0m \u001b[7D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[19.875157, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mcars\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[22C\u001b[0;38;5;16;48;5;231m cars \u001b[A\u001b[13D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[19.880817, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[20.080926, "o", "\u001b[?25l\u001b[?7l\u001b[26D\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0;38;5;28;1mZSCAN\u001b[0m \u001b[0;38;5;71mcars\u001b[0m \r\u001b[65C \r\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
[20.089992, "o", "\u001b[0m\u001b[?7h\u001b[0;38;5;102m(error) \u001b[0;38;5;197;1mwrong number of arguments for 'zscan' command\u001b[0m\u001b[0m"]
[20.091761, "o", "\u001b[0m\u001b[?7h\u001b[0m\r\r\n\u001b[0m"]
[20.092544, "o", "\u001b[?1l\u001b[6n"]
[20.101137, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \r\u001b[65C \r\u001b[7A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[20.109017, "o", "\u001b[?25l\u001b[?7l\u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;248;48;5;235mCtrl-D to exit; \r\u001b[65C \r\u001b[8A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[21.330573, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;28;1mZSCAN\u001b[0m \u001b[0;38;5;71mcars\u001b[0m \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;167;48;5;235;1m(sorted_set) \u001b[0;38;5;28;48;5;235;1mZSCAN\u001b[0;38;5;71;48;5;235m key\u001b[0;38;5;141;48;5;235m cursor\u001b[0;38;5;28;48;5;235;1m [MATCH\u001b[0;38;5;71;48;5;235;1m pattern\u001b[0;38;5;28;48;5;235;1m] [COUNT\u001b[0;38;5;141;48;5;235m count\u001b[0;38;5;28;48;5;235;1m]\u001b[0;38;5;136;48;5;235m sin\r\u001b[65Cc\u001b[8A\r\u001b[26C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[21.340066, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[21.796556, "o", "\u001b[?25l\u001b[?7l\u001b[C\u001b[0m \b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[21.800207, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241m0\u001b[0m \u001b[2D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[22.451791, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;141m0\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[22.460219, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[23.089162, "o", "\u001b[?25l\u001b[?7l\u001b[28D\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0;38;5;28;1mZSCAN\u001b[0m \u001b[0;38;5;71mcars\u001b[0m \u001b[0;38;5;141m0\u001b[0m \r\u001b[65C \r\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
[23.100459, "o", "\u001b[0m\u001b[?7h\u001b[0;38;5;102m(cursor) \u001b[0;38;5;141m0\u001b[0m\r\r\n\u001b[0m1)\u001b[0m \u001b[0;38;5;141m1367522908124000 \u001b[0;38;5;208m\"robins-car\"\u001b[0m\u001b[0m"]
[23.102113, "o", "\u001b[0m\u001b[?7h\u001b[0m\r\r\n\u001b[0m"]
[23.102697, "o", "\u001b[?1l"]
[23.102802, "o", "\u001b[6n"]
[23.105131, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \r\u001b[65C \r\u001b[7A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[23.108162, "o", "\u001b[?25l\u001b[?7l\u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;248;48;5;235mCtrl-D to exit; \r\u001b[65C \r\u001b[8A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[25.209658, "o", "\u001b[?25l\u001b[?7l\u001b[0mt \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m TTL \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m TIME \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m TYPE \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m TOUCH \u001b[0;38;5;16;48;5;238m \u001b[4A\u001b[8D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[25.218301, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mype cars\u001b[0m \u001b[9D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[25.35018, "o", "\u001b[?25l\u001b[?7l\u001b[0my \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m TYPE \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[4A\u001b[17C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[25.354149, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mpe cars\u001b[0m \u001b[8D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[25.529032, "o", "\u001b[?25l\u001b[?7l\u001b[0mp \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m TYPE\u001b[C \u001b[0;38;5;16;48;5;238m \u001b[A\u001b[8D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[25.533149, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241me cars\u001b[0m \u001b[7D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[25.630584, "o", "\u001b[?25l\u001b[?7l\u001b[3D\u001b[0;38;5;28;1mtype\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m TYPE\u001b[C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;167;48;5;235;1m(generic) \u001b[0;38;5;28;48;5;235;1mTYPE\u001b[0;38;5;71;48;5;235m key\u001b[0;38;5;136;48;5;235m since: 1.0.0\u001b[0;38;5;241;48;5;235m complexity:O(1)\u001b[8A\u001b[29D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[25.634871, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241m cars\u001b[0m \u001b[6D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[25.742093, "o", "\u001b[?25l\u001b[?7l\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m cars \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m myset \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m kkk \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m somestream \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m af \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m list:animals \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m hash2 \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[25.750379, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mcars\u001b[0m \u001b[5D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[25.984703, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71ml\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ml\u001b[0;38;5;238;48;5;30mist:animals\u001b[3C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ml\u001b[0;38;5;238;48;5;30mist:buildings\u001b[C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ml\u001b[0;38;5;238;48;5;30mist:restaurant\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mmy\u001b[0;38;5;231;48;5;30;1;4ml\u001b[0;38;5;238;48;5;30mist1\u001b[0;38;5;231;48;5;30m \u001b[6C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mSici\u001b[0;38;5;231;48;5;30;1;4ml\u001b[0;38;5;238;48;5;30my\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[7A\u001b[17D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[25.988778, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mist:animals\u001b[0m \u001b[12D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[26.206791, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mi\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[22C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mli\u001b[0;38;5;238;48;5;30mst:animals\u001b[3C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[22C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mli\u001b[0;38;5;238;48;5;30mst:buildings\u001b[C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[22C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mli\u001b[0;38;5;238;48;5;30mst:restaurant\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[22C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mmy\u001b[0;38;5;231;48;5;30;1;4mli\u001b[0;38;5;238;48;5;30mst1\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[5A\u001b[22C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[26.213146, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mst:animals\u001b[0m \u001b[11D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[26.905529, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mst:animals\u001b[0m\r\r\n\u001b[23C\u001b[0;38;5;16;48;5;231m \u001b[0;38;5;16;48;5;231;4mli\u001b[0;48;5;231mst:animals\u001b[0;38;5;16;48;5;231m \u001b[A\u001b[7D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[26.915375, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[27.735947, "o", "\u001b[?25l\u001b[?7l\u001b[33D\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0;38;5;28;1mtype\u001b[0m \u001b[0;38;5;71mlist:animals\u001b[0m \r\u001b[65C \r\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
[27.740639, "o", "\u001b[0m\u001b[?7h\u001b[0m\"list\"\u001b[0m\u001b[0m"]
[27.743095, "o", "\u001b[0m\u001b[?7h\u001b[0m\r\r\n\u001b[0m"]
[27.743675, "o", "\u001b[?1l"]
[27.74377, "o", "\u001b[6n"]
[27.746106, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \r\u001b[65C \r\u001b[7A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[27.750128, "o", "\u001b[?25l\u001b[?7l\u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;248;48;5;235mCtrl-D to exit; \r\u001b[65C \r\u001b[8A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[28.104759, "o", "\u001b[?25l\u001b[?7l\u001b[0ml \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m LSET \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m LREM \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m LPOP \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m LLEN \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m LTRIM \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m LPUSH \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m LRANGE \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[11D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[28.108111, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mrange list:animals 0 6\u001b[0m \u001b[23D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[28.254897, "o", "\u001b[?25l\u001b[?7l\u001b[0ml \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m LLEN \u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[7A\u001b[10D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[28.259097, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241men testKeyDB2\u001b[0m \u001b[14D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[28.758049, "o", "\u001b[?25l\u001b[?7l\u001b[2D\u001b[0;38;5;28;1mLLEN\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0;38;5;16;48;5;231m LLEN \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;167;48;5;235;1m(list) \u001b[0;38;5;28;48;5;235;1mLLEN\u001b[0;38;5;71;48;5;235m key\u001b[0;38;5;136;48;5;235m since: 1.0.0\u001b[0;38;5;241;48;5;235m complexity:O(1)\u001b[8A\u001b[26D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[28.762078, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[28.981063, "o", "\u001b[?25l\u001b[?7l\u001b[C\u001b[0m \u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m list:animals \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m cars \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m myset \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m kkk \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m somestream \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m af \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m hash2 \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[28.987768, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mlist:animals\u001b[0m \u001b[13D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[29.889228, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mi\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30ml\u001b[0;38;5;231;48;5;30;1;4mi\u001b[0;38;5;238;48;5;30mst:animals\u001b[3C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30ml\u001b[0;38;5;231;48;5;30;1;4mi\u001b[0;38;5;238;48;5;30mst:buildings\u001b[C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mS\u001b[0;38;5;231;48;5;30;1;4mi\u001b[0;38;5;238;48;5;30mcily\u001b[9C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30ml\u001b[0;38;5;231;48;5;30;1;4mi\u001b[0;38;5;238;48;5;30mst:restaurant\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mmyl\u001b[0;38;5;231;48;5;30;1;4mi\u001b[0;38;5;238;48;5;30mst1\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[7A\u001b[17D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[29.895168, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[30.516043, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[5A\u001b[20C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[30.520459, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[30.845385, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71ml\u001b[0m \u001b[0m\r\r\n\u001b[22C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ml\u001b[0;38;5;238;48;5;30mist:animals\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[22C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ml\u001b[0;38;5;238;48;5;30mist:buildings\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[22C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ml\u001b[0;38;5;238;48;5;30mist:restaurant\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[22C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mmy\u001b[0;38;5;231;48;5;30;1;4ml\u001b[0;38;5;238;48;5;30mist1\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[22C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mSici\u001b[0;38;5;231;48;5;30;1;4ml\u001b[0;38;5;238;48;5;30my\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[5A\u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[30.84925, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mist:animals\u001b[0m \u001b[12D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[31.051905, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mi\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[22C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mli\u001b[0;38;5;238;48;5;30mst:animals\u001b[3C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[22C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mli\u001b[0;38;5;238;48;5;30mst:buildings\u001b[C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[22C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4mli\u001b[0;38;5;238;48;5;30mst:restaurant\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[22C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mmy\u001b[0;38;5;231;48;5;30;1;4mli\u001b[0;38;5;238;48;5;30mst1\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[5A\u001b[22C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[31.055859, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mst:animals\u001b[0m \u001b[11D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[31.292784, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mst:animals\u001b[0m\r\r\n\u001b[23C\u001b[0;38;5;16;48;5;231m \u001b[0;38;5;16;48;5;231;4mli\u001b[0;48;5;231mst:animals\u001b[0;38;5;16;48;5;231m \u001b[A\u001b[7D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[31.296722, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[32.658483, "o", "\u001b[?25l\u001b[?7l\u001b[33D\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0;38;5;28;1mLLEN\u001b[0m \u001b[0;38;5;71mlist:animals\u001b[0m \r\u001b[65C \r\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
[32.662927, "o", "\u001b[0m\u001b[?7h\u001b[0;38;5;102m(integer) \u001b[0m51\u001b[0m\u001b[0m"]
[32.664937, "o", "\u001b[0m\u001b[?7h\u001b[0m\r\r\n\u001b[0m"]
[32.665552, "o", "\u001b[?1l"]
[32.665646, "o", "\u001b[6n"]
[32.66873, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \r\u001b[65C \r\u001b[7A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[32.671953, "o", "\u001b[?25l\u001b[?7l\u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;248;48;5;235mCtrl-D to exit; \r\u001b[65C \r\u001b[8A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[35.303978, "o", "\u001b[?25l\u001b[?7l\u001b[0ml \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m LSET \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m LREM \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m LPOP \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m LLEN \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m LTRIM \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m LPUSH \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m LRANGE \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[11D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[35.320666, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mrange list:animals 0 6\u001b[0m \u001b[23D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[35.652587, "o", "\u001b[?25l\u001b[?7l\u001b[0mr \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m LREM\u001b[3C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m LRANGE \u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[7A\u001b[10D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[35.656638, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mange list:animals 0 6\u001b[0m \u001b[22D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[35.741188, "o", "\u001b[?25l\u001b[?7l\u001b[0ma \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m LRANGE \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[2A\u001b[18C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[35.744004, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mnge list:animals 0 6\u001b[0m \u001b[21D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[35.819735, "o", "\u001b[?25l\u001b[?7l\u001b[0mn \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m LRANGE \u001b[0;38;5;16;48;5;238m \u001b[A\u001b[9D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[35.823559, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mge list:animals 0 6\u001b[0m \u001b[20D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[35.956968, "o", "\u001b[?25l\u001b[?7l\u001b[0mg \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m LRANGE \u001b[0;38;5;16;48;5;238m \u001b[A\u001b[9D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[35.960667, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241me list:animals 0 6\u001b[0m \u001b[19D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[36.069943, "o", "\u001b[?25l\u001b[?7l\u001b[5D\u001b[0;38;5;28;1mlrange\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m LRANGE \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;167;48;5;235;1m(list) \u001b[0;38;5;28;48;5;235;1mLRANGE\u001b[0;38;5;71;48;5;235m key\u001b[0;38;5;141;48;5;235m start stop\u001b[0;38;5;136;48;5;235m since: 1.0.0\u001b[0;38;5;241;48;5;235m complexity:O(S+N) whe\r\u001b[65Cr\u001b[8A\r\u001b[22C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[36.076669, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241m list:animals 0 6\u001b[0m \u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[36.209277, "o", "\u001b[?25l\u001b[?7l\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[22C\u001b[0m \u001b[0;38;5;231;48;5;30m list:animals \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[23C\u001b[0;38;5;231;48;5;30m cars \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[23C\u001b[0;38;5;231;48;5;30m myset \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[23C\u001b[0;38;5;231;48;5;30m kkk \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[23C\u001b[0;38;5;231;48;5;30m somestream \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[23C\u001b[0;38;5;231;48;5;30m af \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[23C\u001b[0;38;5;231;48;5;30m hash2 \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[36.216403, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mlist:animals 0 6\u001b[0m \u001b[17D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[37.3095, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71ml\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[23C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ml\u001b[0;38;5;238;48;5;30mist:animals\u001b[3C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[23C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ml\u001b[0;38;5;238;48;5;30mist:buildings\u001b[C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[23C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ml\u001b[0;38;5;238;48;5;30mist:restaurant\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[23C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mmy\u001b[0;38;5;231;48;5;30;1;4ml\u001b[0;38;5;238;48;5;30mist1\u001b[8C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[23C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mSici\u001b[0;38;5;231;48;5;30;1;4ml\u001b[0;38;5;238;48;5;30my\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[23C\u001b[0m \u001b[7A\u001b[17D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[37.319061, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mist:animals 0 6\u001b[0m \u001b[16D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[37.965239, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mist:animals\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[24C\u001b[0;38;5;16;48;5;231m \u001b[0;38;5;16;48;5;231;4ml\u001b[0;48;5;231mist:animals\u001b[0;38;5;16;48;5;231m \u001b[A\u001b[6D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[37.96939, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[38.184492, "o", "\u001b[?25l\u001b[?7l\u001b[C\u001b[0m \u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[5A\u001b[35C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[38.189811, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241m0 6\u001b[0m \u001b[4D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[38.809319, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;141m0\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[38.813992, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241m 6\u001b[0m \u001b[3D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[38.969048, "o", "\u001b[?25l\u001b[?7l\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[38.973767, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241m6\u001b[0m \u001b[2D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[41.005131, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;141m6\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[41.015952, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[41.427252, "o", "\u001b[?25l\u001b[?7l\u001b[39D\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0;38;5;28;1mlrange\u001b[0m \u001b[0;38;5;71mlist:animals\u001b[0m \u001b[0;38;5;141m0\u001b[0m \u001b[0;38;5;141m6\u001b[0m \r\u001b[65C \r\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
[41.437426, "o", "\u001b[0m\u001b[?7h\u001b[0m1)\u001b[0m \u001b[0;38;5;208m\"wolf\"\u001b[0m\r\r\n\u001b[0m2)\u001b[0m \u001b[0;38;5;208m\"turtle\"\u001b[0m\r\r\n\u001b[0m3)\u001b[0m \u001b[0;38;5;208m\"tiger\"\u001b[0m\r\r\n\u001b[0m4)\u001b[0m \u001b[0;38;5;208m\"squirrel\"\u001b[0m\r\r\n\u001b[0m5)\u001b[0m \u001b[0;38;5;208m\"spider\"\u001b[0m\r\r\n\u001b[0m6)\u001b[0m \u001b[0;38;5;208m\"snake\"\u001b[0m\r\r\n\u001b[0m7)\u001b[0m \u001b[0;38;5;208m\"snail\"\u001b[0m\u001b[0m"]
[41.439085, "o", "\u001b[0m\u001b[?7h\u001b[0m\r\r\n\u001b[0m"]
[41.439711, "o", "\u001b[?1l"]
[41.439807, "o", "\u001b[6n"]
[41.444224, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \r\u001b[65C \r\u001b[7A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[41.447447, "o", "\u001b[?25l\u001b[?7l\u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;248;48;5;235mCtrl-D to exit; \r\u001b[65C \r\u001b[8A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[45.439479, "o", "\u001b[?25l\u001b[?7l\u001b[0mt \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m TTL \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m TIME \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m TYPE \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m TOUCH \u001b[0;38;5;16;48;5;238m \u001b[4A\u001b[8D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[45.444328, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mype list:animals\u001b[0m \u001b[17D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[45.592128, "o", "\u001b[?25l\u001b[?7l\u001b[0my \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m TYPE \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[4A\u001b[17C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[45.59538, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mpe list:animals\u001b[0m \u001b[16D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[45.812073, "o", "\u001b[?25l\u001b[?7l\u001b[0mp \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m TYPE\u001b[C \u001b[0;38;5;16;48;5;238m \u001b[A\u001b[8D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[45.816301, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241me list:animals\u001b[0m \u001b[15D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[45.953551, "o", "\u001b[?25l\u001b[?7l\u001b[3D\u001b[0;38;5;28;1mtype\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m TYPE\u001b[C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;167;48;5;235;1m(generic) \u001b[0;38;5;28;48;5;235;1mTYPE\u001b[0;38;5;71;48;5;235m key\u001b[0;38;5;136;48;5;235m since: 1.0.0\u001b[0;38;5;241;48;5;235m complexity:O(1)\u001b[8A\u001b[29D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[45.958932, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241m list:animals\u001b[0m \u001b[14D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[46.676064, "o", "\u001b[?25l\u001b[?7l\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m list:animals \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m cars \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m myset \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m kkk \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m somestream \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m af \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m hash2 \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[46.68263, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mlist:animals\u001b[0m \u001b[13D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[46.908434, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mlist:animals\u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;16;48;5;231m list:animals \u001b[A\u001b[5D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[46.912994, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[47.594674, "o", "\u001b[?25l\u001b[?7l\u001b[12D\u001b[0;38;5;71mcars\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m list:animals \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;16;48;5;231m cars \u001b[2A\u001b[13D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[47.599769, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[48.036896, "o", "\u001b[?25l\u001b[?7l\u001b[4D\u001b[0;38;5;71mmyset\u001b[0m \u001b[0m\r\r\n\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m cars \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;16;48;5;231m myset \u001b[3A\u001b[12D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[48.041407, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[48.555102, "o", "\u001b[?25l\u001b[?7l\u001b[5D\u001b[0;38;5;71mkkk\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\r\r\n\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m myset \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;16;48;5;231m kkk \u001b[4A\u001b[14D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[48.559223, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[48.744603, "o", "\u001b[?25l\u001b[?7l\u001b[3D\u001b[0;38;5;71msomestream\u001b[0m \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m kkk \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;16;48;5;231m somestream \u001b[5A\u001b[7D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[48.751509, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[48.895622, "o", "\u001b[?25l\u001b[?7l\u001b[10D\u001b[0;38;5;71maf\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[21C\u001b[0;38;5;231;48;5;30m somestream \u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;16;48;5;231m af \u001b[6A\u001b[15D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[48.900841, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[50.400124, "o", "\u001b[?25l\u001b[?7l\u001b[23D\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0;38;5;28;1mtype\u001b[0m \u001b[0;38;5;71maf\u001b[0m \r\u001b[65C \r\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
[50.41034, "o", "\u001b[0m\u001b[?7h\u001b[0m\"string\"\u001b[0m\u001b[0m"]
[50.412186, "o", "\u001b[0m\u001b[?7h\u001b[0m\r\r\n\u001b[0m"]
[50.41283, "o", "\u001b[?1l"]
[50.41295, "o", "\u001b[6n"]
[50.415152, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \r\u001b[65C \r\u001b[7A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[50.419664, "o", "\u001b[?25l\u001b[?7l\u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;248;48;5;235mCtrl-D to exit; \r\u001b[65C \r\u001b[8A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[51.542536, "o", "\u001b[?25l\u001b[?7l\u001b[0mg \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m GET \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m GETSET \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m GETBIT \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m GEOPOS \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m GEOADD \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m GEOHASH \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m GEODIST \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[20D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[51.555077, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241met foo\u001b[0m \u001b[7D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[51.630144, "o", "\u001b[?25l\u001b[?7l\u001b[0me \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m GET\u001b[14C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m GETSET\u001b[11C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m GETBIT\u001b[11C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m GEOPOS\u001b[11C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m GEOA\u001b[CD\u001b[11C \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m GEOHASH\u001b[10C \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m GEODIST\u001b[10C \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[20D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[51.635334, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mt foo\u001b[0m \u001b[6D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[51.773743, "o", "\u001b[?25l\u001b[?7l\u001b[2D\u001b[0;38;5;28;1mget\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m GET\u001b[6C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m GETSET\u001b[3C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m GETBIT\u001b[3C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m GETRANGE \u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0m\r\r\n\u001b[0;38;5;167;48;5;235;1m(string) \u001b[0;38;5;28;48;5;235;1mGET\u001b[0;38;5;71;48;5;235m key\u001b[0;38;5;136;48;5;235m since: 1.0.0\u001b[0;38;5;241;48;5;235m complexity:O(1)\u001b[8A\u001b[28D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[51.77765, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241m foo\u001b[0m \u001b[5D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[51.91592, "o", "\u001b[?25l\u001b[?7l\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m af\u001b[6C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m list:animals \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m cars \u001b[3C \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m myset \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[20C\u001b[0;38;5;231;48;5;30m kkk \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[20C\u001b[0;38;5;231;48;5;30m somestream \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[20C\u001b[0;38;5;231;48;5;30m hash2 \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[51.921861, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mfoo\u001b[0m \u001b[4D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[52.325226, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71ma\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30mf\u001b[13C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;231;48;5;30m \u001b[4C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30mbc\u001b[12C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mc\u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30mrs\u001b[11C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mh\u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30msh2\u001b[10C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mh\u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30msh3\u001b[0;38;5;231;48;5;30m \u001b[6C \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mh\u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30msh1"]
[52.32535, "o", "\u001b[10C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[52.329701, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[52.448681, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mf\u001b[0m \u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4maf\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[7A\u001b[17D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[52.453078, "o", "\u001b[?25l\u001b[?7l\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[A\u001b[21C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[52.892111, "o", "\u001b[?25l\u001b[?7l\u001b[22D\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0;38;5;28;1mget\u001b[0m \u001b[0;38;5;71maf\u001b[0m \r\u001b[65C \r\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
[52.896263, "o", "\u001b[0m\u001b[?7h\u001b[0m\"asdf\"\u001b[0m\u001b[0m"]
[52.898075, "o", "\u001b[0m\u001b[?7h\u001b[0m\r\r\n\u001b[0m"]
[52.898599, "o", "\u001b[?1l\u001b[6n"]
[52.90232, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \r\u001b[65C \r\u001b[7A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[52.905874, "o", "\u001b[?25l\u001b[?7l\u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;248;48;5;235mCtrl-D to exit; \r\u001b[65C \r\u001b[8A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[53.943112, "o", "\u001b[?25l\u001b[?7l\u001b[0mg \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m GET \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m GETSET \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m GETBIT \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m GEOPOS \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m GEOADD \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m GEOHASH \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m GEODIST \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[20D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[53.9504, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241met af\u001b[0m \u001b[6D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[54.043955, "o", "\u001b[?25l\u001b[?7l\u001b[0me \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m GET\u001b[14C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m GETSET\u001b[11C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m GETBIT\u001b[11C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m GEOPOS\u001b[11C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m GEOA\u001b[CD\u001b[11C \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m GEOHASH\u001b[10C \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m GEODIST\u001b[10C \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[20D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[54.047839, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mt af\u001b[0m \u001b[5D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[54.192601, "o", "\u001b[?25l\u001b[?7l\u001b[2D\u001b[0;38;5;28;1mget\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m GET\u001b[6C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m GETSET\u001b[3C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m GETBIT\u001b[3C\u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m GETRANGE \u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0m\r\r\n\u001b[0;38;5;167;48;5;235;1m(string) \u001b[0;38;5;28;48;5;235;1mGET\u001b[0;38;5;71;48;5;235m key\u001b[0;38;5;136;48;5;235m since: 1.0.0\u001b[0;38;5;241;48;5;235m complexity:O(1)\u001b[8A\u001b[28D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[54.197235, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241m af\u001b[0m \u001b[4D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[54.335864, "o", "\u001b[?25l\u001b[?7l\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m af\u001b[6C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m list:animals \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m cars \u001b[3C \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m myset \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[20C\u001b[0;38;5;231;48;5;30m kkk \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[20C\u001b[0;38;5;231;48;5;30m somestream \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[20C\u001b[0;38;5;231;48;5;30m hash2 \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[54.342312, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241maf\u001b[0m \u001b[3D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[54.76708, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71ma\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30mf\u001b[13C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;231;48;5;30m \u001b[4C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30mbc\u001b[12C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mc\u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30mrs\u001b[11C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mh\u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30msh2\u001b[10C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mh\u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30msh3\u001b[0;38;5;231;48;5;30m \u001b[6C \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mh\u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30msh1"]
[54.767202, "o", "\u001b[10C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[54.771478, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mf\u001b[0m \u001b[2D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[55.027261, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mf\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4maf\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0m \u001b[7A\u001b[17D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[55.03053, "o", "\u001b[?25l\u001b[?7l\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[A\u001b[21C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[55.228293, "o", "\u001b[?25l\u001b[?7l\u001b[C\u001b[0m \b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[55.231931, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[55.573146, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;16;48;5;196mi\u001b[0m \b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[55.577988, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[55.66374, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;16;48;5;196mn\u001b[0m \b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[55.667964, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[55.984948, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;16;48;5;196mv\u001b[0m \b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[55.98961, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[56.157229, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;16;48;5;196ma\u001b[0m \b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[56.162529, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[56.423, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;16;48;5;196ml\u001b[0m \b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[56.427638, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[56.586877, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;16;48;5;196mi\u001b[0m \b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[56.593834, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[56.913957, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;16;48;5;196md\u001b[0m \b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[56.918423, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[57.091248, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;16;48;5;196me\u001b[0m \b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[57.095887, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[57.190026, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;16;48;5;196m \u001b[0m \b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[57.1952, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[57.430926, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;16;48;5;196mi\u001b[0m \b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[57.435255, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[57.500268, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;16;48;5;196mn\u001b[0m \b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[57.506086, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[57.711636, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;16;48;5;196mp\u001b[0m \b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[57.715911, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[57.984832, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;16;48;5;196mu\u001b[0m \b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[57.992887, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[58.141271, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;16;48;5;196mt\u001b[0m \b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[58.145592, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.061523, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.073482, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.309508, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.315274, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.34596, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.350846, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.379097, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.38583, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.416019, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.431899, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.446868, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.45367, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.479192, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.483837, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.512893, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.519325, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.546693, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.550989, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.581696, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.586075, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.613113, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.617986, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.648799, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.652827, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.800181, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.805545, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.955905, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[59.959298, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[60.208194, "o", "\u001b[?25l\u001b[?7l\u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[60.212447, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[60.247807, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[60.254547, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[60.278, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[60.2824, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[60.309265, "o", "\u001b[?25l\u001b[?7l\u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[60.313357, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[60.342096, "o", "\u001b[?25l\u001b[?7l\u001b[3D\u001b[0mge \u001b[0m\u001b[K\u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;248;48;5;235mCtrl-D to exit; \u001b[8A\u001b[29D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[60.352259, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[60.377642, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[60.38131, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[60.408313, "o", "\u001b[?25l\u001b[?7l\b\u001b[0m \u001b[0m\u001b[K\b\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[60.412424, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[60.449815, "o", "\u0007"]
[60.453021, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[60.473651, "o", "\u0007"]
[60.476542, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[62.514509, "o", "\u001b[?25l\u001b[?7l\u001b[0md \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m DEL \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m DECR \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m DUMP \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m DECRBY \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m DBSIZE \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m DISCARD \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0;38;5;231;48;5;30m DEBUG OBJECT \u001b[0;38;5;16;48;5;37m \u001b[7A\u001b[17D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[62.520623, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241mel _kombu.binding.celeryev\u001b[0m \u001b[27D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[62.683241, "o", "\u001b[?25l\u001b[?7l\u001b[0me \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m DEL\u001b[11C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m DECR\u001b[10C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m DECRBY\u001b[8C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m DEBUG\u001b[COBJECT\u001b[2C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[0;38;5;231;48;5;30m DEBUG SEGFAULT \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[17C\u001b[0m \u001b[7A\u001b[16D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[62.686861, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241ml _kombu.binding.celeryev\u001b[0m \u001b[26D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[62.885578, "o", "\u001b[?25l\u001b[?7l\u001b[2D\u001b[0;38;5;28;1mdel\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[18C\u001b[0m \u001b[0;38;5;231;48;5;30m DEL \u001b[0;38;5;16;48;5;238m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[C\u001b[0m\u001b[K\u001b[0m\r\r\n\r\r\n\r\r\n\u001b[0;38;5;167;48;5;235;1m(generic) \u001b[0;38;5;28;48;5;235;1mDEL\u001b[0;38;5;71;48;5;235m key\u001b[0;38;5;136;48;5;235m since: 1.0.0\u001b[0;38;5;241;48;5;235m complexity:O(N) where N is the n\r\u001b[65Cu\u001b[8A\r\u001b[19C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[62.890125, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241m _kombu.binding.celeryev\u001b[0m \u001b[25D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[63.433741, "o", "\u001b[?25l\u001b[?7l\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[19C\u001b[0m \u001b[0;38;5;231;48;5;30m af \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[20C\u001b[0;38;5;231;48;5;30m list:animals \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[20C\u001b[0;38;5;231;48;5;30m cars \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[20C\u001b[0;38;5;231;48;5;30m myset \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[20C\u001b[0;38;5;231;48;5;30m kkk \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[20C\u001b[0;38;5;231;48;5;30m somestream \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[20C\u001b[0;38;5;231;48;5;30m hash2 \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[63.438223, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241m_kombu.binding.celeryev\u001b[0m \u001b[24D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[64.652788, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71ma\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30mf\u001b[13C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;231;48;5;30m \u001b[4C \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30mbc\u001b[12C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mc\u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30mrs\u001b[11C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;238m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mh\u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30msh2\u001b[10C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;37m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mh\u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30msh3\u001b[0;38;5;231;48;5;30m \u001b[6C \u001b[0;38;5;16;48;5;248m \u001b[0m\r\r\n\u001b[20C\u001b[0m \u001b[0;38;5;231;48;5;30m \u001b[0;38;5;238;48;5;30mh\u001b[0;38;5;231;48;5;30;1;4ma\u001b[0;38;5;238;48;5;30msh1"]
[64.652904, "o", "\u001b[10C\u001b[0;38;5;231;48;5;30m \u001b[0;38;5;16;48;5;248m \u001b[7A\u001b[18D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[64.66165, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;241m\\\"bc\u001b[0m \u001b[5D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[65.254736, "o", "\u001b[?25l\u001b[?7l\u001b[0;38;5;71mf\u001b[0m \u001b[0m\u001b[K\u001b[0m\r\r\n\u001b[21C\u001b[0;38;5;16;48;5;231m \u001b[0;38;5;16;48;5;231;4ma\u001b[0;48;5;231mf\u001b[0;38;5;16;48;5;231m \u001b[A\u001b[16D\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[65.26029, "o", "\u001b[?25l\u001b[?7l\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[65.761821, "o", "\u001b[?25l\u001b[?7l\u001b[22D\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0;38;5;28;1mdel\u001b[0m \u001b[0;38;5;71maf\u001b[0m \r\u001b[65C \r\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
[65.766399, "o", "\u001b[0m\u001b[?7h\u001b[0;38;5;102m(integer) \u001b[0m1\u001b[0m\u001b[0m"]
[65.768122, "o", "\u001b[0m\u001b[?7h\u001b[0m\r\r\n\u001b[0m"]
[65.768819, "o", "\u001b[?1l\u001b[6n"]
[65.771167, "o", "\u001b[?2004h\u001b[?25l\u001b[0m\u001b[?7l\u001b[0m\u001b[J\u001b[0m127.0.0.1:6379> \u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0m \r\u001b[65C \r\u001b[7A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[65.774133, "o", "\u001b[?25l\u001b[?7l\u001b[0m\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\r\r\n\u001b[0;38;5;248;48;5;235mCtrl-D to exit; \r\u001b[65C \r\u001b[8A\u001b[16C\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h"]
[66.838972, "o", "\u001b[?25l\u001b[?7l\u001b[16D\u001b[0m\u001b[J\u001b[0;38;5;102m127.0.0.1:6379> \r\u001b[65C \r\u001b[0m\r\r\n\u001b[J\u001b[?7h\u001b[0m\u001b[?12l\u001b[?25h\u001b[?2004l"]
[66.839665, "o", "Goodbye!\r\n"]
[66.871374, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[66.893132, "o", "\u001b]133;D;0\u0007\u001b]1337;RemoteHost=laixintao@Chico.local\u0007\u001b]1337;CurrentDir=/Users/laixintao\u0007"]
[66.896847, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b]133;A\u0007$ \u001b]133;B\u0007\u001b[K"]
[66.897062, "o", "\u001b[?1h\u001b="]
[66.897178, "o", "\u001b[?2004h"]
[67.725923, "o", "\u001b[?2004l\r\r\n"]

214
docs/assets/demo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 297 KiB

BIN
docs/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

7
docs/assets/render.md Normal file
View file

@ -0,0 +1,7 @@
Render using [termtosvg](https://github.com/nbedos/termtosvg/blob/develop/man/termtosvg.md):
```
termtosvg render demo.cast demo1.svg -D 2 -m40 -M300 -t progress_bar
```
size: 66x20

View file

@ -0,0 +1,3 @@
Try redis in docker(which contains a redis-server):
docker build -t iredis . && docker run -it iredis

9
docs/update-redis-doc.md Normal file
View file

@ -0,0 +1,9 @@
# How to Catch Up with Latest Redis-doc
1. `git pull` in submodule.
2. Overwrite `iredis/data/commands.json`.
3. Diff with old `commands.json`, make the changes.
4. `mv redis-doc/commands/*.md iredis/data/commands`
5. `prettier --write --prose-wrap always iredis/data/commands/*.md`
Done!

1
iredis/__init__.py Normal file
View file

@ -0,0 +1 @@
__version__ = "1.9.1"

35
iredis/bottom.py Normal file
View file

@ -0,0 +1,35 @@
import logging
from .commands import commands_summary
from .utils import command_syntax
BUTTOM_TEXT = "Ctrl-D to exit;"
logger = logging.getLogger(__name__)
class BottomToolbar:
CHAR = "⣾⣷⣯⣟⡿⢿⣻⣽"
def __init__(self, command_holder):
self.index = 0
# BottomToolbar can only read this variable
self.command_holder = command_holder
def get_animation_char(self):
animation = self.CHAR[self.index]
self.index += 1
if self.index == len(self.CHAR):
self.index = 0
return animation
def render(self):
text = BUTTOM_TEXT
# add command help if valide
if self.command_holder.command:
try:
command_info = commands_summary[self.command_holder.command]
text = command_syntax(self.command_holder.command, command_info)
except KeyError as e:
logger.exception(e)
pass
return text

685
iredis/client.py Normal file
View file

@ -0,0 +1,685 @@
"""
IRedis client.
"""
import re
import os
import sys
import logging
from subprocess import run
from importlib_resources import read_text
from distutils.version import StrictVersion
import redis
from prompt_toolkit.shortcuts import clear
from prompt_toolkit.formatted_text import FormattedText
from redis.connection import Connection, SSLConnection, UnixDomainSocketConnection
from redis.exceptions import (
AuthenticationError,
ConnectionError,
TimeoutError,
ResponseError,
)
from . import markdown, renders
from .data import commands as commands_data
from .commands import (
command2callback,
commands_summary,
command2syntax,
groups,
split_command_args,
split_unknown_args,
)
from .completers import IRedisCompleter
from .config import config
from .exceptions import NotRedisCommand, InvalidArguments, AmbiguousCommand, NotSupport
from .renders import OutputRender
from .utils import (
compose_command_syntax,
nativestr,
exit,
convert_formatted_text_to_bytes,
parse_url,
)
from .warning import confirm_dangerous_command
logger = logging.getLogger(__name__)
CLIENT_COMMANDS = groups["iredis"]
class Client:
"""
iRedis client, hold a redis-py Client to interact with Redis.
"""
def __init__(
self,
host=None,
port=None,
db=0,
password=None,
path=None,
scheme="redis",
username=None,
):
self.host = host
self.port = port
self.db = db
self.path = path
# FIXME username is not using...
self.username = username
self.scheme = scheme
self.connection = self.create_connection(
host,
port,
db,
password,
path,
scheme,
username,
)
# all command upper case
self.answer_callbacks = command2callback
self.set_default_pager(config)
try:
self.connection.connect()
except Exception as e:
print(str(e), file=sys.stderr)
sys.exit(1)
if not config.no_info:
try:
self.get_server_info()
except Exception as e:
logger.warning(f"[After Connection] {str(e)}")
config.no_version_reason = str(e)
else:
config.no_version_reason = "--no-info flag activated"
if config.version and re.match(r"([\d\.]+)", config.version):
self.auth_compat(config.version)
def create_connection(
self,
host=None,
port=None,
db=0,
password=None,
path=None,
scheme="redis",
username=None,
):
if scheme in ("redis", "rediss"):
connection_kwargs = {
"host": host,
"port": port,
"db": db,
"password": password,
"socket_keepalive": config.socket_keepalive,
}
if scheme == "rediss":
connection_class = SSLConnection
else:
connection_class = Connection
else:
connection_kwargs = {"db": db, "password": password, "path": path}
connection_class = UnixDomainSocketConnection
if config.decode:
connection_kwargs["encoding"] = config.decode
connection_kwargs["decode_responses"] = True
connection_kwargs["encoding_errors"] = "replace"
logger.debug(
f"connection_class={connection_class}, connection_kwargs={connection_kwargs}"
)
return connection_class(**connection_kwargs)
def auth_compat(self, redis_version: str):
with_username = StrictVersion(redis_version) >= StrictVersion("6.0.0")
if with_username:
command2syntax["AUTH"] = "command_usernamex_password"
def set_default_pager(self, config):
configured_pager = config.pager
os_environ_pager = os.environ.get("PAGER")
if configured_pager:
logger.info('Default pager found in config file: "%s"', configured_pager)
os.environ["PAGER"] = configured_pager
elif os_environ_pager:
logger.info(
'Default pager found in PAGER environment variable: "%s"',
os_environ_pager,
)
os.environ["PAGER"] = os_environ_pager
else:
logger.info("No default pager found in environment. Using os default pager")
# Set default set of less recommended options, if they are not already set.
# They are ignored if pager is different than less.
if not os.environ.get("LESS"):
os.environ["LESS"] = "-SRXF"
def get_server_info(self):
# safe to decode Redis's INFO response
info_resp = nativestr(self.execute("INFO"))
version = re.findall(r"^redis_version:([\d\.]+)\r\n", info_resp, re.MULTILINE)[
0
]
logger.debug(f"[Redis Version] {version}")
config.version = version
def __str__(self):
if self.scheme == "unix":
prompt = f"redis {self.path}"
else:
prompt = f"{self.host}:{self.port}"
if self.db:
prompt = f"{prompt}[{self.db}]"
return prompt
def client_execute_command(self, command_name, *args):
command = command_name.upper()
if command == "HELP":
yield self.do_help(*args)
if command == "PEEK":
yield from self.do_peek(*args)
if command == "CLEAR":
clear()
if command == "EXIT":
exit()
def execute(self, *args, **kwargs):
logger.info(
f"execute: connection={self.connection} args={args}, kwargs={kwargs}"
)
return self.execute_by_connection(self.connection, *args, **kwargs)
def execute_by_connection(self, connection, command_name, *args, **options):
"""Execute a command and return a parsed response
Here we retry once for ConnectionError.
"""
logger.info(
f"execute by connection: connection={connection}, name={command_name}, {args}, {options}"
)
retry_times = config.retry_times # FIXME configureable
last_error = None
need_refresh_connection = False
while retry_times >= 0:
try:
if need_refresh_connection:
print(
f"{str(last_error)} retrying... retry left: {retry_times+1}",
file=sys.stderr,
)
connection.disconnect()
connection.connect()
logger.info(f"New connection created, retry on {connection}.")
logger.info(f"send_command: {command_name} , {args}")
connection.send_command(command_name, *args)
response = connection.read_response()
except AuthenticationError:
raise
except (ConnectionError, TimeoutError) as e:
logger.warning(f"Connection Error, got {e}, retrying...")
last_error = e
retry_times -= 1
need_refresh_connection = True
except (ResponseError) as e:
response_message = str(e)
if response_message.startswith("MOVED"):
return self.reissue_with_redirect(
response_message, command_name, *args, **options
)
raise e
except redis.exceptions.ExecAbortError:
config.transaction = False
raise
else:
return response
raise last_error
def reissue_with_redirect(self, response, *args, **kwargs):
"""
For redis cluster, when server response a "MOVE ..." response, we auto-
redirect to the target node, reissue the original command.
This feature is not supported for unix socket connection.
"""
# Redis Cluster only supports database zero.
_, slot, ip_port = response.split(" ")
ip, port = ip_port.split(":")
port = int(port)
print(response, file=sys.stderr)
connection = self.create_connection(ip, port)
# if user sets dsn for dest node
# use username and password from dsn settings
if config.alias_dsn:
for dsn_name, dsn_url in config.alias_dsn.items():
dsn = parse_url(dsn_url)
if dsn.host == ip and dsn.port == port:
print(
f"Connect {ip}:{port} via dns settings of {dsn_name}",
file=sys.stderr,
)
connection = self.create_connection(
dsn.host,
dsn.port,
dsn.db,
dsn.password,
dsn.path,
dsn.scheme,
dsn.username,
)
break
connection.connect()
return self.execute_by_connection(connection, *args, **kwargs)
def render_response(self, response, command_name):
"Parses a response from the Redis server"
logger.info(f"[Redis-Server] Response: {response}")
if config.raw:
callback = OutputRender.render_raw
# if in transaction, use queue render first
elif config.transaction:
callback = renders.OutputRender.render_transaction_queue
else:
callback = OutputRender.get_render(command_name=command_name)
rendered = callback(response)
logger.info(f"[render result] {rendered}")
return rendered
def monitor(self):
"""Redis' MONITOR command:
https://redis.io/commands/monitor
This command need to read from a stream resp, so
it's different
"""
while 1:
response = self.connection.read_response()
if config.raw:
yield OutputRender.render_raw(response)
else:
yield OutputRender.render_bulk_string_decode(response)
def subscribing(self):
while 1:
response = self.connection.read_response()
if config.raw:
yield OutputRender.render_raw(response)
else:
yield OutputRender.render_subscribe(response)
def unsubscribing(self):
"unsubscribe from all channels"
response = self.execute("UNSUBSCRIBE")
if config.raw:
yield OutputRender.render_raw(response)
else:
yield OutputRender.render_subscribe(response)
def split_command_and_pipeline(self, rawinput, completer: IRedisCompleter):
"""
split user raw input to redis command and shell pipeline.
eg:
GET json | jq .key
return: GET json, jq . key
"""
grammar = completer.get_completer(input_text=rawinput).compiled_grammar
matched = grammar.match(rawinput)
if not matched:
# invalide command!
return rawinput, None
variables = matched.variables()
shell_command = variables.get("shellcommand")
if shell_command:
redis_command = rawinput.replace(shell_command, "")
shell_command = shell_command.lstrip("| ")
return redis_command, shell_command
return rawinput, None
def send_command(self, raw_command, completer=None): # noqa
"""
Send raw_command to redis-server, return parsed response.
:param raw_command: text raw_command, not parsed
:param completer: RedisGrammarCompleter will update completer
based on redis response. eg: update key completer after ``keys``
raw_command
"""
if completer is None: # not in a tty
redis_command, shell_command = raw_command, None
else:
redis_command, shell_command = self.split_command_and_pipeline(
raw_command, completer
)
logger.info(f"[Prepare command] Redis: {redis_command}, Shell: {shell_command}")
try:
try:
command_name, args = split_command_args(redis_command)
except (InvalidArguments, AmbiguousCommand):
logger.warn(
"This is not a iredis known command, send to redis-server anyway..."
)
command_name, args = split_unknown_args(redis_command)
logger.info(f"[Split command] command: {command_name}, args: {args}")
input_command_upper = command_name.upper()
# Confirm for dangerous command
if config.warning:
confirm = confirm_dangerous_command(input_command_upper)
if confirm is True:
print("Your Call!!", file=sys.stderr)
elif confirm is False:
print("Canceled!", file=sys.stderr)
return
# None: continue...
self.pre_hook(raw_command, command_name, args, completer)
# if raw_command is not supposed to send to server
if input_command_upper in CLIENT_COMMANDS:
logger.info(f"{input_command_upper} is an iredis command.")
yield from self.client_execute_command(command_name, *args)
return
redis_resp = self.execute(command_name, *args)
# if shell_command and enable shell, do not render, just run in shell pipe and show the
# subcommand's stdout/stderr
if shell_command and config.shell:
# pass the raw response of redis to shell command
if isinstance(redis_resp, list):
# FIXME not handling nested list, use renders.render_raw
# instead
stdin = b"\n".join(redis_resp)
else:
stdin = redis_resp
run(shell_command, input=stdin, shell=True)
return
self.after_hook(raw_command, command_name, args, completer, redis_resp)
yield self.render_response(redis_resp, command_name)
# FIXME generator response do not support pipeline
if input_command_upper == "MONITOR":
# TODO special render for monitor
try:
yield from self.monitor()
except KeyboardInterrupt:
pass
elif input_command_upper in [
"SUBSCRIBE",
"PSUBSCRIBE",
]: # enter subscribe mode
try:
yield from self.subscribing()
except KeyboardInterrupt:
yield from self.unsubscribing()
except Exception as e:
logger.exception(e)
if config.raw:
render_callback = OutputRender.render_raw
else:
render_callback = OutputRender.render_error
yield render_callback(f"ERROR {str(e)}".encode())
finally:
config.withscores = False
def after_hook(self, command, command_name, args, completer, response):
# === After hook ===
# SELECT db on AUTH
if command_name.upper() == "AUTH":
if self.db:
select_result = self.execute("SELECT", self.db)
if nativestr(select_result) != "OK":
raise ConnectionError("Invalid Database")
# When the connection is TimeoutError or ConnectionError, reconnect the connection will use it
self.connection.password = args[0]
elif command_name.upper() == "SELECT":
logger.debug("[After hook] Command is SELECT, change self.db.")
self.db = int(args[0])
# When the connection is TimeoutError or ConnectionError, reconnect the connection will use it
self.connection.db = self.db
elif command_name.upper() == "MULTI":
logger.debug("[After hook] Command is MULTI, start transaction.")
config.transaction = True
if completer:
completer.update_completer_for_response(command_name, args, response)
def pre_hook(self, command, command_name, args, completer: IRedisCompleter):
"""
Before execute command, patch completers first.
Eg: When user run `GET foo`, key completer need to
touch foo.
Only works when compile-grammar thread is done.
"""
if command_name.upper() == "HELLO":
raise NotSupport("IRedis currently not support RESP3, sorry about that.")
# TRANSATION state chage
if command_name.upper() in ["EXEC", "DISCARD"]:
logger.debug(f"[After hook] Command is {command_name}, unset transaction.")
config.transaction = False
# score display for sorted set
if command_name.upper() in ["ZSCAN", "ZPOPMAX", "ZPOPMIN"]:
config.withscores = True
# not a tty
if not completer:
logger.warning(
"[Pre patch completer] Complter is None, not a tty, "
"not patch completers, not set withscores"
)
return
completer.update_completer_for_input(command)
redis_grammar = completer.get_completer(command).compiled_grammar
m = redis_grammar.match(command)
if not m:
# invalide command!
return
variables = m.variables()
# zset withscores
withscores = variables.get("withscores")
if withscores:
config.withscores = True
def do_help(self, *args):
command_docs_name = "-".join(args).lower()
command_summary_name = " ".join(args).upper()
try:
doc = read_text(commands_data, f"{command_docs_name}.md")
except FileNotFoundError:
raise NotRedisCommand(
f"{command_summary_name} is not a valide Redis command."
)
rendered_detail = markdown.render(doc)
summary_dict = commands_summary[command_summary_name]
avaiable_version = summary_dict.get("since", "?")
server_version = config.version
# FIXME anything strange with single quotes?
logger.debug(f"[--version--] '{server_version}'")
try:
is_avaiable = StrictVersion(server_version) > StrictVersion(
avaiable_version
)
except Exception as e:
logger.exception(e)
is_avaiable = None
if is_avaiable:
avaiable_text = f"(Avaiable on your redis-server: {server_version})"
elif is_avaiable is False:
avaiable_text = f"(Not avaiable on your redis-server: {server_version})"
else:
avaiable_text = ""
since_text = f"{avaiable_version} {avaiable_text}"
summary = [
("", "\n"),
("class:doccommand", " " + command_summary_name),
("", "\n"),
("class:dockey", " summary: "),
("", summary_dict.get("summary", "No summary")),
("", "\n"),
("class:dockey", " complexity: "),
("", summary_dict.get("complexity", "?")),
("", "\n"),
("class:dockey", " since: "),
("", since_text),
("", "\n"),
("class:dockey", " group: "),
("", summary_dict.get("group", "?")),
("", "\n"),
("class:dockey", " syntax: "),
("", command_summary_name), # command
*compose_command_syntax(summary_dict, style_class=""), # command args
("", "\n\n"),
]
to_render = FormattedText(summary + rendered_detail)
if config.raw:
return convert_formatted_text_to_bytes(to_render)
return to_render
def do_peek(self, key):
"""
PEEK command implementation.
It's a generator, will run different redis commands based on the key's
type, yields FormattedText once a command reached result.
Redis current supported types:
string, list, set, zset, hash and stream.
"""
def _string(key):
strlen = self.execute("strlen", key)
yield FormattedText([("class:dockey", "strlen: "), ("", str(strlen))])
value = self.execute("GET", key)
yield FormattedText(
[
("class:dockey", "value: "),
("", renders.OutputRender.render_bulk_string(value)),
]
)
def _list(key):
llen = self.execute("llen", key)
yield FormattedText([("class:dockey", "llen: "), ("", str(llen))])
if llen <= 20:
contents = self.execute(f"LRANGE {key} 0 -1")
else:
first_10 = self.execute(f"LRANGE {key} 0 9")
last_10 = self.execute(f"LRANGE {key} -10 -1")
contents = first_10 + [f"{llen-20} elements was omitted ..."] + last_10
yield FormattedText([("class:dockey", "elements: ")])
yield renders.OutputRender.render_list(contents)
def _set(key):
cardinality = self.execute("scard", key)
yield FormattedText(
[("class:dockey", "cardinality: "), ("", str(cardinality))]
)
if cardinality <= 20:
contents = self.execute("smembers", key)
yield FormattedText([("class:dockey", "members: ")])
yield renders.OutputRender.render_list(contents)
else:
_, contents = self.execute(f"sscan {key} 0 count 20")
first_n = len(contents)
yield FormattedText([("class:dockey", f"members (first {first_n}): ")])
yield renders.OutputRender.render_members(contents)
# TODO update completers
def _zset(key):
count = self.execute(f"zcount {key} -inf +inf")
yield FormattedText([("class:dockey", "zcount: "), ("", str(count))])
if count <= 20:
contents = self.execute(f"zrange {key} 0 -1 withscores")
yield FormattedText([("class:dockey", "members: ")])
yield renders.OutputRender.render_members(contents)
else:
_, contents = self.execute(f"zscan {key} 0 count 20")
first_n = len(contents) // 2
yield FormattedText([("class:dockey", f"members (first {first_n}): ")])
config.withscores = True
output = renders.OutputRender.render_members(contents)
config.withscores = False
yield output
def _hash(key):
hlen = self.execute(f"hlen {key}")
yield FormattedText([("class:dockey", "hlen: "), ("", str(hlen))])
if hlen <= 20:
contents = self.execute(f"hgetall {key}")
yield FormattedText([("class:dockey", "fields: ")])
else:
_, contents = self.execute(f"hscan {key} 0 count 20")
first_n = len(contents) // 2
yield FormattedText([("class:dockey", f"fields (first {first_n}): ")])
yield renders.OutputRender.render_hash_pairs(contents)
def _stream(key):
xinfo = self.execute("xinfo stream", key)
yield FormattedText([("class:dockey", "XINFO: ")])
yield renders.OutputRender.render_list(xinfo)
# incase the result is too long, we yield only once so the outputer
# can pager it.
peek_response = []
key_type = nativestr(self.execute("type", key))
if key_type == "none":
yield f"{key} doesn't exist."
return
encoding = nativestr(self.execute("object encoding", key))
# use `memory usage` to get memory, this command available from redis4.0
mem = ""
if config.version and StrictVersion(config.version) >= StrictVersion("4.0.0"):
memory_usage_value = str(self.execute("memory usage", key))
mem = f" mem: {memory_usage_value} bytes"
ttl = str(self.execute("ttl", key))
key_info = f"{key_type} ({encoding}){mem}, ttl: {ttl}"
# FIXME raw write_result parse FormattedText
peek_response.append(FormattedText([("class:dockey", "key: "), ("", key_info)]))
detail_action_fun = {
"string": _string,
"list": _list,
"set": _set,
"zset": _zset,
"hash": _hash,
"stream": _stream,
}[key_type]
detail = list(detail_action_fun(key))
peek_response.extend(detail)
# merge them into only one FormattedText
flat_formatted_text_pair = []
for index, formatted_text in enumerate(peek_response):
for ft in formatted_text:
flat_formatted_text_pair.append(ft)
if index < len(peek_response) - 1:
flat_formatted_text_pair.append(renders.NEWLINE_TUPLE)
if config.raw:
yield convert_formatted_text_to_bytes(flat_formatted_text_pair)
return
yield FormattedText(flat_formatted_text_pair)

151
iredis/commands.py Normal file
View file

@ -0,0 +1,151 @@
import re
import csv
import json
import logging
import functools
from importlib_resources import read_text, open_text
from .utils import timer, strip_quote_args
from .exceptions import InvalidArguments, AmbiguousCommand
from . import data as project_data
logger = logging.getLogger(__name__)
def _load_command_summary():
commands_summary = json.loads(read_text(project_data, "commands.json"))
return commands_summary
def _load_command():
"""
load command informations from file.
:returns:
- original_commans: dict, command name : Command
- command_group: dict, group_name: command_names
"""
first_line = True
command2callback = {}
command2syntax = {}
groups = {}
with open_text(project_data, "command_syntax.csv") as command_syntax:
csvreader = csv.reader(command_syntax)
for line in csvreader:
if first_line:
first_line = False
continue
group, command, syntax, func_name = line
command2callback[command] = func_name
command2syntax[command] = syntax
groups.setdefault(group, []).append(command)
return command2callback, command2syntax, groups
def _load_dangerous():
"""
Load dangerous commands from csv file.
"""
first_line = True
dangerous_command = {}
with open_text(project_data, "dangerous_commands.csv") as dangerous_file:
csvreader = csv.reader(dangerous_file)
for line in csvreader:
if first_line:
first_line = False
continue
command, reason = line
dangerous_command[command] = reason
return dangerous_command
timer("[Loader] Start loading commands file...")
command2callback, command2syntax, groups = _load_command()
# all redis command strings, in UPPER case
# NOTE: Must sort by length, to match longest command first
all_commands = sorted(
list(command2callback.keys()) + ["HELP"], key=lambda x: len(x), reverse=True
)
# load commands information from redis-doc/commands.json
commands_summary = _load_command_summary()
# add iredis' commands' summary
commands_summary.update(
{
"HELP": {
"summary": "Show documents for a Redis command.",
"complexity": "O(1).",
"arguments": [{"name": "command", "type": "string"}],
"since": "1.0",
"group": "iredis",
},
"CLEAR": {
"summary": "Clear the screen like bash clear.",
"complexity": "O(1).",
"since": "1.0",
"group": "iredis",
},
"EXIT": {
"summary": "Exit iredis.",
"complexity": "O(1).",
"since": "1.0",
"group": "iredis",
},
"PEEK": {
"summary": "Get the key's type and value.",
"arguments": [{"name": "key", "type": "key"}],
"since": "1.0",
"complexity": "O(1).",
"since": "1.0",
"group": "iredis",
},
}
)
timer("[Loader] Finished loading commands.")
dangerous_commands = _load_dangerous()
@functools.lru_cache(maxsize=2048)
def split_command_args(command):
"""
Split Redis command text into command and args.
:param command: redis command string, with args
"""
global all_commands
command = command.strip()
for command_name in all_commands:
# for command that is paritaly inputed, like `command in`, we should
# match with `command info`, otherwise, `command in` will result in
# `command` with `args` is ('in') which is an invalid case.
normalized_input_command = " ".join(command.split()).upper()
if (
re.search("\s", command)
and command_name.startswith(normalized_input_command)
and command_name != normalized_input_command
):
raise AmbiguousCommand("command is not finished")
# allow multiplt space in user inputed command
command_allow_multi_spaces = "[ ]+".join(command_name.split())
matcher = re.match(fr"({command_allow_multi_spaces})( |$)", command.upper())
if matcher:
matched_command_len = len(matcher.group(1))
input_command = command[:matched_command_len]
input_args = command[matcher.end() :]
break
else:
raise InvalidArguments(f"`{command}` is not a valide Redis Command")
args = list(strip_quote_args(input_args))
return input_command, args
def split_unknown_args(command):
"""
Split user's input into command and args.
"""
command = command.strip()
input_command, *input_args = command.split(" ")
return input_command, list(strip_quote_args(" ".join(input_args)))

359
iredis/completers.py Normal file
View file

@ -0,0 +1,359 @@
import logging
from typing import Iterable
import pendulum
from prompt_toolkit.completion import (
CompleteEvent,
Completer,
Completion,
FuzzyWordCompleter,
WordCompleter,
)
from prompt_toolkit.contrib.regular_languages.completion import GrammarCompleter
from prompt_toolkit.document import Document
from .commands import split_command_args, commands_summary, all_commands
from .config import config
from .exceptions import InvalidArguments, AmbiguousCommand
from .redis_grammar import CONST, command_grammar, get_command_grammar
from .utils import strip_quote_args, ensure_str
logger = logging.getLogger(__name__)
class MostRecentlyUsedFirstWordMixin:
"""
A Mixin for WordCompleter, with a `touch()` method can make latest used
word appears first. And evict old completion word when `max_words` reached.
Not thread safe.
"""
def __init__(self, max_words, words, *args, **kwargs):
self.words = words
self.max_words = max_words
super().__init__(words, *args, **kwargs)
def touch(self, word):
"""
Make sure word is in the first place of the completer
list.
"""
if word in self.words:
self.words.remove(word)
else: # not in words
if len(self.words) == self.max_words: # full
self.words.pop()
self.words.insert(0, word)
def touch_words(self, words):
for word in words:
self.touch(word)
class MostRecentlyUsedFirstWordCompleter(
MostRecentlyUsedFirstWordMixin, FuzzyWordCompleter
):
pass
class IntegerTypeCompleter(MostRecentlyUsedFirstWordMixin, WordCompleter):
def __init__(self):
words = []
for i in range(1, 64):
words.append(f"i{i}") # signed integer, 64 bit max
words.append(f"u{i}") # unsigned integer, 63 bit max
words.append("i64")
super().__init__(len(words), list(reversed(words)))
class TimestampCompleter(Completer):
"""
Completer for timestamp based on input.
Features:
* Auto complete humanize time, like 3 -> 3 minutes ago, 3 hours ago.
* Auto guess datetime, complete by its timestamp. 2020-01-01 12:00
-> 1577851200.
The timezone is read from system.
"""
when_lower_than = {
"year": 20,
"month": 12,
"day": 31,
"hour": 100,
"minute": 1000,
"second": 1000_000,
}
def _completion_humanize_time(self, document: Document) -> Iterable[Completion]:
text = document.text
if not text.isnumeric():
return
current = int(text)
now = pendulum.now()
for unit, minium in self.when_lower_than.items():
if current <= minium:
dt = now.subtract(**{f"{unit}s": current})
meta = f"{text} {unit}{'s' if current > 1 else ''} ago ({dt.format('YYYY-MM-DD HH:mm:ss')})"
yield Completion(
str(dt.int_timestamp * 1000),
start_position=-len(document.text_before_cursor),
display_meta=meta,
)
def _completion_formatted_time(self, document: Document) -> Iterable[Completion]:
text = document.text
try:
dt = pendulum.parse(text)
except Exception:
return
yield Completion(
str(dt.int_timestamp * 1000),
start_position=-len(document.text_before_cursor),
display_meta=str(dt),
)
def get_completions(
self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]:
completions = list(self._completion_humanize_time(document)) + list(
self._completion_formatted_time(document)
)
# here we yield bigger timestamp first.
for completion in sorted(completions, key=lambda a: a.text):
yield completion
class IRedisCompleter(Completer):
"""
Completer class that can dynamically returns any Completer.
:param get_completer: Callable that returns a :class:`.Completer` instance.
"""
def __init__(self, hint=False, completion_casing="upper"):
super().__init__()
self.completer_mapping = self.get_completer_mapping(hint, completion_casing)
self.current_completer = self.root_completer = GrammarCompleter(
command_grammar, self.completer_mapping
)
@property
def key_completer(self) -> MostRecentlyUsedFirstWordCompleter:
return self.completer_mapping["key"]
@property
def member_completer(self) -> MostRecentlyUsedFirstWordCompleter:
return self.completer_mapping["member"]
@property
def field_completer(self) -> MostRecentlyUsedFirstWordCompleter:
return self.completer_mapping["field"]
@property
def group_completer(self) -> MostRecentlyUsedFirstWordCompleter:
return self.completer_mapping["group"]
@property
def catetoryname_completer(self) -> MostRecentlyUsedFirstWordCompleter:
return self.completer_mapping["categoryname"]
@property
def username_completer(self) -> MostRecentlyUsedFirstWordCompleter:
return self.completer_mapping["username"]
def get_completer(self, input_text):
try:
command, _ = split_command_args(input_text)
# here will compile grammar for this command
grammar = get_command_grammar(command)
completer = GrammarCompleter(
compiled_grammar=grammar, completers=self.completer_mapping
)
except (InvalidArguments, AmbiguousCommand):
completer = self.root_completer
return completer
def get_completions(
self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]:
input_text = document.text
self.current_completer = self.get_completer(input_text)
return self.current_completer.get_completions(document, complete_event)
def update_completer_for_input(self, command):
completer = self.get_completer(command)
grammar = completer.compiled_grammar
m = grammar.match(command)
if not m:
# invalide command!
return
variables = m.variables()
# auto update completion words, if it's LRU strategy.
for _token, _completer in self.completer_mapping.items():
if not isinstance(_completer, MostRecentlyUsedFirstWordMixin):
continue
# getall always returns a []
tokens_in_command = variables.getall(_token)
for _token_in_command in tokens_in_command:
# prompt_toolkit didn't support multi tokens
# like DEL key1 key2 key3
# so we have to split them manualy
for single_token in strip_quote_args(_token_in_command):
_completer.touch(single_token)
def update_completer_for_response(self, command_name, args, response):
command_name = " ".join(command_name.split()).upper()
logger.info(
f"Try update completer using response... command_name is {command_name}"
)
if response is None:
return
response = ensure_str(response)
if command_name in ("HKEYS",):
self.field_completer.touch_words(response)
logger.debug(f"[Completer] field completer updated with {response}.")
if command_name in ("HGETALL",):
self.field_completer.touch_words(response[::2])
logger.debug(f"[Completer] field completer updated with {response[::2]}.")
if command_name in ("ZPOPMAX", "ZPOPMIN", "ZRANGE", "ZRANGE", "ZRANGEBYSCORE"):
if config.withscores:
self.member_completer.touch_words(response[::2])
logger.debug(
f"[Completer] member completer updated with {response[::2]}."
)
else:
self.member_completer.touch_words(response)
logger.debug(f"[Completer] member completer updated with {response}.")
if command_name in ("KEYS",):
self.key_completer.touch_words(response)
logger.debug(f"[Completer] key completer updated with {response}.")
if command_name in ("SCAN",):
self.key_completer.touch_words(response[1])
logger.debug(f"[Completer] key completer updated with {response[1]}.")
if command_name in ("SSCAN", "ZSCAN"):
self.member_completer.touch_words(response[1])
logger.debug(f"[Completer] member completer updated with {response[1]}.")
if command_name in ("HSCAN",):
self.field_completer.touch_words(response[1][::2])
logger.debug(
f"[Completer] field completer updated with {response[1][::2]}."
)
# only update categoryname completer when `ACL CAT` without args.
if command_name == "ACL CAT" and not args:
self.catetoryname_completer.touch_words(response)
if command_name == "ACL USERS":
self.username_completer.touch_words(response)
def _touch_members(self, items):
_step = 1
if config.withscores:
_step = 2
self.member_completer.touch_words(ensure_str(items)[::_step])
def _touch_hash_pairs(self, items):
self.field_completer.touch_words(ensure_str(items)[::2])
def _touch_keys(self, items):
self.key_completer.touch_words(ensure_str(items))
def __repr__(self) -> str:
return "DynamicCompleter(%r -> %r)" % (
self.get_completer,
self.current_completer,
)
def get_completer_mapping(self, hint_on, completion_casing):
completer_mapping = {}
completer_mapping.update(
{
key: WordCompleter(tokens.split(" "), ignore_case=True)
for key, tokens in CONST.items()
}
)
key_completer = MostRecentlyUsedFirstWordCompleter(config.completer_max, [])
member_completer = MostRecentlyUsedFirstWordCompleter(config.completer_max, [])
field_completer = MostRecentlyUsedFirstWordCompleter(config.completer_max, [])
group_completer = MostRecentlyUsedFirstWordCompleter(config.completer_max, [])
username_completer = MostRecentlyUsedFirstWordCompleter(
config.completer_max, []
)
categoryname_completer = MostRecentlyUsedFirstWordCompleter(100, [])
timestamp_completer = TimestampCompleter()
integer_type_completer = IntegerTypeCompleter()
completer_mapping.update(
{
# all key related completers share the same completer
"keys": key_completer,
"key": key_completer,
"destination": key_completer,
"newkey": key_completer,
# member
"member": member_completer,
"members": member_completer,
# zmember
# TODO sperate sorted set and set
# hash fields
"field": field_completer,
"fields": field_completer,
# stream groups
"group": group_completer,
# stream id
"stream_id": timestamp_completer,
"inttype": integer_type_completer,
"categoryname": categoryname_completer,
"username": username_completer,
}
)
# command completer
if hint_on:
command_hint = {
key: info["summary"] for key, info in commands_summary.items()
}
hint = {
command: command_hint.get(command.upper()) for command in all_commands
}
hint.update(
{
command.lower(): command_hint.get(command.upper())
for command in all_commands
}
)
else:
hint = {}
upper_commands = all_commands[::-1]
lower_commands = [command.lower() for command in all_commands[::-1]]
auto_commands = upper_commands + lower_commands
ignore_case = completion_casing != "auto"
command_completions = {
"auto": auto_commands,
"upper": upper_commands,
"lower": lower_commands,
}.get(completion_casing)
completer_mapping["command"] = WordCompleter(
command_completions, ignore_case=ignore_case, sentence=True, meta_dict=hint
)
return completer_mapping

130
iredis/config.py Normal file
View file

@ -0,0 +1,130 @@
from importlib_resources import path
import os
import logging
from configobj import ConfigObj, ConfigObjError
from . import data as project_data
# TODO verbose logger to print to stdout
logger = logging.getLogger(__name__)
system_config_file = "/etc/iredisrc"
pwd_config_file = os.path.join(os.getcwd(), ".iredisrc")
class Config:
"""
Global config, set once on start, then
become readonly, never change again.
:param raw: weather write raw bytes to stdout without any
decoding.
:param decode: How to decode bytes response.(For display and
Completers)
default is None, means show literal bytes. But completers
will try use utf-8 decoding.
"""
def __init__(self):
self.raw = None
self.completer_max = None
# show command hint?
self.newbie_mode = None
self.rainbow = None
self.retry_times = 2
self.socket_keepalive = None
self.decode = None
self.no_info = None
self.bottom_bar = None
self.shell = None
self.enable_pager = None
self.pager = None
self.warning = True
self.no_version_reason = None
self.log_location = None
self.history_location = None
self.completion_casing = None
self.alias_dsn = None
# ===bad code===
# below are not configs, it's global state, it's wrong to write this
# please do not add more global state.
# FIXME this should be removed.
# use client attributes instead.
# use kwargs in render functions.
# for transaction render
self.queued_commands = []
self.transaction = False
# display zset withscores?
self.withscores = False
self.version = "Unknown"
def __setter__(self, name, value):
# for every time start a transaction
# clear the queued commands first
if name == "transaction" and value is True:
self.queued_commands = []
super().__setattr__(name, value)
config = Config()
def read_config_file(f):
"""Read a config file."""
if isinstance(f, str):
f = os.path.expanduser(f)
try:
config = ConfigObj(f, interpolation=False, encoding="utf8")
except ConfigObjError as e:
logger.error(
"Unable to parse line {0} of config file " "'{1}'.".format(e.line_number, f)
)
logger.error("Using successfully parsed config values.")
return e.config
except (IOError, OSError) as e:
logger.error(
"You don't have permission to read " "config file '{0}'.".format(e.filename)
)
return None
return config
def load_config_files(iredisrc):
global config
with path(project_data, "iredisrc") as p:
config_obj = ConfigObj(str(p))
for _file in [system_config_file, iredisrc, pwd_config_file]:
_config = read_config_file(_file)
if bool(_config) is True:
config_obj.merge(_config)
config_obj.filename = _config.filename
config.raw = config_obj["main"].as_bool("raw")
config.completer_max = config_obj["main"].as_int("completer_max")
config.retry_times = config_obj["main"].as_int("retry_times")
config.newbie_mode = config_obj["main"].as_bool("newbie_mode")
config.rainbow = config_obj["main"].as_bool("rainbow")
config.socket_keepalive = config_obj["main"].as_bool("socket_keepalive")
config.no_info = config_obj["main"].as_bool("no_info")
config.bottom_bar = config_obj["main"].as_bool("bottom_bar")
config.warning = config_obj["main"].as_bool("warning")
config.decode = config_obj["main"]["decode"]
config.log_location = config_obj["main"]["log_location"]
config.completion_casing = config_obj["main"]["completion_casing"]
config.history_location = config_obj["main"]["history_location"]
config.alias_dsn = config_obj["alias_dsn"]
config.shell = config_obj["main"].as_bool("shell")
config.pager = config_obj["main"].get("pager")
config.enable_pager = config_obj["main"].as_bool("enable_pager")
return config_obj

0
iredis/data/__init__.py Normal file
View file

View file

@ -0,0 +1,264 @@
Group,Command,Syntax,Callback
cluster,CLUSTER ADDSLOTS,command_slots,render_simple_string
cluster,CLUSTER BUMPEPOCH,command,render_simple_string
cluster,CLUSTER COUNT-FAILURE-REPORTS,command_node,render_int
cluster,CLUSTER COUNTKEYSINSLOT,command_slot,render_int
cluster,CLUSTER DELSLOTS,command_slots,render_simple_string
cluster,CLUSTER FAILOVER,command_failoverchoice,render_simple_string
cluster,CLUSTER FLUSHSLOTS,command,render_simple_string
cluster,CLUSTER FORGET,command_node,render_simple_string
cluster,CLUSTER GETKEYSINSLOT,command_slot_count,render_list
cluster,CLUSTER INFO,command,render_bulk_string_decode
cluster,CLUSTER KEYSLOT,command_key,render_int
cluster,CLUSTER MEET,command_ip_port,render_simple_string
cluster,CLUSTER MYID,command,render_bulk_string_decode
cluster,CLUSTER NODES,command,render_bulk_string_decode
cluster,CLUSTER REPLICAS,command_node,render_bulk_string_decode
cluster,CLUSTER REPLICATE,command_node,render_simple_string
cluster,CLUSTER RESET,command_resetchoice,render_simple_string
cluster,CLUSTER SAVECONFIG,command,render_simple_string
cluster,CLUSTER SET-CONFIG-EPOCH,command_epoch,render_simple_string
cluster,CLUSTER SETSLOT,command_slot_slotsubcmd_nodex,render_simple_string
cluster,CLUSTER SLAVES,command_node,render_bulk_string_decode
cluster,CLUSTER SLOTS,command,render_list
cluster,READONLY,command,render_simple_string
cluster,READWRITE,command,render_simple_string
connection,AUTH,command_password,render_simple_string
connection,ECHO,command_message,render_bulk_string
connection,HELLO,command_any,render_list
connection,PING,command_messagex,render_bulk_string
connection,QUIT,command,render_simple_string
connection,SELECT,command_index,render_simple_string
connection,CLIENT CACHING,command_yes,render_simple_string
connection,CLIENT GETREDIR,command,render_int
connection,CLIENT TRACKING,command_client_tracking,render_simple_string
connection,CLIENT LIST,command_type_conntype_x,render_bulk_string_decode
connection,CLIENT GETNAME,command,render_bulk_string
connection,CLIENT ID,command,render_int
connection,CLIENT KILL,command_clientkill,render_string_or_int
connection,CLIENT PAUSE,command_timeout,render_simple_string
connection,CLIENT REPLY,command_switch,render_simple_string
connection,CLIENT SETNAME,command_value,render_simple_string
connection,CLIENT UNBLOCK,command_clientid_errorx,render_int
generic,DEL,command_keys,render_int
generic,DUMP,command_key,render_bulk_string
generic,EXISTS,command_keys,render_int
generic,EXPIRE,command_key_second,render_int
generic,EXPIREAT,command_key_timestamp,render_int
generic,KEYS,command_pattern,command_keys
generic,MIGRATE,command_migrate,render_simple_string
generic,MOVE,command_key_index,render_int
generic,OBJECT,command_object_key,render_string_or_int
generic,PERSIST,command_key,render_int
generic,PEXPIRE,command_key_millisecond,render_int
generic,PEXPIREAT,command_key_timestampms,render_int
generic,PTTL,command_key,render_int
generic,RANDOMKEY,command,render_bulk_string
generic,RENAME,command_key_newkey,render_simple_string
generic,RENAMENX,command_key_newkey,render_int
generic,RESTORE,command_restore,render_simple_string
generic,SCAN,command_cursor_match_pattern_count_type,command_scan
generic,SORT,command_any,render_list_or_string
generic,TOUCH,command_keys,render_int
generic,TTL,command_key,render_int
generic,TYPE,command_key,render_bulk_string
generic,UNLINK,command_keys,render_int
generic,WAIT,command_count_timeout,render_int
geo,GEOADD,command_key_longitude_latitude_members,render_int
geo,GEODIST,command_geodist,render_bulk_string
geo,GEOHASH,command_key_members,render_list
geo,GEOPOS,command_key_members,render_list
geo,GEORADIUS,command_radius,render_list_or_string
geo,GEORADIUSBYMEMBER,command_georadiusbymember,render_list_or_string
hash,HDEL,command_key_fields,render_int
hash,HEXISTS,command_key_field,render_int
hash,HGET,command_key_field,render_bulk_string
hash,HGETALL,command_key,render_hash_pairs
hash,HINCRBY,command_key_field_delta,render_int
hash,HINCRBYFLOAT,command_key_field_float,render_bulk_string
hash,HKEYS,command_key,command_hkeys
hash,HLEN,command_key,render_int
hash,HMGET,command_key_fields,render_list
hash,HMSET,command_key_fieldvalues,render_bulk_string
hash,HSCAN,command_key_cursor_match_pattern_count,command_hscan
hash,HSET,command_key_field_value,render_int
hash,HSETNX,command_key_field_value,render_int
hash,HSTRLEN,command_key_field,render_int
hash,HVALS,command_key,render_list
hyperloglog,PFADD,command_key_values,render_int
hyperloglog,PFCOUNT,command_keys,render_int
hyperloglog,PFMERGE,command_newkey_keys,render_simple_string
list,BLPOP,command_keys_timeout,render_list_or_string
list,BRPOP,command_keys_timeout,render_list_or_string
list,BRPOPLPUSH,command_key_newkey_timeout,render_bulk_string
list,LINDEX,command_key_position,render_bulk_string
list,LINSERT,command_key_positionchoice_pivot_value,render_int
list,LLEN,command_key,render_int
list,LPOS,command_lpos,render_list_or_string
list,LPOP,command_key,render_bulk_string
list,LPUSH,command_key_values,render_int
list,LPUSHX,command_key_values,render_int
list,LRANGE,command_key_start_end,render_list
list,LREM,command_key_position_value,render_int
list,LSET,command_key_position_value,render_simple_string
list,LTRIM,command_key_start_end,render_simple_string
list,RPOP,command_key,render_bulk_string
list,RPOPLPUSH,command_key_newkey,render_bulk_string
list,RPUSH,command_key_values,render_int
list,RPUSHX,command_key_value,render_int
pubsub,PSUBSCRIBE,command_channels,render_subscribe
pubsub,PUBLISH,command_channel_message,render_int
pubsub,PUBSUB,command_pubsubcmd_channels,render_list_or_string
pubsub,PUNSUBSCRIBE,command_channels,render_subscribe
pubsub,SUBSCRIBE,command_channels,render_subscribe
pubsub,UNSUBSCRIBE,command_channels,render_subscribe
scripting,EVAL,command_lua_any,render_list_or_string
scripting,EVALSHA,command_any,render_list_or_string
scripting,SCRIPT DEBUG,command_scriptdebug,render_simple_string
scripting,SCRIPT EXISTS,command_any,render_list
scripting,SCRIPT FLUSH,command,render_simple_string
scripting,SCRIPT KILL,command,render_simple_string
scripting,SCRIPT LOAD,command_lua_any,render_bulk_string_decode
server,ACL CAT,command_categorynamex,render_list
server,ACL DELUSER,command_usernames,render_int
server,ACL GENPASS,command_countx,render_bulk_string
server,ACL GETUSER,command_username,render_list
server,ACL HELP,command,render_list
server,ACL LIST,command,render_list
server,ACL LOAD,command,render_simple_string
server,ACL LOG,command_count_or_resetx,render_list_or_string
server,ACL SAVE,command,render_simple_string
server,ACL SETUSER,command_username_rules,render_simple_string
server,ACL USERS,command,render_list
server,ACL WHOAMI,command,render_bulk_string
server,SWAPDB,command_index_index,render_simple_string
server,BGREWRITEAOF,command,render_simple_string
server,BGSAVE,command_schedulex,render_simple_string
server,COMMAND,command,render_list
server,COMMAND COUNT,command,render_int
server,COMMAND GETKEYS,command_any,render_list
server,COMMAND INFO,command_commandname,render_list
server,CONFIG GET,command_parameter,render_nested_pair
server,CONFIG RESETSTAT,command,render_simple_string
server,CONFIG REWRITE,command,render_simple_string
server,CONFIG SET,command_parameter_value,render_simple_string
server,DBSIZE,command,render_int
server,DEBUG OBJECT,command_key,render_simple_string
server,DEBUG SEGFAULT,command,render_simple_string
server,FLUSHALL,command_asyncx,render_simple_string
server,FLUSHDB,command_asyncx,render_simple_string
server,INFO,command_sectionx,render_bulk_string_decode
server,LOLWUT,command_version,render_bytes
server,LASTSAVE,command,render_unixtime
server,LATENCY DOCTOR,command,render_bulk_string_decode
server,LATENCY GRAPH,command_graphevent,render_bulk_string_decode
server,LATENCY HELP,command,render_list
server,LATENCY HISTORY,command_graphevent,render_list
server,LATENCY LATEST,command,render_list
server,LATENCY RESET,command_graphevents,render_int
server,MEMORY DOCTOR,command,render_bulk_string_decode
server,MEMORY HELP,command,render_list
server,MEMORY MALLOC-STATS,command,render_bulk_string_decode
server,MEMORY PURGE,command,render_simple_string
server,MEMORY STATS,command,render_nested_pair
server,MEMORY USAGE,command_key_samples_count,render_int
server,MODULE LIST,command,render_list
server,MODULE LOAD,command_any,render_simple_string
server,MODULE UNLOAD,command_any,render_simple_string
server,MONITOR,command,render_simple_string
server,PSYNC,command_replicationid_offset,render_bulk_string_decode
server,REPLICAOF,command_any,render_simple_string
server,ROLE,command,render_list
server,SAVE,command,render_simple_string
server,SHUTDOWN,command_shutdown,render_simple_string
server,SLAVEOF,command_any,render_simple_string
server,SLOWLOG,command_slowlog,render_slowlog
server,SYNC,command,render_bulk_string
server,TIME,command,render_time
set,SADD,command_key_members,render_int
set,SCARD,command_key,render_int
set,SDIFF,command_keys,render_list
set,SDIFFSTORE,command_destination_keys,render_int
set,SINTER,command_keys,render_list
set,SINTERSTORE,command_destination_keys,render_int
set,SISMEMBER,command_key_member,render_int
set,SMEMBERS,command_key,render_list
set,SMOVE,command_key_newkey_member,render_int
set,SPOP,command_key_count_x,render_list_or_string
set,SRANDMEMBER,command_key_count_x,render_list_or_string
set,SREM,command_key_members,render_int
set,SSCAN,command_key_cursor_match_pattern_count,command_sscan
set,SUNION,command_keys,render_list
set,SUNIONSTORE,command_destination_keys,render_int
sorted_set,BZPOPMAX,command_keys_timeout,render_list_or_string
sorted_set,BZPOPMIN,command_keys_timeout,render_list_or_string
sorted_set,ZADD,command_key_condition_changed_incr_score_members,render_string_or_int
sorted_set,ZCARD,command_key,render_int
sorted_set,ZCOUNT,command_key_min_max,render_int
sorted_set,ZINCRBY,command_key_float_member,render_bulk_string
sorted_set,ZINTERSTORE,command_any,render_int
sorted_set,ZLEXCOUNT,command_key_lexmin_lexmax,render_int
sorted_set,ZPOPMAX,command_key_count_x,render_members
sorted_set,ZPOPMIN,command_key_count_x,render_members
sorted_set,ZRANGE,command_key_start_end_withscores_x,render_members
sorted_set,ZRANGEBYLEX,command_key_lexmin_lexmax_limit_offset_count,render_list
sorted_set,ZRANGEBYSCORE,command_key_min_max_withscore_x_limit_offset_count_x,render_members
sorted_set,ZRANK,command_key_member,render_int
sorted_set,ZREM,command_key_members,render_int
sorted_set,ZREMRANGEBYLEX,command_key_lexmin_lexmax,render_int
sorted_set,ZREMRANGEBYRANK,command_key_start_end,render_int
sorted_set,ZREMRANGEBYSCORE,command_key_min_max,render_int
sorted_set,ZREVRANGE,command_key_start_end_withscores_x,render_list
sorted_set,ZREVRANGEBYLEX,command_key_lexmin_lexmax_limit_offset_count,render_list
sorted_set,ZREVRANGEBYSCORE,command_key_min_max_withscore_x_limit_offset_count_x,render_list
sorted_set,ZREVRANK,command_key_member,render_int
sorted_set,ZSCAN,command_key_cursor_match_pattern_count,command_sscan
sorted_set,ZSCORE,command_key_member,render_bulk_string
sorted_set,ZUNIONSTORE,command_any,render_int
stream,XACK,command_key_group_ids,render_int
stream,XADD,command_xadd,render_bulk_string
stream,XCLAIM,command_xclaim,render_list
stream,XDEL,command_key_ids,render_int
stream,XGROUP,command_xgroup,render_string_or_int
stream,XINFO,command_xinfo,render_list
stream,XLEN,command_key,render_int
stream,XPENDING,command_xpending,render_list
stream,XRANGE,command_key_start_end_countx,render_list
stream,XREAD,command_xread,render_list
stream,XREADGROUP,command_xreadgroup,render_list
stream,XREVRANGE,command_key_start_end_countx,render_list
stream,XTRIM,command_key_maxlen,render_int
string,APPEND,command_key_value,render_int
string,BITCOUNT,command_key_start_end_x,render_int
string,BITFIELD,command_bitfield,render_list
string,BITOP,command_operation_key_keys,render_int
string,BITPOS,command_key_bit_start_end,render_int
string,DECR,command_key,render_int
string,DECRBY,command_key_delta,render_int
string,GET,command_key,render_bulk_string
string,GETBIT,command_key_offset,render_int
string,GETRANGE,command_key_start_end,render_bulk_string
string,GETSET,command_key_value,render_bulk_string
string,INCR,command_key,render_int
string,INCRBY,command_key_delta,render_int
string,INCRBYFLOAT,command_key_float,render_bulk_string
string,MGET,command_keys,render_list
string,MSET,command_key_valuess,render_simple_string
string,MSETNX,command_key_valuess,render_int
string,PSETEX,command_key_millisecond_value,render_bulk_string
string,SET,command_set,render_simple_string
string,SETBIT,command_key_offset_bit,render_int
string,SETEX,command_key_second_value,render_bulk_string
string,SETNX,command_key_value,render_int
string,SETRANGE,command_key_offset_value,render_int
string,STRALGO,command_stralgo,render_list_or_string
string,STRLEN,command_key,render_int
transactions,DISCARD,command,render_simple_string
transactions,EXEC,command,render_list_or_string
transactions,MULTI,command,render_simple_string
transactions,UNWATCH,command,render_simple_string
transactions,WATCH,command_keys,render_simple_string
iredis,HELP,command_command,
iredis,PEEK,command_key,
iredis,CLEAR,command,
iredis,EXIT,command,
1 Group Command Syntax Callback
2 cluster CLUSTER ADDSLOTS command_slots render_simple_string
3 cluster CLUSTER BUMPEPOCH command render_simple_string
4 cluster CLUSTER COUNT-FAILURE-REPORTS command_node render_int
5 cluster CLUSTER COUNTKEYSINSLOT command_slot render_int
6 cluster CLUSTER DELSLOTS command_slots render_simple_string
7 cluster CLUSTER FAILOVER command_failoverchoice render_simple_string
8 cluster CLUSTER FLUSHSLOTS command render_simple_string
9 cluster CLUSTER FORGET command_node render_simple_string
10 cluster CLUSTER GETKEYSINSLOT command_slot_count render_list
11 cluster CLUSTER INFO command render_bulk_string_decode
12 cluster CLUSTER KEYSLOT command_key render_int
13 cluster CLUSTER MEET command_ip_port render_simple_string
14 cluster CLUSTER MYID command render_bulk_string_decode
15 cluster CLUSTER NODES command render_bulk_string_decode
16 cluster CLUSTER REPLICAS command_node render_bulk_string_decode
17 cluster CLUSTER REPLICATE command_node render_simple_string
18 cluster CLUSTER RESET command_resetchoice render_simple_string
19 cluster CLUSTER SAVECONFIG command render_simple_string
20 cluster CLUSTER SET-CONFIG-EPOCH command_epoch render_simple_string
21 cluster CLUSTER SETSLOT command_slot_slotsubcmd_nodex render_simple_string
22 cluster CLUSTER SLAVES command_node render_bulk_string_decode
23 cluster CLUSTER SLOTS command render_list
24 cluster READONLY command render_simple_string
25 cluster READWRITE command render_simple_string
26 connection AUTH command_password render_simple_string
27 connection ECHO command_message render_bulk_string
28 connection HELLO command_any render_list
29 connection PING command_messagex render_bulk_string
30 connection QUIT command render_simple_string
31 connection SELECT command_index render_simple_string
32 connection CLIENT CACHING command_yes render_simple_string
33 connection CLIENT GETREDIR command render_int
34 connection CLIENT TRACKING command_client_tracking render_simple_string
35 connection CLIENT LIST command_type_conntype_x render_bulk_string_decode
36 connection CLIENT GETNAME command render_bulk_string
37 connection CLIENT ID command render_int
38 connection CLIENT KILL command_clientkill render_string_or_int
39 connection CLIENT PAUSE command_timeout render_simple_string
40 connection CLIENT REPLY command_switch render_simple_string
41 connection CLIENT SETNAME command_value render_simple_string
42 connection CLIENT UNBLOCK command_clientid_errorx render_int
43 generic DEL command_keys render_int
44 generic DUMP command_key render_bulk_string
45 generic EXISTS command_keys render_int
46 generic EXPIRE command_key_second render_int
47 generic EXPIREAT command_key_timestamp render_int
48 generic KEYS command_pattern command_keys
49 generic MIGRATE command_migrate render_simple_string
50 generic MOVE command_key_index render_int
51 generic OBJECT command_object_key render_string_or_int
52 generic PERSIST command_key render_int
53 generic PEXPIRE command_key_millisecond render_int
54 generic PEXPIREAT command_key_timestampms render_int
55 generic PTTL command_key render_int
56 generic RANDOMKEY command render_bulk_string
57 generic RENAME command_key_newkey render_simple_string
58 generic RENAMENX command_key_newkey render_int
59 generic RESTORE command_restore render_simple_string
60 generic SCAN command_cursor_match_pattern_count_type command_scan
61 generic SORT command_any render_list_or_string
62 generic TOUCH command_keys render_int
63 generic TTL command_key render_int
64 generic TYPE command_key render_bulk_string
65 generic UNLINK command_keys render_int
66 generic WAIT command_count_timeout render_int
67 geo GEOADD command_key_longitude_latitude_members render_int
68 geo GEODIST command_geodist render_bulk_string
69 geo GEOHASH command_key_members render_list
70 geo GEOPOS command_key_members render_list
71 geo GEORADIUS command_radius render_list_or_string
72 geo GEORADIUSBYMEMBER command_georadiusbymember render_list_or_string
73 hash HDEL command_key_fields render_int
74 hash HEXISTS command_key_field render_int
75 hash HGET command_key_field render_bulk_string
76 hash HGETALL command_key render_hash_pairs
77 hash HINCRBY command_key_field_delta render_int
78 hash HINCRBYFLOAT command_key_field_float render_bulk_string
79 hash HKEYS command_key command_hkeys
80 hash HLEN command_key render_int
81 hash HMGET command_key_fields render_list
82 hash HMSET command_key_fieldvalues render_bulk_string
83 hash HSCAN command_key_cursor_match_pattern_count command_hscan
84 hash HSET command_key_field_value render_int
85 hash HSETNX command_key_field_value render_int
86 hash HSTRLEN command_key_field render_int
87 hash HVALS command_key render_list
88 hyperloglog PFADD command_key_values render_int
89 hyperloglog PFCOUNT command_keys render_int
90 hyperloglog PFMERGE command_newkey_keys render_simple_string
91 list BLPOP command_keys_timeout render_list_or_string
92 list BRPOP command_keys_timeout render_list_or_string
93 list BRPOPLPUSH command_key_newkey_timeout render_bulk_string
94 list LINDEX command_key_position render_bulk_string
95 list LINSERT command_key_positionchoice_pivot_value render_int
96 list LLEN command_key render_int
97 list LPOS command_lpos render_list_or_string
98 list LPOP command_key render_bulk_string
99 list LPUSH command_key_values render_int
100 list LPUSHX command_key_values render_int
101 list LRANGE command_key_start_end render_list
102 list LREM command_key_position_value render_int
103 list LSET command_key_position_value render_simple_string
104 list LTRIM command_key_start_end render_simple_string
105 list RPOP command_key render_bulk_string
106 list RPOPLPUSH command_key_newkey render_bulk_string
107 list RPUSH command_key_values render_int
108 list RPUSHX command_key_value render_int
109 pubsub PSUBSCRIBE command_channels render_subscribe
110 pubsub PUBLISH command_channel_message render_int
111 pubsub PUBSUB command_pubsubcmd_channels render_list_or_string
112 pubsub PUNSUBSCRIBE command_channels render_subscribe
113 pubsub SUBSCRIBE command_channels render_subscribe
114 pubsub UNSUBSCRIBE command_channels render_subscribe
115 scripting EVAL command_lua_any render_list_or_string
116 scripting EVALSHA command_any render_list_or_string
117 scripting SCRIPT DEBUG command_scriptdebug render_simple_string
118 scripting SCRIPT EXISTS command_any render_list
119 scripting SCRIPT FLUSH command render_simple_string
120 scripting SCRIPT KILL command render_simple_string
121 scripting SCRIPT LOAD command_lua_any render_bulk_string_decode
122 server ACL CAT command_categorynamex render_list
123 server ACL DELUSER command_usernames render_int
124 server ACL GENPASS command_countx render_bulk_string
125 server ACL GETUSER command_username render_list
126 server ACL HELP command render_list
127 server ACL LIST command render_list
128 server ACL LOAD command render_simple_string
129 server ACL LOG command_count_or_resetx render_list_or_string
130 server ACL SAVE command render_simple_string
131 server ACL SETUSER command_username_rules render_simple_string
132 server ACL USERS command render_list
133 server ACL WHOAMI command render_bulk_string
134 server SWAPDB command_index_index render_simple_string
135 server BGREWRITEAOF command render_simple_string
136 server BGSAVE command_schedulex render_simple_string
137 server COMMAND command render_list
138 server COMMAND COUNT command render_int
139 server COMMAND GETKEYS command_any render_list
140 server COMMAND INFO command_commandname render_list
141 server CONFIG GET command_parameter render_nested_pair
142 server CONFIG RESETSTAT command render_simple_string
143 server CONFIG REWRITE command render_simple_string
144 server CONFIG SET command_parameter_value render_simple_string
145 server DBSIZE command render_int
146 server DEBUG OBJECT command_key render_simple_string
147 server DEBUG SEGFAULT command render_simple_string
148 server FLUSHALL command_asyncx render_simple_string
149 server FLUSHDB command_asyncx render_simple_string
150 server INFO command_sectionx render_bulk_string_decode
151 server LOLWUT command_version render_bytes
152 server LASTSAVE command render_unixtime
153 server LATENCY DOCTOR command render_bulk_string_decode
154 server LATENCY GRAPH command_graphevent render_bulk_string_decode
155 server LATENCY HELP command render_list
156 server LATENCY HISTORY command_graphevent render_list
157 server LATENCY LATEST command render_list
158 server LATENCY RESET command_graphevents render_int
159 server MEMORY DOCTOR command render_bulk_string_decode
160 server MEMORY HELP command render_list
161 server MEMORY MALLOC-STATS command render_bulk_string_decode
162 server MEMORY PURGE command render_simple_string
163 server MEMORY STATS command render_nested_pair
164 server MEMORY USAGE command_key_samples_count render_int
165 server MODULE LIST command render_list
166 server MODULE LOAD command_any render_simple_string
167 server MODULE UNLOAD command_any render_simple_string
168 server MONITOR command render_simple_string
169 server PSYNC command_replicationid_offset render_bulk_string_decode
170 server REPLICAOF command_any render_simple_string
171 server ROLE command render_list
172 server SAVE command render_simple_string
173 server SHUTDOWN command_shutdown render_simple_string
174 server SLAVEOF command_any render_simple_string
175 server SLOWLOG command_slowlog render_slowlog
176 server SYNC command render_bulk_string
177 server TIME command render_time
178 set SADD command_key_members render_int
179 set SCARD command_key render_int
180 set SDIFF command_keys render_list
181 set SDIFFSTORE command_destination_keys render_int
182 set SINTER command_keys render_list
183 set SINTERSTORE command_destination_keys render_int
184 set SISMEMBER command_key_member render_int
185 set SMEMBERS command_key render_list
186 set SMOVE command_key_newkey_member render_int
187 set SPOP command_key_count_x render_list_or_string
188 set SRANDMEMBER command_key_count_x render_list_or_string
189 set SREM command_key_members render_int
190 set SSCAN command_key_cursor_match_pattern_count command_sscan
191 set SUNION command_keys render_list
192 set SUNIONSTORE command_destination_keys render_int
193 sorted_set BZPOPMAX command_keys_timeout render_list_or_string
194 sorted_set BZPOPMIN command_keys_timeout render_list_or_string
195 sorted_set ZADD command_key_condition_changed_incr_score_members render_string_or_int
196 sorted_set ZCARD command_key render_int
197 sorted_set ZCOUNT command_key_min_max render_int
198 sorted_set ZINCRBY command_key_float_member render_bulk_string
199 sorted_set ZINTERSTORE command_any render_int
200 sorted_set ZLEXCOUNT command_key_lexmin_lexmax render_int
201 sorted_set ZPOPMAX command_key_count_x render_members
202 sorted_set ZPOPMIN command_key_count_x render_members
203 sorted_set ZRANGE command_key_start_end_withscores_x render_members
204 sorted_set ZRANGEBYLEX command_key_lexmin_lexmax_limit_offset_count render_list
205 sorted_set ZRANGEBYSCORE command_key_min_max_withscore_x_limit_offset_count_x render_members
206 sorted_set ZRANK command_key_member render_int
207 sorted_set ZREM command_key_members render_int
208 sorted_set ZREMRANGEBYLEX command_key_lexmin_lexmax render_int
209 sorted_set ZREMRANGEBYRANK command_key_start_end render_int
210 sorted_set ZREMRANGEBYSCORE command_key_min_max render_int
211 sorted_set ZREVRANGE command_key_start_end_withscores_x render_list
212 sorted_set ZREVRANGEBYLEX command_key_lexmin_lexmax_limit_offset_count render_list
213 sorted_set ZREVRANGEBYSCORE command_key_min_max_withscore_x_limit_offset_count_x render_list
214 sorted_set ZREVRANK command_key_member render_int
215 sorted_set ZSCAN command_key_cursor_match_pattern_count command_sscan
216 sorted_set ZSCORE command_key_member render_bulk_string
217 sorted_set ZUNIONSTORE command_any render_int
218 stream XACK command_key_group_ids render_int
219 stream XADD command_xadd render_bulk_string
220 stream XCLAIM command_xclaim render_list
221 stream XDEL command_key_ids render_int
222 stream XGROUP command_xgroup render_string_or_int
223 stream XINFO command_xinfo render_list
224 stream XLEN command_key render_int
225 stream XPENDING command_xpending render_list
226 stream XRANGE command_key_start_end_countx render_list
227 stream XREAD command_xread render_list
228 stream XREADGROUP command_xreadgroup render_list
229 stream XREVRANGE command_key_start_end_countx render_list
230 stream XTRIM command_key_maxlen render_int
231 string APPEND command_key_value render_int
232 string BITCOUNT command_key_start_end_x render_int
233 string BITFIELD command_bitfield render_list
234 string BITOP command_operation_key_keys render_int
235 string BITPOS command_key_bit_start_end render_int
236 string DECR command_key render_int
237 string DECRBY command_key_delta render_int
238 string GET command_key render_bulk_string
239 string GETBIT command_key_offset render_int
240 string GETRANGE command_key_start_end render_bulk_string
241 string GETSET command_key_value render_bulk_string
242 string INCR command_key render_int
243 string INCRBY command_key_delta render_int
244 string INCRBYFLOAT command_key_float render_bulk_string
245 string MGET command_keys render_list
246 string MSET command_key_valuess render_simple_string
247 string MSETNX command_key_valuess render_int
248 string PSETEX command_key_millisecond_value render_bulk_string
249 string SET command_set render_simple_string
250 string SETBIT command_key_offset_bit render_int
251 string SETEX command_key_second_value render_bulk_string
252 string SETNX command_key_value render_int
253 string SETRANGE command_key_offset_value render_int
254 string STRALGO command_stralgo render_list_or_string
255 string STRLEN command_key render_int
256 transactions DISCARD command render_simple_string
257 transactions EXEC command render_list_or_string
258 transactions MULTI command render_simple_string
259 transactions UNWATCH command render_simple_string
260 transactions WATCH command_keys render_simple_string
261 iredis HELP command_command
262 iredis PEEK command_key
263 iredis CLEAR command
264 iredis EXIT command

4470
iredis/data/commands.json Normal file

File diff suppressed because it is too large Load diff

View file

View file

@ -0,0 +1,84 @@
The command shows the available ACL categories if called without arguments. If a
category name is given, the command shows all the Redis commands in the
specified category.
ACL categories are very useful in order to create ACL rules that include or
exclude a large set of commands at once, without specifying every single
command. For instance, the following rule will let the user `karin` perform
everything but the most dangerous operations that may affect the server
stability:
ACL SETUSER karin on +@all -@dangerous
We first add all the commands to the set of commands that `karin` is able to
execute, but then we remove all the dangerous commands.
Checking for all the available categories is as simple as:
```
> ACL CAT
1) "keyspace"
2) "read"
3) "write"
4) "set"
5) "sortedset"
6) "list"
7) "hash"
8) "string"
9) "bitmap"
10) "hyperloglog"
11) "geo"
12) "stream"
13) "pubsub"
14) "admin"
15) "fast"
16) "slow"
17) "blocking"
18) "dangerous"
19) "connection"
20) "transaction"
21) "scripting"
```
Then we may want to know what commands are part of a given category:
```
> ACL CAT dangerous
1) "flushdb"
2) "acl"
3) "slowlog"
4) "debug"
5) "role"
6) "keys"
7) "pfselftest"
8) "client"
9) "bgrewriteaof"
10) "replicaof"
11) "monitor"
12) "restore-asking"
13) "latency"
14) "replconf"
15) "pfdebug"
16) "bgsave"
17) "sync"
18) "config"
19) "flushall"
20) "cluster"
21) "info"
22) "lastsave"
23) "slaveof"
24) "swapdb"
25) "module"
26) "restore"
27) "migrate"
28) "save"
29) "shutdown"
30) "psync"
31) "sort"
```
@return
@array-reply: a list of ACL categories or a list of commands inside a given
category. The command may return an error if an invalid category name is given
as argument.

View file

@ -0,0 +1,17 @@
Delete all the specified ACL users and terminate all the connections that are
authenticated with such users. Note: the special `default` user cannot be
removed from the system, this is the default user that every new connection is
authenticated with. The list of users may include usernames that do not exist,
in such case no operation is performed for the non existing users.
@return
@integer-reply: The number of users that were deleted. This number will not
always match the number of arguments since certain users may not exist.
@examples
```
> ACL DELUSER antirez
1
```

View file

@ -0,0 +1,43 @@
ACL users need a solid password in order to authenticate to the server without
security risks. Such password does not need to be remembered by humans, but only
by computers, so it can be very long and strong (unguessable by an external
attacker). The `ACL GENPASS` command generates a password starting from
/dev/urandom if available, otherwise (in systems without /dev/urandom) it uses a
weaker system that is likely still better than picking a weak password by hand.
By default (if /dev/urandom is available) the password is strong and can be used
for other uses in the context of a Redis application, for instance in order to
create unique session identifiers or other kind of unguessable and not colliding
IDs. The password generation is also very cheap because we don't really ask
/dev/urandom for bits at every execution. At startup Redis creates a seed using
/dev/urandom, then it will use SHA256 in counter mode, with
HMAC-SHA256(seed,counter) as primitive, in order to create more random bytes as
needed. This means that the application developer should be feel free to abuse
`ACL GENPASS` to create as many secure pseudorandom strings as needed.
The command output is an hexadecimal representation of a binary string. By
default it emits 256 bits (so 64 hex characters). The user can provide an
argument in form of number of bits to emit from 1 to 1024 to change the output
length. Note that the number of bits provided is always rounded to the next
multiple of 4. So for instance asking for just 1 bit password will result in 4
bits to be emitted, in the form of a single hex character.
@return
@bulk-string-reply: by default 64 bytes string representing 256 bits of
pseudorandom data. Otherwise if an argument if needed, the output string length
is the number of specified bits (rounded to the next multiple of 4) divided
by 4.
@examples
```
> ACL GENPASS
"dd721260bfe1b3d9601e7fbab36de6d04e2e67b0ef1c53de59d45950db0dd3cc"
> ACL GENPASS 32
"355ef3dd"
> ACL GENPASS 5
"90"
```

View file

@ -0,0 +1,28 @@
The command returns all the rules defined for an existing ACL user.
Specifically, it lists the user's ACL flags, password hashes and key name
patterns. Note that command rules are returned as a string in the same format
used with the `ACL SETUSER` command. This description of command rules reflects
the user's effective permissions, so while it may not be identical to the set of
rules used to configure the user, it is still functionally identical.
@array-reply: a list of ACL rule definitions for the user.
@examples
Here's the default configuration for the default user:
```
> ACL GETUSER default
1) "flags"
2) 1) "on"
2) "allkeys"
3) "allcommands"
4) "nopass"
3) "passwords"
4) (empty array)
5) "commands"
6) "+@all"
7) "keys"
8) 1) "*"
```

View file

@ -0,0 +1,6 @@
The `ACL HELP` command returns a helpful text describing the different
subcommands.
@return
@array-reply: a list of subcommands and their descriptions

View file

@ -0,0 +1,17 @@
The command shows the currently active ACL rules in the Redis server. Each line
in the returned array defines a different user, and the format is the same used
in the redis.conf file or the external ACL file, so you can cut and paste what
is returned by the ACL LIST command directly inside a configuration file if you
wish (but make sure to check `ACL SAVE`).
@return
An array of strings.
@examples
```
> ACL LIST
1) "user antirez on #9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 ~objects:* +@all -@admin -@dangerous"
2) "user default on nopass ~* +@all"
```

View file

@ -0,0 +1,27 @@
When Redis is configured to use an ACL file (with the `aclfile` configuration
option), this command will reload the ACLs from the file, replacing all the
current ACL rules with the ones defined in the file. The command makes sure to
have an _all or nothing_ behavior, that is:
- If every line in the file is valid, all the ACLs are loaded.
- If one or more line in the file is not valid, nothing is loaded, and the old
ACL rules defined in the server memory continue to be used.
@return
@simple-string-reply: `OK` on success.
The command may fail with an error for several reasons: if the file is not
readable, if there is an error inside the file, and in such case the error will
be reported to the user in the error. Finally the command will fail if the
server is not configured to use an external ACL file.
@examples
```
> ACL LOAD
+OK
> ACL LOAD
-ERR /tmp/foo:1: Unknown command or category name in ACL...
```

View file

@ -0,0 +1,41 @@
The command shows a list of recent ACL security events:
1. Failures to authenticate their connections with `AUTH` or `HELLO`.
2. Commands denied because against the current ACL rules.
3. Commands denied because accessing keys not allowed in the current ACL rules.
The optional argument specifies how many entries to show. By default up to ten
failures are returned. The special `RESET` argument clears the log. Entries are
displayed starting from the most recent.
@return
When called to show security events:
@array-reply: a list of ACL security events.
When called with `RESET`:
@simple-string-reply: `OK` if the security log was cleared.
@examples
```
> AUTH someuser wrongpassword
(error) WRONGPASS invalid username-password pair
> ACL LOG 1
1) 1) "count"
2) (integer) 1
3) "reason"
4) "auth"
5) "context"
6) "toplevel"
7) "object"
8) "AUTH"
9) "username"
10) "someuser"
11) "age-seconds"
12) "4.0960000000000001"
13) "client-info"
14) "id=6 addr=127.0.0.1:63026 fd=8 name= age=9 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=48 qbuf-free=32720 obl=0 oll=0 omem=0 events=r cmd=auth user=default"
```

View file

@ -0,0 +1,20 @@
When Redis is configured to use an ACL file (with the `aclfile` configuration
option), this command will save the currently defined ACLs from the server
memory to the ACL file.
@return
@simple-string-reply: `OK` on success.
The command may fail with an error for several reasons: if the file cannot be
written or if the server is not configured to use an external ACL file.
@examples
```
> ACL SAVE
+OK
> ACL SAVE
-ERR There was an error trying to save the ACLs. Please check the server logs for more information
```

View file

@ -0,0 +1,115 @@
Create an ACL user with the specified rules or modify the rules of an existing
user. This is the main interface in order to manipulate Redis ACL users
interactively: if the username does not exist, the command creates the username
without any privilege, then reads from left to right all the rules provided as
successive arguments, setting the user ACL rules as specified.
If the user already exists, the provided ACL rules are simply applied _in
addition_ to the rules already set. For example:
ACL SETUSER virginia on allkeys +set
The above command will create a user called `virginia` that is active (the on
rule), can access any key (allkeys rule), and can call the set command (+set
rule). Then another SETUSER call can modify the user rules:
ACL SETUSER virginia +get
The above rule will not apply the new rule to the user virginia, so other than
`SET`, the user virginia will now be able to also use the `GET` command.
When we want to be sure to define an user from scratch, without caring if it had
previously defined rules associated, we can use the special rule `reset` as
first rule, in order to flush all the other existing rules:
ACL SETUSER antirez reset [... other rules ...]
After resetting an user, it returns back to the status it has when it was just
created: non active (off rule), can't execute any command, can't access any key:
> ACL SETUSER antirez reset
+OK
> ACL LIST
1) "user antirez off -@all"
ACL rules are either words like "on", "off", "reset", "allkeys", or are special
rules that start with a special character, and are followed by another string
(without any space in between), like "+SET".
The following documentation is a reference manual about the capabilities of this
command, however our [ACL tutorial](/topics/acl) may be a more gentle
introduction to how the ACL system works in general.
## List of rules
This is a list of all the supported Redis ACL rules:
- `on`: set the user as active, it will be possible to authenticate as this user
using `AUTH <username> <password>`.
- `off`: set user as not active, it will be impossible to log as this user.
Please note that if a user gets disabled (set to off) after there are
connections already authenticated with such a user, the connections will
continue to work as expected. To also kill the old connections you can use
`CLIENT KILL` with the user option. An alternative is to delete the user with
`ACL DELUSER`, that will result in all the connections authenticated as the
deleted user to be disconnected.
- `~<pattern>`: add the specified key pattern (glob style pattern, like in the
`KEYS` command), to the list of key patterns accessible by the user. You can
add as many key patterns you want to the same user. Example: `~objects:*`
- `allkeys`: alias for `~*`, it allows the user to access all the keys.
- `resetkey`: removes all the key patterns from the list of key patterns the
user can access.
- `+<command>`: add this command to the list of the commands the user can call.
Example: `+zadd`.
- `+@<category>`: add all the commands in the specified category to the list of
commands the user is able to execute. Example: `+@string` (adds all the string
commands). For a list of categories check the `ACL CAT` command.
- `+<command>|<subcommand>`: add the specified command to the list of the
commands the user can execute, but only for the specified subcommand. Example:
`+config|get`. Generates an error if the specified command is already allowed
in its full version for the specified user. Note: there is no symmetrical
command to remove subcommands, you need to remove the whole command and re-add
the subcommands you want to allow. This is much safer than removing
subcommands, in the future Redis may add new dangerous subcommands, so
configuring by subtraction is not good.
- `allcommands`: alias of `+@all`. Adds all the commands there are in the
server, including _future commands_ loaded via module, to be executed by this
user.
- `-<command>`. Like `+<command>` but removes the command instead of adding it.
- `-@<category>`: Like `+@<category>` but removes all the commands in the
category instead of adding them.
- `nocommands`: alias for `-@all`. Removes all the commands, the user will no
longer be able to execute anything.
- `nopass`: the user is set as a "no password" user. It means that it will be
possible to authenticate as such user with any password. By default, the
`default` special user is set as "nopass". The `nopass` rule will also reset
all the configured passwords for the user.
- `>password`: Add the specified clear text password as an hashed password in
the list of the users passwords. Every user can have many active passwords, so
that password rotation will be simpler. The specified password is not stored
in cleartext inside the server. Example: `>mypassword`.
- `#<hashedpassword>`: Add the specified hashed password to the list of user
passwords. A Redis hashed password is hashed with SHA256 and translated into a
hexadecimal string. Example:
`#c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2`.
- `<password`: Like `>password` but removes the password instead of adding it.
- `!<hashedpassword>`: Like `#<hashedpassword>` but removes the password instead
of adding it.
- reset: Remove any capability from the user. It is set to off, without
passwords, unable to execute any command, unable to access any key.
@return
@simple-string-reply: `OK` on success.
If the rules contain errors, the error is returned.
@examples
```
> ACL SETUSER alan allkeys +@string +@set -SADD >alanpassword
+OK
> ACL SETUSER antirez heeyyyy
(error) ERR Error in ACL SETUSER modifier 'heeyyyy': Syntax error
```

View file

@ -0,0 +1,15 @@
The command shows a list of all the usernames of the currently configured users
in the Redis ACL system.
@return
An array of strings.
@examples
```
> ACL USERS
1) "anna"
2) "antirez"
3) "default"
```

View file

@ -0,0 +1,14 @@
Return the username the current connection is authenticated with. New
connections are authenticated with the "default" user. They can change user
using `AUTH`.
@return
@bulk-string-reply: the username of the current connection.
@examples
```
> ACL WHOAMI
"default"
```

View file

@ -0,0 +1,55 @@
If `key` already exists and is a string, this command appends the `value` at the
end of the string. If `key` does not exist it is created and set as an empty
string, so `APPEND` will be similar to `SET` in this special case.
@return
@integer-reply: the length of the string after the append operation.
@examples
```cli
EXISTS mykey
APPEND mykey "Hello"
APPEND mykey " World"
GET mykey
```
## Pattern: Time series
The `APPEND` command can be used to create a very compact representation of a
list of fixed-size samples, usually referred as _time series_. Every time a new
sample arrives we can store it using the command
```
APPEND timeseries "fixed-size sample"
```
Accessing individual elements in the time series is not hard:
- `STRLEN` can be used in order to obtain the number of samples.
- `GETRANGE` allows for random access of elements. If our time series have
associated time information we can easily implement a binary search to get
range combining `GETRANGE` with the Lua scripting engine available in Redis
2.6.
- `SETRANGE` can be used to overwrite an existing time series.
The limitation of this pattern is that we are forced into an append-only mode of
operation, there is no way to cut the time series to a given size easily because
Redis currently lacks a command able to trim string objects. However the space
efficiency of time series stored in this way is remarkable.
Hint: it is possible to switch to a different key based on the current Unix
time, in this way it is possible to have just a relatively small amount of
samples per key, to avoid dealing with very big keys, and to make this pattern
more friendly to be distributed across many Redis instances.
An example sampling the temperature of a sensor using fixed-size strings (using
a binary format is better in real implementations).
```cli
APPEND ts "0043"
APPEND ts "0035"
GETRANGE ts 0 3
GETRANGE ts 4 7
```

View file

@ -0,0 +1,42 @@
The AUTH command authenticates the current connection in two cases:
1. If the Redis server is password protected via the `requirepass` option.
2. If a Redis 6.0 instance, or greater, is using the
[Redis ACL system](/topics/acl).
Redis versions prior of Redis 6 were only able to understand the one argument
version of the command:
AUTH <password>
This form just authenticates against the password set with `requirepass`. In
this configuration Redis will deny any command executed by the just connected
clients, unless the connection gets authenticated via `AUTH`.
If the password provided via AUTH matches the password in the configuration
file, the server replies with the `OK` status code and starts accepting
commands. Otherwise, an error is returned and the clients needs to try a new
password.
When Redis ACLs are used, the command should be given in an extended way:
AUTH <username> <password>
In order to authenticate the current connection with one of the connections
defined in the ACL list (see `ACL SETUSER`) and the official
[ACL guide](/topics/acl) for more information.
When ACLs are used, the single argument form of the command, where only the
password is specified, assumes that the implicit username is "default".
## Security notice
Because of the high performance nature of Redis, it is possible to try a lot of
passwords in parallel in very short time, so make sure to generate a strong and
very long password so that this attack is infeasible. A good way to generate
strong passwords is via the `ACL GENPASS` command.
@return
@simple-string-reply or an error if the password, or username/password pair, is
invalid.

View file

@ -0,0 +1,37 @@
Instruct Redis to start an [Append Only File][tpaof] rewrite process. The
rewrite will create a small optimized version of the current Append Only File.
[tpaof]: /topics/persistence#append-only-file
If `BGREWRITEAOF` fails, no data gets lost as the old AOF will be untouched.
The rewrite will be only triggered by Redis if there is not already a background
process doing persistence.
Specifically:
- If a Redis child is creating a snapshot on disk, the AOF rewrite is
_scheduled_ but not started until the saving child producing the RDB file
terminates. In this case the `BGREWRITEAOF` will still return an positive
status reply, but with an appropriate message. You can check if an AOF rewrite
is scheduled looking at the `INFO` command as of Redis 2.6 or successive
versions.
- If an AOF rewrite is already in progress the command returns an error and no
AOF rewrite will be scheduled for a later time.
- If the AOF rewrite could start, but the attempt at starting it fails (for
instance because of an error in creating the child process), an error is
returned to the caller.
Since Redis 2.4 the AOF rewrite is automatically triggered by Redis, however the
`BGREWRITEAOF` command can be used to trigger a rewrite at any time.
Please refer to the [persistence documentation][tp] for detailed information.
[tp]: /topics/persistence
@return
@simple-string-reply: A simple string reply indicating that the rewriting
started or is about to start ASAP, when the call is executed with success.
The command may reply with an error in certain cases, as documented above.

View file

@ -0,0 +1,28 @@
Save the DB in background.
Normally the OK code is immediately returned. Redis forks, the parent continues
to serve the clients, the child saves the DB on disk then exits.
An error is returned if there is already a background save running or if there
is another non-background-save process running, specifically an in-progress AOF
rewrite.
If `BGSAVE SCHEDULE` is used, the command will immediately return `OK` when an
AOF rewrite is in progress and schedule the background save to run at the next
opportunity.
A client may be able to check if the operation succeeded using the `LASTSAVE`
command.
Please refer to the [persistence documentation][tp] for detailed information.
[tp]: /topics/persistence
@return
@simple-string-reply: `Background saving started` if `BGSAVE` started correctly
or `Background saving scheduled` when used with the `SCHEDULE` subcommand.
@history
- `>= 3.2.2`: Added the `SCHEDULE` option.

View file

@ -0,0 +1,66 @@
Count the number of set bits (population counting) in a string.
By default all the bytes contained in the string are examined. It is possible to
specify the counting operation only in an interval passing the additional
arguments _start_ and _end_.
Like for the `GETRANGE` command start and end can contain negative values in
order to index bytes starting from the end of the string, where -1 is the last
byte, -2 is the penultimate, and so forth.
Non-existent keys are treated as empty strings, so the command will return zero.
@return
@integer-reply
The number of bits set to 1.
@examples
```cli
SET mykey "foobar"
BITCOUNT mykey
BITCOUNT mykey 0 0
BITCOUNT mykey 1 1
```
## Pattern: real-time metrics using bitmaps
Bitmaps are a very space-efficient representation of certain kinds of
information. One example is a Web application that needs the history of user
visits, so that for instance it is possible to determine what users are good
targets of beta features.
Using the `SETBIT` command this is trivial to accomplish, identifying every day
with a small progressive integer. For instance day 0 is the first day the
application was put online, day 1 the next day, and so forth.
Every time a user performs a page view, the application can register that in the
current day the user visited the web site using the `SETBIT` command setting the
bit corresponding to the current day.
Later it will be trivial to know the number of single days the user visited the
web site simply calling the `BITCOUNT` command against the bitmap.
A similar pattern where user IDs are used instead of days is described in the
article called "[Fast easy realtime metrics using Redis
bitmaps][hbgc212fermurb]".
[hbgc212fermurb]:
http://blog.getspool.com/2011/11/29/fast-easy-realtime-metrics-using-redis-bitmaps
## Performance considerations
In the above example of counting days, even after 10 years the application is
online we still have just `365*10` bits of data per user, that is just 456 bytes
per user. With this amount of data `BITCOUNT` is still as fast as any other O(1)
Redis command like `GET` or `INCR`.
When the bitmap is big, there are two alternatives:
- Taking a separated key that is incremented every time the bitmap is modified.
This can be very efficient and atomic using a small Redis Lua script.
- Running the bitmap incrementally using the `BITCOUNT` _start_ and _end_
optional parameters, accumulating the results client-side, and optionally
caching the result into a key.

View file

@ -0,0 +1,155 @@
The command treats a Redis string as a array of bits, and is capable of
addressing specific integer fields of varying bit widths and arbitrary non
(necessary) aligned offset. In practical terms using this command you can set,
for example, a signed 5 bits integer at bit offset 1234 to a specific value,
retrieve a 31 bit unsigned integer from offset 4567. Similarly the command
handles increments and decrements of the specified integers, providing
guaranteed and well specified overflow and underflow behavior that the user can
configure.
`BITFIELD` is able to operate with multiple bit fields in the same command call.
It takes a list of operations to perform, and returns an array of replies, where
each array matches the corresponding operation in the list of arguments.
For example the following command increments an 5 bit signed integer at bit
offset 100, and gets the value of the 4 bit unsigned integer at bit offset 0:
> BITFIELD mykey INCRBY i5 100 1 GET u4 0
1) (integer) 1
2) (integer) 0
Note that:
1. Addressing with `GET` bits outside the current string length (including the
case the key does not exist at all), results in the operation to be performed
like the missing part all consists of bits set to 0.
2. Addressing with `SET` or `INCRBY` bits outside the current string length will
enlarge the string, zero-padding it, as needed, for the minimal length
needed, according to the most far bit touched.
## Supported subcommands and integer types
The following is the list of supported commands.
- **GET** `<type>` `<offset>` -- Returns the specified bit field.
- **SET** `<type>` `<offset>` `<value>` -- Set the specified bit field and
returns its old value.
- **INCRBY** `<type>` `<offset>` `<increment>` -- Increments or decrements (if a
negative increment is given) the specified bit field and returns the new
value.
There is another subcommand that only changes the behavior of successive
`INCRBY` subcommand calls by setting the overflow behavior:
- **OVERFLOW** `[WRAP|SAT|FAIL]`
Where an integer type is expected, it can be composed by prefixing with `i` for
signed integers and `u` for unsigned integers with the number of bits of our
integer type. So for example `u8` is an unsigned integer of 8 bits and `i16` is
a signed integer of 16 bits.
The supported types are up to 64 bits for signed integers, and up to 63 bits for
unsigned integers. This limitation with unsigned integers is due to the fact
that currently the Redis protocol is unable to return 64 bit unsigned integers
as replies.
## Bits and positional offsets
There are two ways in order to specify offsets in the bitfield command. If a
number without any prefix is specified, it is used just as a zero based bit
offset inside the string.
However if the offset is prefixed with a `#` character, the specified offset is
multiplied by the integer type width, so for example:
BITFIELD mystring SET i8 #0 100 SET i8 #1 200
Will set the first i8 integer at offset 0 and the second at offset 8. This way
you don't have to do the math yourself inside your client if what you want is a
plain array of integers of a given size.
## Overflow control
Using the `OVERFLOW` command the user is able to fine-tune the behavior of the
increment or decrement overflow (or underflow) by specifying one of the
following behaviors:
- **WRAP**: wrap around, both with signed and unsigned integers. In the case of
unsigned integers, wrapping is like performing the operation modulo the
maximum value the integer can contain (the C standard behavior). With signed
integers instead wrapping means that overflows restart towards the most
negative value and underflows towards the most positive ones, so for example
if an `i8` integer is set to the value 127, incrementing it by 1 will yield
`-128`.
- **SAT**: uses saturation arithmetic, that is, on underflows the value is set
to the minimum integer value, and on overflows to the maximum integer value.
For example incrementing an `i8` integer starting from value 120 with an
increment of 10, will result into the value 127, and further increments will
always keep the value at 127. The same happens on underflows, but towards the
value is blocked at the most negative value.
- **FAIL**: in this mode no operation is performed on overflows or underflows
detected. The corresponding return value is set to NULL to signal the
condition to the caller.
Note that each `OVERFLOW` statement only affects the `INCRBY` commands that
follow it in the list of subcommands, up to the next `OVERFLOW` statement.
By default, **WRAP** is used if not otherwise specified.
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 1
2) (integer) 1
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 2
2) (integer) 2
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 3
2) (integer) 3
> BITFIELD mykey incrby u2 100 1 OVERFLOW SAT incrby u2 102 1
1) (integer) 0
2) (integer) 3
## Return value
The command returns an array with each entry being the corresponding result of
the sub command given at the same position. `OVERFLOW` subcommands don't count
as generating a reply.
The following is an example of `OVERFLOW FAIL` returning NULL.
> BITFIELD mykey OVERFLOW FAIL incrby u2 102 1
1) (nil)
## Motivations
The motivation for this command is that the ability to store many small integers
as a single large bitmap (or segmented over a few keys to avoid having huge
keys) is extremely memory efficient, and opens new use cases for Redis to be
applied, especially in the field of real time analytics. This use cases are
supported by the ability to specify the overflow in a controlled way.
Fun fact: Reddit's 2017 April fools' project
[r/place](https://reddit.com/r/place) was
[built using the Redis BITFIELD command](https://redditblog.com/2017/04/13/how-we-built-rplace/)
in order to take an in-memory representation of the collaborative canvas.
## Performance considerations
Usually `BITFIELD` is a fast command, however note that addressing far bits of
currently short strings will trigger an allocation that may be more costly than
executing the command on bits already existing.
## Orders of bits
The representation used by `BITFIELD` considers the bitmap as having the bit
number 0 to be the most significant bit of the first byte, and so forth, so for
example setting a 5 bits unsigned integer to value 23 at offset 7 into a bitmap
previously set to all zeroes, will produce the following representation:
+--------+--------+
|00000001|01110000|
+--------+--------+
When offsets and integer sizes are aligned to bytes boundaries, this is the same
as big endian, however when such alignment does not exist, its important to also
understand how the bits inside a byte are ordered.

View file

@ -0,0 +1,61 @@
Perform a bitwise operation between multiple keys (containing string values) and
store the result in the destination key.
The `BITOP` command supports four bitwise operations: **AND**, **OR**, **XOR**
and **NOT**, thus the valid forms to call the command are:
- `BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN`
- `BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN`
- `BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN`
- `BITOP NOT destkey srckey`
As you can see **NOT** is special as it only takes an input key, because it
performs inversion of bits so it only makes sense as an unary operator.
The result of the operation is always stored at `destkey`.
## Handling of strings with different lengths
When an operation is performed between strings having different lengths, all the
strings shorter than the longest string in the set are treated as if they were
zero-padded up to the length of the longest string.
The same holds true for non-existent keys, that are considered as a stream of
zero bytes up to the length of the longest string.
@return
@integer-reply
The size of the string stored in the destination key, that is equal to the size
of the longest input string.
@examples
```cli
SET key1 "foobar"
SET key2 "abcdef"
BITOP AND dest key1 key2
GET dest
```
## Pattern: real time metrics using bitmaps
`BITOP` is a good complement to the pattern documented in the `BITCOUNT` command
documentation. Different bitmaps can be combined in order to obtain a target
bitmap where the population counting operation is performed.
See the article called "[Fast easy realtime metrics using Redis
bitmaps][hbgc212fermurb]" for a interesting use cases.
[hbgc212fermurb]:
http://blog.getspool.com/2011/11/29/fast-easy-realtime-metrics-using-redis-bitmaps
## Performance considerations
`BITOP` is a potentially slow command as it runs in O(N) time. Care should be
taken when running it against long input strings.
For real-time metrics and statistics involving large inputs a good approach is
to use a replica (with read-only option disabled) where the bit-wise operations
are performed to avoid blocking the master instance.

View file

@ -0,0 +1,60 @@
Return the position of the first bit set to 1 or 0 in a string.
The position is returned, thinking of the string as an array of bits from left
to right, where the first byte's most significant bit is at position 0, the
second byte's most significant bit is at position 8, and so forth.
The same bit position convention is followed by `GETBIT` and `SETBIT`.
By default, all the bytes contained in the string are examined. It is possible
to look for bits only in a specified interval passing the additional arguments
_start_ and _end_ (it is possible to just pass _start_, the operation will
assume that the end is the last byte of the string. However there are semantic
differences as explained later). The range is interpreted as a range of bytes
and not a range of bits, so `start=0` and `end=2` means to look at the first
three bytes.
Note that bit positions are returned always as absolute values starting from bit
zero even when _start_ and _end_ are used to specify a range.
Like for the `GETRANGE` command start and end can contain negative values in
order to index bytes starting from the end of the string, where -1 is the last
byte, -2 is the penultimate, and so forth.
Non-existent keys are treated as empty strings.
@return
@integer-reply
The command returns the position of the first bit set to 1 or 0 according to the
request.
If we look for set bits (the bit argument is 1) and the string is empty or
composed of just zero bytes, -1 is returned.
If we look for clear bits (the bit argument is 0) and the string only contains
bit set to 1, the function returns the first bit not part of the string on the
right. So if the string is three bytes set to the value `0xff` the command
`BITPOS key 0` will return 24, since up to bit 23 all the bits are 1.
Basically, the function considers the right of the string as padded with zeros
if you look for clear bits and specify no range or the _start_ argument
**only**.
However, this behavior changes if you are looking for clear bits and specify a
range with both **start** and **end**. If no clear bit is found in the specified
range, the function returns -1 as the user specified a clear range and there are
no 0 bits in that range.
@examples
```cli
SET mykey "\xff\xf0\x00"
BITPOS mykey 0
SET mykey "\x00\xff\xf0"
BITPOS mykey 1 0
BITPOS mykey 1 2
set mykey "\x00\x00\x00"
BITPOS mykey 1
```

View file

@ -0,0 +1,182 @@
`BLPOP` is a blocking list pop primitive. It is the blocking version of `LPOP`
because it blocks the connection when there are no elements to pop from any of
the given lists. An element is popped from the head of the first list that is
non-empty, with the given keys being checked in the order that they are given.
## Non-blocking behavior
When `BLPOP` is called, if at least one of the specified keys contains a
non-empty list, an element is popped from the head of the list and returned to
the caller together with the `key` it was popped from.
Keys are checked in the order that they are given. Let's say that the key
`list1` doesn't exist and `list2` and `list3` hold non-empty lists. Consider the
following command:
```
BLPOP list1 list2 list3 0
```
`BLPOP` guarantees to return an element from the list stored at `list2` (since
it is the first non empty list when checking `list1`, `list2` and `list3` in
that order).
## Blocking behavior
If none of the specified keys exist, `BLPOP` blocks the connection until another
client performs an `LPUSH` or `RPUSH` operation against one of the keys.
Once new data is present on one of the lists, the client returns with the name
of the key unblocking it and the popped value.
When `BLPOP` causes a client to block and a non-zero timeout is specified, the
client will unblock returning a `nil` multi-bulk value when the specified
timeout has expired without a push operation against at least one of the
specified keys.
**The timeout argument is interpreted as an integer value specifying the maximum
number of seconds to block**. A timeout of zero can be used to block
indefinitely.
## What key is served first? What client? What element? Priority ordering details.
- If the client tries to blocks for multiple keys, but at least one key contains
elements, the returned key / element pair is the first key from left to right
that has one or more elements. In this case the client is not blocked. So for
instance `BLPOP key1 key2 key3 key4 0`, assuming that both `key2` and `key4`
are non-empty, will always return an element from `key2`.
- If multiple clients are blocked for the same key, the first client to be
served is the one that was waiting for more time (the first that blocked for
the key). Once a client is unblocked it does not retain any priority, when it
blocks again with the next call to `BLPOP` it will be served accordingly to
the number of clients already blocked for the same key, that will all be
served before it (from the first to the last that blocked).
- When a client is blocking for multiple keys at the same time, and elements are
available at the same time in multiple keys (because of a transaction or a Lua
script added elements to multiple lists), the client will be unblocked using
the first key that received a push operation (assuming it has enough elements
to serve our client, as there may be other clients as well waiting for this
key). Basically after the execution of every command Redis will run a list of
all the keys that received data AND that have at least a client blocked. The
list is ordered by new element arrival time, from the first key that received
data to the last. For every key processed, Redis will serve all the clients
waiting for that key in a FIFO fashion, as long as there are elements in this
key. When the key is empty or there are no longer clients waiting for this
key, the next key that received new data in the previous command / transaction
/ script is processed, and so forth.
## Behavior of `!BLPOP` when multiple elements are pushed inside a list.
There are times when a list can receive multiple elements in the context of the
same conceptual command:
- Variadic push operations such as `LPUSH mylist a b c`.
- After an `EXEC` of a `MULTI` block with multiple push operations against the
same list.
- Executing a Lua Script with Redis 2.6 or newer.
When multiple elements are pushed inside a list where there are clients
blocking, the behavior is different for Redis 2.4 and Redis 2.6 or newer.
For Redis 2.6 what happens is that the command performing multiple pushes is
executed, and _only after_ the execution of the command the blocked clients are
served. Consider this sequence of commands.
Client A: BLPOP foo 0
Client B: LPUSH foo a b c
If the above condition happens using a Redis 2.6 server or greater, Client **A**
will be served with the `c` element, because after the `LPUSH` command the list
contains `c,b,a`, so taking an element from the left means to return `c`.
Instead Redis 2.4 works in a different way: clients are served _in the context_
of the push operation, so as long as `LPUSH foo a b c` starts pushing the first
element to the list, it will be delivered to the Client **A**, that will receive
`a` (the first element pushed).
The behavior of Redis 2.4 creates a lot of problems when replicating or
persisting data into the AOF file, so the much more generic and semantically
simpler behavior was introduced into Redis 2.6 to prevent problems.
Note that for the same reason a Lua script or a `MULTI/EXEC` block may push
elements into a list and afterward **delete the list**. In this case the blocked
clients will not be served at all and will continue to be blocked as long as no
data is present on the list after the execution of a single command,
transaction, or script.
## `!BLPOP` inside a `!MULTI` / `!EXEC` transaction
`BLPOP` can be used with pipelining (sending multiple commands and reading the
replies in batch), however this setup makes sense almost solely when it is the
last command of the pipeline.
Using `BLPOP` inside a `MULTI` / `EXEC` block does not make a lot of sense as it
would require blocking the entire server in order to execute the block
atomically, which in turn does not allow other clients to perform a push
operation. For this reason the behavior of `BLPOP` inside `MULTI` / `EXEC` when
the list is empty is to return a `nil` multi-bulk reply, which is the same thing
that happens when the timeout is reached.
If you like science fiction, think of time flowing at infinite speed inside a
`MULTI` / `EXEC` block...
@return
@array-reply: specifically:
- A `nil` multi-bulk when no element could be popped and the timeout expired.
- A two-element multi-bulk with the first element being the name of the key
where an element was popped and the second element being the value of the
popped element.
@examples
```
redis> DEL list1 list2
(integer) 0
redis> RPUSH list1 a b c
(integer) 3
redis> BLPOP list1 list2 0
1) "list1"
2) "a"
```
## Reliable queues
When `BLPOP` returns an element to the client, it also removes the element from
the list. This means that the element only exists in the context of the client:
if the client crashes while processing the returned element, it is lost forever.
This can be a problem with some application where we want a more reliable
messaging system. When this is the case, please check the `BRPOPLPUSH` command,
that is a variant of `BLPOP` that adds the returned element to a target list
before returning it to the client.
## Pattern: Event notification
Using blocking list operations it is possible to mount different blocking
primitives. For instance for some application you may need to block waiting for
elements into a Redis Set, so that as far as a new element is added to the Set,
it is possible to retrieve it without resort to polling. This would require a
blocking version of `SPOP` that is not available, but using blocking list
operations we can easily accomplish this task.
The consumer will do:
```
LOOP forever
WHILE SPOP(key) returns elements
... process elements ...
END
BRPOP helper_key
END
```
While in the producer side we'll use simply:
```
MULTI
SADD key element
LPUSH helper_key x
EXEC
```

View file

@ -0,0 +1,31 @@
`BRPOP` is a blocking list pop primitive. It is the blocking version of `RPOP`
because it blocks the connection when there are no elements to pop from any of
the given lists. An element is popped from the tail of the first list that is
non-empty, with the given keys being checked in the order that they are given.
See the [BLPOP documentation][cb] for the exact semantics, since `BRPOP` is
identical to `BLPOP` with the only difference being that it pops elements from
the tail of a list instead of popping from the head.
[cb]: /commands/blpop
@return
@array-reply: specifically:
- A `nil` multi-bulk when no element could be popped and the timeout expired.
- A two-element multi-bulk with the first element being the name of the key
where an element was popped and the second element being the value of the
popped element.
@examples
```
redis> DEL list1 list2
(integer) 0
redis> RPUSH list1 a b c
(integer) 3
redis> BRPOP list1 list2 0
1) "list1"
2) "c"
```

View file

@ -0,0 +1,21 @@
`BRPOPLPUSH` is the blocking variant of `RPOPLPUSH`. When `source` contains
elements, this command behaves exactly like `RPOPLPUSH`. When used inside a
`MULTI`/`EXEC` block, this command behaves exactly like `RPOPLPUSH`. When
`source` is empty, Redis will block the connection until another client pushes
to it or until `timeout` is reached. A `timeout` of zero can be used to block
indefinitely.
See `RPOPLPUSH` for more information.
@return
@bulk-string-reply: the element being popped from `source` and pushed to
`destination`. If `timeout` is reached, a @nil-reply is returned.
## Pattern: Reliable queue
Please see the pattern description in the `RPOPLPUSH` documentation.
## Pattern: Circular list
Please see the pattern description in the `RPOPLPUSH` documentation.

View file

@ -0,0 +1,37 @@
`BZPOPMAX` is the blocking variant of the sorted set `ZPOPMAX` primitive.
It is the blocking version because it blocks the connection when there are no
members to pop from any of the given sorted sets. A member with the highest
score is popped from first sorted set that is non-empty, with the given keys
being checked in the order that they are given.
The `timeout` argument is interpreted as an integer value specifying the maximum
number of seconds to block. A timeout of zero can be used to block indefinitely.
See the [BZPOPMIN documentation][cb] for the exact semantics, since `BZPOPMAX`
is identical to `BZPOPMIN` with the only difference being that it pops members
with the highest scores instead of popping the ones with the lowest scores.
[cb]: /commands/bzpopmin
@return
@array-reply: specifically:
- A `nil` multi-bulk when no element could be popped and the timeout expired.
- A three-element multi-bulk with the first element being the name of the key
where a member was popped, the second element is the popped member itself, and
the third element is the score of the popped element.
@examples
```
redis> DEL zset1 zset2
(integer) 0
redis> ZADD zset1 0 a 1 b 2 c
(integer) 3
redis> BZPOPMAX zset1 zset2 0
1) "zset1"
2) "c"
3) "2"
```

View file

@ -0,0 +1,37 @@
`BZPOPMIN` is the blocking variant of the sorted set `ZPOPMIN` primitive.
It is the blocking version because it blocks the connection when there are no
members to pop from any of the given sorted sets. A member with the lowest score
is popped from first sorted set that is non-empty, with the given keys being
checked in the order that they are given.
The `timeout` argument is interpreted as an integer value specifying the maximum
number of seconds to block. A timeout of zero can be used to block indefinitely.
See the [BLPOP documentation][cl] for the exact semantics, since `BZPOPMIN` is
identical to `BLPOP` with the only difference being the data structure being
popped from.
[cl]: /commands/blpop
@return
@array-reply: specifically:
- A `nil` multi-bulk when no element could be popped and the timeout expired.
- A three-element multi-bulk with the first element being the name of the key
where a member was popped, the second element is the popped member itself, and
the third element is the score of the popped element.
@examples
```
redis> DEL zset1 zset2
(integer) 0
redis> ZADD zset1 0 a 1 b 2 c
(integer) 3
redis> BZPOPMIN zset1 zset2 0
1) "zset1"
2) "a"
3) "0"
```

View file

@ -0,0 +1,19 @@
This command controls the tracking of the keys in the next command executed by
the connection, when tracking is enabled in `OPTIN` or `OPTOUT` mode. Please
check the [client side caching documentation](/topics/client-side-caching) for
background informations.
When tracking is enabled Redis, using the `CLIENT TRACKING` command, it is
possible to specify the `OPTIN` or `OPTOUT` options, so that keys in read only
commands are not automatically remembered by the server to be invalidated later.
When we are in `OPTIN` mode, we can enable the tracking of the keys in the next
command by calling `CLIENT CACHING yes` immediately before it. Similarly when we
are in `OPTOUT` mode, and keys are normally tracked, we can avoid the keys in
the next command to be tracked using `CLIENT CACHING no`.
Basically the command sets a state in the connection, that is valid only for the
next command execution, that will modify the behavior of client tracking.
@return
@simple-string-reply: `OK` or an error if the argument is not yes or no.

View file

@ -0,0 +1,7 @@
The `CLIENT GETNAME` returns the name of the current connection as set by
`CLIENT SETNAME`. Since every new connection starts without an associated name,
if no name was assigned a null bulk reply is returned.
@return
@bulk-string-reply: The connection name, or a null bulk reply if no name is set.

View file

@ -0,0 +1,13 @@
This command returns the client ID we are redirecting our
[tracking](/topics/client-side-caching) notifications to. We set a client to
redirect to when using `CLIENT TRACKING` to enable tracking. However in order to
avoid forcing client libraries implementations to remember the ID notifications
are redirected to, this command exists in order to improve introspection and
allow clients to check later if redirection is active and towards which client
ID.
@return
@integer-reply: the ID of the client we are redirecting the notifications to.
The command returns `-1` if client tracking is not enabled, or `0` if client
tracking is enabled but we are not redirecting the notifications to any client.

View file

@ -0,0 +1,25 @@
The command just returns the ID of the current connection. Every connection ID
has certain guarantees:
1. It is never repeated, so if `CLIENT ID` returns the same number, the caller
can be sure that the underlying client did not disconnect and reconnect the
connection, but it is still the same connection.
2. The ID is monotonically incremental. If the ID of a connection is greater
than the ID of another connection, it is guaranteed that the second
connection was established with the server at a later time.
This command is especially useful together with `CLIENT UNBLOCK` which was
introduced also in Redis 5 together with `CLIENT ID`. Check the `CLIENT UNBLOCK`
command page for a pattern involving the two commands.
@examples
```cli
CLIENT ID
```
@return
@integer-reply
The id of the client.

View file

@ -0,0 +1,73 @@
The `CLIENT KILL` command closes a given client connection. Up to Redis 2.8.11
it was possible to close a connection only by client address, using the
following form:
CLIENT KILL addr:port
The `ip:port` should match a line returned by the `CLIENT LIST` command (`addr`
field).
However starting with Redis 2.8.12 or greater, the command accepts the following
form:
CLIENT KILL <filter> <value> ... ... <filter> <value>
With the new form it is possible to kill clients by different attributes instead
of killing just by address. The following filters are available:
- `CLIENT KILL ADDR ip:port`. This is exactly the same as the old
three-arguments behavior.
- `CLIENT KILL ID client-id`. Allows to kill a client by its unique `ID` field,
which was introduced in the `CLIENT LIST` command starting from Redis 2.8.12.
- `CLIENT KILL TYPE type`, where _type_ is one of `normal`, `master`, `slave`
and `pubsub` (the `master` type is available from v3.2). This closes the
connections of **all the clients** in the specified class. Note that clients
blocked into the `MONITOR` command are considered to belong to the `normal`
class.
- `CLIENT KILL USER username`. Closes all the connections that are authenticated
with the specified [ACL](/topics/acl) username, however it returns an error if
the username does not map to an existing ACL user.
- `CLIENT KILL SKIPME yes/no`. By default this option is set to `yes`, that is,
the client calling the command will not get killed, however setting this
option to `no` will have the effect of also killing the client calling the
command.
**Note: starting with Redis 5 the project is no longer using the slave word. You
can use `TYPE replica` instead, however the old form is still supported for
backward compatibility.**
It is possible to provide multiple filters at the same time. The command will
handle multiple filters via logical AND. For example:
CLIENT KILL addr 127.0.0.1:12345 type pubsub
is valid and will kill only a pubsub client with the specified address. This
format containing multiple filters is rarely useful currently.
When the new form is used the command no longer returns `OK` or an error, but
instead the number of killed clients, that may be zero.
## CLIENT KILL and Redis Sentinel
Recent versions of Redis Sentinel (Redis 2.8.12 or greater) use CLIENT KILL in
order to kill clients when an instance is reconfigured, in order to force
clients to perform the handshake with one Sentinel again and update its
configuration.
## Notes
Due to the single-threaded nature of Redis, it is not possible to kill a client
connection while it is executing a command. From the client point of view, the
connection can never be closed in the middle of the execution of a command.
However, the client will notice the connection has been closed only when the
next command is sent (and results in network error).
@return
When called with the three arguments format:
@simple-string-reply: `OK` if the connection exists and has been closed
When called with the filter / value format:
@integer-reply: the number of clients killed.

View file

@ -0,0 +1,70 @@
The `CLIENT LIST` command returns information and statistics about the client
connections server in a mostly human readable format.
As of v5.0, the optional `TYPE type` subcommand can be used to filter the list
by clients' type, where _type_ is one of `normal`, `master`, `replica` and
`pubsub`. Note that clients blocked into the `MONITOR` command are considered to
belong to the `normal` class.
@return
@bulk-string-reply: a unique string, formatted as follows:
- One client connection per line (separated by LF)
- Each line is composed of a succession of `property=value` fields separated by
a space character.
Here is the meaning of the fields:
- `id`: an unique 64-bit client ID (introduced in Redis 2.8.12).
- `name`: the name set by the client with `CLIENT SETNAME`
- `addr`: address/port of the client
- `fd`: file descriptor corresponding to the socket
- `age`: total duration of the connection in seconds
- `idle`: idle time of the connection in seconds
- `flags`: client flags (see below)
- `db`: current database ID
- `sub`: number of channel subscriptions
- `psub`: number of pattern matching subscriptions
- `multi`: number of commands in a MULTI/EXEC context
- `qbuf`: query buffer length (0 means no query pending)
- `qbuf-free`: free space of the query buffer (0 means the buffer is full)
- `obl`: output buffer length
- `oll`: output list length (replies are queued in this list when the buffer is
full)
- `omem`: output buffer memory usage
- `events`: file descriptor events (see below)
- `cmd`: last command played
The client flags can be a combination of:
```
A: connection to be closed ASAP
b: the client is waiting in a blocking operation
c: connection to be closed after writing entire reply
d: a watched keys has been modified - EXEC will fail
i: the client is waiting for a VM I/O (deprecated)
M: the client is a master
N: no specific flag set
O: the client is a client in MONITOR mode
P: the client is a Pub/Sub subscriber
r: the client is in readonly mode against a cluster node
S: the client is a replica node connection to this instance
u: the client is unblocked
U: the client is connected via a Unix domain socket
x: the client is in a MULTI/EXEC context
```
The file descriptor events can be:
```
r: the client socket is readable (event loop)
w: the client socket is writable (event loop)
```
## Notes
New fields are regularly added for debugging purpose. Some could be removed in
the future. A version safe Redis client using this command should parse the
output accordingly (i.e. handling gracefully missing fields, skipping unknown
fields).

View file

@ -0,0 +1,38 @@
`CLIENT PAUSE` is a connections control command able to suspend all the Redis
clients for the specified amount of time (in milliseconds).
The command performs the following actions:
- It stops processing all the pending commands from normal and pub/sub clients.
However interactions with replicas will continue normally.
- However it returns OK to the caller ASAP, so the `CLIENT PAUSE` command
execution is not paused by itself.
- When the specified amount of time has elapsed, all the clients are unblocked:
this will trigger the processing of all the commands accumulated in the query
buffer of every client during the pause.
This command is useful as it makes able to switch clients from a Redis instance
to another one in a controlled way. For example during an instance upgrade the
system administrator could do the following:
- Pause the clients using `CLIENT PAUSE`
- Wait a few seconds to make sure the replicas processed the latest replication
stream from the master.
- Turn one of the replicas into a master.
- Reconfigure clients to connect with the new master.
It is possible to send `CLIENT PAUSE` in a MULTI/EXEC block together with the
`INFO replication` command in order to get the current master offset at the time
the clients are blocked. This way it is possible to wait for a specific offset
in the replica side in order to make sure all the replication stream was
processed.
Since Redis 3.2.10 / 4.0.0, this command also prevents keys to be evicted or
expired during the time clients are paused. This way the dataset is guaranteed
to be static not just from the point of view of clients not being able to write,
but also from the point of view of internal operations.
@return
@simple-string-reply: The command returns OK or an error if the timeout is
invalid.

View file

@ -0,0 +1,21 @@
Sometimes it can be useful for clients to completely disable replies from the
Redis server. For example when the client sends fire and forget commands or
performs a mass loading of data, or in caching contexts where new data is
streamed constantly. In such contexts to use server time and bandwidth in order
to send back replies to clients, which are going to be ignored, is considered
wasteful.
The `CLIENT REPLY` command controls whether the server will reply the client's
commands. The following modes are available:
- `ON`. This is the default mode in which the server returns a reply to every
command.
- `OFF`. In this mode the server will not reply to client commands.
- `SKIP`. This mode skips the reply of command immediately after it.
@return
When called with either `OFF` or `SKIP` subcommands, no reply is made. When
called with `ON`:
@simple-string-reply: `OK`.

View file

@ -0,0 +1,28 @@
The `CLIENT SETNAME` command assigns a name to the current connection.
The assigned name is displayed in the output of `CLIENT LIST` so that it is
possible to identify the client that performed a given connection.
For instance when Redis is used in order to implement a queue, producers and
consumers of messages may want to set the name of the connection according to
their role.
There is no limit to the length of the name that can be assigned if not the
usual limits of the Redis string type (512 MB). However it is not possible to
use spaces in the connection name as this would violate the format of the
`CLIENT LIST` reply.
It is possible to entirely remove the connection name setting it to the empty
string, that is not a valid connection name since it serves to this specific
purpose.
The connection name can be inspected using `CLIENT GETNAME`.
Every new connection starts without an assigned name.
Tip: setting names to connections is a good way to debug connection leaks due to
bugs in the application using Redis.
@return
@simple-string-reply: `OK` if the connection name was successfully set.

View file

@ -0,0 +1,54 @@
This command enables the tracking feature of the Redis server, that is used for
[server assisted client side caching](/topics/client-side-caching).
When tracking is enabled Redis remembers the keys that the connection requested,
in order to send later invalidation messages when such keys are modified.
Invalidation messages are sent in the same connection (only available when the
RESP3 protocol is used) or redirected in a different connection (available also
with RESP2 and Pub/Sub). A special _broadcasting_ mode is available where
clients participating in this protocol receive every notification just
subscribing to given key prefixes, regardless of the keys that they requested.
Given the complexity of the argument please refer to
[the main client side caching documentation](/topics/client-side-caching) for
the details. This manual page is only a reference for the options of this
subcommand.
In order to enable tracking, use:
CLIENT TRACKING on ... options ...
The feature will remain active in the current connection for all its life,
unless tracking is turned on with `CLIENT TRACKING off` at some point.
The following are the list of options that modify the behavior of the command
when enabling tracking:
- `REDIRECT <id>`: send redirection messages to the connection with the
specified ID. The connection must exist, you can get the ID of such connection
using `CLIENT ID`. If the connection we are redirecting to is terminated, when
in RESP3 mode the connection with tracking enabled will receive
`tracking-redir-broken` push messages in order to signal the condition.
- `BCAST`: enable tracking in broadcasting mode. In this mode invalidation
messages are reported for all the prefixes specified, regardless of the keys
requested by the connection. Instead when the broadcasting mode is not
enabled, Redis will track which keys are fetched using read-only commands, and
will report invalidation messages only for such keys.
- `PREFIX <prefix>`: for broadcasting, register a given key prefix, so that
notifications will be provided only for keys starting with this string. This
option can be given multiple times to register multiple prefixes. If
broadcasting is enabled without this option, Redis will send notifications for
every key.
- `OPTIN`: when broadcasting is NOT active, normally don't track keys in read
only commands, unless they are called immediately after a `CLIENT CACHING yes`
command.
- `OPTOUT`: when broadcasting is NOT active, normally track keys in read only
commands, unless they are called immediately after a `CLIENT CACHING no`
command.
- `NOLOOP`: don't send notifications about keys modified by this connection
itself.
@return
@simple-string-reply: `OK` if the connection was successfully put in tracking
mode or if the tracking mode was successfully disabled. Otherwise an error is
returned.

View file

@ -0,0 +1,63 @@
This command can unblock, from a different connection, a client blocked in a
blocking operation, such as for instance `BRPOP` or `XREAD` or `WAIT`.
By default the client is unblocked as if the timeout of the command was reached,
however if an additional (and optional) argument is passed, it is possible to
specify the unblocking behavior, that can be **TIMEOUT** (the default) or
**ERROR**. If **ERROR** is specified, the behavior is to unblock the client
returning as error the fact that the client was force-unblocked. Specifically
the client will receive the following error:
-UNBLOCKED client unblocked via CLIENT UNBLOCK
Note: of course as usually it is not guaranteed that the error text remains the
same, however the error code will remain `-UNBLOCKED`.
This command is useful especially when we are monitoring many keys with a
limited number of connections. For instance we may want to monitor multiple
streams with `XREAD` without using more than N connections. However at some
point the consumer process is informed that there is one more stream key to
monitor. In order to avoid using more connections, the best behavior would be to
stop the blocking command from one of the connections in the pool, add the new
key, and issue the blocking command again.
To obtain this behavior the following pattern is used. The process uses an
additional _control connection_ in order to send the `CLIENT UNBLOCK` command if
needed. In the meantime, before running the blocking operation on the other
connections, the process runs `CLIENT ID` in order to get the ID associated with
that connection. When a new key should be added, or when a key should no longer
be monitored, the relevant connection blocking command is aborted by sending
`CLIENT UNBLOCK` in the control connection. The blocking command will return and
can be finally reissued.
This example shows the application in the context of Redis streams, however the
pattern is a general one and can be applied to other cases.
@example
```
Connection A (blocking connection):
> CLIENT ID
2934
> BRPOP key1 key2 key3 0
(client is blocked)
... Now we want to add a new key ...
Connection B (control connection):
> CLIENT UNBLOCK 2934
1
Connection A (blocking connection):
... BRPOP reply with timeout ...
NULL
> BRPOP key1 key2 key3 key4 0
(client is blocked again)
```
@return
@integer-reply, specifically:
- `1` if the client was unblocked successfully.
- `0` if the client wasn't unblocked.

View file

@ -0,0 +1,55 @@
This command is useful in order to modify a node's view of the cluster
configuration. Specifically it assigns a set of hash slots to the node receiving
the command. If the command is successful, the node will map the specified hash
slots to itself, and will start broadcasting the new configuration.
However note that:
1. The command only works if all the specified slots are, from the point of view
of the node receiving the command, currently not assigned. A node will refuse
to take ownership for slots that already belong to some other node (including
itself).
2. The command fails if the same slot is specified multiple times.
3. As a side effect of the command execution, if a slot among the ones specified
as argument is set as `importing`, this state gets cleared once the node
assigns the (previously unbound) slot to itself.
## Example
For example the following command assigns slots 1 2 3 to the node receiving the
command:
> CLUSTER ADDSLOTS 1 2 3
OK
However trying to execute it again results into an error since the slots are
already assigned:
> CLUSTER ADDSLOTS 1 2 3
ERR Slot 1 is already busy
## Usage in Redis Cluster
This command only works in cluster mode and is useful in the following Redis
Cluster operations:
1. To create a new cluster ADDSLOTS is used in order to initially setup master
nodes splitting the available hash slots among them.
2. In order to fix a broken cluster where certain slots are unassigned.
## Information about slots propagation and warnings
Note that once a node assigns a set of slots to itself, it will start
propagating this information in heartbeat packet headers. However the other
nodes will accept the information only if they have the slot as not already
bound with another node, or if the configuration epoch of the node advertising
the new hash slot, is greater than the node currently listed in the table.
This means that this command should be used with care only by applications
orchestrating Redis Cluster, like `redis-trib`, and the command if used out of
the right context can leave the cluster in a wrong state or cause data loss.
@return
@simple-string-reply: `OK` if the command was successful. Otherwise an error is
returned.

View file

@ -0,0 +1,15 @@
Advances the cluster config epoch.
The `CLUSTER BUMPEPOCH` command triggers an increment to the cluster's config
epoch from the connected node. The epoch will be incremented if the node's
config epoch is zero, or if it is less than the cluster's greatest epoch.
**Note:** config epoch management is performed internally by the cluster, and
relies on obtaining a consensus of nodes. The `CLUSTER BUMPEPOCH` attempts to
increment the config epoch **WITHOUT** getting the consensus, so using it may
violate the "last failover wins" rule. Use it with caution.
@return
@simple-string-reply: `BUMPED` if the epoch was incremented, or `STILL` if the
node already has the greatest config epoch in the cluster.

View file

@ -0,0 +1,34 @@
The command returns the number of _failure reports_ for the specified node.
Failure reports are the way Redis Cluster uses in order to promote a `PFAIL`
state, that means a node is not reachable, to a `FAIL` state, that means that
the majority of masters in the cluster agreed within a window of time that the
node is not reachable.
A few more details:
- A node flags another node with `PFAIL` when the node is not reachable for a
time greater than the configured _node timeout_, which is a fundamental
configuration parameter of a Redis Cluster.
- Nodes in `PFAIL` state are provided in gossip sections of heartbeat packets.
- Every time a node processes gossip packets from other nodes, it creates (and
refreshes the TTL if needed) **failure reports**, remembering that a given
node said another given node is in `PFAIL` condition.
- Each failure report has a time to live of two times the _node timeout_ time.
- If at a given time a node has another node flagged with `PFAIL`, and at the
same time collected the majority of other master nodes _failure reports_ about
this node (including itself if it is a master), then it elevates the failure
state of the node from `PFAIL` to `FAIL`, and broadcasts a message forcing all
the nodes that can be reached to flag the node as `FAIL`.
This command returns the number of failure reports for the current node which
are currently not expired (so received within two times the _node timeout_
time). The count does not include what the node we are asking this count
believes about the node ID we pass as argument, the count _only_ includes the
failure reports the node received from other nodes.
This command is mainly useful for debugging, when the failure detector of Redis
Cluster is not operating as we believe it should.
@return
@integer-reply: the number of active failure reports for the node.

View file

@ -0,0 +1,13 @@
Returns the number of keys in the specified Redis Cluster hash slot. The command
only queries the local data set, so contacting a node that is not serving the
specified hash slot will always result in a count of zero being returned.
```
> CLUSTER COUNTKEYSINSLOT 7000
(integer) 50341
```
@return
@integer-reply: The number of keys in the specified hash slot, or an error if
the hash slot is invalid.

View file

@ -0,0 +1,47 @@
In Redis Cluster, each node keeps track of which master is serving a particular
hash slot.
The `DELSLOTS` command asks a particular Redis Cluster node to forget which
master is serving the hash slots specified as arguments.
In the context of a node that has received a `DELSLOTS` command and has
consequently removed the associations for the passed hash slots, we say those
hash slots are _unbound_. Note that the existence of unbound hash slots occurs
naturally when a node has not been configured to handle them (something that can
be done with the `ADDSLOTS` command) and if it has not received any information
about who owns those hash slots (something that it can learn from heartbeat or
update messages).
If a node with unbound hash slots receives a heartbeat packet from another node
that claims to be the owner of some of those hash slots, the association is
established instantly. Moreover, if a heartbeat or update message is received
with a configuration epoch greater than the node's own, the association is
re-established.
However, note that:
1. The command only works if all the specified slots are already associated with
some node.
2. The command fails if the same slot is specified multiple times.
3. As a side effect of the command execution, the node may go into _down_ state
because not all hash slots are covered.
## Example
The following command removes the association for slots 5000 and 5001 from the
node receiving the command:
> CLUSTER DELSLOTS 5000 5001
OK
## Usage in Redis Cluster
This command only works in cluster mode and may be useful for debugging and in
order to manually orchestrate a cluster configuration when a new cluster is
created. It is currently not used by `redis-trib`, and mainly exists for API
completeness.
@return
@simple-string-reply: `OK` if the command was successful. Otherwise an error is
returned.

View file

@ -0,0 +1,81 @@
This command, that can only be sent to a Redis Cluster replica node, forces the
replica to start a manual failover of its master instance.
A manual failover is a special kind of failover that is usually executed when
there are no actual failures, but we wish to swap the current master with one of
its replicas (which is the node we send the command to), in a safe way, without
any window for data loss. It works in the following way:
1. The replica tells the master to stop processing queries from clients.
2. The master replies to the replica with the current _replication offset_.
3. The replica waits for the replication offset to match on its side, to make
sure it processed all the data from the master before it continues.
4. The replica starts a failover, obtains a new configuration epoch from the
majority of the masters, and broadcasts the new configuration.
5. The old master receives the configuration update: unblocks its clients and
starts replying with redirection messages so that they'll continue the chat
with the new master.
This way clients are moved away from the old master to the new master atomically
and only when the replica that is turning into the new master has processed all
of the replication stream from the old master.
## FORCE option: manual failover when the master is down
The command behavior can be modified by two options: **FORCE** and **TAKEOVER**.
If the **FORCE** option is given, the replica does not perform any handshake
with the master, that may be not reachable, but instead just starts a failover
ASAP starting from point 4. This is useful when we want to start a manual
failover while the master is no longer reachable.
However using **FORCE** we still need the majority of masters to be available in
order to authorize the failover and generate a new configuration epoch for the
replica that is going to become master.
## TAKEOVER option: manual failover without cluster consensus
There are situations where this is not enough, and we want a replica to failover
without any agreement with the rest of the cluster. A real world use case for
this is to mass promote replicas in a different data center to masters in order
to perform a data center switch, while all the masters are down or partitioned
away.
The **TAKEOVER** option implies everything **FORCE** implies, but also does not
uses any cluster authorization in order to failover. A replica receiving
`CLUSTER FAILOVER TAKEOVER` will instead:
1. Generate a new `configEpoch` unilaterally, just taking the current greatest
epoch available and incrementing it if its local configuration epoch is not
already the greatest.
2. Assign itself all the hash slots of its master, and propagate the new
configuration to every node which is reachable ASAP, and eventually to every
other node.
Note that **TAKEOVER violates the last-failover-wins principle** of Redis
Cluster, since the configuration epoch generated by the replica violates the
normal generation of configuration epochs in several ways:
1. There is no guarantee that it is actually the higher configuration epoch,
since, for example, we can use the **TAKEOVER** option within a minority, nor
any message exchange is performed to generate the new configuration epoch.
2. If we generate a configuration epoch which happens to collide with another
instance, eventually our configuration epoch, or the one of another instance
with our same epoch, will be moved away using the _configuration epoch
collision resolution algorithm_.
Because of this the **TAKEOVER** option should be used with care.
## Implementation details and notes
`CLUSTER FAILOVER`, unless the **TAKEOVER** option is specified, does not
execute a failover synchronously, it only _schedules_ a manual failover,
bypassing the failure detection stage, so to check if the failover actually
happened, `CLUSTER NODES` or other means should be used in order to verify that
the state of the cluster changes after some time the command was sent.
@return
@simple-string-reply: `OK` if the command was accepted and a manual failover is
going to be attempted. An error if the operation cannot be executed, for example
if we are talking with a node which is already a master.

View file

@ -0,0 +1,8 @@
Deletes all slots from a node.
The `CLUSTER FLUSHSLOTS` deletes all information about slots from the connected
node. It can only be called when the database is empty.
@reply
@simple-string-reply: `OK`

View file

@ -0,0 +1,59 @@
The command is used in order to remove a node, specified via its node ID, from
the set of _known nodes_ of the Redis Cluster node receiving the command. In
other words the specified node is removed from the _nodes table_ of the node
receiving the command.
Because when a given node is part of the cluster, all the other nodes
participating in the cluster knows about it, in order for a node to be
completely removed from a cluster, the `CLUSTER FORGET` command must be sent to
all the remaining nodes, regardless of the fact they are masters or replicas.
However the command cannot simply drop the node from the internal node table of
the node receiving the command, it also implements a ban-list, not allowing the
same node to be added again as a side effect of processing the _gossip section_
of the heartbeat packets received from other nodes.
## Details on why the ban-list is needed
In the following example we'll show why the command must not just remove a given
node from the nodes table, but also prevent it for being re-inserted again for
some time.
Let's assume we have four nodes, A, B, C and D. In order to end with just a
three nodes cluster A, B, C we may follow these steps:
1. Reshard all the hash slots from D to nodes A, B, C.
2. D is now empty, but still listed in the nodes table of A, B and C.
3. We contact A, and send `CLUSTER FORGET D`.
4. B sends node A a heartbeat packet, where node D is listed.
5. A does no longer known node D (see step 3), so it starts an handshake with D.
6. D ends re-added in the nodes table of A.
As you can see in this way removing a node is fragile, we need to send
`CLUSTER FORGET` commands to all the nodes ASAP hoping there are no gossip
sections processing in the meantime. Because of this problem the command
implements a ban-list with an expire time for each entry.
So what the command really does is:
1. The specified node gets removed from the nodes table.
2. The node ID of the removed node gets added to the ban-list, for 1 minute.
3. The node will skip all the node IDs listed in the ban-list when processing
gossip sections received in heartbeat packets from other nodes.
This way we have a 60 second window to inform all the nodes in the cluster that
we want to remove a node.
## Special conditions not allowing the command execution
The command does not succeed and returns an error in the following cases:
1. The specified node ID is not found in the nodes table.
2. The node receiving the command is a replica, and the specified node ID
identifies its current master.
3. The node ID identifies the same node we are sending the command to.
@return
@simple-string-reply: `OK` if the command was executed successfully, otherwise
an error is returned.

View file

@ -0,0 +1,20 @@
The command returns an array of keys names stored in the contacted node and
hashing to the specified hash slot. The maximum number of keys to return is
specified via the `count` argument, so that it is possible for the user of this
API to batch-processing keys.
The main usage of this command is during rehashing of cluster slots from one
node to another. The way the rehashing is performed is exposed in the Redis
Cluster specification, or in a more simple to digest form, as an appendix of the
`CLUSTER SETSLOT` command documentation.
```
> CLUSTER GETKEYSINSLOT 7000 3
"47344|273766|70329104160040|key_39015"
"47344|273766|70329104160040|key_89793"
"47344|273766|70329104160040|key_92937"
```
@return
@array-reply: From 0 to _count_ key names in a Redis array reply.

View file

@ -0,0 +1,56 @@
`CLUSTER INFO` provides `INFO` style information about Redis Cluster vital
parameters. The following is a sample output, followed by the description of
each field reported.
```
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:2
cluster_stats_messages_sent:1483972
cluster_stats_messages_received:1483968
```
- `cluster_state`: State is `ok` if the node is able to receive queries. `fail`
if there is at least one hash slot which is unbound (no node associated), in
error state (node serving it is flagged with FAIL flag), or if the majority of
masters can't be reached by this node.
- `cluster_slots_assigned`: Number of slots which are associated to some node
(not unbound). This number should be 16384 for the node to work properly,
which means that each hash slot should be mapped to a node.
- `cluster_slots_ok`: Number of hash slots mapping to a node not in `FAIL` or
`PFAIL` state.
- `cluster_slots_pfail`: Number of hash slots mapping to a node in `PFAIL`
state. Note that those hash slots still work correctly, as long as the `PFAIL`
state is not promoted to `FAIL` by the failure detection algorithm. `PFAIL`
only means that we are currently not able to talk with the node, but may be
just a transient error.
- `cluster_slots_fail`: Number of hash slots mapping to a node in `FAIL` state.
If this number is not zero the node is not able to serve queries unless
`cluster-require-full-coverage` is set to `no` in the configuration.
- `cluster_known_nodes`: The total number of known nodes in the cluster,
including nodes in `HANDSHAKE` state that may not currently be proper members
of the cluster.
- `cluster_size`: The number of master nodes serving at least one hash slot in
the cluster.
- `cluster_current_epoch`: The local `Current Epoch` variable. This is used in
order to create unique increasing version numbers during fail overs.
- `cluster_my_epoch`: The `Config Epoch` of the node we are talking with. This
is the current configuration version assigned to this node.
- `cluster_stats_messages_sent`: Number of messages sent via the cluster
node-to-node binary bus.
- `cluster_stats_messages_received`: Number of messages received via the cluster
node-to-node binary bus.
More information about the Current Epoch and Config Epoch variables are
available in the Redis Cluster specification document.
@return
@bulk-string-reply: A map between named fields and values in the form of
`<field>:<value>` lines separated by newlines composed by the two bytes `CRLF`.

View file

@ -0,0 +1,32 @@
Returns an integer identifying the hash slot the specified key hashes to. This
command is mainly useful for debugging and testing, since it exposes via an API
the underlying Redis implementation of the hashing algorithm. Example use cases
for this command:
1. Client libraries may use Redis in order to test their own hashing algorithm,
generating random keys and hashing them with both their local implementation
and using Redis `CLUSTER KEYSLOT` command, then checking if the result is the
same.
2. Humans may use this command in order to check what is the hash slot, and then
the associated Redis Cluster node, responsible for a given key.
## Example
```
> CLUSTER KEYSLOT somekey
11058
> CLUSTER KEYSLOT foo{hash_tag}
(integer) 2515
> CLUSTER KEYSLOT bar{hash_tag}
(integer) 2515
```
Note that the command implements the full hashing algorithm, including support
for **hash tags**, that is the special property of Redis Cluster key hashing
algorithm, of hashing just what is between `{` and `}` if such a pattern is
found inside the key name, in order to force multiple keys to be handled by the
same node.
@return
@integer-reply: The hash slot number.

View file

@ -0,0 +1,55 @@
`CLUSTER MEET` is used in order to connect different Redis nodes with cluster
support enabled, into a working cluster.
The basic idea is that nodes by default don't trust each other, and are
considered unknown, so that it is unlikely that different cluster nodes will mix
into a single one because of system administration errors or network addresses
modifications.
So in order for a given node to accept another one into the list of nodes
composing a Redis Cluster, there are only two ways:
1. The system administrator sends a `CLUSTER MEET` command to force a node to
meet another one.
2. An already known node sends a list of nodes in the gossip section that we are
not aware of. If the receiving node trusts the sending node as a known node,
it will process the gossip section and send an handshake to the nodes that
are still not known.
Note that Redis Cluster needs to form a full mesh (each node is connected with
each other node), but in order to create a cluster, there is no need to send all
the `CLUSTER MEET` commands needed to form the full mesh. What matter is to send
enough `CLUSTER MEET` messages so that each node can reach each other node
through a _chain of known nodes_. Thanks to the exchange of gossip information
in heartbeat packets, the missing links will be created.
So, if we link node A with node B via `CLUSTER MEET`, and B with C, A and C will
find their ways to handshake and create a link.
Another example: if we imagine a cluster formed of the following four nodes
called A, B, C and D, we may send just the following set of commands to A:
1. `CLUSTER MEET B-ip B-port`
2. `CLUSTER MEET C-ip C-port`
3. `CLUSTER MEET D-ip D-port`
As a side effect of `A` knowing and being known by all the other nodes, it will
send gossip sections in the heartbeat packets that will allow each other node to
create a link with each other one, forming a full mesh in a matter of seconds,
even if the cluster is large.
Moreover `CLUSTER MEET` does not need to be reciprocal. If I send the command to
A in order to join B, I don't need to also send it to B in order to join A.
## Implementation details: MEET and PING packets
When a given node receives a `CLUSTER MEET` message, the node specified in the
command still does not know the node we sent the command to. So in order for the
node to force the receiver to accept it as a trusted node, it sends a `MEET`
packet instead of a `PING` packet. The two packets have exactly the same format,
but the former forces the receiver to acknowledge the node as trusted.
@return
@simple-string-reply: `OK` if the command was successful. If the address or port
specified are invalid an error is returned.

View file

@ -0,0 +1,8 @@
Returns the node's id.
The `CLUSTER MYID` command returns the unique, auto-generated identifier that is
associated with the connected cluster node.
@return
@bulk-string-reply: The node id.

View file

@ -0,0 +1,147 @@
Each node in a Redis Cluster has its view of the current cluster configuration,
given by the set of known nodes, the state of the connection we have with such
nodes, their flags, properties and assigned slots, and so forth.
`CLUSTER NODES` provides all this information, that is, the current cluster
configuration of the node we are contacting, in a serialization format which
happens to be exactly the same as the one used by Redis Cluster itself in order
to store on disk the cluster state (however the on disk cluster state has a few
additional info appended at the end).
Note that normally clients willing to fetch the map between Cluster hash slots
and node addresses should use `CLUSTER SLOTS` instead. `CLUSTER NODES`, that
provides more information, should be used for administrative tasks, debugging,
and configuration inspections. It is also used by `redis-trib` in order to
manage a cluster.
## Serialization format
The output of the command is just a space-separated CSV string, where each line
represents a node in the cluster. The following is an example of output:
```
07c37dfeb235213a872192d90877d0cd55635b91 127.0.0.1:30004@31004 slave e7d1eecce10fd6bb5eb35b9f99a514335d9ba9ca 0 1426238317239 4 connected
67ed2db8d677e59ec4a4cefb06858cf2a1a89fa1 127.0.0.1:30002@31002 master - 0 1426238316232 2 connected 5461-10922
292f8b365bb7edb5e285caf0b7e6ddc7265d2f4f 127.0.0.1:30003@31003 master - 0 1426238318243 3 connected 10923-16383
6ec23923021cf3ffec47632106199cb7f496ce01 127.0.0.1:30005@31005 slave 67ed2db8d677e59ec4a4cefb06858cf2a1a89fa1 0 1426238316232 5 connected
824fe116063bc5fcf9f4ffd895bc17aee7731ac3 127.0.0.1:30006@31006 slave 292f8b365bb7edb5e285caf0b7e6ddc7265d2f4f 0 1426238317741 6 connected
e7d1eecce10fd6bb5eb35b9f99a514335d9ba9ca 127.0.0.1:30001@31001 myself,master - 0 0 1 connected 0-5460
```
Each line is composed of the following fields:
```
<id> <ip:port@cport> <flags> <master> <ping-sent> <pong-recv> <config-epoch> <link-state> <slot> <slot> ... <slot>
```
The meaning of each filed is the following:
1. `id`: The node ID, a 40 characters random string generated when a node is
created and never changed again (unless `CLUSTER RESET HARD` is used).
2. `ip:port@cport`: The node address where clients should contact the node to
run queries.
3. `flags`: A list of comma separated flags: `myself`, `master`, `slave`,
`fail?`, `fail`, `handshake`, `noaddr`, `noflags`. Flags are explained in
detail in the next section.
4. `master`: If the node is a replica, and the master is known, the master node
ID, otherwise the "-" character.
5. `ping-sent`: Milliseconds unix time at which the currently active ping was
sent, or zero if there are no pending pings.
6. `pong-recv`: Milliseconds unix time the last pong was received.
7. `config-epoch`: The configuration epoch (or version) of the current node (or
of the current master if the node is a replica). Each time there is a
failover, a new, unique, monotonically increasing configuration epoch is
created. If multiple nodes claim to serve the same hash slots, the one with
higher configuration epoch wins.
8. `link-state`: The state of the link used for the node-to-node cluster bus. We
use this link to communicate with the node. Can be `connected` or
`disconnected`.
9. `slot`: A hash slot number or range. Starting from argument number 9, but
there may be up to 16384 entries in total (limit never reached). This is the
list of hash slots served by this node. If the entry is just a number, is
parsed as such. If it is a range, it is in the form `start-end`, and means
that the node is responsible for all the hash slots from `start` to `end`
including the start and end values.
Meaning of the flags (field number 3):
- `myself`: The node you are contacting.
- `master`: Node is a master.
- `slave`: Node is a replica.
- `fail?`: Node is in `PFAIL` state. Not reachable for the node you are
contacting, but still logically reachable (not in `FAIL` state).
- `fail`: Node is in `FAIL` state. It was not reachable for multiple nodes that
promoted the `PFAIL` state to `FAIL`.
- `handshake`: Untrusted node, we are handshaking.
- `noaddr`: No address known for this node.
- `noflags`: No flags at all.
## Notes on published config epochs
Replicas broadcast their master's config epochs (in order to get an `UPDATE`
message if they are found to be stale), so the real config epoch of the replica
(which is meaningless more or less, since they don't serve hash slots) can be
only obtained checking the node flagged as `myself`, which is the entry of the
node we are asking to generate `CLUSTER NODES` output. The other replicas epochs
reflect what they publish in heartbeat packets, which is, the configuration
epoch of the masters they are currently replicating.
## Special slot entries
Normally hash slots associated to a given node are in one of the following
formats, as already explained above:
1. Single number: 3894
2. Range: 3900-4000
However node hash slots can be in a special state, used in order to communicate
errors after a node restart (mismatch between the keys in the AOF/RDB file, and
the node hash slots configuration), or when there is a resharding operation in
progress. This two states are **importing** and **migrating**.
The meaning of the two states is explained in the Redis Specification, however
the gist of the two states is the following:
- **Importing** slots are yet not part of the nodes hash slot, there is a
migration in progress. The node will accept queries about these slots only if
the `ASK` command is used.
- **Migrating** slots are assigned to the node, but are being migrated to some
other node. The node will accept queries if all the keys in the command exist
already, otherwise it will emit what is called an **ASK redirection**, to
force new keys creation directly in the importing node.
Importing and migrating slots are emitted in the `CLUSTER NODES` output as
follows:
- **Importing slot:** `[slot_number-<-importing_from_node_id]`
- **Migrating slot:** `[slot_number->-migrating_to_node_id]`
The following are a few examples of importing and migrating slots:
- `[93-<-292f8b365bb7edb5e285caf0b7e6ddc7265d2f4f]`
- `[1002-<-67ed2db8d677e59ec4a4cefb06858cf2a1a89fa1]`
- `[77->-e7d1eecce10fd6bb5eb35b9f99a514335d9ba9ca]`
- `[16311->-292f8b365bb7edb5e285caf0b7e6ddc7265d2f4f]`
Note that the format does not have any space, so `CLUSTER NODES` output format
is plain CSV with space as separator even when this special slots are emitted.
However a complete parser for the format should be able to handle them.
Note that:
1. Migration and importing slots are only added to the node flagged as `myself`.
This information is local to a node, for its own slots.
2. Importing and migrating slots are provided as **additional info**. If the
node has a given hash slot assigned, it will be also a plain number in the
list of hash slots, so clients that don't have a clue about hash slots
migrations can just skip this special fields.
@return
@bulk-string-reply: The serialized cluster configuration.
**A note about the word slave used in this man page and command name**: Starting
with Redis 5, if not for backward compatibility, the Redis project no longer
uses the word slave. Unfortunately in this command the word slave is part of the
protocol, so we'll be able to remove such occurrences only when this API will be
naturally deprecated.

View file

@ -0,0 +1,16 @@
The command provides a list of replica nodes replicating from the specified
master node. The list is provided in the same format used by `CLUSTER NODES`
(please refer to its documentation for the specification of the format).
The command will fail if the specified node is not known or if it is not a
master according to the node table of the node receiving the command.
Note that if a replica is added, moved, or removed from a given master node, and
we ask `CLUSTER REPLICAS` to a node that has not yet received the configuration
update, it may show stale information. However eventually (in a matter of
seconds if there are no network partitions) all the nodes will agree about the
set of nodes associated with a given master.
@return
The command returns data in the same format as `CLUSTER NODES`.

View file

@ -0,0 +1,29 @@
The command reconfigures a node as a replica of the specified master. If the
node receiving the command is an _empty master_, as a side effect of the
command, the node role is changed from master to replica.
Once a node is turned into the replica of another master node, there is no need
to inform the other cluster nodes about the change: heartbeat packets exchanged
between nodes will propagate the new configuration automatically.
A replica will always accept the command, assuming that:
1. The specified node ID exists in its nodes table.
2. The specified node ID does not identify the instance we are sending the
command to.
3. The specified node ID is a master.
If the node receiving the command is not already a replica, but is a master, the
command will only succeed, and the node will be converted into a replica, only
if the following additional conditions are met:
1. The node is not serving any hash slots.
2. The node is empty, no keys are stored at all in the key space.
If the command succeeds the new replica will immediately try to contact its
master in order to replicate from it.
@return
@simple-string-reply: `OK` if the command was executed successfully, otherwise
an error is returned.

View file

@ -0,0 +1,29 @@
Reset a Redis Cluster node, in a more or less drastic way depending on the reset
type, that can be **hard** or **soft**. Note that this command **does not work
for masters if they hold one or more keys**, in that case to completely reset a
master node keys must be removed first, e.g. by using `FLUSHALL` first, and then
`CLUSTER RESET`.
Effects on the node:
1. All the other nodes in the cluster are forgotten.
2. All the assigned / open slots are reset, so the slots-to-nodes mapping is
totally cleared.
3. If the node is a replica it is turned into an (empty) master. Its dataset is
flushed, so at the end the node will be an empty master.
4. **Hard reset only**: a new Node ID is generated.
5. **Hard reset only**: `currentEpoch` and `configEpoch` vars are set to 0.
6. The new configuration is persisted on disk in the node cluster configuration
file.
This command is mainly useful to re-provision a Redis Cluster node in order to
be used in the context of a new, different cluster. The command is also
extensively used by the Redis Cluster testing framework in order to reset the
state of the cluster every time a new test unit is executed.
If no reset type is specified, the default is **soft**.
@return
@simple-string-reply: `OK` if the command was successful. Otherwise an error is
returned.

View file

@ -0,0 +1,15 @@
Forces a node to save the `nodes.conf` configuration on disk. Before to return
the command calls `fsync(2)` in order to make sure the configuration is flushed
on the computer disk.
This command is mainly used in the event a `nodes.conf` node state file gets
lost / deleted for some reason, and we want to generate it again from scratch.
It can also be useful in case of mundane alterations of a node cluster
configuration via the `CLUSTER` command in order to ensure the new configuration
is persisted on disk, however all the commands should normally be able to auto
schedule to persist the configuration on disk when it is important to do so for
the correctness of the system in the event of a restart.
@return
@simple-string-reply: `OK` or an error if the operation fails.

View file

@ -0,0 +1,25 @@
This command sets a specific _config epoch_ in a fresh node. It only works when:
1. The nodes table of the node is empty.
2. The node current _config epoch_ is zero.
These prerequisites are needed since usually, manually altering the
configuration epoch of a node is unsafe, we want to be sure that the node with
the higher configuration epoch value (that is the last that failed over) wins
over other nodes in claiming the hash slots ownership.
However there is an exception to this rule, and it is when a new cluster is
created from scratch. Redis Cluster _config epoch collision resolution_
algorithm can deal with new nodes all configured with the same configuration at
startup, but this process is slow and should be the exception, only to make sure
that whatever happens, two more nodes eventually always move away from the state
of having the same configuration epoch.
So, using `CONFIG SET-CONFIG-EPOCH`, when a new cluster is created, we can
assign a different progressive configuration epoch to each node before joining
the cluster together.
@return
@simple-string-reply: `OK` if the command was executed successfully, otherwise
an error is returned.

View file

@ -0,0 +1,132 @@
`CLUSTER SETSLOT` is responsible of changing the state of a hash slot in the
receiving node in different ways. It can, depending on the subcommand used:
1. `MIGRATING` subcommand: Set a hash slot in _migrating_ state.
2. `IMPORTING` subcommand: Set a hash slot in _importing_ state.
3. `STABLE` subcommand: Clear any importing / migrating state from hash slot.
4. `NODE` subcommand: Bind the hash slot to a different node.
The command with its set of subcommands is useful in order to start and end
cluster live resharding operations, which are accomplished by setting a hash
slot in migrating state in the source node, and importing state in the
destination node.
Each subcommand is documented below. At the end you'll find a description of how
live resharding is performed using this command and other related commands.
## CLUSTER SETSLOT `<slot>` MIGRATING `<destination-node-id>`
This subcommand sets a slot to _migrating_ state. In order to set a slot in this
state, the node receiving the command must be the hash slot owner, otherwise an
error is returned.
When a slot is set in migrating state, the node changes behavior in the
following way:
1. If a command is received about an existing key, the command is processed as
usually.
2. If a command is received about a key that does not exists, an `ASK`
redirection is emitted by the node, asking the client to retry only that
specific query into `destination-node`. In this case the client should not
update its hash slot to node mapping.
3. If the command contains multiple keys, in case none exist, the behavior is
the same as point 2, if all exist, it is the same as point 1, however if only
a partial number of keys exist, the command emits a `TRYAGAIN` error in order
for the keys interested to finish being migrated to the target node, so that
the multi keys command can be executed.
## CLUSTER SETSLOT `<slot>` IMPORTING `<source-node-id>`
This subcommand is the reverse of `MIGRATING`, and prepares the destination node
to import keys from the specified source node. The command only works if the
node is not already owner of the specified hash slot.
When a slot is set in importing state, the node changes behavior in the
following way:
1. Commands about this hash slot are refused and a `MOVED` redirection is
generated as usually, but in the case the command follows an `ASKING`
command, in this case the command is executed.
In this way when a node in migrating state generates an `ASK` redirection, the
client contacts the target node, sends `ASKING`, and immediately after sends the
command. This way commands about non-existing keys in the old node or keys
already migrated to the target node are executed in the target node, so that:
1. New keys are always created in the target node. During a hash slot migration
we'll have to move only old keys, not new ones.
2. Commands about keys already migrated are correctly processed in the context
of the node which is the target of the migration, the new hash slot owner, in
order to guarantee consistency.
3. Without `ASKING` the behavior is the same as usually. This guarantees that
clients with a broken hash slots mapping will not write for error in the
target node, creating a new version of a key that has yet to be migrated.
## CLUSTER SETSLOT `<slot>` STABLE
This subcommand just clears migrating / importing state from the slot. It is
mainly used to fix a cluster stuck in a wrong state by `redis-trib fix`.
Normally the two states are cleared automatically at the end of the migration
using the `SETSLOT ... NODE ...` subcommand as explained in the next section.
## CLUSTER SETSLOT `<slot>` NODE `<node-id>`
The `NODE` subcommand is the one with the most complex semantics. It associates
the hash slot with the specified node, however the command works only in
specific situations and has different side effects depending on the slot state.
The following is the set of pre-conditions and side effects of the command:
1. If the current hash slot owner is the node receiving the command, but for
effect of the command the slot would be assigned to a different node, the
command will return an error if there are still keys for that hash slot in
the node receiving the command.
2. If the slot is in _migrating_ state, the state gets cleared when the slot is
assigned to another node.
3. If the slot was in _importing_ state in the node receiving the command, and
the command assigns the slot to this node (which happens in the target node
at the end of the resharding of a hash slot from one node to another), the
command has the following side effects: A) the _importing_ state is cleared.
B) If the node config epoch is not already the greatest of the cluster, it
generates a new one and assigns the new config epoch to itself. This way its
new hash slot ownership will win over any past configuration created by
previous failovers or slot migrations.
It is important to note that step 3 is the only time when a Redis Cluster node
will create a new config epoch without agreement from other nodes. This only
happens when a manual configuration is operated. However it is impossible that
this creates a non-transient setup where two nodes have the same config epoch,
since Redis Cluster uses a config epoch collision resolution algorithm.
@return
@simple-string-reply: All the subcommands return `OK` if the command was
successful. Otherwise an error is returned.
## Redis Cluster live resharding explained
The `CLUSTER SETSLOT` command is an important piece used by Redis Cluster in
order to migrate all the keys contained in one hash slot from one node to
another. This is how the migration is orchestrated, with the help of other
commands as well. We'll call the node that has the current ownership of the hash
slot the `source` node, and the node where we want to migrate the `destination`
node.
1. Set the destination node slot to _importing_ state using
`CLUSTER SETSLOT <slot> IMPORTING <source-node-id>`.
2. Set the source node slot to _migrating_ state using
`CLUSTER SETSLOT <slot> MIGRATING <destination-node-id>`.
3. Get keys from the source node with `CLUSTER GETKEYSINSLOT` command and move
them into the destination node using the `MIGRATE` command.
4. Use `CLUSTER SETSLOT <slot> NODE <destination-node-id>` in the source or
destination.
Notes:
- The order of step 1 and 2 is important. We want the destination node to be
ready to accept `ASK` redirections when the source node is configured to
redirect.
- Step 4 does not technically need to use `SETSLOT` in the nodes not involved in
the resharding, since the configuration will eventually propagate itself,
however it is a good idea to do so in order to stop nodes from pointing to the
wrong node for the hash slot moved as soon as possible, resulting in less
redirections to find the right node.

View file

@ -0,0 +1,22 @@
**A note about the word slave used in this man page and command name**: Starting
with Redis 5 this command: starting with Redis version 5, if not for backward
compatibility, the Redis project no longer uses the word slave. Please use the
new command `CLUSTER REPLICAS`. The command `SLAVEOF` will continue to work for
backward compatibility.
The command provides a list of replica nodes replicating from the specified
master node. The list is provided in the same format used by `CLUSTER NODES`
(please refer to its documentation for the specification of the format).
The command will fail if the specified node is not known or if it is not a
master according to the node table of the node receiving the command.
Note that if a replica is added, moved, or removed from a given master node, and
we ask `CLUSTER SLAVES` to a node that has not yet received the configuration
update, it may show stale information. However eventually (in a matter of
seconds if there are no network partitions) all the nodes will agree about the
set of nodes associated with a given master.
@return
The command returns data in the same format as `CLUSTER NODES`.

View file

@ -0,0 +1,102 @@
`CLUSTER SLOTS` returns details about which cluster slots map to which Redis
instances. The command is suitable to be used by Redis Cluster client libraries
implementations in order to retrieve (or update when a redirection is received)
the map associating cluster _hash slots_ with actual nodes network coordinates
(composed of an IP address and a TCP port), so that when a command is received,
it can be sent to what is likely the right instance for the keys specified in
the command.
## Nested Result Array
Each nested result is:
- Start slot range
- End slot range
- Master for slot range represented as nested IP/Port array
- First replica of master for slot range
- Second replica
- ...continues until all replicas for this master are returned.
Each result includes all active replicas of the master instance for the listed
slot range. Failed replicas are not returned.
The third nested reply is guaranteed to be the IP/Port pair of the master
instance for the slot range. All IP/Port pairs after the third nested reply are
replicas of the master.
If a cluster instance has non-contiguous slots (e.g. 1-400,900,1800-6000) then
master and replica IP/Port results will be duplicated for each top-level slot
range reply.
**Warning:** Newer versions of Redis Cluster will output, for each Redis
instance, not just the IP and port, but also the node ID as third element of the
array. In future versions there could be more elements describing the node
better. In general a client implementation should just rely on the fact that
certain parameters are at fixed positions as specified, but more parameters may
follow and should be ignored. Similarly a client library should try if possible
to cope with the fact that older versions may just have the IP and port
parameter.
@return
@array-reply: nested list of slot ranges with IP/Port mappings.
### Sample Output (old version)
```
127.0.0.1:7001> cluster slots
1) 1) (integer) 0
2) (integer) 4095
3) 1) "127.0.0.1"
2) (integer) 7000
4) 1) "127.0.0.1"
2) (integer) 7004
2) 1) (integer) 12288
2) (integer) 16383
3) 1) "127.0.0.1"
2) (integer) 7003
4) 1) "127.0.0.1"
2) (integer) 7007
3) 1) (integer) 4096
2) (integer) 8191
3) 1) "127.0.0.1"
2) (integer) 7001
4) 1) "127.0.0.1"
2) (integer) 7005
4) 1) (integer) 8192
2) (integer) 12287
3) 1) "127.0.0.1"
2) (integer) 7002
4) 1) "127.0.0.1"
2) (integer) 7006
```
### Sample Output (new version, includes IDs)
```
127.0.0.1:30001> cluster slots
1) 1) (integer) 0
2) (integer) 5460
3) 1) "127.0.0.1"
2) (integer) 30001
3) "09dbe9720cda62f7865eabc5fd8857c5d2678366"
4) 1) "127.0.0.1"
2) (integer) 30004
3) "821d8ca00d7ccf931ed3ffc7e3db0599d2271abf"
2) 1) (integer) 5461
2) (integer) 10922
3) 1) "127.0.0.1"
2) (integer) 30002
3) "c9d93d9f2c0c524ff34cc11838c2003d8c29e013"
4) 1) "127.0.0.1"
2) (integer) 30005
3) "faadb3eb99009de4ab72ad6b6ed87634c7ee410f"
3) 1) (integer) 10923
2) (integer) 16383
3) 1) "127.0.0.1"
2) (integer) 30003
3) "044ec91f325b7595e76dbcb18cc688b6a5b434a1"
4) 1) "127.0.0.1"
2) (integer) 30006
3) "58e6e48d41228013e5d9c1c37c5060693925e97e"
```

View file

@ -0,0 +1,11 @@
Returns @integer-reply of number of total commands in this Redis server.
@return
@integer-reply: number of commands returned by `COMMAND`
@examples
```cli
COMMAND COUNT
```

View file

@ -0,0 +1,21 @@
Returns @array-reply of keys from a full Redis command.
`COMMAND GETKEYS` is a helper command to let you find the keys from a full Redis
command.
`COMMAND` shows some commands as having movablekeys meaning the entire command
must be parsed to discover storage or retrieval keys. You can use
`COMMAND GETKEYS` to discover key positions directly from how Redis parses the
commands.
@return
@array-reply: list of keys from your command.
@examples
```cli
COMMAND GETKEYS MSET a b c d e f
COMMAND GETKEYS EVAL "not consulted" 3 key1 key2 key3 arg1 arg2 arg3 argN
COMMAND GETKEYS SORT mylist ALPHA STORE outlist
```

View file

@ -0,0 +1,18 @@
Returns @array-reply of details about multiple Redis commands.
Same result format as `COMMAND` except you can specify which commands get
returned.
If you request details about non-existing commands, their return position will
be nil.
@return
@array-reply: nested list of command details.
@examples
```cli
COMMAND INFO get set eval
COMMAND INFO foo evalsha config bar
```

View file

@ -0,0 +1,179 @@
Returns @array-reply of details about all Redis commands.
Cluster clients must be aware of key positions in commands so commands can go to
matching instances, but Redis commands vary between accepting one key, multiple
keys, or even multiple keys separated by other data.
You can use `COMMAND` to cache a mapping between commands and key positions for
each command to enable exact routing of commands to cluster instances.
## Nested Result Array
Each top-level result contains six nested results. Each nested result is:
- command name
- command arity specification
- nested @array-reply of command flags
- position of first key in argument list
- position of last key in argument list
- step count for locating repeating keys
### Command Name
Command name is the command returned as a lowercase string.
### Command Arity
<table style="width:50%">
<tr><td>
<pre>
<code>1) 1) "get"
2) (integer) 2
3) 1) readonly
4) (integer) 1
5) (integer) 1
6) (integer) 1
</code>
</pre>
</td>
<td>
<pre>
<code>1) 1) "mget"
2) (integer) -2
3) 1) readonly
4) (integer) 1
5) (integer) -1
6) (integer) 1
</code>
</pre>
</td></tr>
</table>
Command arity follows a simple pattern:
- positive if command has fixed number of required arguments.
- negative if command has minimum number of required arguments, but may have
more.
Command arity _includes_ counting the command name itself.
Examples:
- `GET` arity is 2 since the command only accepts one argument and always has
the format `GET _key_`.
- `MGET` arity is -2 since the command accepts at a minimum one argument, but up
to an unlimited number: `MGET _key1_ [key2] [key3] ...`.
Also note with `MGET`, the -1 value for "last key position" means the list of
keys may have unlimited length.
### Flags
Command flags is @array-reply containing one or more status replies:
- _write_ - command may result in modifications
- _readonly_ - command will never modify keys
- _denyoom_ - reject command if currently OOM
- _admin_ - server admin command
- _pubsub_ - pubsub-related command
- _noscript_ - deny this command from scripts
- _random_ - command has random results, dangerous for scripts
- _sort_for_script_ - if called from script, sort output
- _loading_ - allow command while database is loading
- _stale_ - allow command while replica has stale data
- _skip_monitor_ - do not show this command in MONITOR
- _asking_ - cluster related - accept even if importing
- _fast_ - command operates in constant or log(N) time. Used for latency
monitoring.
- _movablekeys_ - keys have no pre-determined position. You must discover keys
yourself.
### Movable Keys
```
1) 1) "sort"
2) (integer) -2
3) 1) write
2) denyoom
3) movablekeys
4) (integer) 1
5) (integer) 1
6) (integer) 1
```
Some Redis commands have no predetermined key locations. For those commands,
flag `movablekeys` is added to the command flags @array-reply. Your Redis
Cluster client needs to parse commands marked `movablekeys` to locate all
relevant key positions.
Complete list of commands currently requiring key location parsing:
- `SORT` - optional `STORE` key, optional `BY` weights, optional `GET` keys
- `ZUNIONSTORE` - keys stop when `WEIGHT` or `AGGREGATE` starts
- `ZINTERSTORE` - keys stop when `WEIGHT` or `AGGREGATE` starts
- `EVAL` - keys stop after `numkeys` count arguments
- `EVALSHA` - keys stop after `numkeys` count arguments
Also see `COMMAND GETKEYS` for getting your Redis server tell you where keys are
in any given command.
### First Key in Argument List
For most commands the first key is position 1. Position 0 is always the command
name itself.
### Last Key in Argument List
Redis commands usually accept one key, two keys, or an unlimited number of keys.
If a command accepts one key, the first key and last key positions is 1.
If a command accepts two keys (e.g. `BRPOPLPUSH`, `SMOVE`, `RENAME`, ...) then
the last key position is the location of the last key in the argument list.
If a command accepts an unlimited number of keys, the last key position is -1.
### Step Count
<table style="width:50%">
<tr><td>
<pre>
<code>1) 1) "mset"
2) (integer) -3
3) 1) write
2) denyoom
4) (integer) 1
5) (integer) -1
6) (integer) 2
</code>
</pre>
</td>
<td>
<pre>
<code>1) 1) "mget"
2) (integer) -2
3) 1) readonly
4) (integer) 1
5) (integer) -1
6) (integer) 1
</code>
</pre>
</td></tr>
</table>
Key step count allows us to find key positions in commands like `MSET` where the
format is `MSET _key1_ _val1_ [key2] [val2] [key3] [val3]...`.
In the case of `MSET`, keys are every other position so the step value is 2.
Compare with `MGET` above where the step value is just 1.
@return
@array-reply: nested list of command details. Commands are returned in random
order.
@examples
```cli
COMMAND
```

View file

@ -0,0 +1,52 @@
The `CONFIG GET` command is used to read the configuration parameters of a
running Redis server. Not all the configuration parameters are supported in
Redis 2.4, while Redis 2.6 can read the whole configuration of a server using
this command.
The symmetric command used to alter the configuration at run time is
`CONFIG SET`.
`CONFIG GET` takes a single argument, which is a glob-style pattern. All the
configuration parameters matching this parameter are reported as a list of
key-value pairs. Example:
```
redis> config get *max-*-entries*
1) "hash-max-zipmap-entries"
2) "512"
3) "list-max-ziplist-entries"
4) "512"
5) "set-max-intset-entries"
6) "512"
```
You can obtain a list of all the supported configuration parameters by typing
`CONFIG GET *` in an open `redis-cli` prompt.
All the supported parameters have the same meaning of the equivalent
configuration parameter used in the [redis.conf][hgcarr22rc] file, with the
following important differences:
[hgcarr22rc]: http://github.com/redis/redis/raw/2.8/redis.conf
- Where bytes or other quantities are specified, it is not possible to use the
`redis.conf` abbreviated form (`10k`, `2gb` ... and so forth), everything
should be specified as a well-formed 64-bit integer, in the base unit of the
configuration directive.
- The save parameter is a single string of space-separated integers. Every pair
of integers represent a seconds/modifications threshold.
For instance what in `redis.conf` looks like:
```
save 900 1
save 300 10
```
that means, save after 900 seconds if there is at least 1 change to the dataset,
and after 300 seconds if there are at least 10 changes to the dataset, will be
reported by `CONFIG GET` as "900 1 300 10".
@return
The return type of the command is a @array-reply.

View file

@ -0,0 +1,16 @@
Resets the statistics reported by Redis using the `INFO` command.
These are the counters that are reset:
- Keyspace hits
- Keyspace misses
- Number of commands processed
- Number of connections received
- Number of expired keys
- Number of rejected connections
- Latest fork(2) time
- The `aof_delayed_fsync` counter
@return
@simple-string-reply: always `OK`.

View file

@ -0,0 +1,37 @@
The `CONFIG REWRITE` command rewrites the `redis.conf` file the server was
started with, applying the minimal changes needed to make it reflect the
configuration currently used by the server, which may be different compared to
the original one because of the use of the `CONFIG SET` command.
The rewrite is performed in a very conservative way:
- Comments and the overall structure of the original redis.conf are preserved as
much as possible.
- If an option already exists in the old redis.conf file, it will be rewritten
at the same position (line number).
- If an option was not already present, but it is set to its default value, it
is not added by the rewrite process.
- If an option was not already present, but it is set to a non-default value, it
is appended at the end of the file.
- Non used lines are blanked. For instance if you used to have multiple `save`
directives, but the current configuration has fewer or none as you disabled
RDB persistence, all the lines will be blanked.
CONFIG REWRITE is also able to rewrite the configuration file from scratch if
the original one no longer exists for some reason. However if the server was
started without a configuration file at all, the CONFIG REWRITE will just return
an error.
## Atomic rewrite process
In order to make sure the redis.conf file is always consistent, that is, on
errors or crashes you always end with the old file, or the new one, the rewrite
is performed with a single `write(2)` call that has enough content to be at
least as big as the old file. Sometimes additional padding in the form of
comments is added in order to make sure the resulting file is big enough, and
later the file gets truncated to remove the padding at the end.
@return
@simple-string-reply: `OK` when the configuration was rewritten properly.
Otherwise an error is returned.

View file

@ -0,0 +1,56 @@
The `CONFIG SET` command is used in order to reconfigure the server at run time
without the need to restart Redis. You can change both trivial parameters or
switch from one to another persistence option using this command.
The list of configuration parameters supported by `CONFIG SET` can be obtained
issuing a `CONFIG GET *` command, that is the symmetrical command used to obtain
information about the configuration of a running Redis instance.
All the configuration parameters set using `CONFIG SET` are immediately loaded
by Redis and will take effect starting with the next command executed.
All the supported parameters have the same meaning of the equivalent
configuration parameter used in the [redis.conf][hgcarr22rc] file, with the
following important differences:
[hgcarr22rc]: http://github.com/redis/redis/raw/2.8/redis.conf
- In options where bytes or other quantities are specified, it is not possible
to use the `redis.conf` abbreviated form (`10k`, `2gb` ... and so forth),
everything should be specified as a well-formed 64-bit integer, in the base
unit of the configuration directive. However since Redis version 3.0 or
greater, it is possible to use `CONFIG SET` with memory units for `maxmemory`,
client output buffers, and replication backlog size.
- The save parameter is a single string of space-separated integers. Every pair
of integers represent a seconds/modifications threshold.
For instance what in `redis.conf` looks like:
```
save 900 1
save 300 10
```
that means, save after 900 seconds if there is at least 1 change to the dataset,
and after 300 seconds if there are at least 10 changes to the dataset, should be
set using `CONFIG SET SAVE "900 1 300 10"`.
It is possible to switch persistence from RDB snapshotting to append-only file
(and the other way around) using the `CONFIG SET` command. For more information
about how to do that please check the [persistence page][tp].
[tp]: /topics/persistence
In general what you should know is that setting the `appendonly` parameter to
`yes` will start a background process to save the initial append-only file
(obtained from the in memory data set), and will append all the subsequent
commands on the append-only file, thus obtaining exactly the same effect of a
Redis server that started with AOF turned on since the start.
You can have both the AOF enabled with RDB snapshotting if you want, the two
options are not mutually exclusive.
@return
@simple-string-reply: `OK` when the configuration was set properly. Otherwise an
error is returned.

View file

@ -0,0 +1,5 @@
Return the number of keys in the currently-selected database.
@return
@integer-reply

View file

@ -0,0 +1,6 @@
`DEBUG OBJECT` is a debugging command that should not be used by clients. Check
the `OBJECT` command instead.
@return
@simple-string-reply

View file

@ -0,0 +1,6 @@
`DEBUG SEGFAULT` performs an invalid memory access that crashes Redis. It is
used to simulate bugs during the development.
@return
@simple-string-reply

View file

@ -0,0 +1,19 @@
Decrements the number stored at `key` by one. If the key does not exist, it is
set to `0` before performing the operation. An error is returned if the key
contains a value of the wrong type or contains a string that can not be
represented as integer. This operation is limited to **64 bit signed integers**.
See `INCR` for extra information on increment/decrement operations.
@return
@integer-reply: the value of `key` after the decrement
@examples
```cli
SET mykey "10"
DECR mykey
SET mykey "234293482390480948029348230948"
DECR mykey
```

View file

@ -0,0 +1,17 @@
Decrements the number stored at `key` by `decrement`. If the key does not exist,
it is set to `0` before performing the operation. An error is returned if the
key contains a value of the wrong type or contains a string that can not be
represented as integer. This operation is limited to 64 bit signed integers.
See `INCR` for extra information on increment/decrement operations.
@return
@integer-reply: the value of `key` after the decrement
@examples
```cli
SET mykey "10"
DECRBY mykey 3
```

View file

@ -0,0 +1,13 @@
Removes the specified keys. A key is ignored if it does not exist.
@return
@integer-reply: The number of keys that were removed.
@examples
```cli
SET key1 "Hello"
SET key2 "World"
DEL key1 key2 key3
```

Some files were not shown because too many files have changed in this diff Show more