From bac21cd3aa51d9b44032931cc73f3ac12dd4f495 Mon Sep 17 00:00:00 2001 From: LiiNen Date: Wed, 21 Feb 2024 22:12:09 +0900 Subject: [PATCH 1/3] BITCOUNT optional end for consistency with BITPOS --- src/bitops.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/bitops.c b/src/bitops.c index 925c2a71d..93123e55d 100644 --- a/src/bitops.c +++ b/src/bitops.c @@ -792,7 +792,7 @@ void bitopCommand(client *c) { addReplyLongLong(c,maxlen); /* Return the output string length in bytes. */ } -/* BITCOUNT key [start end [BIT|BYTE]] */ +/* BITCOUNT key [start [end [BIT|BYTE]]] */ void bitcountCommand(client *c) { robj *o; long long start, end; @@ -803,11 +803,9 @@ void bitcountCommand(client *c) { unsigned char first_byte_neg_mask = 0, last_byte_neg_mask = 0; /* Parse start/end range if any. */ - if (c->argc == 4 || c->argc == 5) { + if (c->argc == 3 || c->argc == 4 || c->argc == 5) { if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK) return; - if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK) - return; if (c->argc == 5) { if (!strcasecmp(c->argv[4]->ptr,"bit")) isbit = 1; else if (!strcasecmp(c->argv[4]->ptr,"byte")) isbit = 0; @@ -816,21 +814,32 @@ void bitcountCommand(client *c) { return; } } + if (c->argc >= 4) { + if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK) + return; + } + /* Lookup, check for type. */ o = lookupKeyRead(c->db, c->argv[1]); if (checkType(c, o, OBJ_STRING)) return; p = getObjectReadOnlyString(o,&strlen,llbuf); - long long totlen = strlen; /* Make sure we will not overflow */ + long long totlen = strlen; serverAssert(totlen <= LLONG_MAX >> 3); + if (c->argc < 4) { + if (isbit) end = (totlen<<3) + 7; + else end = totlen-1; + } + /* Convert negative indexes */ if (start < 0 && end < 0 && start > end) { addReply(c,shared.czero); return; } if (isbit) totlen <<= 3; + /* Convert negative indexes */ if (start < 0) start = totlen+start; if (end < 0) end = totlen+end; if (start < 0) start = 0; @@ -849,6 +858,7 @@ void bitcountCommand(client *c) { o = lookupKeyRead(c->db, c->argv[1]); if (checkType(c, o, OBJ_STRING)) return; p = getObjectReadOnlyString(o,&strlen,llbuf); + /* The whole string. */ start = 0; end = strlen-1; From 5838edf3907fbc7eef937173b645b06d82320b3c Mon Sep 17 00:00:00 2001 From: LiiNen Date: Wed, 21 Feb 2024 23:05:35 +0900 Subject: [PATCH 2/3] Add tests --- tests/unit/bitops.tcl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/unit/bitops.tcl b/tests/unit/bitops.tcl index f50f65dfa..808a9b531 100644 --- a/tests/unit/bitops.tcl +++ b/tests/unit/bitops.tcl @@ -128,6 +128,15 @@ start_server {tags {"bitops"}} { } } + test {BITCOUNT with start} { + set s "foobar" + r set s $s + assert_equal [r bitcount s 0] [count_bits "foobar"] + assert_equal [r bitcount s 1] [count_bits "oobar"] + assert_equal [r bitcount s -1] [count_bits "r"] + assert_equal [r bitcount s -2] [count_bits "ar"] + } + test {BITCOUNT with start, end} { set s "foobar" r set s $s @@ -150,12 +159,10 @@ start_server {tags {"bitops"}} { test {BITCOUNT with illegal arguments} { # Used to return 0 for non-existing key instead of errors r del s - assert_error {ERR *syntax*} {r bitcount s 0} assert_error {ERR *syntax*} {r bitcount s 0 1 hello} assert_error {ERR *syntax*} {r bitcount s 0 1 hello hello2} r set s 1 - assert_error {ERR *syntax*} {r bitcount s 0} assert_error {ERR *syntax*} {r bitcount s 0 1 hello} assert_error {ERR *syntax*} {r bitcount s 0 1 hello hello2} } From 00a7f7173cfc3c00700925832dd76bd6d608b7cb Mon Sep 17 00:00:00 2001 From: LiiNen Date: Fri, 23 Feb 2024 12:45:01 +0900 Subject: [PATCH 3/3] Fix commands syntax hint --- src/commands.def | 15 ++++++++++----- src/commands/bitcount.json | 33 ++++++++++++++++++++------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/commands.def b/src/commands.def index c8413e4f6..f1794abf7 100644 --- a/src/commands.def +++ b/src/commands.def @@ -51,23 +51,28 @@ keySpec BITCOUNT_Keyspecs[1] = { }; #endif -/* BITCOUNT range unit argument table */ -struct COMMAND_ARG BITCOUNT_range_unit_Subargs[] = { +/* BITCOUNT range end_unit_block unit argument table */ +struct COMMAND_ARG BITCOUNT_range_end_unit_block_unit_Subargs[] = { {MAKE_ARG("byte",ARG_TYPE_PURE_TOKEN,-1,"BYTE",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("bit",ARG_TYPE_PURE_TOKEN,-1,"BIT",NULL,NULL,CMD_ARG_NONE,0,NULL)}, }; +/* BITCOUNT range end_unit_block argument table */ +struct COMMAND_ARG BITCOUNT_range_end_unit_block_Subargs[] = { +{MAKE_ARG("end",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, +{MAKE_ARG("unit",ARG_TYPE_ONEOF,-1,NULL,NULL,"7.0.0",CMD_ARG_OPTIONAL,2,NULL),.subargs=BITCOUNT_range_end_unit_block_unit_Subargs}, +}; + /* BITCOUNT range argument table */ struct COMMAND_ARG BITCOUNT_range_Subargs[] = { {MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, -{MAKE_ARG("end",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, -{MAKE_ARG("unit",ARG_TYPE_ONEOF,-1,NULL,NULL,"7.0.0",CMD_ARG_OPTIONAL,2,NULL),.subargs=BITCOUNT_range_unit_Subargs}, +{MAKE_ARG("end-unit-block",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=BITCOUNT_range_end_unit_block_Subargs}, }; /* BITCOUNT argument table */ struct COMMAND_ARG BITCOUNT_Args[] = { {MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)}, -{MAKE_ARG("range",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=BITCOUNT_range_Subargs}, +{MAKE_ARG("range",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=BITCOUNT_range_Subargs}, }; /********** BITFIELD ********************/ diff --git a/src/commands/bitcount.json b/src/commands/bitcount.json index 2d277a855..616e9fa02 100644 --- a/src/commands/bitcount.json +++ b/src/commands/bitcount.json @@ -54,24 +54,31 @@ "type": "integer" }, { - "name": "end", - "type": "integer" - }, - { - "name": "unit", - "type": "oneof", + "name": "end-unit-block", + "type": "block", "optional": true, - "since": "7.0.0", "arguments": [ { - "name": "byte", - "type": "pure-token", - "token": "BYTE" + "name": "end", + "type": "integer" }, { - "name": "bit", - "type": "pure-token", - "token": "BIT" + "name": "unit", + "type": "oneof", + "optional": true, + "since": "7.0.0", + "arguments": [ + { + "name": "byte", + "type": "pure-token", + "token": "BYTE" + }, + { + "name": "bit", + "type": "pure-token", + "token": "BIT" + } + ] } ] }