Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
* @author Shyngys Sapraliyev
* @author Jeonggyu Choi
* @author Mingi Lee
* @author Anne Lee
*/
@NullUnmarked
@SuppressWarnings({ "ConstantConditions", "deprecation" })
Expand Down Expand Up @@ -248,7 +249,12 @@ public RedisZSetCommands zSetCommands() {
return this;
}

@Override
@Override
public RedisVectorSetCommands vectorSetCommands() {
return delegate.vectorSetCommands();
}

@Override
public Long append(byte[] key, byte[] value) {
return convertAndReturn(delegate.append(key, value), Converters.identityConverter());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* Provides access to {@link RedisCommands} and the segregated command interfaces.
*
* @author Mark Paluch
* @author Anne Lee
* @since 3.0
*/
public interface RedisCommandsProvider {
Expand Down Expand Up @@ -118,4 +119,12 @@ public interface RedisCommandsProvider {
* @since 2.0
*/
RedisZSetCommands zSetCommands();

/**
* Get {@link RedisVectorSetCommands}.
*
* @return never {@literal null}.
* @since 3.5
*/
RedisVectorSetCommands vectorSetCommands();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* Copyright 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.connection;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;
import org.springframework.data.domain.Vector;

import java.util.Objects;

/**
* Vector Set-specific commands supported by Redis.
*
* @author Anne Lee
* @see RedisCommands
*/
@NullUnmarked
public interface RedisVectorSetCommands {

/**
* Add a vector to a vector set using FP32 binary format.
*
* @param key the key
* @param vector the vector as FP32 binary blob
* @param element the element name
* @param options the options for the command
* @return true if the element was added, false if it already existed
*/
Boolean vAdd(byte @NonNull [] key, byte @NonNull [] vector, byte @NonNull [] element, @Nullable VAddOptions options);

/**
* Add a vector to a vector set using Vector.
*
* @param key the key
* @param vector the vector
* @param element the element name
* @param options the options for the command
* @return true if the element was added, false if it already existed
*/
Boolean vAdd(byte @NonNull [] key, @NonNull Vector vector, byte @NonNull [] element, VAddOptions options);

/**
* Add a vector to a vector set using double array.
*
* @param key the key
* @param vector the vector as double array
* @param element the element name
* @param options the options for the command
* @return true if the element was added, false if it already existed
*/
default Boolean vAdd(byte @NonNull [] key, double @NonNull [] vector, byte @NonNull [] element, VAddOptions options) {
return vAdd(key, Vector.unsafe(vector), element, options);
}

/**
* Options for the VADD command.
*
* Note on attributes:
* - Attributes should be provided as a JSON string
* - The caller is responsible for JSON serialization
*/
class VAddOptions {

private static final VAddOptions DEFAULT = new VAddOptions(null, false, QuantizationType.Q8, null, null, null);

private final @Nullable Integer reduceDim;
private final boolean cas;
private final QuantizationType quantization;
private final @Nullable Integer efBuildFactor;
private final @Nullable String attributes;
private final @Nullable Integer maxConnections;

public VAddOptions(@Nullable Integer reduceDim, boolean cas, QuantizationType quantization,
@Nullable Integer efBuildFactor, @Nullable String attributes,
@Nullable Integer maxConnections) {
this.reduceDim = reduceDim;
this.cas = cas;
this.quantization = quantization;
this.efBuildFactor = efBuildFactor;
this.attributes = attributes;
this.maxConnections = maxConnections;
}

public static VAddOptions defaults() {
return DEFAULT;
}

public static VAddOptions reduceDim(@Nullable Integer reduceDim) {
return new VAddOptions(reduceDim, false, QuantizationType.Q8, null, null, null);
}

public static VAddOptions cas(boolean cas) {
return new VAddOptions(null, cas, QuantizationType.Q8, null, null, null);
}

public static VAddOptions quantization(QuantizationType quantization) {
return new VAddOptions(null, false, quantization, null, null, null);
}

public static VAddOptions efBuildFactor(@Nullable Integer efBuildFactor) {
return new VAddOptions(null, false, QuantizationType.Q8, efBuildFactor, null, null);
}

public static VAddOptions attributes(@Nullable String attributes) {
return new VAddOptions(null, false, QuantizationType.Q8, null, attributes, null);
}

public static VAddOptions maxConnections(@Nullable Integer maxConnections) {
return new VAddOptions(null, false, QuantizationType.Q8, null, null, maxConnections);
}

public static VAddOptions casWithQuantization(boolean cas, QuantizationType quantization) {
return new VAddOptions(null, cas, quantization, null, null, null);
}

public static VAddOptions reduceDimWithQuantization(Integer reduceDim, QuantizationType quantization) {
return new VAddOptions(reduceDim, false, quantization, null, null, null);
}

public @Nullable Integer getReduceDim() {
return reduceDim;
}

public boolean isCas() {
return cas;
}

public QuantizationType getQuantization() {
return quantization;
}

public @Nullable Integer getEfBuildFactor() {
return efBuildFactor;
}

public @Nullable String getAttributes() {
return attributes;
}

public @Nullable Integer getMaxConnections() {
return maxConnections;
}

@Override
public boolean equals(Object o) {
if (!(o instanceof VAddOptions that)) {return false;}
return cas == that.cas && Objects.equals(reduceDim, that.reduceDim)
&& quantization == that.quantization && Objects.equals(efBuildFactor, that.efBuildFactor)
&& Objects.equals(attributes, that.attributes) && Objects.equals(maxConnections,
that.maxConnections);
}

@Override
public int hashCode() {
return Objects.hash(reduceDim, cas, quantization, efBuildFactor, attributes, maxConnections);
}

public enum QuantizationType {
NOQUANT,
Q8,
BIN,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
* @author Pavel Khokhlov
* @author Liming Deng
* @author John Blum
* @author Anne Lee
* @since 1.7
*/
@NullUnmarked
Expand All @@ -97,6 +98,7 @@ public class JedisClusterConnection implements RedisClusterConnection {
private final JedisClusterStreamCommands streamCommands = new JedisClusterStreamCommands(this);
private final JedisClusterStringCommands stringCommands = new JedisClusterStringCommands(this);
private final JedisClusterZSetCommands zSetCommands = new JedisClusterZSetCommands(this);
private final JedisClusterVSetCommands vSetCommands = new JedisClusterVSetCommands(this);

private boolean closed;

Expand Down Expand Up @@ -309,7 +311,10 @@ public RedisZSetCommands zSetCommands() {
return zSetCommands;
}

@Override
@Override
public RedisVectorSetCommands vectorSetCommands() { return vSetCommands; }

@Override
public RedisScriptingCommands scriptingCommands() {
return new JedisClusterScriptingCommands(this);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.connection.jedis;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.NullUnmarked;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Vector;
import org.springframework.data.redis.connection.RedisVectorSetCommands;
import org.springframework.util.Assert;

import redis.clients.jedis.params.VAddParams;

/**
* Cluster {@link RedisVectorSetCommands} implementation for Jedis.
*
* @author Anne Lee
* @since 3.5
*/
@NullUnmarked
class JedisClusterVSetCommands implements RedisVectorSetCommands {


private final JedisClusterConnection connection;

JedisClusterVSetCommands(@NonNull JedisClusterConnection connection) {
this.connection = connection;
}

@Override
public Boolean vAdd(byte @NonNull [] key, byte @NonNull [] vector, byte @NonNull [] element,
VAddOptions options) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(vector, "Vector must not be null");
Assert.notNull(element, "Element must not be null");

try {
if (options == null) {
return connection.getCluster().vaddFP32(key, vector, element);
}

VAddParams params = JedisConverters.toVAddParams(options);

if (options.getReduceDim() != null) {
// With REDUCE dimension
return connection.getCluster().vaddFP32(key, vector, element, options.getReduceDim(), params);
}

return connection.getCluster().vaddFP32(key, vector, element, params);
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}
}

@Override
public Boolean vAdd(byte @NonNull [] key, @NonNull Vector vector, byte @NonNull [] element,
VAddOptions options) {

Assert.notNull(key, "Key must not be null");
Assert.notNull(vector, "Vector must not be null");
Assert.notNull(element, "Element must not be null");

try {
if (options == null) {
return connection.getCluster().vadd(key, vector.toFloatArray(), element);
}

VAddParams params = JedisConverters.toVAddParams(options);

if (options.getReduceDim() != null) {
// With REDUCE dimension
return connection.getCluster().vadd(key, vector.toFloatArray(), element, options.getReduceDim(), params);
}

return connection.getCluster().vadd(key, vector.toFloatArray(), element, params);
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}
}

private DataAccessException convertJedisAccessException(Exception ex) {
return connection.convertJedisAccessException(ex);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
* @author Guy Korland
* @author Dengliming
* @author John Blum
* @author Anne Lee
* @see redis.clients.jedis.Jedis
*/
@NullUnmarked
Expand Down Expand Up @@ -109,6 +110,7 @@ public class JedisConnection extends AbstractRedisConnection {
private final JedisStreamCommands streamCommands = new JedisStreamCommands(this);
private final JedisStringCommands stringCommands = new JedisStringCommands(this);
private final JedisZSetCommands zSetCommands = new JedisZSetCommands(this);
private final JedisVectorSetCommands vectorSetCommands = new JedisVectorSetCommands(this);

private final Log LOGGER = LogFactory.getLog(getClass());

Expand Down Expand Up @@ -284,6 +286,11 @@ public RedisServerCommands serverCommands() {
return serverCommands;
}

@Override
public RedisVectorSetCommands vectorSetCommands() {
return vectorSetCommands;
}

@Override
public Object execute(@NonNull String command, byte @NonNull []... args) {

Expand Down
Loading