Skip to content

Commit aede4bc

Browse files
committed
MLE-22706 Added encode/decode support for vectors
1 parent e6eccc2 commit aede4bc

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
3+
*/
4+
package com.marklogic.client.util;
5+
6+
import java.nio.ByteBuffer;
7+
import java.nio.ByteOrder;
8+
import java.util.Base64;
9+
10+
/**
11+
* Supports encoding and decoding vectors using the same approach as the vec:base64-encode and vec:base64-decode
12+
* functions supported by the MarkLogic server.
13+
*
14+
* @since 7.2.0
15+
*/
16+
public interface VectorUtil {
17+
18+
/**
19+
* @param vector
20+
* @return a base64-encoded string representing the vector and using the same approach as the vec:base64-encode
21+
* function supported by the MarkLogic server.
22+
*/
23+
static String base64Encode(float... vector) {
24+
final int dimensions = vector.length;
25+
ByteBuffer buffer = ByteBuffer.allocate(8 + 4 * dimensions);
26+
buffer.order(ByteOrder.LITTLE_ENDIAN);
27+
buffer.putInt(0); // version
28+
buffer.putInt(dimensions);
29+
for (float v : vector) {
30+
buffer.putFloat(v);
31+
}
32+
return Base64.getEncoder().encodeToString(buffer.array());
33+
}
34+
35+
/**
36+
* @param encodedVector
37+
* @return a vector represented by the base64-encoded string and using the same approach as the vec:base64-decode
38+
* function supported by the MarkLogic server.
39+
*/
40+
static float[] base64Decode(String encodedVector) {
41+
ByteBuffer buffer = ByteBuffer.wrap(Base64.getDecoder().decode(encodedVector));
42+
buffer.order(ByteOrder.LITTLE_ENDIAN);
43+
44+
final int version = buffer.getInt();
45+
if (version != 0) {
46+
throw new IllegalArgumentException("Unsupported vector version: " + version);
47+
}
48+
49+
final int dimensions = buffer.getInt();
50+
float[] vector = new float[dimensions];
51+
for (int i = 0; i < dimensions; i++) {
52+
vector[i] = buffer.getFloat();
53+
}
54+
return vector;
55+
}
56+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
3+
*/
4+
package com.marklogic.client.util;
5+
6+
import com.fasterxml.jackson.databind.JsonNode;
7+
import com.fasterxml.jackson.databind.node.ArrayNode;
8+
import com.marklogic.client.test.Common;
9+
import org.junit.jupiter.api.Test;
10+
11+
import static org.junit.jupiter.api.Assertions.assertEquals;
12+
13+
class VectorUtilTest {
14+
15+
private final float[] VECTOR = new float[]{3.14f, 1.59f, 2.65f};
16+
private final double ACCEPTABLE_DELTA = 0.0001;
17+
18+
@Test
19+
void encodeAndDecodeWithJavaClient() {
20+
String encoded = VectorUtil.base64Encode(VECTOR);
21+
assertEquals("AAAAAAMAAADD9UhAH4XLP5qZKUA=", encoded);
22+
23+
float[] decoded = VectorUtil.base64Decode(encoded);
24+
assertEquals(VECTOR.length, decoded.length);
25+
for (int i = 0; i < VECTOR.length; i++) {
26+
assertEquals(VECTOR[i], decoded[i], ACCEPTABLE_DELTA);
27+
}
28+
}
29+
30+
@Test
31+
void encodeAndDecodeWithServer() {
32+
String encoded = VectorUtil.base64Encode(VECTOR);
33+
assertEquals("AAAAAAMAAADD9UhAH4XLP5qZKUA=", encoded);
34+
35+
ArrayNode decoded = (ArrayNode) Common.newEvalClient().newServerEval()
36+
.xquery("vec:base64-decode('%s')".formatted(encoded))
37+
.evalAs(JsonNode.class);
38+
39+
assertEquals(3, decoded.size());
40+
assertEquals(3.14f, decoded.get(0).asDouble(), ACCEPTABLE_DELTA);
41+
assertEquals(1.59f, decoded.get(1).asDouble(), ACCEPTABLE_DELTA);
42+
assertEquals(2.65f, decoded.get(2).asDouble(), ACCEPTABLE_DELTA);
43+
}
44+
45+
@Test
46+
void encodeWithServerAndDecodeWithJavaClient() {
47+
String encoded = Common.newEvalClient().newServerEval()
48+
.xquery("vec:base64-encode(vec:vector((3.14, 1.59, 2.65)))")
49+
.evalAs(String.class);
50+
assertEquals("AAAAAAMAAADD9UhAH4XLP5qZKUA=", encoded);
51+
52+
float[] decoded = VectorUtil.base64Decode(encoded);
53+
assertEquals(VECTOR.length, decoded.length);
54+
for (int i = 0; i < VECTOR.length; i++) {
55+
assertEquals(VECTOR[i], decoded[i], ACCEPTABLE_DELTA);
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)