Skip to content

Commit 3a931a5

Browse files
authored
Add generateUuid() function (#6653)
Add generateUuid() function for UUID creation Signed-off-by: Xun Zhang <xunzh@amazon.com>
1 parent d875a66 commit 3a931a5

5 files changed

Lines changed: 193 additions & 3 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*/
9+
10+
package org.opensearch.dataprepper.expression;
11+
12+
import org.opensearch.dataprepper.model.event.Event;
13+
14+
import javax.inject.Named;
15+
import java.util.List;
16+
import java.util.UUID;
17+
import java.util.function.Function;
18+
19+
/**
20+
* Expression function that generates a random UUID (version 4) string.
21+
* Usage: {@code generateUuid()}
22+
*/
23+
@Named
24+
public class GenerateUuidExpressionFunction implements ExpressionFunction {
25+
26+
static final String FUNCTION_NAME = "generateUuid";
27+
28+
@Override
29+
public String getFunctionName() {
30+
return FUNCTION_NAME;
31+
}
32+
33+
@Override
34+
public Object evaluate(final List<Object> args, final Event event, final Function<Object, Object> convertLiteralType) {
35+
if (!args.isEmpty()) {
36+
throw new RuntimeException(FUNCTION_NAME + "() does not take any arguments");
37+
}
38+
return UUID.randomUUID().toString();
39+
}
40+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*/
9+
10+
package org.opensearch.dataprepper.expression;
11+
12+
import org.junit.jupiter.api.Test;
13+
import org.opensearch.dataprepper.model.event.Event;
14+
15+
import java.util.Collections;
16+
import java.util.List;
17+
import java.util.UUID;
18+
import java.util.function.Function;
19+
20+
import static org.hamcrest.CoreMatchers.instanceOf;
21+
import static org.hamcrest.CoreMatchers.not;
22+
import static org.hamcrest.CoreMatchers.equalTo;
23+
import static org.hamcrest.MatcherAssert.assertThat;
24+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
25+
import static org.junit.jupiter.api.Assertions.assertThrows;
26+
import static org.mockito.Mockito.mock;
27+
28+
class GenerateUuidExpressionFunctionTest {
29+
30+
private final GenerateUuidExpressionFunction function = new GenerateUuidExpressionFunction();
31+
private final Event event = mock(Event.class);
32+
private final Function<Object, Object> convertLiteralType = v -> v;
33+
34+
@Test
35+
void getFunctionName_returns_generateUuid() {
36+
assertThat(function.getFunctionName(), equalTo("generateUuid"));
37+
}
38+
39+
@Test
40+
void evaluate_returns_valid_uuid_string() {
41+
final Object result = function.evaluate(Collections.emptyList(), event, convertLiteralType);
42+
assertThat(result, instanceOf(String.class));
43+
final String uuidStr = (String) result;
44+
final UUID regeneratedUuid = assertDoesNotThrow(() -> UUID.fromString(uuidStr));
45+
assertThat(regeneratedUuid.toString(), equalTo(uuidStr));
46+
}
47+
48+
@Test
49+
void evaluate_returns_unique_values_on_successive_calls() {
50+
final String first = (String) function.evaluate(Collections.emptyList(), event, convertLiteralType);
51+
final String second = (String) function.evaluate(Collections.emptyList(), event, convertLiteralType);
52+
assertThat(first, not(equalTo(second)));
53+
}
54+
55+
@Test
56+
void evaluate_throws_when_args_are_provided() {
57+
assertThrows(RuntimeException.class,
58+
() -> function.evaluate(List.of("unexpected"), event, convertLiteralType));
59+
}
60+
}

data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ private Object retrieveValue(final AddEntryProcessorConfig.Entry entry, final Ev
313313
int entryIndex = entries.indexOf(entry);
314314
EntryProperties props = entryProperties.get(entryIndex);
315315
KeyInfo keyInfo = preprocessedKeys.get(entryIndex);
316-
316+
317317
if (!Objects.isNull(entry.getValueExpression())) {
318318
value = expressionEvaluator.evaluate(entry.getValueExpression(), context);
319319
} else if (!Objects.isNull(entry.getFormat())) {

data-prepper-plugins/mutate-event-processors/src/main/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorConfig.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ public boolean getFlattenKey(){
214214

215215
public String getAddWhen() { return addWhen; }
216216

217-
@AssertTrue(message = "Either value or format or expression must be specified, and only one of them can be specified")
217+
@AssertTrue(message = "Exactly one of value, format, or value_expression must be specified")
218218
public boolean hasValueOrFormatOrExpression() {
219219
return Stream.of(value, format, valueExpression).filter(n -> n!=null).count() == 1;
220220
}
@@ -250,7 +250,6 @@ public Entry(final String key,
250250
if (metadataKey != null && iterateOn != null) {
251251
throw new IllegalArgumentException("iterate_on cannot be applied to metadata");
252252
}
253-
254253
if (iterateOn == null && addToElementWhen != null) {
255254
throw new InvalidPluginConfigurationException("add_to_element_when only applies when iterate_on is configured.");
256255
}

data-prepper-plugins/mutate-event-processors/src/test/java/org/opensearch/dataprepper/plugins/processor/mutateevent/AddEntryProcessorTests.java

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,6 +1088,96 @@ public void testAddFlattenedNestedEntryIterateOn() {
10881088
equalTo(List.of(Map.of("key", 5, "nested/newMessage", 3)))); // [{"key": 5, "nested/newMessage": 3}}]
10891089
}
10901090

1091+
@Test
1092+
void test_generateUuid_expression_adds_uuid_string_to_event() {
1093+
final String uuidExpr = "generateUuid()";
1094+
final String generatedUuid = UUID.randomUUID().toString();
1095+
when(mockConfig.getEntries()).thenReturn(createListOfEntries(
1096+
createEntry("recordId", null, null, null, uuidExpr, false, false, null, null, null)));
1097+
when(expressionEvaluator.isValidExpressionStatement(uuidExpr)).thenReturn(true);
1098+
when(expressionEvaluator.evaluate(eq(uuidExpr), any())).thenReturn(generatedUuid);
1099+
1100+
final AddEntryProcessor processor = createObjectUnderTest();
1101+
final Record<Event> record = getEvent("test-message");
1102+
final List<Record<Event>> result = (List<Record<Event>>) processor.doExecute(Collections.singletonList(record));
1103+
1104+
final Event event = result.get(0).getData();
1105+
assertThat(event.containsKey("recordId"), is(true));
1106+
assertThat(event.get("recordId", String.class), equalTo(generatedUuid));
1107+
}
1108+
1109+
@Test
1110+
void test_generateUuid_expression_produces_unique_values_per_event() {
1111+
final String uuidExpr = "generateUuid()";
1112+
final String uuid1 = UUID.randomUUID().toString();
1113+
final String uuid2 = UUID.randomUUID().toString();
1114+
when(mockConfig.getEntries()).thenReturn(createListOfEntries(
1115+
createEntry("recordId", null, null, null, uuidExpr, false, false, null, null, null)));
1116+
when(expressionEvaluator.isValidExpressionStatement(uuidExpr)).thenReturn(true);
1117+
when(expressionEvaluator.evaluate(eq(uuidExpr), any()))
1118+
.thenReturn(uuid1)
1119+
.thenReturn(uuid2);
1120+
1121+
final AddEntryProcessor processor = createObjectUnderTest();
1122+
final List<Record<Event>> result = (List<Record<Event>>) processor.doExecute(
1123+
Arrays.asList(getEvent("message-one"), getEvent("message-two")));
1124+
1125+
assertThat(result.get(0).getData().get("recordId", String.class), equalTo(uuid1));
1126+
assertThat(result.get(1).getData().get("recordId", String.class), equalTo(uuid2));
1127+
assertThat(uuid1.equals(uuid2), is(false));
1128+
}
1129+
1130+
@Test
1131+
void test_generateUuid_expression_does_not_overwrite_existing_key_by_default() {
1132+
final String uuidExpr = "generateUuid()";
1133+
when(mockConfig.getEntries()).thenReturn(createListOfEntries(
1134+
createEntry("existingId", null, null, null, uuidExpr, false, false, null, null, null)));
1135+
when(expressionEvaluator.isValidExpressionStatement(uuidExpr)).thenReturn(true);
1136+
1137+
final AddEntryProcessor processor = createObjectUnderTest();
1138+
final Map<String, Object> data = new HashMap<>();
1139+
data.put("existingId", "original-value");
1140+
final Record<Event> record = buildRecordWithEvent(data);
1141+
final List<Record<Event>> result = (List<Record<Event>>) processor.doExecute(Collections.singletonList(record));
1142+
1143+
assertThat(result.get(0).getData().get("existingId", String.class), equalTo("original-value"));
1144+
}
1145+
1146+
@Test
1147+
void test_generateUuid_expression_overwrites_existing_key_when_overwrite_is_true() {
1148+
final String uuidExpr = "generateUuid()";
1149+
final String newUuid = UUID.randomUUID().toString();
1150+
when(mockConfig.getEntries()).thenReturn(createListOfEntries(
1151+
createEntry("existingId", null, null, null, uuidExpr, true, false, null, null, null)));
1152+
when(expressionEvaluator.isValidExpressionStatement(uuidExpr)).thenReturn(true);
1153+
when(expressionEvaluator.evaluate(eq(uuidExpr), any())).thenReturn(newUuid);
1154+
1155+
final AddEntryProcessor processor = createObjectUnderTest();
1156+
final Map<String, Object> data = new HashMap<>();
1157+
data.put("existingId", "original-value");
1158+
final Record<Event> record = buildRecordWithEvent(data);
1159+
final List<Record<Event>> result = (List<Record<Event>>) processor.doExecute(Collections.singletonList(record));
1160+
1161+
assertThat(result.get(0).getData().get("existingId", String.class), equalTo(newUuid));
1162+
}
1163+
1164+
@Test
1165+
void test_generateUuid_expression_respects_add_when_condition() {
1166+
final String uuidExpr = "generateUuid()";
1167+
final String addWhen = "/skip == true";
1168+
when(mockConfig.getEntries()).thenReturn(createListOfEntries(
1169+
createEntry("recordId", null, null, null, uuidExpr, false, false, addWhen, null, null)));
1170+
when(expressionEvaluator.isValidExpressionStatement(uuidExpr)).thenReturn(true);
1171+
when(expressionEvaluator.isValidExpressionStatement(addWhen)).thenReturn(true);
1172+
when(expressionEvaluator.evaluateConditional(eq(addWhen), any())).thenReturn(false);
1173+
1174+
final AddEntryProcessor processor = createObjectUnderTest();
1175+
final Record<Event> record = getEvent("message");
1176+
final List<Record<Event>> result = (List<Record<Event>>) processor.doExecute(Collections.singletonList(record));
1177+
1178+
assertThat(result.get(0).getData().containsKey("recordId"), is(false));
1179+
}
1180+
10911181
private AddEntryProcessor createObjectUnderTest() {
10921182
return new AddEntryProcessor(pluginMetrics, mockConfig, expressionEvaluator, eventKeyFactory);
10931183
}
@@ -1124,6 +1214,7 @@ private AddEntryProcessorConfig.Entry createEntry(
11241214
iterateOn, addToElementWhen);
11251215
}
11261216

1217+
11271218
private List<AddEntryProcessorConfig.Entry> createListOfEntries(final AddEntryProcessorConfig.Entry... entries) {
11281219
return new LinkedList<>(Arrays.asList(entries));
11291220
}

0 commit comments

Comments
 (0)