Skip to content

Commit 1bb42dc

Browse files
authored
Add a aws_hash_table_put helper (awslabs#134)
* Add a aws_hash_table_put helper This makes the common use case of inserting a key/value pair with overwrite easier (removes the need to declare an element local). * Handle null destructors in hash_table_put * Fix clang-format error
1 parent bb12423 commit 1bb42dc

File tree

4 files changed

+158
-6
lines changed

4 files changed

+158
-6
lines changed

include/aws/common/hash_table.h

+14
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,20 @@ int aws_hash_table_create(
217217
struct aws_hash_element **p_elem,
218218
int *was_created);
219219

220+
/**
221+
* Inserts a new element at key, with the given value. If another element
222+
* exists at that key, the old element will be overwritten; both old key and
223+
* value objects will be destroyed.
224+
*
225+
* If was_created is non-NULL, *was_created is set to 0 if an existing
226+
* element was found, or 1 is a new element was created.
227+
*
228+
* Returns AWS_OP_SUCCESS if an item was found or created.
229+
* Raises AWS_ERROR_OOM if hash table expansion was required and memory
230+
*/
231+
AWS_COMMON_API
232+
int aws_hash_table_put(struct aws_hash_table *map, const void *key, void *value, int *was_created);
233+
220234
/**
221235
* Removes element at key. Always returns AWS_OP_SUCCESS.
222236
*

source/hash_table.c

+35
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,41 @@ int aws_hash_table_create(
505505
return AWS_OP_SUCCESS;
506506
}
507507

508+
AWS_COMMON_API
509+
int aws_hash_table_put(struct aws_hash_table *map, const void *key, void *value, int *was_created) {
510+
struct aws_hash_element *p_elem;
511+
int was_created_fallback;
512+
513+
if (!was_created) {
514+
was_created = &was_created_fallback;
515+
}
516+
517+
if (aws_hash_table_create(map, key, &p_elem, was_created)) {
518+
return AWS_OP_ERR;
519+
}
520+
521+
/*
522+
* aws_hash_table_create might resize the table, which results in map->p_impl changing.
523+
* It is therefore important to wait to read p_impl until after we return.
524+
*/
525+
struct hash_table_state *state = map->p_impl;
526+
527+
if (!*was_created) {
528+
if (p_elem->key != key && state->destroy_key_fn) {
529+
state->destroy_key_fn((void *)p_elem->key);
530+
}
531+
532+
if (state->destroy_value_fn) {
533+
state->destroy_value_fn((void *)p_elem->value);
534+
}
535+
}
536+
537+
p_elem->key = key;
538+
p_elem->value = value;
539+
540+
return AWS_OP_SUCCESS;
541+
}
542+
508543
/* Clears an entry. Does _not_ invoke destructor callbacks.
509544
* Returns the last slot touched (note that if we wrap, we'll report an index
510545
* lower than the original entry's index)

tests/CMakeLists.txt

+4-2
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,10 @@ add_test_case(scheduler_pops_task_late_test)
119119
add_test_case(scheduler_task_timestamp_test)
120120
add_test_case(scheduler_reentrant_safe)
121121

122-
add_test_case(test_hash_table_put_get)
123-
add_test_case(test_hash_table_string_put_get)
122+
add_test_case(test_hash_table_create_find)
123+
add_test_case(test_hash_table_string_create_find)
124+
add_test_case(test_hash_table_put)
125+
add_test_case(test_hash_table_put_null_dtor)
124126
add_test_case(test_hash_table_string_clean_up)
125127
add_test_case(test_hash_table_hash_collision)
126128
add_test_case(test_hash_table_hash_overwrite)

tests/hash_table_test.c

+105-4
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,29 @@ static const char *TEST_VAL_STR_2 = "value 2";
2929
#define ASSERT_HASH_TABLE_ENTRY_COUNT(map, count) \
3030
ASSERT_UINT_EQUALS(count, aws_hash_table_get_entry_count(map), "Hash map should have %d entries", count)
3131

32-
AWS_TEST_CASE(test_hash_table_put_get, s_test_hash_table_put_get_fn)
33-
static int s_test_hash_table_put_get_fn(struct aws_allocator *allocator, void *ctx) {
32+
#define ASSERT_NO_KEY(hash_table, key) \
33+
do { \
34+
AWS_STATIC_STRING_FROM_LITERAL(assert_key, key); \
35+
struct aws_hash_element *pElem_assert; \
36+
ASSERT_SUCCESS(aws_hash_table_find((hash_table), (void *)assert_key, &pElem_assert)); \
37+
ASSERT_NULL(pElem_assert, "Expected key to not be present: " key); \
38+
} while (0)
39+
40+
#define ASSERT_KEY_VALUE(hash_table, key, expected) \
41+
do { \
42+
AWS_STATIC_STRING_FROM_LITERAL(assert_key, key); \
43+
AWS_STATIC_STRING_FROM_LITERAL(assert_value, expected); \
44+
struct aws_hash_element *pElem_assert; \
45+
ASSERT_SUCCESS(aws_hash_table_find((hash_table), (void *)assert_key, &pElem_assert)); \
46+
ASSERT_NOT_NULL(pElem_assert, "Expected key to be present: " key); \
47+
ASSERT_TRUE( \
48+
aws_string_eq(assert_value, (const struct aws_string *)pElem_assert->value), \
49+
"Expected key \"" key "\" to have value \"" expected "\"; actually had value \"%s\"", \
50+
aws_string_bytes((const struct aws_string *)pElem_assert->value)); \
51+
} while (0)
52+
53+
AWS_TEST_CASE(test_hash_table_create_find, s_test_hash_table_create_find_fn)
54+
static int s_test_hash_table_create_find_fn(struct aws_allocator *allocator, void *ctx) {
3455

3556
(void)ctx;
3657

@@ -81,8 +102,8 @@ static int s_test_hash_table_put_get_fn(struct aws_allocator *allocator, void *c
81102
return 0;
82103
}
83104

84-
AWS_TEST_CASE(test_hash_table_string_put_get, s_test_hash_table_string_put_get_fn)
85-
static int s_test_hash_table_string_put_get_fn(struct aws_allocator *allocator, void *ctx) {
105+
AWS_TEST_CASE(test_hash_table_string_create_find, s_test_hash_table_string_create_find_fn)
106+
static int s_test_hash_table_string_create_find_fn(struct aws_allocator *allocator, void *ctx) {
86107
(void)ctx;
87108

88109
struct aws_hash_table hash_table;
@@ -189,6 +210,86 @@ static int s_test_hash_table_string_put_get_fn(struct aws_allocator *allocator,
189210
return 0;
190211
}
191212

213+
static const void *last_key, *last_value;
214+
215+
static void destroy_key_record(void *key) {
216+
last_key = key;
217+
}
218+
219+
static void destroy_value_record(void *value) {
220+
last_value = value;
221+
}
222+
223+
AWS_TEST_CASE(test_hash_table_put, s_test_hash_table_put_fn)
224+
static int s_test_hash_table_put_fn(struct aws_allocator *allocator, void *ctx) {
225+
(void)ctx;
226+
227+
struct aws_hash_table hash_table;
228+
struct aws_hash_element *pElem;
229+
int was_created;
230+
231+
int ret = aws_hash_table_init(
232+
&hash_table, allocator, 10, aws_hash_string, aws_string_eq, destroy_key_record, destroy_value_record);
233+
ASSERT_SUCCESS(ret, "Hash Map init should have succeeded.");
234+
235+
AWS_STATIC_STRING_FROM_LITERAL(sentinel, "");
236+
AWS_STATIC_STRING_FROM_LITERAL(key_a_1, "a");
237+
AWS_STATIC_STRING_FROM_LITERAL(value_b_1, "b");
238+
239+
ASSERT_NO_KEY(&hash_table, "a");
240+
last_key = last_value = sentinel;
241+
aws_hash_table_put(&hash_table, key_a_1, (void *)value_b_1, &was_created);
242+
ASSERT_INT_EQUALS(was_created, 1);
243+
ASSERT_KEY_VALUE(&hash_table, "a", "b");
244+
/* dtors were not called, even with nulls */
245+
ASSERT_PTR_EQUALS(last_key, sentinel);
246+
ASSERT_PTR_EQUALS(last_value, sentinel);
247+
248+
AWS_STATIC_STRING_FROM_LITERAL(key_a_2, "a");
249+
AWS_STATIC_STRING_FROM_LITERAL(value_c_1, "c");
250+
251+
last_key = last_value = NULL;
252+
aws_hash_table_put(&hash_table, key_a_2, (void *)value_c_1, &was_created);
253+
ASSERT_INT_EQUALS(was_created, 0);
254+
ASSERT_KEY_VALUE(&hash_table, "a", "c");
255+
256+
ASSERT_SUCCESS(aws_hash_table_find(&hash_table, (void *)key_a_1, &pElem));
257+
ASSERT_PTR_EQUALS(key_a_2, pElem->key);
258+
/* verify dtor was called on the old key ptr */
259+
ASSERT_PTR_EQUALS(last_key, key_a_1);
260+
ASSERT_PTR_EQUALS(last_value, value_b_1);
261+
262+
last_key = last_value = NULL;
263+
aws_hash_table_put(&hash_table, key_a_2, (void *)value_b_1, NULL);
264+
ASSERT_KEY_VALUE(&hash_table, "a", "b");
265+
266+
/* Since the key ptr did not change, it was not destroyed */
267+
ASSERT_PTR_EQUALS(last_key, NULL);
268+
/* The value was destroyed however */
269+
ASSERT_PTR_EQUALS(last_value, value_c_1);
270+
271+
aws_hash_table_clean_up(&hash_table);
272+
273+
return 0;
274+
}
275+
AWS_TEST_CASE(test_hash_table_put_null_dtor, s_test_hash_table_put_null_dtor_fn)
276+
static int s_test_hash_table_put_null_dtor_fn(struct aws_allocator *allocator, void *ctx) {
277+
(void)ctx;
278+
279+
struct aws_hash_table hash_table;
280+
281+
int ret = aws_hash_table_init(&hash_table, allocator, 10, aws_hash_string, aws_string_eq, NULL, NULL);
282+
ASSERT_SUCCESS(ret, "Hash Map init should have succeeded.");
283+
284+
AWS_STATIC_STRING_FROM_LITERAL(foo, "foo");
285+
ASSERT_SUCCESS(aws_hash_table_put(&hash_table, foo, (void *)foo, NULL));
286+
ASSERT_SUCCESS(aws_hash_table_put(&hash_table, foo, (void *)foo, NULL));
287+
288+
aws_hash_table_clean_up(&hash_table);
289+
290+
return 0;
291+
}
292+
192293
AWS_TEST_CASE(test_hash_table_string_clean_up, s_test_hash_table_string_clean_up_fn)
193294
static int s_test_hash_table_string_clean_up_fn(struct aws_allocator *allocator, void *ctx) {
194295

0 commit comments

Comments
 (0)