Skip to content

Commit e1d032b

Browse files
authored
Modify upsert example (#1648)
Signed-off-by: yhmo <[email protected]>
1 parent 20886c8 commit e1d032b

File tree

1 file changed

+182
-73
lines changed

1 file changed

+182
-73
lines changed

examples/src/main/java/io/milvus/v2/UpsertExample.java

Lines changed: 182 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ public class UpsertExample {
5050
private static final String COLLECTION_NAME = "java_sdk_example_upsert_v2";
5151
private static final String ID_FIELD = "pk";
5252
private static final String VECTOR_FIELD = "vector";
53-
private static final String TEXT_FIELD = "text";
54-
private static final String NULLABLE_FIELD = "nullable";
53+
private static final String TEXT_FIELD = "text_field";
54+
private static final String JSON_FIELD = "json_field";
55+
private static final String NULLABLE_FIELD = "nullable_field";
5556
private static final Integer VECTOR_DIM = 4;
5657

5758
private static List<Object> createCollection(boolean autoID) {
@@ -62,6 +63,7 @@ private static List<Object> createCollection(boolean autoID) {
6263

6364
// Create collection
6465
CreateCollectionReq.CollectionSchema collectionSchema = CreateCollectionReq.CollectionSchema.builder()
66+
.enableDynamicField(true)
6567
.build();
6668
collectionSchema.addField(AddFieldReq.builder()
6769
.fieldName(ID_FIELD)
@@ -79,6 +81,10 @@ private static List<Object> createCollection(boolean autoID) {
7981
.dataType(DataType.VarChar)
8082
.maxLength(100)
8183
.build());
84+
collectionSchema.addField(AddFieldReq.builder()
85+
.fieldName(JSON_FIELD)
86+
.dataType(DataType.JSON)
87+
.build());
8288
collectionSchema.addField(AddFieldReq.builder()
8389
.fieldName(NULLABLE_FIELD)
8490
.dataType(DataType.Int32)
@@ -112,7 +118,12 @@ private static List<Object> createCollection(boolean autoID) {
112118
List<Float> vector = CommonUtils.generateFloatVector(VECTOR_DIM);
113119
row.add(VECTOR_FIELD, gson.toJsonTree(vector));
114120
row.addProperty(TEXT_FIELD, String.format("text_%d", i));
121+
JsonObject metadata = new JsonObject();
122+
metadata.addProperty("foo", i);
123+
metadata.addProperty("bar", i);
124+
row.add(JSON_FIELD, metadata);
115125
row.addProperty(NULLABLE_FIELD, i);
126+
row.addProperty("dynamic", String.format("dynamic_%d", i)); // this is dynamic field
116127
rows.add(row);
117128
}
118129
InsertResp resp = client.insert(InsertReq.builder()
@@ -122,94 +133,192 @@ private static List<Object> createCollection(boolean autoID) {
122133
return resp.getPrimaryKeys();
123134
}
124135

125-
private static void queryWithExpr(String expr) {
136+
private static List<QueryResp.QueryResult> queryWithExpr(String expr) {
126137
QueryResp queryRet = client.query(QueryReq.builder()
127138
.collectionName(COLLECTION_NAME)
128139
.filter(expr)
129-
.outputFields(Arrays.asList(ID_FIELD, VECTOR_FIELD, TEXT_FIELD, NULLABLE_FIELD))
140+
.outputFields(Collections.singletonList("*"))
130141
.consistencyLevel(ConsistencyLevel.STRONG)
131142
.build());
132-
System.out.println("\nQuery with expression: " + expr);
143+
System.out.println("Query with expression: " + expr);
133144
List<QueryResp.QueryResult> records = queryRet.getQueryResults();
134145
for (QueryResp.QueryResult record : records) {
135146
System.out.println(record.getEntity());
136147
}
148+
return records;
137149
}
138150

139-
private static void doUpsert(boolean autoID) {
140-
System.out.printf("\n============================= autoID = %s =============================", autoID ? "true" : "false");
141-
// If autoID is true, the collection primary key is auto-generated by server
142-
List<Object> ids = createCollection(autoID);
151+
// update the entire row
152+
private static void fullUpsert(Object id) {
153+
System.out.println("------------------------------ full upsert ------------------------------");
154+
Gson gson = new Gson();
155+
// Query before upsert, get the No.2 primary key
156+
String filter = String.format("%s == %s", ID_FIELD, id);
157+
queryWithExpr(filter);
158+
159+
// Upsert, update all fields value
160+
// If autoID is true, the server will return a new primary key for the updated entity
161+
JsonObject row = new JsonObject();
162+
row.addProperty(ID_FIELD, (Long)id); // primary key must be input so that it can know which entity to be updated
163+
List<Float> vectorUpdated = Arrays.asList(1.0f, 1.0f, 1.0f, 1.0f);
164+
row.add(VECTOR_FIELD, gson.toJsonTree(vectorUpdated));
165+
String textUpdated = "this field has been updated";
166+
row.addProperty(TEXT_FIELD, textUpdated);
167+
JsonObject metadata = new JsonObject();
168+
metadata.addProperty("updated", "yes");
169+
row.add(JSON_FIELD, metadata); // the json field will be overridden
170+
row.add(NULLABLE_FIELD, null); // update nullable field to null
171+
UpsertResp upsertResp = client.upsert(UpsertReq.builder()
172+
.collectionName(COLLECTION_NAME)
173+
.data(Collections.singletonList(row))
174+
.build());
175+
List<Object> newIds = upsertResp.getPrimaryKeys();
176+
System.out.printf("\nUpsert done, primary key %s has been updated to %s%n", id, newIds.get(0));
177+
178+
// Query after upsert, you will see the vector field is [1.0f, 1.0f, 1.0f, 1.0f],
179+
// text field is "this field has been updated", nullable field is null
180+
filter = String.format("%s == %s", ID_FIELD, newIds.get(0));
181+
List<QueryResp.QueryResult> results = queryWithExpr(filter);
182+
183+
// Verify the result
184+
if (results.size() != newIds.size()) {
185+
throw new RuntimeException("Incorrect query result for filter: " + filter);
186+
}
187+
Map<String, Object> entity = results.get(0).getEntity();
188+
if (!vectorUpdated.equals(entity.get(VECTOR_FIELD))) {
189+
throw new RuntimeException("Vector field is not correctly updated for filter: " + filter);
190+
}
191+
if (!textUpdated.equals(entity.get(TEXT_FIELD))) {
192+
throw new RuntimeException("Text field is not correctly updated for filter: " + filter);
193+
}
194+
// In full upsert, JSON field is overridden
195+
if (!entity.get(JSON_FIELD).equals(metadata)) {
196+
throw new RuntimeException("JSON field is not correctly updated for filter: " + filter);
197+
}
198+
if (entity.get(NULLABLE_FIELD) != null) {
199+
throw new RuntimeException("Nullable field is not correctly updated for filter: " + filter);
200+
}
201+
// Note that we didn't input the dynamic field for full update, so it will treat it as removed
202+
if (entity.containsKey("dynamic")) {
203+
throw new RuntimeException("Dynamic field is not removed for filter: " + filter);
204+
}
205+
}
143206

207+
// update the specified field, other fields will keep old values
208+
private static void partialUpsert(List<Object> ids, boolean updateVector) {
209+
System.out.printf("\n------------------------------ partial upsert %s ------------------------------%n",
210+
updateVector ? "vector" : "scalars");
144211
Gson gson = new Gson();
145-
{
146-
// Query before upsert, get the No.2 primary key
147-
Long oldID = (Long) ids.get(1);
148-
String filter = String.format("%s == %d", ID_FIELD, oldID);
149-
queryWithExpr(filter);
150-
151-
// Upsert, update all fields value
152-
// If autoID is true, the server will return a new primary key for the updated entity
212+
// Query before upsert
213+
String filter = String.format("%s in %s", ID_FIELD, ids);
214+
List<QueryResp.QueryResult> oldResults = queryWithExpr(filter);
215+
216+
// Partial upsert, only update the specified field, other fields will keep old values
217+
// If autoID is true, the server will return a new primary key for the updated entity
218+
// Note: for the case to do partial upsert for multi entities, it only allows updating
219+
// the same fields for all rows.
220+
// For example, assume a collection has 2 fields: A and B
221+
// it is legal to update the same fields as: client.upsert(data = [ {"A": 1}, {"A": 3}])
222+
// it is illegal to update different fields as: client.upsert(data = [ {"A": 1}, {"B": 3}])
223+
// Read the doc for more info: https://milvus.io/docs/upsert-entities.md
224+
List<Float> vectorUpdated = Arrays.asList(1.0f, 1.0f, 1.0f, 1.0f);
225+
String textUpdated = "this row has been partially updated";
226+
List<JsonObject> rows = new ArrayList<>();
227+
for (Object id : ids) {
153228
JsonObject row = new JsonObject();
154-
row.addProperty(ID_FIELD, oldID);
155-
List<Float> vector = Arrays.asList(1.0f, 1.0f, 1.0f, 1.0f);
156-
row.add(VECTOR_FIELD, gson.toJsonTree(vector));
157-
row.addProperty(TEXT_FIELD, "this field has been updated");
158-
row.add(NULLABLE_FIELD, null); // update nullable field to null
159-
UpsertResp upsertResp = client.upsert(UpsertReq.builder()
160-
.collectionName(COLLECTION_NAME)
161-
.data(Collections.singletonList(row))
162-
.build());
163-
List<Object> newIds = upsertResp.getPrimaryKeys();
164-
Long newID = (Long) newIds.get(0);
165-
System.out.printf("\nUpsert done, primary key %d has been updated to %d%n", oldID, newID);
166-
167-
// Query after upsert, you will see the vector field is [1.0f, 1.0f, 1.0f, 1.0f],
168-
// text field is "this field has been updated", nullable field is null
169-
filter = String.format("%s == %d", ID_FIELD, newID);
170-
queryWithExpr(filter);
229+
row.addProperty(ID_FIELD, (Long) id); // primary key must be input so that it can know which entity to be updated
230+
if (updateVector) {
231+
row.add(VECTOR_FIELD, gson.toJsonTree(vectorUpdated));
232+
} else {
233+
row.addProperty(TEXT_FIELD, textUpdated);
234+
row.add(NULLABLE_FIELD, null);
235+
JsonObject metadata = new JsonObject();
236+
metadata.addProperty("updated", "yes");
237+
row.add(JSON_FIELD, metadata); // the json field will be merged in partial upsert
238+
}
239+
row.addProperty("new_dynamic", "new"); // add a new dynamic field
240+
rows.add(row);
171241
}
172242

173-
{
174-
// Query before upsert, get the No.5 and No.6 primary key
175-
Long oldID1 = (Long)ids.get(4);
176-
Long oldID2 = (Long)ids.get(5);
177-
String filter = String.format("%s in [%d, %d]", ID_FIELD, oldID1, oldID2);
178-
queryWithExpr(filter);
179-
180-
// Partial upsert, only update the specified field, other fields will keep old values
181-
// If autoID is true, the server will return a new primary key for the updated entity
182-
// Note: for the case to do partial upsert for multi entities, it only allows updating
183-
// the same fields for all rows.
184-
// For example, assume a collection has 2 fields: A and B
185-
// it is legal to update the same fields as: client.upsert(data = [ {"A": 1}, {"A": 3}])
186-
// it is illegal to update different fields as: client.upsert(data = [ {"A": 1}, {"B": 3}])
187-
// Read the doc for more info: https://milvus.io/docs/upsert-entities.md
188-
// Here we update the same field "text" for the two rows.
189-
JsonObject row1 = new JsonObject();
190-
row1.addProperty(ID_FIELD, oldID1);
191-
row1.addProperty(TEXT_FIELD, "this row has been partially updated");
192-
193-
JsonObject row2 = new JsonObject();
194-
row2.addProperty(ID_FIELD, oldID2);
195-
row2.addProperty(TEXT_FIELD, "this row has been partially updated");
196-
197-
UpsertResp upsertResp = client.upsert(UpsertReq.builder()
198-
.collectionName(COLLECTION_NAME)
199-
.data(Arrays.asList(row1, row2))
200-
.partialUpdate(true)
201-
.build());
202-
List<Object> newIds = upsertResp.getPrimaryKeys();
203-
Long newID1 = (Long) newIds.get(0);
204-
Long newID2 = (Long) newIds.get(1);
205-
System.out.printf("\nPartial upsert done, primary key %d has been updated to %d, %d has been updated to %d%n",
206-
oldID1, newID1, oldID2, newID2);
207-
208-
// query after upsert, you will see the text field is "this row has been partially updated"
209-
// the other fields keep old values
210-
filter = String.format("%s in [%d, %d]", ID_FIELD, newID1, newID2);
211-
queryWithExpr(filter);
243+
UpsertResp upsertResp = client.upsert(UpsertReq.builder()
244+
.collectionName(COLLECTION_NAME)
245+
.data(rows)
246+
.partialUpdate(true)
247+
.build());
248+
List<Object> newIds = upsertResp.getPrimaryKeys();
249+
System.out.printf("\nPartial upsert done, primary keys %s has been updated to %s%n", ids, newIds);
250+
251+
// query after upsert, you will see the text field is "this row has been partially updated"
252+
// the other fields keep old values
253+
filter = String.format("%s in %s", ID_FIELD, newIds);
254+
List<QueryResp.QueryResult> results = queryWithExpr(filter);
255+
256+
// Verify the result
257+
if (results.size() != newIds.size()) {
258+
throw new RuntimeException("Incorrect query result for filter: " + filter);
212259
}
260+
261+
for (int i = 0; i < results.size(); i++) {
262+
Map<String, Object> oldEntity = oldResults.get(i).getEntity();
263+
Map<String, Object> entity = results.get(i).getEntity();
264+
if (updateVector) {
265+
// only vector field is updated
266+
if (!vectorUpdated.equals(entity.get(VECTOR_FIELD))) {
267+
throw new RuntimeException("Vector field is not correctly updated for filter: " + filter);
268+
}
269+
270+
// the other fields keep old values
271+
if (!entity.get(TEXT_FIELD).equals(oldEntity.get(TEXT_FIELD))) {
272+
throw new RuntimeException("Text field should not be updated for filter: " + filter);
273+
}
274+
if (!entity.get(JSON_FIELD).equals(oldEntity.get(JSON_FIELD))) {
275+
throw new RuntimeException("JSON field should not be updated for filter: " + filter);
276+
}
277+
if (!entity.get(NULLABLE_FIELD).equals(oldEntity.get(NULLABLE_FIELD))) {
278+
throw new RuntimeException("Nullable field should not be updated for filter: " + filter);
279+
}
280+
} else {
281+
// scalar fields are updated
282+
if (!textUpdated.equals(entity.get(TEXT_FIELD))) {
283+
throw new RuntimeException("Text field is not correctly updated for filter: " + filter);
284+
}
285+
if (entity.get(NULLABLE_FIELD) != null) {
286+
throw new RuntimeException("Nullable field is not correctly updated for filter: " + filter);
287+
}
288+
JsonObject newJson = (JsonObject)entity.get(JSON_FIELD);
289+
if (!newJson.has("updated") && !newJson.get("updated").equals("yes")) {
290+
throw new RuntimeException("JSON field is not correctly updated for filter: " + filter);
291+
}
292+
293+
// vector field keep old value
294+
if (!entity.get(VECTOR_FIELD).equals(oldEntity.get(VECTOR_FIELD))) {
295+
throw new RuntimeException("Vector field should not be updated for filter: " + filter);
296+
}
297+
}
298+
// Note that we didn't input the dynamic field for partial update, it will keep old value
299+
if (!entity.get("dynamic").equals(oldEntity.get("dynamic"))) {
300+
throw new RuntimeException("Dynamic field should not be updated for filter: " + filter);
301+
}
302+
// Verify the new dynamic field is merged
303+
if (!entity.containsKey("new_dynamic") && !entity.get("new_dynamic").equals("new")) {
304+
throw new RuntimeException("New dynamic field is not merged for filter: " + filter);
305+
}
306+
}
307+
}
308+
309+
private static void doUpsert(boolean autoID) {
310+
System.out.printf("\n================================== autoID = %s ==================================", autoID ? "true" : "false");
311+
// If autoID is true, the collection primary key is auto-generated by server
312+
List<Object> ids = createCollection(autoID);
313+
314+
// Update the entire row of the No.2 entity
315+
fullUpsert((Long)ids.get(1));
316+
317+
// Partially update the vectors of No.5 and No.6 entities
318+
partialUpsert(ids.subList(4, 6), true);
319+
320+
// Partially update the scalar fields of No.10 entity
321+
partialUpsert(ids.subList(9, 10), false);
213322
}
214323

215324
public static void main(String[] args) {

0 commit comments

Comments
 (0)