Unverified Commit 2667c412 authored by Oran Agra's avatar Oran Agra Committed by GitHub
Browse files

Merge pull request #10829 from oranagra/release-7.0.1

Release 7.0.1
parents d375595d 595e725d
...@@ -7,14 +7,14 @@ start_server {tags {"pubsubshard external:skip"}} { ...@@ -7,14 +7,14 @@ start_server {tags {"pubsubshard external:skip"}} {
assert_equal {2} [ssubscribe $rd1 {chan2}] assert_equal {2} [ssubscribe $rd1 {chan2}]
assert_equal 1 [r SPUBLISH chan1 hello] assert_equal 1 [r SPUBLISH chan1 hello]
assert_equal 1 [r SPUBLISH chan2 world] assert_equal 1 [r SPUBLISH chan2 world]
assert_equal {message chan1 hello} [$rd1 read] assert_equal {smessage chan1 hello} [$rd1 read]
assert_equal {message chan2 world} [$rd1 read] assert_equal {smessage chan2 world} [$rd1 read]
# unsubscribe from one of the channels # unsubscribe from one of the channels
sunsubscribe $rd1 {chan1} sunsubscribe $rd1 {chan1}
assert_equal 0 [r SPUBLISH chan1 hello] assert_equal 0 [r SPUBLISH chan1 hello]
assert_equal 1 [r SPUBLISH chan2 world] assert_equal 1 [r SPUBLISH chan2 world]
assert_equal {message chan2 world} [$rd1 read] assert_equal {smessage chan2 world} [$rd1 read]
# unsubscribe from the remaining channel # unsubscribe from the remaining channel
sunsubscribe $rd1 {chan2} sunsubscribe $rd1 {chan2}
...@@ -32,8 +32,8 @@ start_server {tags {"pubsubshard external:skip"}} { ...@@ -32,8 +32,8 @@ start_server {tags {"pubsubshard external:skip"}} {
assert_equal {1} [ssubscribe $rd1 {chan1}] assert_equal {1} [ssubscribe $rd1 {chan1}]
assert_equal {1} [ssubscribe $rd2 {chan1}] assert_equal {1} [ssubscribe $rd2 {chan1}]
assert_equal 2 [r SPUBLISH chan1 hello] assert_equal 2 [r SPUBLISH chan1 hello]
assert_equal {message chan1 hello} [$rd1 read] assert_equal {smessage chan1 hello} [$rd1 read]
assert_equal {message chan1 hello} [$rd2 read] assert_equal {smessage chan1 hello} [$rd2 read]
# clean up clients # clean up clients
$rd1 close $rd1 close
...@@ -58,7 +58,7 @@ start_server {tags {"pubsubshard external:skip"}} { ...@@ -58,7 +58,7 @@ start_server {tags {"pubsubshard external:skip"}} {
set rd1 [redis_deferring_client] set rd1 [redis_deferring_client]
assert_equal {1 1 1} [ssubscribe $rd1 {chan1 chan1 chan1}] assert_equal {1 1 1} [ssubscribe $rd1 {chan1 chan1 chan1}]
assert_equal 1 [r SPUBLISH chan1 hello] assert_equal 1 [r SPUBLISH chan1 hello]
assert_equal {message chan1 hello} [$rd1 read] assert_equal {smessage chan1 hello} [$rd1 read]
# clean up clients # clean up clients
$rd1 close $rd1 close
...@@ -129,9 +129,9 @@ start_server {tags {"pubsubshard external:skip"}} { ...@@ -129,9 +129,9 @@ start_server {tags {"pubsubshard external:skip"}} {
assert_equal {1} [ssubscribe $rd1 {chan1}] assert_equal {1} [ssubscribe $rd1 {chan1}]
$rd0 SPUBLISH chan1 hello $rd0 SPUBLISH chan1 hello
assert_equal {message chan1 hello} [$rd1 read] assert_equal {smessage chan1 hello} [$rd1 read]
$rd0 SPUBLISH chan1 world $rd0 SPUBLISH chan1 world
assert_equal {message chan1 world} [$rd1 read] assert_equal {smessage chan1 world} [$rd1 read]
} }
} }
} }
\ No newline at end of file
...@@ -1416,18 +1416,49 @@ start_server {tags {"scripting"}} { ...@@ -1416,18 +1416,49 @@ start_server {tags {"scripting"}} {
] {123} ] {123}
# Fail to execute regardless of script content when we use default flags in OOM condition # Fail to execute regardless of script content when we use default flags in OOM condition
assert_error {OOM allow-oom flag is not set on the script, can not run it when used memory > 'maxmemory'} { assert_error {OOM *} {
r eval {#!lua flags= r eval {#!lua flags=
return 1 return 1
} 0 } 0
} }
# Script with allow-oom can write despite being in OOM state
assert_equal [ assert_equal [
r eval {#!lua flags=allow-oom r eval {#!lua flags=allow-oom
redis.call('set','x',1) redis.call('set','x',1)
return 1 return 1
} 1 x
] 1
# read-only scripts implies allow-oom
assert_equal [
r eval {#!lua flags=no-writes
redis.call('get','x')
return 1
} 0 } 0
] 1 ] 1
assert_equal [
r eval_ro {#!lua flags=no-writes
redis.call('get','x')
return 1
} 1 x
] 1
# Script with no shebang can read in OOM state
assert_equal [
r eval {
redis.call('get','x')
return 1
} 1 x
] 1
# Script with no shebang can read in OOM state (eval_ro variant)
assert_equal [
r eval_ro {
redis.call('get','x')
return 1
} 1 x
] 1
r config set maxmemory 0 r config set maxmemory 0
} {OK} {needs:config-maxmemory} } {OK} {needs:config-maxmemory}
...@@ -1458,12 +1489,85 @@ start_server {tags {"scripting"}} { ...@@ -1458,12 +1489,85 @@ start_server {tags {"scripting"}} {
} 1 x } 1 x
] "some value" ] "some value"
assert_error {ERR Can not run script with write flag on readonly replica} { assert_error {READONLY You can't write against a read only replica.} {
r eval {#!lua r eval {#!lua
return redis.call('get','x') return redis.call('get','x')
} 1 x } 1 x
} }
# test no-write inside multi-exec
r multi
r eval {#!lua flags=no-writes
redis.call('get','x')
return 1
} 1 x
assert_equal [r exec] 1
# test no shebang without write inside multi-exec
r multi
r eval {
redis.call('get','x')
return 1
} 1 x
assert_equal [r exec] 1
# temporarily set the server to master, so it doesn't block the queuing
# and we can test the evaluation of the flags on exec
r replicaof no one
set rr [redis_client]
set rr2 [redis_client]
$rr multi
$rr2 multi
# test write inside multi-exec
# we don't need to do any actual write
$rr eval {#!lua
return 1
} 0
# test no shebang with write inside multi-exec
$rr2 eval {
redis.call('set','x',1)
return 1
} 1 x
r replicaof [srv -1 host] [srv -1 port]
assert_error {EXECABORT Transaction discarded because of: READONLY *} {$rr exec}
assert_error {READONLY You can't write against a read only replica. script: *} {$rr2 exec}
$rr close
$rr2 close
}
}
test "not enough good replicas" {
r set x "some value"
r config set min-replicas-to-write 1
assert_equal [
r eval {#!lua flags=no-writes
return redis.call('get','x')
} 1 x
] "some value"
assert_equal [
r eval {
return redis.call('get','x')
} 1 x
] "some value"
assert_error {NOREPLICAS *} {
r eval {#!lua
return redis.call('get','x')
} 1 x
} }
assert_error {NOREPLICAS *} {
r eval {
return redis.call('set','x', 1)
} 1 x
}
r config set min-replicas-to-write 0
} }
test "allow-stale shebang flag" { test "allow-stale shebang flag" {
...@@ -1476,7 +1580,7 @@ start_server {tags {"scripting"}} { ...@@ -1476,7 +1580,7 @@ start_server {tags {"scripting"}} {
} 1 x } 1 x
} }
assert_error {*'allow-stale' flag is not set on the script*} { assert_error {MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'.} {
r eval {#!lua flags=no-writes r eval {#!lua flags=no-writes
return 1 return 1
} 0 } 0
...@@ -1517,7 +1621,7 @@ start_server {tags {"scripting"}} { ...@@ -1517,7 +1621,7 @@ start_server {tags {"scripting"}} {
test "reject script do not cause a Lua stack leak" { test "reject script do not cause a Lua stack leak" {
r config set maxmemory 1 r config set maxmemory 1
for {set i 0} {$i < 50} {incr i} { for {set i 0} {$i < 50} {incr i} {
assert_error {OOM allow-oom flag is not set on the script, can not run it when used memory > 'maxmemory'} {r eval {#!lua assert_error {OOM *} {r eval {#!lua
return 1 return 1
} 0} } 0}
} }
......
...@@ -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