Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Valkey-8: change bitcount end param to optional #2248

Merged
merged 8 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -3844,6 +3844,18 @@ public CompletableFuture<Long> bitcount(@NonNull GlideString key) {
BitCount, new GlideString[] {key}, this::handleLongResponse);
}

@Override
public CompletableFuture<Long> bitcount(@NonNull String key, long start) {
return commandManager.submitNewCommand(
BitCount, new String[] {key, Long.toString(start)}, this::handleLongResponse);
}

@Override
public CompletableFuture<Long> bitcount(@NonNull GlideString key, long start) {
return commandManager.submitNewCommand(
BitCount, new GlideString[] {key, gs(Long.toString(start))}, this::handleLongResponse);
}

@Override
public CompletableFuture<Long> bitcount(@NonNull String key, long start, long end) {
return commandManager.submitNewCommand(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,48 @@ public interface BitmapBaseCommands {
*/
CompletableFuture<Long> bitcount(GlideString key);

/**
* Counts the number of set bits (population counting) in a string stored at <code>key</code>. The
* offset <code>start</code> is a zero-based index, with <code>0</code> being the first byte of
* the list, <code>1</code> being the next byte and so on. This offset can also be a negative
* number indicating offsets starting at the end of the list, with <code>-1</code> being the last
* byte of the list, <code>-2</code> being the penultimate, and so on.
*
* @since Valkey 8.0 and above
* @see <a href="https://valkey.io/commands/bitcount/">valkey.io</a> for details.
* @param key The key for the string to count the set bits of.
* @param start The starting offset byte index.
* @return The number of set bits in the string byte interval specified by <code>start</code> to
* the last byte. Returns zero if the key is missing as it is treated as an empty string.
* @example
* <pre>{@code
* Long payload = client.bitcount("myKey1", 1).get();
* assert payload == 2L; // From the second to the last bytes of the string stored at "myKey1" are 2 set bits.
* }</pre>
*/
CompletableFuture<Long> bitcount(String key, long start);

/**
* Counts the number of set bits (population counting) in a string stored at <code>key</code>. The
* offset <code>start</code> is a zero-based index, with <code>0</code> being the first byte of
* the list, <code>1</code> being the next byte and so on. This offset can also be a negative
* number indicating offsets starting at the end of the list, with <code>-1</code> being the last
* byte of the list, <code>-2</code> being the penultimate, and so on.
*
* @since Valkey 8.0 and above
* @see <a href="https://valkey.io/commands/bitcount/">valkey.io</a> for details.
* @param key The key for the string to count the set bits of.
* @param start The starting offset byte index.
* @return The number of set bits in the string byte interval specified by <code>start</code> to
* the last byte. Returns zero if the key is missing as it is treated as an empty string.
* @example
* <pre>{@code
* Long payload = client.bitcount(gs("myKey1"), 1).get();
* assert payload == 2L; // From the second to the last bytes of the string stored at "myKey1" are 2 set bits.
* }</pre>
*/
CompletableFuture<Long> bitcount(GlideString key, long start);

/**
* Counts the number of set bits (population counting) in a string stored at <code>key</code>. The
* offsets <code>start</code> and <code>end</code> are zero-based indexes, with <code>0</code>
Expand Down
20 changes: 20 additions & 0 deletions java/client/src/main/java/glide/api/models/BaseTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -5301,6 +5301,26 @@ public <ArgType> T bitcount(@NonNull ArgType key) {
return getThis();
}

/**
* Counts the number of set bits (population counting) in a string stored at <code>key</code>. The
* offset <code>start</code> is a zero-based index, with <code>0</code> being the first byte of
* the list, <code>1</code> being the next byte and so on. This offset can also be a negative
* number indicating offsets starting at the end of the list, with <code>-1</code> being the last
* byte of the list, <code>-2</code> being the penultimate, and so on.
*
* @see <a href="https://valkey.io/commands/bitcount/">valkey.io</a> for details.
* @param key The key for the string to count the set bits of.
* @param start The starting offset byte index.
* @return Command Response - The number of set bits in the string byte interval specified by
* <code>start</code> to the last byte. Returns zero if the key is missing as it is treated as
* an empty string.
*/
public <ArgType> T bitcount(@NonNull ArgType key, long start) {
checkTypeOrThrow(key);
protobufTransaction.addCommands(buildCommand(BitCount, newArgsBuilder().add(key).add(start)));
return getThis();
}

/**
* Counts the number of set bits (population counting) in a string stored at <code>key</code>. The
* offsets <code>start</code> and <code>end</code> are zero-based indexes, with <code>0</code>
Expand Down
45 changes: 45 additions & 0 deletions java/client/src/test/java/glide/api/GlideClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11643,6 +11643,51 @@ public void bitcount_binary_returns_success() {
assertEquals(bitcount, payload);
}

@SneakyThrows
@Test
public void bitcount_start_returns_success() {
// setup
String key = "testKey";
Long bitcount = 1L;
CompletableFuture<Long> testResponse = new CompletableFuture<>();
testResponse.complete(bitcount);

// match on protobuf request
when(commandManager.<Long>submitNewCommand(eq(BitCount), eq(new String[] {key, "1"}), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<Long> response = service.bitcount(key, 1);
Long payload = response.get();

// verify
assertEquals(testResponse, response);
assertEquals(bitcount, payload);
}

@SneakyThrows
@Test
public void bitcount_start_binary_returns_success() {
// setup
GlideString key = gs("testKey");
Long bitcount = 1L;
CompletableFuture<Long> testResponse = new CompletableFuture<>();
testResponse.complete(bitcount);

// match on protobuf request
when(commandManager.<Long>submitNewCommand(
eq(BitCount), eq(new GlideString[] {key, gs("1")}), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<Long> response = service.bitcount(key, 1);
Long payload = response.get();

// verify
assertEquals(testResponse, response);
assertEquals(bitcount, payload);
}

@SneakyThrows
@Test
public void bitcount_indices_returns_success() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,9 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)),
transaction.bitcount("key");
results.add(Pair.of(BitCount, buildArgs("key")));

transaction.bitcount("key", 1);
results.add(Pair.of(BitCount, buildArgs("key", "1")));

transaction.bitcount("key", 1, 1);
results.add(Pair.of(BitCount, buildArgs("key", "1", "1")));

Expand Down
18 changes: 18 additions & 0 deletions java/integTest/src/test/java/glide/SharedCommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -10309,6 +10309,24 @@ public void bitcount(BaseClient client) {
() -> client.bitcount(key1, 5, 30, BitmapIndexType.BIT).get());
assertTrue(executionException.getCause() instanceof RequestException);
}
if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) {
assertEquals(26L, client.bitcount(key1, 0).get());
assertEquals(4L, client.bitcount(key1, 5).get());
assertEquals(0L, client.bitcount(key1, 80).get());
assertEquals(7L, client.bitcount(key1, -2).get());
assertEquals(0, client.bitcount(missingKey, 5).get());

// Exception thrown due to the key holding a value with the wrong type
executionException =
assertThrows(ExecutionException.class, () -> client.bitcount(key2, 1).get());
assertTrue(executionException.getCause() instanceof RequestException);

} else {
// Exception thrown because optional end was implemented after 8.0.0
executionException =
assertThrows(ExecutionException.class, () -> client.bitcount(key1, 5).get());
assertTrue(executionException.getCause() instanceof RequestException);
}
}

@SneakyThrows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,10 @@ private static Object[] bitmapCommands(BaseTransaction<?> transaction) {
.bitpos(key3, 1, 44, 50, BitmapIndexType.BIT);
}

if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) {
transaction.set(key4, "foobar").bitcount(key4, 0);
}

var expectedResults =
new Object[] {
OK, // set(key1, "foobar")
Expand All @@ -1307,13 +1311,24 @@ private static Object[] bitmapCommands(BaseTransaction<?> transaction) {
};

if (SERVER_VERSION.isGreaterThanOrEqualTo("7.0.0")) {
return concatenateArrays(
expectedResults,
new Object[] {
OK, // set(key1, "foobar")
17L, // bitcount(key, 5, 30, BitmapIndexType.BIT)
46L, // bitpos(key, 1, 44, 50, BitmapIndexType.BIT)
});
expectedResults =
concatenateArrays(
expectedResults,
new Object[] {
OK, // set(key1, "foobar")
shohamazon marked this conversation as resolved.
Show resolved Hide resolved
17L, // bitcount(key, 5, 30, BitmapIndexType.BIT)
shohamazon marked this conversation as resolved.
Show resolved Hide resolved
46L, // bitpos(key, 1, 44, 50, BitmapIndexType.BIT)
shohamazon marked this conversation as resolved.
Show resolved Hide resolved
});
}

if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) {
expectedResults =
concatenateArrays(
expectedResults,
new Object[] {
OK, // set(key1, "foobar")
shohamazon marked this conversation as resolved.
Show resolved Hide resolved
26L, // bitcount(key, 0)
shohamazon marked this conversation as resolved.
Show resolved Hide resolved
});
}
return expectedResults;
}
Expand Down
1 change: 1 addition & 0 deletions node/src/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6109,6 +6109,7 @@ export class BaseClient {
* @example
* ```typescript
* console.log(await client.bitcount("my_key1")); // Output: 2 - The string stored at "my_key1" contains 2 set bits.
* console.log(await client.bitcount("my_key2", { start: 1 })); // Output: 8 - From the second to to the last bytes of the string stored at "my_key2" are contain 8 set bits.
* console.log(await client.bitcount("my_key2", { start: 1, end: 3 })); // Output: 2 - The second to fourth bytes of the string stored at "my_key2" contain 2 set bits.
* console.log(await client.bitcount("my_key3", { start: 1, end: 1, indexType: BitmapIndexType.BIT })); // Output: 1 - Indicates that the second bit of the string stored at "my_key3" is set.
* console.log(await client.bitcount("my_key3", { start: -1, end: -1, indexType: BitmapIndexType.BIT })); // Output: 1 - Indicates that the last bit of the string stored at "my_key3" is set.
Expand Down
8 changes: 5 additions & 3 deletions node/src/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2442,8 +2442,10 @@ export function createFunctionRestore(
export type BitOffsetOptions = {
/** The starting offset index. */
start: number;
/** The ending offset index. */
end: number;
/** The ending offset index. Optional since Valkey version 8.0 and above.
* If not provided, it will default to the end of the string
*/
end?: number;
/**
* The index offset type. This option can only be specified if you are using server version 7.0.0 or above.
* Could be either {@link BitmapIndexType.BYTE} or {@link BitmapIndexType.BIT}.
Expand All @@ -2463,7 +2465,7 @@ export function createBitCount(

if (options) {
args.push(options.start.toString());
args.push(options.end.toString());
if (options.end) args.push(options.end.toString());
shohamazon marked this conversation as resolved.
Show resolved Hide resolved
if (options.indexType) args.push(options.indexType);
}

Expand Down
36 changes: 36 additions & 0 deletions node/tests/SharedTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8516,6 +8516,42 @@ export function runBaseTests(config: {
}),
).rejects.toThrow(RequestError);
}

if (cluster.checkIfServerVersionLessThan("7.9.0")) {
await expect(
client.bitcount(key1, {
start: 2,
}),
).rejects.toThrow();
} else {
expect(
await client.bitcount(key1, {
start: 0,
}),
).toEqual(26);
expect(
await client.bitcount(key1, {
start: 5,
}),
).toEqual(4);
expect(
await client.bitcount(key1, {
start: 80,
}),
).toEqual(0);
expect(
await client.bitcount(uuidv4(), {
start: 80,
}),
).toEqual(0);

// key exists, but it is not a string
await expect(
client.bitcount(key2, {
start: 1,
}),
).rejects.toThrow(RequestError);
}
}, protocol);
},
config.timeout,
Expand Down
9 changes: 9 additions & 0 deletions node/tests/TestUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1370,6 +1370,15 @@ export async function transactionTest(
]);
}

if (gte(version, "7.9.0")) {
baseTransaction.set(key17, "foobar");
responseData.push(['set(key17, "foobar")', "OK"]);
baseTransaction.bitcount(key17, {
start: 0,
});
responseData.push(["bitcount(key17, {start:0 }", 26]);
}

baseTransaction.bitfield(key17, [
new BitFieldSet(
new UnsignedEncoding(10),
Expand Down
11 changes: 8 additions & 3 deletions python/python/glide/async_commands/bitmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ class BitmapIndexType(Enum):

class OffsetOptions:
def __init__(
self, start: int, end: int, index_type: Optional[BitmapIndexType] = None
self,
start: int,
end: Optional[int] = None,
index_type: Optional[BitmapIndexType] = None,
):
"""
Represents offsets specifying a string interval to analyze in the `BITCOUNT` command. The offsets are
Expand All @@ -34,7 +37,7 @@ def __init__(

Args:
start (int): The starting offset index.
end (int): The ending offset index.
end (int): The ending offset index. Optional since Valkey version 8.0.0 and above. If not provided, it will default to the end of the string.
index_type (Optional[BitmapIndexType]): The index offset type. This option can only be specified if you are
using Valkey version 7.0.0 or above. Could be either `BitmapIndexType.BYTE` or `BitmapIndexType.BIT`.
If no index type is provided, the indexes will be assumed to be byte indexes.
Expand All @@ -44,7 +47,9 @@ def __init__(
self.index_type = index_type

def to_args(self) -> List[str]:
args = [str(self.start), str(self.end)]
args = [str(self.start)]
if self.end:
args.append(str(self.end))
if self.index_type is not None:
args.append(self.index_type.value)

Expand Down
2 changes: 2 additions & 0 deletions python/python/glide/async_commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5531,6 +5531,8 @@ async def bitcount(
Examples:
>>> await client.bitcount("my_key1")
2 # The string stored at "my_key1" contains 2 set bits.
>>> await client.bitcount("my_key2", OffsetOptions(1))
8 # From the second to last bytes of the string stored at "my_key2" there are 8 set bits.
>>> await client.bitcount("my_key2", OffsetOptions(1, 3))
2 # The second to fourth bytes of the string stored at "my_key2" contain 2 set bits.
>>> await client.bitcount("my_key3", OffsetOptions(1, 1, BitmapIndexType.BIT))
Expand Down
19 changes: 19 additions & 0 deletions python/python/tests/test_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7148,6 +7148,25 @@ async def test_bitcount(self, glide_client: TGlideClient):
set_key, OffsetOptions(1, 1, BitmapIndexType.BIT)
)

if await check_if_server_version_lt(glide_client, "7.9.0"):
# exception thrown optional end was implemented after 8.0.0
with pytest.raises(RequestError):
await glide_client.bitcount(
key1,
OffsetOptions(
2,
),
)
else:
assert await glide_client.bitcount(key1, OffsetOptions(0)) == 26
assert await glide_client.bitcount(key1, OffsetOptions(5)) == 4
assert await glide_client.bitcount(key1, OffsetOptions(80)) == 0
assert await glide_client.bitcount(non_existing_key, OffsetOptions(5)) == 0

# key exists but it is not a string
with pytest.raises(RequestError):
await glide_client.bitcount(set_key, OffsetOptions(1))

@pytest.mark.parametrize("cluster_mode", [True, False])
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
async def test_setbit(self, glide_client: TGlideClient):
Expand Down
6 changes: 6 additions & 0 deletions python/python/tests/test_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,12 @@ async def transaction_test(
transaction.bitpos_interval(key20, 1, 44, 50, BitmapIndexType.BIT)
args.append(46)

if not await check_if_server_version_lt(glide_client, "7.9.0"):
transaction.set(key20, "foobar")
args.append(OK)
transaction.bitcount(key20, OffsetOptions(0))
args.append(26)

transaction.geoadd(
key12,
{
Expand Down
Loading