anchor.diff:      [].
   
   %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  +%% Fragment handling
  +%%
  +%% We can alter two things at the moment:
  +%% - the table node pool
  +%% - the table fragments
  +%%
  +%% NB: We adjust the fragment status after everything else at the moment,
  +%% so there may be a lot of shuffling going on during a migration.
  +%%
  +%% Fragmentation options are given as:
  +%%
  +%% {frag_properties, FragProps} = list of the following
  +%%   (FragProps = [] means not fragmented)
  +%%
  +%% {n_fragments, N}
  +%% {node_pool, Nodes}
  +%% {n_ram_copies, N}          num replicas of type per fragment
  +%% {n_disc_copies, N}         dito
  +%% {n_disc_only_copies, N}    dito
  +%% {foreign_key, {ForeignTab, Attr}}  
  +%% {hash_module, M}   M module
  +%% {hash_state, St}   St term
  +%%
  +%% NB: we can vary node pool and fragments at runtime, the rest have
  +%% no explicit operations for this. I THINK we can do it by deactivating
  +%% fragmentation, then activating with new values.
  +%%
  +%% UNFINISHED
  +%% - we should perhaps look at the {frag_properties, Props} item instead
  +%% - change hash function and other misc fragmentation specific stuff
  +%%   * there's some of it, isn't there?
  +%% - order fragmentation ops with the others to get better behaviour
  +%% - this code may be a bit redundant, consider refactoring
  +
  +table_fragment_changes(Tab, Old_opts, New_opts) ->
  +    FragProps0 = proplists:get_value(frag_properties, Old_opts, []),
  +    FragProps1 = proplists:get_value(frag_properties, New_opts, []),
  +    case {FragProps0, FragProps1} of
  +	{[], []} ->
  +	    %% Table not fragmented
  +	    [];
  +	{[], Opts} when Opts =/= [] ->
  +	    %% Table has become fragmented
  +	    [{mnesia, change_table_frag, [Tab, {activate, FragProps1}]}];
  +	{Opts, []} when Opts =/= [] ->
  +	    %% Table was fragmented, make it unfragmented
  +	    [{mnesia, change_table_frag, [Tab, deactivate]}];
  +	{_Fr0, _Fr1} ->
  +	    %% Table was and remains fragmented, see what to do
  +	    case need_full_frag_change(FragProps0, FragProps1) of
  +		true ->
  +		    [{comment, "Requires full fragmentation change"},
  +		     {mnesia, change_table_frag, [Tab, deactivate]},
  +		     {mnesia, change_table_frag, [Tab, {activate, FragProps1}]}];
  +		false ->
  +		    alter_node_pool(
  +		      Tab, FragProps0, FragProps1,
  +		      alter_fragments(Tab, FragProps0, FragProps1, []))
  +	    end
  +    end.
  +
  +%% need_full_frag_change: we have a number of "sensitive" options.
  +%% If their values differ in the two lists, we need a full change. Otherwise,
  +%% we're good.
  +
  +need_full_frag_change(Opts0, Opts1) ->
  +    D0 = dict:from_list([{Opt, {Val, undefined}} || {Opt, Val} <- Opts0 ]),
  +    D1 = dict:from_list([{Opt, {undefined, Val}} || {Opt, Val} <- Opts1 ]),
  +    D2 = dict:merge(
  +	   fun(Key, {Val0, undefined}, {undefined, Val1}) ->
  +		   {Val0, Val1}
  +	   end,
  +	   D0,
  +	   D1),
  +    Opts = dict:to_list(D2),
  +    lists:any(fun ff_change_opt/1, Opts).
  +
  +%% If the options are safe, or the values are the same, full frag change is not
  +%% needed. Otherwise, it is needed.
  +
  +ff_change_opt({node_pool, _}) ->
  +    false;
  +ff_change_opt({n_fragments, _}) ->
  +    false;
  +ff_change_opt({_Opt, {Val, Val}}) ->
  +    false;
  +ff_change_opt({_Opt, {Val0, Val1}}) when Val0 =/= Val1 ->
  +    true.
  +
  +%% look through the frag_properties and see whether nodes need to be changed
  +
  +alter_node_pool(Tab, Old_opts, New_opts, RestActions) ->
  +    OldPool = proplists:get_value(node_pool, Old_opts, []),
  +    NewPool = proplists:get_value(node_pool, New_opts, []),
  +    schematool_frag:alter_node_pool(Tab, OldPool, NewPool, RestActions).
  +
  +%% look through frag_properies and alter the number of fragments,
  +%% if needed
  +
  +alter_fragments(Tab, FragOpts0, FragOpts1, RestActions) ->
  +    OldF = proplists:get_value(n_fragments, FragOpts0, 1),
  +    NewF = proplists:get_value(n_fragments, FragOpts1, 1),
  +    if
  +	OldF == NewF ->
  +	    RestActions;
  +	true ->
  +	    [{schematool_helper, adjust_fragments, [Tab, OldF, NewF]}|RestActions]
  +    end.
  +
  +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
   %% We need to split this into two:
   %% - migration planner
   %%   = decide how to do the migration (cost-based)
  
positive.code: %%% Description : 
  %%% Created :  2 May 2014 by Thomas Lindgren <>
  
  %% NOTE: use schematool.erl as the entrypoint.
  %%
  %% We have, in essence:
  %% - table added
  %% - table deleted
  %% - table changed
  %%
  %% New table migration thing, based on table copying.  Failsafe
  %% approach: table copying (perhaps twice).  Try to detect situations
  %% when you can do better. In some cases, you can just flip some
  %% options or add/del indexes or table copies. In other cases, like a
  %% table layout change, you can do a mnesia table rewrite. In the
  %% worst case, you will have to copy the table twice.
  %%
  %% Copying twice is needed because we usually need to keep the table
  %% name, and there is no mnesia rename operation. (Sigh.)
  %%
  %% The copy operation is staged depending on what is being done. In
  %% essence, we want to minimize the cost in time and space. The
  %% approach should be dynamically decided based on table size.
  %%
  %% Schematool should perhaps provide a way to DYNAMICALLY resolve
  %% table names to tables. That way, the second copy is not needed
  %% (just redirect the name to the new table).
  
  -module(schematool2).
  
  %% What are the dimensions that may change for a table?
  %% - table nodes
  %% - fragmentation
  %% - layout
  %% - indexes
  %% - type, storage type
  %% - simple options
  %% - difficult options [need copying]
  %%
  %% Typical worst case (Src -> Dst):
  %% 1/ create Tmp, compatible with leastcost(Src, Dst)
  %% 2/ copy all records from Src to Tmp
  %% 3/ create Dst (if Src = Dst, delete Src then recreate)
  %% 4/ copy all records from Tmp to Dst
  %% 5/ delete Tmp
  %%
  %% Table planning:
  %% - should Tmp be compatible with Src or Dst?
  %% - what does "compatible" mean?
  %%   * "bare bones" to hold records cheaply and cheerfully
  %%   * represent records faithfully
  %%     = either old or new layout
  %%   * any storage option, ram_copies should be fastest
  %%   * no extra indexes
  %%   * type: ... ("compatible")
  %%   * table nodes: use a single node? 
  %%   * simple and difficult options: skip as many as possible
  %% - when to do layout transformation?
  %% - when to create indexes?
  %%
  %% Fragmentation handling? This can apparently be done
  %% on the fly, see the existing table.
  %%
  %% Special cases:
  %% - transform layout
  %% - change options of existing table
  %% - both layout and options
  %% - add or delete table copy
  
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %% Return what strategy is needed
  %% - full copy
  %% - layout transform
  %% - options change
  %% - layout + options
  %%
  %% Layout change is defined by record name and attributes (and defaults).
  %%
  
negative.code: %% fill in the defaults if the value is missing, ie the attribute is
  %% new.
  %%
  %% UNFINISHED
  %% - user-defined attribute transforms not done yet
  
  layout_transform(Tab, Opts0, Opts1) ->
      Rec0a = {R0, As0} = rec_info(Tab, Opts0),
  