From f5e341fa4c2baea0ae4e7f15da0d903fdd070d24 Mon Sep 17 00:00:00 2001 From: Xun Zhang Date: Wed, 18 Mar 2026 13:50:06 -0700 Subject: [PATCH 1/3] add UUID as new entry value in the add_entries processor Signed-off-by: Xun Zhang --- .../mutateevent/AddEntryProcessor.java | 7 +- .../mutateevent/AddEntryProcessorConfig.java | 58 ++++++++++++- .../mutateevent/AddEntryProcessorTests.java | 83 +++++++++++++++++++ 3 files changed, 144 insertions(+), 4 deletions(-) diff --git a/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessor.java b/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessor.java index 0908e072d2..ca63fcb5a6 100644 --- a/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessor.java +++ b/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessor.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.UUID; import java.util.function.Consumer; import java.util.function.Supplier; @@ -313,8 +314,10 @@ private Object retrieveValue(final AddEntryProcessorConfig.Entry entry, final Ev int entryIndex = entries.indexOf(entry); EntryProperties props = entryProperties.get(entryIndex); KeyInfo keyInfo = preprocessedKeys.get(entryIndex); - - if (!Objects.isNull(entry.getValueExpression())) { + + if (entry.getGenerateUuid()) { + return UUID.randomUUID().toString(); + } else if (!Objects.isNull(entry.getValueExpression())) { value = expressionEvaluator.evaluate(entry.getValueExpression(), context); } else if (!Objects.isNull(entry.getFormat())) { try { diff --git a/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorConfig.java b/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorConfig.java index 8456ff90eb..7b998db1e3 100644 --- a/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorConfig.java +++ b/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorConfig.java @@ -166,6 +166,12 @@ public static class Entry { }) private boolean flattenKey = true; + @JsonProperty("generate_uuid") + @JsonPropertyDescription("When set to true, generates a random UUID (version 4) as the value of the new entry. " + + "Each event receives its own unique UUID, providing globally unique identifiers across distributed deployments " + + "without any coordination between nodes. Cannot be used together with value, format, or value_expression.") + private boolean generateUuid = false; + @JsonProperty("add_when") @JsonPropertyDescription("A conditional expression, " + "such as /some-key == \"test\", that will be evaluated to determine whether the processor will be run on the event.") @@ -212,11 +218,19 @@ public boolean getFlattenKey(){ return flattenKey; } + public boolean getGenerateUuid() { + return generateUuid; + } + public String getAddWhen() { return addWhen; } - @AssertTrue(message = "Either value or format or expression must be specified, and only one of them can be specified") + @AssertTrue(message = "Exactly one of value, format, value_expression, or generate_uuid must be specified") public boolean hasValueOrFormatOrExpression() { - return Stream.of(value, format, valueExpression).filter(n -> n!=null).count() == 1; + final long count = Stream.of(value, format, valueExpression).filter(n -> n != null).count(); + if (generateUuid) { + return count == 0; + } + return count == 1; } @AssertTrue(message = "overwrite_if_key_exists and append_if_key_exists can not be set to true at the same time.") @@ -229,6 +243,46 @@ boolean flattenKeyFalseIsUsedWithIterateOn() { return (!flattenKey && iterateOn!=null) || flattenKey; } + public Entry(final String key, + final String metadataKey, + final Object value, + final String format, + final String valueExpression, + final boolean overwriteIfKeyExists, + final boolean appendIfKeyExists, + final String addWhen, + final String iterateOn, + final boolean flattenKey, + final String addToElementWhen, + final boolean generateUuid) + { + if (key != null && metadataKey != null) { + throw new IllegalArgumentException("Only one of the two - key and metadatakey - should be specified"); + } + if (key == null && metadataKey == null) { + throw new IllegalArgumentException("At least one of the two - key and metadatakey - must be specified"); + } + if (metadataKey != null && iterateOn != null) { + throw new IllegalArgumentException("iterate_on cannot be applied to metadata"); + } + if (iterateOn == null && addToElementWhen != null) { + throw new InvalidPluginConfigurationException("add_to_element_when only applies when iterate_on is configured."); + } + + this.key = key; + this.metadataKey = metadataKey; + this.value = value; + this.format = format; + this.valueExpression = valueExpression; + this.overwriteIfKeyExists = overwriteIfKeyExists; + this.appendIfKeyExists = appendIfKeyExists; + this.addWhen = addWhen; + this.iterateOn = iterateOn; + this.flattenKey = flattenKey; + this.addToElementWhen = addToElementWhen; + this.generateUuid = generateUuid; + } + public Entry(final String key, final String metadataKey, final Object value, diff --git a/data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java b/data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java index 07d29bf8b0..222acd3b9e 100644 --- a/data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java +++ b/data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java @@ -1088,6 +1088,81 @@ public void testAddFlattenedNestedEntryIterateOn() { equalTo(List.of(Map.of("key", 5, "nested/newMessage", 3)))); // [{"key": 5, "nested/newMessage": 3}}] } + @Test + void test_generate_uuid_adds_uuid_string_to_event() { + when(mockConfig.getEntries()).thenReturn(createListOfEntries( + createEntryWithGenerateUuid("recordId", false, null))); + + final AddEntryProcessor processor = createObjectUnderTest(); + final Record record = getEvent("test-message"); + final List> result = (List>) processor.doExecute(Collections.singletonList(record)); + + final Event event = result.get(0).getData(); + assertThat(event.containsKey("recordId"), is(true)); + final String uuid = event.get("recordId", String.class); + assertThat(uuid, equalTo(UUID.fromString(uuid).toString())); + } + + @Test + void test_generate_uuid_produces_unique_values_per_event() { + when(mockConfig.getEntries()).thenReturn(createListOfEntries( + createEntryWithGenerateUuid("recordId", false, null))); + + final AddEntryProcessor processor = createObjectUnderTest(); + final Record record1 = getEvent("message-one"); + final Record record2 = getEvent("message-two"); + final List> result = (List>) processor.doExecute(Arrays.asList(record1, record2)); + + final String uuid1 = result.get(0).getData().get("recordId", String.class); + final String uuid2 = result.get(1).getData().get("recordId", String.class); + assertThat(uuid1.equals(uuid2), is(false)); + } + + @Test + void test_generate_uuid_does_not_overwrite_existing_key_by_default() { + when(mockConfig.getEntries()).thenReturn(createListOfEntries( + createEntryWithGenerateUuid("existingId", false, null))); + + final AddEntryProcessor processor = createObjectUnderTest(); + final Map data = new HashMap<>(); + data.put("existingId", "original-value"); + final Record record = buildRecordWithEvent(data); + final List> result = (List>) processor.doExecute(Collections.singletonList(record)); + + assertThat(result.get(0).getData().get("existingId", String.class), equalTo("original-value")); + } + + @Test + void test_generate_uuid_overwrites_existing_key_when_overwrite_is_true() { + when(mockConfig.getEntries()).thenReturn(createListOfEntries( + createEntryWithGenerateUuid("existingId", true, null))); + + final AddEntryProcessor processor = createObjectUnderTest(); + final Map data = new HashMap<>(); + data.put("existingId", "original-value"); + final Record record = buildRecordWithEvent(data); + final List> result = (List>) processor.doExecute(Collections.singletonList(record)); + + final String newValue = result.get(0).getData().get("existingId", String.class); + assertThat(newValue.equals("original-value"), is(false)); + assertThat(newValue, equalTo(UUID.fromString(newValue).toString())); + } + + @Test + void test_generate_uuid_respects_add_when_condition() { + final String addWhen = "/skip == true"; + when(mockConfig.getEntries()).thenReturn(createListOfEntries( + createEntryWithGenerateUuid("recordId", false, addWhen))); + when(expressionEvaluator.isValidExpressionStatement(addWhen)).thenReturn(true); + when(expressionEvaluator.evaluateConditional(eq(addWhen), any())).thenReturn(false); + + final AddEntryProcessor processor = createObjectUnderTest(); + final Record record = getEvent("message"); + final List> result = (List>) processor.doExecute(Collections.singletonList(record)); + + assertThat(result.get(0).getData().containsKey("recordId"), is(false)); + } + private AddEntryProcessor createObjectUnderTest() { return new AddEntryProcessor(pluginMetrics, mockConfig, expressionEvaluator, eventKeyFactory); } @@ -1124,6 +1199,14 @@ private AddEntryProcessorConfig.Entry createEntry( iterateOn, addToElementWhen); } + private AddEntryProcessorConfig.Entry createEntryWithGenerateUuid( + final String key, + final boolean overwriteIfKeyExists, + final String addWhen) { + return new AddEntryProcessorConfig.Entry( + key, null, null, null, null, overwriteIfKeyExists, false, addWhen, null, true, null, true); + } + private List createListOfEntries(final AddEntryProcessorConfig.Entry... entries) { return new LinkedList<>(Arrays.asList(entries)); } From a0960bd18b32012a079e7df1ba2881e345610ab0 Mon Sep 17 00:00:00 2001 From: Xun Zhang Date: Fri, 20 Mar 2026 14:59:02 -0700 Subject: [PATCH 2/3] use expression function for uuid creation Signed-off-by: Xun Zhang --- .../GenerateUuidExpressionFunction.java | 40 ++++++++++++ .../GenerateUuidExpressionFunctionTest.java | 59 ++++++++++++++++++ .../mutateevent/AddEntryProcessor.java | 5 +- .../mutateevent/AddEntryProcessorConfig.java | 59 +----------------- .../mutateevent/AddEntryProcessorTests.java | 62 +++++++++++-------- 5 files changed, 137 insertions(+), 88 deletions(-) create mode 100644 data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/GenerateUuidExpressionFunction.java create mode 100644 data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/GenerateUuidExpressionFunctionTest.java diff --git a/data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/GenerateUuidExpressionFunction.java b/data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/GenerateUuidExpressionFunction.java new file mode 100644 index 0000000000..1aede5ba29 --- /dev/null +++ b/data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/GenerateUuidExpressionFunction.java @@ -0,0 +1,40 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.dataprepper.expression; + +import org.opensearch.dataprepper.model.event.Event; + +import javax.inject.Named; +import java.util.List; +import java.util.UUID; +import java.util.function.Function; + +/** + * Expression function that generates a random UUID (version 4) string. + * Usage: {@code generate_uuid()} + */ +@Named +public class GenerateUuidExpressionFunction implements ExpressionFunction { + + static final String FUNCTION_NAME = "generate_uuid"; + + @Override + public String getFunctionName() { + return FUNCTION_NAME; + } + + @Override + public Object evaluate(final List args, final Event event, final Function convertLiteralType) { + if (!args.isEmpty()) { + throw new RuntimeException(FUNCTION_NAME + "() does not take any arguments"); + } + return UUID.randomUUID().toString(); + } +} diff --git a/data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/GenerateUuidExpressionFunctionTest.java b/data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/GenerateUuidExpressionFunctionTest.java new file mode 100644 index 0000000000..c641d4892c --- /dev/null +++ b/data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/GenerateUuidExpressionFunctionTest.java @@ -0,0 +1,59 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.dataprepper.expression; + +import org.junit.jupiter.api.Test; +import org.opensearch.dataprepper.model.event.Event; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.function.Function; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; + +class GenerateUuidExpressionFunctionTest { + + private final GenerateUuidExpressionFunction function = new GenerateUuidExpressionFunction(); + private final Event event = mock(Event.class); + private final Function convertLiteralType = v -> v; + + @Test + void getFunctionName_returns_generate_uuid() { + assertThat(function.getFunctionName(), equalTo("generate_uuid")); + } + + @Test + void evaluate_returns_valid_uuid_string() { + final Object result = function.evaluate(Collections.emptyList(), event, convertLiteralType); + assertThat(result, instanceOf(String.class)); + final String uuidStr = (String) result; + // UUID.fromString throws if invalid + assertThat(UUID.fromString(uuidStr).toString(), equalTo(uuidStr)); + } + + @Test + void evaluate_returns_unique_values_on_successive_calls() { + final String first = (String) function.evaluate(Collections.emptyList(), event, convertLiteralType); + final String second = (String) function.evaluate(Collections.emptyList(), event, convertLiteralType); + assertThat(first, not(equalTo(second))); + } + + @Test + void evaluate_throws_when_args_are_provided() { + assertThrows(RuntimeException.class, + () -> function.evaluate(List.of("unexpected"), event, convertLiteralType)); + } +} diff --git a/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessor.java b/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessor.java index ca63fcb5a6..77de3bf5c3 100644 --- a/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessor.java +++ b/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessor.java @@ -30,7 +30,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.UUID; import java.util.function.Consumer; import java.util.function.Supplier; @@ -315,9 +314,7 @@ private Object retrieveValue(final AddEntryProcessorConfig.Entry entry, final Ev EntryProperties props = entryProperties.get(entryIndex); KeyInfo keyInfo = preprocessedKeys.get(entryIndex); - if (entry.getGenerateUuid()) { - return UUID.randomUUID().toString(); - } else if (!Objects.isNull(entry.getValueExpression())) { + if (!Objects.isNull(entry.getValueExpression())) { value = expressionEvaluator.evaluate(entry.getValueExpression(), context); } else if (!Objects.isNull(entry.getFormat())) { try { diff --git a/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorConfig.java b/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorConfig.java index 7b998db1e3..9e8af89643 100644 --- a/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorConfig.java +++ b/data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorConfig.java @@ -166,12 +166,6 @@ public static class Entry { }) private boolean flattenKey = true; - @JsonProperty("generate_uuid") - @JsonPropertyDescription("When set to true, generates a random UUID (version 4) as the value of the new entry. " + - "Each event receives its own unique UUID, providing globally unique identifiers across distributed deployments " + - "without any coordination between nodes. Cannot be used together with value, format, or value_expression.") - private boolean generateUuid = false; - @JsonProperty("add_when") @JsonPropertyDescription("A conditional expression, " + "such as /some-key == \"test\", that will be evaluated to determine whether the processor will be run on the event.") @@ -218,19 +212,11 @@ public boolean getFlattenKey(){ return flattenKey; } - public boolean getGenerateUuid() { - return generateUuid; - } - public String getAddWhen() { return addWhen; } - @AssertTrue(message = "Exactly one of value, format, value_expression, or generate_uuid must be specified") + @AssertTrue(message = "Exactly one of value, format, or value_expression must be specified") public boolean hasValueOrFormatOrExpression() { - final long count = Stream.of(value, format, valueExpression).filter(n -> n != null).count(); - if (generateUuid) { - return count == 0; - } - return count == 1; + return Stream.of(value, format, valueExpression).filter(n -> n!=null).count() == 1; } @AssertTrue(message = "overwrite_if_key_exists and append_if_key_exists can not be set to true at the same time.") @@ -243,46 +229,6 @@ boolean flattenKeyFalseIsUsedWithIterateOn() { return (!flattenKey && iterateOn!=null) || flattenKey; } - public Entry(final String key, - final String metadataKey, - final Object value, - final String format, - final String valueExpression, - final boolean overwriteIfKeyExists, - final boolean appendIfKeyExists, - final String addWhen, - final String iterateOn, - final boolean flattenKey, - final String addToElementWhen, - final boolean generateUuid) - { - if (key != null && metadataKey != null) { - throw new IllegalArgumentException("Only one of the two - key and metadatakey - should be specified"); - } - if (key == null && metadataKey == null) { - throw new IllegalArgumentException("At least one of the two - key and metadatakey - must be specified"); - } - if (metadataKey != null && iterateOn != null) { - throw new IllegalArgumentException("iterate_on cannot be applied to metadata"); - } - if (iterateOn == null && addToElementWhen != null) { - throw new InvalidPluginConfigurationException("add_to_element_when only applies when iterate_on is configured."); - } - - this.key = key; - this.metadataKey = metadataKey; - this.value = value; - this.format = format; - this.valueExpression = valueExpression; - this.overwriteIfKeyExists = overwriteIfKeyExists; - this.appendIfKeyExists = appendIfKeyExists; - this.addWhen = addWhen; - this.iterateOn = iterateOn; - this.flattenKey = flattenKey; - this.addToElementWhen = addToElementWhen; - this.generateUuid = generateUuid; - } - public Entry(final String key, final String metadataKey, final Object value, @@ -304,7 +250,6 @@ public Entry(final String key, if (metadataKey != null && iterateOn != null) { throw new IllegalArgumentException("iterate_on cannot be applied to metadata"); } - if (iterateOn == null && addToElementWhen != null) { throw new InvalidPluginConfigurationException("add_to_element_when only applies when iterate_on is configured."); } diff --git a/data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java b/data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java index 222acd3b9e..984e8a4784 100644 --- a/data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java +++ b/data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java @@ -1089,9 +1089,13 @@ public void testAddFlattenedNestedEntryIterateOn() { } @Test - void test_generate_uuid_adds_uuid_string_to_event() { + void test_generate_uuid_expression_adds_uuid_string_to_event() { + final String uuidExpr = "generate_uuid()"; + final String generatedUuid = UUID.randomUUID().toString(); when(mockConfig.getEntries()).thenReturn(createListOfEntries( - createEntryWithGenerateUuid("recordId", false, null))); + createEntry("recordId", null, null, null, uuidExpr, false, false, null, null, null))); + when(expressionEvaluator.isValidExpressionStatement(uuidExpr)).thenReturn(true); + when(expressionEvaluator.evaluate(eq(uuidExpr), any())).thenReturn(generatedUuid); final AddEntryProcessor processor = createObjectUnderTest(); final Record record = getEvent("test-message"); @@ -1099,29 +1103,36 @@ void test_generate_uuid_adds_uuid_string_to_event() { final Event event = result.get(0).getData(); assertThat(event.containsKey("recordId"), is(true)); - final String uuid = event.get("recordId", String.class); - assertThat(uuid, equalTo(UUID.fromString(uuid).toString())); + assertThat(event.get("recordId", String.class), equalTo(generatedUuid)); } @Test - void test_generate_uuid_produces_unique_values_per_event() { + void test_generate_uuid_expression_produces_unique_values_per_event() { + final String uuidExpr = "generate_uuid()"; + final String uuid1 = UUID.randomUUID().toString(); + final String uuid2 = UUID.randomUUID().toString(); when(mockConfig.getEntries()).thenReturn(createListOfEntries( - createEntryWithGenerateUuid("recordId", false, null))); + createEntry("recordId", null, null, null, uuidExpr, false, false, null, null, null))); + when(expressionEvaluator.isValidExpressionStatement(uuidExpr)).thenReturn(true); + when(expressionEvaluator.evaluate(eq(uuidExpr), any())) + .thenReturn(uuid1) + .thenReturn(uuid2); final AddEntryProcessor processor = createObjectUnderTest(); - final Record record1 = getEvent("message-one"); - final Record record2 = getEvent("message-two"); - final List> result = (List>) processor.doExecute(Arrays.asList(record1, record2)); + final List> result = (List>) processor.doExecute( + Arrays.asList(getEvent("message-one"), getEvent("message-two"))); - final String uuid1 = result.get(0).getData().get("recordId", String.class); - final String uuid2 = result.get(1).getData().get("recordId", String.class); + assertThat(result.get(0).getData().get("recordId", String.class), equalTo(uuid1)); + assertThat(result.get(1).getData().get("recordId", String.class), equalTo(uuid2)); assertThat(uuid1.equals(uuid2), is(false)); } @Test - void test_generate_uuid_does_not_overwrite_existing_key_by_default() { + void test_generate_uuid_expression_does_not_overwrite_existing_key_by_default() { + final String uuidExpr = "generate_uuid()"; when(mockConfig.getEntries()).thenReturn(createListOfEntries( - createEntryWithGenerateUuid("existingId", false, null))); + createEntry("existingId", null, null, null, uuidExpr, false, false, null, null, null))); + when(expressionEvaluator.isValidExpressionStatement(uuidExpr)).thenReturn(true); final AddEntryProcessor processor = createObjectUnderTest(); final Map data = new HashMap<>(); @@ -1133,9 +1144,13 @@ void test_generate_uuid_does_not_overwrite_existing_key_by_default() { } @Test - void test_generate_uuid_overwrites_existing_key_when_overwrite_is_true() { + void test_generate_uuid_expression_overwrites_existing_key_when_overwrite_is_true() { + final String uuidExpr = "generate_uuid()"; + final String newUuid = UUID.randomUUID().toString(); when(mockConfig.getEntries()).thenReturn(createListOfEntries( - createEntryWithGenerateUuid("existingId", true, null))); + createEntry("existingId", null, null, null, uuidExpr, true, false, null, null, null))); + when(expressionEvaluator.isValidExpressionStatement(uuidExpr)).thenReturn(true); + when(expressionEvaluator.evaluate(eq(uuidExpr), any())).thenReturn(newUuid); final AddEntryProcessor processor = createObjectUnderTest(); final Map data = new HashMap<>(); @@ -1143,16 +1158,16 @@ void test_generate_uuid_overwrites_existing_key_when_overwrite_is_true() { final Record record = buildRecordWithEvent(data); final List> result = (List>) processor.doExecute(Collections.singletonList(record)); - final String newValue = result.get(0).getData().get("existingId", String.class); - assertThat(newValue.equals("original-value"), is(false)); - assertThat(newValue, equalTo(UUID.fromString(newValue).toString())); + assertThat(result.get(0).getData().get("existingId", String.class), equalTo(newUuid)); } @Test - void test_generate_uuid_respects_add_when_condition() { + void test_generate_uuid_expression_respects_add_when_condition() { + final String uuidExpr = "generate_uuid()"; final String addWhen = "/skip == true"; when(mockConfig.getEntries()).thenReturn(createListOfEntries( - createEntryWithGenerateUuid("recordId", false, addWhen))); + createEntry("recordId", null, null, null, uuidExpr, false, false, addWhen, null, null))); + when(expressionEvaluator.isValidExpressionStatement(uuidExpr)).thenReturn(true); when(expressionEvaluator.isValidExpressionStatement(addWhen)).thenReturn(true); when(expressionEvaluator.evaluateConditional(eq(addWhen), any())).thenReturn(false); @@ -1199,13 +1214,6 @@ private AddEntryProcessorConfig.Entry createEntry( iterateOn, addToElementWhen); } - private AddEntryProcessorConfig.Entry createEntryWithGenerateUuid( - final String key, - final boolean overwriteIfKeyExists, - final String addWhen) { - return new AddEntryProcessorConfig.Entry( - key, null, null, null, null, overwriteIfKeyExists, false, addWhen, null, true, null, true); - } private List createListOfEntries(final AddEntryProcessorConfig.Entry... entries) { return new LinkedList<>(Arrays.asList(entries)); From 069be4add12b5dd5d578fd87800afed0df8aa75b Mon Sep 17 00:00:00 2001 From: Xun Zhang Date: Mon, 23 Mar 2026 13:26:33 -0700 Subject: [PATCH 3/3] update function name to generateUuid() Signed-off-by: Xun Zhang --- .../GenerateUuidExpressionFunction.java | 4 ++-- .../GenerateUuidExpressionFunctionTest.java | 9 +++++---- .../mutateevent/AddEntryProcessorTests.java | 20 +++++++++---------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/GenerateUuidExpressionFunction.java b/data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/GenerateUuidExpressionFunction.java index 1aede5ba29..987baad3ae 100644 --- a/data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/GenerateUuidExpressionFunction.java +++ b/data-prepper-expression/src/main/java/org/opensearch/dataprepper/expression/GenerateUuidExpressionFunction.java @@ -18,12 +18,12 @@ /** * Expression function that generates a random UUID (version 4) string. - * Usage: {@code generate_uuid()} + * Usage: {@code generateUuid()} */ @Named public class GenerateUuidExpressionFunction implements ExpressionFunction { - static final String FUNCTION_NAME = "generate_uuid"; + static final String FUNCTION_NAME = "generateUuid"; @Override public String getFunctionName() { diff --git a/data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/GenerateUuidExpressionFunctionTest.java b/data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/GenerateUuidExpressionFunctionTest.java index c641d4892c..edcca1bf5f 100644 --- a/data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/GenerateUuidExpressionFunctionTest.java +++ b/data-prepper-expression/src/test/java/org/opensearch/dataprepper/expression/GenerateUuidExpressionFunctionTest.java @@ -21,6 +21,7 @@ import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; @@ -31,8 +32,8 @@ class GenerateUuidExpressionFunctionTest { private final Function convertLiteralType = v -> v; @Test - void getFunctionName_returns_generate_uuid() { - assertThat(function.getFunctionName(), equalTo("generate_uuid")); + void getFunctionName_returns_generateUuid() { + assertThat(function.getFunctionName(), equalTo("generateUuid")); } @Test @@ -40,8 +41,8 @@ void evaluate_returns_valid_uuid_string() { final Object result = function.evaluate(Collections.emptyList(), event, convertLiteralType); assertThat(result, instanceOf(String.class)); final String uuidStr = (String) result; - // UUID.fromString throws if invalid - assertThat(UUID.fromString(uuidStr).toString(), equalTo(uuidStr)); + final UUID regeneratedUuid = assertDoesNotThrow(() -> UUID.fromString(uuidStr)); + assertThat(regeneratedUuid.toString(), equalTo(uuidStr)); } @Test diff --git a/data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java b/data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java index 984e8a4784..17395cb33a 100644 --- a/data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java +++ b/data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java @@ -1089,8 +1089,8 @@ public void testAddFlattenedNestedEntryIterateOn() { } @Test - void test_generate_uuid_expression_adds_uuid_string_to_event() { - final String uuidExpr = "generate_uuid()"; + void test_generateUuid_expression_adds_uuid_string_to_event() { + final String uuidExpr = "generateUuid()"; final String generatedUuid = UUID.randomUUID().toString(); when(mockConfig.getEntries()).thenReturn(createListOfEntries( createEntry("recordId", null, null, null, uuidExpr, false, false, null, null, null))); @@ -1107,8 +1107,8 @@ void test_generate_uuid_expression_adds_uuid_string_to_event() { } @Test - void test_generate_uuid_expression_produces_unique_values_per_event() { - final String uuidExpr = "generate_uuid()"; + void test_generateUuid_expression_produces_unique_values_per_event() { + final String uuidExpr = "generateUuid()"; final String uuid1 = UUID.randomUUID().toString(); final String uuid2 = UUID.randomUUID().toString(); when(mockConfig.getEntries()).thenReturn(createListOfEntries( @@ -1128,8 +1128,8 @@ void test_generate_uuid_expression_produces_unique_values_per_event() { } @Test - void test_generate_uuid_expression_does_not_overwrite_existing_key_by_default() { - final String uuidExpr = "generate_uuid()"; + void test_generateUuid_expression_does_not_overwrite_existing_key_by_default() { + final String uuidExpr = "generateUuid()"; when(mockConfig.getEntries()).thenReturn(createListOfEntries( createEntry("existingId", null, null, null, uuidExpr, false, false, null, null, null))); when(expressionEvaluator.isValidExpressionStatement(uuidExpr)).thenReturn(true); @@ -1144,8 +1144,8 @@ void test_generate_uuid_expression_does_not_overwrite_existing_key_by_default() } @Test - void test_generate_uuid_expression_overwrites_existing_key_when_overwrite_is_true() { - final String uuidExpr = "generate_uuid()"; + void test_generateUuid_expression_overwrites_existing_key_when_overwrite_is_true() { + final String uuidExpr = "generateUuid()"; final String newUuid = UUID.randomUUID().toString(); when(mockConfig.getEntries()).thenReturn(createListOfEntries( createEntry("existingId", null, null, null, uuidExpr, true, false, null, null, null))); @@ -1162,8 +1162,8 @@ void test_generate_uuid_expression_overwrites_existing_key_when_overwrite_is_tru } @Test - void test_generate_uuid_expression_respects_add_when_condition() { - final String uuidExpr = "generate_uuid()"; + void test_generateUuid_expression_respects_add_when_condition() { + final String uuidExpr = "generateUuid()"; final String addWhen = "/skip == true"; when(mockConfig.getEntries()).thenReturn(createListOfEntries( createEntry("recordId", null, null, null, uuidExpr, false, false, addWhen, null, null)));