Commit a5d2df34 authored by Oran Agra's avatar Oran Agra
Browse files

Merge remote-tracking branch 'origin/unstable' into 7.0

parents d375595d f22bfe86
...@@ -208,6 +208,33 @@ start_server {tags {"tracking network"}} { ...@@ -208,6 +208,33 @@ start_server {tags {"tracking network"}} {
assert {$res eq {key1}} assert {$res eq {key1}}
} }
test {Tracking only occurs for scripts when a command calls a read-only command} {
r CLIENT TRACKING off
r CLIENT TRACKING on
$rd_sg MSET key2{t} 1 key2{t} 1
# If a script doesn't call any read command, don't track any keys
r EVAL "redis.call('set', 'key3{t}', 'bar')" 2 key1{t} key2{t}
$rd_sg MSET key2{t} 2 key1{t} 2
# If a script calls a read command, track all declared keys
r EVAL "redis.call('get', 'key3{t}')" 2 key1{t} key2{t}
$rd_sg MSET key2{t} 2 key1{t} 2
assert_equal {invalidate key2{t}} [r read]
assert_equal {invalidate key1{t}} [r read]
# RO variants work like the normal variants
r EVAL_RO "redis.call('ping')" 2 key1{t} key2{t}
$rd_sg MSET key2{t} 2 key1{t} 2
r EVAL_RO "redis.call('get', 'key1{t}')" 2 key1{t} key2{t}
$rd_sg MSET key2{t} 3 key1{t} 3
assert_equal {invalidate key2{t}} [r read]
assert_equal {invalidate key1{t}} [r read]
assert_equal "PONG" [r ping]
}
test {RESP3 Client gets tracking-redir-broken push message after cached key changed when rediretion client is terminated} { test {RESP3 Client gets tracking-redir-broken push message after cached key changed when rediretion client is terminated} {
r CLIENT TRACKING on REDIRECT $redir_id r CLIENT TRACKING on REDIRECT $redir_id
$rd_sg SET key1 1 $rd_sg SET key1 1
...@@ -369,7 +396,7 @@ start_server {tags {"tracking network"}} { ...@@ -369,7 +396,7 @@ start_server {tags {"tracking network"}} {
$r CLIENT TRACKING OFF $r CLIENT TRACKING OFF
} }
test {hdel deliver invlidate message after response in the same connection} { test {hdel deliver invalidate message after response in the same connection} {
r CLIENT TRACKING off r CLIENT TRACKING off
r HELLO 3 r HELLO 3
r CLIENT TRACKING on r CLIENT TRACKING on
......
...@@ -389,12 +389,13 @@ start_server { ...@@ -389,12 +389,13 @@ start_server {
assert {[r LPOS mylist c] == 2} assert {[r LPOS mylist c] == 2}
} }
test {LPOS RANK (positive and negative rank) option} { test {LPOS RANK (positive, negative and zero rank) option} {
assert {[r LPOS mylist c RANK 1] == 2} assert {[r LPOS mylist c RANK 1] == 2}
assert {[r LPOS mylist c RANK 2] == 6} assert {[r LPOS mylist c RANK 2] == 6}
assert {[r LPOS mylist c RANK 4] eq ""} assert {[r LPOS mylist c RANK 4] eq ""}
assert {[r LPOS mylist c RANK -1] == 7} assert {[r LPOS mylist c RANK -1] == 7}
assert {[r LPOS mylist c RANK -2] == 6} assert {[r LPOS mylist c RANK -2] == 6}
assert_error "*RANK can't be zero: use 1 to start from the first match, 2 from the second ... or use negative to start*" {r LPOS mylist c RANK 0}
} }
test {LPOS COUNT option} { test {LPOS COUNT option} {
......
...@@ -285,6 +285,13 @@ start_server { ...@@ -285,6 +285,13 @@ start_server {
} }
assert_equal {1 2 3 4} [lsort [r smembers setres{t}]] assert_equal {1 2 3 4} [lsort [r smembers setres{t}]]
} }
test "SINTER/SUNION/SDIFF with three same sets - $type" {
set expected [lsort "[r smembers set1{t}]"]
assert_equal $expected [lsort [r sinter set1{t} set1{t} set1{t}]]
assert_equal $expected [lsort [r sunion set1{t} set1{t} set1{t}]]
assert_equal {} [lsort [r sdiff set1{t} set1{t} set1{t}]]
}
} }
test "SDIFF with first set empty" { test "SDIFF with first set empty" {
......
...@@ -2065,6 +2065,33 @@ start_server {tags {"zset"}} { ...@@ -2065,6 +2065,33 @@ start_server {tags {"zset"}} {
close_replication_stream $repl close_replication_stream $repl
} {} {needs:repl} } {} {needs:repl}
test "BZMPOP should not blocks on non key arguments - #10762" {
set rd1 [redis_deferring_client]
set rd2 [redis_deferring_client]
r del myzset myzset2 myzset3
$rd1 bzmpop 0 1 myzset min count 10
wait_for_blocked_clients_count 1
$rd2 bzmpop 0 2 myzset2 myzset3 max count 10
wait_for_blocked_clients_count 2
# These non-key keys will not unblock the clients.
r zadd 0 100 timeout_value
r zadd 1 200 numkeys_value
r zadd min 300 min_token
r zadd max 400 max_token
r zadd count 500 count_token
r zadd 10 600 count_value
r zadd myzset 1 zset
r zadd myzset3 1 zset3
assert_equal {myzset {{zset 1}}} [$rd1 read]
assert_equal {myzset3 {{zset3 1}}} [$rd2 read]
$rd1 close
$rd2 close
} {0} {cluster:skip}
test {ZSET skiplist order consistency when elements are moved} { test {ZSET skiplist order consistency when elements are moved} {
set original_max [lindex [r config get zset-max-ziplist-entries] 1] set original_max [lindex [r config get zset-max-ziplist-entries] 1]
r config set zset-max-ziplist-entries 0 r config set zset-max-ziplist-entries 0
...@@ -2181,6 +2208,15 @@ start_server {tags {"zset"}} { ...@@ -2181,6 +2208,15 @@ start_server {tags {"zset"}} {
assert_match "*syntax*" $err assert_match "*syntax*" $err
} }
test {ZRANGESTORE with zset-max-listpack-entries 0 dst key should use skiplist encoding} {
set original_max [lindex [r config get zset-max-listpack-entries] 1]
r config set zset-max-listpack-entries 0
r del z1{t} z2{t}
r zadd z1{t} 1 a
assert_equal 1 [r zrangestore z2{t} z1{t} 0 -1]
r config set zset-max-listpack-entries $original_max
}
test {ZRANGE invalid syntax} { test {ZRANGE invalid syntax} {
catch {r zrange z1{t} 0 -1 limit 1 2} err catch {r zrange z1{t} 0 -1 limit 1 2} err
assert_match "*syntax*" $err assert_match "*syntax*" $err
......
#!/usr/bin/env python3 #!/usr/bin/env python3
import os
import glob import glob
import json import json
import os
ARG_TYPES = { ARG_TYPES = {
"string": "ARG_TYPE_STRING", "string": "ARG_TYPE_STRING",
...@@ -68,6 +67,55 @@ def get_optional_desc_string(desc, field, force_uppercase=False): ...@@ -68,6 +67,55 @@ def get_optional_desc_string(desc, field, force_uppercase=False):
return ret.replace("\n", "\\n") return ret.replace("\n", "\\n")
def check_command_args_key_specs(args, command_key_specs_index_set, command_arg_key_specs_index_set):
if not args:
return True
for arg in args:
if arg.key_spec_index is not None:
assert isinstance(arg.key_spec_index, int)
if arg.key_spec_index not in command_key_specs_index_set:
print("command: %s arg: %s key_spec_index error" % (command.fullname(), arg.name))
return False
command_arg_key_specs_index_set.add(arg.key_spec_index)
if not check_command_args_key_specs(arg.subargs, command_key_specs_index_set, command_arg_key_specs_index_set):
return False
return True
def check_command_key_specs(command):
if not command.key_specs:
return True
assert isinstance(command.key_specs, list)
for cmd_key_spec in command.key_specs:
if "flags" not in cmd_key_spec:
print("command: %s key_specs missing flags" % command.fullname())
return False
if "NOT_KEY" in cmd_key_spec["flags"]:
# Like SUNSUBSCRIBE / SPUBLISH / SSUBSCRIBE
return True
command_key_specs_index_set = set(range(len(command.key_specs)))
command_arg_key_specs_index_set = set()
# Collect key_spec used for each arg, including arg.subarg
if not check_command_args_key_specs(command.args, command_key_specs_index_set, command_arg_key_specs_index_set):
return False
# Check if we have key_specs not used
if command_key_specs_index_set != command_arg_key_specs_index_set:
print("command: %s may have unused key_spec" % command.fullname())
return False
return True
# Globals # Globals
subcommands = {} # container_name -> dict(subcommand_name -> Subcommand) - Only subcommands subcommands = {} # container_name -> dict(subcommand_name -> Subcommand) - Only subcommands
commands = {} # command_name -> Command - Only commands commands = {} # command_name -> Command - Only commands
...@@ -132,6 +180,7 @@ class Argument(object): ...@@ -132,6 +180,7 @@ class Argument(object):
self.desc = desc self.desc = desc
self.name = self.desc["name"].lower() self.name = self.desc["name"].lower()
self.type = self.desc["type"] self.type = self.desc["type"]
self.key_spec_index = self.desc.get("key_spec_index", None)
self.parent_name = parent_name self.parent_name = parent_name
self.subargs = [] self.subargs = []
self.subargs_name = None self.subargs_name = None
...@@ -200,6 +249,7 @@ class Command(object): ...@@ -200,6 +249,7 @@ class Command(object):
self.name = name.upper() self.name = name.upper()
self.desc = desc self.desc = desc
self.group = self.desc["group"] self.group = self.desc["group"]
self.key_specs = self.desc.get("key_specs", [])
self.subcommands = [] self.subcommands = []
self.args = [] self.args = []
for arg_desc in self.desc.get("arguments", []): for arg_desc in self.desc.get("arguments", []):
...@@ -271,7 +321,7 @@ class Command(object): ...@@ -271,7 +321,7 @@ class Command(object):
def _key_specs_code(): def _key_specs_code():
s = "" s = ""
for spec in self.desc.get("key_specs", []): for spec in self.key_specs:
s += "{%s}," % KeySpec(spec).struct_code() s += "{%s}," % KeySpec(spec).struct_code()
return s[:-1] return s[:-1]
...@@ -398,6 +448,17 @@ for command in commands.values(): ...@@ -398,6 +448,17 @@ for command in commands.values():
subcommand.group = command.group subcommand.group = command.group
command.subcommands.append(subcommand) command.subcommands.append(subcommand)
check_command_error_counter = 0 # An error counter is used to count errors in command checking.
print("Checking all commands...")
for command in commands.values():
if not check_command_key_specs(command):
check_command_error_counter += 1
if check_command_error_counter != 0:
print("Error: There are errors in the commands check, please check the above logs.")
exit(1)
print("Generating commands.c...") print("Generating commands.c...")
with open("%s/commands.c" % srcdir, "w") as f: with open("%s/commands.c" % srcdir, "w") as f:
f.write("/* Automatically generated by %s, do not edit. */\n\n" % os.path.basename(__file__)) f.write("/* Automatically generated by %s, do not edit. */\n\n" % os.path.basename(__file__))
......
...@@ -45,7 +45,11 @@ def argument arg ...@@ -45,7 +45,11 @@ def argument arg
name = arg["name"].is_a?(Array) ? arg["name"].join(" ") : arg["name"] name = arg["name"].is_a?(Array) ? arg["name"].join(" ") : arg["name"]
end end
if arg["multiple"] if arg["multiple"]
name = "#{name} [#{name} ...]" if arg["multiple_token"]
name = "#{name} [#{arg["token"]} #{name} ...]"
else
name = "#{name} [#{name} ...]"
end
end end
if arg["token"] if arg["token"]
name = [arg["token"], name].compact.join " " name = [arg["token"], name].compact.join " "
......
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import json import json
import os
import subprocess import subprocess
from collections import OrderedDict from collections import OrderedDict
from sys import argv, stdin from sys import argv
import os
def convert_flags_to_boolean_dict(flags): def convert_flags_to_boolean_dict(flags):
...@@ -118,7 +118,8 @@ if __name__ == '__main__': ...@@ -118,7 +118,8 @@ if __name__ == '__main__':
stdout, stderr = p.communicate() stdout, stderr = p.communicate()
commands = json.loads(stdout) commands = json.loads(stdout)
p = subprocess.Popen([args.cli, '-h', args.host, '-p', str(args.port), '--json', 'command', 'docs'], stdout=subprocess.PIPE) p = subprocess.Popen([args.cli, '-h', args.host, '-p', str(args.port), '--json', 'command', 'docs'],
stdout=subprocess.PIPE)
stdout, stderr = p.communicate() stdout, stderr = p.communicate()
docs = json.loads(stdout) docs = json.loads(stdout)
......
#!/usr/bin/env ruby #!/usr/bin/env ruby
# coding: utf-8 # coding: utf-8
# gendoc.rb -- Converts the top-comments inside module.c to modules API # gendoc.rb -- Converts the top-comments inside module.c to modules API
# reference documentation in markdown format. # reference documentation in markdown format.
...@@ -80,7 +79,7 @@ def docufy(src,i) ...@@ -80,7 +79,7 @@ def docufy(src,i)
puts "<span id=\"#{name}\"></span>\n\n" puts "<span id=\"#{name}\"></span>\n\n"
puts "### `#{name}`\n\n" puts "### `#{name}`\n\n"
puts " #{proto}\n" puts " #{proto}\n"
puts "**Available since:** #{$since[name]}\n\n" if $since[name] puts "**Available since:** #{$since[name] or "unreleased"}\n\n"
comment = "" comment = ""
while true while true
i = i-1 i = i-1
...@@ -137,7 +136,16 @@ def is_func_line(src, i) ...@@ -137,7 +136,16 @@ def is_func_line(src, i)
src[i-1] =~ /\*\// src[i-1] =~ /\*\//
end end
puts "# Modules API reference\n\n" puts "---\n"
puts "title: \"Modules API reference\"\n"
puts "linkTitle: \"API reference\"\n"
puts "weight: 1\n"
puts "description: >\n"
puts " Reference for the Redis Modules API\n"
puts "aliases:\n"
puts " - /topics/modules-api-ref\n"
puts "---\n"
puts "\n"
puts "<!-- This file is generated from module.c using\n" puts "<!-- This file is generated from module.c using\n"
puts " utils/generate-module-api-doc.rb -->\n\n" puts " utils/generate-module-api-doc.rb -->\n\n"
src = File.open(File.dirname(__FILE__) ++ "/../src/module.c").to_a src = File.open(File.dirname(__FILE__) ++ "/../src/module.c").to_a
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment