Skip to content

Commit 04f5d38

Browse files
External type: add annotation, model API and smoke test objectbox-java#239
1 parent b568219 commit 04f5d38

File tree

9 files changed

+228
-11
lines changed

9 files changed

+228
-11
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright 2025 ObjectBox Ltd. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.objectbox.annotation;
18+
19+
20+
/**
21+
* A property type of an external system (e.g. another database) that has no default mapping to an ObjectBox type.
22+
* <p>
23+
* Use with {@link ExternalType @ExternalType}.
24+
*/
25+
public enum ExternalPropertyType {
26+
27+
/**
28+
* Representing type: ByteVector
29+
* <p>
30+
* Encoding: 1:1 binary representation, little endian (16 bytes)
31+
*/
32+
INT_128,
33+
/**
34+
* Representing type: ByteVector
35+
* <p>
36+
* Encoding: 1:1 binary representation (16 bytes)
37+
*/
38+
UUID,
39+
/**
40+
* IEEE 754 decimal128 type, e.g. supported by MongoDB.
41+
* <p>
42+
* Representing type: ByteVector
43+
* <p>
44+
* Encoding: 1:1 binary representation (16 bytes)
45+
*/
46+
DECIMAL_128,
47+
/**
48+
* A key/value map; e.g. corresponds to a JSON object or a MongoDB document (although not keeping the key order).
49+
* Unlike the Flex type, this must contain a map value (e.g. not a vector or a scalar).
50+
* <p>
51+
* Representing type: Flex
52+
* <p>
53+
* Encoding: Flex
54+
*/
55+
FLEX_MAP,
56+
/**
57+
* A vector (aka list or array) of flexible elements; e.g. corresponds to a JSON array or a MongoDB array. Unlike
58+
* the Flex type, this must contain a vector value (e.g. not a map or a scalar).
59+
* <p>
60+
* Representing type: Flex
61+
* <p>
62+
* Encoding: Flex
63+
*/
64+
FLEX_VECTOR,
65+
/**
66+
* Placeholder (not yet used) for a JSON document.
67+
* <p>
68+
* Representing type: String
69+
*/
70+
JSON,
71+
/**
72+
* Placeholder (not yet used) for a BSON document.
73+
* <p>
74+
* Representing type: ByteVector
75+
*/
76+
BSON,
77+
/**
78+
* JavaScript source code.
79+
* <p>
80+
* Representing type: String
81+
*/
82+
JAVASCRIPT,
83+
/**
84+
* A vector (array) of Int128 values.
85+
*/
86+
INT_128_VECTOR,
87+
/**
88+
* A vector (array) of Int128 values.
89+
*/
90+
UUID_VECTOR,
91+
/**
92+
* The 12-byte ObjectId type in MongoDB.
93+
* <p>
94+
* Representing type: ByteVector
95+
* <p>
96+
* Encoding: 1:1 binary representation (12 bytes)
97+
*/
98+
MONGO_ID,
99+
/**
100+
* A vector (array) of MongoId values.
101+
*/
102+
MONGO_ID_VECTOR,
103+
/**
104+
* Representing type: Long
105+
* <p>
106+
* Encoding: Two unsigned 32-bit integers merged into a 64-bit integer.
107+
*/
108+
MONGO_TIMESTAMP,
109+
/**
110+
* Representing type: ByteVector
111+
* <p>
112+
* Encoding: 3 zero bytes (reserved, functions as padding), fourth byte is the sub-type, followed by the binary
113+
* data.
114+
*/
115+
MONGO_BINARY,
116+
/**
117+
* Representing type: string vector with 2 elements (index 0: pattern, index 1: options)
118+
* <p>
119+
* Encoding: 1:1 string representation
120+
*/
121+
MONGO_REGEX
122+
123+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2025 ObjectBox Ltd. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.objectbox.annotation;
18+
19+
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Use to set the type of property in an external system (like another database).
27+
* <p>
28+
* This is useful if there is no default mapping of the ObjectBox type to the type in the external system.
29+
* <p>
30+
* Carefully look at the documentation of the external type to ensure it is compatible with the ObjectBox type.
31+
*/
32+
@Retention(RetentionPolicy.CLASS)
33+
@Target({ElementType.FIELD})
34+
public @interface ExternalType {
35+
36+
ExternalPropertyType value();
37+
38+
}

objectbox-java/src/main/java/io/objectbox/ModelBuilder.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.objectbox.annotation.HnswIndex;
2525
import io.objectbox.annotation.apihint.Internal;
2626
import io.objectbox.flatbuffers.FlatBufferBuilder;
27+
import io.objectbox.model.ExternalPropertyType;
2728
import io.objectbox.model.HnswDistanceType;
2829
import io.objectbox.model.HnswFlags;
2930
import io.objectbox.model.HnswParams;
@@ -67,6 +68,7 @@ public class PropertyBuilder {
6768
private int indexId;
6869
private long indexUid;
6970
private int indexMaxValueLength;
71+
private int externalPropertyType;
7072
private int hnswParamsOffset;
7173

7274
PropertyBuilder(String name, @Nullable String targetEntityName, @Nullable String virtualTarget, int type) {
@@ -96,6 +98,17 @@ public PropertyBuilder indexMaxValueLength(int indexMaxValueLength) {
9698
return this;
9799
}
98100

101+
/**
102+
* Set a {@link ExternalPropertyType} constant.
103+
*
104+
* @return this builder.
105+
*/
106+
public PropertyBuilder externalType(int externalPropertyType) {
107+
checkNotFinished();
108+
this.externalPropertyType = externalPropertyType;
109+
return this;
110+
}
111+
99112
/**
100113
* Set parameters for {@link HnswIndex}.
101114
*
@@ -183,6 +196,9 @@ public int finish() {
183196
if (indexMaxValueLength > 0) {
184197
ModelProperty.addMaxIndexValueLength(fbb, indexMaxValueLength);
185198
}
199+
if (externalPropertyType != 0) {
200+
ModelProperty.addExternalType(fbb, externalPropertyType);
201+
}
186202
if (hnswParamsOffset != 0) {
187203
ModelProperty.addHnswParams(fbb, hnswParamsOffset);
188204
}

tests/objectbox-java-test/src/main/java/io/objectbox/TestEntity.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import javax.annotation.Nullable;
2525

2626
import io.objectbox.annotation.Entity;
27+
import io.objectbox.annotation.ExternalPropertyType;
28+
import io.objectbox.annotation.ExternalType;
2729
import io.objectbox.annotation.Id;
2830
import io.objectbox.annotation.Unsigned;
2931

@@ -73,6 +75,9 @@ public class TestEntity {
7375
private float[] floatArray;
7476
private double[] doubleArray;
7577
private Date date;
78+
// Just smoke testing, also use UUID instead of the default Mongo ID
79+
@ExternalType(ExternalPropertyType.UUID)
80+
private byte[] externalId;
7681

7782
transient boolean noArgsConstructorCalled;
7883

@@ -107,7 +112,8 @@ public TestEntity(long id,
107112
long[] longArray,
108113
float[] floatArray,
109114
double[] doubleArray,
110-
Date date
115+
Date date,
116+
byte[] externalId
111117
) {
112118
this.id = id;
113119
this.simpleBoolean = simpleBoolean;
@@ -133,6 +139,7 @@ public TestEntity(long id,
133139
this.floatArray = floatArray;
134140
this.doubleArray = doubleArray;
135141
this.date = date;
142+
this.externalId = externalId;
136143
if (STRING_VALUE_THROW_IN_CONSTRUCTOR.equals(simpleString)) {
137144
throw new RuntimeException(EXCEPTION_IN_CONSTRUCTOR_MESSAGE);
138145
}
@@ -348,6 +355,17 @@ public void setDate(Date date) {
348355
this.date = date;
349356
}
350357

358+
@Nullable
359+
public byte[] getExternalId() {
360+
return externalId;
361+
}
362+
363+
@Nullable
364+
public TestEntity setExternalId(byte[] externalId) {
365+
this.externalId = externalId;
366+
return this;
367+
}
368+
351369
@Override
352370
public String toString() {
353371
return "TestEntity{" +
@@ -375,6 +393,7 @@ public String toString() {
375393
", floatArray=" + Arrays.toString(floatArray) +
376394
", doubleArray=" + Arrays.toString(doubleArray) +
377395
", date=" + date +
396+
", externalId=" + Arrays.toString(externalId) +
378397
", noArgsConstructorCalled=" + noArgsConstructorCalled +
379398
'}';
380399
}

tests/objectbox-java-test/src/main/java/io/objectbox/TestEntityCursor.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public Cursor<TestEntity> createCursor(io.objectbox.Transaction tx, long cursorH
7373
private final static int __ID_floatArray = TestEntity_.floatArray.id;
7474
private final static int __ID_doubleArray = TestEntity_.doubleArray.id;
7575
private final static int __ID_date = TestEntity_.date.id;
76+
private final static int __ID_externalId = TestEntity_.externalId.id;
7677

7778
public TestEntityCursor(io.objectbox.Transaction tx, long cursor, BoxStore boxStore) {
7879
super(tx, cursor, TestEntity_.__INSTANCE, boxStore);
@@ -143,23 +144,25 @@ public long put(TestEntity entity) {
143144
int __id8 = simpleString != null ? __ID_simpleString : 0;
144145
byte[] simpleByteArray = entity.getSimpleByteArray();
145146
int __id9 = simpleByteArray != null ? __ID_simpleByteArray : 0;
147+
byte[] externalId = entity.getExternalId();
148+
int __id24 = externalId != null ? __ID_externalId : 0;
146149
Map stringObjectMap = entity.getStringObjectMap();
147150
int __id15 = stringObjectMap != null ? __ID_stringObjectMap : 0;
148-
Object flexProperty = entity.getFlexProperty();
149-
int __id16 = flexProperty != null ? __ID_flexProperty : 0;
150151

151152
collect430000(cursor, 0, 0,
152153
__id8, simpleString, 0, null,
153154
0, null, 0, null,
154-
__id9, simpleByteArray, __id15, __id15 != 0 ? stringObjectMapConverter.convertToDatabaseValue(stringObjectMap) : null,
155-
__id16, __id16 != 0 ? flexPropertyConverter.convertToDatabaseValue(flexProperty) : null);
155+
__id9, simpleByteArray, __id24, externalId,
156+
__id15, __id15 != 0 ? stringObjectMapConverter.convertToDatabaseValue(stringObjectMap) : null);
156157

158+
Object flexProperty = entity.getFlexProperty();
159+
int __id16 = flexProperty != null ? __ID_flexProperty : 0;
157160
java.util.Date date = entity.getDate();
158161
int __id23 = date != null ? __ID_date : 0;
159162

160163
collect313311(cursor, 0, 0,
161164
0, null, 0, null,
162-
0, null, 0, null,
165+
0, null, __id16, __id16 != 0 ? flexPropertyConverter.convertToDatabaseValue(flexProperty) : null,
163166
__ID_simpleLong, entity.getSimpleLong(), __ID_simpleLongU, entity.getSimpleLongU(),
164167
__id23, __id23 != 0 ? date.getTime() : 0, INT_NULL_HACK ? 0 : __ID_simpleInt, entity.getSimpleInt(),
165168
__ID_simpleIntU, entity.getSimpleIntU(), __ID_simpleShort, entity.getSimpleShort(),

tests/objectbox-java-test/src/main/java/io/objectbox/TestEntity_.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ public final class TestEntity_ implements EntityInfo<TestEntity> {
124124
public final static io.objectbox.Property<TestEntity> date =
125125
new io.objectbox.Property<>(__INSTANCE, 23, 24, java.util.Date.class, "date");
126126

127+
public final static io.objectbox.Property<TestEntity> externalId =
128+
new io.objectbox.Property<>(__INSTANCE, 24, 25, byte[].class, "externalId");
129+
127130
@SuppressWarnings("unchecked")
128131
public final static io.objectbox.Property<TestEntity>[] __ALL_PROPERTIES = new io.objectbox.Property[]{
129132
id,
@@ -149,7 +152,8 @@ public final class TestEntity_ implements EntityInfo<TestEntity> {
149152
longArray,
150153
floatArray,
151154
doubleArray,
152-
date
155+
date,
156+
externalId
153157
};
154158

155159
public final static io.objectbox.Property<TestEntity> __ID_PROPERTY = id;

tests/objectbox-java-test/src/test/java/io/objectbox/AbstractObjectBoxTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import io.objectbox.ModelBuilder.PropertyBuilder;
4343
import io.objectbox.annotation.IndexType;
4444
import io.objectbox.config.DebugFlags;
45+
import io.objectbox.model.ExternalPropertyType;
4546
import io.objectbox.model.PropertyFlags;
4647
import io.objectbox.model.PropertyType;
4748

@@ -303,7 +304,14 @@ private void addTestEntity(ModelBuilder modelBuilder, @Nullable IndexType simple
303304
// Date property
304305
entityBuilder.property("date", PropertyType.Date).id(TestEntity_.date.id, ++lastUid);
305306

306-
int lastId = TestEntity_.date.id;
307+
int lastId = TestEntity_.externalId.id;
308+
309+
// External type property
310+
// Note: there is no way to test external type mapping works here. Instead, verify passing a model with
311+
// externalType(int) works.
312+
entityBuilder.property("externalId", PropertyType.ByteVector).id(lastId, ++lastUid)
313+
.externalType(ExternalPropertyType.Uuid);
314+
307315
entityBuilder.lastPropertyId(lastId, lastUid);
308316
addOptionalFlagsToTestEntity(entityBuilder);
309317
entityBuilder.entityDone();
@@ -357,6 +365,9 @@ protected TestEntity createTestEntity(@Nullable String simpleString, int nr) {
357365
entity.setFloatArray(new float[]{-entity.getSimpleFloat(), entity.getSimpleFloat()});
358366
entity.setDoubleArray(new double[]{-entity.getSimpleDouble(), entity.getSimpleDouble()});
359367
entity.setDate(new Date(1000 + nr));
368+
// Note: there is no way to test external type mapping works here. Instead, verify that
369+
// there are no side effects for put and get.
370+
entity.setExternalId(entity.getSimpleByteArray());
360371
return entity;
361372
}
362373

tests/objectbox-java-test/src/test/java/io/objectbox/BoxStoreBuilderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ public void maxDataSize() {
302302
DbMaxDataSizeExceededException.class,
303303
() -> getTestEntityBox().put(testEntity2)
304304
);
305-
assertEquals("Exceeded user-set maximum by [bytes]: 544", maxDataExc.getMessage());
305+
assertEquals("Exceeded user-set maximum by [bytes]: 560", maxDataExc.getMessage());
306306

307307
// Remove to get below max data size, then put again.
308308
getTestEntityBox().remove(testEntity1);

0 commit comments

Comments
 (0)