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> &lt; 1.</item>
           <item><c><anno>Pos</anno></c> &gt; 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

openSUSE Build Service is sponsored by