xref: /5.5.2/couchstore/programs/dbdiff/dbdiff.cc (revision 3edac8da)
1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2#include "config.h"
3
4#include <libcouchstore/couch_db.h>
5
6#include <getopt.h>
7#include <cctype>
8#include <cstdlib>
9#include <cstring>
10
11static int quiet = 0;
12struct compare_context {
13    Db* self;
14    DbInfo self_info;
15    Db* other;
16    DbInfo other_info;
17    int diff;
18};
19
20static void usage() {
21    fprintf(stderr, "USAGE: dbdiff [-q] file1 file2\n");
22    fprintf(stderr, "   -q\tquiet\n");
23    exit(EXIT_FAILURE);
24}
25
26static int is_printable_key(sized_buf key) {
27    size_t ii;
28    for (ii = 0; ii < key.size; ++ii) {
29        if (!isprint(key.buf[ii])) {
30            return 0;
31        }
32    }
33
34    return 1;
35}
36
37static void print_key(sized_buf key) {
38    if (is_printable_key(key)) {
39        fwrite(key.buf, 1, key.size, stderr);
40    } else {
41        size_t ii;
42        for (ii = 0; ii < key.size; ++ii) {
43            fprintf(stderr, "0x%02x", int(key.buf[ii]));
44        }
45    }
46}
47
48static void print_missing(sized_buf key, const char* fname) {
49    if (!quiet) {
50        fprintf(stderr, "Document \"");
51        print_key(key);
52        fprintf(stderr, "\" is missing from \"%s\"\n", fname);
53    }
54}
55
56static void compare_docinfo(compare_context* ctx, DocInfo* a, DocInfo* b) {
57    if (a->db_seq != b->db_seq) {
58        if (!quiet) {
59            fprintf(stderr, "Document db_seq differs for \"");
60            print_key(a->id);
61            fprintf(stderr,
62                    "\": %" PRIu64 " - %" PRIu64 "\n",
63                    a->db_seq,
64                    b->db_seq);
65            ctx->diff = 1;
66        }
67    }
68
69    if (a->rev_seq != b->rev_seq) {
70        if (!quiet) {
71            fprintf(stderr, "Document rev_seq differs for \"");
72            print_key(a->id);
73            fprintf(stderr,
74                    "\": %" PRIu64 " - %" PRIu64 "\n",
75                    a->rev_seq,
76                    b->rev_seq);
77            ctx->diff = 1;
78        }
79    }
80
81    if (a->rev_meta.size != b->rev_meta.size) {
82        if (!quiet) {
83            fprintf(stderr, "Document rev_meta size differs for \"");
84            print_key(a->id);
85            fprintf(stderr,
86                    "\": %" PRIu64 " - %" PRIu64 "\n",
87                    (uint64_t)a->rev_meta.size,
88                    (uint64_t)b->rev_meta.size);
89            fprintf(stderr, "\"\n");
90            ctx->diff = 1;
91        }
92    } else if (memcmp(a->rev_meta.buf, b->rev_meta.buf, a->rev_meta.size) !=
93               0) {
94        if (!quiet) {
95            fprintf(stderr, "Document rev_meta differs for \"");
96            print_key(a->id);
97            fprintf(stderr, "\"\n");
98            ctx->diff = 1;
99        }
100    }
101
102    if (a->deleted != b->deleted) {
103        if (!quiet) {
104            fprintf(stderr, "Document deleted status differs for \"");
105            print_key(a->id);
106            fprintf(stderr, "\": %u - %u\n", a->deleted, b->deleted);
107            ctx->diff = 1;
108        }
109    }
110
111    if (a->content_meta != b->content_meta) {
112        if (!quiet) {
113            fprintf(stderr, "Document content_meta differs for \"");
114            print_key(a->id);
115            fprintf(stderr,
116                    "\": %02x - %02x\n",
117                    a->content_meta,
118                    b->content_meta);
119            ctx->diff = 1;
120        }
121    }
122
123    if (a->size != b->size) {
124        if (!quiet) {
125            fprintf(stderr, "Document size differs for \"");
126            print_key(a->id);
127            fprintf(stderr,
128                    "\": %" PRIu64 " - %" PRIu64 "\n",
129                    (uint64_t)a->size,
130                    (uint64_t)b->size);
131            ctx->diff = 1;
132        }
133    }
134}
135
136static void compare_documents(compare_context* ctx,
137                              DocInfo* this_doc_info,
138                              DocInfo* other_doc_info) {
139    couchstore_error_t e1, e2;
140    Doc *d1, *d2;
141
142    if (this_doc_info->deleted) {
143        return;
144    }
145
146    e1 = couchstore_open_document(
147            ctx->self, this_doc_info->id.buf, this_doc_info->id.size, &d1, 0);
148    e2 = couchstore_open_document(ctx->other,
149                                  other_doc_info->id.buf,
150                                  other_doc_info->id.size,
151                                  &d2,
152                                  0);
153
154    if (e1 == COUCHSTORE_SUCCESS && e2 == COUCHSTORE_SUCCESS) {
155        if (d1->data.size != d2->data.size) {
156            ctx->diff = 1;
157            if (!quiet) {
158                fprintf(stderr, "Document \"");
159                print_key(this_doc_info->id);
160                fprintf(stderr, "\" differs in size!\n");
161            }
162        } else if (memcmp(d1->data.buf, d2->data.buf, d1->data.size) != 0) {
163            ctx->diff = 1;
164            if (!quiet) {
165                fprintf(stderr, "Document \"");
166                print_key(this_doc_info->id);
167                fprintf(stderr, "\" content differs!\n");
168            }
169        }
170        couchstore_free_document(d1);
171        couchstore_free_document(d2);
172    } else {
173        fprintf(stderr,
174                "Failed to open document from this\n this: %s\n other: %s\n",
175                couchstore_strerror(e1),
176                couchstore_strerror(e2));
177        exit(EXIT_FAILURE);
178    }
179}
180
181static int deep_compare(Db* db, DocInfo* docinfo, void* c) {
182    auto* ctx = reinterpret_cast<compare_context*>(c);
183    DocInfo* other_doc_info;
184    couchstore_error_t err;
185
186    err = couchstore_docinfo_by_id(
187            ctx->other, docinfo->id.buf, docinfo->id.size, &other_doc_info);
188
189    if (err == COUCHSTORE_SUCCESS) {
190        /* verify that the docinfos are the same.. */
191        compare_docinfo(ctx, docinfo, other_doc_info);
192        compare_documents(ctx, docinfo, other_doc_info);
193        couchstore_free_docinfo(other_doc_info);
194    } else {
195        ctx->diff = 1;
196        print_missing(docinfo->id, ctx->other_info.filename);
197    }
198
199    return 0;
200}
201
202static int check_existing(Db* db, DocInfo* docinfo, void* c) {
203    auto* ctx = reinterpret_cast<compare_context*>(c);
204    couchstore_error_t err;
205    DocInfo* other_info;
206
207    // This function will be called for all docs, including those which are
208    // deleted (tombstones). As such, we need to first lookup the docinfo in
209    // the 'other' file, only reporting as missing if their delete flags differ.
210    err = couchstore_docinfo_by_id(
211            ctx->other, docinfo->id.buf, docinfo->id.size, &other_info);
212
213    if (err == COUCHSTORE_SUCCESS) {
214        if (other_info->deleted != docinfo->deleted) {
215            ctx->diff = 1;
216            print_missing(docinfo->id, ctx->other_info.filename);
217        }
218        couchstore_free_docinfo(other_info);
219    } else if (err == COUCHSTORE_ERROR_DOC_NOT_FOUND) {
220        ctx->diff = 1;
221        print_missing(docinfo->id, ctx->other_info.filename);
222    } else {
223        fprintf(stderr, "Error trying to read \"");
224        print_key(docinfo->id);
225        fprintf(stderr,
226                "\" from \"%s\": %s\n",
227                ctx->other_info.filename,
228                couchstore_strerror(err));
229        exit(EXIT_FAILURE);
230    }
231
232    return 0;
233}
234
235static int diff(Db** dbs) {
236    couchstore_error_t err;
237    compare_context ctx;
238    DbInfo info;
239
240    ctx.diff = 0;
241    ctx.self = dbs[0];
242    ctx.other = dbs[1];
243
244    if (couchstore_db_info(ctx.self, &ctx.self_info) != COUCHSTORE_SUCCESS ||
245        couchstore_db_info(ctx.other, &ctx.other_info) != COUCHSTORE_SUCCESS) {
246        fprintf(stderr, "Failed to get database info..\n");
247        exit(EXIT_FAILURE);
248    }
249
250    err = couchstore_all_docs(ctx.self, nullptr, 0, deep_compare, &ctx);
251    if (err != COUCHSTORE_SUCCESS) {
252        fprintf(stderr, "An error occured: %s\n", couchstore_strerror(err));
253        return -1;
254    }
255
256    ctx.self = dbs[1];
257    ctx.other = dbs[0];
258    info = ctx.self_info;
259    ctx.self_info = ctx.other_info;
260    ctx.other_info = info;
261
262    err = couchstore_all_docs(ctx.self, nullptr, 0, check_existing, &ctx);
263    if (err != COUCHSTORE_SUCCESS) {
264        fprintf(stderr, "An error occured: %s\n", couchstore_strerror(err));
265        return -1;
266    }
267
268    return ctx.diff;
269}
270
271int main(int argc, char** argv) {
272    int cmd;
273    int ii;
274    Db* dbs[2];
275    int difference;
276
277    while ((cmd = getopt(argc, argv, "q")) != -1) {
278        switch (cmd) {
279        case 'q':
280            quiet = 1;
281            break;
282
283        default:
284            usage();
285            /* NOT REACHED */
286        }
287    }
288
289    if ((optind + 2) != argc) {
290        fprintf(stderr, "Exactly two filenames should be specified\n");
291        usage();
292        /* NOT REACHED */
293    }
294
295    for (ii = 0; ii < 2; ++ii) {
296        couchstore_error_t err;
297        err = couchstore_open_db(
298                argv[optind + ii], COUCHSTORE_OPEN_FLAG_RDONLY, &dbs[ii]);
299        if (err != COUCHSTORE_SUCCESS) {
300            fprintf(stderr,
301                    "Failed to open \"%s\": %s\n",
302                    argv[optind + ii],
303                    couchstore_strerror(err));
304            if (ii == 1) {
305                couchstore_close_file(dbs[0]);
306                couchstore_free_db(dbs[0]);
307            }
308            exit(EXIT_FAILURE);
309        }
310    }
311
312    difference = diff(dbs);
313    for (ii = 0; ii < 2; ++ii) {
314        couchstore_close_file(dbs[ii]);
315        couchstore_free_db(dbs[ii]);
316    }
317
318    if (difference == 0) {
319        if (!quiet) {
320            fprintf(stdout, "The content of the databases is the same\n");
321        }
322        return EXIT_SUCCESS;
323    }
324
325    return EXIT_FAILURE;
326}
327