From bbcf4a77e1441700e672e9b8ede55bcbd5dac912 Mon Sep 17 00:00:00 2001 From: bellengao Date: Sun, 10 May 2020 16:02:17 +0800 Subject: [PATCH] add copy ingest processor --- docs/reference/ingest/ingest-node.asciidoc | 1 + .../reference/ingest/processors/copy.asciidoc | 82 ++++++++ .../ingest/common/CopyProcessor.java | 132 ++++++++++++ .../ingest/common/IngestCommonPlugin.java | 3 +- .../common/CopyProcessorFactoryTests.java | 102 ++++++++++ .../ingest/common/CopyProcessorTests.java | 190 ++++++++++++++++++ .../rest-api-spec/test/ingest/260_copy.yml | 73 +++++++ .../elasticsearch/ingest/IngestDocument.java | 2 +- 8 files changed, 583 insertions(+), 2 deletions(-) create mode 100644 docs/reference/ingest/processors/copy.asciidoc create mode 100644 modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/CopyProcessor.java create mode 100644 modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/CopyProcessorFactoryTests.java create mode 100644 modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/CopyProcessorTests.java create mode 100644 modules/ingest-common/src/test/resources/rest-api-spec/test/ingest/260_copy.yml diff --git a/docs/reference/ingest/ingest-node.asciidoc b/docs/reference/ingest/ingest-node.asciidoc index 878a0835059e3..a68c65587acef 100644 --- a/docs/reference/ingest/ingest-node.asciidoc +++ b/docs/reference/ingest/ingest-node.asciidoc @@ -824,6 +824,7 @@ include::processors/append.asciidoc[] include::processors/bytes.asciidoc[] include::processors/circle.asciidoc[] include::processors/convert.asciidoc[] +include::processors/copy.asciidoc[] include::processors/csv.asciidoc[] include::processors/date.asciidoc[] include::processors/date-index-name.asciidoc[] diff --git a/docs/reference/ingest/processors/copy.asciidoc b/docs/reference/ingest/processors/copy.asciidoc new file mode 100644 index 0000000000000..60bb6ba0bf558 --- /dev/null +++ b/docs/reference/ingest/processors/copy.asciidoc @@ -0,0 +1,82 @@ +[[copy-processor]] +=== COPY Processor +Copy the value of an existed field to other field or the root of the document. + +[[copy-options]] +.Copy Options +[options="header"] +|====== +| Name | Required | Default | Description +| `field` | yes | - | The field copied from +| `target_field` | no | - | The field copied to, `add_to_root` cannot be set to `true` when this field is set +| `add_to_root` | no | `false` | Flag that copy the value of `field` to the top level of the document. `target_field` must not be set when this option is chosen. +| `ignore_missing` | no | `false` | If `true` and `field` does not exist or is `null`, the processor quietly exits without modifying the document +include::common-options.asciidoc[] +|====== + +The supported types which can be copied are `boolean`, `number`, `array`, `object`, `string`, `date`. + +We can copy the sub-field of an object to another field: + +[source,js] +-------------------------------------------------- +{ + "copy" : { + "field" : "foo.bar", + "target_field" : "zoo" + } +} +-------------------------------------------------- +// NOTCONSOLE + +If the following document is processed: + +[source,js] +-------------------------------------------------- +{ + "foo": { + "bar": 1 + } +} +-------------------------------------------------- +// NOTCONSOLE + +after the `copy` processor operates on it, it will look like: + +[source,js] +-------------------------------------------------- +{ + "foo": { + "bar": 1 + }, + "zoo": 1 +} +-------------------------------------------------- +// NOTCONSOLE + +We can set `add_to_root` to `true` if we want to copy one field to the top level of the document: +[source,js] +-------------------------------------------------- +{ + "copy" : { + "field" : "foo", + "add_to_root": true + } +} +-------------------------------------------------- +// NOTCONSOLE + +then after the `copy` processor operates on the document above, it will look like: + +[source,js] +-------------------------------------------------- +{ + "foo": { + "bar": 1 + }, + "bar": 1 +} +-------------------------------------------------- +// NOTCONSOLE + +Note that we can only copy an `object` field to the root of the document. diff --git a/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/CopyProcessor.java b/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/CopyProcessor.java new file mode 100644 index 0000000000000..ae06b633e88f4 --- /dev/null +++ b/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/CopyProcessor.java @@ -0,0 +1,132 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch 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. + */ + +package org.elasticsearch.ingest.common; + +import org.elasticsearch.ingest.AbstractProcessor; +import org.elasticsearch.ingest.ConfigurationUtils; +import org.elasticsearch.ingest.IngestDocument; +import org.elasticsearch.ingest.Processor; + +import java.util.Map; + +import static org.elasticsearch.ingest.ConfigurationUtils.newConfigurationException; + +/** + * Processor that adds a new field with value copied from other existed field. + */ +public class CopyProcessor extends AbstractProcessor { + + public static final String TYPE = "copy"; + + private final String field; + private final String targetField; + private final boolean addToRoot; + private final boolean ignoreMissing; + + CopyProcessor(String tag, String field, String targetField, boolean addToRoot, boolean ignoreMissing) { + super(tag); + this.field = field; + this.targetField = targetField; + this.addToRoot = addToRoot; + this.ignoreMissing = ignoreMissing; + } + + public String getField() { + return field; + } + + public String getTargetField() { + return targetField; + } + + public boolean isAddToRoot() { + return addToRoot; + } + + public boolean isIgnoreMissing() { + return ignoreMissing; + } + + private static void apply(Map ctx, Object value) { + if (value instanceof Map) { + @SuppressWarnings("unchecked") + Map map = (Map) value; + ctx.putAll(map); + } else { + throw new IllegalArgumentException("cannot add non-map fields to root of document"); + } + } + + @Override + public IngestDocument execute(IngestDocument document) { + Object fieldValue = document.getFieldValue(field, Object.class, ignoreMissing); + + if (fieldValue == null && ignoreMissing) { + return document; + } else if (fieldValue == null) { + throw new IllegalArgumentException("field [" + field + "] is null, cannot be copied"); + } + + if (field.equals(targetField)) { + return document; + } + + if (addToRoot) { + apply(document.getSourceAndMetadata(), fieldValue); + } else { + document.setFieldValue(targetField, IngestDocument.deepCopy(fieldValue)); + } + + return document; + } + + @Override + public String getType() { + return TYPE; + } + + public static final class Factory implements Processor.Factory { + @Override + public CopyProcessor create( + Map registry, String processorTag, + Map config) throws Exception { + String field = ConfigurationUtils.readStringProperty(TYPE, processorTag, config, "field"); + String targetField = ConfigurationUtils.readOptionalStringProperty(TYPE, processorTag, config, "target_field"); + boolean ignoreMissing = ConfigurationUtils.readBooleanProperty(TYPE, processorTag, config, "ignore_missing", false); + boolean addToRoot = ConfigurationUtils.readBooleanProperty(TYPE, processorTag, config, "add_to_root", false); + + if (addToRoot == false && targetField == null) { + throw newConfigurationException(TYPE, processorTag, "target_field", + "either `target_field` or `add_to_root` must be set"); + } + if (addToRoot && targetField != null) { + throw newConfigurationException(TYPE, processorTag, "target_field", + "cannot set `target_field` while also setting `add_to_root` to true"); + } + + return new CopyProcessor( + processorTag, + field, + targetField, + addToRoot, + ignoreMissing); + } + } +} diff --git a/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/IngestCommonPlugin.java b/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/IngestCommonPlugin.java index d4293c50e14a0..9326d23a16cb6 100644 --- a/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/IngestCommonPlugin.java +++ b/modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/IngestCommonPlugin.java @@ -89,7 +89,8 @@ public Map getProcessors(Processor.Parameters paramet entry(DissectProcessor.TYPE, new DissectProcessor.Factory()), entry(DropProcessor.TYPE, new DropProcessor.Factory()), entry(HtmlStripProcessor.TYPE, new HtmlStripProcessor.Factory()), - entry(CsvProcessor.TYPE, new CsvProcessor.Factory())); + entry(CsvProcessor.TYPE, new CsvProcessor.Factory()), + entry(CopyProcessor.TYPE, new CopyProcessor.Factory())); } @Override diff --git a/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/CopyProcessorFactoryTests.java b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/CopyProcessorFactoryTests.java new file mode 100644 index 0000000000000..2b7be29f500a2 --- /dev/null +++ b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/CopyProcessorFactoryTests.java @@ -0,0 +1,102 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch 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. + */ + +package org.elasticsearch.ingest.common; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.test.ESTestCase; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.equalTo; + +public class CopyProcessorFactoryTests extends ESTestCase { + + private static final CopyProcessor.Factory FACTORY = new CopyProcessor.Factory(); + + public void testCreate() throws Exception { + String processorTag = randomAlphaOfLength(10); + String randomField = randomAlphaOfLength(10); + String randomTargetField = randomAlphaOfLength(5); + Map config = new HashMap<>(); + config.put("field", randomField); + config.put("target_field", randomTargetField); + CopyProcessor copyProcessor = FACTORY.create(null, processorTag, config); + assertThat(copyProcessor.getTag(), equalTo(processorTag)); + assertThat(copyProcessor.getField(), equalTo(randomField)); + assertThat(copyProcessor.getTargetField(), equalTo(randomTargetField)); + } + + public void testCreateWithAddToRoot() throws Exception { + String processorTag = randomAlphaOfLength(10); + String randomField = randomAlphaOfLength(10); + Map config = new HashMap<>(); + config.put("field", randomField); + config.put("add_to_root", true); + CopyProcessor copyProcessor = FACTORY.create(null, processorTag, config); + assertThat(copyProcessor.getTag(), equalTo(processorTag)); + assertThat(copyProcessor.getField(), equalTo(randomField)); + assertTrue(copyProcessor.isAddToRoot()); + } + + public void testCreateWithMissingField() throws Exception { + Map config = new HashMap<>(); + String processorTag = randomAlphaOfLength(10); + ElasticsearchException exception = expectThrows( + ElasticsearchParseException.class, + () -> FACTORY.create(null, processorTag, config)); + assertThat(exception.getMessage(), equalTo("[field] required property is missing")); + } + + public void testCreateWithIgnoreMissing() throws Exception { + Map config = new HashMap<>(); + config.put("field", "old_field"); + config.put("target_field", "new_field"); + config.put("ignore_missing", true); + String processorTag = randomAlphaOfLength(10); + CopyProcessor copyProcessor = FACTORY.create(null, processorTag, config); + assertThat(copyProcessor.getTag(), equalTo(processorTag)); + assertThat(copyProcessor.getField(), equalTo("old_field")); + assertThat(copyProcessor.getTargetField(), equalTo("new_field")); + assertThat(copyProcessor.isIgnoreMissing(), equalTo(true)); + } + + public void testCreateWithoutTargetFieldAndAddToRoot() throws Exception { + String randomField = randomAlphaOfLength(10); + Map config = new HashMap<>(); + config.put("field", randomField); + ElasticsearchException exception = expectThrows(ElasticsearchParseException.class, + () -> FACTORY.create(null, randomAlphaOfLength(10), config)); + assertThat(exception.getMessage(), equalTo("[target_field] either `target_field` or `add_to_root` must be set")); + } + + public void testCreateWithBothTargetFieldAndAddToRoot() throws Exception { + String randomField = randomAlphaOfLength(10); + String randomTargetField = randomAlphaOfLength(5); + Map config = new HashMap<>(); + config.put("field", randomField); + config.put("target_field", randomTargetField); + config.put("add_to_root", true); + ElasticsearchException exception = expectThrows(ElasticsearchParseException.class, + () -> FACTORY.create(null, randomAlphaOfLength(10), config)); + assertThat(exception.getMessage(), equalTo("[target_field] cannot set `target_field` while also setting `add_to_root` to true")); + } +} diff --git a/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/CopyProcessorTests.java b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/CopyProcessorTests.java new file mode 100644 index 0000000000000..3ac7f99be641c --- /dev/null +++ b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/CopyProcessorTests.java @@ -0,0 +1,190 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch 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. + */ + +package org.elasticsearch.ingest.common; + +import org.elasticsearch.ingest.IngestDocument; +import org.elasticsearch.ingest.RandomDocumentPicks; +import org.elasticsearch.test.ESTestCase; + +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; + +public class CopyProcessorTests extends ESTestCase { + + public void testCopyMap() throws Exception { + CopyProcessor copyProcessor = new CopyProcessor("tag", "field", "target_field", false, false); + Map document = new HashMap<>(); + Map fieldValue = new HashMap<>(); + Map subFieldValue = new HashMap<>(); + subFieldValue.put("field2", 1); + fieldValue.put("field1", subFieldValue); + document.put("field", fieldValue); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + copyProcessor.execute(ingestDocument); + assertThat(ingestDocument.hasField("target_field"), equalTo(true)); + assertThat(ingestDocument.getFieldValue("target_field", Object.class), equalTo(fieldValue)); + } + + public void testCopyArray() throws Exception { + CopyProcessor copyProcessor = new CopyProcessor("tag", "field", "target_field", false, false); + Map document = new HashMap<>(); + List fieldValue = Arrays.asList(true, true, false); + document.put("field", fieldValue); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + copyProcessor.execute(ingestDocument); + assertThat(ingestDocument.hasField("target_field"), equalTo(true)); + assertThat(ingestDocument.getFieldValue("target_field", Object.class), equalTo(fieldValue)); + } + + public void testCopyByteArray() throws Exception { + CopyProcessor copyProcessor = new CopyProcessor("tag", "field", "target_field", false, false); + Map document = new HashMap<>(); + byte[] fieldValue = new byte[] { 0, 1 }; + document.put("field", fieldValue); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + copyProcessor.execute(ingestDocument); + assertThat(ingestDocument.hasField("target_field"), equalTo(true)); + assertThat(ingestDocument.getFieldValue("target_field", Object.class), equalTo(fieldValue)); + } + + public void testCopyString() throws Exception { + CopyProcessor copyProcessor = new CopyProcessor("tag", "field", "target_field", false, false); + Map document = new HashMap<>(); + String fieldValue = "bar"; + document.put("field", fieldValue); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + copyProcessor.execute(ingestDocument); + assertThat(ingestDocument.hasField("target_field"), equalTo(true)); + assertThat(ingestDocument.getFieldValue("target_field", Object.class), equalTo(fieldValue)); + } + + public void testCopyInteger() throws Exception { + CopyProcessor copyProcessor = new CopyProcessor("tag", "field", "target_field", false, false); + Map document = new HashMap<>(); + int fieldValue = 1; + document.put("field", fieldValue); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + copyProcessor.execute(ingestDocument); + assertThat(ingestDocument.hasField("target_field"), equalTo(true)); + assertThat(ingestDocument.getFieldValue("target_field", Object.class), equalTo(fieldValue)); + } + + public void testCopyDouble() throws Exception { + CopyProcessor copyProcessor = new CopyProcessor("tag", "field", "target_field", false, false); + Map document = new HashMap<>(); + double fieldValue = 1.0; + document.put("field", fieldValue); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + copyProcessor.execute(ingestDocument); + assertThat(ingestDocument.hasField("target_field"), equalTo(true)); + assertThat(ingestDocument.getFieldValue("target_field", Object.class), equalTo(fieldValue)); + } + + public void testCopyBoolean() throws Exception { + CopyProcessor copyProcessor = new CopyProcessor("tag", "field", "target_field", false, false); + Map document = new HashMap<>(); + boolean fieldValue = true; + document.put("field", fieldValue); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + copyProcessor.execute(ingestDocument); + assertThat(ingestDocument.hasField("target_field"), equalTo(true)); + assertThat(ingestDocument.getFieldValue("target_field", Object.class), equalTo(fieldValue)); + } + + public void testCopyZonedDateTime() throws Exception { + CopyProcessor copyProcessor = new CopyProcessor("tag", "field", "target_field", false, false); + Map document = new HashMap<>(); + ZonedDateTime fieldValue = ZonedDateTime.now(); + document.put("field", fieldValue); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + copyProcessor.execute(ingestDocument); + assertThat(ingestDocument.hasField("target_field"), equalTo(true)); + assertThat(ingestDocument.getFieldValue("target_field", Object.class), equalTo(fieldValue)); + } + + public void testCopyDate() throws Exception { + CopyProcessor copyProcessor = new CopyProcessor("tag", "field", "target_field", false, false); + Map document = new HashMap<>(); + Date fieldValue = new Date(); + document.put("field", fieldValue); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + copyProcessor.execute(ingestDocument); + assertThat(ingestDocument.hasField("target_field"), equalTo(true)); + assertThat(ingestDocument.getFieldValue("target_field", Object.class), equalTo(fieldValue)); + } + + public void testCopyNull() throws Exception { + CopyProcessor copyProcessor = new CopyProcessor("tag", "field", "target_field", false, false); + Map document = new HashMap<>(); + Object fieldValue = null; + document.put("field", fieldValue); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + Exception exception = expectThrows(IllegalArgumentException.class, () -> copyProcessor.execute(ingestDocument)); + assertThat(exception.getMessage(), equalTo("field [field] is null, cannot be copied")); + } + + public void testFieldMissing() throws Exception { + CopyProcessor copyProcessor = new CopyProcessor("tag", "field", "target_field", false, false); + Map document = new HashMap<>(); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + Exception exception = expectThrows(IllegalArgumentException.class, () -> copyProcessor.execute(ingestDocument)); + assertThat(exception.getMessage(), equalTo("field [field] not present as part of path [field]")); + } + + public void testCopyWithIgnoreMissing() throws Exception { + CopyProcessor copyProcessor = new CopyProcessor("tag", "field", "target_field", false, true); + Map document = new HashMap<>(); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + copyProcessor.execute(ingestDocument); + assertThat(ingestDocument.hasField("target_field"), equalTo(false)); + } + + public void testAddToRoot() throws Exception { + CopyProcessor copyProcessor = new CopyProcessor("tag", "field", "", true, false); + Map document = new HashMap<>(); + Map fieldValue = new HashMap<>(); + Map subFieldValue = new HashMap<>(); + subFieldValue.put("field2", 1); + fieldValue.put("field1", subFieldValue); + document.put("field", fieldValue); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + copyProcessor.execute(ingestDocument); + + Map sourceAndMetadata = ingestDocument.getSourceAndMetadata(); + assertThat(sourceAndMetadata.get("field1"), equalTo(subFieldValue)); + } + + public void testAddNonMapToRoot() throws Exception { + CopyProcessor copyProcessor = new CopyProcessor("tag", "field", "", true, false); + Map document = new HashMap<>(); + document.put("field", true); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + + Exception exception = expectThrows(IllegalArgumentException.class, () -> copyProcessor.execute(ingestDocument)); + assertThat(exception.getMessage(), containsString("cannot add non-map fields to root of document")); + } +} diff --git a/modules/ingest-common/src/test/resources/rest-api-spec/test/ingest/260_copy.yml b/modules/ingest-common/src/test/resources/rest-api-spec/test/ingest/260_copy.yml new file mode 100644 index 0000000000000..5b1fdc04420ca --- /dev/null +++ b/modules/ingest-common/src/test/resources/rest-api-spec/test/ingest/260_copy.yml @@ -0,0 +1,73 @@ +--- +teardown: + - do: + ingest.delete_pipeline: + id: "1" + ignore: 404 + +--- +"Test COPY Processor": + - do: + ingest.put_pipeline: + id: "1" + body: > + { + "processors": [ + { + "copy" : { + "field" : "foo_object", + "target_field": "copied_foo_object" + } + }, + { + "copy" : { + "field" : "foo_array", + "target_field": "copied_foo_array" + } + }, + { + "copy" : { + "field" : "foo_string", + "target_field": "copied_foo_string" + } + }, + { + "copy" : { + "field" : "foo_number", + "target_field": "copied_foo_number" + } + }, + { + "copy" : { + "field" : "foo_boolean", + "target_field": "copied_foo_boolean" + } + } + ] + } + - match: { acknowledged: true } + + - do: + index: + index: test + id: 1 + pipeline: "1" + body: { + foo_object: { + "hello": "world" + }, + foo_array: [1, 2, 3], + foo_string: "bla bla", + foo_number: 3, + foo_boolean: true + } + + - do: + get: + index: test + id: 1 + - match: { _source.copied_foo_object.hello: "world" } + - match: { _source.copied_foo_array.0: 1 } + - match: { _source.copied_foo_string: "bla bla" } + - match: { _source.copied_foo_number: 3 } + - is_true: _source.foo_boolean diff --git a/server/src/main/java/org/elasticsearch/ingest/IngestDocument.java b/server/src/main/java/org/elasticsearch/ingest/IngestDocument.java index d57264dc88d2e..a91dc5448fd86 100644 --- a/server/src/main/java/org/elasticsearch/ingest/IngestDocument.java +++ b/server/src/main/java/org/elasticsearch/ingest/IngestDocument.java @@ -607,7 +607,7 @@ public static Map deepCopyMap(Map source) { return (Map) deepCopy(source); } - private static Object deepCopy(Object value) { + public static Object deepCopy(Object value) { if (value instanceof Map) { Map mapValue = (Map) value; Map copy = new HashMap<>(mapValue.size());