File 7341-Add-ets-update_element-4-that-accepts-a-default-obje.patch of Package erlang
From c888beb7f67b496fe85919700c4bd0eb0f3a6519 Mon Sep 17 00:00:00 2001
From: Jan Uhlig <juhlig@hnc-agency.org>
Date: Wed, 8 Nov 2023 12:12:36 +0100
Subject: [PATCH] Add ets:update_element/4 that accepts a default object
Co-authored-by: Maria Scott <maria-12648430@hnc-agency.org>
---
erts/emulator/beam/bif.tab | 1 +
erts/emulator/beam/erl_db.c | 70 ++++++++++++++++--------
lib/stdlib/doc/src/ets.xml | 10 ++++
lib/stdlib/src/erl_stdlib_errors.erl | 22 ++++++++
lib/stdlib/src/ets.erl | 18 ++++++-
lib/stdlib/test/ets_SUITE.erl | 79 ++++++++++++++++++++++------
6 files changed, 161 insertions(+), 39 deletions(-)
diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab
index 614e8357c8..99344495c0 100644
--- a/erts/emulator/beam/bif.tab
+++ b/erts/emulator/beam/bif.tab
@@ -781,3 +781,8 @@ bif maps:from_keys/2
ubif erlang:min/2
ubif erlang:max/2
bif erts_internal:term_to_string/2
+
+#
+# New in 27.
+#
+bif ets:update_element/4
diff --git a/erts/emulator/beam/erl_db.c b/erts/emulator/beam/erl_db.c
index 8f5e1a9543..009a0d2dd4 100644
--- a/erts/emulator/beam/erl_db.c
+++ b/erts/emulator/beam/erl_db.c
@@ -1203,35 +1203,29 @@ BIF_RETTYPE ets_take_2(BIF_ALIST_2)
BIF_RET(ret);
}
-/*
-** update_element(Tab, Key, {Pos, Value})
-** update_element(Tab, Key, [{Pos, Value}])
-*/
-BIF_RETTYPE ets_update_element_3(BIF_ALIST_3)
+static BIF_RETTYPE do_update_element(Process *p, DbTable *tb,
+ Eterm key, Eterm pos_val, Eterm default_obj)
{
- DbTable* tb;
int cret = DB_ERROR_BADITEM;
Eterm list;
Eterm iter;
- DeclareTmpHeap(cell,2,BIF_P);
+ DeclareTmpHeap(cell,2,p);
DbUpdateHandle handle;
- DB_BIF_GET_TABLE(tb, DB_WRITE, LCK_WRITE_REC, BIF_ets_update_element_3);
-
- UseTmpHeap(2,BIF_P);
+ UseTmpHeap(2,p);
if (!(tb->common.status & (DB_SET | DB_ORDERED_SET | DB_CA_ORDERED_SET))) {
- BIF_P->fvalue = EXI_TAB_TYPE;
+ p->fvalue = EXI_TAB_TYPE;
cret = DB_ERROR_BADPARAM;
goto bail_out;
}
- if (is_tuple(BIF_ARG_3)) {
- list = CONS(cell, BIF_ARG_3, NIL);
+ if (is_tuple(pos_val)) {
+ list = CONS(cell, pos_val, NIL);
}
else {
- list = BIF_ARG_3;
+ list = pos_val;
}
- if (!tb->common.meth->db_lookup_dbterm(BIF_P, tb, BIF_ARG_2, THE_NON_VALUE, &handle)) {
+ if (!tb->common.meth->db_lookup_dbterm(p, tb, key, default_obj, &handle)) {
cret = DB_ERROR_BADKEY;
goto bail_out;
}
@@ -1256,12 +1250,13 @@ BIF_RETTYPE ets_update_element_3(BIF_ALIST_3)
}
position = signed_val(pvp[1]);
if (position == tb->common.keypos) {
- BIF_P->fvalue = EXI_KEY_POS;
+ p->fvalue = EXI_KEY_POS;
cret = DB_ERROR_UNSPEC;
goto finalize;
}
- if (position < 1 || position == tb->common.keypos ||
- position > arityval(handle.dbterm->tpl[0])) {
+ if (position < 1 || position > arityval(handle.dbterm->tpl[0])) {
+ p->fvalue = EXI_POSITION;
+ cret = DB_ERROR_UNSPEC;
goto finalize;
}
}
@@ -1278,7 +1273,7 @@ finalize:
tb->common.meth->db_finalize_dbterm(cret, &handle);
bail_out:
- UnUseTmpHeap(2,BIF_P);
+ UnUseTmpHeap(2,p);
db_unlock(tb, LCK_WRITE_REC);
switch (cret) {
@@ -1287,13 +1282,44 @@ bail_out:
case DB_ERROR_BADKEY:
BIF_RET(am_false);
case DB_ERROR_SYSRES:
- BIF_ERROR(BIF_P, SYSTEM_LIMIT);
+ BIF_ERROR(p, SYSTEM_LIMIT);
case DB_ERROR_UNSPEC:
- BIF_ERROR(BIF_P, BADARG | EXF_HAS_EXT_INFO);
+ BIF_ERROR(p, BADARG | EXF_HAS_EXT_INFO);
default:
break;
}
- BIF_ERROR(BIF_P, BADARG);
+ BIF_ERROR(p, BADARG);
+}
+
+/*
+** update_element(Tab, Key, {Pos, Value})
+** update_element(Tab, Key, [{Pos, Value}])
+*/
+BIF_RETTYPE ets_update_element_3(BIF_ALIST_3)
+{
+ DbTable* tb;
+
+ DB_BIF_GET_TABLE(tb, DB_WRITE, LCK_WRITE_REC, BIF_ets_update_element_3);
+
+ return do_update_element(BIF_P, tb, BIF_ARG_2, BIF_ARG_3, THE_NON_VALUE);
+}
+
+/*
+** update_element(Tab, Key, {Pos, Value}, Default)
+** update_element(Tab, Key, [{Pos, Value}], Default)
+*/
+BIF_RETTYPE ets_update_element_4(BIF_ALIST_4)
+{
+ DbTable* tb;
+
+ DB_BIF_GET_TABLE(tb, DB_WRITE, LCK_WRITE_REC, BIF_ets_update_element_4);
+
+ if (is_not_tuple(BIF_ARG_4)) {
+ db_unlock(tb, LCK_WRITE_REC);
+ BIF_ERROR(BIF_P, BADARG);
+ }
+
+ return do_update_element(BIF_P, tb, BIF_ARG_2, BIF_ARG_3, BIF_ARG_4);
}
static BIF_RETTYPE
diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml
index 855f38d2ae..cc9eb6d4a9 100644
--- a/lib/stdlib/doc/src/ets.xml
+++ b/lib/stdlib/doc/src/ets.xml
@@ -2294,13 +2294,16 @@ true</pre>
<func>
<name name="update_element" arity="3" clause_i="1" since=""/>
+ <name name="update_element" arity="4" clause_i="1" since="OTP 27.0"/>
<name name="update_element" arity="3" clause_i="2" since=""/>
+ <name name="update_element" arity="4" clause_i="2" since="OTP 27.0"/>
<fsummary>Update the <c>Pos</c>:th element of the object with a
specified key in an ETS table.</fsummary>
<type variable="Table"/>
<type variable="Key"/>
<type variable="Value"/>
<type variable="Pos"/>
+ <type variable="Default"/>
<desc>
<p>This function provides an efficient way to update one or more
elements within an object, without the trouble of having to look up,
@@ -2324,12 +2327,19 @@ true</pre>
<c>ordered_set</c> table (for details on the difference, see
<seemfa marker="#lookup/2"><c>lookup/2</c></seemfa> and
<seemfa marker="#new/2"><c>new/2</c></seemfa>).</p>
+ <p>If a default object <c><anno>Default</anno></c> is specified,
+ it is used
+ as the object to be updated if the key is missing from the table. The
+ value in place of the key is ignored and replaced by the proper key
+ value.</p>
<p>The function fails with reason <c>badarg</c> in the following
situations:</p>
<list type="bulleted">
<item>The table type is not <c>set</c> or <c>ordered_set</c>.</item>
<item><c><anno>Pos</anno></c> < 1.</item>
<item><c><anno>Pos</anno></c> > object arity.</item>
+ <item>The default object arity is smaller than
+ <c><![CDATA[<keypos>]]></c>.</item>
<item>The element to update is also the key.</item>
</list>
</desc>
diff --git a/lib/stdlib/src/erl_stdlib_errors.erl b/lib/stdlib/src/erl_stdlib_errors.erl
index 2379695acf..60996113c2 100644
--- a/lib/stdlib/src/erl_stdlib_errors.erl
+++ b/lib/stdlib/src/erl_stdlib_errors.erl
@@ -772,6 +772,8 @@ format_ets_error(update_element, [_,_,ElementSpec]=Args, Cause) ->
case Cause of
keypos ->
[same_as_keypos];
+ position ->
+ [update_op_range];
_ ->
case is_element_spec_top(ElementSpec) of
true ->
@@ -785,6 +787,26 @@ format_ets_error(update_element, [_,_,ElementSpec]=Args, Cause) ->
[<<"is not a valid element specification">>]
end
end];
+format_ets_error(update_element, [_, _, ElementSpec, Default]=Args, Cause) ->
+ TabCause = format_cause(Args, Cause),
+ ArgsCause = case Cause of
+ keypos ->
+ [same_as_keypos];
+ position ->
+ [update_op_range];
+ _ ->
+ case {is_element_spec_top(ElementSpec), format_tuple(Default)} of
+ {true, [""]} ->
+ [range];
+ {true, TupleCause} ->
+ ["" | TupleCause];
+ {false, [""]} ->
+ [<<"is not a valid element specification">>];
+ {false, TupleCause} ->
+ ["" | TupleCause]
+ end
+ end,
+ [TabCause, "" | ArgsCause];
format_ets_error(whereis, _Args, _Cause) ->
[bad_table_name];
format_ets_error(_, Args, Cause) ->
diff --git a/lib/stdlib/src/ets.erl b/lib/stdlib/src/ets.erl
index 8628a7e29f..54f195a409 100644
--- a/lib/stdlib/src/ets.erl
+++ b/lib/stdlib/src/ets.erl
@@ -77,7 +77,7 @@
select_count/2, select_delete/2, select_replace/2, select_reverse/1,
select_reverse/2, select_reverse/3, setopts/2, slot/2,
take/2,
- update_counter/3, update_counter/4, update_element/3,
+ update_counter/3, update_counter/4, update_element/3, update_element/4,
whereis/1]).
%% internal exports
@@ -551,6 +551,22 @@ update_counter(_, _, _, _) ->
update_element(_, _, _) ->
erlang:nif_error(undef).
+-spec update_element(Table, Key, ElementSpec :: {Pos, Value}, Default) -> true when
+ Table :: table(),
+ Key :: term(),
+ Pos :: pos_integer(),
+ Value :: term(),
+ Default :: tuple();
+ (Table, Key, ElementSpec :: [{Pos, Value}], Default) -> true when
+ Table :: table(),
+ Key :: term(),
+ Pos :: pos_integer(),
+ Value :: term(),
+ Default :: tuple().
+
+update_element(_, _, _, _) ->
+ erlang:nif_error(undef).
+
-spec whereis(TableName) -> tid() | undefined when
TableName :: atom().
whereis(_) ->
diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl
index 73fd3df43a..a4105a3c81 100644
--- a/lib/stdlib/test/ets_SUITE.erl
+++ b/lib/stdlib/test/ets_SUITE.erl
@@ -62,7 +62,7 @@
-export([ordered/1, ordered_match/1, interface_equality/1,
fixtable_next/1, fixtable_iter_bag/1,
fixtable_insert/1, rename/1, rename_unnamed/1, evil_rename/1,
- update_element/1, update_counter/1, evil_update_counter/1, partly_bound/1, match_heavy/1]).
+ update_element/1, update_element_default/1, update_counter/1, evil_update_counter/1, partly_bound/1, match_heavy/1]).
-export([update_counter_with_default/1]).
-export([update_counter_with_default_bad_pos/1]).
-export([update_counter_table_growth/1]).
@@ -147,7 +147,7 @@ all() ->
{group, lookup_element}, {group, misc}, {group, files},
{group, heavy}, {group, insert_list}, ordered, ordered_match,
interface_equality, fixtable_next, fixtable_iter_bag, fixtable_insert,
- rename, rename_unnamed, evil_rename, update_element,
+ rename, rename_unnamed, evil_rename, update_element, update_element_default,
update_counter, evil_update_counter,
update_counter_with_default,
update_counter_with_default_bad_pos,
@@ -2552,22 +2552,26 @@ update_element_do(Tab,Tuple,Key,UpdPos) ->
make_ref(), make_ref(), self(), ok, update_element, 28, 29 },
Length = size(Values),
- PosValArgF = fun(ToIx, ResList, [Pos | PosTail], Rand, MeF) ->
+ PosValArgF = fun MeF(ToIx, ResList, [Pos | PosTail], Rand) ->
NextIx = (ToIx+Rand) rem Length,
- MeF(NextIx, [{Pos,element(ToIx+1,Values)} | ResList], PosTail, Rand, MeF);
+ MeF(NextIx, [{Pos,element(ToIx+1,Values)} | ResList], PosTail, Rand);
- (_ToIx, ResList, [], _Rand, _MeF) ->
+ MeF(_ToIx, ResList, [], _Rand) ->
ResList;
- (ToIx, [], Pos, _Rand, _MeF) ->
+ MeF(ToIx, [], Pos, _Rand) ->
{Pos, element(ToIx+1,Values)} % single {pos,value} arg
end,
UpdateF = fun(ToIx,Rand) ->
- PosValArg = PosValArgF(ToIx,[],UpdPos,Rand,PosValArgF),
+ PosValArg = PosValArgF(ToIx,[],UpdPos,Rand),
%%io:format("update_element(~p)~n",[PosValArg]),
ArgHash = erlang:phash2({Tab,Key,PosValArg}),
true = ets:update_element(Tab, Key, PosValArg),
+ [DefaultObj] = ets:lookup(Tab, Key),
+ NewKey = make_ref(),
+ true = ets:update_element(Tab, NewKey, PosValArg, DefaultObj),
+ true = [update_tuple({ets:info(Tab, keypos), NewKey}, DefaultObj)] =:= ets:lookup(Tab, NewKey),
ArgHash = erlang:phash2({Tab,Key,PosValArg}),
NewTuple = update_tuple(PosValArg,Tuple),
[NewTuple] = ets:lookup(Tab,Key)
@@ -2578,27 +2582,27 @@ update_element_do(Tab,Tuple,Key,UpdPos) ->
[NewTuple] = ets:lookup(Tab,Key)
end,
- LoopF = fun(_FromIx, Incr, _Times, Checksum, _MeF) when Incr >= Length ->
+ LoopF = fun MeF(_FromIx, Incr, _Times, Checksum) when Incr >= Length ->
Checksum; % done
- (FromIx, Incr, 0, Checksum, MeF) ->
- MeF(FromIx, Incr+1, Length, Checksum, MeF);
+ MeF(FromIx, Incr, 0, Checksum) ->
+ MeF(FromIx, Incr+1, Length, Checksum);
- (FromIx, Incr, Times, Checksum, MeF) ->
+ MeF(FromIx, Incr, Times, Checksum) ->
ToIx = (FromIx + Incr) rem Length,
UpdateF(ToIx,Checksum),
if
Incr =:= 0 -> UpdateF(ToIx,Checksum); % extra update to same value
true -> true
end,
- MeF(ToIx, Incr, Times-1, Checksum+ToIx+1, MeF)
+ MeF(ToIx, Incr, Times-1, Checksum+ToIx+1)
end,
FirstTuple = Tuple,
true = ets:insert(Tab,FirstTuple),
[FirstTuple] = ets:lookup(Tab,Key),
- Checksum = LoopF(0, 1, Length, 0, LoopF),
+ Checksum = LoopF(0, 1, Length, 0),
Checksum = (Length-1)*Length*(Length+1) div 2, % if Length is a prime
ok.
@@ -2616,6 +2620,7 @@ update_element_neg(Opts) ->
update_element_neg_do(Set),
ets:delete(Set),
{'EXIT',{badarg,_}} = (catch ets:update_element(Set,key,{2,1})),
+ {'EXIT',{badarg,_}} = (catch ets:update_element(Set,key,{2,1},{a,b})),
run_if_valid_opts(
[ordered_set | Opts],
@@ -2623,13 +2628,16 @@ update_element_neg(Opts) ->
OrdSet = ets_new(ordered_set, OptsOrdSet),
update_element_neg_do(OrdSet),
ets:delete(OrdSet),
- {'EXIT',{badarg,_}} = (catch ets:update_element(OrdSet,key,{2,1}))
+ {'EXIT',{badarg,_}} = (catch ets:update_element(OrdSet,key,{2,1})),
+ {'EXIT',{badarg,_}} = (catch ets:update_element(OrdSet,key2,{2,1},{a,b}))
end),
Bag = ets_new(bag,[bag | Opts]),
DBag = ets_new(duplicate_bag,[duplicate_bag | Opts]),
{'EXIT',{badarg,_}} = (catch ets:update_element(Bag,key,{2,1})),
+ {'EXIT',{badarg,_}} = (catch ets:update_element(Bag,key,{2,1},{key,0})),
{'EXIT',{badarg,_}} = (catch ets:update_element(DBag,key,{2,1})),
+ {'EXIT',{badarg,_}} = (catch ets:update_element(DBag,key,{2,1},{key,0})),
true = ets:delete(Bag),
true = ets:delete(DBag),
ok.
@@ -2643,6 +2651,8 @@ update_element_neg_do(T) ->
ArgHash = erlang:phash2({T,key,Arg3}),
{'EXIT',{badarg,_}} = (catch ets:update_element(T,key,Arg3)),
ArgHash = erlang:phash2({T,key,Arg3}),
+ {'EXIT',{badarg,_}} = (catch ets:update_element(T,key2,Arg3,Object)),
+ ArgHash = erlang:phash2({T,key,Arg3}),
[Object] = ets:lookup(T,key)
end,
@@ -2667,6 +2677,32 @@ update_element_neg_do(T) ->
ok.
+update_element_default(Config) when is_list(Config) ->
+ EtsMem = etsmem(),
+ repeat_for_opts(fun update_element_default_opts/1),
+ verify_etsmem(EtsMem).
+
+
+update_element_default_opts(Opts) ->
+ lists:foreach(
+ fun({Type, {Key, Pos}}) ->
+ run_if_valid_opts(
+ [Type, {keypos, Pos} | Opts],
+ fun(TabOpts) ->
+ Tab = ets_new(Type, TabOpts),
+ true = ets:update_element(Tab, Key, {3, b}, {key1, key2, a, x}),
+ [{key1, key2, b, x}] = ets:lookup(Tab, Key),
+ true = ets:update_element(Tab, Key, {3, c}, {key1, key2, a, y}),
+ [{key1, key2, c, x}] = ets:lookup(Tab, Key),
+ ets:delete(Tab)
+ end
+ )
+ end,
+ [{Type, KeyPos} || Type <- [set, ordered_set], KeyPos <- [{key1, 1}, {key2, 2}]]
+ ),
+ ok.
+
+
%% test various variants of update_counter.
update_counter(Config) when is_list(Config) ->
EtsMem = etsmem(),
@@ -9515,9 +9551,20 @@ error_info(_Config) ->
{update_element, ['$Tab', no_key, {2, new}], [no_fail]},
{update_element, [BagTab, no_key, {2, bagged}]},
{update_element, [OneKeyTab, one, not_tuple]},
- {update_element, [OneKeyTab, one, {0, new}]},
+ {update_element, [OneKeyTab, one, {0, new}], [{error_term, position}]},
{update_element, [OneKeyTab, one, {1, new}], [{error_term,keypos}]},
- {update_element, [OneKeyTab, one, {4, new}]},
+ {update_element, [OneKeyTab, one, {4, new}], [{error_term, position}]},
+
+ {update_element, ['$Tab', no_key, {2, new}, {no_key, old}], [no_fail]},
+ {update_element, ['$Tab', no_key, {0, new}, {no_key, old}], [{error_term, position}]},
+ {update_element, ['$Tab', no_key, {1, new}, {no_key, old}], [{error_term, keypos}]},
+ {update_element, ['$Tab', no_key, {4, new}, {no_key, old}], [{error_term, position}]},
+ {update_element, ['$Tab', no_key, {4, new}, not_tuple]},
+ {update_element, [BagTab, no_key, {1, bagged}, {no_key, old}], []},
+ {update_element, [OneKeyTab, no_key, {0, new}, {no_key, old}], [{error_term, position}]},
+ {update_element, [OneKeyTab, no_key, {1, new}, {no_key, old}], [{error_term, keypos}]},
+ {update_element, [OneKeyTab, no_key, {4, new}, {no_key, old}], [{error_term, position}]},
+ {update_element, [OneKeyTab, no_key, {4, new}, not_tuple]},
{whereis, [{bad,name}], [no_table]}
],
--
2.35.3