File 5341-add-ets-first-next-last-prev_lookup.patch of Package erlang
From 85fa2065d7320601f1c7b07f90e21113da83aac5 Mon Sep 17 00:00:00 2001
From: "Anshul Mittal (WhatsApp)" <mittalanshul@meta.com>
Date: Fri, 1 Dec 2023 12:52:37 -0800
Subject: [PATCH 1/2] add ets:first/next/last/prev_lookup
---
erts/emulator/beam/bif.tab | 4 +
erts/emulator/beam/erl_db.c | 93 +++++++++++++
erts/emulator/beam/erl_db_catree.c | 71 ++++++++--
erts/emulator/beam/erl_db_hash.c | 70 +++++++++-
erts/emulator/beam/erl_db_tree.c | 103 ++++++++++++--
erts/emulator/beam/erl_db_tree_util.h | 15 ++-
erts/emulator/beam/erl_db_util.h | 15 +++
lib/stdlib/doc/src/ets.xml | 68 +++++++++-
lib/stdlib/src/erl_stdlib_errors.erl | 4 +
lib/stdlib/src/ets.erl | 40 +++++-
lib/stdlib/test/Makefile | 1 +
lib/stdlib/test/ets_SUITE.erl | 141 ++++++++++++--------
lib/stdlib/test/ets_property_test_SUITE.erl | 55 ++++++++
lib/stdlib/test/property_test/ets_prop.erl | 108 +++++++++++++++
14 files changed, 699 insertions(+), 89 deletions(-)
create mode 100644 lib/stdlib/test/ets_property_test_SUITE.erl
create mode 100644 lib/stdlib/test/property_test/ets_prop.erl
diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab
index 614e8357c8..a12068c1f8 100644
--- a/erts/emulator/beam/bif.tab
+++ b/erts/emulator/beam/bif.tab
@@ -357,6 +357,7 @@ bif ets:delete/1
bif ets:delete/2
bif ets:delete_object/2
bif ets:first/1
+bif ets:first_lookup/1
bif ets:is_compiled_ms/1
bif ets:lookup/2
bif ets:lookup_element/3
@@ -364,6 +365,7 @@ bif ets:lookup_element/4
bif ets:info/1
bif ets:info/2
bif ets:last/1
+bif ets:last_lookup/1
bif ets:match/1
bif ets:match/2
bif ets:match/3
@@ -372,7 +374,9 @@ bif ets:match_object/2
bif ets:match_object/3
bif ets:member/2
bif ets:next/2
+bif ets:next_lookup/2
bif ets:prev/2
+bif ets:prev_lookup/2
bif ets:insert/2
bif ets:insert_new/2
bif ets:rename/2
diff --git a/erts/emulator/beam/erl_db.c b/erts/emulator/beam/erl_db.c
index 8f5e1a9543..22cc537775 100644
--- a/erts/emulator/beam/erl_db.c
+++ b/erts/emulator/beam/erl_db.c
@@ -1115,6 +1115,29 @@ BIF_RETTYPE ets_first_1(BIF_ALIST_1)
BIF_RET(ret);
}
+/*
+** Returns the first {key, object(s)} in a table
+*/
+BIF_RETTYPE ets_first_lookup_1(BIF_ALIST_1)
+{
+ DbTable* tb;
+ int cret;
+ Eterm ret;
+
+ CHECK_TABLES();
+
+ DB_BIF_GET_TABLE(tb, DB_READ, LCK_READ, BIF_ets_first_lookup_1);
+
+ cret = tb->common.meth->db_first_lookup(BIF_P, tb, &ret);
+
+ db_unlock(tb, LCK_READ);
+
+ if (cret != DB_ERROR_NONE) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
+ BIF_RET(ret);
+}
+
/*
** The next BIF, given a key, return the "next" key
*/
@@ -1138,6 +1161,30 @@ BIF_RETTYPE ets_next_2(BIF_ALIST_2)
BIF_RET(ret);
}
+
+/*
+** The next_lookup BIF, given a key, return the "next" {key, object(s)}
+*/
+BIF_RETTYPE ets_next_lookup_2(BIF_ALIST_2)
+{
+ DbTable* tb;
+ int cret;
+ Eterm ret;
+
+ CHECK_TABLES();
+
+ DB_BIF_GET_TABLE(tb, DB_READ, LCK_READ, BIF_ets_next_lookup_2);
+
+ cret = tb->common.meth->db_next_lookup(BIF_P, tb, BIF_ARG_2, &ret);
+
+ db_unlock(tb, LCK_READ);
+
+ if (cret != DB_ERROR_NONE) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
+ BIF_RET(ret);
+}
+
/*
** Returns the last Key in a table
*/
@@ -1161,6 +1208,29 @@ BIF_RETTYPE ets_last_1(BIF_ALIST_1)
BIF_RET(ret);
}
+/*
+** Returns the last {key, object(s)} in a table
+*/
+BIF_RETTYPE ets_last_lookup_1(BIF_ALIST_1)
+{
+ DbTable* tb;
+ int cret;
+ Eterm ret;
+
+ CHECK_TABLES();
+
+ DB_BIF_GET_TABLE(tb, DB_READ, LCK_READ, BIF_ets_last_lookup_1);
+
+ cret = tb->common.meth->db_last_lookup(BIF_P, tb, &ret);
+
+ db_unlock(tb, LCK_READ);
+
+ if (cret != DB_ERROR_NONE) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
+ BIF_RET(ret);
+}
+
/*
** The prev BIF, given a key, return the "previous" key
*/
@@ -1184,6 +1254,29 @@ BIF_RETTYPE ets_prev_2(BIF_ALIST_2)
BIF_RET(ret);
}
+/*
+** The prev_lookup BIF, given a key, return the "previous" {key, object(s)}
+*/
+BIF_RETTYPE ets_prev_lookup_2(BIF_ALIST_2)
+{
+ DbTable* tb;
+ int cret;
+ Eterm ret;
+
+ CHECK_TABLES();
+
+ DB_BIF_GET_TABLE(tb, DB_READ, LCK_READ, BIF_ets_prev_lookup_2);
+
+ cret = tb->common.meth->db_prev_lookup(BIF_P, tb, BIF_ARG_2, &ret);
+
+ db_unlock(tb, LCK_READ);
+
+ if (cret != DB_ERROR_NONE) {
+ BIF_ERROR(BIF_P, BADARG);
+ }
+ BIF_RET(ret);
+}
+
/*
** take(Tab, Key)
*/
diff --git a/erts/emulator/beam/erl_db_catree.c b/erts/emulator/beam/erl_db_catree.c
index 700003438c..e441faf0bd 100644
--- a/erts/emulator/beam/erl_db_catree.c
+++ b/erts/emulator/beam/erl_db_catree.c
@@ -98,13 +98,22 @@ static SWord do_delete_base_node_cont(DbTableCATree *tb,
/* Method interface functions */
static int db_first_catree(Process *p, DbTable *tbl,
Eterm *ret);
+static int db_first_lookup_catree(Process *p, DbTable *tbl,
+ Eterm *ret);
static int db_next_catree(Process *p, DbTable *tbl,
Eterm key, Eterm *ret);
+static int db_next_lookup_catree(Process *p, DbTable *tbl,
+ Eterm key, Eterm *ret);
static int db_last_catree(Process *p, DbTable *tbl,
Eterm *ret);
+static int db_last_lookup_catree(Process *p, DbTable *tbl,
+ Eterm *ret);
static int db_prev_catree(Process *p, DbTable *tbl,
Eterm key,
Eterm *ret);
+static int db_prev_lookup_catree(Process *p, DbTable *tbl,
+ Eterm key,
+ Eterm *ret);
static int db_put_catree(DbTable *tbl, Eterm obj, int key_clash_fail,
SWord *consumed_reds_p);
static int db_get_catree(Process *p, DbTable *tbl,
@@ -227,7 +236,11 @@ DbTableMethod db_catree =
db_get_dbterm_key_tree_common,
db_get_binary_info_catree,
db_first_catree, /* raw_first same as first */
- db_next_catree /* raw_next same as next */
+ db_next_catree, /* raw_next same as next */
+ db_first_lookup_catree,
+ db_next_lookup_catree,
+ db_last_lookup_catree,
+ db_prev_lookup_catree
};
/*
@@ -1567,7 +1580,7 @@ int db_create_catree(Process *p, DbTable *tbl)
return DB_ERROR_NONE;
}
-static int db_first_catree(Process *p, DbTable *tbl, Eterm *ret)
+static int db_first_catree_common(Process *p, DbTable *tbl, Eterm *ret, Eterm (*func)(Process *, DbTable *, TreeDbTerm *))
{
TreeDbTerm *root;
CATreeRootIterator iter;
@@ -1580,13 +1593,23 @@ static int db_first_catree(Process *p, DbTable *tbl, Eterm *ret)
root = pp ? *pp : NULL;
}
- result = db_first_tree_common(p, tbl, root, ret, NULL);
+ result = db_first_tree_common(p, tbl, root, ret, NULL, func);
destroy_root_iterator(&iter);
return result;
}
-static int db_next_catree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+static int db_first_catree(Process *p, DbTable *tbl, Eterm *ret)
+{
+ return db_first_catree_common(p, tbl, ret, db_copy_key_tree);
+}
+
+static int db_first_lookup_catree(Process *p, DbTable *tbl, Eterm *ret)
+{
+ return db_first_catree_common(p, tbl, ret, db_copy_key_and_object_tree);
+}
+
+static int db_next_catree_common(Process *p, DbTable *tbl, Eterm key, Eterm *ret, Eterm (*func)(Process *, DbTable *, TreeDbTerm *))
{
DbTreeStack stack;
TreeDbTerm * stack_array[STACK_NEED];
@@ -1600,7 +1623,7 @@ static int db_next_catree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
do {
init_tree_stack(&stack, stack_array, 0);
- result = db_next_tree_common(p, tbl, (rootp ? *rootp : NULL), key, ret, &stack);
+ result = db_next_tree_common(p, tbl, (rootp ? *rootp : NULL), key, ret, &stack, func);
if (result != DB_ERROR_NONE || *ret != am_EOT)
break;
@@ -1611,7 +1634,17 @@ static int db_next_catree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
return result;
}
-static int db_last_catree(Process *p, DbTable *tbl, Eterm *ret)
+static int db_next_catree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+{
+ return db_next_catree_common(p, tbl, key, ret, db_copy_key_tree);
+}
+
+static int db_next_lookup_catree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+{
+ return db_next_catree_common(p, tbl, key, ret, db_copy_key_and_object_tree);
+}
+
+static int db_last_catree_common(Process *p, DbTable *tbl, Eterm *ret, Eterm (*func)(Process *, DbTable *, TreeDbTerm *))
{
TreeDbTerm *root;
CATreeRootIterator iter;
@@ -1624,13 +1657,23 @@ static int db_last_catree(Process *p, DbTable *tbl, Eterm *ret)
root = pp ? *pp : NULL;
}
- result = db_last_tree_common(p, tbl, root, ret, NULL);
+ result = db_last_tree_common(p, tbl, root, ret, NULL, func);
destroy_root_iterator(&iter);
return result;
}
-static int db_prev_catree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+static int db_last_catree(Process *p, DbTable *tbl, Eterm *ret)
+{
+ return db_last_catree_common(p, tbl, ret, db_copy_key_tree);
+}
+
+static int db_last_lookup_catree(Process *p, DbTable *tbl, Eterm *ret)
+{
+ return db_last_catree_common(p, tbl, ret, db_copy_key_and_object_tree);
+}
+
+static int db_prev_catree_common(Process *p, DbTable *tbl, Eterm key, Eterm *ret, Eterm (*func)(Process *, DbTable *, TreeDbTerm *))
{
DbTreeStack stack;
TreeDbTerm * stack_array[STACK_NEED];
@@ -1645,7 +1688,7 @@ static int db_prev_catree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
do {
init_tree_stack(&stack, stack_array, 0);
result = db_prev_tree_common(p, tbl, (rootp ? *rootp : NULL), key, ret,
- &stack);
+ &stack, func);
if (result != DB_ERROR_NONE || *ret != am_EOT)
break;
rootp = catree_find_prev_root(&iter, NULL);
@@ -1655,6 +1698,16 @@ static int db_prev_catree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
return result;
}
+static int db_prev_catree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+{
+ return db_prev_catree_common(p, tbl, key, ret, db_copy_key_tree);
+}
+
+static int db_prev_lookup_catree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+{
+ return db_prev_catree_common(p, tbl, key, ret, db_copy_key_and_object_tree);
+}
+
static int db_put_dbterm_catree(DbTable* tbl,
void* obj,
int key_clash_fail,
diff --git a/erts/emulator/beam/erl_db_hash.c b/erts/emulator/beam/erl_db_hash.c
index 05ee2d1be5..8997b521d4 100644
--- a/erts/emulator/beam/erl_db_hash.c
+++ b/erts/emulator/beam/erl_db_hash.c
@@ -643,6 +643,8 @@ static void shrink(DbTableHash* tb, int nitems);
static void grow(DbTableHash* tb, int nitems);
static Eterm build_term_list(Process* p, HashDbTerm* ptr1, HashDbTerm* ptr2,
Uint sz, DbTableHash*);
+static Eterm get_term_list(Process *p, DbTableHash *tb, Eterm key, HashValue hval,
+ HashDbTerm *b1, HashDbTerm **bend);
static int analyze_pattern(DbTableHash *tb, Eterm pattern,
ExtraMatchValidatorF*, /* Optional callback */
struct mp_info *mpi);
@@ -654,11 +656,20 @@ static int db_first_hash(Process *p,
DbTable *tbl,
Eterm *ret);
+static int db_first_lookup_hash(Process *p,
+ DbTable *tbl,
+ Eterm *ret);
+
static int db_next_hash(Process *p,
DbTable *tbl,
Eterm key,
Eterm *ret);
+static int db_next_lookup_hash(Process *p,
+ DbTable *tbl,
+ Eterm key,
+ Eterm *ret);
+
static int db_member_hash(DbTable *tbl, Eterm key, Eterm *ret);
static int db_get_element_hash(Process *p, DbTable *tbl,
@@ -873,7 +884,11 @@ DbTableMethod db_hash =
db_get_dbterm_key_hash,
db_get_binary_info_hash,
db_raw_first_hash,
- db_raw_next_hash
+ db_raw_next_hash,
+ db_first_lookup_hash,
+ db_next_lookup_hash,
+ db_first_lookup_hash, /* last == first */
+ db_next_lookup_hash /* prev == next */
};
#ifdef DEBUG
@@ -1072,7 +1087,32 @@ int db_create_hash(Process *p, DbTable *tbl)
return DB_ERROR_NONE;
}
-static int db_first_hash(Process *p, DbTable *tbl, Eterm *ret)
+static ERTS_INLINE Eterm db_copy_key_hash(Process* p, DbTable* tbl, HashDbTerm* b)
+{
+ Eterm key = GETKEY(&tbl->common, b->dbterm.tpl);
+ if IS_CONST(key) return key;
+ else {
+ Uint size = size_object(key);
+ Eterm* hp = HAlloc(p, size);
+ Eterm res = copy_struct(key, size, &hp, &MSO(p));
+ ASSERT(EQ(res,key));
+ return res;
+ }
+}
+
+static ERTS_INLINE Eterm db_copy_key_and_objects_hash(Process* p, DbTable* tbl, HashDbTerm* b) {
+ Eterm key = db_copy_key_hash(p, tbl, b);
+ HashValue hval = MAKE_HASH(key);
+ DbTableHash *tb = &tbl->hash;
+ Eterm objects = get_term_list(p, tb, key, hval, b, NULL);
+ Eterm *hp, res;
+ hp = HAlloc(p, 3);
+ res = TUPLE2(hp, key, objects);
+
+ return res;
+}
+
+static int db_first_hash_common(Process *p, DbTable *tbl, Eterm *ret, Eterm (*func)(Process *, DbTable *, HashDbTerm *))
{
DbTableHash *tb = &tbl->hash;
Uint ix = 0;
@@ -1083,7 +1123,7 @@ static int db_first_hash(Process *p, DbTable *tbl, Eterm *ret)
list = next_live(tb, &ix, &lck, list);
if (list != NULL) {
- *ret = db_copy_key(p, tbl, &list->dbterm);
+ *ret = (*func)(p, tbl, list);
RUNLOCK_HASH(lck);
}
else {
@@ -1092,8 +1132,17 @@ static int db_first_hash(Process *p, DbTable *tbl, Eterm *ret)
return DB_ERROR_NONE;
}
+static int db_first_hash(Process *p, DbTable *tbl, Eterm *ret)
+{
+ return db_first_hash_common(p, tbl, ret, db_copy_key_hash);
+}
-static int db_next_hash(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+static int db_first_lookup_hash(Process *p, DbTable *tbl, Eterm *ret)
+{
+ return db_first_hash_common(p, tbl, ret, db_copy_key_and_objects_hash);
+}
+
+static int db_next_hash_common(Process *p, DbTable *tbl, Eterm key, Eterm *ret, Eterm (*func)(Process *, DbTable *, HashDbTerm *))
{
DbTableHash *tb = &tbl->hash;
HashValue hval;
@@ -1132,12 +1181,23 @@ static int db_next_hash(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
}
else {
ASSERT(!is_pseudo_deleted(b));
- *ret = db_copy_key(p, tbl, &b->dbterm);
+ *ret = (*func)(p, tbl, b);
RUNLOCK_HASH(lck);
}
return DB_ERROR_NONE;
}
+static int db_next_hash(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+{
+ return db_next_hash_common(p, tbl, key, ret, db_copy_key_hash);
+}
+
+
+static int db_next_lookup_hash(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+{
+ return db_next_hash_common(p, tbl, key, ret, db_copy_key_and_objects_hash);
+}
+
struct tmp_uncomp_term {
Eterm term;
ErlOffHeap oh;
diff --git a/erts/emulator/beam/erl_db_tree.c b/erts/emulator/beam/erl_db_tree.c
index 31834d4131..d409ee9381 100644
--- a/erts/emulator/beam/erl_db_tree.c
+++ b/erts/emulator/beam/erl_db_tree.c
@@ -402,13 +402,22 @@ static BIF_RETTYPE ets_select_reverse(BIF_ALIST_3);
/* Method interface functions */
static int db_first_tree(Process *p, DbTable *tbl,
Eterm *ret);
+static int db_first_lookup_tree(Process *p, DbTable *tbl,
+ Eterm *ret);
static int db_next_tree(Process *p, DbTable *tbl,
Eterm key, Eterm *ret);
+static int db_next_lookup_tree(Process *p, DbTable *tbl,
+ Eterm key, Eterm *ret);
static int db_last_tree(Process *p, DbTable *tbl,
Eterm *ret);
+static int db_last_lookup_tree(Process *p, DbTable *tbl,
+ Eterm *ret);
static int db_prev_tree(Process *p, DbTable *tbl,
Eterm key,
Eterm *ret);
+static int db_prev_lookup_tree(Process *p, DbTable *tbl,
+ Eterm key,
+ Eterm *ret);
static int db_put_tree(DbTable *tbl, Eterm obj, int key_clash_fail, SWord *consumed_reds_p);
static int db_get_tree(Process *p, DbTable *tbl,
Eterm key, Eterm *ret);
@@ -526,7 +535,11 @@ DbTableMethod db_tree =
db_get_dbterm_key_tree_common,
db_get_binary_info_tree,
db_first_tree, /* raw_first same as first */
- db_next_tree /* raw_next same as next */
+ db_next_tree, /* raw_next same as next */
+ db_first_lookup_tree,
+ db_next_lookup_tree,
+ db_last_lookup_tree,
+ db_prev_lookup_tree
};
@@ -558,8 +571,40 @@ int db_create_tree(Process *p, DbTable *tbl)
return DB_ERROR_NONE;
}
+Eterm db_copy_key_tree(Process* p, DbTable* tbl, TreeDbTerm* node)
+{
+ Eterm key = GETKEY(&tbl->common, node->dbterm.tpl);
+ if IS_CONST(key) return key;
+ else {
+ Uint size = size_object(key);
+ Eterm* hp = HAlloc(p, size);
+ Eterm res = copy_struct(key, size, &hp, &MSO(p));
+ ASSERT(EQ(res,key));
+ return res;
+ }
+}
+
+Eterm db_copy_key_and_object_tree(Process* p, DbTable* tbl, TreeDbTerm* node) {
+ Eterm key = db_copy_key_tree(p, tbl, node);
+ Eterm *hp, *hend, copy, object, res;
+
+ // +2 for CONS and +3 for TUPLE2
+ int size = node->dbterm.size + 2 + 3;
+ hp = HAlloc(p, size);
+ hend = hp + size;
+ copy = db_copy_object_from_ets(&tbl->common, &node->dbterm, &hp, &MSO(p));
+ object = CONS(hp, copy, NIL);
+ hp += 2;
+ res = TUPLE2(hp, key, object);
+ hp += 3;
+ HRelease(p,hend,hp);
+
+ return res;
+}
+
int db_first_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root,
- Eterm *ret, DbTableTree *stack_container)
+ Eterm *ret, DbTableTree *stack_container,
+ Eterm (*func)(Process *, DbTable *, TreeDbTerm *))
{
DbTreeStack* stack;
TreeDbTerm *this;
@@ -581,19 +626,26 @@ int db_first_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root,
stack->slot = 1;
release_stack(tbl,stack_container,stack);
}
- *ret = db_copy_key(p, tbl, &this->dbterm);
+ *ret = (*func)(p, tbl, this);
return DB_ERROR_NONE;
}
static int db_first_tree(Process *p, DbTable *tbl, Eterm *ret)
{
DbTableTree *tb = &tbl->tree;
- return db_first_tree_common(p, tbl, tb->root, ret, tb);
+ return db_first_tree_common(p, tbl, tb->root, ret, tb, db_copy_key_tree);
+}
+
+static int db_first_lookup_tree(Process *p, DbTable *tbl, Eterm *ret)
+{
+ DbTableTree *tb = &tbl->tree;
+ return db_first_tree_common(p, tbl, tb->root, ret, tb, db_copy_key_and_object_tree);
}
int db_next_tree_common(Process *p, DbTable *tbl,
TreeDbTerm *root, Eterm key,
- Eterm *ret, DbTreeStack* stack)
+ Eterm *ret, DbTreeStack* stack,
+ Eterm (*func)(Process *, DbTable *, TreeDbTerm *))
{
TreeDbTerm *this;
@@ -604,7 +656,7 @@ int db_next_tree_common(Process *p, DbTable *tbl,
*ret = am_EOT;
return DB_ERROR_NONE;
}
- *ret = db_copy_key(p, tbl, &this->dbterm);
+ *ret = (*func)(p, tbl, this);
return DB_ERROR_NONE;
}
@@ -612,13 +664,23 @@ static int db_next_tree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
{
DbTableTree *tb = &tbl->tree;
DbTreeStack* stack = get_any_stack(tbl, tb);
- int ret_val = db_next_tree_common(p, tbl, tb->root, key, ret, stack);
+ int ret_val = db_next_tree_common(p, tbl, tb->root, key, ret, stack, db_copy_key_tree);
+ release_stack(tbl,tb,stack);
+ return ret_val;
+}
+
+static int db_next_lookup_tree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+{
+ DbTableTree *tb = &tbl->tree;
+ DbTreeStack* stack = get_any_stack(tbl, tb);
+ int ret_val = db_next_tree_common(p, tbl, tb->root, key, ret, stack, db_copy_key_and_object_tree);
release_stack(tbl,tb,stack);
return ret_val;
}
int db_last_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root,
- Eterm *ret, DbTableTree *stack_container)
+ Eterm *ret, DbTableTree *stack_container,
+ Eterm (*func)(Process *, DbTable *, TreeDbTerm *))
{
TreeDbTerm *this;
DbTreeStack* stack;
@@ -641,18 +703,24 @@ int db_last_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root,
stack->slot = NITEMS_CENTRALIZED(tbl);
release_stack(tbl,stack_container,stack);
}
- *ret = db_copy_key(p, tbl, &this->dbterm);
+ *ret = (*func)(p, tbl, this);
return DB_ERROR_NONE;
}
static int db_last_tree(Process *p, DbTable *tbl, Eterm *ret)
{
DbTableTree *tb = &tbl->tree;
- return db_last_tree_common(p, tbl, tb->root, ret, tb);
+ return db_last_tree_common(p, tbl, tb->root, ret, tb, db_copy_key_tree);
+}
+
+static int db_last_lookup_tree(Process *p, DbTable *tbl, Eterm *ret)
+{
+ DbTableTree *tb = &tbl->tree;
+ return db_last_tree_common(p, tbl, tb->root, ret, tb, db_copy_key_and_object_tree);
}
int db_prev_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root, Eterm key,
- Eterm *ret, DbTreeStack* stack)
+ Eterm *ret, DbTreeStack* stack, Eterm (*func)(Process *, DbTable *, TreeDbTerm *))
{
TreeDbTerm *this;
@@ -663,7 +731,7 @@ int db_prev_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root, Eterm key,
*ret = am_EOT;
return DB_ERROR_NONE;
}
- *ret = db_copy_key(p, tbl, &this->dbterm);
+ *ret = (*func)(p, tbl, this);
return DB_ERROR_NONE;
}
@@ -671,7 +739,16 @@ static int db_prev_tree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
{
DbTableTree *tb = &tbl->tree;
DbTreeStack* stack = get_any_stack(tbl, tb);
- int res = db_prev_tree_common(p, tbl, tb->root, key, ret, stack);
+ int res = db_prev_tree_common(p, tbl, tb->root, key, ret, stack, db_copy_key_tree);
+ release_stack(tbl,tb,stack);
+ return res;
+}
+
+static int db_prev_lookup_tree(Process *p, DbTable *tbl, Eterm key, Eterm *ret)
+{
+ DbTableTree *tb = &tbl->tree;
+ DbTreeStack* stack = get_any_stack(tbl, tb);
+ int res = db_prev_tree_common(p, tbl, tb->root, key, ret, stack, db_copy_key_and_object_tree);
release_stack(tbl,tb,stack);
return res;
}
diff --git a/erts/emulator/beam/erl_db_tree_util.h b/erts/emulator/beam/erl_db_tree_util.h
index 08d55e3373..4cb238298f 100644
--- a/erts/emulator/beam/erl_db_tree_util.h
+++ b/erts/emulator/beam/erl_db_tree_util.h
@@ -86,14 +86,18 @@ int tree_balance_left(TreeDbTerm **this);
int tree_balance_right(TreeDbTerm **this);
int db_first_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root,
- Eterm *ret, DbTableTree *stack_container);
+ Eterm *ret, DbTableTree *stack_container,
+ Eterm (*func)(Process *, DbTable *, TreeDbTerm *));
int db_next_tree_common(Process *p, DbTable *tbl,
TreeDbTerm *root, Eterm key,
- Eterm *ret, DbTreeStack* stack);
+ Eterm *ret, DbTreeStack* stack,
+ Eterm (*func)(Process *, DbTable *, TreeDbTerm *));
int db_last_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root,
- Eterm *ret, DbTableTree *stack_container);
+ Eterm *ret, DbTableTree *stack_container,
+ Eterm (*func)(Process *, DbTable *, TreeDbTerm *));
int db_prev_tree_common(Process *p, DbTable *tbl, TreeDbTerm *root, Eterm key,
- Eterm *ret, DbTreeStack* stack);
+ Eterm *ret, DbTreeStack* stack,
+ Eterm (*func)(Process *, DbTable *, TreeDbTerm *));
int db_put_tree_common(DbTableCommon *tb, TreeDbTerm **root, Eterm obj,
int key_clash_fail, DbTableTree *stack_container);
int db_get_tree_common(Process *p, DbTableCommon *tb, TreeDbTerm *root, Eterm key,
@@ -184,4 +188,7 @@ TreeDbTerm *db_find_tree_node_common(DbTableCommon*, TreeDbTerm *root,
Eterm key);
Eterm db_binary_info_tree_common(Process*, TreeDbTerm*);
+Eterm db_copy_key_tree(Process* p, DbTable* tbl, TreeDbTerm* node);
+Eterm db_copy_key_and_object_tree(Process* p, DbTable* tbl, TreeDbTerm* node);
+
#endif /* _DB_TREE_UTIL_H */
diff --git a/erts/emulator/beam/erl_db_util.h b/erts/emulator/beam/erl_db_util.h
index 35e320482c..62fb6e742e 100644
--- a/erts/emulator/beam/erl_db_util.h
+++ b/erts/emulator/beam/erl_db_util.h
@@ -256,6 +256,21 @@ typedef struct db_table_method
Only internal use by ets:info(_,binary) */
int (*db_raw_first)(Process*, DbTable*, Eterm* ret);
int (*db_raw_next)(Process*, DbTable*, Eterm key, Eterm* ret);
+ /* Same as first/last/next/prev, but returns object(s) along with key */
+ int (*db_first_lookup)(Process* p,
+ DbTable* tb, /* [in out] */
+ Eterm* ret /* [out] */);
+ int (*db_next_lookup)(Process* p,
+ DbTable* tb, /* [in out] */
+ Eterm key, /* [in] */
+ Eterm* ret /* [out] */);
+ int (*db_last_lookup)(Process* p,
+ DbTable* tb, /* [in out] */
+ Eterm* ret /* [out] */);
+ int (*db_prev_lookup)(Process* p,
+ DbTable* tb, /* [in out] */
+ Eterm key,
+ Eterm* ret);
} DbTableMethod;
typedef struct db_fixation {
diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml
index 855f38d2ae..9cc39c9484 100644
--- a/lib/stdlib/doc/src/ets.xml
+++ b/lib/stdlib/doc/src/ets.xml
@@ -197,6 +197,13 @@
<seemfa marker="#last/1"><c>last/1</c></seemfa> and
<seemfa marker="#prev/2"><c>prev/2</c></seemfa>.</p>
</item>
+ <item><p><em>Single-step</em> traversal one key at at time, but using
+ <seemfa marker="#first_lookup/1"><c>first_lookup/1</c></seemfa>,
+ <seemfa marker="#next_lookup/2"><c>next_lookup/2</c></seemfa>,
+ <seemfa marker="#last_lookup/1"><c>last_lookup/1</c></seemfa> and
+ <seemfa marker="#prev_lookup/2"><c>prev_lookup/2</c></seemfa>. This is more
+ efficient when you also need to lookup the objects for the keys.</p>
+ </item>
<item><p>Search with simple <em>match patterns</em>, using
<seemfa marker="#match/1"><c>match/1/2/3</c></seemfa>,
<seemfa marker="#match_delete/2"><c>match_delete/2</c></seemfa> and
@@ -454,6 +461,20 @@ true
</desc>
</func>
+ <func>
+ <name name="first_lookup" arity="1" since=""/>
+ <fsummary>Return the first key and object(s) in an ETS table.</fsummary>
+ <desc>
+ <p>Similar to <seemfa marker="#first/1"><c>first/1</c></seemfa> except that
+ it returns the object(s) along with the key stored in the table. This is equivalent to doing
+ <seemfa marker="#first/1"><c>first/1</c></seemfa> followed by a <seemfa marker="#lookup/2"><c>lookup/2</c></seemfa>.
+ If the table is empty, <c>'$end_of_table'</c> is returned.
+ </p>
+ <p>To find subsequent objects in the table, use
+ <seemfa marker="#next_lookup/2"><c>next_lookup/2</c></seemfa>.</p>
+ </desc>
+ </func>
+
<func>
<name name="foldl" arity="3" since=""/>
<fsummary>Fold a function over an ETS table.</fsummary>
@@ -936,6 +957,21 @@ Error: fun containing local Erlang function calls
</desc>
</func>
+ <func>
+ <name name="last_lookup" arity="1" since=""/>
+ <fsummary>Return the last key and object in an ETS table of type
+ <c>ordered_set</c>.</fsummary>
+ <desc>
+ <p>Similar to <seemfa marker="#last/1"><c>last/1</c></seemfa> except that
+ it returns the object(s) along with the key stored in the table. This is equivalent to doing
+ <seemfa marker="#last/1"><c>last/1</c></seemfa> followed by a <seemfa marker="#lookup/2"><c>lookup/2</c></seemfa>.
+ If the table is empty, <c>'$end_of_table'</c> is returned.
+ </p>
+ <p>To find preceding objects in the table, use
+ <seemfa marker="#prev_lookup/2"><c>prev_lookup/2</c></seemfa>.</p>
+ </desc>
+ </func>
+
<func>
<name name="lookup" arity="2" since=""/>
<fsummary>Return all objects with a specified key in an ETS table.
@@ -1482,6 +1518,21 @@ ets:select(Table, MatchSpec),</code>
</desc>
</func>
+ <func>
+ <name name="next_lookup" arity="2" since=""/>
+ <fsummary>Return the next key and object(s) in an ETS table.</fsummary>
+ <desc>
+ <p>Similar to <seemfa marker="#next/2"><c>next/2</c></seemfa> except that
+ it returns the object(s) along with the key stored in the table. This is equivalent to doing
+ <seemfa marker="#next/2"><c>next/2</c></seemfa> followed by a <seemfa marker="#lookup/2"><c>lookup/2</c></seemfa>.
+ If no next key exists, <c>'$end_of_table'</c> is returned.
+ </p>
+ <p>
+ It can be interleaved with <seemfa marker="#next/2"><c>next/2</c></seemfa> during traversal.
+ </p>
+ </desc>
+ </func>
+
<func>
<name name="prev" arity="2" since=""/>
<fsummary>Return the previous key in an ETS table of type
@@ -1498,6 +1549,22 @@ ets:select(Table, MatchSpec),</code>
</desc>
</func>
+ <func>
+ <name name="prev_lookup" arity="2" since=""/>
+ <fsummary>Return the previous key and object(s) in an ETS table of type
+ <c>ordered_set</c>.</fsummary>
+ <desc>
+ <p>Similar to <seemfa marker="#prev/2"><c>prev/2</c></seemfa> except that
+ it returns the object(s) along with the key stored in the table. This is equivalent to doing
+ <seemfa marker="#prev/2"><c>prev/2</c></seemfa> followed by a <seemfa marker="#lookup/2"><c>lookup/2</c></seemfa>.
+ If no previous key exists, <c>'$end_of_table'</c> is returned.
+ </p>
+ <p>
+ It can be interleaved with <seemfa marker="#prev/2"><c>prev/2</c></seemfa> during traversal.
+ </p>
+ </desc>
+ </func>
+
<func>
<name name="rename" arity="2" since=""/>
<fsummary>Rename a named ETS table.</fsummary>
@@ -2351,4 +2418,3 @@ true</pre>
</func>
</funcs>
</erlref>
-
diff --git a/lib/stdlib/src/erl_stdlib_errors.erl b/lib/stdlib/src/erl_stdlib_errors.erl
index a90d6477a7..bea2ae8042 100644
--- a/lib/stdlib/src/erl_stdlib_errors.erl
+++ b/lib/stdlib/src/erl_stdlib_errors.erl
@@ -665,6 +665,8 @@ format_ets_error(match_spec_compile, [_], _Cause) ->
[bad_matchspec];
format_ets_error(next, Args, Cause) ->
format_default(bad_key, Args, Cause);
+format_ets_error(next_lookup, Args, Cause) ->
+ format_default(bad_key, Args, Cause);
format_ets_error(new, [Name,Options], Cause) ->
NameError = if
is_atom(Name) -> [];
@@ -681,6 +683,8 @@ format_ets_error(new, [Name,Options], Cause) ->
end;
format_ets_error(prev, Args, Cause) ->
format_default(bad_key, Args, Cause);
+format_ets_error(prev_lookup, Args, Cause) ->
+ format_default(bad_key, Args, Cause);
format_ets_error(rename, [_,NewName]=Args, Cause) ->
case [format_cause(Args, Cause),
if
diff --git a/lib/stdlib/src/ets.erl b/lib/stdlib/src/ets.erl
index 8628a7e29f..f54eca4a3e 100644
--- a/lib/stdlib/src/ets.erl
+++ b/lib/stdlib/src/ets.erl
@@ -68,11 +68,11 @@
%%% BIFs
-export([all/0, delete/1, delete/2, delete_all_objects/1,
- delete_object/2, first/1, give_away/3, info/1, info/2,
- insert/2, insert_new/2, is_compiled_ms/1, last/1, lookup/2,
+ delete_object/2, first/1, first_lookup/1, give_away/3, info/1, info/2,
+ insert/2, insert_new/2, is_compiled_ms/1, last/1, last_lookup/1, lookup/2,
lookup_element/3, lookup_element/4, match/1, match/2, match/3, match_object/1,
match_object/2, match_object/3, match_spec_compile/1,
- match_spec_run_r/3, member/2, new/2, next/2, prev/2,
+ match_spec_run_r/3, member/2, new/2, next/2, next_lookup/2, prev/2, prev_lookup/2,
rename/2, safe_fixtable/2, select/1, select/2, select/3,
select_count/2, select_delete/2, select_replace/2, select_reverse/1,
select_reverse/2, select_reverse/3, setopts/2, slot/2,
@@ -147,6 +147,14 @@ delete_object(_, _) ->
first(_) ->
erlang:nif_error(undef).
+-spec first_lookup(Table) -> {Key, [Object]} | '$end_of_table' when
+ Table :: table(),
+ Key :: term(),
+ Object :: tuple().
+
+first_lookup(_) ->
+ erlang:nif_error(undef).
+
-spec give_away(Table, Pid, GiftData) -> true when
Table :: table(),
Pid :: pid(),
@@ -215,6 +223,14 @@ is_compiled_ms(_) ->
last(_) ->
erlang:nif_error(undef).
+-spec last_lookup(Table) -> {Key, [Object]} | '$end_of_table' when
+ Table :: table(),
+ Key :: term(),
+ Object :: tuple().
+
+last_lookup(_) ->
+ erlang:nif_error(undef).
+
-spec lookup(Table, Key) -> [Object] when
Table :: table(),
Key :: term(),
@@ -343,6 +359,15 @@ new(_, _) ->
next(_, _) ->
erlang:nif_error(undef).
+-spec next_lookup(Table, Key1) -> {Key2, [Object]} | '$end_of_table' when
+ Table :: table(),
+ Key1 :: term(),
+ Key2 :: term(),
+ Object :: tuple().
+
+next_lookup(_, _) ->
+ erlang:nif_error(undef).
+
-spec prev(Table, Key1) -> Key2 | '$end_of_table' when
Table :: table(),
Key1 :: term(),
@@ -351,6 +376,15 @@ next(_, _) ->
prev(_, _) ->
erlang:nif_error(undef).
+-spec prev_lookup(Table, Key1) -> {Key2, [Object]} | '$end_of_table' when
+ Table :: table(),
+ Key1 :: term(),
+ Key2 :: term(),
+ Object :: tuple().
+
+prev_lookup(_, _) ->
+ erlang:nif_error(undef).
+
%% Shadowed by erl_bif_types: ets:rename/2
-spec rename(Table, Name) -> Name when
Table :: table(),
diff --git a/lib/stdlib/test/Makefile b/lib/stdlib/test/Makefile
index d9418b9c13..7eaff2406a 100644
--- a/lib/stdlib/test/Makefile
+++ b/lib/stdlib/test/Makefile
@@ -37,6 +37,7 @@ MODULES= \
error_info_lib \
error_logger_h_SUITE \
escript_SUITE \
+ ets_property_test_SUITE \
ets_SUITE \
ets_tough_SUITE \
filelib_SUITE \
diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl
index 73fd3df43a..20f4ab0d6c 100644
--- a/lib/stdlib/test/ets_SUITE.erl
+++ b/lib/stdlib/test/ets_SUITE.erl
@@ -30,6 +30,7 @@
evil_delete/1,baddelete/1,match_delete/1,table_leak/1]).
-export([match_delete3/1]).
-export([firstnext/1,firstnext_concurrent/1]).
+-export([firstnext_lookup/1,firstnext_lookup_concurrent/1]).
-export([slot/1]).
-export([hash_clash/1]).
-export([match1/1, match2/1, match_object/1, match_object2/1]).
@@ -142,7 +143,8 @@ suite() ->
all() ->
[{group, new}, {group, insert}, {group, lookup},
- {group, delete}, firstnext, firstnext_concurrent, slot, hash_clash,
+ {group, delete}, firstnext, firstnext_concurrent,
+ firstnext_lookup, firstnext_lookup_concurrent, slot, hash_clash,
{group, match}, t_match_spec_run,
{group, lookup_element}, {group, misc}, {group, files},
{group, heavy}, {group, insert_list}, ordered, ordered_match,
@@ -279,7 +281,7 @@ init_per_group(_GroupName, Config) ->
end_per_group(benchmark, Config) ->
T = proplists:get_value(ets_benchmark_result_summary_tab, Config),
EtsProcess = proplists:get_value(ets_benchmark_result_summary_tab_process, Config),
- Report =
+ Report =
fun(NOfBenchmarksCtr, TotThroughputCtr, Name) ->
NBench = ets:lookup_element(T, NOfBenchmarksCtr, 2),
Average = if
@@ -291,7 +293,7 @@ end_per_group(benchmark, Config) ->
end,
io:format("~p ~p~n", [Name, Average]),
ct_event:notify(
- #event{name = benchmark_data,
+ #event{name = benchmark_data,
data = [{suite,"ets_bench"},
{name, Name},
{value, Average}]})
@@ -1020,12 +1022,12 @@ t_delete_all_objects_do(Opts) ->
Self = self(),
Inserters = [spawn_link(fun() -> inserter(T2, 1, Self) end) || _ <- [1,2,3,4]],
[receive {Ipid, running} -> ok end || Ipid <- Inserters],
-
+
ets:delete_all_objects(T2),
erlang:yield(),
[Ipid ! stop || Ipid <- Inserters],
Result = [receive {Ipid, stopped, Highest} -> {Ipid,Highest} end || Ipid <- Inserters],
-
+
%% Verify unbroken sequences of objects inserted _after_ ets:delete_all_objects.
Sum = lists:foldl(fun({Ipid, Highest}, AccSum) ->
%% ets:fun2ms(fun({{K,Ipid}}) when K =< Highest -> true end),
@@ -1061,7 +1063,7 @@ inserter(T, Next, Papa) ->
_ ->
0
end,
-
+
ets:insert(T, {{Next, self()}}),
receive
stop ->
@@ -1241,7 +1243,7 @@ do_fill_dbag_using_lists(T,0) ->
do_fill_dbag_using_lists(T,N) ->
ets:insert(T,[{N,integer_to_list(N)},
{N + N rem 2,integer_to_list(N + N rem 2)}]),
- do_fill_dbag_using_lists(T,N - 1).
+ do_fill_dbag_using_lists(T,N - 1).
%% Test the insert_new function.
@@ -1713,7 +1715,7 @@ t_select_delete(Config) when is_list(Config) ->
F = case ets:info(Table,type) of
X when X == bag; X == duplicate_bag ->
2;
- _ ->
+ _ ->
1
end,
xfilltabstr(Table, 4000),
@@ -2099,7 +2101,7 @@ t_select_hashmap_term_copy_bug(_Config) ->
V = [LM#{ Key => Dollar1 }]
end, maps:keys(LM)),
-
+
%% Create a hashmap with enough keys before and after the '$1' for it to
%% remain a hashmap when we remove those keys.
LMWithDollar = make_lm_with_dollar(LM#{ '$1' => a }, LargeMapSize, FlatmapSize),
@@ -2141,7 +2143,7 @@ t_select_hashmap_term_copy_bug(_Config) ->
(_, M) when map_size(M) > FlatmapSize ->
M
end, LMWithDollar, lists:reverse(maps:keys(LMWithDollar))),
-
+
%% Test hashmap with a key-value pair that are variable
V3 = ets:select(T, [{{'$1'},[], [LM#{ '$1' => '$1' }]}]),
erlang:garbage_collect(),
@@ -2236,7 +2238,7 @@ match_heavy(Config) when is_list(Config) ->
ok.
%%% Extra safety for the very low probability that this is not
-%%% caught by the random test (Statistically impossible???)
+%%% caught by the random test (Statistically impossible???)
drop_match() ->
EtsMem = etsmem(),
T = build_table([a,b],[a],1500),
@@ -2297,9 +2299,9 @@ random_test() ->
io:format(F,"~p. ~n",[Seed]),
file:close(F),
io:format("Random seed ~p written to ~s, copy to ~s to rerun with "
- "same seed.",[Seed,
+ "same seed.",[Seed,
filename:join([WriteDir, "last_random_seed.txt"]),
- filename:join([ReadDir,
+ filename:join([ReadDir,
"preset_random_seed.txt"])]),
do_random_test().
@@ -3158,7 +3160,7 @@ do_fixtable_iter_bag(T) ->
DelSorted = lists:sort(Deleted),
DelSorted = lists:usort(Deleted), %% No duplicates
NDels = length(Deleted),
-
+
%% Nr of keys where all values were deleted.
NDeletedKeys = lists:sum([factorial(N) || N <- lists:seq(1,MaxValues)]),
@@ -3999,23 +4001,23 @@ pick_all_backwards(T) ->
%% Small test case for both set and bag type ets tables.
setbag(Config) when is_list(Config) ->
EtsMem = etsmem(),
- lists:foreach(fun(SetType) ->
+ lists:foreach(fun(SetType) ->
Set = ets_new(SetType,[SetType]),
Bag = ets_new(bag,[bag]),
Key = {foo,bar},
-
+
%% insert some value
ets:insert(Set,{Key,val1}),
ets:insert(Bag,{Key,val1}),
-
+
%% insert new value for same key again
ets:insert(Set,{Key,val2}),
ets:insert(Bag,{Key,val2}),
-
+
%% check
[{Key,val2}] = ets:lookup(Set,Key),
[{Key,val1},{Key,val2}] = ets:lookup(Bag,Key),
-
+
true = ets:delete(Set),
true = ets:delete(Bag)
end, [set, cat_ord_set,stim_cat_ord_set,ordered_set]),
@@ -4051,7 +4053,7 @@ named(Config) when is_list(Config) ->
%% Test case to check if specified keypos works.
keypos2(Config) when is_list(Config) ->
EtsMem = etsmem(),
- lists:foreach(fun(SetType) ->
+ lists:foreach(fun(SetType) ->
Tab = make_table(foo,
[SetType,{keypos,2}],
[{val,key}, {val2,key}]),
@@ -4887,35 +4889,59 @@ match_delete3_do(Opts) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Test ets:first/1 & ets:next/2.
+
+ets_first_using_first_lookup(Tab) ->
+ case ets:first_lookup(Tab) of
+ '$end_of_table' ->
+ '$end_of_table';
+ {Key, _} ->
+ Key
+ end.
+
+ets_next_using_next_lookup(Tab, Key) ->
+ case ets:next_lookup(Tab, Key) of
+ '$end_of_table' ->
+ '$end_of_table';
+ {Key2, _} ->
+ Key2
+ end.
+
firstnext(Config) when is_list(Config) ->
- repeat_for_opts_all_set_table_types(fun firstnext_do/1).
+ repeat_for_opts_all_set_table_types(
+ fun(Opts) -> firstnext_do(Opts, fun ets:first/1, fun ets:next/2) end).
+
+firstnext_lookup(Config) when is_list(Config) ->
+ repeat_for_opts_all_set_table_types(
+ fun(Opts) -> firstnext_do(Opts, fun ets_first_using_first_lookup/1, fun ets_next_using_next_lookup/2) end).
-firstnext_do(Opts) ->
+firstnext_do(Opts, FirstKeyFun, NextKeyFun) ->
EtsMem = etsmem(),
Tab = ets_new(foo,Opts),
- [] = firstnext_collect(Tab,ets:first(Tab),[]),
+ [] = firstnext_collect(Tab,FirstKeyFun(Tab),[], NextKeyFun),
fill_tab(Tab,foo),
Len = length(ets:tab2list(Tab)),
- Len = length(firstnext_collect(Tab,ets:first(Tab),[])),
+ Len = length(firstnext_collect(Tab,FirstKeyFun(Tab),[], NextKeyFun)),
true = ets:delete(Tab),
verify_etsmem(EtsMem).
-firstnext_collect(_Tab,'$end_of_table',List) ->
+firstnext_collect(_Tab,'$end_of_table',List, _NextKeyFun) ->
List;
-firstnext_collect(Tab,Key,List) ->
- firstnext_collect(Tab,ets:next(Tab,Key),[Key|List]).
+firstnext_collect(Tab,Key,List, NextKeyFun) ->
+ firstnext_collect(Tab,NextKeyFun(Tab,Key),[Key|List], NextKeyFun).
+firstnext_concurrent(Config) when is_list(Config) ->
+ firstnext_concurrent_do(Config, fun ets:first/1, fun ets:next/2).
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+firstnext_lookup_concurrent(Config) when is_list(Config) ->
+ firstnext_concurrent_do(Config, fun ets_first_using_first_lookup/1, fun ets_next_using_next_lookup/2).
-%% Tests ets:first/1 & ets:next/2.
-firstnext_concurrent(Config) when is_list(Config) ->
+firstnext_concurrent_do(Config, FirstKeyFun, NextKeyFun) when is_list(Config) ->
lists:foreach(
- fun(TableType) ->
+ fun(TableType) ->
register(master, self()),
TableName = list_to_atom(atom_to_list(?MODULE) ++ atom_to_list(TableType)),
ets_init(TableName, 20, TableType),
- [dynamic_go(TableName) || _ <- lists:seq(1, 2)],
+ [dynamic_go(TableName, FirstKeyFun, NextKeyFun) || _ <- lists:seq(1, 2)],
receive
after 5000 -> ok
end,
@@ -4931,18 +4957,18 @@ cycle(Tab, L) ->
ets:insert(Tab,list_to_tuple(L)),
cycle(Tab, tl(L)++[hd(L)]).
-dynamic_go(TableName) -> my_spawn_link(fun() -> dynamic_init(TableName) end).
+dynamic_go(TableName, FirstKeyFun, NextKeyFun) -> my_spawn_link(fun() -> dynamic_init(TableName, FirstKeyFun, NextKeyFun) end).
-dynamic_init(TableName) -> [dyn_lookup(TableName) || _ <- lists:seq(1, 10)].
+dynamic_init(TableName, FirstKeyFun, NextKeyFun) -> [dyn_lookup(TableName, FirstKeyFun, NextKeyFun) || _ <- lists:seq(1, 10)].
-dyn_lookup(T) -> dyn_lookup(T, ets:first(T)).
+dyn_lookup(T, FirstKeyFun, NextKeyFun) -> dyn_lookup_next(T, FirstKeyFun(T), NextKeyFun).
-dyn_lookup(_T, '$end_of_table') -> [];
-dyn_lookup(T, K) ->
- NextKey = ets:next(T,K),
- case ets:next(T,K) of
+dyn_lookup_next(_T, '$end_of_table', _NextKeyFun) -> [];
+dyn_lookup_next(T, K, NextKeyFun) ->
+ NextKey = NextKeyFun(T,K),
+ case NextKeyFun(T,K) of
NextKey ->
- dyn_lookup(T, NextKey);
+ dyn_lookup_next(T, NextKey, NextKeyFun);
NK ->
io:fwrite("hmmm... ~p =/= ~p~n", [NextKey,NK]),
exit(failed)
@@ -5324,14 +5350,14 @@ info(Config) when is_list(Config) ->
info_do(Opts) ->
EtsMem = etsmem(),
TableType = lists:foldl(
- fun(Item, Curr) ->
+ fun(Item, Curr) ->
case Item of
set -> set;
ordered_set -> ordered_set;
cat_ord_set -> ordered_set;
stim_cat_ord_set -> ordered_set;
bag -> bag;
- duplicate_bag -> duplicate_bag;
+ duplicate_bag -> duplicate_bag;
_ -> Curr
end
end, set, Opts),
@@ -6064,9 +6090,9 @@ tabfile_ext1_do(Opts,Config) ->
Name = make_ref(),
[ets:insert(T,{X,integer_to_list(X)}) || X <- L],
ok = ets:tab2file(T,FName,[{extended_info,[object_count]}]),
- true = lists:sort(ets:tab2list(T)) =:=
+ true = lists:sort(ets:tab2list(T)) =:=
lists:sort(ets:tab2list(element(2,ets:file2tab(FName)))),
- true = lists:sort(ets:tab2list(T)) =:=
+ true = lists:sort(ets:tab2list(T)) =:=
lists:sort(ets:tab2list(
element(2,ets:file2tab(FName,[{verify,true}])))),
{ok,Name} = disk_log:open([{name,Name},{file,FName}]),
@@ -7095,11 +7121,11 @@ grow_shrink(Config) when is_list(Config) ->
repeat_for_all_set_table_types(
fun(Opts) ->
EtsMem = etsmem(),
-
+
Set = ets_new(a, Opts, 5000),
grow_shrink_0(0, 3071, 3000, 5000, Set),
ets:delete(Set),
-
+
verify_etsmem(EtsMem)
end).
@@ -7901,7 +7927,7 @@ otp_9423(Config) when is_list(Config) ->
case run_smp_workers(InitF, ExecF, FiniF, infinite, #{exclude => 1}) of
Pids when is_list(Pids) ->
%%[P ! start || P <- Pids],
- repeat(fun() -> ets_new(otp_9423, [named_table, public,
+ repeat(fun() -> ets_new(otp_9423, [named_table, public,
{write_concurrency,true}|Opts]),
ets:delete(otp_9423)
end, 10000),
@@ -7918,7 +7944,7 @@ otp_9423(Config) when is_list(Config) ->
%% Corrupted binary in compressed table
otp_10182(Config) when is_list(Config) ->
repeat_for_opts_all_table_types(
- fun(Opts) ->
+ fun(Opts) ->
Bin = <<"aHR0cDovL2hvb3RzdWl0ZS5jb20vYy9wcm8tYWRyb2xsLWFi">>,
Key = {test, Bin},
Value = base64:decode(Bin),
@@ -9044,7 +9070,7 @@ pid_status(Pid) ->
error:undef ->
erts_debug:set_internal_state(available_internal_state, true),
pid_status(Pid)
- end.
+ end.
start_spawn_logger() ->
case whereis(ets_test_spawn_logger) of
@@ -9333,6 +9359,7 @@ error_info(_Config) ->
{file2tab, 2}, %Not BIF.
{first, ['$Tab']},
+ {first_lookup, ['$Tab']},
{foldl, 3}, %Not BIF.
{foldr, 3}, %Not BIF.
@@ -9377,6 +9404,7 @@ error_info(_Config) ->
{is_compiled_ms, [bad_ms], [no_fail, no_table]},
{last, ['$Tab']},
+ {last_lookup, ['$Tab']},
{lookup, ['$Tab', no_key], [no_fail]},
@@ -9418,11 +9446,15 @@ error_info(_Config) ->
%% not exist.
{next, [Set, no_key]},
{prev, [Set, no_key]},
+ {next_lookup, [Set, no_key]},
+ {prev_lookup, [Set, no_key]},
- %% For an ordered set, ets:next/2 and ets:prev/2 succeeds
- %% even if the key does not exist.
+ % For an ordered set, ets:next/2 and ets:prev/2 succeeds
+ % even if the key does not exist.
{next, [OrderedSet, no_key], [no_fail]},
{prev, [OrderedSet, no_key], [no_fail]},
+ {next_lookup, [OrderedSet, no_key], [no_fail]},
+ {prev_lookup, [OrderedSet, no_key], [no_fail]},
{rename, ['$Tab', {bad,name}]},
{rename, [NamedTable, '$named_table']},
@@ -9983,6 +10015,7 @@ repeat_for_opts(F, [Atom | Tail], AccList) when is_atom(Atom) ->
repeat_for_opts(F, [repeat_for_opts_atom2list(Atom) | Tail ], AccList).
repeat_for_opts_atom2list(set_types) -> [set,ordered_set,stim_cat_ord_set,cat_ord_set];
+repeat_for_opts_atom2list(hash_types) -> [set,bag,duplicate_bag];
repeat_for_opts_atom2list(ord_set_types) -> [ordered_set,stim_cat_ord_set,cat_ord_set];
repeat_for_opts_atom2list(all_types) -> [set,ordered_set,stim_cat_ord_set,cat_ord_set,bag,duplicate_bag];
repeat_for_opts_atom2list(all_non_stim_types) -> [set,ordered_set,cat_ord_set,bag,duplicate_bag];
@@ -10055,7 +10088,7 @@ ets_new(Name, Opts0, KeyRange, KeyFun) ->
{erlang:system_info(schedulers) > 1,false, false, []},
Opts0),
Opts = lists:reverse(RevOpts),
- EtsNewHelper =
+ EtsNewHelper =
fun (UseOpts) ->
case get(ets_new_opts) of
UseOpts ->
@@ -10071,12 +10104,12 @@ ets_new(Name, Opts0, KeyRange, KeyFun) ->
(not lists:member(private, Opts)) andalso
(not lists:member(protected, Opts)) of
true ->
- NewOpts1 =
+ NewOpts1 =
case lists:member({write_concurrency, true}, Opts) of
true -> Opts;
false -> [{write_concurrency, true}|Opts]
end,
- NewOpts2 =
+ NewOpts2 =
case lists:member(public, NewOpts1) of
true -> NewOpts1;
false -> [public|NewOpts1]
diff --git a/lib/stdlib/test/ets_property_test_SUITE.erl b/lib/stdlib/test/ets_property_test_SUITE.erl
new file mode 100644
index 0000000000..9aa501119e
--- /dev/null
+++ b/lib/stdlib/test/ets_property_test_SUITE.erl
@@ -0,0 +1,55 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2021-2022. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(ets_property_test_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+
+-compile(export_all).
+-compile(nowarn_export_all).
+
+all() -> [
+ first_case,
+ next_case,
+ last_case,
+ prev_case
+ ].
+
+init_per_suite(Config) ->
+ ct_property_test:init_per_suite(Config).
+
+end_per_suite(Config) ->
+ Config.
+
+first_case(Config) ->
+ do_proptest(prop_first, Config).
+
+next_case(Config) ->
+ do_proptest(prop_next, Config).
+
+last_case(Config) ->
+ do_proptest(prop_last, Config).
+
+prev_case(Config) ->
+ do_proptest(prop_prev, Config).
+
+do_proptest(Prop, Config) ->
+ ct_property_test:quickcheck(
+ ets_prop:Prop(),
+ Config).
diff --git a/lib/stdlib/test/property_test/ets_prop.erl b/lib/stdlib/test/property_test/ets_prop.erl
new file mode 100644
index 0000000000..3c16f2658d
--- /dev/null
+++ b/lib/stdlib/test/property_test/ets_prop.erl
@@ -0,0 +1,108 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2021-2022. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(ets_prop).
+
+-include_lib("common_test/include/ct_property_test.hrl").
+
+-type table_type() :: set | ordered_set | bag | duplicate_bag.
+
+-define(ETS_TAB_DATA, proper_types:list({ct_proper_ext:safe_any(), ct_proper_ext:safe_any()})).
+
+%%%%%%%%%%%%%%%%%%
+%%% Properties %%%
+%%%%%%%%%%%%%%%%%%
+
+%% --- first/2 ----------------------------------------------------------
+prop_first() ->
+ ?FORALL(Type, noshrink(table_type()),
+ ?FORALL(
+ DataList,
+ ?ETS_TAB_DATA,
+ compare_with_and_without_lookup_variants(
+ Type, DataList,
+ fun (T, _) -> ets:first(T) end,
+ fun (T, _) -> ets:first_lookup(T) end)
+ )).
+
+%% --- next/2 ----------------------------------------------------------
+prop_next() ->
+ ?FORALL(Type, noshrink(table_type()),
+ ?FORALL(
+ DataList,
+ ?ETS_TAB_DATA,
+ compare_with_and_without_lookup_variants(
+ Type, DataList, fun ets:next/2, fun ets:next_lookup/2)
+ )).
+
+%% --- last/2 ----------------------------------------------------------
+prop_last() ->
+ ?FORALL(Type, noshrink(table_type()),
+ ?FORALL(
+ DataList,
+ ?ETS_TAB_DATA,
+ compare_with_and_without_lookup_variants(
+ Type, DataList,
+ fun (T, _) -> ets:last(T) end,
+ fun (T, _) -> ets:last_lookup(T) end)
+ )).
+
+%% --- prev/2 ----------------------------------------------------------
+prop_prev() ->
+ ?FORALL(Type, noshrink(table_type()),
+ ?FORALL(
+ DataList,
+ ?ETS_TAB_DATA,
+ compare_with_and_without_lookup_variants(
+ Type, DataList, fun ets:prev/2, fun ets:prev_lookup/2)
+ )).
+
+%%%% helpers %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+random_key([]) ->
+ '$end_of_table';
+random_key([{Key, _}]) ->
+ Key;
+random_key(Data) ->
+ RandomN = 1 + erlang:phash2(erlang:unique_integer(), length(Data)),
+ {Key, _} = lists:nth(RandomN, Data),
+ Key.
+
+compare_with_and_without_lookup_variants(TableType, TableData, WithoutLookupFun, LookupFun) ->
+ Tab = ets:new(test, [TableType]),
+ ets:insert(Tab, TableData),
+ Res = do_compare_with_and_without_lookup_variants(
+ random_key(TableData), Tab, WithoutLookupFun, LookupFun),
+ ets:delete(Tab),
+ Res.
+
+% compare variants of first/next/last/prev with and without _lookup to make sure they are consistent
+% Key is the current position in the table, used for prev/next and ignored for first/last
+% Key = '$end_of_table' means nothing to compare
+do_compare_with_and_without_lookup_variants('$end_of_table', _Tab, _WithoutLookupFun, _LookupFun) ->
+ true;
+do_compare_with_and_without_lookup_variants(Key, Tab, WithoutLookupFun, LookupFun) ->
+ Key2 = WithoutLookupFun(Tab, Key),
+ case Key2 of
+ '$end_of_table' ->
+ '$end_of_table' =:= LookupFun(Tab, Key);
+ _ ->
+ Values2 = ets:lookup(Tab, Key2),
+ {Key2, Values2} =:= LookupFun(Tab, Key)
+ end.
--
2.35.3