WIP migrate table encoding

This commit is contained in:
Ulf Wiger
2026-08-04 15:30:31 +02:00
parent 6246f3f2e1
commit 1a99fec600
4 changed files with 1056 additions and 29 deletions
+473 -22
View File
@@ -19,7 +19,13 @@
]).
-export([ migrate_standalone/2
, migrate_standalone/3 ]).
, migrate_standalone/3
%% Online encoding migration (set tables): dual-write + CF copy
, migrate_encoding/3
, migrate_encoding/4
, migration_status/1
, finalize_migration/2
]).
-export([ start_link/0
, init/1
@@ -49,6 +55,7 @@
backends = #{} :: #{ alias() => backend() }
, standalone = #{} :: #{{alias(), table()} := cf() }
, default_opts = [] :: [{atom(), _}]
, migrators = #{} :: #{ table() => pid() }
}).
-type st() :: #st{}.
@@ -79,6 +86,9 @@
| {write_table_property, tabname(), tuple()}
| {remove_aliases, [alias()]}
| {migrate, [tabname() | {tabname(), map()}], rpt()}
| {migrate_encoding, tabname(), any(), rpt()}
| {migration_status, tabname()}
| {finalize_migration, tabname()}
| {prep_close, table()}
| {close_table, table()}
| {clear_table, table() | cf() }.
@@ -289,6 +299,39 @@ migrate_standalone(Alias, Tabs, Rpt0) ->
end,
call(Alias, {migrate, Tabs, Rpt}).
%% @doc Start online encoding migration for a set table (dual-write + CF copy).
-spec migrate_encoding(alias(), tabname(), Encoding :: any()) ->
ok | {error, term()}.
migrate_encoding(Alias, Tab, Encoding) ->
migrate_encoding(Alias, Tab, Encoding, undefined).
-spec migrate_encoding(alias(), tabname(), Encoding :: any(), rpt()) ->
ok | {error, term()}.
migrate_encoding(Alias, Tab, Encoding, Rpt0) ->
Rpt = case Rpt0 of
undefined -> undefined;
To when is_pid(To); is_atom(To) ->
#{to => To, tag => migrate_encoding};
#{} = M -> M
end,
call(Alias, {migrate_encoding, Tab, Encoding, Rpt}).
-spec migration_status(tabname()) -> idle | map().
migration_status(Tab) when is_atom(Tab) ->
case get_ref(Tab, error) of
#{migration := _} = Ref ->
case maps:get(migration_meta, Ref, undefined) of
#{} = Meta -> Meta;
undefined -> #{phase => dual_write}
end;
_ ->
idle
end.
-spec finalize_migration(alias(), tabname()) -> ok | {error, term()}.
finalize_migration(Alias, Tab) ->
call(Alias, {finalize_migration, Tab}).
-spec call(alias() | [], req()) -> no_return() | any().
call(Alias, Req) ->
call(Alias, Req, infinity).
@@ -451,6 +494,15 @@ handle_info({mnesia_table_event, Event}, St) ->
_ ->
{noreply, St}
end;
handle_info({'DOWN', _MRef, process, Pid, Reason},
#st{migrators = Ms} = St) ->
case lists:keyfind(Pid, 2, maps:to_list(Ms)) of
{Tab, Pid} ->
?log(info, "Encoding migrator for ~p exited: ~p", [Tab, Reason]),
{noreply, St#st{migrators = maps:remove(Tab, Ms)}};
false ->
{noreply, St}
end;
handle_info(_Msg, St) ->
{noreply, St}.
@@ -593,16 +645,36 @@ handle_req(Alias, {migrate, Tabs0, Rpt}, Backend, St) ->
{reply, Res, St1};
{error, _} = Error ->
{reply, Error, St}
end.
end;
handle_req(Alias, {migrate_encoding, Tab, Encoding, Rpt}, Backend, St) ->
case start_encoding_migration(Alias, Tab, Encoding, Rpt, Backend, St) of
{ok, St1} ->
{reply, ok, St1};
{error, _} = Error ->
{reply, Error, St}
end;
handle_req(Alias, {finalize_migration, Tab}, Backend, St) ->
case do_finalize_encoding_migration(Alias, Tab, Backend, St) of
{ok, St1} ->
{reply, ok, St1};
{error, _} = Error ->
{reply, Error, St}
end;
handle_req(_Alias, {migration_status, Tab}, _Backend, St) ->
{reply, migration_status(Tab), St}.
handle_load_table_req(Alias, Name, TRec, Backend, St) ->
case create_table_from_trec(Alias, Name, TRec, Backend, St) of
{ok, TRec1, St1} ->
TRec2 = TRec1#{status => open},
St2 = update_cf(Alias, Name, TRec2, St1),
Backend1 = maps:get(Alias, St1#st.backends, Backend),
{TRec3, St2} =
maybe_resume_encoding_migration(
Alias, Name, TRec2, Backend1, St1),
St3 = update_cf(Alias, Name, TRec3, St2),
?log(debug, "Table loaded ~p", [Name]),
put_pt(Name, TRec2),
{reply, {ok, TRec2}, St2};
put_pt(Name, TRec3),
{reply, {ok, TRec3}, St3};
{error, _} = Error ->
{reply, Error, St}
end.
@@ -1158,8 +1230,11 @@ create_table_as_cf(Alias, Name, #{db_ref := DbRef} = R, St) ->
CfName = tab_to_cf_name(Name),
case create_column_family(DbRef, CfName, cfopts(), R) of
{ok, CfH} ->
R1 = check_version_and_encoding(R#{ cf_handle => CfH
, type => column_family }),
R1 = check_version_and_encoding(
R#{ cf_handle => CfH
, type => column_family
, cf_gen => maps:get(cf_gen, R, 0)
, cf_name => CfName }),
{ok, R1, update_cf(Alias, Name, R1, St)};
{error, _} = Error ->
Error
@@ -1363,37 +1438,413 @@ admin_cfs({info, _} = I) -> [ {tab_to_cf_name(I), cfopts()} ].
map_cfs({ok, Ref, CfHandles}, CFs, Alias, Acc) ->
ZippedCFs = lists:zip(CFs, CfHandles),
%% io:fwrite("ZippedCFs = ~p~n", [ZippedCFs]),
CfInfo = maps:from_list(
[{cf_name_to_tab(N, Alias), #{ db_ref => Ref
, cf_handle => H
, alias => Alias
, status => pre_existing
, type => column_family }}
|| {{N,_}, H} <- ZippedCFs]),
%% Group versioned data CFs by logical tab and generation. cf_info keeps one
%% provisional live handle per logical name (highest gen); full map is in
%% data_cf_by_gen for migration resume (source gen may be lower than target).
{CfInfo, ByGen} =
lists:foldl(
fun({{N, _}, H}, {Map, BG}) ->
Logical = cf_name_to_tab(N, Alias),
Gen = case cf_name_to_data_gen(N) of
{ok, _, G} -> G;
error -> 0
end,
Rec = #{ db_ref => Ref
, cf_handle => H
, alias => Alias
, status => pre_existing
, type => column_family
, cf_gen => Gen
, cf_name => N },
BG1 = case is_atom(Logical) of
true ->
GMap0 = maps:get(Logical, BG, #{}),
BG#{Logical => GMap0#{Gen => Rec}};
false ->
BG
end,
Map1 = case maps:find(Logical, Map) of
{ok, #{cf_gen := OldG}}
when is_integer(OldG), OldG > Gen ->
Map;
_ ->
Map#{Logical => Rec}
end,
{Map1, BG1}
end, {#{}, #{}}, ZippedCFs),
{ok, Acc#{ db_ref => Ref
, cf_info => CfInfo }}.
, cf_info => CfInfo
, data_cf_by_gen => ByGen }}.
tab_to_cf_name(Tab) when is_atom(Tab) -> write_term({d, Tab});
%% Column-family naming
%% ---------------------
%% RocksDB requires unique CF names within a DB. Logical table names (atoms)
%% are not enough once we rewrite a table into a new CF (encoding migration):
%% the old and new CFs must coexist until cutover.
%%
%% Data CFs are versioned:
%% gen 0 (legacy / first create): "{d, Tab}"
%% gen N (N >= 1): "{d, Tab, N}"
%%
%% The live generation is stored on the db_ref as `cf_gen` and durably in
%% admin info `{cf_gen, Tab}`. `cf_name_to_tab/2` maps any generation back to
%% the logical table name for open/recovery; when several gens exist, the
%% recorded live gen wins (see map_cfs / resolve_data_cf).
%%
%% Index / retainer / admin CFs are unchanged: one logical resource ↔ one CF.
tab_to_cf_name(Tab) when is_atom(Tab) ->
data_cf_name(Tab, 0);
tab_to_cf_name({admin, Alias}) -> write_term({a, Alias});
tab_to_cf_name({info, Tab}) -> write_term({n, Tab});
tab_to_cf_name({Tab, index, I}) -> write_term({i, Tab, I});
tab_to_cf_name({Tab, retainer, R}) -> write_term({r, Tab, R}).
%% Physical CF name for a data table generation.
data_cf_name(Tab, 0) when is_atom(Tab) ->
write_term({d, Tab});
data_cf_name(Tab, Gen) when is_atom(Tab), is_integer(Gen), Gen > 0 ->
write_term({d, Tab, Gen}).
write_term(T) ->
lists:flatten(io_lib:fwrite("~w", [T])).
cf_name_to_tab(Cf, Alias) ->
case read_term(Cf) of
{ok, {d, Table}} -> Table;
{ok, {i, Table, I}} -> {Table, index, I};
{ok, {r, Table, R}} -> {Table, retainer, R};
{ok, {n, Table}} -> {info, Table};
{ok, {a, Alias}} -> {admin, Alias};
{ok, {d, Table}} -> Table;
{ok, {d, Table, _Gen}} -> Table; %% versioned data CF → logical tab
{ok, {i, Table, I}} -> {Table, index, I};
{ok, {r, Table, R}} -> {Table, retainer, R};
{ok, {n, Table}} -> {info, Table};
{ok, {a, Alias}} -> {admin, Alias};
_ ->
{ext, Alias, Cf}
end.
%% Parse generation from a data CF name string; non-data → error.
cf_name_to_data_gen(Cf) ->
case read_term(Cf) of
{ok, {d, Table}} -> {ok, Table, 0};
{ok, {d, Table, Gen}} -> {ok, Table, Gen};
_ ->
error
end.
%% =====================================================================
%% Online encoding migration (set tables)
%% =====================================================================
start_encoding_migration(Alias, Tab, Encoding0, Rpt, Backend, St)
when is_atom(Tab) ->
case find_cf(Alias, Tab, Backend, St) of
{ok, #{status := open, semantics := bag}} ->
{error, bag_not_supported};
{ok, #{status := open, migration := _}} ->
{error, already_migrating};
{ok, #{status := open} = OldRef} ->
case maps:get(type, OldRef, column_family) of
standalone ->
{error, standalone_not_supported};
column_family ->
start_encoding_migration_(Alias, Tab, Encoding0, Rpt,
OldRef, Backend, St)
end;
{ok, _} ->
{error, not_open};
error ->
{error, not_found}
end;
start_encoding_migration(_, _, _, _, _, _) ->
{error, badarg}.
start_encoding_migration_(Alias, Tab, Encoding0, Rpt, OldRef, Backend, St) ->
As = maps:get(attributes,
maps:get(properties, OldRef, #{}),
[key, val]),
case mnesia_rocksdb_lib:check_encoding(Encoding0, As) of
{ok, NewEnc} ->
case maps:get(encoding, OldRef) of
NewEnc ->
{error, same_encoding};
_OldEnc ->
create_and_start_encoding_mig(
Alias, Tab, NewEnc, Rpt, OldRef, Backend, St)
end;
{error, _} = Err ->
Err
end.
create_and_start_encoding_mig(Alias, Tab, NewEnc, Rpt, OldRef,
#{db_ref := DbRef} = _Backend, St) ->
OldGen = maps:get(cf_gen, OldRef, 0),
NewGen = OldGen + 1,
CfName = data_cf_name(Tab, NewGen),
case create_column_family(DbRef, CfName, cfopts(), OldRef) of
{ok, CfH} ->
%% Target ref: same DB, new versioned CF, new encoding; no migration field.
NewRef0 = maps:without(
[migration, migration_meta, migration_epoch],
OldRef),
NewRef0b = NewRef0#{ cf_handle => CfH
, encoding => NewEnc
, cf_gen => NewGen
, cf_name => CfName
, name => Tab
, type => column_family
, status => open },
%% Persist encoding in user_properties *before* check_version_and_encoding
%% so it is not replaced by the table default.
NewRef1 = update_user_properties(
{mrdb_encoding, NewEnc},
check_version_and_encoding(NewRef0b)),
NewRef = NewRef1#{encoding => NewEnc, cf_gen => NewGen, cf_name => CfName},
Meta = #{ phase => copying
, target => #{encoding => NewEnc, cf_gen => NewGen}
, source_gen => OldGen
, cursor => '$first'
, epoch => 1
, started_at => erlang:system_time(millisecond) },
%% Live ref: dual-write to NewRef; reads still use Old CF handle.
LiveRef = OldRef#{ migration => NewRef
, migration_meta => Meta
, migration_epoch => 1
, cf_gen => OldGen },
write_info(Alias, Tab, encoding_migration, Meta),
%% Keep live encoding as old for correct reads; only target has NewEnc.
%% Schema mrdb_encoding is updated on finalize.
LiveRef2 = LiveRef#{migration := NewRef},
St1 = update_cf(Alias, Tab, LiveRef2, St),
put_pt(Tab, LiveRef2),
{Pid, _MRef} = spawn_monitor(
fun() ->
encoding_migrator(Alias, Tab, LiveRef2,
NewRef, Rpt)
end),
rpt(Rpt, "Started encoding migration ~p gen ~p -> ~p (~s)~n",
[Tab, OldGen, NewGen, CfName]),
{ok, St1#st{migrators = maps:put(Tab, Pid, St#st.migrators)}};
{error, _} = Err ->
Err
end.
%% Re-attach dual-write (and copier) after restart when durable meta says so.
maybe_resume_encoding_migration(Alias, Name, LiveRef, Backend, St)
when is_atom(Name) ->
case read_info(Alias, Name, encoding_migration, undefined) of
#{phase := Phase, target := #{encoding := Enc, cf_gen := NewGen}} = Meta
when Phase =:= copying; Phase =:= copy_done ->
SourceGen = maps:get(source_gen, Meta, NewGen - 1),
ByGen = maps:get(data_cf_by_gen, Backend, #{}),
TabGens = maps:get(Name, ByGen, #{}),
case {maps:find(SourceGen, TabGens), maps:find(NewGen, TabGens)} of
{{ok, SrcRec}, {ok, TgtRec}} ->
resume_encoding_migration_(
Alias, Name, LiveRef, Meta, Enc, NewGen, SourceGen,
SrcRec, TgtRec, Phase, St);
_ ->
?log(warning,
"encoding migration meta for ~p but CFs missing "
"(source=~p target=~p gens=~p)",
[Name, SourceGen, NewGen, maps:keys(TabGens)]),
{LiveRef, St}
end;
_ ->
%% Completed migration: ensure live handle matches durable cf_gen.
maybe_align_cf_gen(Alias, Name, LiveRef, Backend, St)
end;
maybe_resume_encoding_migration(_, _, LiveRef, _, St) ->
{LiveRef, St}.
maybe_align_cf_gen(Alias, Name, LiveRef, Backend, St) ->
case read_info(Alias, Name, cf_gen, undefined) of
Gen when is_integer(Gen) ->
ByGen = maps:get(data_cf_by_gen, Backend, #{}),
case maps:find(Gen, maps:get(Name, ByGen, #{})) of
{ok, Rec} ->
Live1 = maps:merge(
LiveRef,
maps:with([cf_handle, cf_gen, cf_name, db_ref],
Rec)),
{Live1, St};
error ->
{LiveRef, St}
end;
_ ->
{LiveRef, St}
end.
resume_encoding_migration_(Alias, Name, LiveRef, Meta, Enc, NewGen, SourceGen,
SrcRec, TgtRec, Phase, St) ->
%% Live reads use source gen CF + old encoding (from LiveRef / schema).
Live0 = maps:merge(
LiveRef,
maps:with([cf_handle, cf_name, db_ref], SrcRec)),
Live1 = Live0#{cf_gen => SourceGen, status => open},
NewRef0 = maps:without(
[migration, migration_meta, migration_epoch], LiveRef),
NewRef = NewRef0#{ cf_handle => maps:get(cf_handle, TgtRec)
, cf_name => maps:get(cf_name, TgtRec)
, cf_gen => NewGen
, encoding => Enc
, db_ref => maps:get(db_ref, TgtRec)
, name => Name
, status => open },
NewRef1 = update_user_properties({mrdb_encoding, Enc}, NewRef),
NewRef2 = NewRef1#{encoding => Enc},
Meta1 = Meta#{phase => Phase},
Live2 = Live1#{ migration => NewRef2
, migration_meta => Meta1
, migration_epoch => maps:get(epoch, Meta, 1) },
put_pt(Name, Live2),
St1 = case Phase of
copying ->
case maps:get(Name, St#st.migrators, undefined) of
Pid when is_pid(Pid) ->
St;
_ ->
{Pid, _} = spawn_monitor(
fun() ->
encoding_migrator(
Alias, Name, Live2, NewRef2,
undefined)
end),
St#st{migrators = maps:put(Name, Pid, St#st.migrators)}
end;
copy_done ->
St
end,
{Live2, St1}.
%% Background copier: walk old CF, install into new if missing (no clobber).
encoding_migrator(Alias, Tab, OldRef, NewRef, Rpt) ->
try
Chunk = 500,
N = encoding_copy_loop(OldRef, NewRef, '$first', 0, Chunk, Rpt),
Meta = #{ phase => copy_done
, target => #{encoding => maps:get(encoding, NewRef)}
, cursor => '$end'
, epoch => 1
, copied => N
, finished_at => erlang:system_time(millisecond) },
%% Update live PT meta to copy_done (keep dual-write).
case get_ref(Tab, error) of
#{migration := _} = Live ->
put_pt(Tab, Live#{migration_meta => Meta});
_ ->
ok
end,
write_info(Alias, Tab, encoding_migration, Meta),
rpt(Rpt, "Encoding migration copy done for ~p (~p objs)~n", [Tab, N]),
ok
catch
C:R:ST ->
?log(error, "encoding_migrator ~p failed: ~p:~p / ~p",
[Tab, C, R, ST]),
error({C, R})
end.
encoding_copy_loop(OldRef, NewRef, Cursor, N, Chunk, Rpt) ->
{Batch, Cont} = encoding_select_chunk(OldRef, Cursor, Chunk),
N1 = lists:foldl(
fun(Obj, Acc) ->
encoding_maybe_install(OldRef, NewRef, Obj),
Acc + 1
end, N, Batch),
case Cont of
'$end_of_table' ->
N1;
NextCursor ->
maybe_progress(Rpt, N1),
encoding_copy_loop(OldRef, NewRef, NextCursor, N1, Chunk, Rpt)
end.
%% Chunked select on the old CF. Continuations use mrdb:select/1 when available.
encoding_select_chunk(OldRef, '$first', Limit) ->
case mrdb:select(OldRef, [{'_', [], ['$_']}], Limit) of
{L, Cont} when is_list(L) ->
{L, {cont, Cont}};
L when is_list(L) ->
{L, '$end_of_table'};
'$end_of_table' ->
{[], '$end_of_table'}
end;
encoding_select_chunk(_OldRef, {cont, Cont}, _Limit) ->
case mrdb:select(Cont) of
{L, Cont1} when is_list(L) ->
{L, {cont, Cont1}};
L when is_list(L) ->
{L, '$end_of_table'};
'$end_of_table' ->
{[], '$end_of_table'}
end;
encoding_select_chunk(OldRef, _Other, Limit) ->
%% Fallback: restart from first (safe, may re-process; install is idempotent).
encoding_select_chunk(OldRef, '$first', Limit).
encoding_maybe_install(OldRef, NewRef, Obj) ->
Name = maps:get(name, OldRef),
KP = mnesia_rocksdb_lib:keypos(Name),
Key = element(KP, Obj),
OldOnly = maps:without(
[migration, migration_meta, migration_epoch], OldRef),
%% Live re-check on old (source of truth for reads during migration).
case mrdb:read(OldOnly, Key) of
[] ->
ok; %% deleted after we observed Obj
[LiveObj] ->
case mrdb:read(NewRef, Key) of
[_] ->
ok; %% dual-write already installed
[] ->
mrdb:insert(NewRef, LiveObj)
end
end.
do_finalize_encoding_migration(Alias, Tab, _Backend, St) ->
%% Prefer PT (migrator updates phase there) over gen_server state.
case get_ref(Tab, error) of
#{migration := MigRef} = LiveRef ->
Meta = maps:get(migration_meta, LiveRef, #{}),
case maps:get(phase, Meta, undefined) of
copy_done ->
finalize_encoding_migration_(Alias, Tab, LiveRef, MigRef, St);
Phase ->
{error, {not_ready, Phase}}
end;
error ->
{error, not_found};
_ ->
{error, not_migrating}
end.
%% Cutover: make the versioned target CF the live one and drop the old CF.
%% No second copy — the migration target *is* the new generation.
finalize_encoding_migration_(Alias, Tab, LiveRef, MigRef, St) ->
#{db_ref := DbRef, cf_handle := OldCfH} = LiveRef,
NewEnc = maps:get(encoding, MigRef),
NewGen = maps:get(cf_gen, MigRef, maps:get(cf_gen, LiveRef, 0) + 1),
LiveNew = maps:without(
[migration, migration_meta, migration_epoch],
MigRef#{ name => Tab
, status => open
, encoding => NewEnc
, cf_gen => NewGen }),
LiveNew1 = update_user_properties({mrdb_encoding, NewEnc}, LiveNew),
put_pt(Tab, LiveNew1),
St1 = update_cf(Alias, Tab, LiveNew1, St),
%% Durable live generation + schema encoding for restart.
write_info(Alias, Tab, cf_gen, NewGen),
delete_info(Alias, Tab, encoding_migration),
_ = maybe_write_user_props(LiveNew1),
%% Drop previous generation CF (by handle; name may be {d,Tab} or {d,Tab,G}).
ok = rocksdb:drop_column_family(DbRef, OldCfH),
catch rocksdb:destroy_column_family(DbRef, OldCfH),
drop_cached_cf(maps:get(cf_name, LiveRef,
data_cf_name(Tab, maps:get(cf_gen, LiveRef, 0))),
OldCfH),
St2 = St1#st{migrators = maps:remove(Tab, St1#st.migrators)},
{ok, St2}.
read_term(Str) ->
case erl_scan:string(Str) of
{ok, Tokens, _} ->
+29 -6
View File
@@ -811,8 +811,12 @@ insert_(#{semantics := bag} = Ref, Key, EncKey, EncVal, Obj, Opts) ->
batch_if_index(Ref, insert, bag, fun insert_bag/5, Key, EncKey, EncVal, Obj, Opts);
%% insert_bag(Ref, Obj, Opts);
insert_(Ref, Key, EncKey, EncVal, Obj, Opts) ->
batch_if_index(Ref, insert, set, fun insert_set/5, Key, EncKey, EncVal, Obj, Opts).
%% insert_set(Ref, Obj, Opts).
%% Close over Key/Obj so dual-write can re-encode into migration target CF.
F = fun(R, EK, EV, _Ix, Os) ->
rdb_put(R, EK, EV, Os),
dual_put(R, Key, Obj, Os)
end,
batch_if_index(Ref, insert, set, F, Key, EncKey, EncVal, Obj, Opts).
insert_set(Ref, EncKey, EncVal, _, Opts) ->
rdb_put(Ref, EncKey, EncVal, Opts).
@@ -822,6 +826,19 @@ insert_bag(Ref, EncKey, EncVal, _, Opts) ->
%% #{vsn := 1} ->
insert_bag_v1(Ref, EncKey, EncVal, Opts).
%% During online encoding migration, mirror set put/delete onto the target CF.
dual_put(#{migration := Mig0} = R, Key, Obj, Opts) ->
Mig = maps:merge(Mig0, maps:with([activity, snapshot], R)),
rdb_put(Mig, encode_key(Key, Mig), encode_val(Obj, Mig), Opts);
dual_put(_, _, _, _) ->
ok.
dual_delete(#{migration := Mig0} = R, Key, Opts) ->
Mig = maps:merge(Mig0, maps:with([activity, snapshot], R)),
rdb_delete(Mig, encode_key(Key, Mig), Opts);
dual_delete(_, _, _) ->
ok.
batch_if_index(#{mode := mnesia} = Ref, _, _, F, _Key, EncKey, Data, _Obj, Opts) ->
F(Ref, EncKey, Data, undefined, Opts);
batch_if_index(#{name := Name, properties := #{index := [_|_] = Ixs}} = Ref,
@@ -1264,7 +1281,11 @@ delete(Tab, Key, Opts) ->
delete_(#{semantics := bag} = Ref, Key, EncKey, Opts) ->
batch_if_index(Ref, delete, bag, fun delete_bag/5, Key, EncKey, [], [], Opts);
delete_(Ref, Key, EncKey, Opts) ->
batch_if_index(Ref, delete, set, fun delete_set/5, Key, EncKey, [], [], Opts).
F = fun(R, EK, _D, _Ix, Os) ->
rdb_delete(R, EK, Os),
dual_delete(R, Key, Os)
end,
batch_if_index(Ref, delete, set, F, Key, EncKey, [], [], Opts).
delete_object(Tab, Obj) ->
delete_object(Tab, Obj, []).
@@ -1598,15 +1619,17 @@ do_del_obj_bag_(Sz, K, Res, Obj, #{name := Name} = Ref, I, Opts) ->
delete_obj_set(_, _, _, not_found, _) ->
ok;
delete_obj_set(Ref, _, _, RawKey, Opts) when is_binary(RawKey) ->
rdb_delete(Ref, RawKey, Opts);
delete_obj_set(#{name := Name} = Ref, _, Obj, RawKey, Opts) when is_binary(RawKey) ->
rdb_delete(Ref, RawKey, Opts),
dual_delete(Ref, element(keypos(Name), Obj), Opts);
delete_obj_set(#{name := Name} = Ref, EncKey, Obj, _, Opts) ->
case rdb_get(Ref, EncKey, []) of
{ok, Bin} ->
Key = element(keypos(Name), Obj),
case decode_val(Bin, Key, Ref) of
Obj ->
rdb_delete(Ref, EncKey, Opts);
rdb_delete(Ref, EncKey, Opts),
dual_delete(Ref, Key, Opts);
_ ->
ok
end;