Skip to content

Commit daaabc2

Browse files
committed
ets: rename internal lookup function
Signed-off-by: Jakub Gonet <jakub.gonet@swmansion.com>
1 parent c804e42 commit daaabc2

File tree

4 files changed

+103
-10
lines changed

4 files changed

+103
-10
lines changed

src/libAtomVM/ets.c

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -247,22 +247,36 @@ static void ets_delete_all_tables(struct Ets *ets, GlobalContext *global)
247247
ets_delete_tables_internal(ets, true_pred, NULL, global);
248248
}
249249

250+
static inline bool has_key_at(term tuple, size_t key_index)
251+
{
252+
return key_index < (size_t) term_get_tuple_arity(tuple);
253+
}
254+
250255
static EtsErrorCode insert(struct EtsTable *ets_table, term tuple, Context *ctx)
251256
{
252257
assert(term_is_tuple(tuple));
253258
size_t key_index = ets_table->key_index;
254-
if ((size_t) term_get_tuple_arity(tuple) <= key_index) {
259+
if (UNLIKELY(!has_key_at(tuple, key_index))) {
255260
return EtsBadEntry;
256261
}
257262

258-
struct HNode *new_node = ets_hashtable_new_node(tuple, key_index);
259-
if (IS_NULL_PTR(new_node)) {
263+
bool is_duplicate_bag = ets_table->table_type == EtsTableDuplicateBag;
264+
struct HNode *node = NULL;
265+
if (is_duplicate_bag) {
266+
term key = term_get_tuple_element(tuple, key_index);
267+
term old_tuples = ets_hashtable_lookup(ets_table->hashtable, key, key_index, ctx->global);
268+
bool is_new = term_is_nil(old_tuples);
269+
node = ets_hashtable_new_node_from_list(is_new ? term_nil() : old_tuples, tuple, key_index);
270+
} else {
271+
node = ets_hashtable_new_node(tuple, key_index);
272+
}
273+
if (IS_NULL_PTR(node)) {
260274
return EtsAllocationFailure;
261275
}
262276

263-
EtsHashtableStatus res = ets_hashtable_insert(ets_table->hashtable, new_node, EtsHashtableAllowOverwrite, ctx->global);
277+
EtsHashtableStatus res = ets_hashtable_insert(ets_table->hashtable, node, EtsHashtableAllowOverwrite, ctx->global);
264278
if (UNLIKELY(res != EtsHashtableOk)) {
265-
ets_hashtable_free_node(new_node, ctx->global);
279+
ets_hashtable_free_node(node, ctx->global);
266280
return EtsAllocationFailure;
267281
}
268282

@@ -336,7 +350,7 @@ EtsErrorCode ets_insert(term name_or_ref, term entry, Context *ctx)
336350
return result;
337351
}
338352

339-
static EtsErrorCode ets_table_lookup_maybe_gc(struct EtsTable *ets_table, term key, term *ret, Context *ctx, int num_roots, term *roots)
353+
static EtsErrorCode lookup_maybe_gc(struct EtsTable *ets_table, term key, term *ret, Context *ctx, int num_roots, term *roots)
340354
{
341355
term res = ets_hashtable_lookup(ets_table->hashtable, key, ets_table->key_index, ctx->global);
342356

@@ -363,7 +377,7 @@ EtsErrorCode ets_lookup_maybe_gc(term name_or_ref, term key, term *ret, Context
363377
return EtsBadAccess;
364378
}
365379

366-
EtsErrorCode result = ets_table_lookup_maybe_gc(ets_table, key, ret, ctx, 0, NULL);
380+
EtsErrorCode result = lookup_maybe_gc(ets_table, key, ret, ctx, 0, NULL);
367381
SMP_UNLOCK(ets_table);
368382

369383
return result;
@@ -490,7 +504,7 @@ EtsErrorCode ets_update_counter_maybe_gc(term ref, term key, term operation, ter
490504
term roots[] = { key, operation, safe_default_value };
491505

492506
term list;
493-
EtsErrorCode result = ets_table_lookup_maybe_gc(ets_table, key, &list, ctx, 3, roots);
507+
EtsErrorCode result = lookup_maybe_gc(ets_table, key, &list, ctx, 3, roots);
494508
if (UNLIKELY(result != EtsOk)) {
495509
SMP_UNLOCK(ets_table);
496510
return result;

src/libAtomVM/ets_hashtable.c

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
#include "ets_hashtable.h"
2222

23+
#include "memory.h"
2324
#include "smp.h"
2425
#include "term.h"
2526
#include "utils.h"
@@ -89,6 +90,8 @@ static void print_info(struct EtsHashTable *hash_table)
8990

9091
struct HNode *ets_hashtable_new_node(term tuple, int key_index)
9192
{
93+
assert(term_is_tuple(tuple));
94+
assert(term_get_tuple_arity(tuple) >= key_index);
9295
struct HNode *new_node = malloc(sizeof(struct HNode));
9396
if (IS_NULL_PTR(new_node)) {
9497
goto cleanup;
@@ -100,8 +103,6 @@ struct HNode *ets_hashtable_new_node(term tuple, int key_index)
100103
}
101104

102105
term new_entry = memory_copy_term_tree(&new_node->heap, tuple);
103-
assert(term_is_tuple(new_entry));
104-
assert(term_get_tuple_arity(new_entry) >= key_index);
105106
term key = term_get_tuple_element(new_entry, key_index);
106107

107108
new_node->next = NULL;
@@ -115,6 +116,39 @@ struct HNode *ets_hashtable_new_node(term tuple, int key_index)
115116
return NULL;
116117
}
117118

119+
// TODO: create list elsewhere, by copying terms from orig heap, appending new copied tuple and using ets_hashtable_new_node
120+
struct HNode *ets_hashtable_new_node_from_list(term old_tuples, term tuple, size_t key_index)
121+
{
122+
assert(term_is_tuple(tuple));
123+
assert((size_t) term_get_tuple_arity(tuple) >= key_index);
124+
assert(term_is_list(old_tuples));
125+
126+
struct HNode *new_node = malloc(sizeof(struct HNode));
127+
if (IS_NULL_PTR(new_node)) {
128+
goto oom;
129+
}
130+
131+
size_t old_list_size = memory_estimate_usage(old_tuples);
132+
size_t new_tuple_size = memory_estimate_usage(tuple);
133+
if (UNLIKELY(memory_init_heap(&new_node->heap, old_list_size + new_tuple_size + CONS_SIZE) != MEMORY_GC_OK)) {
134+
goto oom;
135+
}
136+
term ets_list = memory_copy_term_tree(&new_node->heap, old_tuples);
137+
term ets_tuple = memory_copy_term_tree(&new_node->heap, tuple);
138+
139+
term new_key = term_get_tuple_element(ets_tuple, key_index);
140+
ets_list = term_list_prepend(ets_tuple, ets_list, &new_node->heap);
141+
142+
new_node->next = NULL;
143+
new_node->key = new_key;
144+
new_node->entry = ets_list;
145+
return new_node;
146+
147+
oom:
148+
free(new_node);
149+
return NULL;
150+
}
151+
118152
EtsHashtableStatus ets_hashtable_insert(struct EtsHashTable *hash_table, struct HNode *new_node, EtsHashtableOptions opts, GlobalContext *global)
119153
{
120154
term key = new_node->key;

src/libAtomVM/ets_hashtable.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ struct EtsHashTable *ets_hashtable_new();
5353
void ets_hashtable_destroy(struct EtsHashTable *hash_table, GlobalContext *global);
5454

5555
struct HNode *ets_hashtable_new_node(term entry, int key_index);
56+
struct HNode *ets_hashtable_new_node_from_list(term old_tuples_or_tuple, term new_tuple, size_t key_index);
5657
void ets_hashtable_free_node(struct HNode *node, GlobalContext *global);
5758

5859
EtsHashtableStatus ets_hashtable_insert(struct EtsHashTable *hash_table, struct HNode *new_node, EtsHashtableOptions opts, GlobalContext *global);

tests/erlang_tests/test_ets.erl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ start() ->
3131
ok = isolated(fun test_delete/0),
3232
ok = isolated(fun test_lookup_element/0),
3333
ok = isolated(fun test_update_counter/0),
34+
ok = isolated(fun test_duplicate_bag/0),
3435
0.
3536

3637
test_ets_new() ->
@@ -260,6 +261,49 @@ test_update_counter() ->
260261
assert_badarg(fun() -> ets:update_counter(TErr, key, {2, 10, 100, not_number}) end),
261262
ok.
262263

264+
test_duplicate_bag() ->
265+
Tid = ets:new(test_duplicate_bag, [duplicate_bag, {keypos, 2}]),
266+
T = {ok, foo, 100, extra},
267+
T2 = {error, foo, 200},
268+
T3 = {error, foo, 300},
269+
270+
% true = ets:insert_new(Tid, T),
271+
% false = ets:insert_new(Tid, T),
272+
true = ets:insert(Tid, T),
273+
true = ets:insert(Tid, [T, T]),
274+
true = ets:insert(Tid, [T2]),
275+
% true = [T, T, T, T2] == ets:lookup(Tid, foo),
276+
% true = [T, T, T, T, T2] == ets:lookup(Tid, foo),
277+
% true = ets:member(Tid, foo),
278+
279+
% % nothing inserted, T exists in table
280+
% false = ets:insert_new(Tid, [T, {ok, bar, batat}]),
281+
% false = ets:member(Tid, bar),
282+
283+
% [ok, ok, ok, ok, error] = ets:lookup_element(Tid, foo, 1),
284+
% [foo, foo, foo, foo, foo] = ets:lookup_element(Tid, foo, 2),
285+
% [100, 100, 100, 100, 200] = ets:lookup_element(Tid, foo, 3),
286+
% % some tuples don't have 4 arity
287+
% ok = expect_failure(fun() -> ets:lookup_element(Tid, foo, 4) end),
288+
289+
% % unsupported for duplicate bag
290+
% ok = expect_failure(fun() -> ets:update_counter(Tid, foo, 10) end),
291+
% ok = expect_failure(fun() -> ets:update_element(Tid, foo, {1, error}) end),
292+
293+
% true = ets:delete_object(Tid, {bad, bad}),
294+
% true = [T, T, T, T, T2] == ets:lookup(Tid, foo),
295+
% true = ets:delete_object(Tid, T),
296+
% true = [T2] == ets:lookup(Tid, foo),
297+
298+
% true = ets:insert(Tid, T3),
299+
% % keeps insertion order
300+
% true = [T2, T3] == ets:take(Tid, foo),
301+
302+
% true = ets:delete(Tid),
303+
% ok = expect_failure(fun() -> ets:insert(Tid, T) end),
304+
305+
ok.
306+
263307
%%-----------------------------------------------------------------------------
264308
%% @doc Performs specified operation on ETS table implicitly asserting that no exception is raised.
265309
%% [badarg] can be passed as an option to assert that exception was raised.

0 commit comments

Comments
 (0)