@@ -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 ("\n Query 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 ("\n Upsert 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 ("\n Upsert 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 ("\n Partial 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 ("\n Partial 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