diff --git a/dev-tools/maven/lucene/sandbox/pom.xml.template b/dev-tools/maven/lucene/sandbox/pom.xml.template index d357c6465e4..baa8a3c04db 100644 --- a/dev-tools/maven/lucene/sandbox/pom.xml.template +++ b/dev-tools/maven/lucene/sandbox/pom.xml.template @@ -48,13 +48,6 @@ lucene-test-framework test - - org.apache.lucene - lucene-spatial - ${project.version} - test-jar - test - @lucene-sandbox.internal.dependencies@ @lucene-sandbox.external.dependencies@ @lucene-sandbox.internal.test.dependencies@ diff --git a/lucene/sandbox/build.xml b/lucene/sandbox/build.xml index 4323241e2ed..93bc2754ece 100644 --- a/lucene/sandbox/build.xml +++ b/lucene/sandbox/build.xml @@ -23,39 +23,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java b/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java index 8c909e6b593..39c7a045832 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java +++ b/lucene/sandbox/src/test/org/apache/lucene/search/TestLatLonPointQueries.java @@ -18,7 +18,7 @@ package org.apache.lucene.search; import org.apache.lucene.document.Document; import org.apache.lucene.document.LatLonPoint; -import org.apache.lucene.spatial.util.BaseGeoPointTestCase; +import org.apache.lucene.geo.BaseGeoPointTestCase; import org.apache.lucene.geo.Polygon; import org.apache.lucene.geo.GeoEncodingUtils; diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java index 7e64430be41..e5b766daa64 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java @@ -19,11 +19,11 @@ package org.apache.lucene.spatial.geopoint.search; import org.apache.lucene.document.Document; import org.apache.lucene.search.Query; import org.apache.lucene.spatial.util.GeoEncodingUtils; +import org.apache.lucene.geo.BaseGeoPointTestCase; import org.apache.lucene.geo.Polygon; import org.apache.lucene.geo.Rectangle; import org.apache.lucene.spatial.geopoint.document.GeoPointField; import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding; -import org.apache.lucene.spatial.util.BaseGeoPointTestCase; /** * random testing for GeoPoint query logic diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java index 75cc377d0e5..d0b57fe9d4f 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java @@ -19,11 +19,11 @@ package org.apache.lucene.spatial.geopoint.search; import org.apache.lucene.document.Document; import org.apache.lucene.search.Query; import org.apache.lucene.spatial.util.GeoEncodingUtils; +import org.apache.lucene.geo.BaseGeoPointTestCase; import org.apache.lucene.geo.Polygon; import org.apache.lucene.geo.Rectangle; import org.apache.lucene.spatial.geopoint.document.GeoPointField; import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding; -import org.apache.lucene.spatial.util.BaseGeoPointTestCase; /** * random testing for GeoPoint query logic (with deprecated numeric encoding) diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java similarity index 99% rename from lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java rename to lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java index daf3cbda197..dbdf189f3c4 100644 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java +++ b/lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.lucene.spatial.util; +package org.apache.lucene.geo; import java.io.IOException; import java.text.DecimalFormat; @@ -771,13 +771,16 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase { static final boolean rectContainsPoint(Rectangle rect, double pointLat, double pointLon) { assert Double.isNaN(pointLat) == false; + + if (pointLat < rect.minLat || pointLat > rect.maxLat) { + return false; + } if (rect.minLon <= rect.maxLon) { - return GeoRelationUtils.pointInRectPrecise(pointLat, pointLon, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon); + return pointLon >= rect.minLon && pointLon <= rect.maxLon; } else { // Rect crosses dateline: - return GeoRelationUtils.pointInRectPrecise(pointLat, pointLon, rect.minLat, rect.maxLat, -180.0, rect.maxLon) - || GeoRelationUtils.pointInRectPrecise(pointLat, pointLon, rect.minLat, rect.maxLat, rect.minLon, 180.0); + return pointLon <= rect.maxLon || pointLon >= rect.minLon; } } diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index 2b6c868bb5c..5d81091f59a 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -120,7 +120,12 @@ Bug Fixes (Nicolas Gavalda, Jorge Luis Betancourt Gonzalez via Mark Miller) * SOLR-8946: bin/post failed to detect stdin usage on Ubuntu; maybe other unixes. (David Smiley) - + +* SOLR-8662: SchemaManager waits correctly for replicas to be notified of a new change. + (sarowe, Noble Paul, Varun Thacker) + +* SOLR-9004: Fix "name" field type definition in films example. (Alexandre Rafalovitch via Varun Thacker) + Optimizations ---------------------- * SOLR-8722: Don't force a full ZkStateReader refresh on every Overseer operation. diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkSolrResourceLoader.java b/solr/core/src/java/org/apache/solr/cloud/ZkSolrResourceLoader.java index 4d12db906d4..2b60e53ea70 100644 --- a/solr/core/src/java/org/apache/solr/cloud/ZkSolrResourceLoader.java +++ b/solr/core/src/java/org/apache/solr/cloud/ZkSolrResourceLoader.java @@ -80,17 +80,42 @@ public class ZkSolrResourceLoader extends SolrResourceLoader { */ @Override public InputStream openResource(String resource) throws IOException { - InputStream is = null; + InputStream is; String file = configSetZkPath + "/" + resource; - try { - if (zkController.pathExists(file)) { - Stat stat = new Stat(); - byte[] bytes = zkController.getZkClient().getData(file, null, stat, true); - return new ZkByteArrayInputStream(bytes, stat); + int maxTries = 10; + Exception exception = null; + while (maxTries -- > 0) { + try { + if (zkController.pathExists(file)) { + Stat stat = new Stat(); + byte[] bytes = zkController.getZkClient().getData(file, null, stat, true); + return new ZkByteArrayInputStream(bytes, stat); + } else { + //Path does not exists. We only retry for session expired exceptions. + break; + } + } catch (KeeperException.SessionExpiredException e) { + exception = e; + // Retry in case of session expiry + try { + Thread.sleep(1000); + log.debug("Sleeping for 1s before retrying fetching resource=" + resource); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new IOException("Could not load resource=" + resource, ie); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("Error opening " + file, e); + } catch (KeeperException e) { + throw new IOException("Error opening " + file, e); } - } catch (Exception e) { - throw new IOException("Error opening " + file, e); } + + if (exception != null) { + throw new IOException("We re-tried 10 times but was still unable to fetch resource=" + resource + " from ZK", exception); + } + try { // delegate to the class loader (looking into $INSTANCE_DIR/lib jars) is = classLoader.getResourceAsStream(resource.replace(File.separatorChar, '/')); diff --git a/solr/core/src/java/org/apache/solr/schema/SchemaManager.java b/solr/core/src/java/org/apache/solr/schema/SchemaManager.java index e70b84ffc0f..3b492a75dbe 100644 --- a/solr/core/src/java/org/apache/solr/schema/SchemaManager.java +++ b/solr/core/src/java/org/apache/solr/schema/SchemaManager.java @@ -25,6 +25,7 @@ import org.apache.solr.core.SolrResourceLoader; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.rest.BaseSolrResource; import org.apache.solr.util.CommandOperation; +import org.apache.solr.util.TimeOut; import org.apache.zookeeper.KeeperException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -86,20 +87,27 @@ public class SchemaManager { if (!errs.isEmpty()) return errs; IndexSchema schema = req.getCore().getLatestSchema(); - if (!(schema instanceof ManagedIndexSchema)) { + if (schema instanceof ManagedIndexSchema && schema.isMutable()) { + synchronized (schema.getSchemaUpdateLock()) { + return doOperations(ops); + } + } else { return singletonList(singletonMap(CommandOperation.ERR_MSGS, "schema is not editable")); } - synchronized (schema.getSchemaUpdateLock()) { - return doOperations(ops); - } } private List doOperations(List operations) throws InterruptedException, IOException, KeeperException { - int timeout = req.getParams().getInt(BaseSolrResource.UPDATE_TIMEOUT_SECS, -1); - long startTime = System.nanoTime(); - long endTime = timeout > 0 ? System.nanoTime() + (timeout * 1000 * 1000) : Long.MAX_VALUE; + //The default timeout is 10 minutes when no BaseSolrResource.UPDATE_TIMEOUT_SECS is specified + int timeout = req.getParams().getInt(BaseSolrResource.UPDATE_TIMEOUT_SECS, 600); + + //If BaseSolrResource.UPDATE_TIMEOUT_SECS=0 or -1 then end time then we'll try for 10 mins ( default timeout ) + if (timeout < 1) { + timeout = 600; + } + TimeOut timeOut = new TimeOut(timeout, TimeUnit.SECONDS); SolrCore core = req.getCore(); - while (System.nanoTime() < endTime) { + String errorMsg = "Unable to persist managed schema. "; + while (!timeOut.hasTimedOut()) { managedIndexSchema = getFreshManagedSchema(); for (CommandOperation op : operations) { OpType opType = OpType.get(op.name); @@ -118,25 +126,18 @@ public class SchemaManager { try { managedIndexSchema.persist(sw); } catch (IOException e) { - log.info("race condition "); throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "unable to serialize schema"); //unlikely } try { - ZkController.persistConfigResourceToZooKeeper(zkLoader, - managedIndexSchema.getSchemaZkVersion(), - managedIndexSchema.getResourceName(), - sw.toString().getBytes(StandardCharsets.UTF_8), - true); - waitForOtherReplicasToUpdate(timeout, startTime); + ZkController.persistConfigResourceToZooKeeper(zkLoader, managedIndexSchema.getSchemaZkVersion(), + managedIndexSchema.getResourceName(), sw.toString().getBytes(StandardCharsets.UTF_8), true); + waitForOtherReplicasToUpdate(timeOut); + core.setLatestSchema(managedIndexSchema); return Collections.emptyList(); } catch (ZkController.ResourceModifiedInZkException e) { - log.info("Race condition schema modified by another node"); - } catch (Exception e) { - String s = "Exception persisting schema"; - log.warn(s, e); - return singletonList(s + e.getMessage()); + log.info("Schema was modified by another node. Retrying.."); } } else { try { @@ -144,36 +145,30 @@ public class SchemaManager { managedIndexSchema.persistManagedSchema(false); core.setLatestSchema(managedIndexSchema); return Collections.emptyList(); - } catch (ManagedIndexSchema.SchemaChangedInZkException e) { - String s = "Failed to update schema because schema is modified"; - log.warn(s, e); - } catch (Exception e) { - String s = "Exception persisting schema"; - log.warn(s, e); - return singletonList(s + e.getMessage()); + } catch (SolrException e) { + log.warn(errorMsg); + return singletonList(errorMsg + e.getMessage()); } } } - return singletonList("Unable to persist schema"); + log.warn(errorMsg + "Timed out."); + return singletonList(errorMsg + "Timed out."); } - private void waitForOtherReplicasToUpdate(int timeout, long startTime) { - if (timeout > 0 && managedIndexSchema.getResourceLoader() instanceof ZkSolrResourceLoader) { - CoreDescriptor cd = req.getCore().getCoreDescriptor(); - String collection = cd.getCollectionName(); - if (collection != null) { - ZkSolrResourceLoader zkLoader = (ZkSolrResourceLoader) managedIndexSchema.getResourceLoader(); - long timeLeftSecs = timeout - TimeUnit.SECONDS.convert(System.nanoTime() - startTime, TimeUnit.NANOSECONDS); - if (timeLeftSecs <= 0) { - throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, - "Not enough time left to update replicas. However, the schema is updated already."); - } - ManagedIndexSchema.waitForSchemaZkVersionAgreement(collection, - cd.getCloudDescriptor().getCoreNodeName(), - (managedIndexSchema).getSchemaZkVersion(), - zkLoader.getZkController(), - (int) timeLeftSecs); + private void waitForOtherReplicasToUpdate(TimeOut timeOut) { + CoreDescriptor cd = req.getCore().getCoreDescriptor(); + String collection = cd.getCollectionName(); + if (collection != null) { + ZkSolrResourceLoader zkLoader = (ZkSolrResourceLoader) managedIndexSchema.getResourceLoader(); + if (timeOut.hasTimedOut()) { + throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, + "Not enough time left to update replicas. However, the schema is updated already."); } + ManagedIndexSchema.waitForSchemaZkVersionAgreement(collection, + cd.getCloudDescriptor().getCoreNodeName(), + (managedIndexSchema).getSchemaZkVersion(), + zkLoader.getZkController(), + (int) timeOut.timeLeft(TimeUnit.SECONDS)); } } @@ -198,7 +193,7 @@ public class SchemaManager { @Override public boolean perform(CommandOperation op, SchemaManager mgr) { String src = op.getStr(SOURCE); List dests = op.getStrs(DESTINATION); - + int maxChars = CopyField.UNLIMITED; // If maxChars is not specified, there is no limit on copied chars String maxCharsStr = op.getStr(MAX_CHARS, null); if (null != maxCharsStr) { @@ -241,7 +236,7 @@ public class SchemaManager { } try { SchemaField field = SchemaField.create(name, ft, op.getValuesExcluding(NAME, TYPE)); - mgr.managedIndexSchema + mgr.managedIndexSchema = mgr.managedIndexSchema.addFields(singletonList(field), Collections.emptyMap(), false); return true; } catch (Exception e) { @@ -262,8 +257,8 @@ public class SchemaManager { return false; } try { - SchemaField field = SchemaField.create(name, ft, op.getValuesExcluding(NAME, TYPE)); - mgr.managedIndexSchema + SchemaField field = SchemaField.create(name, ft, op.getValuesExcluding(NAME, TYPE)); + mgr.managedIndexSchema = mgr.managedIndexSchema.addDynamicFields(singletonList(field), Collections.emptyMap(), false); return true; } catch (Exception e) { @@ -297,7 +292,7 @@ public class SchemaManager { if (op.hasError()) return false; if ( ! op.getValuesExcluding(SOURCE, DESTINATION).isEmpty()) { - op.addError("Only the '" + SOURCE + "' and '" + DESTINATION + op.addError("Only the '" + SOURCE + "' and '" + DESTINATION + "' params are allowed with the 'delete-copy-field' operation"); return false; } @@ -318,14 +313,14 @@ public class SchemaManager { if ( ! op.getValuesExcluding(NAME).isEmpty()) { op.addError("Only the '" + NAME + "' param is allowed with the 'delete-field' operation"); return false; - } + } try { mgr.managedIndexSchema = mgr.managedIndexSchema.deleteFields(singleton(name)); return true; } catch (Exception e) { op.addError(getErrorStr(e)); return false; - } + } } }, DELETE_DYNAMIC_FIELD("delete-dynamic-field") { @@ -436,7 +431,7 @@ public class SchemaManager { int version = ((ZkSolrResourceLoader.ZkByteArrayInputStream) in).getStat().getVersion(); log.info("managed schema loaded . version : {} ", version); return new ManagedIndexSchema - (req.getCore().getSolrConfig(), req.getSchema().getResourceName(), new InputSource(in), + (req.getCore().getSolrConfig(), req.getSchema().getResourceName(), new InputSource(in), true, req.getSchema().getResourceName(), version, req.getSchema().getSchemaUpdateLock()); } else { return (ManagedIndexSchema) req.getCore().getLatestSchema(); diff --git a/solr/core/src/test-files/solr/configsets/configset-1/conf/schema-minimal.xml b/solr/core/src/test-files/solr/configsets/cloud-managed/conf/managed-schema similarity index 65% rename from solr/core/src/test-files/solr/configsets/configset-1/conf/schema-minimal.xml rename to solr/core/src/test-files/solr/configsets/cloud-managed/conf/managed-schema index 9e2f9471026..fd7be8308f9 100644 --- a/solr/core/src/test-files/solr/configsets/configset-1/conf/schema-minimal.xml +++ b/solr/core/src/test-files/solr/configsets/cloud-managed/conf/managed-schema @@ -18,8 +18,14 @@ + + - + + + + + id diff --git a/solr/core/src/test-files/solr/configsets/configset-1/conf/solrconfig-minimal.xml b/solr/core/src/test-files/solr/configsets/cloud-managed/conf/solrconfig.xml similarity index 72% rename from solr/core/src/test-files/solr/configsets/configset-1/conf/solrconfig-minimal.xml rename to solr/core/src/test-files/solr/configsets/cloud-managed/conf/solrconfig.xml index a6fe5bae8fa..aabfa2f9b66 100644 --- a/solr/core/src/test-files/solr/configsets/configset-1/conf/solrconfig-minimal.xml +++ b/solr/core/src/test-files/solr/configsets/cloud-managed/conf/solrconfig.xml @@ -17,16 +17,7 @@ limitations under the License. --> - + @@ -34,7 +25,11 @@ - + + + ${managed.schema.mutable} + managed-schema + ${tests.luceneMatchVersion:LATEST} @@ -42,8 +37,9 @@ ${solr.commitwithin.softcommit:true} - + + explicit @@ -53,4 +49,3 @@ - diff --git a/solr/core/src/test/org/apache/solr/schema/TestManagedSchemaAPI.java b/solr/core/src/test/org/apache/solr/schema/TestManagedSchemaAPI.java new file mode 100644 index 00000000000..3bd4dea938b --- /dev/null +++ b/solr/core/src/test/org/apache/solr/schema/TestManagedSchemaAPI.java @@ -0,0 +1,101 @@ +package org.apache.solr.schema; + +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.solr.client.solrj.SolrServerException; +import org.apache.solr.client.solrj.impl.CloudSolrClient; +import org.apache.solr.client.solrj.request.CollectionAdminRequest; +import org.apache.solr.client.solrj.request.UpdateRequest; +import org.apache.solr.client.solrj.request.schema.SchemaRequest; +import org.apache.solr.client.solrj.response.CollectionAdminResponse; +import org.apache.solr.client.solrj.response.schema.SchemaResponse; +import org.apache.solr.cloud.SolrCloudTestCase; +import org.apache.solr.common.SolrInputDocument; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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. + */ + +public class TestManagedSchemaAPI extends SolrCloudTestCase { + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + @BeforeClass + public static void createCluster() throws Exception { + System.setProperty("managed.schema.mutable", "true"); + configureCluster(2) + .addConfig("conf1", TEST_PATH().resolve("configsets").resolve("cloud-managed").resolve("conf")) + .configure(); + } + + @Test + public void test() throws Exception { + String collection = "testschemaapi"; + cluster.createCollection(collection, 1, 2, "conf1", null); + testReloadAndAddSimple(collection); + testAddFieldAndDocument(collection); + } + + private void testReloadAndAddSimple(String collection) throws IOException, SolrServerException { + CloudSolrClient cloudClient = cluster.getSolrClient(); + + String fieldName = "myNewField"; + addStringField(fieldName, collection, cloudClient); + + CollectionAdminRequest.Reload reloadRequest = CollectionAdminRequest.reloadCollection(collection); + CollectionAdminResponse response = reloadRequest.process(cloudClient); + assertEquals(0, response.getStatus()); + assertTrue(response.isSuccess()); + + SolrInputDocument doc = new SolrInputDocument(); + doc.addField("id", "1"); + doc.addField(fieldName, "val"); + UpdateRequest ureq = new UpdateRequest().add(doc); + cloudClient.request(ureq, collection); + } + + private void testAddFieldAndDocument(String collection) throws IOException, SolrServerException { + CloudSolrClient cloudClient = cluster.getSolrClient(); + + String fieldName = "myNewField1"; + addStringField(fieldName, collection, cloudClient); + + SolrInputDocument doc = new SolrInputDocument(); + doc.addField("id", "2"); + doc.addField(fieldName, "val1"); + UpdateRequest ureq = new UpdateRequest().add(doc); + cloudClient.request(ureq, collection);; + } + + private void addStringField(String fieldName, String collection, CloudSolrClient cloudClient) throws IOException, SolrServerException { + Map fieldAttributes = new LinkedHashMap<>(); + fieldAttributes.put("name", fieldName); + fieldAttributes.put("type", "string"); + SchemaRequest.AddField addFieldUpdateSchemaRequest = new SchemaRequest.AddField(fieldAttributes); + SchemaResponse.UpdateResponse addFieldResponse = addFieldUpdateSchemaRequest.process(cloudClient, collection); + assertEquals(0, addFieldResponse.getStatus()); + assertNull(addFieldResponse.getResponse().get("errors")); + + log.info("added new field="+fieldName); + } + +} diff --git a/solr/example/films/README.txt b/solr/example/films/README.txt index 74a29a1ace0..f1fabe05a56 100644 --- a/solr/example/films/README.txt +++ b/solr/example/films/README.txt @@ -22,6 +22,7 @@ curl http://localhost:8983/solr/films/schema -X POST -H 'Content-type:applicatio "add-field" : { "name":"name", "type":"text_general", + "multiValued":false, "stored":true }, "add-field" : { @@ -103,6 +104,7 @@ curl http://localhost:8983/solr/films/schema -X POST -H 'Content-type:applicatio "add-field" : { "name":"name", "type":"text_general", + "multiValued":false, "stored":true }, "add-field" : {