Skip to content
This repository
Fetching contributors…

Cannot retrieve contributors at this time

file 149 lines (143 sloc) 5.855 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.

-module(couch_db_consistency_check).

-export([check_db_file/1]).

-include("couch_db.hrl").

check_db_file(Filename) when is_list(Filename) or is_binary(Filename)->
    Fd = case couch_file:open(Filename) of
    {ok, Fd0} ->
        Fd0;
    Error ->
        fatal_error("Couldn't open file ~s:~p", [Filename, Error])
    end,
    Header = try
        {ok, NewHeaderBin} = couch_file:read_header_bin(Fd),
        couch_db_updater:header_bin_to_db_header(NewHeaderBin)
    catch
    Type0:Error0 ->
        fatal_error("Problem reading header ~s:~p", [Filename, {Type0,Error0}])
    end,
    Db = try
        couch_db_updater:init_db(Filename, Filename, Fd, Header, [])
    catch
    Type1:Error1 ->
        fatal_error("Problem initializing DB ~s:~p", [Filename, {Type1,Error1}])
    end,
    check_db_file(Db),
    couch_file:close(Fd);
check_db_file(Db) ->
    Filename = Db#db.filepath,
    ?LOG_INFO("Doing consistency check for db file \"~s\"", [Filename]),
    % first scan the by_sequence index
    Count = couch_db:count_changes_since(Db, 0),
    EtsById = ets:new(couch_db_consistency_check_name, [set,private]),
    {ok, {_,EncounteredCount}} = couch_db:changes_since(Db, 0,
        fun(DocInfo, {PrevSeq, Count0}) ->
            if PrevSeq > DocInfo#doc_info.local_seq ->
                fatal_error("Out of order seq found in by_seq idex ~s old ~p new ~p",
                    [Filename, PrevSeq, DocInfo#doc_info.local_seq]);
            true ->
                ok
            end,
            case ets:insert_new(EtsById, {DocInfo#doc_info.id, DocInfo}) of
            false ->
                fatal_error("Duplicate id found in by_seq idex ~s ~s",
                    [Filename, DocInfo#doc_info.id]);
            true ->
                ok
            end,
            if DocInfo#doc_info.body_ptr /= 0 ->
                {ok, Body} = try
                    {ok, _} = couch_file:pread_iolist(Db#db.fd,
                            DocInfo#doc_info.body_ptr)
                catch
                Type2:Error2 ->
                    fatal_error("Problem reading doc DB ~s id ~s: ~p",
                            [Filename, DocInfo#doc_info.id, {Type2,Error2}])
                end,
                if (DocInfo#doc_info.content_meta band
                        ?CONTENT_META_SNAPPY_COMPRESSED) > 0 ->
                    Body2 = try
                            couch_compress:decompress(Body)
                    catch
                    Type3:Error3 ->
                        fatal_error("Problem decompressing doc DB ~s id ~s: ~p",
                                [Filename, DocInfo#doc_info.id, {Type3,Error3}])
                    end;
                true ->
                    Body2 = Body
                end,
                if (DocInfo#doc_info.content_meta band
                        (bnot ?CONTENT_META_JSON)) == ?CONTENT_META_JSON ->
                    try
                        ?JSON_DECODE(Body2)
                    catch
                    Type4:Error4 ->
                        fatal_error("Problem validitate json DB ~s id ~s: ~p",
                                [Filename, DocInfo#doc_info.id, {Type4,Error4}])
                    end;
                true ->
                    ok
                end;
            true ->
                ok
            end,
            {ok, {DocInfo#doc_info.local_seq, Count0 + 1}}
        end, {0,0}),
    if EncounteredCount /= Count ->
        fatal_error("Count of by seq index is not the same as processed DB ~s ~p",
                [Filename, Count]);
    true ->
        ok
    end,
    {ok, _NonDeletedByIdCount, {_Id, ByIdCount}} = couch_db:enum_docs(Db,
        fun(DocInfo, {PrevId, ByIdCountAcc}) ->
            [{_, DocInfo2}] = ets:lookup(EtsById, DocInfo#doc_info.id),
            if DocInfo == DocInfo2 ->
                ok;
            true ->
                fatal_error("For file ~s found doc_info in by_id (~p) that"
                        " differs from the by_seq entry (~p)",
                        [Filename, DocInfo, DocInfo2])
            end,
            if PrevId < DocInfo#doc_info.id ->
                ok;
            true ->
                fatal_error("For file ~s found doc_info in by_id (~p) that"
                        " is in incorrect order",
                        [Filename, DocInfo])
            end,
            true = ets:delete(EtsById, DocInfo#doc_info.id),
            {ok, {DocInfo#doc_info.id, ByIdCountAcc + 1}}
        end, {0, 0}, []), % first docid is num, as sorts lower than all strings
    case ets:tab2list(EtsById) of
    [] ->
        ok;
    LeftOverDocInfos ->
        fatal_error("Not all docs in by_seq index were found in the by id index ~s ~p",
            [Filename, LeftOverDocInfos])
    end,
    ets:delete(EtsById),
    if ByIdCount /= Count ->
        fatal_error("Count of by id index is not the same as by seq DB ~s ~p ~p",
            [Filename, ByIdCount, Count]);
    true ->
        ok
    end.

-spec fatal_error(list(), list()) -> no_return().
fatal_error(Fmt, Args) ->
    fatal_error(io_lib:format(Fmt, Args)).

-spec fatal_error(list()) -> no_return().
fatal_error(Msg) ->
    Msg2 = lists:flatten(Msg),
    ?LOG_ERROR("couch_db_consistency_check fatal error: ~s",[Msg2]),
    erlang:halt("couch_db_consistency_check fatal error: " ++ Msg2).
Something went wrong with that request. Please try again.