Repository navigation
Calculate heuristic distance on failed queries #1801
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
aschenzle
wants to merge
5
commits into
master
Choose a base branch
from
feature/dynamodb-conditional-put-heuristics
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
8172ccc
recording failed conditional writes and adding tests to verify Dynamo…
aschenzle 50c086d
Addressing comments by adding DynamoDbTableItemsCache and splitting c…
aschenzle 2a1b39a
Merge branch 'master' into feature/dynamodb-conditional-put-heuristics
aschenzle ea2ae29
Addressing comments, adding tests for DynamoDbTableItemsCache
aschenzle be29c1a
Merge branch 'master' into feature/dynamodb-conditional-put-heuristics
aschenzle File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
97 changes: 97 additions & 0 deletions
97
...va/org/evomaster/client/java/controller/internal/db/dynamodb/DynamoDbTableItemsCache.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| package org.evomaster.client.java.controller.internal.db.dynamodb; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
|
|
||
| /** | ||
| * Caches normalized DynamoDB table items loaded during one command evaluation batch. | ||
| * <p> | ||
| * The cache maintains an unmodifiable list of item maps for each table accessed in the batch. | ||
| * Note that while the outer list and the per-item attribute maps are unmodifiable, the underlying | ||
| * {@link Object} attribute values (for instance, nested collections, maps, or byte arrays) are not | ||
| * guaranteed to be deeply immutable. | ||
| */ | ||
| final class DynamoDbTableItemsCache { | ||
|
|
||
| private final DynamoDbTableDataAccessor tableDataAccessor; | ||
| private final Object dynamoDbClient; | ||
|
|
||
| /** | ||
| * Map from DynamoDB table name ({@link String}) to the list of items scanned from that table. | ||
| * Each item is represented as a {@link Map} where the key is the attribute name ({@link String}) | ||
| * and the value is the attribute value ({@link Object}). | ||
| */ | ||
| private final Map<String, List<Map<String, Object>>> itemsByTable = new HashMap<>(); | ||
|
|
||
| /** | ||
| * Creates a cache backed by the given table accessor and DynamoDB client. | ||
| * | ||
| * @param tableDataAccessor accessor used to scan tables on cache misses | ||
| * @param dynamoDbClient SDK v2 synchronous or asynchronous DynamoDB client | ||
| */ | ||
| DynamoDbTableItemsCache(DynamoDbTableDataAccessor tableDataAccessor, Object dynamoDbClient) { | ||
| this.tableDataAccessor = Objects.requireNonNull(tableDataAccessor, | ||
| "DynamoDB table data accessor cannot be null"); | ||
| this.dynamoDbClient = dynamoDbClient; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the cached items for a table, scanning it on the first request in this batch. | ||
| * | ||
| * @param tableName table to read | ||
| * @return unmodifiable list of normalized items for the table, where each item is represented | ||
| * as an unmodifiable map of attribute name ({@link String}) to attribute value ({@link Object}). | ||
| * Note that the individual attribute value {@link Object}s are not guaranteed to be | ||
| * deeply immutable. | ||
| */ | ||
| List<Map<String, Object>> getItems(String tableName) { | ||
| String validTableName = validateTableName(tableName); | ||
| List<Map<String, Object>> items = itemsByTable.get(validTableName); | ||
| if (items == null) { | ||
| items = validateItems(tableDataAccessor.getItems(dynamoDbClient, validTableName)); | ||
| itemsByTable.put(validTableName, items); | ||
| } | ||
| return items; | ||
| } | ||
|
|
||
| /** | ||
| * Checks that a table name can be used as a stable cache key and returns its trimmed representation. | ||
| * | ||
| * @param tableName candidate table name | ||
| * @return validated and trimmed table name | ||
| */ | ||
| private String validateTableName(String tableName) { | ||
| Objects.requireNonNull(tableName, "DynamoDB table name cannot be null"); | ||
| String trimmed = tableName.trim(); | ||
| if (trimmed.isEmpty()) { | ||
| throw new IllegalArgumentException("DynamoDB table name cannot be blank"); | ||
| } | ||
| return trimmed; | ||
| } | ||
|
|
||
| /** | ||
| * Copies scanned items into unmodifiable structures and checks the cache invariants. | ||
| * | ||
| * @param items scanned table items, where each item is a {@link Map} of attribute name ({@link String}) | ||
| * to attribute value ({@link Object}) | ||
| * @return unmodifiable list of unmodifiable item maps. Note that individual attribute value | ||
| * {@link Object} instances within each map are not guaranteed to be deeply immutable. | ||
| */ | ||
| private List<Map<String, Object>> validateItems(List<Map<String, Object>> items) { | ||
| Objects.requireNonNull(items, "DynamoDB table items cannot be null"); | ||
| List<Map<String, Object>> copy = new ArrayList<>(items.size()); | ||
| for (Map<String, Object> item : items) { | ||
| Objects.requireNonNull(item, "DynamoDB table item cannot be null"); | ||
| for (String attributeName : item.keySet()) { | ||
| Objects.requireNonNull(attributeName, "DynamoDB attribute name cannot be null"); | ||
| } | ||
| copy.add(Collections.unmodifiableMap(new LinkedHashMap<>(item))); | ||
| } | ||
| return Collections.unmodifiableList(copy); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
...rg/evomaster/client/java/controller/internal/db/dynamodb/DynamoDbTableItemsCacheTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| package org.evomaster.client.java.controller.internal.db.dynamodb; | ||
|
|
||
| import org.junit.jupiter.api.Test; | ||
| import software.amazon.awssdk.services.dynamodb.DynamoDbClient; | ||
| import software.amazon.awssdk.services.dynamodb.model.AttributeValue; | ||
| import software.amazon.awssdk.services.dynamodb.model.ScanRequest; | ||
| import software.amazon.awssdk.services.dynamodb.model.ScanResponse; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertThrows; | ||
| import static org.mockito.ArgumentMatchers.any; | ||
| import static org.mockito.Mockito.mock; | ||
| import static org.mockito.Mockito.times; | ||
| import static org.mockito.Mockito.verify; | ||
| import static org.mockito.Mockito.when; | ||
|
|
||
| /** | ||
| * Tests DynamoDB table item caching and validation invariants. | ||
| */ | ||
| public class DynamoDbTableItemsCacheTest { | ||
|
|
||
| private static final String TABLE = "WorldCupPlayers"; | ||
|
|
||
| private final DynamoDbTableDataAccessor accessor = new DynamoDbTableDataAccessor(); | ||
|
|
||
| /** | ||
| * Verifies that items for a table are loaded on the first call and cached for subsequent calls. | ||
| */ | ||
| @Test | ||
| public void testCachesTableItemsAcrossMultipleRequests() { | ||
| DynamoDbClient client = mock(DynamoDbClient.class); | ||
| ScanResponse response = ScanResponse.builder() | ||
| .items(Collections.singletonList(player("Argentina", "Lionel Messi", 10))) | ||
| .build(); | ||
| when(client.scan(any(ScanRequest.class))).thenReturn(response); | ||
|
|
||
| DynamoDbTableItemsCache cache = new DynamoDbTableItemsCache(accessor, client); | ||
|
|
||
| List<Map<String, Object>> first = cache.getItems(TABLE); | ||
| List<Map<String, Object>> second = cache.getItems(TABLE); | ||
|
|
||
| assertEquals(1, first.size()); | ||
| assertEquals("Lionel Messi", first.get(0).get("playerName")); | ||
| assertEquals(first, second); | ||
| verify(client, times(1)).scan(any(ScanRequest.class)); | ||
| } | ||
|
|
||
| /** | ||
| * Verifies that whitespace-padded table names are trimmed and resolve to the same cache entry. | ||
| */ | ||
| @Test | ||
| public void testTrimsTableNameAndReusesCachedItems() { | ||
| DynamoDbClient client = mock(DynamoDbClient.class); | ||
| ScanResponse response = ScanResponse.builder() | ||
| .items(Collections.singletonList(player("Argentina", "Diego Maradona", 10))) | ||
| .build(); | ||
| when(client.scan(any(ScanRequest.class))).thenReturn(response); | ||
|
|
||
| DynamoDbTableItemsCache cache = new DynamoDbTableItemsCache(accessor, client); | ||
|
|
||
| List<Map<String, Object>> direct = cache.getItems(TABLE); | ||
| List<Map<String, Object>> padded = cache.getItems(" " + TABLE + " "); | ||
|
|
||
| assertEquals(direct, padded); | ||
| verify(client, times(1)).scan(any(ScanRequest.class)); | ||
| } | ||
|
|
||
| /** | ||
| * Verifies that null and blank table names are rejected. | ||
| */ | ||
| @Test | ||
| public void testRejectsNullOrBlankTableName() { | ||
| DynamoDbTableItemsCache cache = new DynamoDbTableItemsCache(accessor, mock(DynamoDbClient.class)); | ||
|
|
||
| NullPointerException nullTable = assertThrows(NullPointerException.class, () -> cache.getItems(null)); | ||
| assertEquals("DynamoDB table name cannot be null", nullTable.getMessage()); | ||
|
|
||
| IllegalArgumentException emptyTable = assertThrows(IllegalArgumentException.class, () -> cache.getItems("")); | ||
| assertEquals("DynamoDB table name cannot be blank", emptyTable.getMessage()); | ||
|
|
||
| IllegalArgumentException blankTable = assertThrows(IllegalArgumentException.class, () -> cache.getItems(" ")); | ||
| assertEquals("DynamoDB table name cannot be blank", blankTable.getMessage()); | ||
| } | ||
|
|
||
| /** | ||
| * Verifies that null accessor arguments in constructor are rejected. | ||
| */ | ||
| @Test | ||
| public void testRejectsNullConstructorArguments() { | ||
| NullPointerException exception = assertThrows(NullPointerException.class, | ||
| () -> new DynamoDbTableItemsCache(null, new Object())); | ||
| assertEquals("DynamoDB table data accessor cannot be null", exception.getMessage()); | ||
| } | ||
|
|
||
| /** | ||
| * Verifies that cached item lists and maps cannot be mutated by callers. | ||
| */ | ||
| @Test | ||
| public void testReturnsUnmodifiableListAndMaps() { | ||
| DynamoDbClient client = mock(DynamoDbClient.class); | ||
| ScanResponse response = ScanResponse.builder() | ||
| .items(Collections.singletonList(player("Croatia", "Luka Modric", 10))) | ||
| .build(); | ||
| when(client.scan(any(ScanRequest.class))).thenReturn(response); | ||
|
|
||
| DynamoDbTableItemsCache cache = new DynamoDbTableItemsCache(accessor, client); | ||
| List<Map<String, Object>> items = cache.getItems(TABLE); | ||
|
|
||
| Map<String, Object> newItem = new HashMap<>(); | ||
| newItem.put("country", "France"); | ||
| newItem.put("playerName", "Kylian Mbappe"); | ||
| assertThrows(UnsupportedOperationException.class, () -> items.add(newItem)); | ||
| assertThrows(UnsupportedOperationException.class, () -> items.get(0).put("goals", 5)); | ||
| } | ||
|
|
||
| /** | ||
| * Creates one deterministic World Cup player item representation. | ||
| * | ||
| * @param country country name | ||
| * @param playerName player name | ||
| * @param squadNumber player squad number | ||
| * @return map representing player attributes as AttributeValues | ||
| */ | ||
| private Map<String, AttributeValue> player(String country, String playerName, int squadNumber) { | ||
| Map<String, AttributeValue> item = new HashMap<>(); | ||
| item.put("country", AttributeValue.builder().s(country).build()); | ||
| item.put("playerName", AttributeValue.builder().s(playerName).build()); | ||
| item.put("squadNumber", AttributeValue.builder().n(Integer.toString(squadNumber)).build()); | ||
| return item; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.