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