diff --git a/.github/json_matrices/engine-matrix.json b/.github/json_matrices/engine-matrix.json index bf755b782e..464aedf31a 100644 --- a/.github/json_matrices/engine-matrix.json +++ b/.github/json_matrices/engine-matrix.json @@ -5,6 +5,6 @@ }, { "type": "valkey", - "version": "8.0.0-rc1" + "version": "8.0.0" } ] diff --git a/.github/workflows/install-shared-dependencies/action.yml b/.github/workflows/install-shared-dependencies/action.yml index 0a134eecc9..abca1966cd 100644 --- a/.github/workflows/install-shared-dependencies/action.yml +++ b/.github/workflows/install-shared-dependencies/action.yml @@ -40,7 +40,6 @@ runs: if: "${{ inputs.os == 'macos' }}" run: | brew update - brew upgrade || true brew install git gcc pkgconfig openssl coreutils - name: Install software dependencies for Ubuntu GNU diff --git a/.github/workflows/java-cd.yml b/.github/workflows/java-cd.yml index 26efa0cfb7..e9df283c50 100644 --- a/.github/workflows/java-cd.yml +++ b/.github/workflows/java-cd.yml @@ -221,16 +221,10 @@ jobs: fail-fast: false matrix: host: ${{ fromJson(needs.load-platform-matrix.outputs.PLATFORM_MATRIX) }} - runs-on: ${{ matrix.host.RUNNER }} steps: - - name: Start Valkey server - uses: ./.github/actions/install-valkey - with: - engine-version: "7.2.5" - target: ${{ matrix.host.TARGET }} - - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 with: submodules: recursive @@ -244,6 +238,7 @@ jobs: uses: ./.github/workflows/install-shared-dependencies with: os: ${{ matrix.host.OS }} + engine-version: "7.2.5" target: ${{ matrix.host.TARGET }} github-token: ${{ secrets.GITHUB_TOKEN }} @@ -253,17 +248,26 @@ jobs: version: "26.1" repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Start standalone Valkey server + working-directory: utils + id: port + run: | + PORT=$(python3 ./cluster_manager.py start -r 0 2>&1 | grep CLUSTER_NODES | cut -d = -f 2 | cut -d , -f 1 | cut -d : -f 2) + echo "PORT=$PORT" >> $GITHUB_OUTPUT + - name: Test deployment working-directory: java + env: + PORT: ${{ steps.port.outputs.PORT }} run: | export ORG_GRADLE_PROJECT_centralManualTestingAuthHeaderName="Authorization" export ORG_GRADLE_PROJECT_centralManualTestingAuthHeaderValue="Bearer $(echo "${{ secrets.CENTRAL_TOKEN_USERNAME }}:${{ secrets.CENTRAL_TOKEN_PASSWORD }}" | base64)" export GLIDE_RELEASE_VERSION=${{ env.RELEASE_VERSION }} - ./gradlew :benchmarks:run --args="--minimal --clients glide" + ./gradlew :benchmarks:run --args="--minimal --clients glide --port ${{ env.PORT }}" publish-release-to-maven: if: ${{ inputs.maven_publish == true || github.event_name == 'push' }} - needs: [test-deployment-on-all-architectures] + needs: [publish-to-maven-central-deployment, test-deployment-on-all-architectures] runs-on: ubuntu-latest environment: AWS_ACTIONS env: @@ -287,4 +291,3 @@ jobs: curl --request DELETE \ -u "${{ secrets.CENTRAL_TOKEN_USERNAME }}:${{ secrets.CENTRAL_TOKEN_PASSWORD }}" \ "https://central.sonatype.com/api/v1/publisher/deployment/${{ env.DEPLOYMENT_ID }}" - diff --git a/.github/workflows/npm-cd.yml b/.github/workflows/npm-cd.yml index 25db537ab5..3788d87e04 100644 --- a/.github/workflows/npm-cd.yml +++ b/.github/workflows/npm-cd.yml @@ -108,6 +108,7 @@ jobs: if ${{ env.EVENT_NAME == 'pull_request' }}; then R_VERSION="255.255.255" elif ${{ env.EVENT_NAME == 'workflow_dispatch' }}; then + echo "${{env.EVENT_NAME}}" R_VERSION="${{ env.INPUT_VERSION }}" else R_VERSION=${GITHUB_REF:11} @@ -229,8 +230,16 @@ jobs: working-directory: ./node/npm/glide run: | export pkg_name=valkey-glide - echo "${GITHUB_REF:11}" - export package_version=${GITHUB_REF:11} + + echo "The workflow is: ${{env.EVENT_NAME}}" + if ${{ env.EVENT_NAME == 'workflow_dispatch' }}; then + R_VERSION="${{ env.INPUT_VERSION }}" + else + R_VERSION=${GITHUB_REF:11} + fi + echo "RELEASE_VERSION=${R_VERSION}" >> $GITHUB_ENV + + export package_version=${R_VERSION} export scope=`if [ "$NPM_SCOPE" != '' ]; then echo "$NPM_SCOPE/"; fi` mv package.json package.json.tmpl envsubst < package.json.tmpl > "package.json" @@ -239,6 +248,8 @@ jobs: sed -i "s|@scope/|${scope}|g" index.ts env: NPM_SCOPE: ${{ vars.NPM_SCOPE }} + EVENT_NAME: ${{ github.event_name }} + INPUT_VERSION: ${{ github.event.inputs.version }} - name: Build Node wrapper uses: ./.github/workflows/build-node-wrapper @@ -251,7 +262,7 @@ jobs: - name: Check if RC and set a distribution tag for the package shell: bash run: | - if [[ "${GITHUB_REF:11}" == *"rc"* ]] + if [[ ${{ env.RELEASE_VERSION }} == *"rc"* ]] then echo "This is a release candidate" export npm_tag="next" diff --git a/.github/workflows/pypi-cd.yml b/.github/workflows/pypi-cd.yml index b30810c233..e69343f234 100644 --- a/.github/workflows/pypi-cd.yml +++ b/.github/workflows/pypi-cd.yml @@ -63,11 +63,11 @@ jobs: if: github.repository_owner == 'valkey-io' name: Publish packages to PyPi runs-on: ${{ matrix.build.RUNNER }} - timeout-minutes: 25 + timeout-minutes: 35 strategy: fail-fast: false matrix: - build: ${{fromJson( needs.load-platform-matrix.outputs.PLATFORM_MATRIX )}} + build: ${{ fromJson(needs.load-platform-matrix.outputs.PLATFORM_MATRIX) }} steps: - name: Setup self-hosted runner access if: ${{ contains(matrix.build.RUNNER, 'self-hosted') }} @@ -118,8 +118,7 @@ jobs: if: startsWith(matrix.build.NAMED_OS, 'darwin') run: | brew update - brew upgrade || true - brew install python@3.9 + brew install python@3.8 python@3.9 - name: Setup Python for self-hosted Ubuntu runners if: contains(matrix.build.OS, 'ubuntu') && contains(matrix.build.RUNNER, 'self-hosted') @@ -191,9 +190,9 @@ jobs: - name: Upload Python wheels if: github.event_name != 'pull_request' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: wheels + name: wheels-${{ matrix.build.TARGET }} path: python/wheels if-no-files-found: error @@ -206,7 +205,8 @@ jobs: - uses: actions/download-artifact@v4 with: path: python/wheels - name: wheels + merge-multiple: true + - name: Publish to PyPI uses: PyO3/maturin-action@v1 env: @@ -215,3 +215,62 @@ jobs: with: command: upload args: --skip-existing python/wheels/* + + test-release: + if: github.event_name != 'pull_request' + name: Test the release + runs-on: ${{ matrix.build.RUNNER }} + needs: [publish-to-pypi, load-platform-matrix] + strategy: + fail-fast: false + matrix: + build: ${{ fromJson(needs.load-platform-matrix.outputs.PLATFORM_MATRIX) }} + steps: + - name: Setup self-hosted runner access + if: ${{ matrix.build.TARGET == 'aarch64-unknown-linux-gnu' }} + run: sudo chown -R $USER:$USER /home/ubuntu/actions-runner/_work/valkey-glide + + - name: checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.12 + + - name: Install ValKey + uses: ./.github/workflows/install-valkey + with: + version: "8.0.0" + + - name: Check if RC and set a distribution tag for the package + shell: bash + run: | + if [[ "${GITHUB_REF:11}" == *"rc"* ]] + then + echo "This is a release candidate" + export pip_pre="--pre" + else + echo "This is a stable release" + export pip_pre="" + fi + echo "PIP_PRE=${pip_pre}" >> $GITHUB_ENV + + - name: Run the tests + shell: bash + working-directory: ./utils/release-candidate-testing/python + run: | + python -m venv venv + source venv/bin/activate + pip install -U pip + pip install ${PIP_PRE} valkey-glide + python rc_test.py + + # Reset the repository to make sure we get the clean checkout of the action later in other actions. + # It is not required since in other actions we are cleaning before the action, but it is a good practice to do it here as well. + - name: Reset repository + if: ${{ contains(matrix.build.RUNNER, 'self-hosted') }} + shell: bash + run: | + git reset --hard + git clean -xdf diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 2511c8c1f4..a1b5a16721 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -358,6 +358,6 @@ jobs: continue-on-error: true uses: actions/upload-artifact@v4 with: - name: smoke-test-report-amazon-linux + name: modules-test-report-${{ matrix.host.TARGET }}-python-${{ matrix.python }}-server-${{ matrix.engine.version }} path: | python/python/tests/pytest_report.html diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 3ebfc8a5a3..c022e3e419 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -69,6 +69,7 @@ jobs: os: "ubuntu" target: "x86_64-unknown-linux-gnu" engine-version: ${{ matrix.engine.version }} + github-token: ${{ secrets.GITHUB_TOKEN }} - uses: Swatinem/rust-cache@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a00046cff..2cf0385c97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ #### Changes +* Node: Fix binary variant for xinfogroups and lrem ([#2324](https://github.com/valkey-io/valkey-glide/pull/2324)) +* Node: Fixed missing exports ([#2301](https://github.com/valkey-io/valkey-glide/pull/2301)) * Node: Fix BITPOS for Valkey8 ([#2227](https://github.com/valkey-io/valkey-glide/pull/2227)) * Node: Use `options` struct for all optional arguments ([#2287](https://github.com/valkey-io/valkey-glide/pull/2287)) * Node: Added `invokeScript` API with routing for cluster client ([#2284](https://github.com/valkey-io/valkey-glide/pull/2284)) @@ -140,9 +142,11 @@ * Node: Fix ZADD bug where command could not be called with only the `changed` optional parameter ([#1995](https://github.com/valkey-io/valkey-glide/pull/1995)) * Java: `XRange`/`XRevRange` should return `null` instead of `GlideException` when given a negative count ([#1920](https://github.com/valkey-io/valkey-glide/pull/1920)) * Python: Fix `XClaim` return type to `List[bytes]` instead of `List[TEncodable]` ([#2075](https://github.com/valkey-io/valkey-glide/pull/2075)) +* Python: Add missing exports ([#2341](https://github.com/valkey-io/valkey-glide/pull/2341)) +* Node: Add missing exports ([#2342](https://github.com/valkey-io/valkey-glide/pull/2342)) ### Operational Enhancements -* CI/CD: Create Workflow to deploy artifacts for all platforms ([#2285](https://github.com/valkey-io/valkey-glide/pull/2285) +* CI/CD: Create Workflow to deploy artifacts for all platforms ([#2285](https://github.com/valkey-io/valkey-glide/pull/2285)) * Node: Get valkey/redis version using client's info command ([#2276](https://github.com/valkey-io/valkey-glide/pull/2276)) * Java: Fetch server version using client's info command ([#2258](https://github.com/valkey-io/valkey-glide/pull/2258)) * CI/CD: Add workflow for automating Maven release ([#2128](https://github.com/valkey-io/valkey-glide/pull/2128)) @@ -303,7 +307,7 @@ * Python: Added SETRANGE command ([#1453](https://github.com/valkey-io/valkey-glide/pull/1453)) #### Fixes -* Python: Fix typing error "‘type’ object is not subscriptable" ([#1203](https://github.com/valkey-io/valkey-glide/pull/1203)) +* Python: Fix typing error "'type' object is not subscriptable" ([#1203](https://github.com/valkey-io/valkey-glide/pull/1203)) * Core: Fixed blocking commands to use the specified timeout from the command argument ([#1283](https://github.com/valkey-io/valkey-glide/pull/1283)) ### Breaking Changes diff --git a/README.md b/README.md index 4ccfd8594d..cf3714f92f 100644 --- a/README.md +++ b/README.md @@ -4,17 +4,18 @@ Valkey General Language Independent Driver for the Enterprise (GLIDE), is an ope ## Supported Engine Versions Valkey GLIDE is API-compatible with the following engine versions: -| Engine Type | 6.2 | 7.0 | 7.2 | -|-----------------------|-------|-------|-------| -| Valkey | - | - | V | -| Redis | V | V | V | +| Engine Type | 6.2 | 7.0 | 7.2 | 8.0 | +|-----------------------|-------|-------|-------|-------| +| Valkey | - | - | V | V | +| Redis | V | V | V | - | ## Current Status -In this release, Valkey GLIDE is available for Python and Java. Support for Node.js is actively under development, with plans to include more programming languages in the future. We're tracking future features on the [roadmap](https://github.com/orgs/valkey-io/projects/11). +In this release, Valkey GLIDE is available for Python, Java and Node.js. Support for GO is actively under development, with plans to include more programming languages in the future. We're tracking future features on the [roadmap](https://github.com/orgs/valkey-io/projects/11). ## Getting Started - [Java](./java/README.md) - [Python](./python/README.md) +- [Node](./node/README.md) - [Documentation](https://github.com/valkey-io/valkey-glide/wiki) ## Getting Help diff --git a/benchmarks/python/python_benchmark.py b/benchmarks/python/python_benchmark.py index 1da52f9941..580ac8a41d 100644 --- a/benchmarks/python/python_benchmark.py +++ b/benchmarks/python/python_benchmark.py @@ -16,7 +16,8 @@ import numpy as np import redis.asyncio as redispy # type: ignore from glide import ( - BaseClientConfiguration, + GlideClientConfiguration, + GlideClusterClientConfiguration, GlideClient, GlideClusterClient, Logger, @@ -289,7 +290,9 @@ async def main( if clients_to_run == "all" or clients_to_run == "glide": # Glide Socket client_class = GlideClusterClient if is_cluster else GlideClient - config = BaseClientConfiguration( + config = GlideClusterClientConfiguration( + [NodeAddress(host=host, port=port)], use_tls=use_tls + ) if is_cluster else GlideClientConfiguration( [NodeAddress(host=host, port=port)], use_tls=use_tls ) clients = await create_clients( diff --git a/csharp/README.md b/csharp/README.md deleted file mode 100644 index 390b146adc..0000000000 --- a/csharp/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# C# wrapper - -The C# wrapper is currently not in a usable state and is under development. - -# Valkey GLIDE - -Valkey General Language Independent Driver for the Enterprise (GLIDE), is an open-source Valkey client library. Valkey GLIDE is one of the official client libraries for Valkey, and it supports all Valkey commands. Valkey GLIDE supports Valkey 7.2 and above, and Redis open-source 6.2, 7.0 and 7.2. Application programmers use Valkey GLIDE to safely and reliably connect their applications to Valkey- and Redis OSS- compatible services. Valkey GLIDE is designed for reliability, optimized performance, and high-availability, for Valkey and Redis OSS based applications. It is sponsored and supported by AWS, and is pre-configured with best practices learned from over a decade of operating Redis OSS-compatible services used by hundreds of thousands of customers. To help ensure consistency in application development and operations, Valkey GLIDE is implemented using a core driver framework, written in Rust, with language specific extensions. This design ensures consistency in features across languages and reduces overall complexity. - -## Supported Engine Versions - -Refer to the [Supported Engine Versions table](https://github.com/valkey-io/valkey-glide/blob/main/README.md#supported-engine-versions) for details. - -## Current Status - -We've made Valkey GLIDE an open-source project, and are releasing it in Preview to the community to gather feedback, and actively collaborate on the project roadmap. We welcome questions and contributions from all Redis stakeholders. -This preview release is recommended for testing purposes only. - -# Getting Started - C# Wrapper - -## .net sdk supported version - -.net 6.0 or higher. - -## Basic Example - -```csharp - -using Glide; - -AsyncClient glideClient = new (host, PORT, useTLS); -await glideClient.SetAsync("foo", "bar"); -string? value = await glideClient.GetAsync("foo"); -glideClient.Dispose(); -``` - -### Building & Testing - -Development instructions for local building & testing the package are in the [DEVELOPER.md](DEVELOPER.md#build-from-source) file. diff --git a/examples/java/src/main/java/glide/examples/ClusterExample.java b/examples/java/src/main/java/glide/examples/ClusterExample.java index cc598b632a..5fce398f7a 100644 --- a/examples/java/src/main/java/glide/examples/ClusterExample.java +++ b/examples/java/src/main/java/glide/examples/ClusterExample.java @@ -10,7 +10,7 @@ import glide.api.GlideClusterClient; import glide.api.logging.Logger; import glide.api.models.ClusterValue; -import glide.api.models.commands.InfoOptions; +import glide.api.models.commands.InfoOptions.Section; import glide.api.models.configuration.GlideClusterClientConfiguration; import glide.api.models.configuration.NodeAddress; import glide.api.models.exceptions.ClosingException; @@ -76,7 +76,7 @@ public static void appLogic(GlideClusterClient client) // Send INFO REPLICATION with routing option to all nodes ClusterValue infoResponse = client - .info(InfoOptions.builder().section(InfoOptions.Section.REPLICATION).build(), ALL_NODES) + .info(new Section[] {Section.REPLICATION}, ALL_NODES) .get(); log( INFO, diff --git a/examples/node/README.MD b/examples/node/README.MD index adea1790ed..4bac2015c4 100644 --- a/examples/node/README.MD +++ b/examples/node/README.MD @@ -1,38 +1,19 @@ -## Pre-requirements -- GCC -- pkg-config -- protobuf-compiler (protoc) -- openssl -- libssl-dev - -Installation for ubuntu: -`sudo apt install -y gcc pkg-config protobuf-compiler openssl libssl-dev python3` - -### node 16 (or newer) - -This is required for the NodeJS wrapper, and for running benchmarks. - +### Install node 16 (or newer) ``` curl -s https://deb.nodesource.com/setup_16.x | sudo bash apt-get install nodejs npm npm i -g npm@8 ``` -## Build -To build GLIDE's Node client, run (on unix based systems): +## Install GLIDE package and build the example ``` -cd valkey-glide/node -git submodule update --init --recursive -npm install -rm -rf build-ts -npm run build:release cd valkey-glide/examples/node npm install npx tsc ``` ## Run -To run the example: +To run the example (make sure redis/valkey server is available at the address used in the example): ``` cd valkey-glide/examples/node node index.js diff --git a/examples/node/index.ts b/examples/node/index.ts index 49fa9a531d..b721d51331 100644 --- a/examples/node/index.ts +++ b/examples/node/index.ts @@ -4,8 +4,8 @@ import { GlideClient, GlideClusterClient, Logger } from "@valkey/valkey-glide"; -async function sendPingToNode() { - // When in Redis is in standalone mode, add address of the primary node, and any replicas you'd like to be able to read from. +async function sendPingToStandAloneNode() { + // When Valkey is in standalone mode, add address of the primary node, and any replicas you'd like to be able to read from. const addresses = [ { host: "localhost", @@ -34,7 +34,7 @@ async function send_set_and_get(client: GlideClient | GlideClusterClient) { } async function sendPingToRandomNodeInCluster() { - // When in Redis is cluster mode, add address of any nodes, and the client will find all nodes in the cluster. + // When Valkey is in cluster mode, add address of any nodes, and the client will find all nodes in the cluster. const addresses = [ { host: "localhost", @@ -49,7 +49,7 @@ async function sendPingToRandomNodeInCluster() { clientName: "test_cluster_client", }); // The empty array signifies that there are no additional arguments. - const pong = await client.customCommand(["PING"], "randomNode"); + const pong = await client.customCommand(["PING"], { route: "randomNode" }); console.log(pong); await send_set_and_get(client); client.close(); @@ -64,6 +64,9 @@ function setConsoleLogger() { } setFileLogger(); -await sendPingToNode(); setConsoleLogger(); -await sendPingToRandomNodeInCluster(); +// Enable for standalone mode +await sendPingToStandAloneNode(); + +// Enable for cluster mode +// await sendPingToRandomNodeInCluster(); diff --git a/examples/node/package.json b/examples/node/package.json index 1521a6d8c4..dbcd4ce52d 100644 --- a/examples/node/package.json +++ b/examples/node/package.json @@ -1,7 +1,7 @@ { "type": "module", "dependencies": { - "@valkey/valkey-glide": "^1.0.0", + "@valkey/valkey-glide": "latest", "@types/node": "^20.4.8" }, "devDependencies": { diff --git a/java/client/build.gradle b/java/client/build.gradle index 0178f311ea..46fa8f4cee 100644 --- a/java/client/build.gradle +++ b/java/client/build.gradle @@ -209,7 +209,7 @@ publishing { } developers { developer { - name = 'valkey-glide' + name = 'Valkey GLIDE Maintainers' url = 'https://github.com/valkey-io/valkey-glide.git' } } diff --git a/java/integTest/src/test/java/glide/PubSubTests.java b/java/integTest/src/test/java/glide/PubSubTests.java index ff2220c0dd..e4eeab6cad 100644 --- a/java/integTest/src/test/java/glide/PubSubTests.java +++ b/java/integTest/src/test/java/glide/PubSubTests.java @@ -7,7 +7,6 @@ import static glide.TestUtilities.commonClusterClientConfig; import static glide.api.BaseClient.OK; import static glide.api.models.GlideString.gs; -import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleMultiNodeRoute.ALL_NODES; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; @@ -36,6 +35,7 @@ import glide.api.models.exceptions.RequestException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; @@ -83,12 +83,15 @@ private BaseClient createClientWithSubscriptions( if (callback.isPresent()) { subConfigBuilder.callback(callback.get(), context.get()); } - return GlideClient.createClient( - commonClientConfig() - .requestTimeout(5000) - .subscriptionConfiguration(subConfigBuilder.build()) - .build()) - .get(); + var client = + GlideClient.createClient( + commonClientConfig() + .requestTimeout(5000) + .subscriptionConfiguration(subConfigBuilder.build()) + .build()) + .get(); + listeners.put(client, subscriptions); + return client; } else { var subConfigBuilder = ClusterSubscriptionConfiguration.builder() @@ -98,26 +101,35 @@ private BaseClient createClientWithSubscriptions( subConfigBuilder.callback(callback.get(), context.get()); } - return GlideClusterClient.createClient( - commonClusterClientConfig() - .requestTimeout(5000) - .subscriptionConfiguration(subConfigBuilder.build()) - .build()) - .get(); + var client = + GlideClusterClient.createClient( + commonClusterClientConfig() + .requestTimeout(5000) + .subscriptionConfiguration(subConfigBuilder.build()) + .build()) + .get(); + listeners.put(client, subscriptions); + return client; } } private BaseClient createClientWithSubscriptions( boolean standalone, Map> subscriptions) { - return createClientWithSubscriptions( - standalone, subscriptions, Optional.empty(), Optional.empty()); + var client = + createClientWithSubscriptions( + standalone, subscriptions, Optional.empty(), Optional.empty()); + listeners.put(client, subscriptions); + return client; } @SneakyThrows private BaseClient createClient(boolean standalone) { - return standalone - ? GlideClient.createClient(commonClientConfig().build()).get() - : GlideClusterClient.createClient(commonClusterClientConfig().build()).get(); + var client = + standalone + ? GlideClient.createClient(commonClientConfig().build()).get() + : GlideClusterClient.createClient(commonClusterClientConfig().build()).get(); + senders.add(client); + return client; } /** @@ -126,32 +138,67 @@ private BaseClient createClient(boolean standalone) { private final ConcurrentLinkedDeque> pubsubMessageQueue = new ConcurrentLinkedDeque<>(); - /** Clients used in a test. */ - private final List clients = new ArrayList<>(); + /** Subscribed clients used in a test. */ + private final Map>> listeners = + new HashMap<>(); + + /** Other clients used in a test. */ + private final List senders = new ArrayList<>(); private static final int MESSAGE_DELIVERY_DELAY = 500; // ms @AfterEach @SneakyThrows public void cleanup() { - for (var client : clients) { + for (var pair : listeners.entrySet()) { + var client = pair.getKey(); + var subscriptionTypes = pair.getValue(); if (client instanceof GlideClusterClient) { - ((GlideClusterClient) client) - .customCommand(new GlideString[] {gs("unsubscribe")}, ALL_NODES) - .get(); - ((GlideClusterClient) client) - .customCommand(new GlideString[] {gs("punsubscribe")}, ALL_NODES) - .get(); - ((GlideClusterClient) client) - .customCommand(new GlideString[] {gs("sunsubscribe")}, ALL_NODES) - .get(); + for (var subscription : subscriptionTypes.entrySet()) { + var channels = subscription.getValue().toArray(GlideString[]::new); + for (GlideString channel : channels) { + switch ((PubSubClusterChannelMode) subscription.getKey()) { + case EXACT: + ((GlideClusterClient) client) + .customCommand(new GlideString[] {gs("unsubscribe"), channel}) + .get(); + break; + case PATTERN: + ((GlideClusterClient) client) + .customCommand(new GlideString[] {gs("punsubscribe"), channel}) + .get(); + break; + case SHARDED: + ((GlideClusterClient) client) + .customCommand(new GlideString[] {gs("sunsubscribe"), channel}) + .get(); + break; + } + } + } } else { - ((GlideClient) client).customCommand(new GlideString[] {gs("unsubscribe")}).get(); - ((GlideClient) client).customCommand(new GlideString[] {gs("punsubscribe")}).get(); + for (var subscription : subscriptionTypes.entrySet()) { + var channels = subscription.getValue().toArray(GlideString[]::new); + switch ((PubSubChannelMode) subscription.getKey()) { + case EXACT: + ((GlideClient) client) + .customCommand(ArrayUtils.addFirst(channels, gs("unsubscribe"))) + .get(); + break; + case PATTERN: + ((GlideClient) client) + .customCommand(ArrayUtils.addFirst(channels, gs("punsubscribe"))) + .get(); + break; + } + } } + } + listeners.clear(); + for (var client : senders) { client.close(); } - clients.clear(); + senders.clear(); pubsubMessageQueue.clear(); } @@ -246,7 +293,6 @@ public void exact_happy_path(boolean standalone, MessageReadMethod method) { var listener = createListener(standalone, method == MessageReadMethod.Callback, 1, subscriptions); var sender = createClient(standalone); - clients.addAll(List.of(listener, sender)); sender.publish(message, channel).get(); Thread.sleep(MESSAGE_DELIVERY_DELAY); // deliver the message @@ -279,7 +325,6 @@ public void exact_happy_path_many_channels(boolean standalone, MessageReadMethod var listener = createListener(standalone, method == MessageReadMethod.Callback, 1, subscriptions); var sender = createClient(standalone); - clients.addAll(List.of(listener, sender)); for (var pubsubMessage : messages) { sender.publish(pubsubMessage.getMessage(), pubsubMessage.getChannel()).get(); @@ -305,7 +350,6 @@ public void sharded_pubsub(MessageReadMethod method) { var listener = createListener(false, method == MessageReadMethod.Callback, 1, subscriptions); var sender = (GlideClusterClient) createClient(false); - clients.addAll(List.of(listener, sender)); sender.publish(pubsubMessage, channel, true).get(); Thread.sleep(MESSAGE_DELIVERY_DELAY); // deliver the message @@ -339,7 +383,6 @@ public void sharded_pubsub_many_channels(MessageReadMethod method) { var listener = createListener(false, method == MessageReadMethod.Callback, 1, subscriptions); var sender = (GlideClusterClient) createClient(false); - clients.addAll(List.of(listener, sender)); for (var pubsubMessage : pubsubMessages) { sender.publish(pubsubMessage.getMessage(), pubsubMessage.getChannel(), true).get(); @@ -376,7 +419,6 @@ public void pattern(boolean standalone, MessageReadMethod method) { var listener = createListener(standalone, method == MessageReadMethod.Callback, 1, subscriptions); var sender = createClient(standalone); - clients.addAll(List.of(listener, sender)); Thread.sleep(MESSAGE_DELIVERY_DELAY); // need some time to propagate subscriptions - why? @@ -419,7 +461,6 @@ public void pattern_many_channels(boolean standalone, MessageReadMethod method) var listener = createListener(standalone, method == MessageReadMethod.Callback, 1, subscriptions); var sender = createClient(standalone); - clients.addAll(List.of(listener, sender)); Thread.sleep(MESSAGE_DELIVERY_DELAY); // need some time to propagate subscriptions - why? @@ -470,7 +511,6 @@ public void combined_exact_and_pattern_one_client(boolean standalone, MessageRea var listener = createListener(standalone, method == MessageReadMethod.Callback, 1, subscriptions); var sender = createClient(standalone); - clients.addAll(List.of(listener, sender)); for (var pubsubMessage : messages) { sender.publish(pubsubMessage.getMessage(), pubsubMessage.getChannel()).get(); @@ -519,7 +559,6 @@ public void combined_exact_and_pattern_multiple_clients( createListener(standalone, method == MessageReadMethod.Callback, 2, subscriptions); var sender = createClient(standalone); - clients.addAll(List.of(listenerExactSub, listenerPatternSub, sender)); for (var pubsubMessage : messages) { sender.publish(pubsubMessage.getMessage(), pubsubMessage.getChannel()).get(); @@ -596,7 +635,6 @@ public void combined_exact_pattern_and_sharded_one_client(MessageReadMethod meth var listener = createListener(false, method == MessageReadMethod.Callback, 1, subscriptions); var sender = (GlideClusterClient) createClient(false); - clients.addAll(List.of(listener, sender)); for (var pubsubMessage : messages) { sender.publish(pubsubMessage.getMessage(), pubsubMessage.getChannel()).get(); @@ -653,7 +691,6 @@ public void coexistense_of_sync_and_async_read() { var listener = createListener(false, false, 1, subscriptions); var sender = (GlideClusterClient) createClient(false); - clients.addAll(List.of(listener, sender)); for (var pubsubMessage : messages) { sender.publish(pubsubMessage.getMessage(), pubsubMessage.getChannel()).get(); @@ -756,7 +793,6 @@ public void combined_exact_pattern_and_sharded_multi_client(MessageReadMethod me subscriptionsSharded); var sender = (GlideClusterClient) createClient(false); - clients.addAll(List.of(listenerExact, listenerPattern, listenerSharded, sender)); for (var pubsubMessage : exactMessages) { sender.publish(pubsubMessage.getMessage(), pubsubMessage.getChannel()).get(); @@ -849,8 +885,6 @@ public void three_publishing_clients_same_name_with_sharded(MessageReadMethod me false, true, PubSubClusterChannelMode.SHARDED.ordinal(), subscriptionsSharded) : (GlideClusterClient) createClientWithSubscriptions(false, subscriptionsSharded); - clients.addAll(List.of(listenerExact, listenerPattern, listenerSharded)); - listenerPattern.publish(exactMessage.getMessage(), channel).get(); listenerSharded.publish(patternMessage.getMessage(), channel).get(); listenerExact.publish(shardedMessage.getMessage(), channel, true).get(); @@ -961,7 +995,6 @@ public void transaction_with_all_types_of_messages(boolean standalone, MessageRe var listener = createListener(standalone, method == MessageReadMethod.Callback, 1, subscriptions); var sender = createClient(standalone); - clients.addAll(List.of(listener, sender)); if (standalone) { var transaction = @@ -1004,7 +1037,6 @@ public void pubsub_exact_max_size_message(boolean standalone) { : Map.of(PubSubClusterChannelMode.EXACT, Set.of(channel)); var listener = createClientWithSubscriptions(standalone, subscriptions); var sender = createClient(standalone); - clients.addAll(Arrays.asList(listener, sender)); assertEquals(OK, sender.publish(message, channel).get()); assertEquals(OK, sender.publish(message2, channel).get()); @@ -1044,7 +1076,6 @@ public void pubsub_sharded_max_size_message(boolean standalone) { Map.of(PubSubClusterChannelMode.SHARDED, Set.of(channel)); var listener = createClientWithSubscriptions(standalone, subscriptions); var sender = createClient(standalone); - clients.addAll(Arrays.asList(listener, sender)); assertEquals(OK, sender.publish(message, channel).get()); assertEquals(OK, ((GlideClusterClient) sender).publish(message2, channel, true).get()); @@ -1101,7 +1132,6 @@ public void pubsub_exact_max_size_message_callback(boolean standalone) { Optional.ofNullable(callback), Optional.of(callbackMessages)); var sender = createClient(standalone); - clients.addAll(Arrays.asList(listener, sender)); assertEquals(OK, sender.publish(message, channel).get()); @@ -1143,7 +1173,6 @@ public void pubsub_sharded_max_size_message_callback(boolean standalone) { Optional.ofNullable(callback), Optional.of(callbackMessages)); var sender = createClient(standalone); - clients.addAll(Arrays.asList(listener, sender)); assertEquals(OK, ((GlideClusterClient) sender).publish(message, channel, true).get()); @@ -1188,7 +1217,6 @@ public void pubsub_test_callback_exception(boolean standalone) { Optional.ofNullable(callback), Optional.of(callbackMessages)); var sender = createClient(standalone); - clients.addAll(Arrays.asList(listener, sender)); assertEquals(OK, sender.publish(message1, channel).get()); assertEquals(OK, sender.publish(message2, channel).get()); @@ -1241,7 +1269,6 @@ public void pubsub_with_binary(boolean standalone) { createClientWithSubscriptions( standalone, subscriptions, Optional.of(callback), Optional.of(callbackMessages)); var sender = createClient(standalone); - clients.addAll(List.of(listener, listener2, sender)); assertEquals(OK, sender.publish(message.getMessage(), channel).get()); Thread.sleep(MESSAGE_DELIVERY_DELAY); // deliver the messages @@ -1277,7 +1304,6 @@ public void pubsub_channels(boolean standalone) { channels.stream().map(GlideString::gs).collect(Collectors.toSet())); var listener = createClientWithSubscriptions(standalone, subscriptions); - clients.addAll(List.of(client, listener)); // test without pattern assertEquals(channels, Set.of(client.pubsubChannels().get())); @@ -1330,7 +1356,6 @@ public void pubsub_numpat(boolean standalone) { patterns.stream().map(GlideString::gs).collect(Collectors.toSet())); var listener = createClientWithSubscriptions(standalone, subscriptions); - clients.addAll(List.of(client, listener)); assertEquals(2, client.pubsubNumPat().get()); assertEquals(2, listener.pubsubNumPat().get()); @@ -1376,8 +1401,6 @@ public void pubsub_numsub(boolean standalone) { : Map.of(PubSubClusterChannelMode.PATTERN, Set.of(gs("channel*"))); var listener4 = createClientWithSubscriptions(standalone, subscriptions4); - clients.addAll(List.of(client, listener1, listener2, listener3, listener4)); - var expected = Map.of("channel1", 1L, "channel2", 2L, "channel3", 3L, "channel4", 0L); assertEquals(expected, client.pubsubNumSub(ArrayUtils.addFirst(channels, "channel4")).get()); assertEquals(expected, listener1.pubsubNumSub(ArrayUtils.addFirst(channels, "channel4")).get()); @@ -1447,7 +1470,6 @@ public void pubsub_channels_and_numpat_and_numsub_in_transaction(boolean standal patterns.stream().map(GlideString::gs).collect(Collectors.toSet())); var listener = createClientWithSubscriptions(standalone, subscriptions); - clients.addAll(List.of(client, listener)); result = standalone @@ -1491,7 +1513,6 @@ public void pubsub_shard_channels() { GlideClusterClient listener = (GlideClusterClient) createClientWithSubscriptions(false, subscriptions); - clients.addAll(List.of(client, listener)); // test without pattern assertEquals(channels, Set.of(client.pubsubShardChannels().get())); @@ -1553,8 +1574,6 @@ public void pubsub_shardnumsub() { GlideClusterClient listener3 = (GlideClusterClient) createClientWithSubscriptions(false, subscriptions3); - clients.addAll(List.of(client, listener1, listener2, listener3)); - var expected = Map.of("channel1", 1L, "channel2", 2L, "channel3", 3L, "channel4", 0L); assertEquals( expected, client.pubsubShardNumSub(ArrayUtils.addFirst(channels, "channel4")).get()); diff --git a/java/integTest/src/test/java/glide/SharedCommandTests.java b/java/integTest/src/test/java/glide/SharedCommandTests.java index c3e600d5c9..3224f45c5c 100644 --- a/java/integTest/src/test/java/glide/SharedCommandTests.java +++ b/java/integTest/src/test/java/glide/SharedCommandTests.java @@ -918,14 +918,10 @@ public void getrange(BaseClient client) { assertEquals("", client.getrange(stringKey, -1, -3).get()); // a redis bug, fixed in version 8: https://github.com/redis/redis/issues/13207 - assertEquals( - SERVER_VERSION.isLowerThan("8.0.0") ? "T" : "", - client.getrange(stringKey, -200, -100).get()); + assertEquals("T", client.getrange(stringKey, -200, -100).get()); // empty key (returning null isn't implemented) - assertEquals( - SERVER_VERSION.isLowerThan("8.0.0") ? "" : null, - client.getrange(nonStringKey, 0, -1).get()); + assertEquals("", client.getrange(nonStringKey, 0, -1).get()); // non-string key assertEquals(1, client.lpush(nonStringKey, new String[] {"_"}).get()); @@ -955,14 +951,10 @@ public void getrange_binary(BaseClient client) { assertEquals(gs(""), client.getrange(stringKey, -1, -3).get()); // a redis bug, fixed in version 8: https://github.com/redis/redis/issues/13207 - assertEquals( - gs(SERVER_VERSION.isLowerThan("8.0.0") ? "T" : ""), - client.getrange(stringKey, -200, -100).get()); + assertEquals(gs("T"), client.getrange(stringKey, -200, -100).get()); // empty key (returning null isn't implemented) - assertEquals( - gs(SERVER_VERSION.isLowerThan("8.0.0") ? "" : null), - client.getrange(nonStringKey, 0, -1).get()); + assertEquals(gs(""), client.getrange(nonStringKey, 0, -1).get()); // non-string key assertEquals(1, client.lpush(nonStringKey, new GlideString[] {gs("_")}).get()); @@ -3279,7 +3271,7 @@ public void persist_on_existing_and_non_existing_key(BaseClient client) { @ParameterizedTest(autoCloseArguments = false) @MethodSource("getClients") public void scriptShow_test(BaseClient client) { - assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")); + assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")); String code = "return '" + UUID.randomUUID().toString().substring(0, 5) + "'"; Script script = new Script(code, false); @@ -10423,7 +10415,7 @@ 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")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { assertEquals(26L, client.bitcount(key1, 0).get()); assertEquals(4L, client.bitcount(key1, 5).get()); assertEquals(0L, client.bitcount(key1, 80).get()); @@ -12557,7 +12549,7 @@ public void sort_binary(BaseClient client) { @MethodSource("getClients") public void sort_with_pattern(BaseClient client) { if (client instanceof GlideClusterClient) { - assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0"), "This feature added in version 8"); + assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0"), "This feature added in version 8"); } String setKey1 = "{setKey}1"; String setKey2 = "{setKey}2"; @@ -12739,7 +12731,7 @@ public void sort_with_pattern(BaseClient client) { @MethodSource("getClients") public void sort_with_pattern_binary(BaseClient client) { if (client instanceof GlideClusterClient) { - assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0"), "This feature added in version 8"); + assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0"), "This feature added in version 8"); } GlideString setKey1 = gs("{setKeyGs}1"); @@ -14291,7 +14283,7 @@ public void sscan(BaseClient client) { assertDeepEquals(new String[] {}, result[resultCollectionIndex]); // Negative cursor - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { ExecutionException executionException = assertThrows(ExecutionException.class, () -> client.sscan(key1, "-1").get()); } else { @@ -14432,7 +14424,7 @@ public void sscan_binary(BaseClient client) { assertDeepEquals(new GlideString[] {}, result[resultCollectionIndex]); // Negative cursor - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { ExecutionException executionException = assertThrows(ExecutionException.class, () -> client.sscan(key1, gs("-1")).get()); } else { @@ -14586,7 +14578,7 @@ public void zscan(BaseClient client) { assertDeepEquals(new String[] {}, result[resultCollectionIndex]); // Negative cursor - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { ExecutionException executionException = assertThrows(ExecutionException.class, () -> client.zscan(key1, "-1").get()); } else { @@ -14710,7 +14702,7 @@ public void zscan(BaseClient client) { assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0); assertTrue(ArrayUtils.getLength(result[resultCollectionIndex]) >= 0); - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { result = client.zscan(key1, initialCursor, ZScanOptions.builder().noScores(true).build()).get(); assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0); @@ -14788,7 +14780,7 @@ public void zscan_binary(BaseClient client) { assertDeepEquals(new GlideString[] {}, result[resultCollectionIndex]); // Negative cursor - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { ExecutionException executionException = assertThrows(ExecutionException.class, () -> client.zscan(key1, gs("-1")).get()); } else { @@ -14917,7 +14909,7 @@ public void zscan_binary(BaseClient client) { assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0); assertTrue(ArrayUtils.getLength(result[resultCollectionIndex]) >= 0); - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { result = client .zscan(key1, initialCursor, ZScanOptionsBinary.builder().noScores(true).build()) @@ -14993,7 +14985,7 @@ public void hscan(BaseClient client) { assertDeepEquals(new String[] {}, result[resultCollectionIndex]); // Negative cursor - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { ExecutionException executionException = assertThrows(ExecutionException.class, () -> client.hscan(key1, "-1").get()); } else { @@ -15104,7 +15096,7 @@ public void hscan(BaseClient client) { assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0); assertTrue(ArrayUtils.getLength(result[resultCollectionIndex]) >= 0); - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { result = client.hscan(key1, initialCursor, HScanOptions.builder().noValues(true).build()).get(); assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0); @@ -15175,7 +15167,7 @@ public void hscan_binary(BaseClient client) { assertDeepEquals(new GlideString[] {}, result[resultCollectionIndex]); // Negative cursor - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { ExecutionException executionException = assertThrows(ExecutionException.class, () -> client.hscan(key1, gs("-1")).get()); } else { @@ -15293,7 +15285,7 @@ public void hscan_binary(BaseClient client) { assertTrue(Long.parseLong(result[resultCursorIndex].toString()) >= 0); assertTrue(ArrayUtils.getLength(result[resultCollectionIndex]) >= 0); - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { result = client .hscan(key1, initialCursor, HScanOptionsBinary.builder().noValues(true).build()) diff --git a/java/integTest/src/test/java/glide/TransactionTestUtilities.java b/java/integTest/src/test/java/glide/TransactionTestUtilities.java index 30675fcc56..46545f786a 100644 --- a/java/integTest/src/test/java/glide/TransactionTestUtilities.java +++ b/java/integTest/src/test/java/glide/TransactionTestUtilities.java @@ -382,7 +382,7 @@ private static Object[] hashCommands(BaseTransaction transaction) { .hscan(hashKey2, "0") .hscan(hashKey2, "0", HScanOptions.builder().count(20L).build()); - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { transaction .hscan(hashKey2, "0", HScanOptions.builder().count(20L).noValues(false).build()) .hscan(hashKey2, "0", HScanOptions.builder().count(20L).noValues(true).build()); @@ -417,7 +417,7 @@ private static Object[] hashCommands(BaseTransaction transaction) { }, // hscan(hashKey2, "0", HScanOptions.builder().count(20L).build()); }; - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { result = concatenateArrays( result, @@ -670,7 +670,7 @@ private static Object[] sortedSetCommands(BaseTransaction transaction) { .zrandmemberWithCountWithScores(zSetKey2, 1) .zscan(zSetKey2, "0") .zscan(zSetKey2, "0", ZScanOptions.builder().count(20L).build()); - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { transaction .zscan(zSetKey2, 0, ZScanOptions.builder().count(20L).noScores(false).build()) .zscan(zSetKey2, 0, ZScanOptions.builder().count(20L).noScores(true).build()); @@ -741,7 +741,7 @@ private static Object[] sortedSetCommands(BaseTransaction transaction) { }, // zscan(zSetKey2, 0, ZScanOptions.builder().count(20L).build()) }; - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { expectedResults = concatenateArrays( expectedResults, @@ -1288,7 +1288,7 @@ private static Object[] bitmapCommands(BaseTransaction transaction) { .bitpos(key3, 1, 44, 50, BitmapIndexType.BIT); } - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { transaction.set(key4, "foobar").bitcount(key4, 0); } @@ -1321,7 +1321,7 @@ private static Object[] bitmapCommands(BaseTransaction transaction) { }); } - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { expectedResults = concatenateArrays( expectedResults, diff --git a/java/integTest/src/test/java/glide/cluster/ClusterTransactionTests.java b/java/integTest/src/test/java/glide/cluster/ClusterTransactionTests.java index faf13c2ecd..4f5c2a7918 100644 --- a/java/integTest/src/test/java/glide/cluster/ClusterTransactionTests.java +++ b/java/integTest/src/test/java/glide/cluster/ClusterTransactionTests.java @@ -311,7 +311,7 @@ public void sort() { transaction.sortReadOnly(key1, SortOptions.builder().orderBy(DESC).build()); } - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { transaction .hset(key3, Map.of("name", "Alice", "age", "30")) .hset(key4, Map.of("name", "Bob", "age", "25")) @@ -358,7 +358,7 @@ public void sort() { ); } - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { expectedResult = concatenateArrays( expectedResult, diff --git a/java/integTest/src/test/java/glide/cluster/CommandTests.java b/java/integTest/src/test/java/glide/cluster/CommandTests.java index bd893a7693..cadd37a707 100644 --- a/java/integTest/src/test/java/glide/cluster/CommandTests.java +++ b/java/integTest/src/test/java/glide/cluster/CommandTests.java @@ -1055,7 +1055,7 @@ public void flushall() { assertEquals(OK, clusterClient.flushall(ASYNC, route).get()); var replicaRoute = new SlotKeyRoute("key", REPLICA); - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { // Since Valkey 8.0.0 flushall can run on replicas assertEquals(OK, clusterClient.flushall(route).get()); } else { @@ -1650,7 +1650,7 @@ public void fcall_binary_with_keys(String prefix) { public void fcall_readonly_function() { assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("7.0.0"), "This feature added in version 7"); assumeTrue( - !SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0"), + !SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0"), "Temporary disabeling this test on valkey 8"); String libName = "fcall_readonly_function"; @@ -1708,7 +1708,7 @@ public void fcall_readonly_function() { public void fcall_readonly_binary_function() { assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("7.0.0"), "This feature added in version 7"); assumeTrue( - !SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0"), + !SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0"), "Temporary disabeling this test on valkey 8"); String libName = "fcall_readonly_function"; diff --git a/java/integTest/src/test/java/glide/standalone/CommandTests.java b/java/integTest/src/test/java/glide/standalone/CommandTests.java index 34abf7861e..5e558a0273 100644 --- a/java/integTest/src/test/java/glide/standalone/CommandTests.java +++ b/java/integTest/src/test/java/glide/standalone/CommandTests.java @@ -1176,7 +1176,7 @@ public void scan() { assertDeepEquals(new String[] {}, emptyResult[resultCollectionIndex]); // Negative cursor - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { ExecutionException executionException = assertThrows(ExecutionException.class, () -> regularClient.scan("-1").get()); } else { @@ -1235,7 +1235,7 @@ public void scan_binary() { assertDeepEquals(new String[] {}, emptyResult[resultCollectionIndex]); // Negative cursor - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { ExecutionException executionException = assertThrows(ExecutionException.class, () -> regularClient.scan(gs("-1")).get()); } else { @@ -1303,7 +1303,7 @@ public void scan_with_options() { assertDeepEquals(new String[] {}, emptyResult[resultCollectionIndex]); // Negative cursor - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { final ScanOptions finalOptions = options; ExecutionException executionException = assertThrows( @@ -1392,7 +1392,7 @@ public void scan_binary_with_options() { assertDeepEquals(new String[] {}, emptyResult[resultCollectionIndex]); // Negative cursor - if (SERVER_VERSION.isGreaterThanOrEqualTo("7.9.0")) { + if (SERVER_VERSION.isGreaterThanOrEqualTo("8.0.0")) { final ScanOptions finalOptions = options; ExecutionException executionException = assertThrows( diff --git a/node/README.md b/node/README.md index 6d98b6a856..d215bc102f 100644 --- a/node/README.md +++ b/node/README.md @@ -6,16 +6,20 @@ Valkey General Language Independent Driver for the Enterprise (GLIDE), is an ope Refer to the [Supported Engine Versions table](https://github.com/valkey-io/valkey-glide/blob/main/README.md#supported-engine-versions) for details. -## Current Status - -We've made Valkey GLIDE an open-source project, and are releasing it in Preview to the community to gather feedback, and actively collaborate on the project roadmap. We welcome questions and contributions from all Redis stakeholders. -This preview release is recommended for testing purposes only. - # Getting Started - Node Wrapper ## System Requirements -In this release, Valkey GLIDE is available for Python and Java. Support for Node.js is actively under development, with plans to include more programming languages in the future. We're tracking future features on the [roadmap](https://github.com/orgs/aws/projects/187/). +The release of Valkey GLIDE was tested on the following platforms: + +Linux: + +- Ubuntu 22.04.1 (x86_64) +- Amazon Linux 2023 (AL2023) (x86_64) + +macOS: + +- macOS 12.7 (Apple silicon/aarch_64 and Intel/x86_64) ## NodeJS supported version @@ -29,6 +33,63 @@ Visit our [wiki](https://github.com/valkey-io/valkey-glide/wiki/NodeJS-wrapper) Development instructions for local building & testing the package are in the [DEVELOPER.md](https://github.com/valkey-io/valkey-glide/blob/main/node/DEVELOPER.md#build-from-source) file. +## Basic Examples + +#### Standalone Mode: + +```typescript +import { GlideClient, GlideClusterClient, Logger } from "@valkey/valkey-glide"; +// When Valkey is in standalone mode, add address of the primary node, and any replicas you'd like to be able to read from. +const addresses = [ + { + host: "localhost", + port: 6379, + }, +]; +// Check `GlideClientConfiguration/GlideClusterClientConfiguration` for additional options. +const client = await GlideClient.createClient({ + addresses: addresses, + // if the server uses TLS, you'll need to enable it. Otherwise, the connection attempt will time out silently. + // useTLS: true, + clientName: "test_standalone_client", +}); +// The empty array signifies that there are no additional arguments. +const pong = await client.customCommand(["PING"]); +console.log(pong); +const set_response = await client.set("foo", "bar"); +console.log(`Set response is = ${set_response}`); +const get_response = await client.get("foo"); +console.log(`Get response is = ${get_response}`); +``` + +#### Cluster Mode: + +```typescript +import { GlideClient, GlideClusterClient, Logger } from "@valkey/valkey-glide"; +// When Valkey is in cluster mode, add address of any nodes, and the client will find all nodes in the cluster. +const addresses = [ + { + host: "localhost", + port: 6379, + }, +]; +// Check `GlideClientConfiguration/GlideClusterClientConfiguration` for additional options. +const client = await GlideClusterClient.createClient({ + addresses: addresses, + // if the cluster nodes use TLS, you'll need to enable it. Otherwise the connection attempt will time out silently. + // useTLS: true, + clientName: "test_cluster_client", +}); +// The empty array signifies that there are no additional arguments. +const pong = await client.customCommand(["PING"], { route: "randomNode" }); +console.log(pong); +const set_response = await client.set("foo", "bar"); +console.log(`Set response is = ${set_response}`); +const get_response = await client.get("foo"); +console.log(`Get response is = ${get_response}`); +client.close(); +``` + ### Supported platforms Currentlly the package is supported on: diff --git a/node/npm/glide/index.ts b/node/npm/glide/index.ts index 546f3ebe83..98171bfef4 100644 --- a/node/npm/glide/index.ts +++ b/node/npm/glide/index.ts @@ -192,6 +192,16 @@ function initialize() { createLeakedMap, createLeakedString, parseInfoResponse, + Script, + ObjectType, + ClusterScanCursor, + BaseClientConfiguration, + GlideClusterClientConfiguration, + LevelOptions, + ReturnTypeRecord, + ReturnTypeMap, + ClusterResponse, + ReturnTypeAttribute, } = nativeBinding; module.exports = { @@ -305,6 +315,16 @@ function initialize() { createLeakedMap, createLeakedString, parseInfoResponse, + Script, + ObjectType, + ClusterScanCursor, + BaseClientConfiguration, + GlideClusterClientConfiguration, + LevelOptions, + ReturnTypeRecord, + ReturnTypeMap, + ClusterResponse, + ReturnTypeAttribute, }; globalObject = Object.assign(global, nativeBinding); diff --git a/node/npm/glide/package.json b/node/npm/glide/package.json index 14a274572c..9514160893 100644 --- a/node/npm/glide/package.json +++ b/node/npm/glide/package.json @@ -2,7 +2,7 @@ "name": "${scope}${pkg_name}", "types": "build-ts/index.d.ts", "version": "${package_version}", - "description": "An AWS-sponsored, open-source Redis client.", + "description": "General Language Independent Driver for the Enterprise (GLIDE) for Valkey", "main": "build-ts/index.js", "module": "build-ts/index.js", "type": "commonjs", @@ -26,7 +26,7 @@ "client", "valkey-glide" ], - "author": "Amazon Web Services", + "author": "Valkey GLIDE Maintainers", "license": "Apache-2.0", "bugs": { "url": "https://github.com/valkey-io/valkey-glide/issues" diff --git a/node/package.json b/node/package.json index ce6fdb9747..20dbeea80b 100644 --- a/node/package.json +++ b/node/package.json @@ -58,7 +58,7 @@ "typescript": "^5.5.4", "uuid": "^10.0.0" }, - "author": "Valkey contributors", + "author": "Valkey GLIDE Maintainers", "license": "Apache-2.0", "publishConfig": { "${registry_scope}registry": "https://registry.npmjs.org/", diff --git a/node/src/BaseClient.ts b/node/src/BaseClient.ts index cd2b500a97..6b09e30e98 100644 --- a/node/src/BaseClient.ts +++ b/node/src/BaseClient.ts @@ -5485,7 +5485,7 @@ export class BaseClient { * ``` */ public async xinfoGroups( - key: string, + key: GlideString, options?: DecoderOption, ): Promise[]> { return this.createWritePromise< diff --git a/node/src/Commands.ts b/node/src/Commands.ts index 21115b1706..b56e64db7b 100644 --- a/node/src/Commands.ts +++ b/node/src/Commands.ts @@ -2707,7 +2707,7 @@ export function createXInfoStream( } /** @internal */ -export function createXInfoGroups(key: string): command_request.Command { +export function createXInfoGroups(key: GlideString): command_request.Command { return createCommand(RequestType.XInfoGroups, [key]); } diff --git a/node/src/Logger.ts b/node/src/Logger.ts index f5f09d269d..df38c77994 100644 --- a/node/src/Logger.ts +++ b/node/src/Logger.ts @@ -12,7 +12,7 @@ const LEVEL = new Map([ ["trace", Level.Trace], [undefined, undefined], ]); -type LevelOptions = "error" | "warn" | "info" | "debug" | "trace"; +export type LevelOptions = "error" | "warn" | "info" | "debug" | "trace"; /* * A singleton class that allows logging which is consistent with logs from the internal rust core. diff --git a/node/src/Transaction.ts b/node/src/Transaction.ts index ac7400947e..bdccbe151f 100644 --- a/node/src/Transaction.ts +++ b/node/src/Transaction.ts @@ -1221,7 +1221,7 @@ export class BaseTransaction> { * Command Response - the number of the removed elements. * If `key` does not exist, 0 is returned. */ - public lrem(key: GlideString, count: number, element: string): T { + public lrem(key: GlideString, count: number, element: GlideString): T { return this.addAndReturn(createLRem(key, count, element)); } @@ -2620,7 +2620,7 @@ export class BaseTransaction> { * attributes of a consumer group for the stream at `key`. * The response comes in format `GlideRecord[]`, see {@link GlideRecord}. */ - public xinfoGroups(key: string): T { + public xinfoGroups(key: GlideString): T { return this.addAndReturn(createXInfoGroups(key)); } diff --git a/node/tests/PubSub.test.ts b/node/tests/PubSub.test.ts index 82aec7f826..5e8d40207a 100644 --- a/node/tests/PubSub.test.ts +++ b/node/tests/PubSub.test.ts @@ -4018,7 +4018,7 @@ describe("PubSub", () => { * * @param clusterMode - Indicates if the test should be run in cluster mode. */ - it.each([true, false])( + it.each([true])( "test pubsub numsub and shardnumsub separation_%p", async (clusterMode) => { //const clusterMode = false; @@ -4070,17 +4070,13 @@ describe("PubSub", () => { }); // Test pubsubShardnumsub - if (clusterMode) { - const shardSubscribers = await ( - client2 as GlideClusterClient - ).pubsubShardNumSub([regularChannel, shardChannel]); - expect( - convertGlideRecordToRecord(shardSubscribers), - ).toEqual({ - [regularChannel]: 0, - [shardChannel]: 2, - }); - } + const shardSubscribers = await ( + client2 as GlideClusterClient + ).pubsubShardNumSub([regularChannel, shardChannel]); + expect(convertGlideRecordToRecord(shardSubscribers)).toEqual({ + [regularChannel]: 0, + [shardChannel]: 2, + }); } finally { if (client1) { await clientCleanup(client1, pubSub!); diff --git a/node/tests/SharedTests.ts b/node/tests/SharedTests.ts index 23dd4c29f8..b55de76fac 100644 --- a/node/tests/SharedTests.ts +++ b/node/tests/SharedTests.ts @@ -1292,7 +1292,7 @@ export function runBaseTests(config: { it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])( `getrange test_%p`, async (protocol) => { - await runTest(async (client: BaseClient, cluster) => { + await runTest(async (client: BaseClient) => { const key = uuidv4(); const nonStringKey = uuidv4(); const valueEncoded = Buffer.from("This is a string"); @@ -1332,15 +1332,10 @@ export function runBaseTests(config: { // incorrect range expect(await client.getrange(key, -1, -3)).toEqual(""); - // a bug fixed in version 8: https://github.com/redis/redis/issues/13207 - expect(await client.getrange(key, -200, -100)).toEqual( - cluster.checkIfServerVersionLessThan("8.0.0") ? "T" : "", - ); + expect(await client.getrange(key, -200, -100)).toEqual("T"); // empty key (returning null isn't implemented) - expect(await client.getrange(nonStringKey, 0, -1)).toEqual( - cluster.checkIfServerVersionLessThan("8.0.0") ? "" : null, - ); + expect(await client.getrange(nonStringKey, 0, -1)).toEqual(""); // non-string key expect(await client.lpush(nonStringKey, ["_"])).toEqual(1); @@ -1599,7 +1594,7 @@ export function runBaseTests(config: { expect(result[resultCursorIndex]).not.toEqual(initialCursor); expect(result[resultCollectionIndex].length).toBeGreaterThan(0); - if (!cluster.checkIfServerVersionLessThan("7.9.0")) { + if (!cluster.checkIfServerVersionLessThan("8.0.0")) { const result = await client.hscan(key1, initialCursor, { noValues: true, }); @@ -1640,7 +1635,7 @@ export function runBaseTests(config: { expect(result2[resultCollectionIndex]).toEqual([]); // Negative cursor - if (cluster.checkIfServerVersionLessThan("7.9.0")) { + if (cluster.checkIfServerVersionLessThan("8.0.0")) { result = await client.hscan(key1, "-1"); expect(result[resultCursorIndex]).toEqual( initialCursor, @@ -4189,7 +4184,7 @@ export function runBaseTests(config: { `script show test_%p`, async (protocol) => { await runTest(async (client: BaseClient, cluster) => { - if (cluster.checkIfServerVersionLessThan("7.9.0")) { + if (cluster.checkIfServerVersionLessThan("8.0.0")) { return; } @@ -8916,7 +8911,7 @@ export function runBaseTests(config: { ).rejects.toThrow(RequestError); } - if (cluster.checkIfServerVersionLessThan("7.9.0")) { + if (cluster.checkIfServerVersionLessThan("8.0.0")) { await expect( client.bitcount(key1, { start: 2, @@ -9750,7 +9745,7 @@ export function runBaseTests(config: { expect(result[resultCollectionIndex]).toEqual([]); // Negative cursor - if (cluster.checkIfServerVersionLessThan("7.9.0")) { + if (cluster.checkIfServerVersionLessThan("8.0.0")) { result = await client.zscan(key1, "-1"); expect(result[resultCursorIndex]).toEqual( initialCursor, @@ -9864,7 +9859,7 @@ export function runBaseTests(config: { result[resultCollectionIndex].length, ).toBeGreaterThan(0); - if (!cluster.checkIfServerVersionLessThan("7.9.0")) { + if (!cluster.checkIfServerVersionLessThan("8.0.0")) { const result = await client.zscan(key1, initialCursor, { noScores: true, }); @@ -10752,7 +10747,7 @@ export function runBaseTests(config: { ).toEqual("OK"); // one empty group exists - expect(await client.xinfoGroups(key)).toEqual( + expect(await client.xinfoGroups(Buffer.from(key))).toEqual( cluster.checkIfServerVersionLessThan("7.0.0") ? [ { @@ -12074,7 +12069,7 @@ export function runBaseTests(config: { await runTest( async (client: BaseClient, cluster: ValkeyCluster) => { if ( - cluster.checkIfServerVersionLessThan("7.9.0") && + cluster.checkIfServerVersionLessThan("8.0.0") && client instanceof GlideClusterClient ) { return; diff --git a/node/tests/TestUtilities.ts b/node/tests/TestUtilities.ts index eabf061288..1a12cc3f2f 100644 --- a/node/tests/TestUtilities.ts +++ b/node/tests/TestUtilities.ts @@ -812,7 +812,7 @@ export async function transactionTest( baseTransaction.hscan(key4, "0"); responseData.push(['hscan(key4, "0")', ["0", [field, value]]]); - if (gte(version, "7.9.0")) { + if (gte(version, "8.0.0")) { baseTransaction.hscan(key4, "0", { noValues: false }); responseData.push([ 'hscan(key4, "0", {noValues: false})', @@ -1081,7 +1081,7 @@ export async function transactionTest( baseTransaction.zscan(key12, "0"); responseData.push(['zscan(key12, "0")', ["0", ["one", "1", "two", "2"]]]); - if (gte(version, "7.9.0")) { + if (gte(version, "8.0.0")) { baseTransaction.zscan(key12, "0", { noScores: false }); responseData.push([ 'zscan(key12, "0", {noScores: false})', @@ -1467,7 +1467,7 @@ export async function transactionTest( ]); } - if (gte(version, "7.9.0")) { + if (gte(version, "8.0.0")) { baseTransaction.set(key17, "foobar"); responseData.push(['set(key17, "foobar")', "OK"]); baseTransaction.bitcount(key17, { diff --git a/python/pyproject.toml b/python/pyproject.toml index e75b8f83d3..e25ffc3dc8 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -33,4 +33,4 @@ extend-ignore = ['E203'] target-version = ['py38', 'py39', 'py310', 'py311', 'py312'] [tool.mypy] -exclude = [ 'submodules' ] +exclude = [ 'submodules', 'utils/release-candidate-testing' ] diff --git a/python/python/glide/__init__.py b/python/python/glide/__init__.py index 50caeb5f4a..cf817c128a 100644 --- a/python/python/glide/__init__.py +++ b/python/python/glide/__init__.py @@ -17,7 +17,7 @@ SignedEncoding, UnsignedEncoding, ) -from glide.async_commands.command_args import Limit, ListDirection, OrderBy +from glide.async_commands.command_args import Limit, ListDirection, ObjectType, OrderBy from glide.async_commands.core import ( ConditionalChange, CoreCommands, @@ -64,10 +64,13 @@ TrimByMaxLen, TrimByMinId, ) -from glide.async_commands.transaction import ClusterTransaction, Transaction +from glide.async_commands.transaction import ( + ClusterTransaction, + Transaction, + TTransaction, +) from glide.config import ( BackoffStrategy, - BaseClientConfiguration, GlideClientConfiguration, GlideClusterClientConfiguration, NodeAddress, @@ -77,7 +80,19 @@ ReadFrom, ServerCredentials, ) -from glide.constants import OK +from glide.constants import ( + OK, + TOK, + TClusterResponse, + TEncodable, + TFunctionListResponse, + TFunctionStatsFullResponse, + TFunctionStatsSingleNodeResponse, + TResult, + TSingleNodeRoute, + TXInfoStreamFullResponse, + TXInfoStreamResponse, +) from glide.exceptions import ( ClosingError, ConfigurationError, @@ -87,7 +102,7 @@ RequestError, TimeoutError, ) -from glide.glide_client import GlideClient, GlideClusterClient +from glide.glide_client import GlideClient, GlideClusterClient, TGlideClient from glide.logger import Level as LogLevel from glide.logger import Logger from glide.routes import ( @@ -95,6 +110,7 @@ AllPrimaries, ByAddressRoute, RandomNode, + Route, SlotIdRoute, SlotKeyRoute, SlotType, @@ -110,8 +126,9 @@ "GlideClusterClient", "Transaction", "ClusterTransaction", + "TGlideClient", + "TTransaction", # Config - "BaseClientConfiguration", "GlideClientConfiguration", "GlideClusterClientConfiguration", "BackoffStrategy", @@ -123,6 +140,15 @@ "PeriodicChecksStatus", # Response "OK", + "TClusterResponse", + "TEncodable", + "TFunctionListResponse", + "TFunctionStatsFullResponse", + "TFunctionStatsSingleNodeResponse", + "TOK", + "TResult", + "TXInfoStreamFullResponse", + "TXInfoStreamResponse", # Commands "BitEncoding", "BitFieldGet", @@ -166,6 +192,7 @@ "RangeByLex", "RangeByScore", "ScoreFilter", + "ObjectType", "OrderBy", "ExclusiveIdBound", "IdBound", @@ -189,6 +216,7 @@ "Logger", "LogLevel", # Routes + "Route", "SlotType", "AllNodes", "AllPrimaries", @@ -196,6 +224,7 @@ "RandomNode", "SlotKeyRoute", "SlotIdRoute", + "TSingleNodeRoute", # Exceptions "ClosingError", "ConfigurationError", diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 7febd0e2c9..e9897c7b2d 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -539,14 +539,9 @@ async def test_getrange(self, glide_client: TGlideClient): # incorrect range assert await glide_client.getrange(key, -1, -3) == b"" - # a redis bug, fixed in version 8: https://github.com/redis/redis/issues/13207 - if await check_if_server_version_lt(glide_client, "8.0.0"): - assert await glide_client.getrange(key, -200, -100) == value[0].encode() - else: - assert await glide_client.getrange(key, -200, -100) == b"" + assert await glide_client.getrange(key, -200, -100) == value[0].encode() - if await check_if_server_version_lt(glide_client, "8.0.0"): - assert await glide_client.getrange(non_string_key, 0, -1) == b"" + assert await glide_client.getrange(non_string_key, 0, -1) == b"" # non-string key assert await glide_client.lpush(non_string_key, ["_"]) == 1 @@ -4720,7 +4715,7 @@ async def test_sort_and_sort_store_with_get_or_by_args( ): if isinstance( glide_client, GlideClusterClient - ) and await check_if_server_version_lt(glide_client, "7.9.0"): + ) and await check_if_server_version_lt(glide_client, "8.0.0"): return pytest.mark.skip( reason=f"Valkey version required in cluster mode>= 8.0.0" ) @@ -7133,7 +7128,7 @@ 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"): + if await check_if_server_version_lt(glide_client, "8.0.0"): # exception thrown optional end was implemented after 8.0.0 with pytest.raises(RequestError): await glide_client.bitcount( @@ -9835,7 +9830,7 @@ async def test_sscan(self, glide_client: GlideClusterClient): assert result[result_collection_index] == [] # Negative cursor - if await check_if_server_version_lt(glide_client, "7.9.0"): + if await check_if_server_version_lt(glide_client, "8.0.0"): result = await glide_client.sscan(key1, "-1") assert result[result_cursor_index] == initial_cursor.encode() assert result[result_collection_index] == [] @@ -9949,7 +9944,7 @@ async def test_zscan(self, glide_client: GlideClusterClient): assert result[result_collection_index] == [] # Negative cursor - if await check_if_server_version_lt(glide_client, "7.9.0"): + if await check_if_server_version_lt(glide_client, "8.0.0"): result = await glide_client.zscan(key1, "-1") assert result[result_cursor_index] == initial_cursor.encode() assert result[result_collection_index] == [] @@ -10025,7 +10020,7 @@ async def test_zscan(self, glide_client: GlideClusterClient): assert len(result[result_collection_index]) >= 0 # Test no_scores option - if not await check_if_server_version_lt(glide_client, "7.9.0"): + if not await check_if_server_version_lt(glide_client, "8.0.0"): result = await glide_client.zscan(key1, initial_cursor, no_scores=True) assert result[result_cursor_index] != b"0" values_array = cast(List[bytes], result[result_collection_index]) @@ -10076,7 +10071,7 @@ async def test_hscan(self, glide_client: GlideClusterClient): assert result[result_collection_index] == [] # Negative cursor - if await check_if_server_version_lt(glide_client, "7.9.0"): + if await check_if_server_version_lt(glide_client, "8.0.0"): result = await glide_client.hscan(key1, "-1") assert result[result_cursor_index] == initial_cursor.encode() assert result[result_collection_index] == [] @@ -10152,7 +10147,7 @@ async def test_hscan(self, glide_client: GlideClusterClient): assert len(result[result_collection_index]) >= 0 # Test no_values option - if not await check_if_server_version_lt(glide_client, "7.9.0"): + if not await check_if_server_version_lt(glide_client, "8.0.0"): result = await glide_client.hscan(key1, initial_cursor, no_values=True) assert result[result_cursor_index] != b"0" values_array = cast(List[bytes], result[result_collection_index]) @@ -10487,7 +10482,7 @@ async def attempt_kill_writing_script(): @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_script_show(self, glide_client: TGlideClient): - min_version = "7.9.0" + min_version = "8.0.0" if await check_if_server_version_lt(glide_client, min_version): return pytest.mark.skip(reason=f"Valkey version required >= {min_version}") diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index 526c70681e..dadef84200 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -308,7 +308,7 @@ async def transaction_test( args.append([b"0", [key3.encode(), b"10.5"]]) transaction.hscan(key4, "0", match="*", count=10) args.append([b"0", [key3.encode(), b"10.5"]]) - if not await check_if_server_version_lt(glide_client, "7.9.0"): + if not await check_if_server_version_lt(glide_client, "8.0.0"): transaction.hscan(key4, "0", match="*", count=10, no_values=True) args.append([b"0", [key3.encode()]]) transaction.hrandfield(key4) @@ -463,7 +463,7 @@ async def transaction_test( args.append([b"0", [b"three", b"3"]]) transaction.zscan(key8, "0", match="*", count=20) args.append([b"0", [b"three", b"3"]]) - if not await check_if_server_version_lt(glide_client, "7.9.0"): + if not await check_if_server_version_lt(glide_client, "8.0.0"): transaction.zscan(key8, "0", match="*", count=20, no_scores=True) args.append([b"0", [b"three"]]) transaction.zpopmax(key8) @@ -561,7 +561,7 @@ 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"): + if not await check_if_server_version_lt(glide_client, "8.0.0"): transaction.set(key20, "foobar") args.append(OK) transaction.bitcount(key20, OffsetOptions(0)) @@ -720,7 +720,7 @@ async def transaction_test( alpha=True, ) args.append(4) - if not await check_if_server_version_lt(glide_client, "7.9.0"): + if not await check_if_server_version_lt(glide_client, "8.0.0"): transaction.hset(f"{{{keyslot}}}:1", {"name": "Alice", "age": "30"}) args.append(2) transaction.hset(f"{{{keyslot}}}:2", {"name": "Bob", "age": "25"}) diff --git a/submodules/redis-rs b/submodules/redis-rs index 426bb99d0e..8e89ad8f3d 160000 --- a/submodules/redis-rs +++ b/submodules/redis-rs @@ -1 +1 @@ -Subproject commit 426bb99d0e2d02a3fcb35c28d8d8dd6beae252ef +Subproject commit 8e89ad8f3d4a872ceb9bd87027e55ea7f1477b83 diff --git a/utils/.gitignore b/utils/.gitignore new file mode 100644 index 0000000000..d10b2296e7 --- /dev/null +++ b/utils/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +servers/ diff --git a/utils/release-candidate-testing/node/index.js b/utils/release-candidate-testing/node/index.js index 257f40ea05..be55f97cc4 100644 --- a/utils/release-candidate-testing/node/index.js +++ b/utils/release-candidate-testing/node/index.js @@ -1,5 +1,8 @@ -import { RedisClient, RedisClusterClient } from "@valkey/valkey-glide"; -import { RedisCluster } from "../../TestUtils.js"; +/** + * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + */ +import { GlideClient, GlideClusterClient } from "@valkey/valkey-glide"; +import { ValkeyCluster } from "../../TestUtils.js"; async function runCommands(client) { @@ -62,24 +65,30 @@ async function closeClientAndCluster(client, Cluster) { console.log("Clusters closed"); } +async function getServerVersion(addresses, clusterMode) { + // General version for those tests + return "255.255.255"; +} + async function clusterTests() { try { console.log("Testing cluster"); console.log("Creating cluster"); - let redisCluster = await RedisCluster.createCluster(true, + let valkeyCluster = await ValkeyCluster.createCluster(true, 3, 1, + getServerVersion, ); console.log("Cluster created"); console.log("Connecting to cluster"); - let addresses = redisCluster.getAddresses().map((address) => { return { host: address[0], port: address[1] } }); - const client = await RedisClusterClient.createClient({ addresses: addresses }); + let addresses = valkeyCluster.getAddresses().map((address) => { return { host: address[0], port: address[1] } }); + const client = await GlideClusterClient.createClient({ addresses: addresses }); console.log("Connected to cluster"); await runCommands(client); - await closeClientAndCluster(client, redisCluster); + await closeClientAndCluster(client, valkeyCluster); console.log("Done"); } catch (error) { // Need this part just when running in our self-hosted runner, so if the test fails before closing Clusters we still kill them and clean up @@ -96,18 +105,19 @@ async function standaloneTests() { try { console.log("Testing standalone Cluster") console.log("Creating Cluster"); - let redisCluster = await RedisCluster.createCluster(false, + let valkeyCluster = await ValkeyCluster.createCluster(false, 1, 1, + getServerVersion, ); console.log("Cluster created"); console.log("Connecting to Cluster"); - let addresses = redisCluster.getAddresses().map((address) => { return { host: address[0], port: address[1] } }); - const client = await RedisClient.createClient({ addresses: addresses }); + let addresses = valkeyCluster.getAddresses().map((address) => { return { host: address[0], port: address[1] } }); + const client = await GlideClient.createClient({ addresses: addresses }); console.log("Connected to Cluster"); - await closeClientAndCluster(client, redisCluster); + await closeClientAndCluster(client, valkeyCluster); console.log("Done"); } catch (error) { diff --git a/utils/release-candidate-testing/python/__init__.py b/utils/release-candidate-testing/python/__init__.py new file mode 100644 index 0000000000..fa59791e66 --- /dev/null +++ b/utils/release-candidate-testing/python/__init__.py @@ -0,0 +1 @@ +# Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 diff --git a/utils/release-candidate-testing/python/rc_test.py b/utils/release-candidate-testing/python/rc_test.py new file mode 100644 index 0000000000..669b303be7 --- /dev/null +++ b/utils/release-candidate-testing/python/rc_test.py @@ -0,0 +1,208 @@ +# Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +import asyncio +from typing import List, Tuple +import os +import subprocess +import sys + +SCRIPT_FILE = os.path.abspath(f"{__file__}/../../../cluster_manager.py") + +from glide import ( + GlideClusterClient, + GlideClusterClientConfiguration, + NodeAddress, + GlideClient, + GlideClientConfiguration, +) + + +def start_servers(cluster_mode: bool, shard_count: int, replica_count: int) -> str: + args_list: List[str] = [sys.executable, SCRIPT_FILE] + args_list.append("start") + if cluster_mode: + args_list.append("--cluster-mode") + args_list.append(f"-n {shard_count}") + args_list.append(f"-r {replica_count}") + p = subprocess.Popen( + args_list, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + output, err = p.communicate(timeout=40) + if p.returncode != 0: + raise Exception(f"Failed to create a cluster. Executed: {p}:\n{err}") + print("Servers started successfully") + return output + + +def parse_cluster_script_start_output(output: str) -> Tuple[List[NodeAddress], str]: + assert "CLUSTER_FOLDER" in output and "CLUSTER_NODES" in output + lines_output: List[str] = output.splitlines() + cluster_folder: str = "" + nodes_addr: List[NodeAddress] = [] + for line in lines_output: + if "CLUSTER_FOLDER" in line: + splitted_line = line.split("CLUSTER_FOLDER=") + assert len(splitted_line) == 2 + cluster_folder = splitted_line[1] + if "CLUSTER_NODES" in line: + nodes_list: List[NodeAddress] = [] + splitted_line = line.split("CLUSTER_NODES=") + assert len(splitted_line) == 2 + nodes_addresses = splitted_line[1].split(",") + assert len(nodes_addresses) > 0 + for addr in nodes_addresses: + host, port = addr.split(":") + nodes_list.append(NodeAddress(host, int(port))) + nodes_addr = nodes_list + print("Cluster script output parsed successfully") + return nodes_addr, cluster_folder + + +def stop_servers(folder: str) -> str: + args_list: List[str] = [sys.executable, SCRIPT_FILE] + args_list.extend(["stop", "--cluster-folder", folder]) + p = subprocess.Popen( + args_list, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + output, err = p.communicate(timeout=40) + if p.returncode != 0: + raise Exception(f"Failed to stop the cluster. Executed: {p}:\n{err}") + print("Servers stopped successfully") + return output + + +async def run_commands(client: GlideClient) -> None: + print("Executing commands") + # Set a bunch of keys + for i in range(100): + res = await client.set(f"foo{i}".encode(), b"bar") + if res != "OK": + print(res) + raise Exception(f"Unexpected set response, expected 'OK', got {res}") + print("Keys set successfully") + # Get the keys + for i in range(10): + val = await client.get(f"foo{i}") + if val != b"bar": + print(val) + raise Exception(f"Unexpected value, expected b'bar', got {val}") + print("Keys retrieved successfully") + # Run some various commands + pong = await client.ping() + if pong != b"PONG": + print(pong) + raise Exception(f"Unexpected ping response, expected b'PONG', got {pong}") + print(f"Ping successful: {pong}") + # Set a bunch of keys to delete + array_of_keys: List[bytes] = [f"foo{i}".encode() for i in range(1, 4)] + # delete the keys + deleted_keys_num = await client.delete(array_of_keys) + print(f"Deleted keys: {deleted_keys_num}") + # check that the correct number of keys were deleted + if deleted_keys_num != 3: + print(deleted_keys_num) + raise Exception( + f"Unexpected number of keys deleted, expected 3, got {deleted_keys_num}" + ) + # check that the keys were deleted + for i in range(1, 4): + val = await client.get(f"foo{i}") + if val is not None: + print(val) + raise Exception(f"Unexpected value, expected None, got {val}") + print("Keys deleted successfully") + + # Test INCR command + incr_key = b"counter" + await client.set(incr_key, b"0") + incr_result = await client.incr(incr_key) + if incr_result != 1: + raise Exception(f"Unexpected INCR result, expected 1, got {incr_result}") + print("INCR command successful") + + # Test LPUSH and LRANGE commands + list_key = b"mylist" + await client.lpush(list_key, [b"world", b"hello"]) + list_values = await client.lrange(list_key, 0, -1) + if list_values != [b"hello", b"world"]: + raise Exception( + f"Unexpected LRANGE result, expected [b'hello', b'world'], got {list_values}" + ) + print("LPUSH and LRANGE commands successful") + + # Test HSET and HGETALL commands + hash_key = b"myhash" + await client.hset(hash_key, {b"field1": b"value1", b"field2": b"value2"}) + hash_values = await client.hgetall(hash_key) + if hash_values != {b"field1": b"value1", b"field2": b"value2"}: + raise Exception( + f"Unexpected HGETALL result, expected {{b'field1': b'value1', b'field2': b'value2'}}, got {hash_values}" + ) + print("HSET and HGETALL commands successful") + + print("All commands executed successfully") + + +async def create_cluster_client( + nodes_list: List[NodeAddress] = [("localhost", 6379)] +) -> GlideClusterClient: + addresses: List[NodeAddress] = nodes_list + config = GlideClusterClientConfiguration( + addresses=addresses, + client_name="test_cluster_client", + ) + client = await GlideClusterClient.create(config) + print("Cluster client created successfully") + return client + + +async def create_standalone_client(server: List[NodeAddress]) -> GlideClient: + config = GlideClientConfiguration( + addresses=server, + client_name="test_standalone_client", + ) + client = await GlideClient.create(config) + print("Standalone client created successfully") + return client + + +async def test_standalone_client() -> None: + print("Testing standalone client") + output = start_servers(False, 1, 1) + servers, folder = parse_cluster_script_start_output(output) + standalone_client = await create_standalone_client(servers) + await run_commands(standalone_client) + stop_servers(folder) + print("Standalone client test completed") + + +async def test_cluster_client() -> None: + print("Testing cluster client") + output = start_servers(True, 3, 1) + servers, folder = parse_cluster_script_start_output(output) + cluster_client = await create_cluster_client(servers) + await run_commands(cluster_client) + stop_servers(folder) + print("Cluster client test completed") + + +async def test_clients() -> None: + await test_cluster_client() + print("Cluster client test passed") + await test_standalone_client() + print("Standalone client test passed") + + +def main() -> None: + asyncio.run(test_clients()) + print("All tests completed successfully") + + +if __name__ == "__main__": + main() diff --git a/utils/release-candidate-testing/python/requirements.txt b/utils/release-candidate-testing/python/requirements.txt new file mode 100644 index 0000000000..cac8dc5db0 --- /dev/null +++ b/utils/release-candidate-testing/python/requirements.txt @@ -0,0 +1 @@ +valkey-glide