anchor.diff:      truncate(Tab, NextKey, 0, M + 1);
   truncate(Tab, Key, N, 0) ->
       truncate(Tab, mnesia:prev(Tab, Key), N - 1, 0).
  +
  +
  +%%
  +%% Tests
  +%%
  +
  +shutdown_child(Pid) ->
  +    exit(Pid, shutdown),
  +    receive
  +        {'EXIT', Pid, shutdown} ->
  +            ok;
  +        {'EXIT', Pid, Reason} ->
  +            exit({shutdown_failed, Reason})
  +    after 5000 ->
  +            ?log_error("Mnesia shutdown timed out", []),
  +            exit(Pid, kill),
  +            exit(shutdown_timeout)
  +    end.
  +
  +
  +startup_test() ->
  +    file:delete(backup_file()),
  +    process_flag(trap_exit, true),
  +    Node = node(),
  +    ok = mnesia:delete_schema([Node]),
  +    {ok, Pid} = start_link(),
  +    yes = mnesia:system_info(is_running),
  +    [Node] = mnesia:table_info(schema, disc_copies),
  +    receive
  +        {'EXIT', Pid, Reason} ->
  +            exit({child_exited, Reason})
  +    after 0 -> ok
  +    end,
  +    shutdown_child(Pid).
  +
  +
  +decluster_test() ->
  +    file:delete(backup_file()),
  +    process_flag(trap_exit, true),
  +    Node = node(),
  +    ok = mnesia:delete_schema([Node]),
  +    {ok, Pid} = start_link(),
  +    %% Create a disk-based table to make sure it gets removed
  +    ensure_table(test_disc, []),
  +    ensure_table(test_local, [{local_content, true}]),
  +    gen_server:call(?MODULE, demote_self),
  +    ?assertEqual([schema, test_local],
  +                 lists:sort(mnesia:system_info(local_tables))),
  +    shutdown_child(Pid).
  
positive.code: 
  -include("ns_common.hrl").
  
  -behaviour(gen_server).
  
  -record(state, {}).
  
  %% API
  -export([delete_schema/0,
           ensure_table/2,
           prepare_rename/0, rename_node/2,
           start_link/0,
           truncate/2]).
  
negative.code:     {ok, State}.
  
  
  handle_call({demote, Node}, _From, State) ->
      %% Remove a node from the mnesia cluster after it's been disconnected.
      {atomic, ok} = mnesia:del_table_copy(schema, Node),
      {reply, ok, State};
  
  handle_call(demote_self, _From, State) ->
      %% Back up, remove the schema from disk, then restart. The
      %% initialization routines will handle declustering.
      case mnesia:system_info(db_nodes) == [node()]
          andalso non_local_tables() == [] of
          true ->
              %% No-op if it's not a peer
              {reply, ok, State};
          false ->
              backup(),
              stopped = mnesia:stop(),
              ok = mnesia:delete_schema([node()]),
              ?log_info("Demoting. Deleted local schema. Mnesia info:~n~p",
                        [mnesia:system_info(all)]),
              {ok, State1} = init([]),
              {reply, ok, State1}
      end;
  
  handle_call({promote_self, Node}, _From, State) ->
      case mnesia:system_info(db_nodes) of
          [N] when N == node() ->
              %% Even though we use local_content tables, it's impossible to
              %% merge them, so instead we backup and restore.
              ?log_info("Promoting this node to a peer.", []),
              Tables = backup(),
              stopped = mnesia:stop(),
              ok = mnesia:delete_schema([node()]),
              start_mnesia(),
              %% Connect to an existing peer
              {ok, [Node]} = mnesia:change_config(extra_db_nodes, [Node]),
              %% Write the schema to disk
              ensure_schema(),
              ?log_info("Restoring tables. Mnesia config:~n~p",
                        [mnesia:system_info(all)]),
              KeepTables = mnesia:system_info(tables),
              CreateTables = Tables -- KeepTables,
              %% Restore our old data to the new tables
              {atomic, _} = mnesia:restore(
                              backup_file(),
                              [{keep_tables, KeepTables},
                               {recreate_tables, CreateTables}]),
              ok = file:delete(backup_file());
          _ ->
              %% Only promote if we're not already a peer
              ?log_error("Attempt to promote even though we're already a peer.",
                         []),
              ok
      end,
      {reply, ok, State};
  
  handle_call(peers, _From, State) ->
      Reply = try mnesia:table_info(config, disc_copies)
              catch exit:{aborted, {no_exists, _, _}} -> []
              end,
      {reply, Reply, State};
  
  handle_call(prepare_rename, _From, State) ->
      backup(),
      {reply, ok, State};
  
  handle_call({ensure_table, TableName, Opts}, _From, State) ->
      try mnesia:table_info(TableName, disc_copies) of
  