diff --git a/.github/wordlist.txt b/.github/wordlist.txt index ad57186af9..b4dbd628a8 100644 --- a/.github/wordlist.txt +++ b/.github/wordlist.txt @@ -272,4 +272,9 @@ bom ubuntu behaviour databind -jackson \ No newline at end of file +jackson +ACR +AMR +Entra +authx +entraid \ No newline at end of file diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 8010acf332..19b38e8915 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -41,25 +41,24 @@ jobs: - name: Install missing dependencies to container run: | sudo apt update - sudo apt install -y stunnel make git gcc - - name: Maven offline - run: | - mvn -q dependency:go-offline - - name: Clean environment + - name: Set up Docker Compose environment run: | - make cleanup + mkdir -m 777 $REDIS_ENV_WORK_DIR + make docker-start env: - JVM_OPTS: -Xmx3200m - TERM: dumb - - name: Start servers + REDIS_ENV_WORK_DIR: ${{ github.workspace }}/work + - name: Maven offline run: | - make start + mvn -q dependency:go-offline - name: Run benchmarks run: | mvn -Pjmh clean test env: JVM_OPTS: -Xmx3200m TERM: dumb + - name: Tear down Docker Compose environment + run: | + docker compose $COMPOSE_ENV_FILES -f src/test/resources/docker-env/docker-compose.yml down # Download previous benchmark result from cache (if exists) - name: Download previous benchmark data uses: actions/cache@v4 diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index a065c23210..f4ebe1dc90 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -24,10 +24,11 @@ jobs: fail-fast: false matrix: redis_version: - - "unstable" - "8.0" - "7.4" - "7.2" + env: + REDIS_ENV_WORK_DIR: ${{ github.workspace }}/work steps: - name: Test Redis Server Version @@ -35,15 +36,15 @@ jobs: run: | # Map requested version to github or tag case "${{ matrix.redis_version }}" in - "unstable") redis_branch="unstable" stack_version="8.0-M04-pre" ;; - "8.0") redis_branch="8.0" stack_version="8.0-M04-pre" ;; - "7.4") redis_branch="7.4" stack_version="rs-7.4.0-v2" ;; - "7.2") redis_branch="7.2" stack_version="rs-7.2.0-v14" ;; + "8.0") redis_branch="8.0" stack_version="8.0-M04-pre" redis_test_version="8.0-M04-pre";; + "7.4") redis_branch="7.4" stack_version="rs-7.4.0-v2" redis_test_version="7.4.2";; + "7.2") redis_branch="7.2" stack_version="rs-7.2.0-v14" redis_test_version="7.2.7";; *) echo "Unsupported version: ${{ matrix.redis_version }}" && exit 1 ;; esac # Save them as outputs for later use echo "redis_branch=$redis_branch" >> $GITHUB_OUTPUT echo "redis_stack_version=$stack_version" >> $GITHUB_OUTPUT + echo "redis_test_version=$redis_test_version" >> $GITHUB_OUTPUT - name: Checkout project uses: actions/checkout@v4 - name: Set Java up in the runner @@ -59,24 +60,28 @@ jobs: - name: Install missing dependencies to container run: | sudo apt update - sudo apt install -y stunnel make git gcc + - name: Set up Docker Compose environment + run: | + mkdir -m 777 $REDIS_ENV_WORK_DIR + export REDIS_VERSION="${{ steps.map-tags.outputs.redis_test_version }}" + make docker-start version=$REDIS_VERSION - name: Maven offline run: | mvn -q dependency:go-offline - - name: Clean environment - run: | - make cleanup - env: - JVM_OPTS: -Xmx3200m - TERM: dumb + continue-on-error: true - name: Run tests run: | + export TEST_WORK_FOLDER=$REDIS_ENV_WORK_DIR + echo $TEST_WORK_FOLDER + ls -la $TEST_WORK_FOLDER make test-coverage env: - REDIS: ${{ steps.map-tags.outputs.redis_branch }} REDIS_STACK_VERSION: ${{ steps.map-tags.outputs.redis_stack_version }} JVM_OPTS: -Xmx3200m TERM: dumb + - name: Tear down Docker Compose environment + run: | + docker compose $COMPOSE_ENV_FILES -f src/test/resources/docker-env/docker-compose.yml down - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 with: diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml index c7f091def9..de20973f4b 100644 --- a/.github/workflows/stale-issues.yml +++ b/.github/workflows/stale-issues.yml @@ -12,15 +12,15 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/stale@v3 + - uses: actions/stale@v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 30 days this issue will be closed.' stale-pr-message: 'Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.' - days-before-stale: 365 - days-before-close: 30 + days-before-stale: 30 + days-before-close: 14 stale-issue-label: "status: feedback-reminder" stale-pr-label: "status: feedback-reminder" - operations-per-run: 10 - remove-stale-when-updated: false + operations-per-run: 30 + remove-stale-when-updated: true only-labels: "status: waiting-for-feedback" diff --git a/.gitignore b/.gitignore index 76cfba0e03..106e035520 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ work/ dependency-reduced-pom.xml .idea .flattened-pom.xml +*.java-version +*.DS_Store \ No newline at end of file diff --git a/Makefile b/Makefile index f288f218da..ec7f088406 100644 --- a/Makefile +++ b/Makefile @@ -1,459 +1,48 @@ SHELL := /bin/bash PATH := ./work/redis-git/src:${PATH} ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) -STUNNEL_BIN := $(shell which stunnel) -BREW_BIN := $(shell which brew) -YUM_BIN := $(shell which yum) -APT_BIN := $(shell which apt-get) PROFILE ?= ci -REDIS ?= unstable - -define REDIS_CLUSTER_CONFIG1 -c2043458aa5646cee429fdd5e3c18220dddf2ce5 127.0.0.1:7380 master - 1434887920102 1434887920002 0 connected 12000-16383 -27f88788f03a86296b7d860152f4ae24ee59c8c9 127.0.0.1:7379 myself,master - 0 0 1 connected 0-11999 -2c07344ffa94ede5ea57a2367f190af6144c1adb 127.0.0.1:7382 slave c2043458aa5646cee429fdd5e3c18220dddf2ce5 1434887920102 1434887920002 2 connected -1c541b6daf98719769e6aacf338a7d81f108a180 127.0.0.1:7381 slave 27f88788f03a86296b7d860152f4ae24ee59c8c9 1434887920102 1434887920002 3 connected -vars currentEpoch 3 lastVoteEpoch 0 -endef - -define REDIS_CLUSTER_CONFIG2 -2c07344ffa94ede5ea57a2367f190af6144c1adb 127.0.0.1:7382 slave c2043458aa5646cee429fdd5e3c18220dddf2ce5 1434887920102 1434887920002 2 connected -27f88788f03a86296b7d860152f4ae24ee59c8c9 127.0.0.1:7379 master - 1434887920102 1434887920002 1 connected 0-11999 -1c541b6daf98719769e6aacf338a7d81f108a180 127.0.0.1:7381 slave 27f88788f03a86296b7d860152f4ae24ee59c8c9 1434887920102 1434887920002 3 connected -c2043458aa5646cee429fdd5e3c18220dddf2ce5 127.0.0.1:7380 myself,master - 0 0 0 connected 12000-16383 -vars currentEpoch 3 lastVoteEpoch 0 -endef - -define REDIS_CLUSTER_CONFIG3 -1c541b6daf98719769e6aacf338a7d81f108a180 127.0.0.1:7381 myself,slave 27f88788f03a86296b7d860152f4ae24ee59c8c9 0 0 3 connected -2c07344ffa94ede5ea57a2367f190af6144c1adb 127.0.0.1:7382 slave c2043458aa5646cee429fdd5e3c18220dddf2ce5 1434887920102 1434887920002 2 connected -c2043458aa5646cee429fdd5e3c18220dddf2ce5 127.0.0.1:7380 master - 1434887920102 1434887920002 0 connected 12000-16383 -27f88788f03a86296b7d860152f4ae24ee59c8c9 127.0.0.1:7379 master - 1434887920102 1434887920002 1 connected 0-11999 -vars currentEpoch 3 lastVoteEpoch 0 -endef - -define REDIS_CLUSTER_CONFIG4 -c2043458aa5646cee429fdd5e3c18220dddf2ce5 127.0.0.1:7380 master - 0 1434887920102 0 connected 12000-16383 -1c541b6daf98719769e6aacf338a7d81f108a180 127.0.0.1:7381 slave 27f88788f03a86296b7d860152f4ae24ee59c8c9 0 1434887920102 3 connected -2c07344ffa94ede5ea57a2367f190af6144c1adb 127.0.0.1:7382 myself,slave c2043458aa5646cee429fdd5e3c18220dddf2ce5 0 0 2 connected -27f88788f03a86296b7d860152f4ae24ee59c8c9 127.0.0.1:7379 master - 0 1434887920102 1 connected 0-11999 -vars currentEpoch 3 lastVoteEpoch 0 -endef - -define REDIS_CLUSTER_CONFIG8 -c2043458aa5646cee429fdd5e3c18220dddf2ce5 127.0.0.1:7580 master - 1434887920102 1434887920002 0 connected 10001-16383 -27f88788f03a86296b7d860152f4ae24ee59c8c9 127.0.0.1:7579 myself,master - 0 0 1 connected 0-10000 -2c07344ffa94ede5ea57a2367f190af6144c1adb 127.0.0.1:7582 slave c2043458aa5646cee429fdd5e3c18220dddf2ce5 1434887920102 1434887920002 2 connected -1c541b6daf98719769e6aacf338a7d81f108a180 127.0.0.1:7581 slave 27f88788f03a86296b7d860152f4ae24ee59c8c9 1434887920102 1434887920002 3 connected -vars currentEpoch 3 lastVoteEpoch 0 -endef - -define REDIS_CLUSTER_CONFIG_SSL_1 -cf2354ef19ee813a962350b51438314aebce1fe2 127.0.0.1:7479@17479 myself,master - 0 1578163609000 0 connected 0-10000 -cac8e053dd6f85fab470be57d29dcbac2a4b85c4 127.0.0.1:7480@17480 slave cf2354ef19ee813a962350b51438314aebce1fe2 0 1578163609301 1 connected -6554e5b1b158dccd4b1d9ca294a3e46a2d3e556d 127.0.0.1:7481@17481 master - 0 1578163609301 2 connected 10001-16383 -vars currentEpoch 2 lastVoteEpoch 0 -endef - -define REDIS_CLUSTER_CONFIG_SSL_2 -cf2354ef19ee813a962350b51438314aebce1fe2 127.0.0.1:7479@17479 master - 0 1578163609245 0 connected 0-10000 -cac8e053dd6f85fab470be57d29dcbac2a4b85c4 127.0.0.1:7480@17480 myself,slave cf2354ef19ee813a962350b51438314aebce1fe2 0 1578163609000 1 connected -6554e5b1b158dccd4b1d9ca294a3e46a2d3e556d 127.0.0.1:7481@17481 master - 0 1578163609245 2 connected 10001-16383 -vars currentEpoch 2 lastVoteEpoch 0 -endef - -define REDIS_CLUSTER_CONFIG_SSL_3 -cac8e053dd6f85fab470be57d29dcbac2a4b85c4 127.0.0.1:7480@17480 slave cf2354ef19ee813a962350b51438314aebce1fe2 0 1578163609279 1 connected -cf2354ef19ee813a962350b51438314aebce1fe2 127.0.0.1:7479@17479 master - 0 1578163609279 0 connected 0-10000 -6554e5b1b158dccd4b1d9ca294a3e46a2d3e556d 127.0.0.1:7481@17481 myself,master - 0 1578163609000 2 connected 10001-16383 -vars currentEpoch 2 lastVoteEpoch 0 -endef - - -####### -# Redis -####### -.PRECIOUS: work/redis-%.conf - -# Sentinel monitored slave -work/redis-6483.conf: - @mkdir -p $(@D) - - @echo port 6483 >> $@ - @echo daemonize yes >> $@ - @echo pidfile $(shell pwd)/work/redis-6483.pid >> $@ - @echo logfile $(shell pwd)/work/redis-6483.log >> $@ - @echo save \"\" >> $@ - @echo appendonly no >> $@ - @echo client-output-buffer-limit pubsub 256k 128k 5 >> $@ - @echo unixsocket $(ROOT_DIR)/work/socket-6483 >> $@ - @echo unixsocketperm 777 >> $@ - @echo enable-debug-command yes >> $@ -ifeq ($(REDIS),unstable) - @echo slaveof localhost 6482 >> $@ - @echo replica-announce-ip localhost >> $@ -else - @echo slaveof 127.0.0.1 6482 >> $@ -endif - - -work/redis-%.conf: - @mkdir -p $(@D) - - @echo port $* >> $@ - @echo daemonize yes >> $@ - @echo pidfile $(shell pwd)/work/redis-$*.pid >> $@ - @echo logfile $(shell pwd)/work/redis-$*.log >> $@ - @echo save \"\" >> $@ - @echo appendonly no >> $@ - @echo client-output-buffer-limit pubsub 256k 128k 5 >> $@ - @echo unixsocket $(ROOT_DIR)/work/socket-$* >> $@ - @echo unixsocketperm 777 >> $@ - @echo enable-debug-command yes >> $@ -ifeq ($(REDIS),unstable) - @echo replica-announce-ip localhost >> $@ -endif - -work/redis-%.pid: work/redis-%.conf work/redis-git/src/redis-server - work/redis-git/src/redis-server $< - -redis-start: work/redis-6479.pid work/redis-6480.pid work/redis-6481.pid work/redis-6482.pid work/redis-6483.pid work/redis-6484.pid - -########## -# Sentinel -########## -.PRECIOUS: work/sentinel-%.conf - -work/sentinel-%.conf: - @mkdir -p $(@D) - - @echo port $* >> $@ - @echo daemonize yes >> $@ - @echo pidfile $(shell pwd)/work/redis-sentinel-$*.pid >> $@ - @echo logfile $(shell pwd)/work/redis-sentinel-$*.log >> $@ - -ifeq ($(REDIS),unstable) - @echo sentinel monitor mymaster localhost 6482 1 >> $@ - @echo sentinel announce-hostnames yes >> $@ - @echo sentinel resolve-hostnames yes >> $@ - @echo sentinel announce-ip localhost >> $@ -else - @echo sentinel monitor mymaster 127.0.0.1 6482 1 >> $@ -endif - @echo sentinel down-after-milliseconds mymaster 200 >> $@ - @echo sentinel failover-timeout mymaster 200 >> $@ - @echo sentinel parallel-syncs mymaster 1 >> $@ - @echo unixsocket $(ROOT_DIR)/work/socket-$* >> $@ - @echo unixsocketperm 777 >> $@ - -work/sentinel-26381.conf: - @mkdir -p $(@D) - - @echo port 26381 >> $@ - @echo daemonize yes >> $@ - @echo pidfile $(shell pwd)/work/redis-sentinel-26381.pid >> $@ - @echo logfile $(shell pwd)/work/redis-sentinel-26381.log >> $@ - -ifeq ($(REDIS),unstable) - @echo sentinel monitor mymaster localhost 6484 1 >> $@ - @echo sentinel announce-hostnames yes >> $@ - @echo sentinel resolve-hostnames yes >> $@ -else - @echo sentinel monitor mymaster 127.0.0.1 6484 1 >> $@ -endif - @echo sentinel down-after-milliseconds mymaster 200 >> $@ - @echo sentinel failover-timeout mymaster 200 >> $@ - @echo sentinel parallel-syncs mymaster 1 >> $@ - @echo unixsocket $(ROOT_DIR)/work/socket-$* >> $@ - @echo unixsocketperm 777 >> $@ - @echo requirepass foobared >> $@ - -work/sentinel-%.pid: work/sentinel-%.conf work/redis-git/src/redis-server - work/redis-git/src/redis-server $< --sentinel - sleep 0.5 - -sentinel-start: work/sentinel-26379.pid work/sentinel-26380.pid work/sentinel-26381.pid - -########## -# Cluster -########## -.PRECIOUS: work/cluster-node-%.conf - -work/cluster-node-7385.conf: - @mkdir -p $(@D) - - @echo port 7385 >> $@ - @echo daemonize yes >> $@ - @echo pidfile $(shell pwd)/work/cluster-node-7385.pid >> $@ - @echo logfile $(shell pwd)/work/cluster-node-7385.log >> $@ - @echo save \"\" >> $@ - @echo appendonly no >> $@ - @echo unixsocket $(ROOT_DIR)/work/socket-7385 >> $@ - @echo cluster-enabled yes >> $@ - @echo cluster-node-timeout 150 >> $@ - @echo cluster-config-file $(shell pwd)/work/cluster-node-config-7385.conf >> $@ - @echo requirepass foobared >> $@ - - -work/cluster-node-7479.conf: - @mkdir -p $(@D) - - @echo port 7479 >> $@ - @echo daemonize yes >> $@ - @echo pidfile $(shell pwd)/work/cluster-node-7479.pid >> $@ - @echo logfile $(shell pwd)/work/cluster-node-7479.log >> $@ - @echo save \"\" >> $@ - @echo appendonly no >> $@ - @echo cluster-enabled yes >> $@ - @echo cluster-node-timeout 150 >> $@ - @echo cluster-config-file $(shell pwd)/work/cluster-node-config-7479.conf >> $@ - @echo cluster-announce-port 7442 >> $@ - @echo requirepass foobared >> $@ - - -work/cluster-node-7480.conf: - @mkdir -p $(@D) - - @echo port 7480 >> $@ - @echo daemonize yes >> $@ - @echo pidfile $(shell pwd)/work/cluster-node-7480.pid >> $@ - @echo logfile $(shell pwd)/work/cluster-node-7480.log >> $@ - @echo save \"\" >> $@ - @echo appendonly no >> $@ - @echo cluster-enabled yes >> $@ - @echo cluster-node-timeout 150 >> $@ - @echo cluster-config-file $(shell pwd)/work/cluster-node-config-7480.conf >> $@ - @echo cluster-announce-port 7444 >> $@ - @echo requirepass foobared >> $@ - - -work/cluster-node-7481.conf: - @mkdir -p $(@D) - - @echo port 7481 >> $@ - @echo daemonize yes >> $@ - @echo pidfile $(shell pwd)/work/cluster-node-7481.pid >> $@ - @echo logfile $(shell pwd)/work/cluster-node-7481.log >> $@ - @echo save \"\" >> $@ - @echo appendonly no >> $@ - @echo cluster-enabled yes >> $@ - @echo cluster-node-timeout 150 >> $@ - @echo cluster-config-file $(shell pwd)/work/cluster-node-config-7481.conf >> $@ - @echo cluster-announce-port 7445 >> $@ - @echo requirepass foobared >> $@ - - -work/cluster-node-%.conf: - @mkdir -p $(@D) - - @echo port $* >> $@ - @echo daemonize yes >> $@ - @echo pidfile $(shell pwd)/work/cluster-node-$*.pid >> $@ - @echo logfile $(shell pwd)/work/cluster-node-$*.log >> $@ - @echo save \"\" >> $@ - @echo appendonly no >> $@ - @echo client-output-buffer-limit pubsub 256k 128k 5 >> $@ - @echo unixsocket $(ROOT_DIR)/work/socket-$* >> $@ - @echo cluster-enabled yes >> $@ - @echo cluster-node-timeout 150 >> $@ - @echo cluster-config-file $(shell pwd)/work/cluster-node-config-$*.conf >> $@ - -work/cluster-node-%.pid: work/cluster-node-%.conf work/redis-git/src/redis-server - work/redis-git/src/redis-server $< || true - -cluster-start: work/cluster-node-7379.pid work/cluster-node-7380.pid work/cluster-node-7381.pid work/cluster-node-7382.pid work/cluster-node-7383.pid work/cluster-node-7384.pid work/cluster-node-7385.pid work/cluster-node-7479.pid work/cluster-node-7480.pid work/cluster-node-7481.pid work/cluster-node-7582.pid - -########## -# stunnel -########## - -work/stunnel.conf: - @mkdir -p $(@D) - - @echo cert=$(ROOT_DIR)/work/ca/certs/localhost.cert.pem >> $@ - @echo key=$(ROOT_DIR)/work/ca/private/localhost.decrypted.key.pem >> $@ - @echo capath=$(ROOT_DIR)/work/ca/certs/ca.cert.pem >> $@ - @echo cafile=$(ROOT_DIR)/work/ca/certs/ca.cert.pem >> $@ - @echo delay=yes >> $@ - @echo pid=$(ROOT_DIR)/work/stunnel.pid >> $@ - @echo foreground = no >> $@ - - @echo [stunnel] >> $@ - @echo accept = 127.0.0.1:6443 >> $@ - @echo connect = 127.0.0.1:6479 >> $@ - - @echo [redis-sni-vritual] >> $@ - @echo accept = 127.0.0.1:36443 >> $@ - @echo cert=$(ROOT_DIR)/work/ca/certs/foo-host.cert.pem >> $@ - @echo key=$(ROOT_DIR)/work/ca/private/foo-host.decrypted.key.pem >> $@ - @echo connect = unavailable.internal.mydomain.com:6666 >> $@ - - @echo [redis-sni1] >> $@ - @echo sni = redis-sni-vritual:redis-sni1.local >> $@ - @echo key=$(ROOT_DIR)/work/ca/private/localhost.decrypted.key.pem >> $@ - @echo cert=$(ROOT_DIR)/work/ca/certs/localhost.cert.pem >> $@ - @echo connect = localhost:6480 >> $@ - - @echo [redis-sni2] >> $@ - @echo sni = redis-sni-vritual:redis-sni2.local >> $@ - @echo connect = localhost:6479 >> $@ - @echo cert=$(ROOT_DIR)/work/ca/certs/foo-host.cert.pem >> $@ - @echo key=$(ROOT_DIR)/work/ca/private/foo-host.decrypted.key.pem >> $@ - - @echo [foo-host] >> $@ - @echo accept = 127.0.0.1:6444 >> $@ - @echo connect = 127.0.0.1:6479 >> $@ - @echo cert=$(ROOT_DIR)/work/ca/certs/foo-host.cert.pem >> $@ - @echo key=$(ROOT_DIR)/work/ca/private/foo-host.decrypted.key.pem >> $@ - - @echo [ssl-cluster-node-1] >> $@ - @echo accept = 127.0.0.1:7442 >> $@ - @echo connect = 127.0.0.1:7479 >> $@ - - @echo [ssl-cluster-node-2] >> $@ - @echo accept = 127.0.0.1:7444 >> $@ - @echo connect = 127.0.0.1:7480 >> $@ - - @echo [ssl-cluster-node-3] >> $@ - @echo accept = 127.0.0.1:7445 >> $@ - @echo connect = 127.0.0.1:7481 >> $@ - - @echo [ssl-sentinel-1] >> $@ - @echo accept = 127.0.0.1:26822 >> $@ - @echo connect = 127.0.0.1:26379 >> $@ - - @echo [ssl-sentinel-2] >> $@ - @echo accept = 127.0.0.1:26823 >> $@ - @echo connect = 127.0.0.1:26380 >> $@ - - @echo [ssl-sentinel-3] >> $@ - @echo accept = 127.0.0.1:26824 >> $@ - @echo connect = 127.0.0.1:26381 >> $@ - - @echo [ssl-sentinel-master] >> $@ - @echo accept = 127.0.0.1:6925 >> $@ - @echo connect = 127.0.0.1:6482 >> $@ - - @echo [ssl-sentinel-slave] >> $@ - @echo accept = 127.0.0.1:6926 >> $@ - @echo connect = 127.0.0.1:6482 >> $@ - - @echo [stunnel-client-cert] >> $@ - @echo accept = 127.0.0.1:6445 >> $@ - @echo connect = 127.0.0.1:6479 >> $@ - @echo verify=2 >> $@ - - @echo [stunnel-master-slave-node-1] >> $@ - @echo accept = 127.0.0.1:8443 >> $@ - @echo connect = 127.0.0.1:6482 >> $@ - - @echo [stunnel-master-slave-node-2] >> $@ - @echo accept = 127.0.0.1:8444 >> $@ - @echo connect = 127.0.0.1:6483 >> $@ - -work/stunnel.pid: work/stunnel.conf ssl-keys - which stunnel4 >/dev/null 2>&1 && stunnel4 $(ROOT_DIR)/work/stunnel.conf || stunnel $(ROOT_DIR)/work/stunnel.conf - -stunnel-start: work/stunnel.pid - -export REDIS_CLUSTER_CONFIG1 -export REDIS_CLUSTER_CONFIG2 -export REDIS_CLUSTER_CONFIG3 -export REDIS_CLUSTER_CONFIG4 -export REDIS_CLUSTER_CONFIG8 -export REDIS_CLUSTER_CONFIG_SSL_1 -export REDIS_CLUSTER_CONFIG_SSL_2 -export REDIS_CLUSTER_CONFIG_SSL_3 - -start: cleanup - @echo "$$REDIS_CLUSTER_CONFIG1" > work/cluster-node-config-7379.conf - @echo "$$REDIS_CLUSTER_CONFIG2" > work/cluster-node-config-7380.conf - @echo "$$REDIS_CLUSTER_CONFIG3" > work/cluster-node-config-7381.conf - @echo "$$REDIS_CLUSTER_CONFIG4" > work/cluster-node-config-7382.conf - @echo "$$REDIS_CLUSTER_CONFIG8" > work/cluster-node-config-7582.conf - @echo "$$REDIS_CLUSTER_CONFIG_SSL_1" > work/cluster-node-config-7479.conf - @echo "$$REDIS_CLUSTER_CONFIG_SSL_2" > work/cluster-node-config-7480.conf - @echo "$$REDIS_CLUSTER_CONFIG_SSL_3" > work/cluster-node-config-7481.conf - $(MAKE) redis-start - $(MAKE) sentinel-start - $(MAKE) cluster-start - $(MAKE) stunnel-start - - -cleanup: stop - @mkdir -p work - rm -f work/cluster-node*.conf 2>/dev/null - rm -f work/*.rdb work/*.aof work/*.conf work/*.log 2>/dev/null - rm -f *.aof - rm -f *.rdb - rm -f work/socket-* - -########## -# SSL Keys -# - remove Java keystore as becomes stale -########## -work/keystore.jks: - @mkdir -p $(@D) - - rm -f work/*.jks - - rm -Rf work/ca - src/test/bash/create_certificates.sh - -ssl-keys: work/keystore.jks - -stop: - pkill stunnel || true - pkill redis-server && sleep 1 || true - pkill redis-sentinel && sleep 1 || true - -test-coverage: start - mvn -DskipITs=false clean compile verify jacoco:report -P$(PROFILE) - $(MAKE) stop - -test: start +SUPPORTED_TEST_ENV_VERSIONS := 8.0-M04-pre 7.4.2 7.2.7 +DEFAULT_TEST_ENV_VERSION := 8.0-M04-pre +REDIS_ENV_WORK_DIR := $(or ${REDIS_ENV_WORK_DIR},$(ROOT_DIR)/work) + +docker-start: + @if [ -z "$(version)" ]; then \ + version=$(arg); \ + if [ -z "$$version" ]; then \ + version="$(DEFAULT_TEST_ENV_VERSION)"; \ + fi; \ + fi; \ + if ! echo "$(SUPPORTED_TEST_ENV_VERSIONS)" | grep -qw "$$version"; then \ + echo "Error: Invalid version '$$version'. Supported versions are: $(SUPPORTED_TEST_ENV_VERSIONS)."; \ + exit 1; \ + fi; \ + echo "Version: $(version)"; \ + default_env_file="src/test/resources/docker-env/.env"; \ + custom_env_file="src/test/resources/docker-env/.env.v$$version"; \ + env_files="--env-file $$default_env_file"; \ + if [ -f "$$custom_env_file" ]; then \ + env_files="$$env_files --env-file $$custom_env_file"; \ + fi; \ + echo "Environment work directory: $(REDIS_ENV_WORK_DIR)"; \ + rm -rf "$(REDIS_ENV_WORK_DIR)"; \ + mkdir -p "$(REDIS_ENV_WORK_DIR)"; \ + export REDIS_VERSION=$$version && \ + docker compose $$env_files -f src/test/resources/docker-env/docker-compose.yml up -d; \ + echo "Started test environment with Redis version $$version." + + +docker-test: mvn -DskipITs=false clean compile verify -P$(PROFILE) - $(MAKE) stop -prepare: stop - -ifndef STUNNEL_BIN -ifeq ($(shell uname -s),Linux) -ifdef APT_BIN - sudo apt-get install -y stunnel -else - -ifdef YUM_BIN - sudo yum install stunnel -else - @@echo "Cannot install stunnel using yum/apt-get" - @exit 1 -endif - -endif - -endif - -ifeq ($(shell uname -s),Darwin) - -ifndef BREW_BIN - @@echo "Cannot install stunnel because missing brew.sh" - @exit 1 -endif - - brew install stunnel - -endif - -endif +test-coverage: + mvn -DskipITs=false clean compile verify jacoco:report -P$(PROFILE) -work/redis-git/src/redis-cli work/redis-git/src/redis-server: - [ -d "work/redis-git" ] && cd work/redis-git && git reset --hard || \ - git clone https://github.com/antirez/redis.git work/redis-git - cd work/redis-git && git checkout -q $(REDIS) && git pull origin $(REDIS) - $(MAKE) -C work/redis-git clean - $(MAKE) -C work/redis-git -j4 +docker-stop: + docker compose --env-file src/test/resources/docker-env/.env -f src/test/resources/docker-env/docker-compose.yml down; \ + rm -rf "$(REDIS_ENV_WORK_DIR)" clean: - rm -Rf work/ rm -Rf target/ release: diff --git a/README.md b/README.md index 21860f8784..d06e87be9c 100644 --- a/README.md +++ b/README.md @@ -155,22 +155,20 @@ Building ----------- Lettuce is built with Apache Maven. The tests require multiple running Redis instances for different test cases which -are configured using a ```Makefile```. Tests run by default against Redis `unstable`. +are configured using a ```Makefile```. Tests run by default against Redis `latest`. To build: ``` $ git clone https://github.com/redis/lettuce.git $ cd lettuce/ -$ make prepare ssl-keys -$ make test +$ make docker-start ``` -* Initial environment setup (clone and build `redis`): ```make prepare``` -* Setup SSL Keys: ```make ssl-keys``` -* Run the build: ```make test``` -* Start Redis (manually): ```make start``` -* Stop Redis (manually): ```make stop``` +* Run the build: ```make docker-test``` +* Start Redis (manually): ```make docker-start``` +* Stop Redis (manually): ```make docker-stop``` +* Clean up: ```make clean``` Bugs and Feedback ----------- diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index d37abd2383..74f154dee4 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -1133,7 +1133,7 @@ events is: `ConnectionDeactivatedEvent` 5. Since 5.3: Reconnect failed: A reconnect attempt failed. Contains - the reconnect failure and and the retry counter. Event type: + the reconnect failure and the retry counter. Event type: `ReconnectFailedEvent` #### Metrics events @@ -2515,7 +2515,7 @@ Commands that are issued as long as the failure persists are buffered. #### Exceptions to *at-least-once* -Lettuce does not loose commands while sending them. A command execution +Lettuce does not lose commands while sending them. A command execution can, however, fail for the same reasons as a normal method call can on the JVM: @@ -2558,7 +2558,7 @@ from the response queue, and the connection stays usable. In general, when `Errors` occur while operating on a connection, you should close the connection and use a new one. Connections, that -experienced such severe failures get into a unrecoverable state, and no +experienced such severe failures get into an unrecoverable state, and no further response processing is possible. Executing commands more than once diff --git a/docs/user-guide/connecting-redis.md b/docs/user-guide/connecting-redis.md index dac3102b88..6de6e55347 100644 --- a/docs/user-guide/connecting-redis.md +++ b/docs/user-guide/connecting-redis.md @@ -237,3 +237,161 @@ RedisClient client = RedisClient.create(redisUri); client.shutdown(); ``` +## Streaming Credentials Provider +[Lettuce 6.6.0](https://github.com/redis/lettuce/releases/tag/6.6.0.RELEASE) extends `RedisCredentialsProvider` to support streaming credentials. +It is useful when you need to refresh credentials periodically. Example use cases include: token expiration, rotating credentials, etc. +Connection configured with `RedisCredentialsProvider` supporting streaming will be re-authenticated automatically when new credentials are emitted and `ReauthenticateBehavior` is set to `ON_NEW_CREDENTIALS`. + +### Step 1 - Create a Streaming Credentials Provider +A simple example of a streaming credentials provider that emits new credentials. +```java +public class MyStreamingRedisCredentialsProvider implements RedisCredentialsProvider { + + private final Sinks.Many credentialsSink = Sinks.many().replay().latest(); + + @Override + public boolean supportsStreaming() { + return true; + } + + @Override + public Mono resolveCredentials() { + return credentialsSink.asFlux().next(); + } + + @Override + // Provide a continuous stream of credentials + public Flux credentials() { + return credentialsSink.asFlux().onBackpressureLatest(); + } + + public void close() { + credentialsSink.tryEmitComplete(); + } + // Emit new credentials when needed + public void emitCredentials(String username, char[] password) { + credentialsSink.tryEmitNext(new StaticRedisCredentials(username, password)); + } + +} +``` +### Step 2 - Create a RedisURI with streaming credentials provider + +```java + // Create a streaming credentials provider + MyStreamingRedisCredentialsProvider streamingCredentialsProvider = new MyStreamingRedisCredentialsProvider(); + + // Emit initial credentials + streamingCredentialsProvider.emitCredentials("testuser", "testpass".toCharArray()); + + // Enable automatic re-authentication + ClientOptions clientOptions = ClientOptions.builder() + // enable automatic re-authentication + .reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.ON_NEW_CREDENTIALS) + .build(); + + // Create a RedisURI with streaming credentials provider + RedisURI redisURI = RedisURI.builder().withHost(HOST).withPort(PORT) + .withAuthentication(streamingCredentialsProvider) + .build(); + + // RedisClient + RedisClient redisClient = RedisClient.create(redisURI); + rediscClient.connect().sync().ping(); + + // ... + // Emit new credentials when needed + streamingCredentialsProvider.emitCredentials("testuser", "password-rotated".toCharArray()); + +``` + +## Microsoft Entra ID Authentication + +[Lettuce 6.6.0](https://github.com/redis/lettuce/releases/tag/6.6.0.RELEASE) introduces built-in support for authentication with [Azure Managed Redis](https://azure.microsoft.com/en-us/products/managed-redis) and Azure Cache for Redis using Microsoft Entra ID (formerly Azure Active Directory). It enables seamless integration with Azure's Redis services by fetching authentication tokens and managing the token renewal in the background. +Integration is built on top of [redis-authx](https://github.com/redis/jvm-redis-authx-entraid) library, and provides support for: + + - System-assigned managed identity + - User-assigned managed identity + - Service principal + +You can learn more about managed identities in the [Microsoft Entra ID documentation](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview). + +### Basic Usage + +#### Pre-requisites +* [register an application and create a service principal](https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals?tabs=browser) in Azure. +* Create a Redis cache in Azure and grant your service principal access: [AMR](https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/managed-redis/managed-redis-entra-for-authentication) or [ACR](https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-azure-active-directory-for-authentication) documentation. + +#### Step 1 - Add the dependencies +Lettuce requires [redis-authx-entraid](https://github.com/redis/jvm-redis-authx-entraid/) dependency to provide Microsoft Entra ID authentication support. Make sure to include that dependency on your classpath. + +If using Maven, add the following dependency to your `pom.xml`: + +``` xml + + redis.clients.authentication + redis-authx-entraid + 0.1.1-beta1 + +``` + + +### Step 2 - Create Entra ID enabled credentials provider +The lifecycle of the credentials provider is not managed by the Lettuce client. You can create it once and reuse it across multiple clients\connections. When no longer needed, you should close the provider to release resources `TokenBasedRedisCredentialsProvider#close`. + +#### Create Microsoft Entra ID enabled credentials provider +```java + // Entra ID enabled credentials provider for Service Principle Identity with Client Secret + TokenBasedRedisCredentialsProvider credentialsSP; + try ( EntraIDTokenAuthConfigBuilder builder = EntraIDTokenAuthConfigBuilder.builder()) { + builder.clientId(CLIENT_ID) + .secret(CLIENT_SECRET) + .authority(AUTHORITY) // "https://login.microsoftonline.com/{YOUR_TENANT_ID}"; + .scopes(SCOPES); // "https://redis.azure.com/.default"; + credentialsSP = TokenBasedRedisCredentialsProvider.create(builder.build()); + } +``` + +You can test the credentials provider by obtaining a token. + +```java + // Test Entra ID credentials provider can resolve credentials + credentialsSP.resolveCredentials() + .doOnNext(c-> System.out.println(c.getUsername())) + .block(); +``` + +### Step 3 - Enable automatic re-authentication +Microsoft Entra ID tokens have a limited lifetime. Lettuce provides a mechanism to automatically re-authenticate when new credentials are emitted by a `RedisCredentialsProvider`. +```java + // Enable automatic re-authentication + ClientOptions clientOptions = ClientOptions.builder() + .reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.ON_NEW_CREDENTIALS) + .build(); +``` + +### Step 4 - Connect with Entra ID enabled credentials provider + +```java + // Use Entra ID credentials provider + RedisURI redisURI = RedisURI.builder() + .withHost(HOST) + .withPort(PORT) + .withAuthentication(credentialsSP).build(); + + // RedisClient + RedisClient redisClient = RedisClient.create(redisURI1); + redisClient.setOptions(clientOptions); + + try { + redisClient.setOptions(clientOptions); + // Connect with Entra ID credentials provider + try (StatefulRedisConnection user1 = redisClient.connect(StringCodec.UTF8)) { + System.out.println("Connected to redis as :" + user1.sync().aclWhoami()); + System.out.println("Db size :" + user1.sync().dbsize()); + } + } finally { + redisClient.shutdown(); // Shutdown Redis client and close connections + credentialsSP.close(); // Shutdown Entra ID Credentials provider + } +``` \ No newline at end of file diff --git a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java index 1a8994d28d..fd1ad55ebd 100644 --- a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java +++ b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java @@ -48,9 +48,6 @@ import io.lettuce.core.protocol.CommandType; import io.lettuce.core.protocol.ProtocolKeyword; import io.lettuce.core.protocol.RedisCommand; -import io.lettuce.core.search.Field; -import io.lettuce.core.search.arguments.CreateArgs; -import reactor.core.publisher.Mono; import java.time.Duration; import java.time.Instant; @@ -58,6 +55,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; import static io.lettuce.core.ClientOptions.DEFAULT_JSON_PARSER; import static io.lettuce.core.protocol.CommandType.EXEC; @@ -81,8 +79,7 @@ public abstract class AbstractRedisAsyncCommands implements RedisAclAsyncC RedisKeyAsyncCommands, RedisStringAsyncCommands, RedisListAsyncCommands, RedisSetAsyncCommands, RedisSortedSetAsyncCommands, RedisScriptingAsyncCommands, RedisServerAsyncCommands, RedisHLLAsyncCommands, BaseRedisAsyncCommands, RedisTransactionalAsyncCommands, - RedisGeoAsyncCommands, RedisClusterAsyncCommands, RedisJsonAsyncCommands, - RediSearchAsyncCommands { + RedisGeoAsyncCommands, RedisClusterAsyncCommands, RedisJsonAsyncCommands { private final StatefulConnection connection; @@ -90,9 +87,7 @@ public abstract class AbstractRedisAsyncCommands implements RedisAclAsyncC private final RedisJsonCommandBuilder jsonCommandBuilder; - private final RediSearchCommandBuilder searchCommandBuilder; - - private final Mono parser; + private final Supplier parser; /** * Initialize a new instance. @@ -101,12 +96,12 @@ public abstract class AbstractRedisAsyncCommands implements RedisAclAsyncC * @param codec the codec for command encoding * @param parser the implementation of the {@link JsonParser} to use */ - public AbstractRedisAsyncCommands(StatefulConnection connection, RedisCodec codec, Mono parser) { + public AbstractRedisAsyncCommands(StatefulConnection connection, RedisCodec codec, + Supplier parser) { this.parser = parser; this.connection = connection; this.commandBuilder = new RedisCommandBuilder<>(codec); this.jsonCommandBuilder = new RedisJsonCommandBuilder<>(codec, parser); - this.searchCommandBuilder = new RediSearchCommandBuilder<>(codec); } /** @@ -1484,11 +1479,6 @@ public boolean isOpen() { return connection.isOpen(); } - @Override - public RedisFuture ftCreate(K index, CreateArgs options, List> fields) { - return dispatch(searchCommandBuilder.ftCreate(index, options, fields)); - } - @Override public RedisFuture> jsonArrappend(K key, JsonPath jsonPath, JsonValue... values) { return dispatch(jsonCommandBuilder.jsonArrappend(key, jsonPath, values)); @@ -3407,7 +3397,7 @@ public RedisFuture>> clusterLinks() { @Override public JsonParser getJsonParser() { - return this.parser.block(); + return this.parser.get(); } private byte[] encodeFunction(String functionCode) { diff --git a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java index 02f37afd4d..e1651e5623 100644 --- a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java +++ b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java @@ -49,8 +49,6 @@ import io.lettuce.core.protocol.RedisCommand; import io.lettuce.core.protocol.TracedCommand; import io.lettuce.core.resource.ClientResources; -import io.lettuce.core.search.Field; -import io.lettuce.core.search.arguments.CreateArgs; import io.lettuce.core.tracing.TraceContext; import io.lettuce.core.tracing.TraceContextProvider; import io.lettuce.core.tracing.Tracing; @@ -86,12 +84,12 @@ * @author Tihomir Mateev * @since 4.0 */ -public abstract class AbstractRedisReactiveCommands implements RedisAclReactiveCommands, - RedisHashReactiveCommands, RedisKeyReactiveCommands, RedisStringReactiveCommands, - RedisListReactiveCommands, RedisSetReactiveCommands, RedisSortedSetReactiveCommands, - RedisScriptingReactiveCommands, RedisServerReactiveCommands, RedisHLLReactiveCommands, - BaseRedisReactiveCommands, RedisTransactionalReactiveCommands, RedisGeoReactiveCommands, - RedisClusterReactiveCommands, RedisJsonReactiveCommands, RediSearchReactiveCommands { +public abstract class AbstractRedisReactiveCommands + implements RedisAclReactiveCommands, RedisHashReactiveCommands, RedisKeyReactiveCommands, + RedisStringReactiveCommands, RedisListReactiveCommands, RedisSetReactiveCommands, + RedisSortedSetReactiveCommands, RedisScriptingReactiveCommands, RedisServerReactiveCommands, + RedisHLLReactiveCommands, BaseRedisReactiveCommands, RedisTransactionalReactiveCommands, + RedisGeoReactiveCommands, RedisClusterReactiveCommands, RedisJsonReactiveCommands { private final StatefulConnection connection; @@ -99,9 +97,7 @@ public abstract class AbstractRedisReactiveCommands implements RedisAclRea private final RedisJsonCommandBuilder jsonCommandBuilder; - private final RediSearchCommandBuilder searchCommandBuilder; - - private final Mono parser; + private final Supplier parser; private final ClientResources clientResources; @@ -116,12 +112,12 @@ public abstract class AbstractRedisReactiveCommands implements RedisAclRea * @param codec the codec for command encoding. * @param parser the implementation of the {@link JsonParser} to use */ - public AbstractRedisReactiveCommands(StatefulConnection connection, RedisCodec codec, Mono parser) { + public AbstractRedisReactiveCommands(StatefulConnection connection, RedisCodec codec, + Supplier parser) { this.connection = connection; this.parser = parser; this.commandBuilder = new RedisCommandBuilder<>(codec); this.jsonCommandBuilder = new RedisJsonCommandBuilder<>(codec, parser); - this.searchCommandBuilder = new RediSearchCommandBuilder<>(codec); this.clientResources = connection.getResources(); this.tracingEnabled = clientResources.tracing().isEnabled(); } @@ -154,7 +150,7 @@ private EventExecutorGroup getScheduler() { @Override public JsonParser getJsonParser() { - return parser.block(); + return parser.get(); } @Override @@ -1548,11 +1544,6 @@ public boolean isOpen() { return connection.isOpen(); } - @Override - public Mono ftCreate(K index, CreateArgs options, List> fields) { - return createMono(() -> searchCommandBuilder.ftCreate(index, options, fields)); - } - @Override public Flux jsonArrappend(K key, JsonPath jsonPath, JsonValue... values) { return createDissolvingFlux(() -> jsonCommandBuilder.jsonArrappend(key, jsonPath, values)); diff --git a/src/main/java/io/lettuce/core/ClientOptions.java b/src/main/java/io/lettuce/core/ClientOptions.java index 3fd635e4ee..5c02c3b18c 100644 --- a/src/main/java/io/lettuce/core/ClientOptions.java +++ b/src/main/java/io/lettuce/core/ClientOptions.java @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.ServiceConfigurationError; import java.util.ServiceLoader; +import java.util.function.Supplier; import io.lettuce.core.api.StatefulConnection; import io.lettuce.core.internal.LettuceAssert; @@ -71,7 +72,7 @@ public class ClientOptions implements Serializable { public static final SocketOptions DEFAULT_SOCKET_OPTIONS = SocketOptions.create(); - public static final Mono DEFAULT_JSON_PARSER = Mono.defer(() -> Mono.fromCallable(() -> { + public static final Supplier DEFAULT_JSON_PARSER = () -> { try { Iterator services = ServiceLoader.load(JsonParser.class).iterator(); return services.hasNext() ? services.next() : null; @@ -79,7 +80,7 @@ public class ClientOptions implements Serializable { throw new RedisJsonException("Could not load JsonParser, please consult the guide" + "at https://redis.github.io/lettuce/user-guide/redis-json/", e); } - })); + }; public static final SslOptions DEFAULT_SSL_OPTIONS = SslOptions.create(); @@ -111,7 +112,7 @@ public class ClientOptions implements Serializable { private final Charset scriptCharset; - private final Mono jsonParser; + private final Supplier jsonParser; private final SocketOptions socketOptions; @@ -216,7 +217,7 @@ public static class Builder { private Charset scriptCharset = DEFAULT_SCRIPT_CHARSET; - private Mono jsonParser = DEFAULT_JSON_PARSER; + private Supplier jsonParser = DEFAULT_JSON_PARSER; private SocketOptions socketOptions = DEFAULT_SOCKET_OPTIONS; @@ -429,7 +430,7 @@ public Builder scriptCharset(Charset scriptCharset) { * @see JsonParser * @since 6.5 */ - public Builder jsonParser(Mono parser) { + public Builder jsonParser(Supplier parser) { LettuceAssert.notNull(parser, "JsonParser must not be null"); this.jsonParser = parser; @@ -705,7 +706,7 @@ public Charset getScriptCharset() { * @return the implementation of the {@link JsonParser} to use. * @since 6.5 */ - public Mono getJsonParser() { + public Supplier getJsonParser() { return jsonParser; } diff --git a/src/main/java/io/lettuce/core/RediSearchCommandBuilder.java b/src/main/java/io/lettuce/core/RediSearchCommandBuilder.java deleted file mode 100644 index 1f8f25d303..0000000000 --- a/src/main/java/io/lettuce/core/RediSearchCommandBuilder.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2025, Redis Ltd. and Contributors - * All rights reserved. - * - * Licensed under the MIT License. - */ -package io.lettuce.core; - -import io.lettuce.core.codec.RedisCodec; -import io.lettuce.core.output.StatusOutput; -import io.lettuce.core.protocol.BaseRedisCommandBuilder; -import io.lettuce.core.protocol.Command; -import io.lettuce.core.protocol.CommandArgs; -import io.lettuce.core.protocol.CommandKeyword; -import io.lettuce.core.search.arguments.CreateArgs; -import io.lettuce.core.search.Field; - -import java.util.List; - -import static io.lettuce.core.protocol.CommandType.*; - -/** - * Command builder for RediSearch commands. - * - * @param Key type. - * @param Value type. - * @since 6.6 - */ -class RediSearchCommandBuilder extends BaseRedisCommandBuilder { - - RediSearchCommandBuilder(RedisCodec codec) { - super(codec); - } - - /** - * Create a new index with the given name, index options and fields. - * - * @param index the index name - * @param createArgs the index options - * @param fields the fields - * @return the result of the create command - */ - public Command ftCreate(K index, CreateArgs createArgs, List> fields) { - notNullKey(index); - notEmpty(fields.toArray()); - - CommandArgs args = new CommandArgs<>(codec).addKey(index); - - if (createArgs != null) { - createArgs.build(args); - } - - args.add(CommandKeyword.SCHEMA); - - for (Field field : fields) { - field.build(args); - } - - return createCommand(FT_CREATE, new StatusOutput<>(codec), args); - - } - -} diff --git a/src/main/java/io/lettuce/core/RedisAsyncCommandsImpl.java b/src/main/java/io/lettuce/core/RedisAsyncCommandsImpl.java index 87bae13e02..4e20755197 100644 --- a/src/main/java/io/lettuce/core/RedisAsyncCommandsImpl.java +++ b/src/main/java/io/lettuce/core/RedisAsyncCommandsImpl.java @@ -5,7 +5,7 @@ import io.lettuce.core.cluster.api.async.RedisClusterAsyncCommands; import io.lettuce.core.codec.RedisCodec; import io.lettuce.core.json.JsonParser; -import reactor.core.publisher.Mono; +import java.util.function.Supplier; /** * An asynchronous and thread-safe API for a Redis connection. @@ -24,7 +24,8 @@ public class RedisAsyncCommandsImpl extends AbstractRedisAsyncCommands connection, RedisCodec codec, Mono parser) { + public RedisAsyncCommandsImpl(StatefulRedisConnection connection, RedisCodec codec, + Supplier parser) { super(connection, codec, parser); } diff --git a/src/main/java/io/lettuce/core/RedisJsonCommandBuilder.java b/src/main/java/io/lettuce/core/RedisJsonCommandBuilder.java index ee7e8cf97b..9aff2e5942 100644 --- a/src/main/java/io/lettuce/core/RedisJsonCommandBuilder.java +++ b/src/main/java/io/lettuce/core/RedisJsonCommandBuilder.java @@ -20,9 +20,9 @@ import io.lettuce.core.protocol.BaseRedisCommandBuilder; import io.lettuce.core.protocol.Command; import io.lettuce.core.protocol.CommandArgs; -import reactor.core.publisher.Mono; import java.util.List; +import java.util.function.Supplier; import static io.lettuce.core.protocol.CommandType.*; @@ -34,9 +34,9 @@ */ class RedisJsonCommandBuilder extends BaseRedisCommandBuilder { - private final Mono parser; + private final Supplier parser; - RedisJsonCommandBuilder(RedisCodec codec, Mono theParser) { + RedisJsonCommandBuilder(RedisCodec codec, Supplier theParser) { super(codec); parser = theParser; } @@ -118,7 +118,7 @@ Command> jsonArrpop(K key, JsonPath jsonPath, int index) { } } - return createCommand(JSON_ARRPOP, new JsonValueListOutput<>(codec, parser.block()), args); + return createCommand(JSON_ARRPOP, new JsonValueListOutput<>(codec, parser.get()), args); } Command> jsonArrtrim(K key, JsonPath jsonPath, JsonRangeArgs range) { @@ -167,7 +167,7 @@ Command> jsonGet(K key, JsonGetArgs options, JsonPath... j } } - return createCommand(JSON_GET, new JsonValueListOutput<>(codec, parser.block()), args); + return createCommand(JSON_GET, new JsonValueListOutput<>(codec, parser.get()), args); } Command jsonMerge(K key, JsonPath jsonPath, JsonValue value) { @@ -194,7 +194,7 @@ Command> jsonMGet(JsonPath jsonPath, K... keys) { args.add(jsonPath.toString()); } - return createCommand(JSON_MGET, new JsonValueListOutput<>(codec, parser.block()), args); + return createCommand(JSON_MGET, new JsonValueListOutput<>(codec, parser.get()), args); } Command jsonMSet(List> arguments) { diff --git a/src/main/java/io/lettuce/core/RedisReactiveCommandsImpl.java b/src/main/java/io/lettuce/core/RedisReactiveCommandsImpl.java index de01957391..620b5510ae 100644 --- a/src/main/java/io/lettuce/core/RedisReactiveCommandsImpl.java +++ b/src/main/java/io/lettuce/core/RedisReactiveCommandsImpl.java @@ -7,6 +7,8 @@ import io.lettuce.core.json.JsonParser; import reactor.core.publisher.Mono; +import java.util.function.Supplier; + /** * A reactive and thread-safe API for a Redis Sentinel connection. * @@ -25,7 +27,7 @@ public class RedisReactiveCommandsImpl extends AbstractRedisReactiveComman * @param parser the implementation of the {@link JsonParser} to use */ public RedisReactiveCommandsImpl(StatefulRedisConnection connection, RedisCodec codec, - Mono parser) { + Supplier parser) { super(connection, codec, parser); } diff --git a/src/main/java/io/lettuce/core/StatefulRedisConnectionImpl.java b/src/main/java/io/lettuce/core/StatefulRedisConnectionImpl.java index b51ee8ffae..8052b95617 100644 --- a/src/main/java/io/lettuce/core/StatefulRedisConnectionImpl.java +++ b/src/main/java/io/lettuce/core/StatefulRedisConnectionImpl.java @@ -27,6 +27,7 @@ import java.util.Collection; import java.util.List; import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.stream.Collectors; import io.lettuce.core.api.StatefulRedisConnection; @@ -67,7 +68,7 @@ public class StatefulRedisConnectionImpl extends RedisChannelHandler private final PushHandler pushHandler; - private final Mono parser; + private final Supplier parser; protected MultiOutput multi; @@ -96,7 +97,7 @@ public StatefulRedisConnectionImpl(RedisChannelWriter writer, PushHandler pushHa * @param parser the parser to use for JSON commands. */ public StatefulRedisConnectionImpl(RedisChannelWriter writer, PushHandler pushHandler, RedisCodec codec, - Duration timeout, Mono parser) { + Duration timeout, Supplier parser) { super(writer, timeout); diff --git a/src/main/java/io/lettuce/core/api/async/RediSearchAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RediSearchAsyncCommands.java deleted file mode 100644 index 7b549160cb..0000000000 --- a/src/main/java/io/lettuce/core/api/async/RediSearchAsyncCommands.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2025, Redis Ltd. and Contributors - * All rights reserved. - * - * Licensed under the MIT License. - */ -package io.lettuce.core.api.async; - -import java.util.List; -import io.lettuce.core.RedisFuture; -import io.lettuce.core.search.Field; -import io.lettuce.core.search.arguments.CreateArgs; - -/** - * Asynchronous executed commands for RediSearch functionality - * - * @param Key type. - * @param Value type. - * @author Tihomir Mateev - * @see RediSearch - * @since 6.6 - * @generated by io.lettuce.apigenerator.CreateAsyncApi - */ -public interface RediSearchAsyncCommands { - - /** - * Create a new index with the given name, index options, and fields. - * - * @param index the index name, as a key - * @param options the index {@link CreateArgs} - * @param fields the {@link Field}s of the index - * @return the result of the create command - * @since 6.6 - * @see FT.CREATE - */ - RedisFuture ftCreate(K index, CreateArgs options, List> fields); - -} diff --git a/src/main/java/io/lettuce/core/api/async/RedisAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisAsyncCommands.java index 5689de96f5..6ff3ef9ad1 100644 --- a/src/main/java/io/lettuce/core/api/async/RedisAsyncCommands.java +++ b/src/main/java/io/lettuce/core/api/async/RedisAsyncCommands.java @@ -37,7 +37,7 @@ public interface RedisAsyncCommands extends BaseRedisAsyncCommands, RedisHashAsyncCommands, RedisHLLAsyncCommands, RedisKeyAsyncCommands, RedisListAsyncCommands, RedisScriptingAsyncCommands, RedisServerAsyncCommands, RedisSetAsyncCommands, RedisSortedSetAsyncCommands, RedisStreamAsyncCommands, RedisStringAsyncCommands, - RedisTransactionalAsyncCommands, RedisJsonAsyncCommands, RediSearchAsyncCommands { + RedisTransactionalAsyncCommands, RedisJsonAsyncCommands { /** * Authenticate to the server. diff --git a/src/main/java/io/lettuce/core/api/reactive/RediSearchReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RediSearchReactiveCommands.java deleted file mode 100644 index ba2268cca3..0000000000 --- a/src/main/java/io/lettuce/core/api/reactive/RediSearchReactiveCommands.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2025, Redis Ltd. and Contributors - * All rights reserved. - * - * Licensed under the MIT License. - */ -package io.lettuce.core.api.reactive; - -import java.util.List; -import io.lettuce.core.search.Field; -import io.lettuce.core.search.arguments.CreateArgs; -import reactor.core.publisher.Mono; - -/** - * Reactive executed commands for RediSearch functionality - * - * @param Key type. - * @param Value type. - * @author Tihomir Mateev - * @see RediSearch - * @since 6.6 - * @generated by io.lettuce.apigenerator.CreateReactiveApi - */ -public interface RediSearchReactiveCommands { - - /** - * Create a new index with the given name, index options, and fields. - * - * @param index the index name, as a key - * @param options the index {@link CreateArgs} - * @param fields the {@link Field}s of the index - * @return the result of the create command - * @since 6.6 - * @see FT.CREATE - */ - Mono ftCreate(K index, CreateArgs options, List> fields); - -} diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisReactiveCommands.java index 76d24ddf10..2f75efcc92 100644 --- a/src/main/java/io/lettuce/core/api/reactive/RedisReactiveCommands.java +++ b/src/main/java/io/lettuce/core/api/reactive/RedisReactiveCommands.java @@ -31,13 +31,12 @@ * @author Mark Paluch * @since 5.0 */ -public interface RedisReactiveCommands - extends BaseRedisReactiveCommands, RedisAclReactiveCommands, RedisClusterReactiveCommands, - RedisFunctionReactiveCommands, RedisGeoReactiveCommands, RedisHashReactiveCommands, - RedisHLLReactiveCommands, RedisKeyReactiveCommands, RedisListReactiveCommands, - RedisScriptingReactiveCommands, RedisServerReactiveCommands, RedisSetReactiveCommands, - RedisSortedSetReactiveCommands, RedisStreamReactiveCommands, RedisStringReactiveCommands, - RedisTransactionalReactiveCommands, RedisJsonReactiveCommands, RediSearchReactiveCommands { +public interface RedisReactiveCommands extends BaseRedisReactiveCommands, RedisAclReactiveCommands, + RedisClusterReactiveCommands, RedisFunctionReactiveCommands, RedisGeoReactiveCommands, + RedisHashReactiveCommands, RedisHLLReactiveCommands, RedisKeyReactiveCommands, + RedisListReactiveCommands, RedisScriptingReactiveCommands, RedisServerReactiveCommands, + RedisSetReactiveCommands, RedisSortedSetReactiveCommands, RedisStreamReactiveCommands, + RedisStringReactiveCommands, RedisTransactionalReactiveCommands, RedisJsonReactiveCommands { /** * Authenticate to the server. diff --git a/src/main/java/io/lettuce/core/api/sync/RediSearchCommands.java b/src/main/java/io/lettuce/core/api/sync/RediSearchCommands.java deleted file mode 100644 index c76f9867e6..0000000000 --- a/src/main/java/io/lettuce/core/api/sync/RediSearchCommands.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2025, Redis Ltd. and Contributors - * All rights reserved. - * - * Licensed under the MIT License. - */ -package io.lettuce.core.api.sync; - -import java.util.List; -import io.lettuce.core.search.Field; -import io.lettuce.core.search.arguments.CreateArgs; - -/** - * Synchronous executed commands for RediSearch functionality - * - * @param Key type. - * @param Value type. - * @author Tihomir Mateev - * @see RediSearch - * @since 6.6 - * @generated by io.lettuce.apigenerator.CreateSyncApi - */ -public interface RediSearchCommands { - - /** - * Create a new index with the given name, index options, and fields. - * - * @param index the index name, as a key - * @param options the index {@link CreateArgs} - * @param fields the {@link Field}s of the index - * @return the result of the create command - * @since 6.6 - * @see FT.CREATE - */ - String ftCreate(K index, CreateArgs options, List> fields); - -} diff --git a/src/main/java/io/lettuce/core/api/sync/RedisCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisCommands.java index e7f74d5378..98f21b4cb2 100644 --- a/src/main/java/io/lettuce/core/api/sync/RedisCommands.java +++ b/src/main/java/io/lettuce/core/api/sync/RedisCommands.java @@ -36,7 +36,7 @@ public interface RedisCommands extends BaseRedisCommands, RedisAclCo RedisFunctionCommands, RedisGeoCommands, RedisHashCommands, RedisHLLCommands, RedisKeyCommands, RedisListCommands, RedisScriptingCommands, RedisServerCommands, RedisSetCommands, RedisSortedSetCommands, RedisStreamCommands, RedisStringCommands, - RedisTransactionalCommands, RedisJsonCommands, RediSearchCommands { + RedisTransactionalCommands, RedisJsonCommands { /** * Authenticate to the server. diff --git a/src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterAsyncCommandsImpl.java b/src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterAsyncCommandsImpl.java index 840a6ef1ff..d8f8e40923 100644 --- a/src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterAsyncCommandsImpl.java +++ b/src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterAsyncCommandsImpl.java @@ -29,13 +29,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ThreadLocalRandom; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.stream.Collectors; import io.lettuce.core.*; @@ -64,7 +64,6 @@ import io.lettuce.core.protocol.Command; import io.lettuce.core.protocol.CommandType; import io.lettuce.core.protocol.ConnectionIntent; -import reactor.core.publisher.Mono; /** * An advanced asynchronous and thread-safe API for a Redis Cluster connection. @@ -89,11 +88,11 @@ public class RedisAdvancedClusterAsyncCommandsImpl extends AbstractRedisAs * @param codec Codec used to encode/decode keys and values. * @param parser the implementation of the {@link JsonParser} to use * @deprecated since 5.1, use - * {@link #RedisAdvancedClusterAsyncCommandsImpl(StatefulRedisClusterConnection, RedisCodec, Mono)}. + * {@link #RedisAdvancedClusterAsyncCommandsImpl(StatefulRedisClusterConnection, RedisCodec, Supplier)}. */ @Deprecated public RedisAdvancedClusterAsyncCommandsImpl(StatefulRedisClusterConnectionImpl connection, RedisCodec codec, - Mono parser) { + Supplier parser) { super(connection, codec, parser); this.codec = codec; } @@ -104,7 +103,7 @@ public RedisAdvancedClusterAsyncCommandsImpl(StatefulRedisClusterConnectionImpl< * @param connection the stateful connection * @param codec Codec used to encode/decode keys and values. * @deprecated since 5.1, use - * {@link #RedisAdvancedClusterAsyncCommandsImpl(StatefulRedisClusterConnection, RedisCodec, Mono)}. + * {@link #RedisAdvancedClusterAsyncCommandsImpl(StatefulRedisClusterConnection, RedisCodec, Supplier)}. */ @Deprecated public RedisAdvancedClusterAsyncCommandsImpl(StatefulRedisClusterConnectionImpl connection, RedisCodec codec) { @@ -120,7 +119,7 @@ public RedisAdvancedClusterAsyncCommandsImpl(StatefulRedisClusterConnectionImpl< * @param parser the implementation of the {@link JsonParser} to use */ public RedisAdvancedClusterAsyncCommandsImpl(StatefulRedisClusterConnection connection, RedisCodec codec, - Mono parser) { + Supplier parser) { super(connection, codec, parser); this.codec = codec; } diff --git a/src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterReactiveCommandsImpl.java b/src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterReactiveCommandsImpl.java index e12a352c4c..448c4073bb 100644 --- a/src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterReactiveCommandsImpl.java +++ b/src/main/java/io/lettuce/core/cluster/RedisAdvancedClusterReactiveCommandsImpl.java @@ -32,6 +32,7 @@ import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.stream.Collectors; import io.lettuce.core.json.JsonParser; @@ -78,11 +79,11 @@ public class RedisAdvancedClusterReactiveCommandsImpl extends AbstractRedi * @param codec Codec used to encode/decode keys and values. * @param parser the implementation of the {@link JsonParser} to use * @deprecated since 5.2, use - * {@link #RedisAdvancedClusterReactiveCommandsImpl(StatefulRedisClusterConnection, RedisCodec, Mono)}. + * {@link #RedisAdvancedClusterReactiveCommandsImpl(StatefulRedisClusterConnection, RedisCodec, Supplier)}. */ @Deprecated public RedisAdvancedClusterReactiveCommandsImpl(StatefulRedisClusterConnectionImpl connection, RedisCodec codec, - Mono parser) { + Supplier parser) { super(connection, codec, parser); this.codec = codec; } @@ -93,7 +94,7 @@ public RedisAdvancedClusterReactiveCommandsImpl(StatefulRedisClusterConnectionIm * @param connection the stateful connection. * @param codec Codec used to encode/decode keys and values. * @deprecated since 5.2, use - * {@link #RedisAdvancedClusterReactiveCommandsImpl(StatefulRedisClusterConnection, RedisCodec, Mono)}. + * {@link #RedisAdvancedClusterReactiveCommandsImpl(StatefulRedisClusterConnection, RedisCodec, Supplier)}. */ @Deprecated public RedisAdvancedClusterReactiveCommandsImpl(StatefulRedisClusterConnectionImpl connection, @@ -110,7 +111,7 @@ public RedisAdvancedClusterReactiveCommandsImpl(StatefulRedisClusterConnectionIm * @param parser the implementation of the {@link JsonParser} to use */ public RedisAdvancedClusterReactiveCommandsImpl(StatefulRedisClusterConnection connection, RedisCodec codec, - Mono parser) { + Supplier parser) { super(connection, codec, parser); this.codec = codec; } diff --git a/src/main/java/io/lettuce/core/cluster/RedisClusterClient.java b/src/main/java/io/lettuce/core/cluster/RedisClusterClient.java index 31125d93c7..b5fa5cf196 100644 --- a/src/main/java/io/lettuce/core/cluster/RedisClusterClient.java +++ b/src/main/java/io/lettuce/core/cluster/RedisClusterClient.java @@ -587,7 +587,7 @@ ConnectionFuture> connectToNodeAsync(RedisC * @return new instance of StatefulRedisConnectionImpl */ protected StatefulRedisConnectionImpl newStatefulRedisConnection(RedisChannelWriter channelWriter, - PushHandler pushHandler, RedisCodec codec, Duration timeout, Mono parser) { + PushHandler pushHandler, RedisCodec codec, Duration timeout, Supplier parser) { return new StatefulRedisConnectionImpl<>(channelWriter, pushHandler, codec, timeout, parser); } @@ -734,7 +734,7 @@ private CompletableFuture> connectCl */ protected StatefulRedisClusterConnectionImpl newStatefulRedisClusterConnection( RedisChannelWriter channelWriter, ClusterPushHandler pushHandler, RedisCodec codec, Duration timeout, - Mono parser) { + Supplier parser) { return new StatefulRedisClusterConnectionImpl(channelWriter, pushHandler, codec, timeout, parser); } diff --git a/src/main/java/io/lettuce/core/cluster/StatefulRedisClusterConnectionImpl.java b/src/main/java/io/lettuce/core/cluster/StatefulRedisClusterConnectionImpl.java index c84193491b..d967ada1ed 100644 --- a/src/main/java/io/lettuce/core/cluster/StatefulRedisClusterConnectionImpl.java +++ b/src/main/java/io/lettuce/core/cluster/StatefulRedisClusterConnectionImpl.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.stream.Collectors; import io.lettuce.core.AbstractRedisClient; @@ -77,7 +78,7 @@ public class StatefulRedisClusterConnectionImpl extends RedisChannelHandle protected final RedisCodec codec; - protected final Mono parser; + protected final Supplier parser; protected final RedisAdvancedClusterCommands sync; @@ -113,7 +114,7 @@ public StatefulRedisClusterConnectionImpl(RedisChannelWriter writer, ClusterPush * @param parser the JSON parser */ public StatefulRedisClusterConnectionImpl(RedisChannelWriter writer, ClusterPushHandler pushHandler, RedisCodec codec, - Duration timeout, Mono parser) { + Duration timeout, Supplier parser) { super(writer, timeout); this.pushHandler = pushHandler; diff --git a/src/main/java/io/lettuce/core/cluster/api/async/RediSearchAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/RediSearchAsyncCommands.java deleted file mode 100644 index d9fb189253..0000000000 --- a/src/main/java/io/lettuce/core/cluster/api/async/RediSearchAsyncCommands.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2025, Redis Ltd. and Contributors - * All rights reserved. - * - * Licensed under the MIT License. - */ -package io.lettuce.core.cluster.api.async; - -import java.util.List; -import io.lettuce.core.search.Field; -import io.lettuce.core.search.arguments.CreateArgs; - -/** - * Asynchronous executed commands on a node selection for RediSearch functionality - * - * @param Key type. - * @param Value type. - * @author Tihomir Mateev - * @see RediSearch - * @since 6.6 - * @generated by io.lettuce.apigenerator.CreateAsyncNodeSelectionClusterApi - */ -public interface RediSearchAsyncCommands { - - /** - * Create a new index with the given name, index options, and fields. - * - * @param index the index name, as a key - * @param options the index {@link CreateArgs} - * @param fields the {@link Field}s of the index - * @return the result of the create command - * @since 6.6 - * @see FT.CREATE - */ - AsyncExecutions ftCreate(K index, CreateArgs options, List> fields); - -} diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/RediSearchCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/RediSearchCommands.java deleted file mode 100644 index 00cbc7b8bc..0000000000 --- a/src/main/java/io/lettuce/core/cluster/api/sync/RediSearchCommands.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2025, Redis Ltd. and Contributors - * All rights reserved. - * - * Licensed under the MIT License. - */ -package io.lettuce.core.cluster.api.sync; - -import java.util.List; -import io.lettuce.core.search.Field; -import io.lettuce.core.search.arguments.CreateArgs; - -/** - * Synchronous executed commands on a node selection for RediSearch functionality - * - * @param Key type. - * @param Value type. - * @author Tihomir Mateev - * @see RediSearch - * @since 6.6 - * @generated by io.lettuce.apigenerator.CreateSyncNodeSelectionClusterApi - */ -public interface RediSearchCommands { - - /** - * Create a new index with the given name, index options, and fields. - * - * @param index the index name, as a key - * @param options the index {@link CreateArgs} - * @param fields the {@link Field}s of the index - * @return the result of the create command - * @since 6.6 - * @see FT.CREATE - */ - Executions ftCreate(K index, CreateArgs options, List> fields); - -} diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/RedisClusterCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/RedisClusterCommands.java index d66093759f..988975740c 100644 --- a/src/main/java/io/lettuce/core/cluster/api/sync/RedisClusterCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/sync/RedisClusterCommands.java @@ -34,7 +34,6 @@ * @param Value type. * @author Mark Paluch * @author dengliming - * @author Tihomir Mateev * @since 4.0 */ public interface RedisClusterCommands diff --git a/src/main/java/io/lettuce/core/masterreplica/StatefulRedisMasterReplicaConnectionImpl.java b/src/main/java/io/lettuce/core/masterreplica/StatefulRedisMasterReplicaConnectionImpl.java index ad1b5a53f6..babafd51e4 100644 --- a/src/main/java/io/lettuce/core/masterreplica/StatefulRedisMasterReplicaConnectionImpl.java +++ b/src/main/java/io/lettuce/core/masterreplica/StatefulRedisMasterReplicaConnectionImpl.java @@ -1,12 +1,12 @@ package io.lettuce.core.masterreplica; import java.time.Duration; +import java.util.function.Supplier; import io.lettuce.core.ReadFrom; import io.lettuce.core.StatefulRedisConnectionImpl; import io.lettuce.core.codec.RedisCodec; import io.lettuce.core.json.JsonParser; -import reactor.core.publisher.Mono; import static io.lettuce.core.ClientOptions.DEFAULT_JSON_PARSER; @@ -36,7 +36,7 @@ class StatefulRedisMasterReplicaConnectionImpl extends StatefulRedisConnec * @param parser the JSON parser to use */ StatefulRedisMasterReplicaConnectionImpl(MasterReplicaChannelWriter writer, RedisCodec codec, Duration timeout, - Mono parser) { + Supplier parser) { super(writer, NoOpPushHandler.INSTANCE, codec, timeout, parser); } diff --git a/src/main/java/io/lettuce/core/output/ClaimedMessagesOutput.java b/src/main/java/io/lettuce/core/output/ClaimedMessagesOutput.java index 49d36c3f5d..3b08480d43 100644 --- a/src/main/java/io/lettuce/core/output/ClaimedMessagesOutput.java +++ b/src/main/java/io/lettuce/core/output/ClaimedMessagesOutput.java @@ -70,12 +70,12 @@ public ClaimedMessagesOutput(RedisCodec codec, K stream, boolean justId) { @Override public void set(ByteBuffer bytes) { if (startId == null) { - startId = decodeAscii(bytes); + startId = decodeString(bytes); return; } if (id == null) { - id = decodeAscii(bytes); + id = decodeString(bytes); return; } diff --git a/src/main/java/io/lettuce/core/output/CommandOutput.java b/src/main/java/io/lettuce/core/output/CommandOutput.java index 2b4a851267..5775f89675 100644 --- a/src/main/java/io/lettuce/core/output/CommandOutput.java +++ b/src/main/java/io/lettuce/core/output/CommandOutput.java @@ -20,6 +20,7 @@ package io.lettuce.core.output; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import io.lettuce.core.codec.RedisCodec; import io.lettuce.core.internal.LettuceAssert; @@ -139,7 +140,7 @@ public void set(boolean value) { * @param error Error message. */ public void setError(ByteBuffer error) { - this.error = decodeAscii(error); + this.error = decodeString(error); } /** @@ -179,16 +180,8 @@ public void complete(int depth) { // nothing to do by default } - protected String decodeAscii(ByteBuffer bytes) { - if (bytes == null) { - return null; - } - - char[] chars = new char[bytes.remaining()]; - for (int i = 0; i < chars.length; i++) { - chars[i] = (char) bytes.get(); - } - return new String(chars); + protected String decodeString(ByteBuffer bytes) { + return bytes == null ? null : StandardCharsets.UTF_8.decode(bytes).toString(); } @Override diff --git a/src/main/java/io/lettuce/core/output/DoubleListOutput.java b/src/main/java/io/lettuce/core/output/DoubleListOutput.java index de1c5dfd6f..72a2e5a427 100644 --- a/src/main/java/io/lettuce/core/output/DoubleListOutput.java +++ b/src/main/java/io/lettuce/core/output/DoubleListOutput.java @@ -46,7 +46,7 @@ public DoubleListOutput(RedisCodec codec) { @Override public void set(ByteBuffer bytes) { - output.add(bytes != null ? parseDouble(decodeAscii(bytes)) : null); + output.add(bytes != null ? parseDouble(decodeString(bytes)) : null); } @Override diff --git a/src/main/java/io/lettuce/core/output/DoubleOutput.java b/src/main/java/io/lettuce/core/output/DoubleOutput.java index 415fe3b997..8f8aac9a63 100644 --- a/src/main/java/io/lettuce/core/output/DoubleOutput.java +++ b/src/main/java/io/lettuce/core/output/DoubleOutput.java @@ -40,7 +40,7 @@ public DoubleOutput(RedisCodec codec) { @Override public void set(ByteBuffer bytes) { - output = (bytes == null) ? null : parseDouble(decodeAscii(bytes)); + output = (bytes == null) ? null : parseDouble(decodeString(bytes)); } @Override diff --git a/src/main/java/io/lettuce/core/output/EnumSetOutput.java b/src/main/java/io/lettuce/core/output/EnumSetOutput.java index dd2c1c40fe..dfd627e709 100644 --- a/src/main/java/io/lettuce/core/output/EnumSetOutput.java +++ b/src/main/java/io/lettuce/core/output/EnumSetOutput.java @@ -51,7 +51,7 @@ public void set(ByteBuffer bytes) { return; } - E enumConstant = resolve(enumValuePreprocessor.apply(decodeAscii(bytes))); + E enumConstant = resolve(enumValuePreprocessor.apply(decodeString(bytes))); if (enumConstant == null) { return; diff --git a/src/main/java/io/lettuce/core/output/GeoCoordinatesListOutput.java b/src/main/java/io/lettuce/core/output/GeoCoordinatesListOutput.java index bb51449cf1..ce3cef58e8 100644 --- a/src/main/java/io/lettuce/core/output/GeoCoordinatesListOutput.java +++ b/src/main/java/io/lettuce/core/output/GeoCoordinatesListOutput.java @@ -39,7 +39,7 @@ public void set(ByteBuffer bytes) { return; } - double value = (bytes == null) ? 0 : parseDouble(decodeAscii(bytes)); + double value = (bytes == null) ? 0 : parseDouble(decodeString(bytes)); set(value); } diff --git a/src/main/java/io/lettuce/core/output/GeoCoordinatesValueListOutput.java b/src/main/java/io/lettuce/core/output/GeoCoordinatesValueListOutput.java index 4bd8927c2a..7fbb3f93ce 100644 --- a/src/main/java/io/lettuce/core/output/GeoCoordinatesValueListOutput.java +++ b/src/main/java/io/lettuce/core/output/GeoCoordinatesValueListOutput.java @@ -40,7 +40,7 @@ public void set(ByteBuffer bytes) { return; } - double value = parseDouble(decodeAscii(bytes)); + double value = parseDouble(decodeString(bytes)); set(value); } diff --git a/src/main/java/io/lettuce/core/output/GeoWithinListOutput.java b/src/main/java/io/lettuce/core/output/GeoWithinListOutput.java index 01d4941915..5a13bbd040 100644 --- a/src/main/java/io/lettuce/core/output/GeoWithinListOutput.java +++ b/src/main/java/io/lettuce/core/output/GeoWithinListOutput.java @@ -73,7 +73,7 @@ public void set(ByteBuffer bytes) { return; } - double value = (bytes == null) ? 0 : parseDouble(decodeAscii(bytes)); + double value = (bytes == null) ? 0 : parseDouble(decodeString(bytes)); set(value); } diff --git a/src/main/java/io/lettuce/core/output/JsonTypeListOutput.java b/src/main/java/io/lettuce/core/output/JsonTypeListOutput.java index 96f942f280..942d80cd3c 100644 --- a/src/main/java/io/lettuce/core/output/JsonTypeListOutput.java +++ b/src/main/java/io/lettuce/core/output/JsonTypeListOutput.java @@ -35,7 +35,7 @@ public void set(ByteBuffer bytes) { multi(1); } - output.add(JsonType.fromString(decodeAscii(bytes))); + output.add(JsonType.fromString(decodeString(bytes))); } @Override diff --git a/src/main/java/io/lettuce/core/output/KeyValueListScoredValueOutput.java b/src/main/java/io/lettuce/core/output/KeyValueListScoredValueOutput.java index d4b79034f7..e3b1b62ed6 100644 --- a/src/main/java/io/lettuce/core/output/KeyValueListScoredValueOutput.java +++ b/src/main/java/io/lettuce/core/output/KeyValueListScoredValueOutput.java @@ -45,7 +45,7 @@ public void set(ByteBuffer bytes) { value = codec.decodeValue(bytes); return; } - score = parseDouble(decodeAscii(bytes)); + score = parseDouble(decodeString(bytes)); } } diff --git a/src/main/java/io/lettuce/core/output/KeyValueOfScoredValueOutput.java b/src/main/java/io/lettuce/core/output/KeyValueOfScoredValueOutput.java index 6bb09df871..fc730b7946 100644 --- a/src/main/java/io/lettuce/core/output/KeyValueOfScoredValueOutput.java +++ b/src/main/java/io/lettuce/core/output/KeyValueOfScoredValueOutput.java @@ -40,7 +40,7 @@ public void set(ByteBuffer bytes) { return; } - output = KeyValue.just(key, ScoredValue.just(parseDouble(decodeAscii(bytes)), value)); + output = KeyValue.just(key, ScoredValue.just(parseDouble(decodeString(bytes)), value)); } } diff --git a/src/main/java/io/lettuce/core/output/KeyValueScoredValueOutput.java b/src/main/java/io/lettuce/core/output/KeyValueScoredValueOutput.java index 994735d614..bbeb0484ff 100644 --- a/src/main/java/io/lettuce/core/output/KeyValueScoredValueOutput.java +++ b/src/main/java/io/lettuce/core/output/KeyValueScoredValueOutput.java @@ -48,7 +48,7 @@ public void set(ByteBuffer bytes) { return; } - double score = LettuceStrings.toDouble(decodeAscii(bytes)); + double score = LettuceStrings.toDouble(decodeString(bytes)); set(score); } diff --git a/src/main/java/io/lettuce/core/output/MultiOutput.java b/src/main/java/io/lettuce/core/output/MultiOutput.java index 949a64cbfa..c8cc277813 100644 --- a/src/main/java/io/lettuce/core/output/MultiOutput.java +++ b/src/main/java/io/lettuce/core/output/MultiOutput.java @@ -133,7 +133,7 @@ public void setError(ByteBuffer error) { } CommandOutput output = queue.isEmpty() ? this : queue.peek().getOutput(); - output.setError(decodeAscii(error)); + output.setError(decodeString(error)); } @Override diff --git a/src/main/java/io/lettuce/core/output/NumberListOutput.java b/src/main/java/io/lettuce/core/output/NumberListOutput.java index cbbb9bd9b3..70041984da 100644 --- a/src/main/java/io/lettuce/core/output/NumberListOutput.java +++ b/src/main/java/io/lettuce/core/output/NumberListOutput.java @@ -65,7 +65,7 @@ public void multi(int count) { private Number parseNumber(ByteBuffer bytes) { Number result = 0; try { - result = NumberFormat.getNumberInstance().parse(decodeAscii(bytes)); + result = NumberFormat.getNumberInstance().parse(decodeString(bytes)); } catch (ParseException e) { LOG.warn("Failed to parse " + bytes, e); } diff --git a/src/main/java/io/lettuce/core/output/PendingMessageListOutput.java b/src/main/java/io/lettuce/core/output/PendingMessageListOutput.java index ed1c8957da..306ec4ce25 100644 --- a/src/main/java/io/lettuce/core/output/PendingMessageListOutput.java +++ b/src/main/java/io/lettuce/core/output/PendingMessageListOutput.java @@ -41,7 +41,7 @@ public PendingMessageListOutput(RedisCodec codec) { public void set(ByteBuffer bytes) { if (messageId == null) { - messageId = decodeAscii(bytes); + messageId = decodeString(bytes); return; } @@ -51,7 +51,7 @@ public void set(ByteBuffer bytes) { return; } - set(Long.parseLong(decodeAscii(bytes))); + set(Long.parseLong(decodeString(bytes))); } @Override diff --git a/src/main/java/io/lettuce/core/output/PendingMessagesOutput.java b/src/main/java/io/lettuce/core/output/PendingMessagesOutput.java index 3c66f6dc6e..b8c942a7f6 100644 --- a/src/main/java/io/lettuce/core/output/PendingMessagesOutput.java +++ b/src/main/java/io/lettuce/core/output/PendingMessagesOutput.java @@ -39,12 +39,12 @@ public PendingMessagesOutput(RedisCodec codec) { public void set(ByteBuffer bytes) { if (messageIdsFrom == null) { - messageIdsFrom = decodeAscii(bytes); + messageIdsFrom = decodeString(bytes); return; } if (messageIdsTo == null) { - messageIdsTo = decodeAscii(bytes); + messageIdsTo = decodeString(bytes); return; } @@ -54,7 +54,7 @@ public void set(ByteBuffer bytes) { return; } - set(Long.parseLong(decodeAscii(bytes))); + set(Long.parseLong(decodeString(bytes))); } @Override diff --git a/src/main/java/io/lettuce/core/output/ScanOutput.java b/src/main/java/io/lettuce/core/output/ScanOutput.java index 92a32aa21b..c6a9168778 100644 --- a/src/main/java/io/lettuce/core/output/ScanOutput.java +++ b/src/main/java/io/lettuce/core/output/ScanOutput.java @@ -24,7 +24,7 @@ public ScanOutput(RedisCodec codec, T cursor) { public void set(ByteBuffer bytes) { if (output.getCursor() == null) { - output.setCursor(decodeAscii(bytes)); + output.setCursor(decodeString(bytes)); if (LettuceStrings.isNotEmpty(output.getCursor()) && "0".equals(output.getCursor())) { output.setFinished(true); } diff --git a/src/main/java/io/lettuce/core/output/ScoredValueListOutput.java b/src/main/java/io/lettuce/core/output/ScoredValueListOutput.java index 2c2179683c..28c6ec9b2e 100644 --- a/src/main/java/io/lettuce/core/output/ScoredValueListOutput.java +++ b/src/main/java/io/lettuce/core/output/ScoredValueListOutput.java @@ -60,7 +60,7 @@ public void set(ByteBuffer bytes) { return; } - double score = LettuceStrings.toDouble(decodeAscii(bytes)); + double score = LettuceStrings.toDouble(decodeString(bytes)); set(score); } diff --git a/src/main/java/io/lettuce/core/output/ScoredValueOutput.java b/src/main/java/io/lettuce/core/output/ScoredValueOutput.java index 08d57798d7..c0db5d1b9c 100644 --- a/src/main/java/io/lettuce/core/output/ScoredValueOutput.java +++ b/src/main/java/io/lettuce/core/output/ScoredValueOutput.java @@ -37,7 +37,7 @@ public void set(ByteBuffer bytes) { return; } - double score = LettuceStrings.toDouble(decodeAscii(bytes)); + double score = LettuceStrings.toDouble(decodeString(bytes)); set(score); } diff --git a/src/main/java/io/lettuce/core/output/ScoredValueScanOutput.java b/src/main/java/io/lettuce/core/output/ScoredValueScanOutput.java index c7f8dc57e1..3e777df462 100644 --- a/src/main/java/io/lettuce/core/output/ScoredValueScanOutput.java +++ b/src/main/java/io/lettuce/core/output/ScoredValueScanOutput.java @@ -33,7 +33,7 @@ protected void setOutput(ByteBuffer bytes) { return; } - double score = LettuceStrings.toDouble(decodeAscii(bytes)); + double score = LettuceStrings.toDouble(decodeString(bytes)); set(score); } diff --git a/src/main/java/io/lettuce/core/output/ScoredValueScanStreamingOutput.java b/src/main/java/io/lettuce/core/output/ScoredValueScanStreamingOutput.java index 352febdaff..3a15f92156 100644 --- a/src/main/java/io/lettuce/core/output/ScoredValueScanStreamingOutput.java +++ b/src/main/java/io/lettuce/core/output/ScoredValueScanStreamingOutput.java @@ -35,7 +35,7 @@ protected void setOutput(ByteBuffer bytes) { return; } - double score = LettuceStrings.toDouble(decodeAscii(bytes)); + double score = LettuceStrings.toDouble(decodeString(bytes)); set(score); } diff --git a/src/main/java/io/lettuce/core/output/ScoredValueStreamingOutput.java b/src/main/java/io/lettuce/core/output/ScoredValueStreamingOutput.java index aecb443c7c..919d522faa 100644 --- a/src/main/java/io/lettuce/core/output/ScoredValueStreamingOutput.java +++ b/src/main/java/io/lettuce/core/output/ScoredValueStreamingOutput.java @@ -35,7 +35,7 @@ public void set(ByteBuffer bytes) { return; } - double score = LettuceStrings.toDouble(decodeAscii(bytes)); + double score = LettuceStrings.toDouble(decodeString(bytes)); set(score); } diff --git a/src/main/java/io/lettuce/core/output/SocketAddressOutput.java b/src/main/java/io/lettuce/core/output/SocketAddressOutput.java index b8cf652649..2a19ed04bb 100644 --- a/src/main/java/io/lettuce/core/output/SocketAddressOutput.java +++ b/src/main/java/io/lettuce/core/output/SocketAddressOutput.java @@ -26,12 +26,12 @@ public SocketAddressOutput(RedisCodec codec) { public void set(ByteBuffer bytes) { if (!hasHostname) { - hostname = decodeAscii(bytes); + hostname = decodeString(bytes); hasHostname = true; return; } - int port = Integer.parseInt(decodeAscii(bytes)); + int port = Integer.parseInt(decodeString(bytes)); output = InetSocketAddress.createUnresolved(hostname, port); } diff --git a/src/main/java/io/lettuce/core/output/StatusOutput.java b/src/main/java/io/lettuce/core/output/StatusOutput.java index 9024b01de6..f62d304888 100644 --- a/src/main/java/io/lettuce/core/output/StatusOutput.java +++ b/src/main/java/io/lettuce/core/output/StatusOutput.java @@ -41,7 +41,7 @@ public StatusOutput(RedisCodec codec) { @Override public void set(ByteBuffer bytes) { - output = OK.equals(bytes) ? "OK" : decodeAscii(bytes); + output = OK.equals(bytes) ? "OK" : decodeString(bytes); } } diff --git a/src/main/java/io/lettuce/core/output/StreamMessageListOutput.java b/src/main/java/io/lettuce/core/output/StreamMessageListOutput.java index c2d0b603ba..84493f80be 100644 --- a/src/main/java/io/lettuce/core/output/StreamMessageListOutput.java +++ b/src/main/java/io/lettuce/core/output/StreamMessageListOutput.java @@ -41,7 +41,7 @@ public StreamMessageListOutput(RedisCodec codec, K stream) { public void set(ByteBuffer bytes) { if (id == null) { - id = decodeAscii(bytes); + id = decodeString(bytes); return; } diff --git a/src/main/java/io/lettuce/core/output/StreamReadOutput.java b/src/main/java/io/lettuce/core/output/StreamReadOutput.java index 67186df93e..977b7bf86b 100644 --- a/src/main/java/io/lettuce/core/output/StreamReadOutput.java +++ b/src/main/java/io/lettuce/core/output/StreamReadOutput.java @@ -52,7 +52,7 @@ public void set(ByteBuffer bytes) { } if (id == null) { - id = decodeAscii(bytes); + id = decodeString(bytes); return; } diff --git a/src/main/java/io/lettuce/core/output/StringListOutput.java b/src/main/java/io/lettuce/core/output/StringListOutput.java index df60ecbfb3..eecdb773e8 100644 --- a/src/main/java/io/lettuce/core/output/StringListOutput.java +++ b/src/main/java/io/lettuce/core/output/StringListOutput.java @@ -32,7 +32,7 @@ public void set(ByteBuffer bytes) { multi(1); } - subscriber.onNext(output, bytes == null ? null : decodeAscii(bytes)); + subscriber.onNext(output, bytes == null ? null : decodeString(bytes)); } @Override diff --git a/src/main/java/io/lettuce/core/output/StringValueListOutput.java b/src/main/java/io/lettuce/core/output/StringValueListOutput.java index 12b0e46313..d69dbeaceb 100644 --- a/src/main/java/io/lettuce/core/output/StringValueListOutput.java +++ b/src/main/java/io/lettuce/core/output/StringValueListOutput.java @@ -30,7 +30,7 @@ public StringValueListOutput(RedisCodec codec) { @Override public void set(ByteBuffer bytes) { - subscriber.onNext(output, bytes == null ? Value.empty() : Value.fromNullable(decodeAscii(bytes))); + subscriber.onNext(output, bytes == null ? Value.empty() : Value.fromNullable(decodeString(bytes))); } @Override diff --git a/src/main/java/io/lettuce/core/protocol/CommandKeyword.java b/src/main/java/io/lettuce/core/protocol/CommandKeyword.java index 62c6e84cd0..c9d782afd8 100644 --- a/src/main/java/io/lettuce/core/protocol/CommandKeyword.java +++ b/src/main/java/io/lettuce/core/protocol/CommandKeyword.java @@ -49,11 +49,7 @@ public enum CommandKeyword implements ProtocolKeyword { MIGRATING, IMPORTING, SAVE, SKIPME, SLAVES, STREAM, STORE, SUM, SEGFAULT, SETUSER, TAKEOVER, TRACKING, TRACKINGINFO, TYPE, UNBLOCK, USERS, USAGE, WEIGHTS, WHOAMI, - WITHMATCHLEN, WITHSCORE, WITHSCORES, WITHVALUES, XOR, XX, YES, INDENT, NEWLINE, SPACE, GT, LT, - - MAXTEXTFIELDS, PREFIX, FILTER, LANGUAGE, LANGUAGE_FIELD, SCORE, SCORE_FIELD, PAYLOAD_FIELD, TEMPORARY, NOOFFSETS, NOHL, NOFIELDS, NOFREQS, SKIPINITIALSCAN, STOPWORDS, AS, SORTABLE, SCHEMA, UNF, NOINDEX, - - NOSTEM, PHONETIC, WEIGHT, SEPARATOR, CASESENSITIVE, WITHSUFFIXTRIE, INDEXEMPTY, INDEXMISSING; + WITHMATCHLEN, WITHSCORE, WITHSCORES, WITHVALUES, XOR, XX, YES, INDENT, NEWLINE, SPACE, GT, LT; public final byte[] bytes; diff --git a/src/main/java/io/lettuce/core/protocol/CommandType.java b/src/main/java/io/lettuce/core/protocol/CommandType.java index 3d5b9f4e9d..9a2fcf83f6 100644 --- a/src/main/java/io/lettuce/core/protocol/CommandType.java +++ b/src/main/java/io/lettuce/core/protocol/CommandType.java @@ -112,9 +112,6 @@ public enum CommandType implements ProtocolKeyword { "JSON.OBJLEN"), JSON_SET("JSON.SET"), JSON_STRAPPEND("JSON.STRAPPEND"), JSON_STRLEN( "JSON.STRLEN"), JSON_TOGGLE("JSON.TOGGLE"), JSON_TYPE("JSON.TYPE"), - // RediSearch - FT_CREATE("FT.CREATE"), - // Others TIME, WAIT, diff --git a/src/main/java/io/lettuce/core/pubsub/PubSubOutput.java b/src/main/java/io/lettuce/core/pubsub/PubSubOutput.java index 0ec41ff646..012e7e4fcb 100644 --- a/src/main/java/io/lettuce/core/pubsub/PubSubOutput.java +++ b/src/main/java/io/lettuce/core/pubsub/PubSubOutput.java @@ -93,7 +93,7 @@ public void set(ByteBuffer bytes) { } if (type == null) { - type = Type.valueOf(decodeAscii(bytes)); + type = Type.valueOf(decodeString(bytes)); return; } diff --git a/src/main/java/io/lettuce/core/search/DocumentLanguage.java b/src/main/java/io/lettuce/core/search/DocumentLanguage.java deleted file mode 100644 index ba3dd1d161..0000000000 --- a/src/main/java/io/lettuce/core/search/DocumentLanguage.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2025, Redis Ltd. and Contributors - * All rights reserved. - * - * Licensed under the MIT License. - */ -package io.lettuce.core.search; - -import java.util.Locale; - -/** - * Supported document languages. - * - * @since 6.6 - * @author Tihomir Mateev - * @see Stemming - */ -public enum DocumentLanguage { - - /** - * Arabic - */ - ARABIC("arabic", new Locale("ar")), - /** - * Armenian - */ - ARMENIAN("armenian", new Locale("hy")), - /** - * Danish - */ - DANISH("danish", new Locale("da")), - /** - * Dutch - */ - DUTCH("dutch", new Locale("nl")), - /** - * English - */ - ENGLISH("english", Locale.ENGLISH), - /** - * Finnish - */ - FINNISH("finnish", new Locale("fi")), - /** - * French - */ - FRENCH("french", Locale.FRENCH), - /** - * German - */ - GERMAN("german", Locale.GERMAN), - /** - * Hungarian - */ - HUNGARIAN("hungarian", new Locale("hu")), - /** - * Italian - */ - ITALIAN("italian", Locale.ITALIAN), - /** - * Norwegian - */ - NORWEGIAN("norwegian", new Locale("no")), - /** - * Portuguese - */ - PORTUGUESE("portuguese", new Locale("pt")), - /** - * Romanian - */ - ROMANIAN("romanian", new Locale("ro")), - /** - * Russian - */ - RUSSIAN("russian", new Locale("ru")), - /** - * Serbian - */ - SERBIAN("serbian", new Locale("sr")), - /** - * Spanish - */ - SPANISH("spanish", new Locale("es")), - /** - * Swedish - */ - SWEDISH("swedish", new Locale("sv")), - /** - * Tamil - */ - TAMIL("tamil", new Locale("ta")), - /** - * Turkish - */ - TURKISH("turkish", new Locale("tr")), - /** - * Yiddish - */ - YIDDISH("yiddish", new Locale("yi")), - /** - * Chinese - * - * @see Chinese - * support - */ - CHINESE("chinese", Locale.CHINESE); - - private final String language; - - private final Locale locale; - - DocumentLanguage(String language, Locale locale) { - this.language = language; - this.locale = locale; - } - - @Override - public String toString() { - return language; - } - - /** - * @return the {@link DocumentLanguage} as a {@link Locale} - */ - public Locale getLocale() { - return locale; - } - - /** - * Retrieve the {@link DocumentLanguage} for a given {@link Locale}. - * - * @param locale the locale - * @return the {@link DocumentLanguage} - */ - public static DocumentLanguage getLanguage(Locale locale) { - for (DocumentLanguage language : DocumentLanguage.values()) { - if (language.getLocale().getLanguage().equals(locale.getLanguage())) { - return language; - } - } - throw new UnsupportedOperationException("No language found for locale: " + locale); - } - -} diff --git a/src/main/java/io/lettuce/core/search/Field.java b/src/main/java/io/lettuce/core/search/Field.java deleted file mode 100644 index 51049936d0..0000000000 --- a/src/main/java/io/lettuce/core/search/Field.java +++ /dev/null @@ -1,522 +0,0 @@ -/* - * Copyright 2025, Redis Ltd. and Contributors - * All rights reserved. - * - * Licensed under the MIT License. - */ -package io.lettuce.core.search; - -import io.lettuce.core.protocol.CommandArgs; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static io.lettuce.core.protocol.CommandKeyword.*; - -/** - * Representation of a field in a RediSearch index. - * - * @param Key type - * @see Field - * and type options - * @since 6.6 - * @author Tihomir Mateev - */ -@SuppressWarnings("OptionalUsedAsFieldOrParameterType") -public class Field { - - /** - * Field types - * - * @see Field and - * type options - */ - public enum Type { - /** - * Allows full-text search queries against the value in this attribute. - */ - TEXT, - /** - * Allows exact-match queries, 1 as categories or primary keys, against the value in this attribute. - * - * @see Tag Fields - */ - TAG, - /** - * Allows numeric range queries against the value in this attribute. See query syntax docs for details on how to use - * numeric ranges. - */ - NUMERIC, - /** - * Allows radius range queries against the value (point) in this attribute. The value of the attribute must be a string - * containing a longitude (first) and latitude separated by a comma. - */ - GEO, - /** - * Allows vector queries against the value in this attribute. Requires query dialect 2 or above (introduced in - * RediSearch v2.4). - * - * @see Vector - * Fields - * @see Query - * Dialect v2 - */ - VECTOR, - /** - * Allows polygon queries against the value in this attribute. The value of the attribute must follow a - * WKT notation list of 2D points - * representing the polygon edges POLYGON((x1 y1, x2 y2, ...) separated by a comma. - *

- * A GEOSHAPE field type can be followed by one of the following coordinate systems: - *

    - *
  • SPHERICAL for Geographic longitude and latitude coordinates
  • - *
  • FLAT for Cartesian X Y coordinates
  • - *
  • The default coordinate system is SPHERICAL.
  • - *
- * - * Currently GEOSHAPE doesn't support JSON multi-value and SORTABLE option. - */ - GEOSHAPE - } - - /** - * Phonetic matchers - * - * @see Phonetic - * Matching - */ - public enum PhoneticMatcher { - - ENGLISH("dm:en"), FRENCH("dm:fr"), PORTUGUESE("dm:pt"), SPANISH("dm:es"); - - PhoneticMatcher(String matcher) { - this.matcher = matcher; - } - - private final String matcher; - - /** - * @return the {@link String} representation of the matcher - */ - public String getMatcher() { - return matcher; - } - - } - - private K name; - - private Optional as = Optional.empty(); - - private Type type; - - private boolean sortable; - - private boolean unNormalizedForm; - - private boolean noStemming; - - private boolean noIndex; - - private Optional phonetic = Optional.empty();; - - private boolean caseSensitive; - - private boolean withSuffixTrie; - - private boolean indexEmpty; - - private boolean indexMissing; - - private Optional weight = Optional.empty();; - - private Optional separator = Optional.empty();; - - private Field() { - } - - /** - * Create a new {@link Field} using the builder pattern. - *

- * One needs to call {@link Builder#build()} to build a single {@link Field} or {@link Builder#buildFields()} to build a - * {@link java.util.List} of {@link Field}s. - * - * @param Key type - * @return a new {@link Builder} - */ - public static Builder builder() { - return new Builder<>(); - } - - /** - * Builder for {@link Field}. - * - * @param Key type - */ - public static class Builder { - - private final Field instance = new Field<>(); - - /** - * The name of the field in a hash the index is going to be based on. - * - * @param name the name of the field - * @return the instance of the {@link Builder} for the purpose of method chaining - */ - public Builder name(K name) { - instance.name = name; - return this; - } - - // TODO handling JsonPath - // public Builder name(JsonPath path) { - // instance.name = path.toString(); - // return this; - // } - - /** - * The type of the field. - * - * @param type the type of the field - * @return the instance of the {@link Builder} for the purpose of method chaining - * @see Type - */ - public Builder type(Type type) { - instance.type = type; - return this; - } - - /** - * Defines the attribute associated to the identifier. For example, you can use this feature to alias a complex JSONPath - * expression with more memorable (and easier to type) name. - * - * @param as the field name to be used in queries - * @return the instance of the {@link Builder} for the purpose of method chaining - */ - public Builder as(K as) { - instance.as = Optional.of(as); - return this; - } - - /** - * {@link Type#NUMERIC}, {@link Type#TAG}, {@link Type#TEXT}, or {@link Type#GEO} attributes can have an optional - * SORTABLE argument. As the user sorts the results by the value of this attribute, the results are available with very - * low latency. Default is false (not sortable). - *

- * Note that this adds memory overhead, so consider not declaring it on large text attributes. You can sort an attribute - * without the SORTABLE option, but the latency is not as good as with SORTABLE. - * - * @return the instance of the {@link Builder} for the purpose of method chaining - */ - public Builder sortable() { - instance.sortable = true; - return this; - } - - /** - * By default, for hashes (not with JSON) SORTABLE applies normalization to the indexed value (characters set to - * lowercase, removal of diacritics). When using the unnormalized form (UNF), you can disable the normalization and keep - * the original form of the value. With JSON, UNF is implicit with SORTABLE (normalization is disabled). - *

- * Default is false (normalized form). - * - * @return the instance of the {@link Builder} for the purpose of method chaining - */ - public Builder unNormalizedForm() { - instance.sortable = true; - instance.unNormalizedForm = true; - return this; - } - - /** - * By default, the index applies stemming to {@link Type#TEXT} fields. If you don't want to apply stemming to the field, - * you can use the NOSTEM argument. This may be ideal for things like proper names. - * - * @return the instance of the {@link Builder} for the purpose of method chaining - */ - public Builder noStemming() { - instance.noStemming = true; - return this; - } - - /** - * Attributes can have the NOINDEX option, which means they will not be indexed. This is useful in conjunction with - * {@link Builder#sortable()}, to create attributes whose update using PARTIAL will not cause full reindexing of the - * document. If an attribute has NOINDEX and doesn't have SORTABLE, it will just be ignored by the index. - * - * @return the instance of the {@link Builder} for the purpose of method chaining - */ - public Builder noIndex() { - instance.noIndex = true; - return this; - } - - /** - * Phonetic matching is a feature that allows you to search for similar-sounding words. For example, a search for - * "Smith" will also return results for "Smyth". Phonetic matching is language-specific, and you can specify the - * language using the PHONETIC argument. - *

- * The following languages are supported: - *

    - *
  • ENGLISH
  • - *
  • FRENCH
  • - *
  • PORTUGUESE
  • x - *
  • SPANISH
  • - *
- * - * @see Phonetic - * Matching - * @param matcher the phonetic matcher - * @return the instance of the {@link Builder} for the purpose of method chaining - */ - public Builder phonetic(PhoneticMatcher matcher) { - instance.phonetic = Optional.of(matcher); - return this; - } - - /** - * The weight of the field. Works with {@link Type#TEXT} attributes, declares the importance of this attribute when - * calculating result accuracy. This is a multiplication factor. The default weight is 1. - * - * @param weight the weight of the field - * @return the instance of the {@link Builder} for the purpose of method chaining - */ - public Builder weight(long weight) { - instance.weight = Optional.of(weight); - return this; - } - - /** - * The separator for {@link Type#TAG} attributes. The default separator is a comma. - * - * @param separator the separator for tag fields - * @return the instance of the {@link Builder} for the purpose of method chaining - */ - public Builder separator(String separator) { - instance.separator = Optional.of(separator); - return this; - } - - /** - * Keeps the original letter cases of the tags. If not specified, the characters are converted to lowercase. Works with - * {@link Type#TAG} attributes. - * - * @return the instance of the {@link Builder} for the purpose of method chaining - */ - public Builder caseSensitive() { - instance.caseSensitive = true; - return this; - } - - /** - * For {@link Type#TEXT} and {@link Type#TAG} attributes, keeps a suffix trie with all terms which match the suffix. It - * is used to optimize contains (foo) and suffix (*foo) queries. Otherwise, a brute-force search on the trie is - * performed. If the suffix trie exists for some fields, these queries will be disabled for other fields. - * - * @return the instance of the {@link Builder} for the purpose of method chaining - */ - public Builder withSuffixTrie() { - instance.withSuffixTrie = true; - return this; - } - - /** - * For {@link Type#TEXT} and {@link Type#TAG} attributes, introduced in v2.10, allows you to index and search for empty - * strings. By default, empty strings are not indexed. - * - * @return the instance of the {@link Builder} for the purpose of method chaining - */ - public Builder indexEmpty() { - instance.indexEmpty = true; - return this; - } - - /** - * For all field types, introduced in v2.10, allows you to search for missing values, that is, documents that do not - * contain a specific field. Note the difference between a field with an empty value and a document with a missing - * value. By default, missing values are not indexed. - */ - public Builder indexMissing() { - instance.indexMissing = true; - return this; - } - - /** - * Build a single {@link Field}. - * - * @return the instance of the {@link Field} - */ - public Field build() { - return instance; - } - - /** - * Build a {@link java.util.List} of {@link Field}s, containing the current {@link Field} as the only element of the - * list. - * - * @return the instance of the {@link Field} - */ - public List> buildFields() { - List> fields = new ArrayList<>(); - fields.add(instance); - return fields; - } - - } - - /** - * @return the type of the field - * @see Builder#type(Type) - */ - public Type getType() { - return type; - } - - /** - * @return the name of the field - * @see Builder#name(Object) - */ - public K getName() { - return name; - } - - /** - * @return the alias of the field - * @see Builder#as(Object) - */ - public Optional getAs() { - return as; - } - - /** - * @return if the field should be sortable - * @see Builder#sortable() - */ - public boolean isSortable() { - return sortable; - } - - /** - * @return if the field should be in unnormalized form - * @see Builder#unNormalizedForm() - */ - public boolean isUnNormalizedForm() { - return unNormalizedForm; - } - - /** - * @return if the field should not be indexed - * @see Builder#noIndex() - */ - public boolean isNoIndex() { - return noIndex; - } - - /** - * @return if the field should not be stemmed - * @see Builder#noStemming() - */ - public boolean isNoStemming() { - return noStemming; - } - - /** - * @return the setting for phonetic matching - * @see Builder#phonetic(PhoneticMatcher) - */ - public Optional isPhonetic() { - return phonetic; - } - - /** - * @return if the field should be case sensitive - * @see Builder#caseSensitive() - */ - public boolean isCaseSensitive() { - return caseSensitive; - } - - /** - * @return if the field should have a suffix trie - * @see Builder#withSuffixTrie() - */ - public boolean isWithSuffixTrie() { - return withSuffixTrie; - } - - /** - * @return if the field should index empty values - * @see Builder#indexEmpty() - */ - public boolean isIndexEmpty() { - return indexEmpty; - } - - /** - * @return if the field should index missing values - * @see Builder#indexMissing() - */ - public boolean isIndexMissing() { - return indexMissing; - } - - /** - * @return the weight of the field - * @see Builder#weight(long) - */ - public Optional getWeight() { - return weight; - } - - /** - * @return the separator for tag fields - * @see Builder#separator(String) - */ - public Optional getSeparator() { - return separator; - } - - /** - * Add all configured arguments to the final command - * - * @param args the command arguments to modify - */ - public void build(CommandArgs args) { - args.addKey(name); - as.ifPresent(a -> args.add(AS).addKey(a)); - args.add(type.toString()); - if (sortable) { - args.add(SORTABLE); - if (unNormalizedForm) { - args.add(UNF); - } - } - if (noStemming) { - args.add(NOSTEM); - } - if (noIndex) { - args.add(NOINDEX); - } - phonetic.ifPresent(p -> args.add(PHONETIC).add(p.getMatcher())); - weight.ifPresent(w -> args.add(WEIGHT).add(w)); - separator.ifPresent(s -> args.add(SEPARATOR).add(s)); - if (caseSensitive) { - args.add(CASESENSITIVE); - } - if (withSuffixTrie) { - args.add(WITHSUFFIXTRIE); - } - if (indexEmpty) { - args.add(INDEXEMPTY); - } - if (indexMissing) { - args.add(INDEXMISSING); - } - } - -} diff --git a/src/main/java/io/lettuce/core/search/arguments/CreateArgs.java b/src/main/java/io/lettuce/core/search/arguments/CreateArgs.java deleted file mode 100644 index 2e61affba3..0000000000 --- a/src/main/java/io/lettuce/core/search/arguments/CreateArgs.java +++ /dev/null @@ -1,537 +0,0 @@ -/* - * Copyright 2025, Redis Ltd. and Contributors - * All rights reserved. - * - * Licensed under the MIT License. - */ -package io.lettuce.core.search.arguments; - -import io.lettuce.core.protocol.CommandArgs; -import io.lettuce.core.search.DocumentLanguage; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.OptionalDouble; -import java.util.OptionalLong; - -import static io.lettuce.core.protocol.CommandKeyword.*; - -/** - * Argument list builder for {@code FT.CREATE}. - * - * @param Key type. - * @param Value type. - * @see FT.CREATE - * @since 6.6 - * @author Tihomir Mateev - */ -@SuppressWarnings("OptionalUsedAsFieldOrParameterType") -public class CreateArgs { - - /** - * Possible target types for the index. - */ - public enum TargetType { - HASH, JSON - } - - private Optional on = Optional.of(TargetType.HASH); - - private final List prefixes = new ArrayList<>(); - - private Optional filter = Optional.empty(); - - private Optional defaultLanguage = Optional.empty(); - - private Optional languageField = Optional.empty(); - - private OptionalDouble defaultScore = OptionalDouble.empty(); - - private Optional scoreField = Optional.empty(); - - private Optional payloadField = Optional.empty(); - - private boolean maxTextFields; - - private OptionalLong temporary = OptionalLong.empty(); - - private boolean noOffsets; - - private boolean noHighlight; - - private boolean noFields; - - private boolean noFrequency; - - private boolean skipInitialScan; - - private Optional> stopWords = Optional.empty(); - - /** - * Used to build a new instance of the {@link CreateArgs}. - * - * @return a {@link Builder} that provides the option to build up a new instance of the {@link CreateArgs} - * @param the key type - * @param the value type - */ - public static Builder builder() { - return new Builder<>(); - } - - /** - * Builder for {@link CreateArgs}. - *

- * As a final step the {@link Builder#build()} method needs to be executed to create the final {@link CreateArgs} instance. - * - * @param the key type - * @param the value type - * @see FT.CREATE - */ - public static class Builder { - - private final CreateArgs instance = new CreateArgs<>(); - - /** - * Set the {@link TargetType} type for the index. Defaults to {@link TargetType#HASH}. - * - * @param targetType the target type - * @return the instance of the current {@link Builder} for the purpose of method chaining - */ - public Builder on(TargetType targetType) { - instance.on = Optional.of(targetType); - return this; - } - - /** - * Add a prefix to the index. You can add several prefixes to index. Default setting is * (all keys). - * - * @param prefix the prefix - * @return the instance of the current {@link Builder} for the purpose of method chaining - * @see {@link Builder#addPrefixes(List)} - */ - public Builder addPrefix(K prefix) { - instance.prefixes.add(prefix); - return this; - } - - /** - * Add a list of prefixes to the index. You can add several prefixes to index. Default setting is * (all keys). - * - * @param prefixes a {@link List} of prefixes - * @return the instance of the current {@link Builder} for the purpose of method chaining - */ - public Builder addPrefixes(List prefixes) { - instance.prefixes.addAll(prefixes); - return this; - } - - /** - * Set a filter for the index. Default setting is to have no filter. - *

- * It is possible to use @__key to access the key that was just added/changed. A field can be used to set field name by - * passing 'FILTER @indexName=="myindexname"'. - * - * @param filter a filter expression with the full RediSearch aggregation expression language - * @return the instance of the current {@link Builder} for the purpose of method chaining - * @see RediSearch Query - */ - public Builder filter(V filter) { - instance.filter = Optional.of(filter); - return this; - } - - /** - * Set the default language for the documents in the index. The default setting is English. - * - * @param language the default language - * @return the instance of the current {@link Builder} for the purpose of method chaining - */ - public Builder defaultLanguage(DocumentLanguage language) { - instance.defaultLanguage = Optional.of(language); - return this; - } - - /** - * Set the field that contains the language setting for the documents in the index. The default setting is to have no - * language field. - * - * @param field the language field - * @return the instance of the current {@link Builder} for the purpose of method chaining - * @see Stemming - */ - public Builder languageField(K field) { - instance.languageField = Optional.of(field); - return this; - } - - /** - * Set the default score for the documents in the index. The default setting is 1.0. - * - * @param score the default score - * @return the instance of the current {@link Builder} for the purpose of method chaining - * @see Scoring - */ - public Builder defaultScore(double score) { - instance.defaultScore = OptionalDouble.of(score); - return this; - } - - /** - * Set the field that contains the score setting for the documents in the index. The default setting is a score of 1.0. - * - * @param field the score field - * @return the instance of the current {@link Builder} for the purpose of method chaining - * @see Scoring - */ - public Builder scoreField(K field) { - instance.scoreField = Optional.of(field); - return this; - } - - /** - * Set the field that contains the payload setting for the documents in the index. The default setting is to have no - * payload field. - *

- * This should be a document attribute that you use as a binary safe payload string to the document that can be - * evaluated at query time by a custom scoring function or retrieved to the client - * - * @param field the payload field - * @return the instance of the current {@link Builder} for the purpose of method chaining - * @see Scoring - */ - public Builder payloadField(K field) { - instance.payloadField = Optional.of(field); - return this; - } - - /** - * Set the maximum number of text fields in the index. The default setting is to have no limit. - *

- * Forces RediSearch to encode indexes as if there were more than 32 text attributes, which allows you to add additional - * attributes (beyond 32) using FT.ALTER. For efficiency, RediSearch encodes indexes differently if they are created - * with less than 32 text attributes. - * - * @param maxTextFields the maximum number of text fields - * @return the instance of the current {@link Builder} for the purpose of method chaining - */ - public Builder maxTextFields(boolean maxTextFields) { - instance.maxTextFields = maxTextFields; - return this; - } - - /** - * Set the temporary index expiration time in seconds. The default setting is to have no expiration time. - *

- * Creates a lightweight temporary index that expires after a specified period of inactivity, in seconds. The internal - * idle timer is reset whenever the index is searched or added to. Because such indexes are lightweight, you can create - * thousands of such indexes without negative performance implications and, therefore, you should consider using - * {@link Builder#skipInitialScan(boolean)} to avoid costly scanning. - *

- * Warning: When temporary indexes expire, they drop all the records associated with them. FT.DROPINDEX was introduced - * with a default of not deleting docs and a DD flag that enforced deletion. However, for temporary indexes, documents - * are deleted along with the index. Historically, RediSearch used an FT.ADD command, which made a connection between - * the document and the index. Then, FT.DROP, also a hystoric command, deleted documents by default. In version 2.x, - * RediSearch indexes hashes and JSONs, and the dependency between the index and documents no longer exists. - * - * @param seconds the temporary index expiration time in seconds - * @return the instance of the current {@link Builder} for the purpose of method chaining - */ - public Builder temporary(long seconds) { - instance.temporary = OptionalLong.of(seconds); - return this; - } - - /** - * Set the no offsets flag. The default setting is to have offsets. - *

- * It saves memory, but does not allow exact searches or highlighting. It implies - * {@link Builder#noHighlighting(boolean)} is set to true. - * - * @param noOffsets the no offsets flag - * @return the instance of the current {@link Builder} for the purpose of method chaining - */ - public Builder noOffsets(boolean noOffsets) { - instance.noOffsets = noOffsets; - return this; - } - - /** - * Set the no highlighting flag. The default setting is to have highlighting. - *

- * Conserves storage space and memory by disabling highlighting support. If set, the corresponding byte offsets for term - * positions are not stored. NOHL is also implied by NOOFFSETS. - * - * @param noHL the no highlighting flag - * @return the instance of the current {@link Builder} for the purpose of method chaining - */ - public Builder noHighlighting(boolean noHL) { - instance.noHighlight = noHL; - return this; - } - - /** - * Set the no fields flag. The default setting is to have fields. - *

- * Does not store attribute bits for each term. It saves memory, but it does not allow filtering by specific attributes. - * - * @param noFields the no fields flag - * @return the instance of the current {@link Builder} for the purpose of method chaining - */ - public Builder noFields(boolean noFields) { - instance.noFields = noFields; - return this; - } - - /** - * Set the no frequency flag. The default setting is to have frequencies. - *

- * Does not store the frequency of each term. It saves memory, but it does not allow sorting by frequency of a given - * term. - * - * @param noFreqs the no frequency flag - * @return the instance of the current {@link Builder} for the purpose of method chaining - */ - public Builder noFrequency(boolean noFreqs) { - instance.noFrequency = noFreqs; - return this; - } - - /** - * Set the skip initial scan flag. The default setting is to scan initially. - * - * @param skipInitialScan the skip initial scan flag - * @return the instance of the current {@link Builder} for the purpose of method chaining - */ - public Builder skipInitialScan(boolean skipInitialScan) { - instance.skipInitialScan = skipInitialScan; - return this; - } - - /** - * Set the index with a custom stopword list, to be ignored during indexing and search time. - *

- * If not set, FT.CREATE takes the default list of stopwords. If {count} is set to 0, the index does not have stopwords. - * - * @param stopWords a list of stop words - * @return the instance of the current {@link Builder} for the purpose of method chaining - * @see Stop - * words - */ - public Builder stopWords(List stopWords) { - instance.stopWords = Optional.of(stopWords); - return this; - } - - public CreateArgs build() { - return instance; - } - - } - - /** - * Get the target type for the index. - * - * @return the target type - * @see TargetType - * @see Builder#on(TargetType) - */ - public Optional getOn() { - return on; - } - - /** - * Get the prefixes for the index. - * - * @return the prefixes - * @see Builder#addPrefix(Object) - * @see Builder#addPrefixes(List) - */ - public List getPrefixes() { - return prefixes; - } - - /** - * Get the filter for the index. - * - * @return the filter - * @see Builder#filter(Object) - */ - public Optional getFilter() { - return filter; - } - - /** - * Get the default language for the documents in the index. - * - * @return the default language - * @see Builder#defaultLanguage(DocumentLanguage) - */ - public Optional getDefaultLanguage() { - return defaultLanguage; - } - - /** - * Get the field that contains the language setting for the documents in the index. - * - * @return the language field - * @see Builder#languageField(Object) - */ - public Optional getLanguageField() { - return languageField; - } - - /** - * Get the default score for the documents in the index. - * - * @return the default score - * @see Builder#defaultScore(double) - */ - public OptionalDouble getDefaultScore() { - return defaultScore; - } - - /** - * Get the field that contains the score setting for the documents in the index. - * - * @return the score field - * @see Builder#scoreField(Object) - */ - public Optional getScoreField() { - return scoreField; - } - - /** - * Get the field that contains the payload setting for the documents in the index. - * - * @return the payload field - * @see Builder#payloadField(Object) - */ - public Optional getPayloadField() { - return payloadField; - } - - /** - * Get the maximum number of text fields in the index. - * - * @return the maximum number of text fields - * @see Builder#maxTextFields(boolean) - */ - public boolean isMaxTextFields() { - return maxTextFields; - } - - /** - * Get the temporary index expiration time in seconds. - * - * @return the temporary index expiration time in seconds - * @see Builder#temporary(long) - */ - public OptionalLong getTemporary() { - return temporary; - } - - /** - * Get the no offsets flag. - * - * @return the no offsets flag - * @see Builder#noOffsets(boolean) - */ - public boolean isNoOffsets() { - return noOffsets; - } - - /** - * Get the no highlighting flag. - * - * @return the no highlighting flag - * @see Builder#noHighlighting(boolean) - */ - public boolean isNoHighlight() { - return noHighlight; - } - - /** - * Get the no fields flag. - * - * @return the no fields flag - * @see Builder#noFields(boolean) - */ - public boolean isNoFields() { - return noFields; - } - - /** - * Get the no frequency flag. - * - * @return the no frequency flag - * @see Builder#noFrequency(boolean) - */ - public boolean isNoFrequency() { - return noFrequency; - } - - /** - * Get the skip initial scan flag. - * - * @return the skip initial scan flag - * @see Builder#skipInitialScan(boolean) - */ - public boolean isSkipInitialScan() { - return skipInitialScan; - } - - /** - * Get the stop words for the index. - * - * @return the stop words - * @see Builder#stopWords(List) - */ - public Optional> getStopWords() { - return stopWords; - } - - /** - * Build a {@link CommandArgs} object that contains all the arguments. - * - * @param args the {@link CommandArgs} object - */ - public void build(CommandArgs args) { - on.ifPresent(targetType -> args.add(ON).add(targetType.name())); - if (!prefixes.isEmpty()) { - args.add(PREFIX).add(prefixes.size()); - prefixes.forEach(args::addKey); - } - filter.ifPresent(filter -> args.add(FILTER).addValue(filter)); - defaultLanguage.ifPresent(language -> args.add(LANGUAGE).add(language.toString())); - languageField.ifPresent(field -> args.add(LANGUAGE_FIELD).addKey(field)); - defaultScore.ifPresent(score -> args.add(SCORE).add(score)); - scoreField.ifPresent(field -> args.add(SCORE_FIELD).addKey(field)); - payloadField.ifPresent(field -> args.add(PAYLOAD_FIELD).addKey(field)); - if (maxTextFields) { - args.add(MAXTEXTFIELDS); - } - temporary.ifPresent(seconds -> args.add(TEMPORARY).add(seconds)); - if (noOffsets) { - args.add(NOOFFSETS); - } - if (noHighlight) { - args.add(NOHL); - } - if (noFields) { - args.add(NOFIELDS); - } - if (noFrequency) { - args.add(NOFREQS); - } - if (skipInitialScan) { - args.add(SKIPINITIALSCAN); - } - stopWords.ifPresent(words -> { - args.add(STOPWORDS).add(words.size()); - words.forEach(args::addValue); - }); - } - -} diff --git a/src/main/java/io/lettuce/core/search/package-info.java b/src/main/java/io/lettuce/core/search/package-info.java deleted file mode 100644 index 0d4f7f5cdd..0000000000 --- a/src/main/java/io/lettuce/core/search/package-info.java +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright 2025, Redis Ltd. and Contributors - * All rights reserved. - * - * Licensed under the MIT License. - */ -/** - * Support for the RediSearch features. - */ -package io.lettuce.core.search; diff --git a/src/main/java/io/lettuce/core/sentinel/RedisSentinelReactiveCommandsImpl.java b/src/main/java/io/lettuce/core/sentinel/RedisSentinelReactiveCommandsImpl.java index e02cf4d960..af5937d4e2 100644 --- a/src/main/java/io/lettuce/core/sentinel/RedisSentinelReactiveCommandsImpl.java +++ b/src/main/java/io/lettuce/core/sentinel/RedisSentinelReactiveCommandsImpl.java @@ -21,6 +21,7 @@ import java.net.SocketAddress; import java.util.Map; +import java.util.function.Supplier; import io.lettuce.core.AbstractRedisReactiveCommands; import io.lettuce.core.ClientListArgs; @@ -52,7 +53,7 @@ public class RedisSentinelReactiveCommandsImpl extends AbstractRedisReacti private final SentinelCommandBuilder commandBuilder; public RedisSentinelReactiveCommandsImpl(StatefulConnection connection, RedisCodec codec, - Mono parser) { + Supplier parser) { super(connection, codec, parser); commandBuilder = new SentinelCommandBuilder(codec); } diff --git a/src/main/java/io/lettuce/core/sentinel/StatefulRedisSentinelConnectionImpl.java b/src/main/java/io/lettuce/core/sentinel/StatefulRedisSentinelConnectionImpl.java index f5a9a9c625..2eb2090461 100644 --- a/src/main/java/io/lettuce/core/sentinel/StatefulRedisSentinelConnectionImpl.java +++ b/src/main/java/io/lettuce/core/sentinel/StatefulRedisSentinelConnectionImpl.java @@ -21,6 +21,7 @@ import java.time.Duration; import java.util.Collection; +import java.util.function.Supplier; import io.lettuce.core.ConnectionState; import io.lettuce.core.RedisChannelHandler; @@ -34,7 +35,6 @@ import io.lettuce.core.sentinel.api.async.RedisSentinelAsyncCommands; import io.lettuce.core.sentinel.api.reactive.RedisSentinelReactiveCommands; import io.lettuce.core.sentinel.api.sync.RedisSentinelCommands; -import reactor.core.publisher.Mono; import static io.lettuce.core.ClientOptions.DEFAULT_JSON_PARSER; @@ -74,7 +74,7 @@ public StatefulRedisSentinelConnectionImpl(RedisChannelWriter writer, RedisCodec * @param parser the parser used to parse JSON responses */ public StatefulRedisSentinelConnectionImpl(RedisChannelWriter writer, RedisCodec codec, Duration timeout, - Mono parser) { + Supplier parser) { super(writer, timeout); diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RediSearchCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RediSearchCoroutinesCommands.kt deleted file mode 100644 index f6a24da5a0..0000000000 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RediSearchCoroutinesCommands.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2025, Redis Ltd. and Contributors - * All rights reserved. - * - * Licensed under the MIT License. - */ - -package io.lettuce.core.api.coroutines - -import io.lettuce.core.ExperimentalLettuceCoroutinesApi -import kotlinx.coroutines.flow.Flow -import io.lettuce.core.search.Field -import io.lettuce.core.search.arguments.CreateArgs - -/** - * Coroutine executed commands for RediSearch functionality - * - * @param Key type. - * @param Value type. - * @author Tihomir Mateev - * @see RediSearch - * @since 6.6 - * @generated by io.lettuce.apigenerator.CreateKotlinCoroutinesApi - */ -@ExperimentalLettuceCoroutinesApi -interface RediSearchCoroutinesCommands { - - /** - * Create a new index with the given name, index options, and fields. - * - * @param index the index name, as a key - * @param options the index [CreateArgs] - * @param fields the [Field]s of the index - * @return the result of the create command - * @since 6.6 - * @see FT.CREATE - */ - suspend fun ftCreate(index: K, options: CreateArgs, fields: List>): String? - -} - diff --git a/src/main/templates/io/lettuce/core/api/RediSearchCommands.java b/src/main/templates/io/lettuce/core/api/RediSearchCommands.java deleted file mode 100644 index 9c348db9c9..0000000000 --- a/src/main/templates/io/lettuce/core/api/RediSearchCommands.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2025, Redis Ltd. and Contributors - * All rights reserved. - * - * Licensed under the MIT License. - */ -package io.lettuce.core.api; - -import io.lettuce.core.search.Field; -import io.lettuce.core.search.arguments.CreateArgs; - -import java.util.List; - -/** - * ${intent} for RediSearch functionality - * - * @param Key type. - * @param Value type. - * @author Tihomir Mateev - * @see RediSearch - * @since 6.6 - */ -public interface RediSearchCommands { - - /** - * Create a new index with the given name, index options, and fields. - * - * @param index the index name, as a key - * @param options the index {@link CreateArgs} - * @param fields the {@link Field}s of the index - * @return the result of the create command - * @since 6.6 - * @see FT.CREATE - */ - String ftCreate(K index, CreateArgs options, List> fields); - -} diff --git a/src/test/bash/create_certificates.sh b/src/test/bash/create_certificates.sh deleted file mode 100755 index 28db1eb517..0000000000 --- a/src/test/bash/create_certificates.sh +++ /dev/null @@ -1,131 +0,0 @@ -#!/bin/bash - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -CA_DIR=work/ca -TRUSTSTORE_FILE=work/truststore.jks -KEYSTORE_FILE=work/keystore.jks - -if [[ -d work/ca ]] ; then - rm -Rf ${CA_DIR} -fi - -if [[ -f ${TRUSTSTORE_FILE} ]] ; then - rm -Rf ${TRUSTSTORE_FILE} -fi - -if [[ -f ${KEYSTORE_FILE} ]] ; then - rm -Rf ${KEYSTORE_FILE} -fi - -if [ ! -x "$(which openssl)" ] ; then - echo "[ERROR] No openssl in PATH" - exit 1 -fi - -KEYTOOL=keytool - -if [ ! -x "${KEYTOOL}" ] ; then - KEYTOOL=${JAVA_HOME}/bin/keytool -fi - -if [ ! -x "${KEYTOOL}" ] ; then - echo "[ERROR] No keytool in PATH/JAVA_HOME" - exit 1 -fi - -mkdir -p ${CA_DIR}/private ${CA_DIR}/certs ${CA_DIR}/crl ${CA_DIR}/csr ${CA_DIR}/newcerts ${CA_DIR}/intermediate - -echo "[INFO] Generating CA private key" -# Less bits = less secure = faster to generate -openssl genrsa -passout pass:changeit -aes256 -out ${CA_DIR}/private/ca.key.pem 2048 - -chmod 400 ${CA_DIR}/private/ca.key.pem - -echo "[INFO] Generating CA certificate" -openssl req -config ${DIR}/openssl.cnf \ - -key ${CA_DIR}/private/ca.key.pem \ - -new -x509 -days 7300 -sha256 -extensions v3_ca \ - -out ${CA_DIR}/certs/ca.cert.pem \ - -passin pass:changeit \ - -subj "/C=NN/ST=Unknown/L=Unknown/O=lettuce/CN=CA Certificate" - -echo "[INFO] Prepare CA database" -echo 1000 > ${CA_DIR}/serial -touch ${CA_DIR}/index.txt - -function generateKey { - - host=$1 - ip=$2 - - echo "[INFO] Generating server private key" - openssl genrsa -aes256 \ - -passout pass:changeit \ - -out ${CA_DIR}/private/${host}.key.pem 2048 - - openssl rsa -in ${CA_DIR}/private/${host}.key.pem \ - -out ${CA_DIR}/private/${host}.decrypted.key.pem \ - -passin pass:changeit - - chmod 400 ${CA_DIR}/private/${host}.key.pem - chmod 400 ${CA_DIR}/private/${host}.decrypted.key.pem - - echo "[INFO] Generating server certificate request" - openssl req -config <(cat ${DIR}/openssl.cnf \ - <(printf "\n[SAN]\nsubjectAltName=DNS:${host},IP:${ip}")) \ - -reqexts SAN \ - -key ${CA_DIR}/private/${host}.key.pem \ - -passin pass:changeit \ - -new -sha256 -out ${CA_DIR}/csr/${host}.csr.pem \ - -subj "/C=NN/ST=Unknown/L=Unknown/O=lettuce/CN=${host}" - - echo "[INFO] Signing certificate request" - openssl ca -config ${DIR}/openssl.cnf \ - -extensions server_cert -days 375 -notext -md sha256 \ - -passin pass:changeit \ - -batch \ - -in ${CA_DIR}/csr/${host}.csr.pem \ - -out ${CA_DIR}/certs/${host}.cert.pem -} - -generateKey "localhost" "127.0.0.1" -generateKey "foo-host" "1.2.3.4" - -echo "[INFO] Generating client auth private key" -openssl genrsa -aes256 \ - -passout pass:changeit \ - -out ${CA_DIR}/private/client.key.pem 2048 - -openssl rsa -in ${CA_DIR}/private/client.key.pem \ - -out ${CA_DIR}/private/client.decrypted.key.pem \ - -passin pass:changeit - -chmod 400 ${CA_DIR}/private/client.key.pem - -echo "[INFO] Generating client certificate request" -openssl req -config ${DIR}/openssl.cnf \ - -key ${CA_DIR}/private/client.key.pem \ - -passin pass:changeit \ - -new -sha256 -out ${CA_DIR}/csr/client.csr.pem \ - -subj "/C=NN/ST=Unknown/L=Unknown/O=lettuce/CN=client" - -echo "[INFO] Signing certificate request" -openssl ca -config ${DIR}/openssl.cnf \ - -extensions usr_cert -days 375 -notext -md sha256 \ - -passin pass:changeit \ - -batch \ - -in ${CA_DIR}/csr/client.csr.pem \ - -out ${CA_DIR}/certs/client.cert.pem - -echo "[INFO] Creating PKCS12 file with client certificate" -openssl pkcs12 -export -clcerts \ - -in ${CA_DIR}/certs/client.cert.pem \ - -inkey ${CA_DIR}/private/client.decrypted.key.pem \ - -passout pass:changeit \ - -out ${CA_DIR}/client.p12 - -${KEYTOOL} -importcert -keystore ${TRUSTSTORE_FILE} -file ${CA_DIR}/certs/ca.cert.pem -noprompt -storepass changeit -${KEYTOOL} -importkeystore \ - -srckeystore ${CA_DIR}/client.p12 -srcstoretype PKCS12 -srcstorepass changeit\ - -destkeystore ${KEYSTORE_FILE} -deststoretype PKCS12 \ - -noprompt -storepass changeit diff --git a/src/test/bash/openssl.cnf b/src/test/bash/openssl.cnf deleted file mode 100644 index 721bd488db..0000000000 --- a/src/test/bash/openssl.cnf +++ /dev/null @@ -1,107 +0,0 @@ -[ ca ] -# `man ca` -default_ca = CA_default - -[ CA_default ] -# Directory and file locations. -dir = work/ca -certs = $dir/certs -crl_dir = $dir/crl -new_certs_dir = $dir/newcerts -database = $dir/index.txt -serial = $dir/serial -RANDFILE = $dir/private/.rand - -# The root key and root certificate. -private_key = $dir/private/ca.key.pem -certificate = $dir/certs/ca.cert.pem - -# For certificate revocation lists. -crlnumber = $dir/crlnumber -crl = $dir/crl/ca.crl.pem -crl_extensions = crl_ext -default_crl_days = 30 - -# SHA-1 is deprecated, so use SHA-2 instead. -default_md = sha256 - -name_opt = ca_default -cert_opt = ca_default -default_days = 375 -preserve = no -policy = policy_strict -copy_extensions = copy - -[ policy_strict ] -# The root CA should only sign intermediate certificates that match. -# See the POLICY FORMAT section of `man ca`. -countryName = match -stateOrProvinceName = match -organizationName = match -organizationalUnitName = optional -commonName = supplied -emailAddress = optional - -[ req ] -# Options for the `req` tool (`man req`). -default_bits = 2048 -distinguished_name = req_distinguished_name -string_mask = utf8only - -# SHA-1 is deprecated, so use SHA-2 instead. -default_md = sha256 - -# Extension to add when the -x509 option is used. -x509_extensions = v3_ca - -[ req_distinguished_name ] -# See . -countryName = Country Name (2 letter code) -stateOrProvinceName = State or Province Name -localityName = Locality Name -0.organizationName = Organization Name -organizationalUnitName = Organizational Unit Name -commonName = Common Name -emailAddress = Email Address - -# Optionally, specify some defaults. -countryName_default = NN -stateOrProvinceName_default = Vault Test -localityName_default = -0.organizationName_default = spring-cloud-vault-config -#organizationalUnitName_default = -#emailAddress_default = info@spring-cloud-vault-config.dummy - -[ v3_ca ] -# Extensions for a typical CA (`man x509v3_config`). -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid:always,issuer -basicConstraints = critical, CA:true -keyUsage = critical, digitalSignature, cRLSign, keyCertSign - -[ v3_intermediate_ca ] -# Extensions for a typical intermediate CA (`man x509v3_config`). -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid:always,issuer -basicConstraints = critical, CA:true, pathlen:0 -keyUsage = critical, digitalSignature, cRLSign, keyCertSign - -[ usr_cert ] -# Extensions for client certificates (`man x509v3_config`). -basicConstraints = CA:FALSE -nsCertType = client, email -nsComment = "OpenSSL Generated Client Certificate" -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid,issuer -keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment -extendedKeyUsage = clientAuth, emailProtection - -[ server_cert ] -# Extensions for server certificates (`man x509v3_config`). -basicConstraints = CA:FALSE -nsCertType = server -nsComment = "OpenSSL Generated Server Certificate" -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid,issuer:always -keyUsage = critical, digitalSignature, keyEncipherment -extendedKeyUsage = serverAuth diff --git a/src/test/java/biz/paluch/redis/extensibility/MyExtendedRedisClusterClient.java b/src/test/java/biz/paluch/redis/extensibility/MyExtendedRedisClusterClient.java index 1ac2848077..f0d7383363 100644 --- a/src/test/java/biz/paluch/redis/extensibility/MyExtendedRedisClusterClient.java +++ b/src/test/java/biz/paluch/redis/extensibility/MyExtendedRedisClusterClient.java @@ -20,6 +20,7 @@ package biz.paluch.redis.extensibility; import java.time.Duration; +import java.util.function.Supplier; import javax.enterprise.inject.Alternative; @@ -51,7 +52,7 @@ public MyExtendedRedisClusterClient() { @Override protected StatefulRedisClusterConnectionImpl newStatefulRedisClusterConnection( RedisChannelWriter channelWriter, ClusterPushHandler pushHandler, RedisCodec codec, Duration timeout, - Mono parser) { + Supplier parser) { return new MyRedisClusterConnection<>(channelWriter, pushHandler, codec, timeout, parser); } diff --git a/src/test/java/biz/paluch/redis/extensibility/MyRedisClusterConnection.java b/src/test/java/biz/paluch/redis/extensibility/MyRedisClusterConnection.java index 04632e463c..a1152b9760 100644 --- a/src/test/java/biz/paluch/redis/extensibility/MyRedisClusterConnection.java +++ b/src/test/java/biz/paluch/redis/extensibility/MyRedisClusterConnection.java @@ -20,6 +20,7 @@ package biz.paluch.redis.extensibility; import java.time.Duration; +import java.util.function.Supplier; import io.lettuce.core.RedisChannelWriter; import io.lettuce.core.cluster.ClusterPushHandler; @@ -37,7 +38,7 @@ class MyRedisClusterConnection extends StatefulRedisClusterConnectionImpl { public MyRedisClusterConnection(RedisChannelWriter writer, ClusterPushHandler pushHandler, RedisCodec codec, - Duration timeout, Mono parser) { + Duration timeout, Supplier parser) { super(writer, pushHandler, codec, timeout, parser); } diff --git a/src/test/java/io/lettuce/apigenerator/Constants.java b/src/test/java/io/lettuce/apigenerator/Constants.java index 4f28a33d95..896b939951 100644 --- a/src/test/java/io/lettuce/apigenerator/Constants.java +++ b/src/test/java/io/lettuce/apigenerator/Constants.java @@ -30,7 +30,7 @@ class Constants { "RedisGeoCommands", "RedisHashCommands", "RedisHLLCommands", "RedisKeyCommands", "RedisListCommands", "RedisScriptingCommands", "RedisSentinelCommands", "RedisServerCommands", "RedisSetCommands", "RedisSortedSetCommands", "RedisStreamCommands", "RedisStringCommands", "RedisTransactionalCommands", - "RedisJsonCommands", "RediSearchCommands" }; + "RedisJsonCommands" }; public static final File TEMPLATES = new File("src/main/templates"); diff --git a/src/test/java/io/lettuce/core/ClientOptionsUnitTests.java b/src/test/java/io/lettuce/core/ClientOptionsUnitTests.java index bb2d0f91a0..49811ea5c8 100644 --- a/src/test/java/io/lettuce/core/ClientOptionsUnitTests.java +++ b/src/test/java/io/lettuce/core/ClientOptionsUnitTests.java @@ -40,7 +40,7 @@ void testDefault() { assertThat(options.getReadOnlyCommands().isReadOnly(new Command<>(CommandType.SET, null))).isFalse(); assertThat(options.getReadOnlyCommands().isReadOnly(new Command<>(CommandType.PUBLISH, null))).isFalse(); assertThat(options.getReadOnlyCommands().isReadOnly(new Command<>(CommandType.GET, null))).isTrue(); - assertThat(options.getJsonParser().block()).isInstanceOf(DefaultJsonParser.class); + assertThat(options.getJsonParser().get()).isInstanceOf(DefaultJsonParser.class); } @Test @@ -66,8 +66,8 @@ void testCopy() { @Test void jsonParser() { JsonParser parser = new CustomJsonParser(); - ClientOptions options = ClientOptions.builder().jsonParser(Mono.justOrEmpty(parser)).build(); - assertThat(options.getJsonParser().block()).isInstanceOf(CustomJsonParser.class); + ClientOptions options = ClientOptions.builder().jsonParser(() -> parser).build(); + assertThat(options.getJsonParser().get()).isInstanceOf(CustomJsonParser.class); } static class CustomJsonParser implements JsonParser { diff --git a/src/test/java/io/lettuce/core/RediSearchCommandBuilderUnitTests.java b/src/test/java/io/lettuce/core/RediSearchCommandBuilderUnitTests.java deleted file mode 100644 index c84e232ebf..0000000000 --- a/src/test/java/io/lettuce/core/RediSearchCommandBuilderUnitTests.java +++ /dev/null @@ -1,92 +0,0 @@ -package io.lettuce.core; - -/* - * Copyright 2025, Redis Ltd. and Contributors - * All rights reserved. - * - * Licensed under the MIT License. - */ -import io.lettuce.core.codec.StringCodec; -import io.lettuce.core.protocol.Command; -import io.lettuce.core.search.Field; -import io.lettuce.core.search.arguments.CreateArgs; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -import static io.lettuce.TestTags.UNIT_TEST; -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit tests for {@link RediSearchCommandBuilder}. - * - * @author Tihomir Mateev - */ -@Tag(UNIT_TEST) -class RediSearchCommandBuilderUnitTests { - - private static final String MY_KEY = "idx"; - - private static final String FIELD1_NAME = "title"; - - private static final String FIELD2_NAME = "published_at"; - - private static final String FIELD3_NAME = "category"; - - private static final String FIELD4_NAME = "sku"; - - private static final String FIELD4_ALIAS1 = "sku_text"; - - private static final String FIELD4_ALIAS2 = "sku_tag"; - - private static final String PREFIX = "blog:post:"; - - RediSearchCommandBuilder builder = new RediSearchCommandBuilder<>(StringCodec.UTF8); - - // FT.CREATE idx ON HASH PREFIX 1 blog:post: SCHEMA title TEXT SORTABLE published_at NUMERIC SORTABLE category TAG SORTABLE - @Test - void shouldCorrectlyConstructFtCreateCommandScenario1() { - Field field1 = Field. builder().name(FIELD1_NAME).type(Field.Type.TEXT).sortable().build(); - Field field2 = Field. builder().name(FIELD2_NAME).type(Field.Type.NUMERIC).sortable().build(); - Field field3 = Field. builder().name(FIELD3_NAME).type(Field.Type.TAG).sortable().build(); - CreateArgs createArgs = CreateArgs. builder().addPrefix(PREFIX) - .on(CreateArgs.TargetType.HASH).build(); - Command command = builder.ftCreate(MY_KEY, createArgs, Arrays.asList(field1, field2, field3)); - ByteBuf buf = Unpooled.directBuffer(); - command.encode(buf); - - String result = "*17\r\n" + "$9\r\n" + "FT.CREATE\r\n" + "$3\r\n" + MY_KEY + "\r\n" + "$2\r\n" + "ON\r\n" + "$4\r\n" - + "HASH\r\n" + "$6\r\n" + "PREFIX\r\n" + "$1\r\n" + "1\r\n" + "$10\r\n" + PREFIX + "\r\n" + "$6\r\n" - + "SCHEMA\r\n" + "$5\r\n" + FIELD1_NAME + "\r\n" + "$4\r\n" + "TEXT\r\n" + "$8\r\n" + "SORTABLE\r\n" + "$12\r\n" - + FIELD2_NAME + "\r\n" + "$7\r\n" + "NUMERIC\r\n" + "$8\r\n" + "SORTABLE\r\n" + "$8\r\n" + FIELD3_NAME + "\r\n" - + "$3\r\n" + "TAG\r\n" + "$8\r\n" + "SORTABLE\r\n"; - - assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo(result); - } - - // FT.CREATE idx ON HASH PREFIX 1 blog:post: SCHEMA sku AS sku_text TEXT sku AS sku_tag TAG SORTABLE - @Test - void shouldCorrectlyConstructFtCreateCommandScenario2() { - Field field1 = Field. builder().name(FIELD4_NAME).as(FIELD4_ALIAS1).type(Field.Type.TEXT).build(); - Field field2 = Field. builder().name(FIELD4_NAME).as(FIELD4_ALIAS2).type(Field.Type.TAG).sortable() - .build(); - CreateArgs createArgs = CreateArgs. builder().addPrefix(PREFIX) - .on(CreateArgs.TargetType.HASH).build(); - Command command = builder.ftCreate(MY_KEY, createArgs, Arrays.asList(field1, field2)); - ByteBuf buf = Unpooled.directBuffer(); - command.encode(buf); - - String result = "*17\r\n" + "$9\r\n" + "FT.CREATE\r\n" + "$3\r\n" + MY_KEY + "\r\n" + "$2\r\n" + "ON\r\n" + "$4\r\n" - + "HASH\r\n" + "$6\r\n" + "PREFIX\r\n" + "$1\r\n" + "1\r\n" + "$10\r\n" + PREFIX + "\r\n" + "$6\r\n" - + "SCHEMA\r\n" + "$3\r\n" + FIELD4_NAME + "\r\n" + "$2\r\n" + "AS\r\n" + "$8\r\n" + FIELD4_ALIAS1 + "\r\n" - + "$4\r\n" + "TEXT\r\n" + "$3\r\n" + FIELD4_NAME + "\r\n" + "$2\r\n" + "AS\r\n" + "$7\r\n" + FIELD4_ALIAS2 - + "\r\n" + "$3\r\n" + "TAG\r\n" + "$8\r\n" + "SORTABLE\r\n"; - - assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo(result); - } - -} diff --git a/src/test/java/io/lettuce/core/RedisContainerIntegrationTests.java b/src/test/java/io/lettuce/core/RedisContainerIntegrationTests.java index f276e37ee8..caa258f744 100644 --- a/src/test/java/io/lettuce/core/RedisContainerIntegrationTests.java +++ b/src/test/java/io/lettuce/core/RedisContainerIntegrationTests.java @@ -26,22 +26,23 @@ public class RedisContainerIntegrationTests { private static final String REDIS_STACK_CLUSTER = "clustered-stack"; - private static final String REDIS_STACK_VERSION = System.getProperty("REDIS_STACK_VERSION", "8.0-M04-pre");; + private static final String REDIS_STACK_VERSION = System.getProperty("REDIS_STACK_VERSION"); private static Exception initializationException; public static ComposeContainer CLUSTERED_STACK = new ComposeContainer( new File("src/test/resources/docker/docker-compose.yml")).withExposedService(REDIS_STACK_CLUSTER, 36379) .withExposedService(REDIS_STACK_CLUSTER, 36380).withExposedService(REDIS_STACK_CLUSTER, 36381) - .withExposedService(REDIS_STACK_STANDALONE, 6379) - .withEnv("CLIENT_LIBS_TEST_IMAGE", "redislabs/client-libs-test") - .withEnv("REDIS_STACK_VERSION", REDIS_STACK_VERSION).withPull(false).withLocalCompose(true); + .withExposedService(REDIS_STACK_STANDALONE, 6379).withPull(false).withLocalCompose(true); // Singleton container pattern - start the containers only once // See https://java.testcontainers.org/test_framework_integration/manual_lifecycle_control/#singleton-containers static { int attempts = 0; + if (REDIS_STACK_VERSION != null && !REDIS_STACK_VERSION.isEmpty()) { + CLUSTERED_STACK.withEnv("REDIS_VERSION", REDIS_STACK_VERSION); + } // In case you need to debug the container uncomment these lines to redirect the output CLUSTERED_STACK.withLogConsumer(REDIS_STACK_CLUSTER, (OutputFrame frame) -> LOGGER.debug(frame.getUtf8String())); CLUSTERED_STACK.withLogConsumer(REDIS_STACK_STANDALONE, (OutputFrame frame) -> LOGGER.debug(frame.getUtf8String())); diff --git a/src/test/java/io/lettuce/core/RedisJsonCommandBuilderUnitTests.java b/src/test/java/io/lettuce/core/RedisJsonCommandBuilderUnitTests.java index 4106a711c2..22f3aed345 100644 --- a/src/test/java/io/lettuce/core/RedisJsonCommandBuilderUnitTests.java +++ b/src/test/java/io/lettuce/core/RedisJsonCommandBuilderUnitTests.java @@ -49,7 +49,7 @@ class RedisJsonCommandBuilderUnitTests { public static final JsonPath MY_PATH = JsonPath.of("$..commuter_bikes"); - RedisJsonCommandBuilder builder = new RedisJsonCommandBuilder<>(StringCodec.UTF8, Mono.just(PARSER)); + RedisJsonCommandBuilder builder = new RedisJsonCommandBuilder<>(StringCodec.UTF8, () -> PARSER); @Test void shouldCorrectlyConstructJsonArrappend() { diff --git a/src/test/java/io/lettuce/core/SslIntegrationTests.java b/src/test/java/io/lettuce/core/SslIntegrationTests.java index bbad87472a..dbea9f90b1 100644 --- a/src/test/java/io/lettuce/core/SslIntegrationTests.java +++ b/src/test/java/io/lettuce/core/SslIntegrationTests.java @@ -42,6 +42,8 @@ import java.io.File; import java.net.MalformedURLException; import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Duration; import java.util.List; import java.util.function.Function; @@ -50,6 +52,7 @@ import static io.lettuce.TestTags.INTEGRATION_TEST; import static io.lettuce.test.settings.TestSettings.sslPort; +import static io.lettuce.test.settings.TlsSettings.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -66,11 +69,13 @@ class SslIntegrationTests extends TestSupport { private static final String KEYSTORE = "work/keystore.jks"; - private static final String TRUSTSTORE = "work/truststore.jks"; + private static File truststoreFile0; - private static final File TRUSTSTORE_FILE = new File(TRUSTSTORE); + private static File truststoreFile1; - private static final File CA_CERT_FILE = new File("work/ca/certs/ca.cert.pem"); + private static File truststoreFile2; + + private static File truststoreFile3; private static final int MASTER_SLAVE_BASE_PORT_OFFSET = 2000; @@ -111,9 +116,25 @@ class SslIntegrationTests extends TestSupport { @BeforeAll static void beforeClass() { + Path path0 = createAndSaveTestTruststore("redis-standalone-0", Paths.get("redis-standalone-0/work/tls"), "changeit"); + truststoreFile0 = path0.toFile(); + + Path path = createAndSaveTestTruststore("redis-standalone-1", Paths.get("redis-standalone-1/work/tls"), "changeit"); + truststoreFile1 = path.toFile(); + + Path path2 = createAndSaveTestTruststore("redis-standalone-sentinel-controlled", + Paths.get("redis-standalone-sentinel-controlled/work/tls"), "changeit"); + truststoreFile2 = path2.toFile(); + + truststoreFile3 = createAndSaveTestTruststore("redis-standalone-5-client-cert", + Paths.get("redis-standalone-5-client-cert/work/tls"), "changeit").toFile(); assumeTrue(CanConnect.to(TestSettings.host(), sslPort()), "Assume that stunnel runs on port 6443"); - assertThat(TRUSTSTORE_FILE).exists(); + // Maybe we should do a list. + assertThat(truststoreFile0).exists(); + assertThat(truststoreFile1).exists(); + assertThat(truststoreFile2).exists(); + assertThat(truststoreFile3).exists(); } @Test @@ -130,7 +151,7 @@ void standaloneWithJdkSsl() { SslOptions sslOptions = SslOptions.builder() // .jdkSslProvider() // - .truststore(TRUSTSTORE_FILE) // + .truststore(truststoreFile1, "changeit") // .build(); setOptions(sslOptions); @@ -142,7 +163,7 @@ void standaloneWithVerifyCaOnly() { SslOptions sslOptions = SslOptions.builder() // .jdkSslProvider() // - .truststore(TRUSTSTORE_FILE) // + .truststore(truststoreFile0, "changeit") // .build(); setOptions(sslOptions); @@ -153,7 +174,7 @@ void standaloneWithVerifyCaOnly() { void standaloneWithPemCert() { SslOptions sslOptions = SslOptions.builder() // - .trustManager(CA_CERT_FILE) // + .trustManager(envCa(Paths.get("redis-standalone-1/work/tls")).toFile()) // .build(); setOptions(sslOptions); verifyConnection(URI_VERIFY); @@ -164,7 +185,7 @@ void standaloneWithPemCertAndImpossibleTimeout() { Assertions.setMaxStackTraceElementsDisplayed(30); SslOptions sslOptions = SslOptions.builder() // - .trustManager(CA_CERT_FILE) // + .trustManager(envCa(Paths.get("redis-standalone-1/work/tls")).toFile()) // .build(); setOptions(sslOptions); redisClient.setOptions(ClientOptions.builder().protocolVersion(ProtocolVersion.RESP3).sslOptions(sslOptions).build()); @@ -183,7 +204,7 @@ void standaloneWithJdkSslUsingTruststoreUrl() throws Exception { SslOptions sslOptions = SslOptions.builder() // .jdkSslProvider() // - .truststore(truststoreURL()) // + .truststore(truststoreURL(truststoreFile1), "changeit") // .build(); setOptions(sslOptions); @@ -192,11 +213,12 @@ void standaloneWithJdkSslUsingTruststoreUrl() throws Exception { @Test void standaloneWithClientCertificates() { - + // 6445 + File keystore = envClientP12(Paths.get("redis-standalone-5-client-cert/work/tls")).toFile(); SslOptions sslOptions = SslOptions.builder() // .jdkSslProvider() // - .keystore(new File(KEYSTORE), "changeit".toCharArray()) // - .truststore(TRUSTSTORE_FILE) // + .keystore(keystore, "changeit".toCharArray()) // + .truststore(truststoreFile3, "changeit") // .build(); setOptions(sslOptions); @@ -208,7 +230,7 @@ void standaloneWithClientCertificatesWithoutKeystore() { SslOptions sslOptions = SslOptions.builder() // .jdkSslProvider() // - .truststore(TRUSTSTORE_FILE) // + .truststore(truststoreFile1, "changeit") // .build(); setOptions(sslOptions); @@ -220,7 +242,7 @@ void standaloneWithJdkSslUsingTruststoreUrlWithWrongPassword() throws Exception SslOptions sslOptions = SslOptions.builder() // .jdkSslProvider() // - .truststore(truststoreURL(), "knödel") // + .truststore(truststoreURL(truststoreFile0), "knödel") // .build(); setOptions(sslOptions); @@ -245,7 +267,7 @@ void standaloneWithOpenSsl() { SslOptions sslOptions = SslOptions.builder() // .openSslProvider() // - .truststore(TRUSTSTORE_FILE) // + .truststore(truststoreFile0, "changeit") // .build(); setOptions(sslOptions); @@ -298,7 +320,7 @@ void masterSlaveWithJdkSsl() { SslOptions sslOptions = SslOptions.builder() // .jdkSslProvider() // - .truststore(TRUSTSTORE_FILE) // + .truststore(truststoreFile2, "changeit") // .build(); setOptions(sslOptions); @@ -310,7 +332,7 @@ void masterSlaveWithJdkSslUsingTruststoreUrl() throws Exception { SslOptions sslOptions = SslOptions.builder() // .jdkSslProvider() // - .truststore(truststoreURL()) // + .truststore(truststoreURL(truststoreFile2), "changeit") // .build(); setOptions(sslOptions); @@ -322,7 +344,7 @@ void masterSlaveWithJdkSslUsingTruststoreUrlWithWrongPassword() throws Exception SslOptions sslOptions = SslOptions.builder() // .jdkSslProvider() // - .truststore(truststoreURL(), "knödel") // + .truststore(truststoreURL(truststoreFile0), "knödel") // .build(); setOptions(sslOptions); @@ -363,7 +385,7 @@ void masterSlaveSslWithOneInvalidHostWillSucceed() { SslOptions sslOptions = SslOptions.builder() // .jdkSslProvider() // - .truststore(TRUSTSTORE_FILE) // + .truststore(truststoreFile2, "changeit") // .build(); setOptions(sslOptions); @@ -375,7 +397,7 @@ void masterSlaveSslWithAllInvalidHostsWillFail() { SslOptions sslOptions = SslOptions.builder() // .jdkSslProvider() // - .truststore(TRUSTSTORE_FILE) // + .truststore(truststoreFile0, "changeit") // .build(); setOptions(sslOptions); @@ -415,8 +437,8 @@ private static List sslUris(IntStream masterSlaveOffsets, .map(builderCustomizer).map(RedisURI.Builder::build).collect(Collectors.toList()); } - private URL truststoreURL() throws MalformedURLException { - return TRUSTSTORE_FILE.toURI().toURL(); + private URL truststoreURL(File truststoreFile) throws MalformedURLException { + return truststoreFile.toURI().toURL(); } private void setOptions(SslOptions sslOptions) { diff --git a/src/test/java/io/lettuce/core/commands/AclCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/AclCommandIntegrationTests.java index 4d154dc0f7..7fbeb7cf9f 100644 --- a/src/test/java/io/lettuce/core/commands/AclCommandIntegrationTests.java +++ b/src/test/java/io/lettuce/core/commands/AclCommandIntegrationTests.java @@ -131,7 +131,7 @@ void aclLog() { @Test void aclList() { - assertThat(redis.aclList()).hasSize(2).first().asString().contains("user default"); + assertThat(redis.aclList()).hasSize(1).first().asString().contains("user default"); } @Test @@ -161,7 +161,7 @@ void aclSetuserWithCategories() { @Test void aclUsers() { - assertThat(redis.aclUsers()).hasSize(2).first().isEqualTo("default"); + assertThat(redis.aclUsers()).hasSize(1).first().isEqualTo("default"); } @Test diff --git a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java index c24e231f17..2e1ba647c3 100644 --- a/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java +++ b/src/test/java/io/lettuce/core/commands/KeyCommandIntegrationTests.java @@ -33,10 +33,8 @@ import javax.inject.Inject; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; +import org.junit.Ignore; +import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; import io.lettuce.core.CopyArgs; @@ -410,6 +408,7 @@ void restoreReplace() { } @Test + @Disabled("https://github.com/redis/lettuce/issues/3181") void restoreIdleTime() { redis.set(key, value); diff --git a/src/test/java/io/lettuce/core/commands/RunOnlyOnceServerCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/RunOnlyOnceServerCommandIntegrationTests.java index 2e94eb035c..ca8233e023 100644 --- a/src/test/java/io/lettuce/core/commands/RunOnlyOnceServerCommandIntegrationTests.java +++ b/src/test/java/io/lettuce/core/commands/RunOnlyOnceServerCommandIntegrationTests.java @@ -9,9 +9,7 @@ import javax.inject.Inject; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; import io.lettuce.core.MigrateArgs; @@ -32,6 +30,7 @@ */ @Tag(INTEGRATION_TEST) @ExtendWith(LettuceExtension.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) class RunOnlyOnceServerCommandIntegrationTests extends TestSupport { private final RedisClient client; @@ -54,6 +53,7 @@ class RunOnlyOnceServerCommandIntegrationTests extends TestSupport { */ @Test @Disabled + @Order(1) void debugSegfault() { assumeTrue(CanConnect.to(host(), port(1))); @@ -74,13 +74,14 @@ void debugSegfault() { * Executed in order: 2 */ @Test + @Order(2) void migrate() { - assumeTrue(CanConnect.to(host(), port(2))); + assumeTrue(CanConnect.to(host(), port(7))); redis.set(key, value); - String result = redis.migrate("localhost", TestSettings.port(2), key, 0, 10); + String result = redis.migrate("localhost", TestSettings.port(7), key, 0, 10); assertThat(result).isEqualTo("OK"); } @@ -88,18 +89,19 @@ void migrate() { * Executed in order: 3 */ @Test + @Order(3) void migrateCopyReplace() { - assumeTrue(CanConnect.to(host(), port(2))); + assumeTrue(CanConnect.to(host(), port(7))); redis.set(key, value); - redis.set("key1", value); redis.set("key2", value); + redis.set("key3", value); - String result = redis.migrate("localhost", TestSettings.port(2), 0, 10, MigrateArgs.Builder.keys(key).copy().replace()); + String result = redis.migrate("localhost", TestSettings.port(7), 0, 10, MigrateArgs.Builder.keys(key).copy().replace()); assertThat(result).isEqualTo("OK"); - result = redis.migrate("localhost", TestSettings.port(2), 0, 10, + result = redis.migrate("localhost", TestSettings.port(7), 0, 10, MigrateArgs.Builder.keys(Arrays.asList("key1", "key2")).replace()); assertThat(result).isEqualTo("OK"); } @@ -109,9 +111,10 @@ void migrateCopyReplace() { * redis. */ @Test + @Order(4) void shutdown() { - assumeTrue(CanConnect.to(host(), port(2))); + assumeTrue(CanConnect.to(host(), port(7))); final RedisAsyncCommands commands = client.connect(RedisURI.Builder.redis(host(), port(2)).build()) .async(); diff --git a/src/test/java/io/lettuce/core/json/RediSearchIntegrationTests.java b/src/test/java/io/lettuce/core/json/RediSearchIntegrationTests.java deleted file mode 100644 index c87472bc64..0000000000 --- a/src/test/java/io/lettuce/core/json/RediSearchIntegrationTests.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2025, Redis Ltd. and Contributors - * All rights reserved. - * - * Licensed under the MIT License. - */ - -package io.lettuce.core.json; - -import io.lettuce.core.RedisClient; -import io.lettuce.core.RedisContainerIntegrationTests; -import io.lettuce.core.RedisURI; -import io.lettuce.core.api.sync.RedisCommands; -import io.lettuce.core.search.Field; -import io.lettuce.core.search.arguments.CreateArgs; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; - -import static io.lettuce.TestTags.INTEGRATION_TEST; -import static org.assertj.core.api.Assertions.assertThat; - -@Tag(INTEGRATION_TEST) -public class RediSearchIntegrationTests extends RedisContainerIntegrationTests { - - private static final String GENERIC_INDEX = "idx"; - - private static final String FIELD1_NAME = "title"; - - private static final String FIELD2_NAME = "published_at"; - - private static final String FIELD3_NAME = "category"; - - private static final String PREFIX = "blog:post:"; - - protected static RedisClient client; - - protected static RedisCommands redis; - - public RediSearchIntegrationTests() { - RedisURI redisURI = RedisURI.Builder.redis("127.0.0.1").withPort(16379).build(); - - client = RedisClient.create(redisURI); - redis = client.connect().sync(); - } - - @BeforeEach - public void prepare() throws IOException { - redis.flushall(); - - Path path = Paths.get("src/test/resources/bike-inventory.json"); - String read = String.join("", Files.readAllLines(path)); - JsonValue value = redis.getJsonParser().createJsonValue(read); - - redis.jsonSet("bikes:inventory", JsonPath.ROOT_PATH, value); - } - - @AfterAll - static void teardown() { - if (client != null) { - client.shutdown(); - } - } - - @Test - void ftCreateScenario1() { - Field field1 = Field. builder().name(FIELD1_NAME).type(Field.Type.TEXT).sortable().build(); - Field field2 = Field. builder().name(FIELD2_NAME).type(Field.Type.NUMERIC).sortable().build(); - Field field3 = Field. builder().name(FIELD3_NAME).type(Field.Type.TAG).sortable().build(); - CreateArgs createArgs = CreateArgs. builder().addPrefix(PREFIX) - .on(CreateArgs.TargetType.HASH).build(); - - String result = redis.ftCreate(GENERIC_INDEX, createArgs, Arrays.asList(field1, field2, field3)); - assertThat(result).isEqualTo("OK"); - } - -} diff --git a/src/test/java/io/lettuce/core/json/RedisJsonIntegrationTests.java b/src/test/java/io/lettuce/core/json/RedisJsonIntegrationTests.java index 76fdd0661f..b8d4ac924c 100644 --- a/src/test/java/io/lettuce/core/json/RedisJsonIntegrationTests.java +++ b/src/test/java/io/lettuce/core/json/RedisJsonIntegrationTests.java @@ -601,7 +601,7 @@ void withCustomParser() { RedisURI redisURI = RedisURI.Builder.redis("127.0.0.1").withPort(16379).build(); try (RedisClient client = RedisClient.create(redisURI)) { - client.setOptions(ClientOptions.builder().jsonParser(Mono.just(new CustomParser())).build()); + client.setOptions(ClientOptions.builder().jsonParser(CustomParser::new).build()); StatefulRedisConnection connection = client.connect(StringCodec.UTF8); RedisCommands redis = connection.sync(); assertThat(redis.getJsonParser()).isInstanceOf(CustomParser.class); diff --git a/src/test/java/io/lettuce/core/masterreplica/MasterReplicaSentinelSslIntegrationTests.java b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaSentinelSslIntegrationTests.java index 1697835398..e6e0e00b31 100644 --- a/src/test/java/io/lettuce/core/masterreplica/MasterReplicaSentinelSslIntegrationTests.java +++ b/src/test/java/io/lettuce/core/masterreplica/MasterReplicaSentinelSslIntegrationTests.java @@ -2,14 +2,11 @@ import javax.inject.Inject; +import io.lettuce.core.*; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import io.lettuce.core.ReadFrom; -import io.lettuce.core.RedisClient; -import io.lettuce.core.RedisURI; -import io.lettuce.core.TestSupport; import io.lettuce.core.codec.StringCodec; import io.lettuce.core.internal.HostAndPort; import io.lettuce.core.resource.ClientResources; @@ -19,7 +16,15 @@ import io.lettuce.test.resource.FastShutdown; import io.lettuce.test.settings.TestSettings; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + import static io.lettuce.TestTags.INTEGRATION_TEST; +import static io.lettuce.test.settings.TlsSettings.createAndSaveTestTruststore; /** * Integration test for Master/Replica using Redis Sentinel over SSL. @@ -32,19 +37,33 @@ class MasterReplicaSentinelSslIntegrationTests extends TestSupport { private final ClientResources clientResources; + private static File truststoreFile; + + private static Map portMap = new HashMap<>(); + static { + portMap.put(26379, 26822); + portMap.put(6482, 8443); + portMap.put(6483, 8444); + } + @Inject MasterReplicaSentinelSslIntegrationTests(ClientResources clientResources) { + this.clientResources = clientResources.mutate() .socketAddressResolver(MappingSocketAddressResolver.create(DnsResolver.jvmDefault(), hostAndPort -> { + int port = hostAndPort.getPort(); + if (portMap.containsKey(port)) { + return HostAndPort.of(hostAndPort.getHostText(), portMap.get(port)); + } - return HostAndPort.of(hostAndPort.getHostText(), hostAndPort.getPort() + 443); + return hostAndPort; })).build(); } @Test void testMasterReplicaSentinelBasic() { - RedisClient client = RedisClient.create(clientResources); + RedisURI redisURI = RedisURI.create("rediss-sentinel://" + TestSettings.host() + ":26379?sentinelMasterId=mymaster"); redisURI.setVerifyPeer(false); StatefulRedisMasterReplicaConnection connection = MasterReplica.connect(client, StringCodec.UTF8, diff --git a/src/test/java/io/lettuce/core/sentinel/SentinelSslIntegrationTests.java b/src/test/java/io/lettuce/core/sentinel/SentinelSslIntegrationTests.java index b8fdc03138..9fd831328b 100644 --- a/src/test/java/io/lettuce/core/sentinel/SentinelSslIntegrationTests.java +++ b/src/test/java/io/lettuce/core/sentinel/SentinelSslIntegrationTests.java @@ -2,10 +2,15 @@ import static io.lettuce.TestTags.INTEGRATION_TEST; import static io.lettuce.test.settings.TestSettings.sslPort; +import static io.lettuce.test.settings.TlsSettings.createAndSaveTestTruststore; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; import javax.inject.Inject; @@ -36,23 +41,38 @@ @ExtendWith(LettuceExtension.class) class SentinelSslIntegrationTests extends TestSupport { - private static final File TRUSTSTORE_FILE = new File("work/truststore.jks"); + private static File truststoreFile; private final ClientResources clientResources; + private static Map portMap = new HashMap<>(); + static { + portMap.put(26379, 26822); + portMap.put(6482, 8443); + portMap.put(6483, 8444); + } + @Inject SentinelSslIntegrationTests(ClientResources clientResources) { + this.clientResources = clientResources.mutate() .socketAddressResolver(MappingSocketAddressResolver.create(DnsResolver.jvmDefault(), hostAndPort -> { + int port = hostAndPort.getPort(); + if (portMap.containsKey(port)) { + return HostAndPort.of(hostAndPort.getHostText(), portMap.get(port)); + } - return HostAndPort.of(hostAndPort.getHostText(), hostAndPort.getPort() + 443); + return hostAndPort; })).build(); } @BeforeAll static void beforeAll() { assumeTrue(CanConnect.to(TestSettings.host(), sslPort()), "Assume that stunnel runs on port 6443"); - assertThat(TRUSTSTORE_FILE).exists(); + Path path2 = createAndSaveTestTruststore("redis-standalone-sentinel-controlled", + Paths.get("redis-standalone-sentinel-controlled/work/tls"), "changeit"); + truststoreFile = path2.toFile(); + assertThat(truststoreFile).exists(); } @Test @@ -75,7 +95,7 @@ void shouldConnectToMasterUsingSentinel() { RedisURI redisURI = RedisURI.create("rediss-sentinel://" + TestSettings.host() + ":" + RedisURI.DEFAULT_SENTINEL_PORT + "?sentinelMasterId=mymaster"); - SslOptions options = SslOptions.builder().truststore(TRUSTSTORE_FILE).build(); + SslOptions options = SslOptions.builder().truststore(truststoreFile, "changeit").build(); RedisClient client = RedisClient.create(clientResources); client.setOptions(ClientOptions.builder().sslOptions(options).build()); diff --git a/src/test/java/io/lettuce/core/support/CommonsPool2ConfigConverterUnitTests.java b/src/test/java/io/lettuce/core/support/CommonsPool2ConfigConverterUnitTests.java index 82772e35c1..7e6fea08f7 100644 --- a/src/test/java/io/lettuce/core/support/CommonsPool2ConfigConverterUnitTests.java +++ b/src/test/java/io/lettuce/core/support/CommonsPool2ConfigConverterUnitTests.java @@ -19,22 +19,27 @@ @Tag(UNIT_TEST) class CommonsPool2ConfigConverterUnitTests { + private static final int MIN_IDLE_EXPECTED = 2; + + private static final int MAX_IDLE_EXPECTED = 12; + + private static final int MAX_TOTAL_EXPECTED = 13; + @Test void shouldAdaptConfiguration() { - GenericObjectPoolConfig config = new GenericObjectPoolConfig<>(); - config.setMinIdle(2); - config.setMaxIdle(12); - config.setMaxTotal(13); + config.setMinIdle(MIN_IDLE_EXPECTED); + config.setMaxIdle(MAX_IDLE_EXPECTED); + config.setMaxTotal(MAX_TOTAL_EXPECTED); config.setTestOnBorrow(true); config.setTestOnReturn(true); config.setTestOnCreate(true); BoundedPoolConfig result = CommonsPool2ConfigConverter.bounded(config); - assertThat(result.getMinIdle()).isEqualTo(2); - assertThat(result.getMaxIdle()).isEqualTo(12); - assertThat(result.getMaxTotal()).isEqualTo(13); + assertThat(result.getMinIdle()).isEqualTo(MIN_IDLE_EXPECTED); + assertThat(result.getMaxIdle()).isEqualTo(MAX_IDLE_EXPECTED); + assertThat(result.getMaxTotal()).isEqualTo(MAX_TOTAL_EXPECTED); assertThat(result.isTestOnAcquire()).isTrue(); assertThat(result.isTestOnCreate()).isTrue(); assertThat(result.isTestOnRelease()).isTrue(); diff --git a/src/test/java/io/lettuce/test/settings/TestSettings.java b/src/test/java/io/lettuce/test/settings/TestSettings.java index cf5411b1ef..3c56dfb5c6 100644 --- a/src/test/java/io/lettuce/test/settings/TestSettings.java +++ b/src/test/java/io/lettuce/test/settings/TestSettings.java @@ -49,7 +49,7 @@ public static String host() { * {@code -Ddomainsocket=YourSocket} */ public static String socket() { - return System.getProperty("domainsocket", "work/socket-6479"); + return System.getProperty("domainsocket", "work/socket-6482"); } /** diff --git a/src/test/java/io/lettuce/test/settings/TlsSettings.java b/src/test/java/io/lettuce/test/settings/TlsSettings.java new file mode 100644 index 0000000000..0187e84f12 --- /dev/null +++ b/src/test/java/io/lettuce/test/settings/TlsSettings.java @@ -0,0 +1,133 @@ +package io.lettuce.test.settings; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class TlsSettings { + + private static final String TRUST_STORE_TYPE = "PKCS12"; + + private static final String TEST_WORK_FOLDER = System.getenv().getOrDefault("TEST_WORK_FOLDER", "work/docker"); + + private static final String TEST_SERVER_CERT = "redis.crt"; + + private static final String TEST_CLIENT_P12 = "client.p12"; + + private static final String TEST_CLIENT_CERT = "client.crt"; + + private static final String TEST_CLIENT_KEY = "client.key"; + + private static final String TEST_CA_CERT = "ca.crt"; + + private static final String TEST_TRUSTSTORE = "truststore.jks"; + + public static Path envClientP12(Path certLocation) { + return Paths.get(TEST_WORK_FOLDER, certLocation.toString(), TEST_CLIENT_P12); + } + + public static Path envServerCert(Path certLocation) { + return Paths.get(TEST_WORK_FOLDER, certLocation.toString(), TEST_SERVER_CERT); + } + + public static Path envCa(Path certLocation) { + return Paths.get(TEST_WORK_FOLDER, certLocation.toString(), TEST_CA_CERT); + } + + public static Path testTruststorePath(String name) { + return Paths.get(TEST_WORK_FOLDER, name + '-' + TEST_TRUSTSTORE); + } + + /** + * Creates an empty truststore. + * + * @return An empty KeyStore object. + * @throws KeyStoreException If there's an error initializing the truststore. + * @throws IOException If there's an error loading the truststore. + * @throws NoSuchAlgorithmException If the algorithm used to check the integrity of the truststore cannot be found. + * @throws CertificateException If any of the certificates in the truststore could not be loaded. + */ + private static KeyStore createTruststore() + throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + KeyStore trustStore = KeyStore.getInstance(TRUST_STORE_TYPE); + trustStore.load(null, null); + return trustStore; + } + + /** + * Loads an X.509 certificate from the given file path. + * + * @param certPath Path to the certificate file. + * @return An X509Certificate object. + * @throws Exception If there's an error loading the certificate. + */ + private static X509Certificate loadCertificate(Path certPath) throws Exception { + try (FileInputStream fis = new FileInputStream(certPath.toFile())) { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return (X509Certificate) certFactory.generateCertificate(fis); + } + } + + /** + * Adds a trusted certificate to the given truststore. + * + * @param trustStore The KeyStore object. + * @param alias Alias for the certificate. + * @param certPath Path to the certificate file. + * @throws Exception If there's an error adding the certificate. + */ + private static void addTrustedCertificate(KeyStore trustStore, String alias, Path certPath) throws Exception { + X509Certificate cert = loadCertificate(certPath); + trustStore.setCertificateEntry(alias, cert); + } + + /** + * Creates a truststore, adds multiple trusted certificates, and saves it to the specified path. + * + * @param trustedCertPaths List of certificate file paths to add to the truststore. + * @param truststorePath Path to save the generated truststore. + * @param truststorePassword Password for the truststore. + * @return Path to the saved truststore file. + */ + public static Path createAndSaveTruststore(List trustedCertPaths, Path truststorePath, String truststorePassword) { + try { + KeyStore trustStore = createTruststore(); + + for (Path certPath : trustedCertPaths) { + addTrustedCertificate(trustStore, "trusted-cert-" + UUID.randomUUID(), certPath); + } + + try (FileOutputStream fos = new FileOutputStream(truststorePath.toFile())) { + trustStore.store(fos, truststorePassword.toCharArray()); + } catch (IOException e) { + throw new RuntimeException("Failed to save truststore to " + truststorePath + ": " + e.getMessage(), e); + } + } catch (Exception e) { + throw new RuntimeException("Failed to create and save truststore: " + e.getMessage(), e); + } + + return truststorePath; + } + + public static Path createAndSaveTestTruststore(String trustStoreName, Path certificateLocations, + String truststorePassword) { + List trustedCertPaths = new ArrayList<>(); + trustedCertPaths.add(envCa(certificateLocations).toAbsolutePath()); + trustedCertPaths.add(envServerCert(certificateLocations).toAbsolutePath()); + + Path trustStorePath = testTruststorePath(trustStoreName).toAbsolutePath(); + + return createAndSaveTruststore(trustedCertPaths, trustStorePath, truststorePassword); + } + +} diff --git a/src/test/java/io/redis/examples/async/StringExample.java b/src/test/java/io/redis/examples/async/StringExample.java index 9be29395b2..af856743e7 100644 --- a/src/test/java/io/redis/examples/async/StringExample.java +++ b/src/test/java/io/redis/examples/async/StringExample.java @@ -7,15 +7,12 @@ // REMOVE_START import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; // REMOVE_END import java.util.*; import java.util.concurrent.CompletableFuture; -// REMOVE_START -import static org.assertj.core.api.Assertions.assertThat; -// REMOVE_END - public class StringExample { // REMOVE_START @@ -29,7 +26,7 @@ public void run() { // STEP_START set_get CompletableFuture setAndGet = asyncCommands.set("bike:1", "Deimos").thenCompose(v -> { - System.out.println(v); // OK + System.out.println(v); // >>> OK // REMOVE_START assertThat(v).isEqualTo("OK"); // REMOVE_END @@ -41,13 +38,16 @@ public void run() { return res; }) // REMOVE_END - .thenAccept(System.out::println) // Deimos + .thenAccept(System.out::println) // >>> Deimos .toCompletableFuture(); // STEP_END + // HIDE_START + setAndGet.join(); + // HIDE_END // STEP_START setnx_xx CompletableFuture setnx = asyncCommands.setnx("bike:1", "bike").thenCompose(v -> { - System.out.println(v); // false (because key already exists) + System.out.println(v); // >>> false (because key already exists) // REMOVE_START assertThat(v).isFalse(); // REMOVE_END @@ -59,8 +59,11 @@ public void run() { return res; }) // REMOVE_END - .thenAccept(System.out::println) // Deimos (value is unchanged) + .thenAccept(System.out::println) // >>> Deimos (value is unchanged) .toCompletableFuture(); + // HIDE_START + setnx.join(); + // HIDE_END // set the value to "bike" if it already exists CompletableFuture setxx = asyncCommands.set("bike:1", "bike", SetArgs.Builder.xx()) @@ -70,8 +73,11 @@ public void run() { return res; }) // REMOVE_END - .thenAccept(System.out::println) // OK + .thenAccept(System.out::println) // >>> OK .toCompletableFuture(); + // HIDE_START + setxx.join(); + // HIDE_END // STEP_END // STEP_START mset @@ -81,7 +87,7 @@ public void run() { bikeMap.put("bike:3", "Vanth"); CompletableFuture mset = asyncCommands.mset(bikeMap).thenCompose(v -> { - System.out.println(v); // OK + System.out.println(v); // >>> OK return asyncCommands.mget("bike:1", "bike:2", "bike:3"); }) // REMOVE_START @@ -93,15 +99,19 @@ public void run() { return res; }) // REMOVE_END - .thenAccept(System.out::println) // [KeyValue[bike:1, Deimos], KeyValue[bike:2, Ares], KeyValue[bike:3, - // Vanth]] + .thenAccept(System.out::println) + // >>> [KeyValue[bike:1, Deimos], KeyValue[bike:2, Ares], KeyValue[bike:3, + // Vanth]] .toCompletableFuture(); // STEP_END + // HIDE_START + mset.join(); + // HIDE_END // STEP_START incr CompletableFuture incrby = asyncCommands.set("total_crashes", "0") .thenCompose(v -> asyncCommands.incr("total_crashes")).thenCompose(v -> { - System.out.println(v); // 1 + System.out.println(v); // >>> 1 // REMOVE_START assertThat(v).isEqualTo(1L); // REMOVE_END @@ -113,12 +123,12 @@ public void run() { return res; }) // REMOVE_END - .thenAccept(System.out::println) // 11 + .thenAccept(System.out::println) // >>> 11 .toCompletableFuture(); // STEP_END - - CompletableFuture.allOf(setAndGet, setnx, setxx, mset, incrby).join(); - + // HIDE_START + incrby.join(); + // HIDE_END } finally { redisClient.shutdown(); } diff --git a/src/test/jmh/io/lettuce/core/dynamic/RedisCommandFactoryBenchmark.java b/src/test/jmh/io/lettuce/core/dynamic/RedisCommandFactoryBenchmark.java index 83ce325998..631e184099 100644 --- a/src/test/jmh/io/lettuce/core/dynamic/RedisCommandFactoryBenchmark.java +++ b/src/test/jmh/io/lettuce/core/dynamic/RedisCommandFactoryBenchmark.java @@ -32,7 +32,7 @@ public void setup() { redisCommandFactory = new RedisCommandFactory(new MockStatefulConnection(EmptyRedisChannelWriter.INSTANCE)); regularCommands = redisCommandFactory.getCommands(RegularCommands.class); - asyncCommands = new RedisAsyncCommandsImpl<>(EmptyStatefulRedisConnection.INSTANCE, StringCodec.UTF8, Mono.just(new DefaultJsonParser())); + asyncCommands = new RedisAsyncCommandsImpl<>(EmptyStatefulRedisConnection.INSTANCE, StringCodec.UTF8); } @Benchmark diff --git a/src/test/resources/docker-env/.env b/src/test/resources/docker-env/.env new file mode 100644 index 0000000000..b294422b69 --- /dev/null +++ b/src/test/resources/docker-env/.env @@ -0,0 +1,2 @@ +REDIS_ENV_WORK_DIR=../../../../work/docker +REDIS_VERSION=8.0-M04-pre diff --git a/src/test/resources/docker-env/docker-compose.yml b/src/test/resources/docker-env/docker-compose.yml new file mode 100644 index 0000000000..130fb1f717 --- /dev/null +++ b/src/test/resources/docker-env/docker-compose.yml @@ -0,0 +1,141 @@ +x-client-libs-image: &client-libs-image + image: "redislabs/client-libs-test:${REDIS_VERSION:-8.0-M04-pre}" + +services: + # Standalone Redis Servers + redis-standalone-0: + <<: *client-libs-image + container_name: redis-standalone-0 + environment: + - TLS_ENABLED=yes + volumes: + - ./redis-standalone-0/config:/redis/config:r + - ${REDIS_ENV_WORK_DIR}/redis-standalone-0/work:/redis/work:rw + ports: + - "6478:6478" + - "6444:6444" # TLS Port + networks: + - redis-network + + redis-standalone-1: + <<: *client-libs-image + container_name: redis-standalone-1 + environment: + - TLS_ENABLED=yes + volumes: + - ./redis-standalone-1/config:/redis/config:r + - ${REDIS_ENV_WORK_DIR}/redis-standalone-1/work:/redis/work:rw + ports: + - "6479:6479" + - "6486:6486" + - "6443:6443" # TLS Port + networks: + - redis-network + + redis-standalone-2: + <<: *client-libs-image + container_name: redis-standalone-2 + volumes: + - ./redis-standalone-2/config:/redis/config:r + ports: + - "6480:6480" + networks: + - redis-network + + redis-standalone-3: + <<: *client-libs-image + container_name: redis-standalone-3 + volumes: + - ./redis-standalone-3/config:/redis/config:r + ports: + - "6481:6481" + networks: + - redis-network + + redis-standalone-4: + <<: *client-libs-image + container_name: redis-standalone-4 + volumes: + - ./redis-standalone-4/config:/redis/config:r + + environment: + - REDIS_CLUSTER=no + ports: + - "6484:6484" + - "26381:26381" + networks: + - redis-network + + redis-standalone-5-client-cert: + <<: *client-libs-image + container_name: redis-standalone-5-client-cert + environment: + - TLS_ENABLED=yes + volumes: + - ./redis-standalone-5-client-cert/config:/redis/config:r + - ${REDIS_ENV_WORK_DIR}/redis-standalone-5-client-cert/work:/redis/work:rw + ports: + - "6485:6485" + - "6445:6445" # TLS Port + networks: + - redis-network + + redis-standalone-sentinel-controlled: + <<: *client-libs-image + container_name: redis-standalone-sentinel-controlled + environment: + - REDIS_CLUSTER=no + - TLS_ENABLED=yes + volumes: + - ./redis-standalone-sentinel-controlled/config:/redis/config:r + - ${REDIS_ENV_WORK_DIR}/redis-standalone-sentinel-controlled/work:/redis/work:rw + - ${REDIS_ENV_WORK_DIR}:/work + ports: + - "26380:26380" + - "26822:26822" # sentinel tls port + - "26379:26379" + - "6482:6482" + - "6483:6483" + - "8443:8443" # TLS Port + - "8444:8444" # TLS Port + networks: + - redis-network + + + ssl-test-cluster: + <<: *client-libs-image + container_name: ssl-test-cluster + environment: + - REDIS_CLUSTER=yes + volumes: + - ./ssl-test-cluster/config:/redis/config:r + ports: + - "7479:7479" + - "7480:7480" + - "7481:7481" + networks: + - redis-network + + # Non-SSL Cluster + test-cluster: + <<: *client-libs-image + container_name: test-cluster + environment: + - REDIS_CLUSTER=yes + volumes: + - ./test-cluster/config:/redis/config:r + ports: + - "7379:7379" + - "7380:7380" + - "7381:7381" + - "7382:7382" + - "7383:7383" + - "7384:7384" + - "7385:7385" + - "7582:7582" + networks: + - redis-network + +networks: + redis-network: + driver: bridge diff --git a/src/test/resources/docker-env/redis-standalone-0/config/node-6478/redis.conf b/src/test/resources/docker-env/redis-standalone-0/config/node-6478/redis.conf new file mode 100644 index 0000000000..6fa6ea2177 --- /dev/null +++ b/src/test/resources/docker-env/redis-standalone-0/config/node-6478/redis.conf @@ -0,0 +1,10 @@ +port 6478 +tls-port 6444 +tls-auth-clients no +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +enable-debug-command yes +unixsocket /work/socket-6478 +unixsocketperm 777 +replica-announce-ip localhost \ No newline at end of file diff --git a/src/test/resources/docker-env/redis-standalone-1/config/node-6479/redis.conf b/src/test/resources/docker-env/redis-standalone-1/config/node-6479/redis.conf new file mode 100644 index 0000000000..544300d1ee --- /dev/null +++ b/src/test/resources/docker-env/redis-standalone-1/config/node-6479/redis.conf @@ -0,0 +1,10 @@ +port 6479 +tls-port 6443 +tls-auth-clients no +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +enable-debug-command yes +unixsocket /work/socket-6479 +unixsocketperm 777 +replica-announce-ip localhost \ No newline at end of file diff --git a/src/test/resources/docker-env/redis-standalone-1/config/node-6486/redis.conf b/src/test/resources/docker-env/redis-standalone-1/config/node-6486/redis.conf new file mode 100644 index 0000000000..4fa8e59d35 --- /dev/null +++ b/src/test/resources/docker-env/redis-standalone-1/config/node-6486/redis.conf @@ -0,0 +1,8 @@ +port 6486 +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +unixsocket /work/socket-6486 +unixsocketperm 777 +enable-debug-command yes +replica-announce-ip localhost \ No newline at end of file diff --git a/src/test/resources/docker-env/redis-standalone-2/config/node-6480/redis.conf b/src/test/resources/docker-env/redis-standalone-2/config/node-6480/redis.conf new file mode 100644 index 0000000000..ebaf1511a7 --- /dev/null +++ b/src/test/resources/docker-env/redis-standalone-2/config/node-6480/redis.conf @@ -0,0 +1,8 @@ +port 6480 +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +unixsocket /work/socket-6480 +unixsocketperm 777 +enable-debug-command yes +replica-announce-ip localhost \ No newline at end of file diff --git a/src/test/resources/docker-env/redis-standalone-3/config/node-6481/redis.conf b/src/test/resources/docker-env/redis-standalone-3/config/node-6481/redis.conf new file mode 100644 index 0000000000..25e45088cf --- /dev/null +++ b/src/test/resources/docker-env/redis-standalone-3/config/node-6481/redis.conf @@ -0,0 +1,8 @@ +port 6481 +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +unixsocket /work/socket-6481 +unixsocketperm 777 +enable-debug-command yes +replica-announce-ip localhost \ No newline at end of file diff --git a/src/test/resources/docker-env/redis-standalone-4/config/node-6484/redis.conf b/src/test/resources/docker-env/redis-standalone-4/config/node-6484/redis.conf new file mode 100644 index 0000000000..1afd6beb3f --- /dev/null +++ b/src/test/resources/docker-env/redis-standalone-4/config/node-6484/redis.conf @@ -0,0 +1,8 @@ +port 6484 +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +unixsocket /work/socket-6484 +unixsocketperm 777 +enable-debug-command yes +replica-announce-ip localhost \ No newline at end of file diff --git a/src/test/resources/docker-env/redis-standalone-4/config/node-sentinel-26381/redis.conf b/src/test/resources/docker-env/redis-standalone-4/config/node-sentinel-26381/redis.conf new file mode 100644 index 0000000000..5208b41f51 --- /dev/null +++ b/src/test/resources/docker-env/redis-standalone-4/config/node-sentinel-26381/redis.conf @@ -0,0 +1,11 @@ +port 26381 +sentinel monitor mymaster localhost 6484 1 +sentinel announce-hostnames yes +sentinel resolve-hostnames yes +sentinel announce-ip localhost +sentinel down-after-milliseconds mymaster 200 +sentinel failover-timeout mymaster 200 +sentinel parallel-syncs mymaster 1 +sentinel auth-pass mymaster foobared +unixsocket /work/socket-26381 +unixsocketperm 777 \ No newline at end of file diff --git a/src/test/resources/docker-env/redis-standalone-5-client-cert/config/node-6478/redis.conf b/src/test/resources/docker-env/redis-standalone-5-client-cert/config/node-6478/redis.conf new file mode 100644 index 0000000000..e5e275ca42 --- /dev/null +++ b/src/test/resources/docker-env/redis-standalone-5-client-cert/config/node-6478/redis.conf @@ -0,0 +1,8 @@ +port 6485 +tls-port 6445 +#tls-auth-clients no +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +enable-debug-command yes +replica-announce-ip localhost \ No newline at end of file diff --git a/src/test/resources/docker-env/redis-standalone-sentinel-controlled/config/node-6482/redis.conf b/src/test/resources/docker-env/redis-standalone-sentinel-controlled/config/node-6482/redis.conf new file mode 100644 index 0000000000..1b15dfab27 --- /dev/null +++ b/src/test/resources/docker-env/redis-standalone-sentinel-controlled/config/node-6482/redis.conf @@ -0,0 +1,10 @@ +port 6482 +tls-port 8443 +tls-auth-clients no +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +unixsocket /work/socket-6482 +unixsocketperm 777 +enable-debug-command yes +replica-announce-ip localhost \ No newline at end of file diff --git a/src/test/resources/docker-env/redis-standalone-sentinel-controlled/config/node-6483/redis.conf b/src/test/resources/docker-env/redis-standalone-sentinel-controlled/config/node-6483/redis.conf new file mode 100644 index 0000000000..2d40968e1a --- /dev/null +++ b/src/test/resources/docker-env/redis-standalone-sentinel-controlled/config/node-6483/redis.conf @@ -0,0 +1,11 @@ +port 6483 +tls-port 8444 +tls-auth-clients no +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +unixsocket /work/socket-6483 +unixsocketperm 777 +enable-debug-command yes +slaveof 127.0.0.1 6482 +replica-announce-ip localhost \ No newline at end of file diff --git a/src/test/resources/docker-env/redis-standalone-sentinel-controlled/config/node-sentinel-26379/redis.conf b/src/test/resources/docker-env/redis-standalone-sentinel-controlled/config/node-sentinel-26379/redis.conf new file mode 100644 index 0000000000..96b2c5beb4 --- /dev/null +++ b/src/test/resources/docker-env/redis-standalone-sentinel-controlled/config/node-sentinel-26379/redis.conf @@ -0,0 +1,12 @@ +port 26379 +tls-port 26822 +tls-auth-clients no +sentinel monitor mymaster localhost 6482 1 +sentinel announce-hostnames yes +sentinel resolve-hostnames yes +sentinel announce-ip localhost +sentinel down-after-milliseconds mymaster 200 +sentinel failover-timeout mymaster 200 +sentinel parallel-syncs mymaster 1 +unixsocket /work/socket-26379 +unixsocketperm 777 diff --git a/src/test/resources/docker-env/redis-standalone-sentinel-controlled/config/node-sentinel-26380/redis.conf b/src/test/resources/docker-env/redis-standalone-sentinel-controlled/config/node-sentinel-26380/redis.conf new file mode 100644 index 0000000000..5eba90ec35 --- /dev/null +++ b/src/test/resources/docker-env/redis-standalone-sentinel-controlled/config/node-sentinel-26380/redis.conf @@ -0,0 +1,10 @@ +port 26380 +sentinel monitor mymaster localhost 6482 1 +sentinel announce-hostnames yes +sentinel resolve-hostnames yes +sentinel announce-ip localhost +sentinel down-after-milliseconds mymaster 200 +sentinel failover-timeout mymaster 200 +sentinel parallel-syncs mymaster 1 +unixsocket /work/socket-26380 +unixsocketperm 777 \ No newline at end of file diff --git a/src/test/resources/docker-env/ssl-test-cluster/config/node-7479/nodes.conf b/src/test/resources/docker-env/ssl-test-cluster/config/node-7479/nodes.conf new file mode 100644 index 0000000000..d578e24d7d --- /dev/null +++ b/src/test/resources/docker-env/ssl-test-cluster/config/node-7479/nodes.conf @@ -0,0 +1,4 @@ +cf2354ef19ee813a962350b51438314aebce1fe2 127.0.0.1:7479@17479 myself,master - 0 1578163609000 0 connected 0-10000 +cac8e053dd6f85fab470be57d29dcbac2a4b85c4 127.0.0.1:7480@17480 slave cf2354ef19ee813a962350b51438314aebce1fe2 0 1578163609301 1 connected +6554e5b1b158dccd4b1d9ca294a3e46a2d3e556d 127.0.0.1:7481@17481 master - 0 1578163609301 2 connected 10001-16383 +vars currentEpoch 2 lastVoteEpoch 0 \ No newline at end of file diff --git a/src/test/resources/docker-env/ssl-test-cluster/config/node-7479/redis.conf b/src/test/resources/docker-env/ssl-test-cluster/config/node-7479/redis.conf new file mode 100644 index 0000000000..8a51dd683f --- /dev/null +++ b/src/test/resources/docker-env/ssl-test-cluster/config/node-7479/redis.conf @@ -0,0 +1,7 @@ +port 7479 +save "" +appendonly no +cluster-enabled yes +cluster-node-timeout 150 +cluster-announce-port 7445 +requirepass foobared \ No newline at end of file diff --git a/src/test/resources/docker-env/ssl-test-cluster/config/node-7480/nodes.conf b/src/test/resources/docker-env/ssl-test-cluster/config/node-7480/nodes.conf new file mode 100644 index 0000000000..8da078b924 --- /dev/null +++ b/src/test/resources/docker-env/ssl-test-cluster/config/node-7480/nodes.conf @@ -0,0 +1,4 @@ +cf2354ef19ee813a962350b51438314aebce1fe2 127.0.0.1:7479@17479 master - 0 1578163609245 0 connected 0-10000 +cac8e053dd6f85fab470be57d29dcbac2a4b85c4 127.0.0.1:7480@17480 myself,slave cf2354ef19ee813a962350b51438314aebce1fe2 0 1578163609000 1 connected +6554e5b1b158dccd4b1d9ca294a3e46a2d3e556d 127.0.0.1:7481@17481 master - 0 1578163609245 2 connected 10001-16383 +vars currentEpoch 2 lastVoteEpoch 0 \ No newline at end of file diff --git a/src/test/resources/docker-env/ssl-test-cluster/config/node-7480/redis.conf b/src/test/resources/docker-env/ssl-test-cluster/config/node-7480/redis.conf new file mode 100644 index 0000000000..190236043c --- /dev/null +++ b/src/test/resources/docker-env/ssl-test-cluster/config/node-7480/redis.conf @@ -0,0 +1,7 @@ +port 7480 +save "" +appendonly no +cluster-enabled yes +cluster-node-timeout 150 +cluster-announce-port 7444 +requirepass foobared \ No newline at end of file diff --git a/src/test/resources/docker-env/ssl-test-cluster/config/node-7481/nodes.conf b/src/test/resources/docker-env/ssl-test-cluster/config/node-7481/nodes.conf new file mode 100644 index 0000000000..fdca668d22 --- /dev/null +++ b/src/test/resources/docker-env/ssl-test-cluster/config/node-7481/nodes.conf @@ -0,0 +1,4 @@ +cac8e053dd6f85fab470be57d29dcbac2a4b85c4 127.0.0.1:7480@17480 slave cf2354ef19ee813a962350b51438314aebce1fe2 0 1578163609279 1 connected +cf2354ef19ee813a962350b51438314aebce1fe2 127.0.0.1:7479@17479 master - 0 1578163609279 0 connected 0-10000 +6554e5b1b158dccd4b1d9ca294a3e46a2d3e556d 127.0.0.1:7481@17481 myself,master - 0 1578163609000 2 connected 10001-16383 +vars currentEpoch 2 lastVoteEpoch 0 \ No newline at end of file diff --git a/src/test/resources/docker-env/ssl-test-cluster/config/node-7481/redis.conf b/src/test/resources/docker-env/ssl-test-cluster/config/node-7481/redis.conf new file mode 100644 index 0000000000..3892728a3d --- /dev/null +++ b/src/test/resources/docker-env/ssl-test-cluster/config/node-7481/redis.conf @@ -0,0 +1,7 @@ +port 7481 +save "" +appendonly no +cluster-enabled yes +cluster-node-timeout 150 +cluster-announce-port 7445 +requirepass foobared \ No newline at end of file diff --git a/src/test/resources/docker-env/test-cluster/config/node-7379/nodes.conf b/src/test/resources/docker-env/test-cluster/config/node-7379/nodes.conf new file mode 100644 index 0000000000..8a59dbabda --- /dev/null +++ b/src/test/resources/docker-env/test-cluster/config/node-7379/nodes.conf @@ -0,0 +1,5 @@ +c2043458aa5646cee429fdd5e3c18220dddf2ce5 127.0.0.1:7380 master - 1434887920102 1434887920002 0 connected 12000-16383 +27f88788f03a86296b7d860152f4ae24ee59c8c9 127.0.0.1:7379 myself,master - 0 0 1 connected 0-11999 +2c07344ffa94ede5ea57a2367f190af6144c1adb 127.0.0.1:7382 slave c2043458aa5646cee429fdd5e3c18220dddf2ce5 1434887920102 1434887920002 2 connected +1c541b6daf98719769e6aacf338a7d81f108a180 127.0.0.1:7381 slave 27f88788f03a86296b7d860152f4ae24ee59c8c9 1434887920102 1434887920002 3 connected +vars currentEpoch 3 lastVoteEpoch 0 \ No newline at end of file diff --git a/src/test/resources/docker-env/test-cluster/config/node-7379/redis.conf b/src/test/resources/docker-env/test-cluster/config/node-7379/redis.conf new file mode 100644 index 0000000000..61ab74a1f1 --- /dev/null +++ b/src/test/resources/docker-env/test-cluster/config/node-7379/redis.conf @@ -0,0 +1,7 @@ +port 7379 +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +unixsocket /work/socket-7379 +cluster-enabled yes +cluster-node-timeout 150 \ No newline at end of file diff --git a/src/test/resources/docker-env/test-cluster/config/node-7380/nodes.conf b/src/test/resources/docker-env/test-cluster/config/node-7380/nodes.conf new file mode 100644 index 0000000000..f71c380f9c --- /dev/null +++ b/src/test/resources/docker-env/test-cluster/config/node-7380/nodes.conf @@ -0,0 +1,5 @@ +2c07344ffa94ede5ea57a2367f190af6144c1adb 127.0.0.1:7382 slave c2043458aa5646cee429fdd5e3c18220dddf2ce5 1434887920102 1434887920002 2 connected +27f88788f03a86296b7d860152f4ae24ee59c8c9 127.0.0.1:7379 master - 1434887920102 1434887920002 1 connected 0-11999 +1c541b6daf98719769e6aacf338a7d81f108a180 127.0.0.1:7381 slave 27f88788f03a86296b7d860152f4ae24ee59c8c9 1434887920102 1434887920002 3 connected +c2043458aa5646cee429fdd5e3c18220dddf2ce5 127.0.0.1:7380 myself,master - 0 0 0 connected 12000-16383 +vars currentEpoch 3 lastVoteEpoch 0 \ No newline at end of file diff --git a/src/test/resources/docker-env/test-cluster/config/node-7380/redis.conf b/src/test/resources/docker-env/test-cluster/config/node-7380/redis.conf new file mode 100644 index 0000000000..2f2ed61e25 --- /dev/null +++ b/src/test/resources/docker-env/test-cluster/config/node-7380/redis.conf @@ -0,0 +1,7 @@ +port 7380 +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +unixsocket /work/socket-7380 +cluster-enabled yes +cluster-node-timeout 150 \ No newline at end of file diff --git a/src/test/resources/docker-env/test-cluster/config/node-7381/nodes.conf b/src/test/resources/docker-env/test-cluster/config/node-7381/nodes.conf new file mode 100644 index 0000000000..aed925c86a --- /dev/null +++ b/src/test/resources/docker-env/test-cluster/config/node-7381/nodes.conf @@ -0,0 +1,5 @@ +1c541b6daf98719769e6aacf338a7d81f108a180 127.0.0.1:7381 myself,slave 27f88788f03a86296b7d860152f4ae24ee59c8c9 0 0 3 connected +2c07344ffa94ede5ea57a2367f190af6144c1adb 127.0.0.1:7382 slave c2043458aa5646cee429fdd5e3c18220dddf2ce5 1434887920102 1434887920002 2 connected +c2043458aa5646cee429fdd5e3c18220dddf2ce5 127.0.0.1:7380 master - 1434887920102 1434887920002 0 connected 12000-16383 +27f88788f03a86296b7d860152f4ae24ee59c8c9 127.0.0.1:7379 master - 1434887920102 1434887920002 1 connected 0-11999 +vars currentEpoch 3 lastVoteEpoch 0 \ No newline at end of file diff --git a/src/test/resources/docker-env/test-cluster/config/node-7381/redis.conf b/src/test/resources/docker-env/test-cluster/config/node-7381/redis.conf new file mode 100644 index 0000000000..0bfd25910f --- /dev/null +++ b/src/test/resources/docker-env/test-cluster/config/node-7381/redis.conf @@ -0,0 +1,7 @@ +port 7381 +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +unixsocket /work/socket-7381 +cluster-enabled yes +cluster-node-timeout 150 \ No newline at end of file diff --git a/src/test/resources/docker-env/test-cluster/config/node-7382/nodes.conf b/src/test/resources/docker-env/test-cluster/config/node-7382/nodes.conf new file mode 100644 index 0000000000..bdcf179a5e --- /dev/null +++ b/src/test/resources/docker-env/test-cluster/config/node-7382/nodes.conf @@ -0,0 +1,5 @@ +c2043458aa5646cee429fdd5e3c18220dddf2ce5 127.0.0.1:7380 master - 0 1434887920102 0 connected 12000-16383 +1c541b6daf98719769e6aacf338a7d81f108a180 127.0.0.1:7381 slave 27f88788f03a86296b7d860152f4ae24ee59c8c9 0 1434887920102 3 connected +2c07344ffa94ede5ea57a2367f190af6144c1adb 127.0.0.1:7382 myself,slave c2043458aa5646cee429fdd5e3c18220dddf2ce5 0 0 2 connected +27f88788f03a86296b7d860152f4ae24ee59c8c9 127.0.0.1:7379 master - 0 1434887920102 1 connected 0-11999 +vars currentEpoch 3 lastVoteEpoch 0 \ No newline at end of file diff --git a/src/test/resources/docker-env/test-cluster/config/node-7382/redis.conf b/src/test/resources/docker-env/test-cluster/config/node-7382/redis.conf new file mode 100644 index 0000000000..a658fe1149 --- /dev/null +++ b/src/test/resources/docker-env/test-cluster/config/node-7382/redis.conf @@ -0,0 +1,7 @@ +port 7382 +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +unixsocket /work/socket-7382 +cluster-enabled yes +cluster-node-timeout 150 \ No newline at end of file diff --git a/src/test/resources/docker-env/test-cluster/config/node-7383/redis.conf b/src/test/resources/docker-env/test-cluster/config/node-7383/redis.conf new file mode 100644 index 0000000000..262122f52b --- /dev/null +++ b/src/test/resources/docker-env/test-cluster/config/node-7383/redis.conf @@ -0,0 +1,7 @@ +port 7383 +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +unixsocket /work/socket-7383 +cluster-enabled yes +cluster-node-timeout 150 \ No newline at end of file diff --git a/src/test/resources/docker-env/test-cluster/config/node-7384/redis.conf b/src/test/resources/docker-env/test-cluster/config/node-7384/redis.conf new file mode 100644 index 0000000000..1594941ae0 --- /dev/null +++ b/src/test/resources/docker-env/test-cluster/config/node-7384/redis.conf @@ -0,0 +1,7 @@ +port 7384 +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +unixsocket /work/socket-7384 +cluster-enabled yes +cluster-node-timeout 150 \ No newline at end of file diff --git a/src/test/resources/docker-env/test-cluster/config/node-7385/redis.conf b/src/test/resources/docker-env/test-cluster/config/node-7385/redis.conf new file mode 100644 index 0000000000..3aee9a7dae --- /dev/null +++ b/src/test/resources/docker-env/test-cluster/config/node-7385/redis.conf @@ -0,0 +1,8 @@ +port 7385 +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +unixsocket /work/socket-7385 +cluster-enabled yes +cluster-node-timeout 150 +requirepass foobared \ No newline at end of file diff --git a/src/test/resources/docker-env/test-cluster/config/node-7582/nodes.conf b/src/test/resources/docker-env/test-cluster/config/node-7582/nodes.conf new file mode 100644 index 0000000000..1c71f1fd5d --- /dev/null +++ b/src/test/resources/docker-env/test-cluster/config/node-7582/nodes.conf @@ -0,0 +1,5 @@ +c2043458aa5646cee429fdd5e3c18220dddf2ce5 127.0.0.1:7580 master - 1434887920102 1434887920002 0 connected 10001-16383 +27f88788f03a86296b7d860152f4ae24ee59c8c9 127.0.0.1:7579 myself,master - 0 0 1 connected 0-10000 +2c07344ffa94ede5ea57a2367f190af6144c1adb 127.0.0.1:7582 slave c2043458aa5646cee429fdd5e3c18220dddf2ce5 1434887920102 1434887920002 2 connected +1c541b6daf98719769e6aacf338a7d81f108a180 127.0.0.1:7581 slave 27f88788f03a86296b7d860152f4ae24ee59c8c9 1434887920102 1434887920002 3 connected +vars currentEpoch 3 lastVoteEpoch 0 \ No newline at end of file diff --git a/src/test/resources/docker-env/test-cluster/config/node-7582/redis.conf b/src/test/resources/docker-env/test-cluster/config/node-7582/redis.conf new file mode 100644 index 0000000000..e6fb7424a8 --- /dev/null +++ b/src/test/resources/docker-env/test-cluster/config/node-7582/redis.conf @@ -0,0 +1,7 @@ +port 7582 +save "" +appendonly no +client-output-buffer-limit pubsub 256k 128k 5 +unixsocket /work/socket-7582 +cluster-enabled yes +cluster-node-timeout 150 \ No newline at end of file diff --git a/src/test/resources/docker/docker-compose.yml b/src/test/resources/docker/docker-compose.yml index 97b1777373..58a83da333 100644 --- a/src/test/resources/docker/docker-compose.yml +++ b/src/test/resources/docker/docker-compose.yml @@ -1,8 +1,11 @@ --- +x-client-libs-stack-image: &client-libs-stack-image + image: "redislabs/client-libs-test:${REDIS_STACK_VERSION:-8.0-M04-pre}" + services: standalone-stack: - image: "${CLIENT_LIBS_TEST_IMAGE}:${REDIS_STACK_VERSION}" + <<: *client-libs-stack-image environment: - REDIS_CLUSTER=no - PORT=6379 @@ -10,7 +13,7 @@ services: - "16379:6379" clustered-stack: - image: "${CLIENT_LIBS_TEST_IMAGE}:${REDIS_STACK_VERSION}" + <<: *client-libs-stack-image environment: - REDIS_CLUSTER=yes - PORT=36379 diff --git a/src/test/resources/log4j2-test.xml b/src/test/resources/log4j2-test.xml index efb5371536..981918b55e 100644 --- a/src/test/resources/log4j2-test.xml +++ b/src/test/resources/log4j2-test.xml @@ -12,8 +12,10 @@ - + +