diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2020-03-21 10:28:17 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2021-02-07 11:16:53 +0000 |
commit | 06cba6ccd165ca8b224797e37fccb9e63f026d77 (patch) | |
tree | e82f1bc439997ae296f2e74f8a64d84c5d95f140 /iredis/redis_grammar.py | |
parent | Initial commit. (diff) | |
download | iredis-06cba6ccd165ca8b224797e37fccb9e63f026d77.tar.xz iredis-06cba6ccd165ca8b224797e37fccb9e63f026d77.zip |
Adding upstream version 1.9.1.upstream/1.9.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'iredis/redis_grammar.py')
-rw-r--r-- | iredis/redis_grammar.py | 637 |
1 files changed, 637 insertions, 0 deletions
diff --git a/iredis/redis_grammar.py b/iredis/redis_grammar.py new file mode 100644 index 0000000..f03459f --- /dev/null +++ b/iredis/redis_grammar.py @@ -0,0 +1,637 @@ +# noqa: F541 +""" +This module describes how to match a redis command to grammar token based on +regex. + +command_nodex: x means node? +command_keys: ends with s means there can be multiple <key> +""" +import logging +from functools import lru_cache + +from prompt_toolkit.contrib.regular_languages.compiler import compile +from .commands import command2syntax + +logger = logging.getLogger(__name__) +CONST = { + "failoverchoice": "TAKEOVER FORCE", + "withscores": "WITHSCORES", + "limit": "LIMIT", + "expiration": "EX PX", + "condition": "NX XX", + "keepttl": "KEEPTTL", + "operation": "AND OR XOR NOT", + "changed": "CH", + "incr": "INCR", + "resetchoice": "HARD SOFT", + "match": "MATCH", + "count_const": "COUNT", + "const_store": "STORE", + "const_storedist": "STOREDIST", + "type_const": "TYPE", + "type": "string list set zset hash stream", + "position_choice": "BEFORE AFTER", + "error": "TIMEOUT ERROR", + "async": "ASYNC", + "conntype": "NORMAL MASTER REPLICA PUBSUB", + "samples": "SAMPLES", + "slotsubcmd": "IMPORTING MIGRATING NODE STABLE", + "weights_const": "WEIGHTS", + "aggregate_const": "AGGREGATE", + "aggregate": "SUM MIN MAX", + "slowlogsub": "LEN RESET GET", + "shutdown": "SAVE NOSAVE", + "switch": "ON OFF SKIP", + "on_off": "ON OFF", + "const_id": "ID", + "addr": "ADDR", + "skipme": "SKIPME", + "yes": "YES NO", + "migratechoice": "COPY REPLACE", + "auth": "AUTH", + "const_keys": "KEYS", + "object": "REFCOUNT ENCODING IDLETIME FREQ HELP", + "subrestore": "REPLACE ABSTTL IDLETIME FREQ", + "distunit": "m km ft mi", + "geochoice": "WITHCOORD WITHDIST WITHHASH", + "order": "ASC DESC", + "pubsubcmd": "CHANNELS NUMSUB NUMPAT", + "scriptdebug": "YES NO SYNC", + "help": "HELP", + "stream": "STREAM", + "streams": "STREAMS", + "stream_create": "CREATE", + "stream_setid": "SETID", + "stream_destroy": "DESTROY", + "stream_delconsumer": "DELCONSUMER", + "stream_consumers": "CONSUMERS", + "stream_groups": "GROUPS", + "stream_group": "GROUP", + "maxlen": "MAXLEN", + "idel": "IDEL", + "time": "TIME", + "retrycount": "RETRYCOUNT", + "force": "FORCE", + "justid": "JUSTID", + "block": "BLOCK", + "noack": "NOACK", + "get": "GET", + "set": "SET", + "incrby": "INCRBY", + "overflow": "OVERFLOW", + "overflow_option": "WRAP SAT FAIL", + "version": "VERSION", + "schedule": "SCHEDULE", + "graphevent": ( + "ACTIVE-DEFRAG-CYCLE " + "AOF-FSYNC-ALWAYS " + "AOF-STAT " + "AOF-REWRITE-DIFF-WRITE " + "AOF-RENAME " + "AOF-WRITE " + "AOF-WRITE-ACTIVE-CHILD " + "AOF-WRITE-ALONE " + "AOF-WRITE-PENDING-FSYNC " + "COMMAND " + "EXPIRE-CYCLE " + "EVICTION-CYCLE " + "EVICTION-DEL " + "FAST-COMMAND " + "FORK " + "RDB-UNLINK-TEMP-FILE" + ), + "section": ( + "SERVER " + "CLIENTS " + "MEMORY " + "PERSISTENCE " + "STATS " + "REPLICATION " + "CPU " + "COMMANDSTATS " + "CLUSTER " + "KEYSPACE " + "ALL " + "DEFAULT " + ), + "redirect_const": "REDIRECT", + "prefix_const": "PREFIX", + "bcast_const": "BCAST", + "optin_const": "OPTIN", + "optout_const": "OPTOUT", + "noloop_const": "NOLOOP", + "reset_const": "RESET", + "const_user": "USER", + "full_const": "FULL", + "str_algo": "LCS", + "len_const": "LEN", + "idx_const": "IDX", + "minmatchlen_const": "MINMATCHLEN", + "withmatchlen_const": "WITHMATCHLEN", + "strings_const": "STRINGS", + "rank_const": "RANK", +} + + +def c(const_name): + const_values = CONST[const_name].split() + uppers = [x.lower() for x in const_values] + const_values.extend(uppers) + return "|".join(const_values) + + +VALID_TOKEN = r"""( +("([^"]|\\")*?") |# with double quotes +('([^']|\\')*?') |# with single quotes +([^\s"]+) # without quotes +)""" +PATTERN = fr"(?P<pattern>{VALID_TOKEN})" +VALID_SLOT = r"\d+" # TODO add range? max value:16384 +VALID_NODE = r"\d+" +NUM = r"\d+" +NNUM = r"-?\+?\(?\[?(\d+|inf)" # number cloud be negative +_FLOAT = r"-?(\d|\.|e)+" +LEXNUM = r"(\[\w+)|(\(\w+)|(\+)|(-)" + +SLOT = fr"(?P<slot>{VALID_SLOT})" +SLOTS = fr"(?P<slots>{VALID_SLOT}(\s+{VALID_SLOT})*)" +NODE = fr"(?P<node>{VALID_NODE})" +KEY = fr"(?P<key>{VALID_TOKEN})" +PREFIX = fr"(?P<prefix>{VALID_TOKEN})" +KEYS = fr"(?P<keys>{VALID_TOKEN}(\s+{VALID_TOKEN})*)" +DESTINATION = fr"(?P<destination>{VALID_TOKEN})" +NEWKEY = fr"(?P<newkey>{VALID_TOKEN})" +VALUE = fr"(?P<value>{VALID_TOKEN})" +VALUES = fr"(?P<values>{VALID_TOKEN}(\s+{VALID_TOKEN})*)" +ELEMENT = fr"(?P<element>{VALID_TOKEN})" # element for list +FIELDS = fr"(?P<fields>{VALID_TOKEN}(\s+{VALID_TOKEN})*)" +FIELD = fr"(?P<field>{VALID_TOKEN})" +SFIELD = fr"(?P<sfield>{VALID_TOKEN})" +SVALUE = fr"(?P<svalue>{VALID_TOKEN})" +MEMBER = fr"(?P<member>{VALID_TOKEN})" +MEMBERS = fr"(?P<members>{VALID_TOKEN}(\s+{VALID_TOKEN})*)" +COUNT = fr"(?P<count>{NNUM})" +LEN = fr"(?P<len>{NNUM})" +RANK = fr"(?P<rank>{NNUM})" +VERSION_NUM = fr"(?P<version_num>{NUM})" +MESSAGE = fr"(?P<message>{VALID_TOKEN})" +CHANNEL = fr"(?P<channel>{VALID_TOKEN})" +GROUP = fr"(?P<group>{VALID_TOKEN})" +CONSUMER = fr"(?P<consumer>{VALID_TOKEN})" +CATEGORYNAME = fr"(?P<categoryname>{VALID_TOKEN})" +USERNAME = fr"(?P<username>{VALID_TOKEN})" +RULE = fr"(?P<rule>{VALID_TOKEN})" +BIT = r"(?P<bit>0|1)" +FLOAT = fr"(?P<float>{_FLOAT})" +LONGITUDE = fr"(?P<longitude>{_FLOAT})" +LATITUDE = fr"(?P<latitude>{_FLOAT})" +CURSOR = fr"(?P<cursor>{NUM})" +PARAMETER = fr"(?P<parameter>{VALID_TOKEN})" +DOUBLE_LUA = r'(?P<double_lua>[^"]*)' +SINGLE_LUA = r"(?P<single_lua>[^']*)" +INTTYPE = r"(?P<inttype>(i|u)\d+)" +# IP re copied from: +# https://www.regular-expressions.info/ip.html +IP = r"""(?P<ip>(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\. + (25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\. + (25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\. + (25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]))""" +# Port re copied from: +# https://stackoverflow.com/questions/12968093/regex-to-validate-port-number +# pompt_toolkit limit: Exception: {4}-style repetition not yet supported +PORT = r"(?P<port>[1-9]|[1-5]?\d\d\d?\d?|6[1-4][0-9]\d\d\d|65[1-4]\d\d|655[1-2][0-9]|6553[1-5])" +EPOCH = fr"(?P<epoch>{NUM})" +PASSWORD = fr"(?P<password>{VALID_TOKEN})" +REPLICATIONID = fr"(?P<replicationid>{VALID_TOKEN})" +INDEX = r"(?P<index>(1[0-5]|\d))" +CLIENTID = fr"(?P<clientid>{NUM})" +SECOND = fr"(?P<second>{NUM})" +TIMESTAMP = fr"(?P<timestamp>{NUM})" +# TODO test lexer & completer for multi spaces in command +# For now, redis command can have one space at most +COMMAND = "(\s* (?P<command>[\w -]+))" +MILLISECOND = fr"(?P<millisecond>{NUM})" +TIMESTAMPMS = fr"(?P<timestampms>{NUM})" +ANY = r"(?P<any>.*)" # TODO deleted +START = fr"(?P<start>{NNUM})" +END = fr"(?P<end>{NNUM})" + +# for stream ids, special ids include: -, +, $, > and * +# please see: +# https://redis.io/topics/streams-intro#special-ids-in-the-streams-api +# stream id, DO NOT use r"" here, or the \+ will be two string +# NOTE: if miss the outer (), multi IDS won't work. +STREAM_ID = "(?P<stream_id>[T\d:>+*\-\$]+)" + +DELTA = fr"(?P<delta>{NNUM})" +OFFSET = fr"(?P<offset>{NUM})" # string offset, can't be negative +SHARP_OFFSET = f"(?P<offset>\#?{NUM})" # for bitfield command +MIN = fr"(?P<min>{NNUM})" +MAX = fr"(?P<max>{NNUM})" +POSITION = fr"(?P<position>{NNUM})" +TIMEOUT = fr"(?P<timeout>{NUM})" +SCORE = fr"(?P<score>{_FLOAT})" +LEXMIN = fr"(?P<lexmin>{LEXNUM})" +LEXMAX = fr"(?P<lexmax>{LEXNUM})" +WEIGHTS = fr"(?P<weights>{_FLOAT}(\s+{_FLOAT})*)" +IP_PORT = fr"(?P<ip_port>{IP}:{PORT})" +HOST = fr"(?P<host>{VALID_TOKEN})" + +# const choices +FAILOVERCHOICE = fr"(?P<failoverchoice>{c('failoverchoice')})" +WITHSCORES = fr"(?P<withscores>{c('withscores')})" +LIMIT = fr"(?P<limit>{c('limit')})" +EXPIRATION = fr"(?P<expiration>{c('expiration')})" +CONDITION = fr"(?P<condition>{c('condition')})" +OPERATION = fr"(?P<operation>{c('operation')})" +CHANGED = fr"(?P<changed>{c('changed')})" +INCR = fr"(?P<incr>{c('incr')})" +RESETCHOICE = fr"(?P<resetchoice>{c('resetchoice')})" +MATCH = fr"(?P<match>{c('match')})" +COUNT_CONST = fr"(?P<count_const>{c('count_const')})" +TYPE_CONST = fr"(?P<type_const>{c('type_const')})" +TYPE = fr"(?P<type>{c('type')})" +POSITION_CHOICE = fr"(?P<position_choice>{c('position_choice')})" +ERROR = fr"(?P<error>{c('error')})" +ASYNC = fr"(?P<async>{c('async')})" +CONNTYPE = fr"(?P<conntype>{c('conntype')})" +SAMPLES = fr"(?P<samples>{c('samples')})" +SLOTSUBCMD = fr"(?P<slotsubcmd>{c('slotsubcmd')})" +WEIGHTS_CONST = fr"(?P<weights_const>{c('weights_const')})" +AGGREGATE_CONST = fr"(?P<aggregate_const>{c('aggregate_const')})" +AGGREGATE = fr"(?P<aggregate>{c('aggregate')})" +SLOWLOGSUB = fr"(?P<slowlogsub>{c('slowlogsub')})" +SHUTDOWN = fr"(?P<shutdown>{c('shutdown')})" +SWITCH = fr"(?P<switch>{c('switch')})" +ON_OFF = fr"(?P<on_off>{c('on_off')})" +CONST_ID = fr"(?P<const_id>{c('const_id')})" +CONST_USER = fr"(?P<const_user>{c('const_user')})" +ADDR = fr"(?P<addr>{c('addr')})" +SKIPME = fr"(?P<skipme>{c('skipme')})" +YES = fr"(?P<yes>{c('yes')})" +MIGRATECHOICE = fr"(?P<migratechoice>{c('migratechoice')})" +AUTH = fr"(?P<auth>{c('auth')})" +CONST_KEYS = fr"(?P<const_keys>{c('const_keys')})" +OBJECT = fr"(?P<object>{c('object')})" +SUBRESTORE = fr"(?P<subrestore>{c('subrestore')})" +DISTUNIT = fr"(?P<distunit>{c('distunit')})" +GEOCHOICE = fr"(?P<geochoice>{c('geochoice')})" +ORDER = fr"(?P<order>{c('order')})" +CONST_STORE = fr"(?P<const_store>{c('const_store')})" +CONST_STOREDIST = fr"(?P<const_storedist>{c('const_storedist')})" +PUBSUBCMD = fr"(?P<pubsubcmd>{c('pubsubcmd')})" +SCRIPTDEBUG = fr"(?P<scriptdebug>{c('scriptdebug')})" +HELP = fr"(?P<help>{c('help')})" +STREAM = fr"(?P<stream>{c('stream')})" +STREAM_GROUPS = fr"(?P<stream_groups>{c('stream_groups')})" +STREAM_GROUP = fr"(?P<stream_group>{c('stream_group')})" +STREAM_CONSUMERS = fr"(?P<stream_consumers>{c('stream_consumers')})" +STREAM_CREATE = fr"(?P<stream_create>{c('stream_create')})" +STREAM_SETID = fr"(?P<stream_setid>{c('stream_setid')})" +STREAM_DESTROY = fr"(?P<stream_destroy>{c('stream_destroy')})" +STREAM_DELCONSUMER = fr"(?P<stream_delconsumer>{c('stream_delconsumer')})" +MAXLEN = fr"(?P<maxlen>{c('maxlen')})" +APPROXIMATELY = r"(?P<approximately>~)" +IDEL = fr"(?P<idel>{c('idel')})" +TIME = fr"(?P<time>{c('time')})" +RETRYCOUNT = fr"(?P<retrycount>{c('retrycount')})" +FORCE = fr"(?P<force>{c('force')})" +JUSTID = fr"(?P<justid>{c('justid')})" +BLOCK = fr"(?P<block>{c('block')})" +STREAMS = fr"(?P<streams>{c('streams')})" +NOACK = fr"(?P<noack>{c('noack')})" +GET = fr"(?P<get>{c('get')})" +SET = fr"(?P<set>{c('set')})" +INCRBY = fr"(?P<incrby>{c('incrby')})" +OVERFLOW = fr"(?P<overflow>{c('overflow')})" +OVERFLOW_OPTION = fr"(?P<overflow_option>{c('overflow_option')})" +KEEPTTL = fr"(?P<keepttl>{c('keepttl')})" +GRAPHEVENT = fr"(?P<graphevent>{c('graphevent')})" +VERSION = fr"(?P<version>{c('version')})" +SECTION = fr"(?P<section>{c('section')})" +SCHEDULE = fr"(?P<schedule>{c('schedule')})" + +REDIRECT_CONST = fr"(?P<redirect_const>{c('redirect_const')})" +PREFIX_CONST = fr"(?P<prefix_const>{c('prefix_const')})" +BCAST_CONST = fr"(?P<bcast_const>{c('bcast_const')})" +OPTIN_CONST = fr"(?P<optin_const>{c('optin_const')})" +OPTOUT_CONST = fr"(?P<optout_const>{c('optout_const')})" +NOLOOP_CONST = fr"(?P<noloop_const>{c('noloop_const')})" + +RESET_CONST = fr"(?P<reset_const>{c('reset_const')})" +FULL_CONST = fr"(?P<full_const>{c('full_const')})" + +STR_ALGO = fr"(?P<str_algo>{c('str_algo')})" +LEN_CONST = fr"(?P<len_const>{c('len_const')})" +IDX_CONST = fr"(?P<idx_const>{c('idx_const')})" +MINMATCHLEN_CONST = fr"(?P<minmatchlen_const>{c('minmatchlen_const')})" +WITHMATCHLEN_CONST = fr"(?P<withmatchlen_const>{c('withmatchlen_const')})" +STRINGS_CONST = fr"(?P<strings_const>{c('strings_const')})" +RANK_CONST = fr"(?P<rank_const>{c('rank_const')})" + +command_grammar = compile(COMMAND) + +# Here are the core grammars, those are tokens after ``command``. +# E.g. SET command's syntax is "SET key value" +# Then it's grammar here is r"\s+ key \s+ value \s*", we needn't add `command` +# here because every syntaxes starts with `command` so we will prepend `command` +# in get_command_grammar function. +GRAMMAR = { + "command_key": fr"\s+ {KEY} \s*", + "command_pattern": fr"\s+ {PATTERN} \s*", + "command_georadiusbymember": fr""" + \s+ {KEY} \s+ {MEMBER} + \s+ {FLOAT} \s+ {DISTUNIT} + (\s+ {GEOCHOICE})* + (\s+ {COUNT_CONST} \s+ {COUNT})? + (\s+ {ORDER})? + (\s+ {CONST_STORE} \s+ {KEY})? + (\s+ {CONST_STOREDIST} \s+ {KEY})? \s*""", + "command_command": fr"\s+ {COMMAND} \s*", + "command_slots": fr"\s+ {SLOTS} \s*", + "command_node": fr"\s+ {NODE} \s*", + "command_slot": fr"\s+ {SLOT} \s*", + "command_failoverchoice": fr"\s+ {FAILOVERCHOICE} \s*", + "command_resetchoice": fr"\s+ {RESETCHOICE} \s*", + "command_slot_count": fr"\s+ {SLOT} \s+ {COUNT} \s*", + "command_key_samples_count": fr""" + \s+ {KEY} \s+ {SAMPLES} \s+ {COUNT} \s*""", + "command": r"\s*", + "command_ip_port": fr"\s+ {IP} \s+ {PORT} \s*", + "command_epoch": fr"\s+ {EPOCH} \s*", + "command_yes": fr"\s+ {YES} \s*", + "command_sectionx": fr"(\s+ {SECTION})? \s*", + "command_asyncx": fr"(\s+ {ASYNC})? \s*", + "command_slot_slotsubcmd_nodex": fr""" + \s+ {SLOT} \s+ {SLOTSUBCMD} (\s+ {NODE})? \s*""", + "command_password": fr"\s+ {PASSWORD} \s*", + "command_usernamex_password": fr"(\s+ {USERNAME})? \s+ {PASSWORD} \s*", + "command_message": fr"\s+ {MESSAGE} \s*", + "command_messagex": fr"(\s+{MESSAGE})? \s*", + "command_index": fr"\s+ {INDEX} \s*", + "command_index_index": fr"\s+ {INDEX} \s+ {INDEX} \s*", + "command_type_conntype_x": fr""" + (\s+ {TYPE_CONST} \s+ {CONNTYPE})? \s*""", + "command_clientid_errorx": fr"\s+ {CLIENTID} (\s+ {ERROR})? \s*", + "command_keys": fr"\s+ {KEYS} \s*", + "command_key_value": fr"\s+ {KEY} \s+ {VALUE} \s*", + "command_parameter_value": fr"\s+ {PARAMETER} \s+ {VALUE} \s*", + "command_parameter": fr"\s+ {PARAMETER} \s+ {VALUE} \s*", + "command_value": fr"\s+ {VALUE} \s*", + "command_key_second": fr"\s+ {KEY} \s+ {SECOND} \s*", + "command_key_timestamp": fr"\s+ {KEY} \s+ {TIMESTAMP} \s*", + "command_key_index": fr"\s+ {KEY} \s+ {INDEX} \s*", + "command_key_millisecond": fr"\s+ {KEY} \s+ {MILLISECOND} \s*", + "command_key_timestampms": fr"\s+ {KEY} \s+ {TIMESTAMPMS} \s*", + "command_key_newkey": fr"\s+ {KEY} \s+ {NEWKEY} \s*", + "command_newkey_keys": fr"\s+ {NEWKEY} \s+ {KEYS} \s*", + "command_key_newkey_timeout": fr"\s+ {KEY} \s+ {NEWKEY} \s+ {TIMEOUT} \s*", + "command_keys_timeout": fr"\s+ {KEYS} \s+ {TIMEOUT} \s*", + "command_count_timeout": fr"\s+ {COUNT} \s+ {TIMEOUT} \s*", + "command_timeout": fr"\s+ {TIMEOUT} \s*", + "command_key_positionchoice_pivot_value": fr""" + \s+ {KEY} \s+ {POSITION_CHOICE} \s+ {VALUE} \s+ {VALUE} \s*""", + "command_pass": fr"\s+ {ANY} \s*", + "command_any": fr"\s+ {ANY} \s*", + "command_set": fr""" + \s+ {KEY} \s+ {VALUE} + ( + (\s+ {EXPIRATION} \s+ {MILLISECOND})| + (\s+ {CONDITION})| + (\s+ {KEEPTTL}) + )* + \s*""", + "command_key_start_end_x": fr"\s+ {KEY} (\s+ {START} \s+ {END})? \s*", + "command_key_start_end": fr"\s+ {KEY} \s+ {START} \s+ {END} \s*", + "command_key_delta": fr"\s+ {KEY} \s+ {DELTA} \s*", + "command_key_offset_value": fr"\s+ {KEY} \s+ {OFFSET} \s+ {VALUE} \s*", + "command_key_field_value": fr"\s+ {KEY} (\s+ {FIELD} \s+ {VALUE})+ \s*", + "command_key_offset_bit": fr"\s+ {KEY} \s+ {OFFSET} \s+ {BIT} \s*", + "command_key_offset": fr"\s+ {KEY} \s+ {OFFSET} \s*", + "command_key_position": fr"\s+ {KEY} \s+ {POSITION} \s*", + "command_key_position_value": fr"\s+ {KEY} \s+ {POSITION} \s+ {VALUE} \s*", + "command_key_second_value": fr"\s+ {KEY} \s+ {SECOND} \s+ {VALUE} \s*", + "command_key_float": fr"\s+ {KEY} \s+ {FLOAT} \s*", + "command_key_valuess": fr"(\s+ {KEY} \s+ {VALUE})+ \s*", + "command_key_values": fr"\s+ {KEY} \s+ {VALUES} \s*", + "command_key_millisecond_value": fr"\s+ {KEY} \s+ {MILLISECOND} \s+ {VALUE} \s*", + "command_operation_key_keys": fr"\s+ {OPERATION} \s+ {KEY} \s+ {KEYS} \s*", + "command_key_bit_start_end": fr"\s+ {KEY} \s+ {BIT} (\s+ {START})? (\s+ {END})? \s*", + "command_key_members": fr"\s+ {KEY} \s+ {MEMBERS} \s*", + "command_geodist": fr"\s+ {KEY} \s+ {MEMBER} \s+ {MEMBER} (\s+ {DISTUNIT})? \s*", + "command_key_longitude_latitude_members": fr""" + \s+ {KEY} (\s+ {LONGITUDE} \s+ {LATITUDE} \s {MEMBER})+ \s*""", + "command_destination_keys": fr"\s+ {DESTINATION} \s+ {KEYS} \s*", + "command_object_key": fr"\s+ {OBJECT} \s+ {KEY} \s*", + "command_key_member": fr"\s+ {KEY} \s+ {MEMBER} \s*", + "command_key_newkey_member": fr"\s+ {KEY} \s+ {NEWKEY} \s+ {MEMBER} \s*", + "command_key_count_x": fr"\s+ {KEY} (\s+ {COUNT})? \s*", + "command_key_min_max": fr"\s+ {KEY} \s+ {MIN} \s+ {MAX} \s*", + "command_key_condition_changed_incr_score_members": fr""" + \s+ {KEY} (\s+ {CONDITION})? + (\s+ {CHANGED})? + (\s+ {INCR})? + (\s+ {SCORE} \s+ {MEMBER})+ \s*""", + "command_key_float_member": fr"\s+ {KEY} \s+ {FLOAT} \s+ {MEMBER} \s*", + "command_key_lexmin_lexmax": fr"\s+ {KEY} \s+ {LEXMIN} \s+ {LEXMAX} \s*", + "command_key_start_end_withscores_x": fr""" + \s+ {KEY} \s+ {START} \s+ {END} (\s+ {WITHSCORES})? \s*""", + "command_key_lexmin_lexmax_limit_offset_count": fr""" + \s+ {KEY} \s+ {LEXMIN} \s+ {LEXMAX} + (\s+ {LIMIT} \s+ {OFFSET} \s+ {COUNT})? \s*""", + "command_key_min_max_withscore_x_limit_offset_count_x": fr""" + \s+ {KEY} \s+ {MIN} \s+ {MAX} (\s+ {WITHSCORES})? + (\s+ {LIMIT} \s+ {OFFSET} \s+ {COUNT})? \s*""", + "command_cursor_match_pattern_count_type": fr""" + \s+ {CURSOR} (\s+ {MATCH} \s+ {PATTERN})? + (\s+ {COUNT_CONST} \s+ {COUNT})? (\s+ {TYPE_CONST} \s+ {TYPE})? \s*""", + "command_key_cursor_match_pattern_count": fr"""\s+ {KEY} + \s+ {CURSOR} (\s+ {MATCH} \s+ {PATTERN})? (\s+ {COUNT_CONST} \s+ {COUNT})? \s*""", + "command_key_fields": fr"\s+ {KEY} \s+ {FIELDS} \s*", + "command_key_field": fr"\s+ {KEY} \s+ {FIELD} \s*", + "command_key_field_delta": fr"\s+ {KEY} \s+ {FIELD} \s+ {DELTA} \s*", + "command_key_field_float": fr"\s+ {KEY} \s+ {FIELD} \s+ {FLOAT} \s*", + "command_key_fieldvalues": fr"\s+ {KEY} (\s+ {FIELD} \s+ {VALUE})+ \s*", + "command_slowlog": fr"\s+ {SLOWLOGSUB} \s+ {NUM} \s*", + "command_switch": fr"\s+ {SWITCH} \s*", + "command_schedulex": fr"(\s+ {SCHEDULE})? \s*", + "command_clientkill": fr""" + ( + (\s+ {IP_PORT})| + (\s+ {ADDR} \s+ {IP_PORT})| + (\s+ {CONST_ID} \s+ {CLIENTID})| + (\s+ {TYPE_CONST} \s+ {CONNTYPE})| + (\s+ {CONST_USER} \s+ {USERNAME})| + (\s+ {SKIPME} \s+ {YES}) + )+ \s*""", + "command_migrate": fr""" + \s+ {HOST} \s+ {PORT} + \s+ {KEY} \s+ {INDEX} \s+ {TIMEOUT} + (\s+ {MIGRATECHOICE})? + ( + (\s+ {AUTH} \s+ {PASSWORD})| + (\s+ {AUTH} \s+ {USERNAME} \s+ {PASSWORD}) + )? + (\s+ {CONST_KEYS} \s+ {KEYS})? + \s*""", + "command_radius": fr"""\s+ {KEY} + \s+ {LONGITUDE} \s+ {LATITUDE} \s+ {FLOAT} \s+ {DISTUNIT} + (\s+ {GEOCHOICE})* (\s+ {COUNT_CONST} \s+ {COUNT})? + (\s+ {ORDER})? + (\s+ {CONST_STORE} \s+ {KEY})? + (\s+ {CONST_STOREDIST} \s+ {KEY})? \s*""", + "command_restore": fr"""\s+ {KEY} + \s+ {TIMEOUT} \s+ {VALUE} (\s+ {SUBRESTORE} \s+ {SECOND})? \s*""", + "command_pubsubcmd_channels": fr"\s+ {PUBSUBCMD} (\s+ {CHANNEL})+ \s*", + "command_channel_message": fr"\s+ {CHANNEL} \s+ {MESSAGE} \s*", + "command_channels": fr"(\s+ {CHANNEL})+ \s*", + "command_lua_any": fr"""(\s+"{DOUBLE_LUA}")? (\s+'{SINGLE_LUA}')? \s+ {ANY} \s*""", + "command_scriptdebug": fr"\s+ {SCRIPTDEBUG} \s*", + "command_shutdown": fr"\s+ {SHUTDOWN} \s*", + "command_key_start_end_countx": fr"""\s+ {KEY} + \s+ {STREAM_ID} + \s+ {STREAM_ID} + (\s+ {COUNT_CONST} \s+ {COUNT})? + \s*""", + "command_xgroup": fr""" + ( + (\s+ {STREAM_CREATE} \s+ {KEY} \s+ {GROUP} \s+ {STREAM_ID})| + (\s+ {STREAM_SETID} \s+ {KEY} \s+ {GROUP} \s+ {STREAM_ID})| + (\s+ {STREAM_DESTROY} \s+ {KEY} \s+ {GROUP})| + (\s+ {STREAM_DELCONSUMER} \s+ {KEY} \s+ {GROUP} \s+ {CONSUMER}) + ) + \s*""", + "command_key_group_ids": fr""" + \s+ {KEY} \s+ {GROUP} (\s+ {STREAM_ID})+ \s*""", + "command_key_ids": fr""" + \s+ {KEY} (\s+ {STREAM_ID})+ \s*""", + "command_xinfo": fr""" + ( + (\s+ {STREAM_CONSUMERS} \s+ {KEY} \s+ {GROUP})| + (\s+ {STREAM_GROUPS} \s+ {KEY})| + (\s+ {STREAM} \s+ {KEY} + (\s+ {FULL_CONST})? + (\s+ {COUNT_CONST} \s+ {COUNT})? + )| + (\s+ {HELP}) + ) + \s*""", + "command_xpending": fr""" + \s+ {KEY} \s+ {GROUP} + (\s+ {STREAM_ID} \s+ {STREAM_ID} \s+ {COUNT})? + (\s+ {CONSUMER})? + \s*""", + "command_xadd": fr""" + \s+ {KEY} + (\s+ {MAXLEN} (\s+ {APPROXIMATELY})? \s+ {COUNT})? + \s+ {STREAM_ID} + (\s+ {SFIELD} \s+ {SVALUE})+ \s*""", + "command_key_maxlen": fr""" + \s+ {KEY} \s+ {MAXLEN} (\s+ {APPROXIMATELY})? \s+ {COUNT} + \s*""", + "command_xclaim": fr""" + \s+ {KEY} \s+ {GROUP} \s+ {CONSUMER} \s+ {MILLISECOND} + (\s+ {STREAM_ID})+ + (\s+ {IDEL} \s+ {MILLISECOND})? + (\s+ {TIME} \s+ {TIMESTAMP})? + (\s+ {RETRYCOUNT} \s+ {COUNT})? + (\s+ {FORCE})? + (\s+ {JUSTID})? + \s*""", + "command_xread": fr""" + (\s+ {COUNT_CONST} \s+ {COUNT})? + (\s+ {BLOCK} \s+ {MILLISECOND})? + \s+ {STREAMS} + \s+ {KEYS} + (\s+ {STREAM_ID})+ + \s*""", + "command_xreadgroup": fr""" + \s+ {STREAM_GROUP} \s+ {GROUP} \s+ {CONSUMER} + (\s+ {COUNT_CONST} \s+ {COUNT})? + (\s+ {BLOCK} \s+ {MILLISECOND})? + (\s+ {NOACK})? + \s+ {STREAMS} + \s+ {KEYS} + (\s+ {STREAM_ID})+ + \s*""", + "command_bitfield": fr""" + \s+ {KEY} + ( + (\s+ {GET} \s+ {INTTYPE} \s+ {SHARP_OFFSET})| + (\s+ {SET} \s+ {INTTYPE} \s+ {SHARP_OFFSET} \s+ {VALUE})| + (\s+ {INCRBY} \s+ {INTTYPE} \s+ {SHARP_OFFSET} \s+ {VALUE})| + (\s+ {OVERFLOW} \s+ {OVERFLOW_OPTION}) + )+ + \s*""", + "command_replicationid_offset": fr"\s+ {REPLICATIONID} \s+ {OFFSET} \s*", + "command_graphevent": fr"\s+ {GRAPHEVENT} \s*", + "command_graphevents": fr"(\s+ {GRAPHEVENT})* \s*", + # before redis 5: lolwut 5 1 + # start from redis 6: lolwut VERSION 5 1 + "command_version": fr"(\s+ {VERSION} \s+ {VERSION_NUM})? (\s+ {ANY})? \s*", + "command_client_tracking": fr""" + \s+ {ON_OFF} + ( + (\s+ {REDIRECT_CONST} \s+ {CLIENTID})| + (\s+ {PREFIX_CONST} \s+ {PREFIX})| + (\s+ {BCAST_CONST})| + (\s+ {OPTIN_CONST})| + (\s+ {OPTOUT_CONST})| + (\s+ {NOLOOP_CONST}) + )* + \s*""", + "command_categorynamex": fr"(\s+ {CATEGORYNAME})? \s*", + "command_usernames": fr"(\s+ {USERNAME})+ \s*", + "command_username": fr"\s+ {USERNAME} \s*", + "command_count_or_resetx": fr"( (\s+ {COUNT}) | (\s+ {RESET_CONST}) )? \s*", + "command_username_rules": fr"\s+ {USERNAME} (\s+ {RULE})* \s*", + "command_count": fr"(\s+ {COUNT})? \s*", + "command_stralgo": fr""" + ( + \s+ {STR_ALGO} + ( + (\s+ {CONST_KEYS} \s+ {KEYS})| + (\s+ {STRINGS_CONST} \s+ {VALUES}) + ) + (\s+ {IDX_CONST})? + (\s+ {LEN_CONST})? + (\s+ {MINMATCHLEN_CONST} \s+ {LEN})? + (\s+ {WITHMATCHLEN_CONST})? + ) + \s*""", + "command_lpos": fr""" + \s+ {KEY} \s+ {ELEMENT} + ( + (\s+ {RANK_CONST} \s+ {RANK})| + (\s+ {COUNT_CONST} \s+ {COUNT})| + (\s+ {MAXLEN} \s+ {LEN}) + )* + \s*""", +} + +pipeline = r"(?P<shellcommand>\|.*)?" + + +@lru_cache(maxsize=256) +def get_command_grammar(command): + """ + :param command: command name in upper case. This command must be raw user + input, otherwise can't match in lexer, cause this command to be invalid; + """ + syntax_name = command2syntax[" ".join(command.split()).upper()] + syntax = GRAMMAR.get(syntax_name) + + # If a command is not supported yet, (e.g. command from latest version added + # by Redis recently, or command from third Redis module.) return a defualt + # grammar, so the lexer and completion won't be activated. + if syntax is None: + return command_grammar + # prepend command token for this syntax + command_allow_multi_spaces = command.replace(r" ", r"\s+") + syntax = fr"\s* (?P<command>{command_allow_multi_spaces}) " + syntax + # allow user input pipeline to redirect to shell, like `get json | jq .` + syntax += pipeline + + logger.info(f"syxtax: {syntax}") + + return compile(syntax) |