static couchstore_error_t by_id_read_docinfo(DocInfo **pInfo, sized_buf *k, sized_buf *v) { const raw_id_index_value *raw = (const raw_id_index_value*)v->buf; ssize_t revMetaSize = v->size - sizeof(*raw); if (revMetaSize < 0) { return COUCHSTORE_ERROR_CORRUPT; } uint32_t datasize, deleted; uint8_t content_meta; uint64_t bp, seq, revnum; seq = decode_raw48(raw->db_seq); datasize = decode_raw32(raw->size); bp = decode_raw48(raw->bp); deleted = (bp & BP_DELETED_FLAG) != 0; bp &= ~BP_DELETED_FLAG; content_meta = decode_raw08(raw->content_meta); revnum = decode_raw48(raw->rev_seq); sized_buf rev_meta = {v->buf + sizeof(*raw), revMetaSize}; DocInfo* docInfo = couchstore_alloc_docinfo(k, &rev_meta); if (!docInfo) { return COUCHSTORE_ERROR_ALLOC_FAIL; } docInfo->db_seq = seq; docInfo->rev_seq = revnum; docInfo->deleted = deleted; docInfo->bp = bp; docInfo->size = datasize; docInfo->content_meta = content_meta; *pInfo = docInfo; return COUCHSTORE_SUCCESS; }
static couchstore_error_t by_seq_read_docinfo(DocInfo **pInfo, sized_buf *k, sized_buf *v) { const raw_seq_index_value *raw = (const raw_seq_index_value*)v->buf; ssize_t extraSize = v->size - sizeof(*raw); if (extraSize < 0) { return COUCHSTORE_ERROR_CORRUPT; } uint32_t idsize, datasize; decode_kv_length(&raw->sizes, &idsize, &datasize); uint64_t bp = decode_raw48(raw->bp); int deleted = (bp & BP_DELETED_FLAG) != 0; bp &= ~BP_DELETED_FLAG; uint8_t content_meta = decode_raw08(raw->content_meta); uint64_t rev_seq = decode_raw48(raw->rev_seq); uint64_t db_seq = decode_sequence_key(k); sized_buf id = {v->buf + sizeof(*raw), idsize}; sized_buf rev_meta = {id.buf + idsize, extraSize - id.size}; DocInfo* docInfo = couchstore_alloc_docinfo(&id, &rev_meta); if (!docInfo) { return COUCHSTORE_ERROR_ALLOC_FAIL; } docInfo->db_seq = db_seq; docInfo->rev_seq = rev_seq; docInfo->deleted = deleted; docInfo->bp = bp; docInfo->size = datasize; docInfo->content_meta = content_meta; *pInfo = docInfo; return COUCHSTORE_SUCCESS; }
static node_pointer *read_pointer(arena* a, sized_buf *key, char *buf) { //Parse KP pair into a node_pointer {K, {ptr, reduce_value, subtreesize}} node_pointer *p = (node_pointer *) arena_alloc(a, sizeof(node_pointer)); if (!p) { return NULL; } const raw_node_pointer *raw = (const raw_node_pointer*)buf; p->pointer = decode_raw48(raw->pointer); p->subtreesize = decode_raw48(raw->subtreesize); p->reduce_value.size = decode_raw16(raw->reduce_value_size); p->reduce_value.buf = buf + sizeof(*raw); p->key = *key; return p; }
static void idfetch_update_cb(couchfile_modify_request *rq, sized_buf *k, sized_buf *v, void *arg) { (void)k; (void)rq; //v contains a seq we need to remove ( {Seq,_,_,_,_} ) uint64_t oldseq; sized_buf *delbuf = NULL; index_update_ctx *ctx = (index_update_ctx *) arg; if (v == NULL) { //Doc not found return; } const raw_id_index_value *raw = (raw_id_index_value*) v->buf; oldseq = decode_raw48(raw->db_seq); delbuf = (sized_buf *) fatbuf_get(ctx->deltermbuf, sizeof(sized_buf)); delbuf->buf = (char *) fatbuf_get(ctx->deltermbuf, 6); delbuf->size = 6; memset(delbuf->buf, 0, 6); *(raw_48*)delbuf->buf = encode_raw48(oldseq); ctx->seqacts[ctx->actpos].type = ACTION_REMOVE; ctx->seqacts[ctx->actpos].value.data = NULL; ctx->seqacts[ctx->actpos].key = delbuf; ctx->actpos++; }
node_pointer *read_root(void *buf, int size) { raw_btree_root *raw = (raw_btree_root*)buf; node_pointer *ptr; uint64_t position = decode_raw48(raw->pointer); uint64_t subtreesize = decode_raw48(raw->subtreesize); int redsize = size - sizeof(*raw); ptr = (node_pointer *) malloc(sizeof(node_pointer) + redsize); buf = (char *) memcpy(ptr + 1, raw + 1, redsize); ptr->key.buf = NULL; ptr->key.size = 0; ptr->pointer = position; ptr->subtreesize = subtreesize; ptr->reduce_value.buf = buf; ptr->reduce_value.size = redsize; return ptr; }
// Attempts to initialize the database from a header at the given file position static couchstore_error_t find_header_at_pos(Db *db, cs_off_t pos) { int errcode = COUCHSTORE_SUCCESS; raw_file_header *header_buf = NULL; uint8_t buf[2]; ssize_t readsize = db->file.ops->pread(db->file.handle, buf, 2, pos); error_unless(readsize == 2, COUCHSTORE_ERROR_READ); if (buf[0] == 0) { return COUCHSTORE_ERROR_NO_HEADER; } else if (buf[0] != 1) { return COUCHSTORE_ERROR_CORRUPT; } int header_len = pread_header(&db->file, pos, (char**)&header_buf); if (header_len < 0) { error_pass(header_len); } db->header.position = pos; db->header.disk_version = decode_raw08(header_buf->version); error_unless(db->header.disk_version == COUCH_DISK_VERSION, COUCHSTORE_ERROR_HEADER_VERSION); db->header.update_seq = decode_raw48(header_buf->update_seq); db->header.purge_seq = decode_raw48(header_buf->purge_seq); db->header.purge_ptr = decode_raw48(header_buf->purge_ptr); error_unless(db->header.purge_ptr <= db->header.position, COUCHSTORE_ERROR_CORRUPT); int seqrootsize = decode_raw16(header_buf->seqrootsize); int idrootsize = decode_raw16(header_buf->idrootsize); int localrootsize = decode_raw16(header_buf->localrootsize); error_unless(header_len == HEADER_BASE_SIZE + seqrootsize + idrootsize + localrootsize, COUCHSTORE_ERROR_CORRUPT); char *root_data = (char*) (header_buf + 1); // i.e. just past *header_buf error_pass(read_db_root(db, &db->header.by_seq_root, root_data, seqrootsize)); root_data += seqrootsize; error_pass(read_db_root(db, &db->header.by_id_root, root_data, idrootsize)); root_data += idrootsize; error_pass(read_db_root(db, &db->header.local_docs_root, root_data, localrootsize)); cleanup: free(header_buf); return errcode; }
static void test_raw_48(uint64_t value, const uint8_t expected[8]) { union { raw_48 raw; uint8_t bytes[8]; } data; memset(&data, 0, sizeof(data)); data.raw = encode_raw48(value); assert(memcmp(data.bytes, expected, 8) == 0); assert(decode_raw48(data.raw) == value); }
static int by_id_read_docinfo(DocInfo **pInfo, sized_buf *k, sized_buf *v) { const raw_id_index_value *raw = (const raw_id_index_value*)v->buf; ssize_t revMetaSize = v->size - sizeof(*raw); if (revMetaSize < 0) { return COUCHSTORE_ERROR_CORRUPT; } uint32_t datasize, deleted; uint8_t content_meta; uint64_t bp, seq, revnum; seq = decode_raw48(raw->db_seq); datasize = decode_raw32(raw->size); bp = decode_raw48(raw->bp); deleted = (bp & BP_DELETED_FLAG) != 0; bp &= ~BP_DELETED_FLAG; content_meta = decode_raw08(raw->content_meta); revnum = decode_raw48(raw->rev_seq); DocInfo* docInfo = malloc(sizeof(DocInfo) + revMetaSize + k->size); if (!docInfo) { return COUCHSTORE_ERROR_ALLOC_FAIL; } char *rbuf = (char *) docInfo; memcpy(rbuf + sizeof(DocInfo), v->buf + sizeof(*raw), revMetaSize); *pInfo = docInfo; docInfo->db_seq = seq; docInfo->rev_seq = revnum; docInfo->deleted = deleted; docInfo->bp = bp; docInfo->size = datasize; docInfo->content_meta = content_meta; docInfo->rev_meta.buf = rbuf + sizeof(DocInfo); docInfo->rev_meta.size = revMetaSize; docInfo->id.buf = docInfo->rev_meta.buf + docInfo->rev_meta.size; docInfo->id.size = k->size; memcpy(docInfo->id.buf, k->buf, k->size); return 0; }
static int by_seq_read_docinfo(DocInfo **pInfo, sized_buf *k, sized_buf *v) { const raw_seq_index_value *raw = (const raw_seq_index_value*)v->buf; ssize_t extraSize = v->size - sizeof(*raw); if (extraSize < 0) { return COUCHSTORE_ERROR_CORRUPT; } uint32_t idsize, datasize; decode_kv_length(&raw->sizes, &idsize, &datasize); uint64_t bp = decode_raw48(raw->bp); int deleted = (bp & BP_DELETED_FLAG) != 0; bp &= ~BP_DELETED_FLAG; uint8_t content_meta = decode_raw08(raw->content_meta); uint64_t rev_seq = decode_raw48(raw->rev_seq); uint64_t db_seq = decode_sequence_key(k); DocInfo* docInfo = malloc(sizeof(DocInfo) + extraSize); if (!docInfo) { return COUCHSTORE_ERROR_ALLOC_FAIL; } char *rbuf = (char *) docInfo; memcpy(rbuf + sizeof(DocInfo), v->buf + sizeof(*raw), extraSize); *pInfo = docInfo; docInfo->db_seq = db_seq; docInfo->rev_seq = rev_seq; docInfo->deleted = deleted; docInfo->bp = bp; docInfo->size = datasize; docInfo->content_meta = content_meta; docInfo->id.buf = rbuf + sizeof(DocInfo); docInfo->id.size = idsize; docInfo->rev_meta.buf = rbuf + sizeof(DocInfo) + idsize; docInfo->rev_meta.size = extraSize - idsize; return 0; }
LIBCOUCHSTORE_API couchstore_error_t couchstore_db_info(Db *db, DbInfo* dbinfo) { const node_pointer *id_root = db->header.by_id_root; const node_pointer *seq_root = db->header.by_seq_root; const node_pointer *local_root = db->header.local_docs_root; dbinfo->filename = db->file.path; dbinfo->header_position = db->header.position; dbinfo->last_sequence = db->header.update_seq; dbinfo->deleted_count = dbinfo->doc_count = dbinfo->space_used = 0; if (id_root) { raw_by_id_reduce* id_reduce = (raw_by_id_reduce*) id_root->reduce_value.buf; dbinfo->doc_count = decode_raw40(id_reduce->notdeleted); dbinfo->deleted_count = decode_raw40(id_reduce->deleted); dbinfo->space_used = decode_raw48(id_reduce->size); dbinfo->space_used += id_root->subtreesize; } if(seq_root) { dbinfo->space_used += seq_root->subtreesize; } if(local_root) { dbinfo->space_used += local_root->subtreesize; } return COUCHSTORE_SUCCESS; }
static int docset_check(Db *db, DocInfo *info, void *ctx) { int errcode = 0; docset *ds = ctx; counterset *ctr = &ds->counters; ctr->totaldocs++; if (info->deleted) { ctr->deleted++; } EQUAL_INFO_BUF(id); EQUAL_INFO_BUF(rev_meta); Doc *doc; try(couchstore_open_doc_with_docinfo(db, info, &doc, DECOMPRESS_DOC_BODIES)); if (testdocset.docs[testdocset.pos].data.size > 0) { assert(doc); EQUAL_DOC_BUF(data); EQUAL_DOC_BUF(id); } testdocset.pos++; couchstore_free_document(doc); cleanup: assert(errcode == 0); return 0; } static int dociter_check(Db *db, DocInfo *info, void *ctx) { int errcode = 0; docset *ds = ctx; counterset *ctr = &ds->counters; ctr->totaldocs++; if (info->deleted) { ctr->deleted++; } Doc *doc; try(couchstore_open_doc_with_docinfo(db, info, &doc, DECOMPRESS_DOC_BODIES)); assert(doc); couchstore_free_document(doc); cleanup: assert(errcode == 0); return 0; } static int dump_count(Db *db) { int errcode = 0; ZERO(counters); try(couchstore_changes_since(db, 0, 0, counter_inc, &counters)); cleanup: assert(errcode == 0); return errcode; } static char zerometa[] = {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3}; static void test_save_docs(int count, const char *doc_tpl) { int errcode = 0; int i; char *idBuf, *valueBuf; Doc **docptrs; DocInfo **nfoptrs; sized_buf *ids = NULL; uint64_t idtreesize = 0; uint64_t seqtreesize = 0; uint64_t docssize = 0; uint64_t dbfilesize = 0; uint64_t *sequences = NULL; fprintf(stderr, "save_docs (doc count %d)... ", count); fflush(stderr); docset_init(count); srandom(0xdeadbeef); // doc IDs should be consistent across runs for (i = 0; i < count; ++i) { idBuf = (char *) malloc(sizeof(char) * 32); assert(idBuf != NULL); int idsize = sprintf(idBuf, "doc%d-%lu", i, (unsigned long)random()); valueBuf = (char *) malloc(sizeof(char) * (strlen(doc_tpl) + 20)); assert(valueBuf != NULL); int valsize = sprintf(valueBuf, doc_tpl, i + 1); setdoc(&testdocset.docs[i], &testdocset.infos[i], idBuf, idsize, valueBuf, valsize, zerometa, sizeof(zerometa)); testdocset.datasize += valsize; } docptrs = (Doc **) malloc(sizeof(Doc*) * count); assert(docptrs != NULL); for (i = 0; i < count; ++i) { docptrs[i] = &testdocset.docs[i]; } nfoptrs = (DocInfo **) malloc(sizeof(DocInfo*) * count); assert(nfoptrs != NULL); for (i = 0; i < count; ++i) { nfoptrs[i] = &testdocset.infos[i]; } unlink(testfilepath); Db *db; try(couchstore_open_db(testfilepath, COUCHSTORE_OPEN_FLAG_CREATE, &db)); assert(strcmp(couchstore_get_db_filename(db), testfilepath) == 0); try(couchstore_save_documents(db, docptrs, nfoptrs, count, 0)); try(couchstore_commit(db)); couchstore_close_db(db); try(couchstore_open_db(testfilepath, 0, &db)); // Read back by doc ID: fprintf(stderr, "get by ID... "); testdocset.pos = 0; for (i = 0; i < count; ++i) { DocInfo* out_info; try(couchstore_docinfo_by_id(db, testdocset.docs[i].id.buf, testdocset.docs[i].id.size, &out_info)); docset_check(db, out_info, &testdocset); couchstore_free_docinfo(out_info); } // Read back in bulk by doc ID: fprintf(stderr, "bulk IDs... "); ids = malloc(count * sizeof(sized_buf)); for (i = 0; i < count; ++i) { ids[i] = docptrs[i]->id; } ZERO(testdocset.counters); try(couchstore_docinfos_by_id(db, ids, count, 0, dociter_check, &testdocset)); assert(testdocset.counters.totaldocs == count); assert(testdocset.counters.deleted == 0); // Read back by sequence: fprintf(stderr, "get by sequence... "); sequences = malloc(count * sizeof(*sequences)); testdocset.pos = 0; for (i = 0; i < count; ++i) { DocInfo* out_info; sequences[i] = testdocset.infos[i].db_seq; assert(sequences[i] == (uint64_t)i + 1); try(couchstore_docinfo_by_sequence(db, testdocset.infos[i].db_seq, &out_info)); docset_check(db, out_info, &testdocset); couchstore_free_docinfo(out_info); } // Read back in bulk by sequence: fprintf(stderr, "bulk sequences... "); testdocset.pos = 0; ZERO(testdocset.counters); try(couchstore_docinfos_by_sequence(db, sequences, count, 0, docset_check, &testdocset)); assert(testdocset.counters.totaldocs == count); assert(testdocset.counters.deleted == 0); // Read back using changes_since: fprintf(stderr, "changes_since... "); testdocset.pos = 0; ZERO(testdocset.counters); try(couchstore_changes_since(db, 0, 0, docset_check, &testdocset)); assert(testdocset.counters.totaldocs == count); assert(testdocset.counters.deleted == 0); idtreesize = db->header.by_id_root->subtreesize; seqtreesize = db->header.by_seq_root->subtreesize; const raw_by_id_reduce *reduce = (const raw_by_id_reduce*)db->header.by_id_root->reduce_value.buf; docssize = decode_raw48(reduce->size); dbfilesize = db->file.pos; assert(dbfilesize > 0); assert(idtreesize > 0); assert(seqtreesize > 0); assert(docssize > 0); assert(idtreesize < dbfilesize); assert(seqtreesize < dbfilesize); assert(docssize < dbfilesize); assert(db->header.local_docs_root == NULL); assert((idtreesize + seqtreesize + docssize) < dbfilesize); couchstore_close_db(db); cleanup: free(ids); free(sequences); for (i = 0; i < count; ++i) { free(docptrs[i]->id.buf); free(docptrs[i]->data.buf); } free(docptrs); free(nfoptrs); assert(errcode == 0); } static void test_save_doc(void) { fprintf(stderr, "save_doc... "); fflush(stderr); int errcode = 0; docset_init(4); SETDOC(0, "doc1", "{\"test_doc_index\":1}", zerometa); SETDOC(1, "doc2", "{\"test_doc_index\":2}", zerometa); SETDOC(2, "doc3", "{\"test_doc_index\":3}", zerometa); SETDOC(3, "doc4", "{\"test_doc_index\":4}", zerometa); unlink(testfilepath); Db *db; try(couchstore_open_db(testfilepath, COUCHSTORE_OPEN_FLAG_CREATE, &db)); try(couchstore_save_document(db, &testdocset.docs[0], &testdocset.infos[0], 0)); try(couchstore_save_document(db, &testdocset.docs[1], &testdocset.infos[1], 0)); try(couchstore_save_document(db, &testdocset.docs[2], &testdocset.infos[2], 0)); try(couchstore_save_document(db, &testdocset.docs[3], &testdocset.infos[3], 0)); try(couchstore_commit(db)); couchstore_close_db(db); // Check that sequence numbers got filled in unsigned i; for (i = 0; i < 4; ++i) { assert(testdocset.infos[i].db_seq == i + 1); } //Read back try(couchstore_open_db(testfilepath, 0, &db)); try(couchstore_changes_since(db, 0, 0, docset_check, &testdocset)); assert(testdocset.counters.totaldocs == 4); assert(testdocset.counters.deleted == 0); DbInfo info; assert(couchstore_db_info(db, &info) == COUCHSTORE_SUCCESS); assert(info.last_sequence == 4); assert(info.doc_count == 4); assert(info.deleted_count == 0); assert(info.header_position == 4096); couchstore_close_db(db); cleanup: assert(errcode == 0); } static void test_compressed_doc_body(void) { fprintf(stderr, "compressed bodies... "); fflush(stderr); int errcode = 0; docset_init(2); SETDOC(0, "doc1", "{\"test_doc_index\":1, \"val\":\"blah blah blah blah blah blah\"}", zerometa); SETDOC(1, "doc2", "{\"test_doc_index\":2, \"val\":\"blah blah blah blah blah blah\"}", zerometa); Doc *docptrs [2] = { &testdocset.docs[0], &testdocset.docs[1] }; DocInfo *nfoptrs [2] = { &testdocset.infos[0], &testdocset.infos[1] }; testdocset.infos[1].content_meta = COUCH_DOC_IS_COMPRESSED; //Mark doc2 as to be snappied. unlink(testfilepath); Db *db; try(couchstore_open_db(testfilepath, COUCHSTORE_OPEN_FLAG_CREATE, &db)); try(couchstore_save_documents(db, docptrs, nfoptrs, 2, COMPRESS_DOC_BODIES)); try(couchstore_commit(db)); couchstore_close_db(db); //Read back try(couchstore_open_db(testfilepath, 0, &db)); try(couchstore_changes_since(db, 0, 0, docset_check, &testdocset)); assert(testdocset.counters.totaldocs == 2); assert(testdocset.counters.deleted == 0); couchstore_close_db(db); cleanup: assert(errcode == 0); } static void test_dump_empty_db(void) { fprintf(stderr, "dump empty db... "); fflush(stderr); unlink(testfilepath); Db *db; couchstore_error_t errcode; try(couchstore_open_db(testfilepath, COUCHSTORE_OPEN_FLAG_CREATE, &db)); try(couchstore_close_db(db)); try(couchstore_open_db(testfilepath, 0, &db)); dump_count(db); assert(counters.totaldocs == 0); assert(counters.deleted == 0); DbInfo info; assert(couchstore_db_info(db, &info) == COUCHSTORE_SUCCESS); assert(strcmp(info.filename, testfilepath) == 0); assert(info.last_sequence == 0); assert(info.doc_count == 0); assert(info.deleted_count == 0); assert(info.space_used == 0); assert(info.header_position == 0); couchstore_close_db(db); cleanup: assert(errcode == 0); } static void test_local_docs(void) { fprintf(stderr, "local docs... "); fflush(stderr); int errcode = 0; Db *db; LocalDoc lDocWrite; LocalDoc *lDocRead = NULL; unlink(testfilepath); try(couchstore_open_db(testfilepath, COUCHSTORE_OPEN_FLAG_CREATE, &db)); lDocWrite.id.buf = "_local/testlocal"; lDocWrite.id.size = 16; lDocWrite.json.buf = "{\"test\":true}"; lDocWrite.json.size = 13; lDocWrite.deleted = 0; couchstore_save_local_document(db, &lDocWrite); couchstore_commit(db); couchstore_close_db(db); couchstore_open_db(testfilepath, 0, &db); couchstore_open_local_document(db, "_local/testlocal", 16, &lDocRead); assert(lDocRead); assert(lDocRead->json.size == 13); assert(memcmp(lDocRead->json.buf, "{\"test\":true}", 13) == 0); couchstore_free_local_document(lDocRead); couchstore_close_db(db); cleanup: assert(errcode == 0); } static void test_open_file_error(void) { fprintf(stderr, "opening nonexistent file errors... "); fflush(stderr); unlink(testfilepath); Db *db; int errcode = couchstore_open_db(testfilepath, 0, &db); if(errcode != 0) { print_os_err(); } assert(errcode == COUCHSTORE_ERROR_NO_SUCH_FILE); // make sure os.c didn't accidentally call close(0): assert(lseek(0, 0, SEEK_CUR) >= 0 || errno != EBADF); } static void shuffle(Doc **docs, DocInfo **docinfos, size_t n) { if (n > 1) { size_t i; for (i = 0; i < n - 1; i++) { size_t j = i + rand() / (RAND_MAX / (n - i) + 1); DocInfo *docinfo; Doc *doc = docs[j]; docs[j] = docs[i]; docs[i] = doc; docinfo = docinfos[j]; docinfos[j] = docinfos[i]; docinfos[i] = docinfo; } } } static int docmap_check(Db *db, DocInfo *info, void *ctx) { (void)db; char* docmap = (char*)ctx; int i; char buffer[100]; memcpy(buffer, info->id.buf, info->id.size); buffer[info->id.size] = 0; // null terminate sscanf(buffer, "doc%d", &i); assert(docmap[i] == 0); docmap[i] = 1; return 0; } static void test_changes_no_dups(void) { fprintf(stderr, "changes no dupes... "); fflush(stderr); int errcode = 0; int i; const int numdocs = 10000; int updatebatch = 1000; Doc **docptrs; DocInfo **nfoptrs; char *docmap; Db *db; docset_init(numdocs); for (i=0; i < numdocs; i++) { char* id = malloc(100); char* body = malloc(100); sprintf(id, "doc%d", i); sprintf(body, "{\"test_doc_index\":%d}", i); setdoc(&testdocset.docs[i], &testdocset.infos[i], id, strlen(id), body, strlen(body), zerometa, sizeof(zerometa)); } docptrs = malloc(numdocs * sizeof(Doc*)); nfoptrs = malloc(numdocs * sizeof(DocInfo*)); docmap = malloc(numdocs); for (i=0; i < numdocs; i++) { docptrs[i] = &testdocset.docs[i]; nfoptrs[i] = &testdocset.infos[i]; } unlink(testfilepath); try(couchstore_open_db(testfilepath, COUCHSTORE_OPEN_FLAG_CREATE, &db)); // only save half the docs at first. try(couchstore_save_documents(db, docptrs, nfoptrs, numdocs/2, 0)); try(couchstore_commit(db)); couchstore_close_db(db); for (i=0; i < numdocs/2; i++) { // increment the rev for already added docs nfoptrs[i]->rev_seq++; } srand(10); // make deterministic // now shuffle so some bulk updates contain previous docs and new docs shuffle(docptrs, nfoptrs, numdocs); try(couchstore_open_db(testfilepath, 0, &db)); for (i=0; i < numdocs; i += updatebatch) { // now do bulk updates and check the changes for dups try(couchstore_save_documents(db, docptrs + i, nfoptrs + i, updatebatch, 0)); try(couchstore_commit(db)); memset(docmap, 0, numdocs); try(couchstore_changes_since(db, 0, 0, docmap_check, docmap)); } DbInfo info; assert(couchstore_db_info(db, &info) == COUCHSTORE_SUCCESS); assert(info.last_sequence == (uint64_t)(numdocs + numdocs/2)); assert(info.doc_count == (uint64_t)numdocs); assert(info.deleted_count == 0); couchstore_close_db(db); cleanup: for (i=0; i < numdocs; i++) { free(docptrs[i]->id.buf); free(docptrs[i]->data.buf); } free(docptrs); free(nfoptrs); free(docmap); assert(errcode == 0); } static void mb5086(void) { Db *db; Doc d; DocInfo i; couchstore_error_t err; fprintf(stderr, "regression mb-5086.... "); fflush(stderr); setdoc(&d, &i, "hi", 2, "foo", 3, NULL, 0); err = couchstore_open_db("mb5085.couch", COUCHSTORE_OPEN_FLAG_CREATE, &db); assert(err == COUCHSTORE_SUCCESS); assert(couchstore_save_document(db, &d, &i, 0) == COUCHSTORE_SUCCESS); assert(couchstore_commit(db) == COUCHSTORE_SUCCESS); assert(couchstore_close_db(db) == COUCHSTORE_SUCCESS); assert(remove("mb5085.couch") == 0); } static void test_asis_seqs(void) { Db *db = NULL; Doc d; DocInfo i; DocInfo *ir; couchstore_error_t errcode; fprintf(stderr, "as-is seqs.... "); fflush(stderr); try(couchstore_open_db(testfilepath, COUCHSTORE_OPEN_FLAG_CREATE, &db)); setdoc(&d, &i, "test", 4, "foo", 3, NULL, 0); i.db_seq = 1; try(couchstore_save_document(db, &d, &i, COUCHSTORE_SEQUENCE_AS_IS)); assert(db->header.update_seq == 1); setdoc(&d, &i, "test_two", 8, "foo", 3, NULL, 0); i.db_seq = 12; try(couchstore_save_document(db, &d, &i, COUCHSTORE_SEQUENCE_AS_IS)); assert(db->header.update_seq == 12); setdoc(&d, &i, "test_foo", 8, "foo", 3, NULL, 0); i.db_seq = 6; try(couchstore_save_document(db, &d, &i, COUCHSTORE_SEQUENCE_AS_IS)); assert(db->header.update_seq == 12); try(couchstore_docinfo_by_id(db, "test", 4, &ir)); assert(ir->db_seq == 1); couchstore_free_docinfo(ir); try(couchstore_docinfo_by_id(db, "test_two", 8, &ir)); assert(ir->db_seq == 12); couchstore_free_docinfo(ir); try(couchstore_docinfo_by_id(db, "test_foo", 8, &ir)); assert(ir->db_seq == 6); couchstore_free_docinfo(ir); cleanup: if (db != NULL) { couchstore_close_db(db); } assert(errcode == COUCHSTORE_SUCCESS); } static void test_huge_revseq(void) { Db *db; Doc d; DocInfo i; DocInfo *i2; couchstore_error_t err; fprintf(stderr, "huge rev_seq.... "); fflush(stderr); setdoc(&d, &i, "hi", 2, "foo", 3, NULL, 0); i.rev_seq = 5294967296; err = couchstore_open_db("bigrevseq.couch", COUCHSTORE_OPEN_FLAG_CREATE, &db); assert(err == COUCHSTORE_SUCCESS); assert(couchstore_save_document(db, &d, &i, 0) == COUCHSTORE_SUCCESS); assert(couchstore_commit(db) == COUCHSTORE_SUCCESS); assert(couchstore_docinfo_by_id(db, "hi", 2, &i2) == COUCHSTORE_SUCCESS); assert(i2->rev_seq == 5294967296); couchstore_free_docinfo(i2); assert(couchstore_close_db(db) == COUCHSTORE_SUCCESS); assert(remove("bigrevseq.couch") == 0); } static void test_dropped_handle(void) { couchstore_error_t errcode; Db* db = NULL; Doc d; DocInfo i; fprintf(stderr, "drop file handle.... "); fflush(stderr); try(couchstore_open_db(testfilepath, COUCHSTORE_OPEN_FLAG_CREATE, &db)); setdoc(&d, &i, "test", 4, "foo", 3, NULL, 0); try(couchstore_save_document(db, &d, &i, 0)); try(couchstore_commit(db)); try(couchstore_drop_file(db)); assert(couchstore_save_document(db, &d, &i, 0) == COUCHSTORE_ERROR_FILE_CLOSED); try(couchstore_reopen_file(db, testfilepath, 0)); Doc* rd; try(couchstore_open_document(db, "test", 4, &rd, 0)); couchstore_free_document(rd); cleanup: if (db != NULL) { couchstore_close_db(db); } assert(errcode == COUCHSTORE_SUCCESS); } int main(int argc, const char *argv[]) { int doc_counts[] = { 4, 69, 666, 9090 }; unsigned i; const char *small_doc_tpl = "{\"test_doc_index\":%d}"; const char *large_doc_tpl = "{" "\"test_doc_index\":%d," "\"field1\": \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"," "\"field2\": \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"," "\"field3\": \"cccccccccccccccccccccccccccccccccccccccccccccccccc" "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\"" "}"; file_merger_tests(); file_sorter_tests(); if (argc > 1) strcpy(testfilepath, argv[1]); printf("Using test database at %s\n", testfilepath); test_bitfield_fns(); test_open_file_error(); fprintf(stderr, "OK \n"); test_dump_empty_db(); fprintf(stderr, " OK\n"); test_save_doc(); fprintf(stderr, " OK\n"); for (i = 0; i < (sizeof(doc_counts) / sizeof(int)); ++i) { test_save_docs(doc_counts[i], small_doc_tpl); fprintf(stderr, " OK\n"); test_save_docs(doc_counts[i], large_doc_tpl); fprintf(stderr, " OK\n"); } test_local_docs(); fprintf(stderr, " OK\n"); test_compressed_doc_body(); fprintf(stderr, " OK\n"); test_changes_no_dups(); fprintf(stderr, " OK\n"); mb5086(); fprintf(stderr, " OK\n"); unlink(testfilepath); test_huge_revseq(); fprintf(stderr, " OK\n"); unlink(testfilepath); test_asis_seqs(); fprintf(stderr, " OK\n"); unlink(testfilepath); test_dropped_handle(); fprintf(stderr, " OK\n"); unlink(testfilepath); // make sure os.c didn't accidentally call close(0): assert(lseek(0, 0, SEEK_CUR) >= 0 || errno != EBADF); mapreduce_tests(); view_tests(); return 0; }
static couchstore_error_t btree_lookup_inner(couchfile_lookup_request *rq, uint64_t diskpos, int current, int end) { int bufpos = 1, nodebuflen = 0; if (current == end) { return 0; } couchstore_error_t errcode = COUCHSTORE_SUCCESS; char *nodebuf = NULL; nodebuflen = pread_compressed(rq->file, diskpos, &nodebuf); error_unless(nodebuflen >= 0, nodebuflen); // if negative, it's an error code if (nodebuf[0] == 0) { //KP Node while (bufpos < nodebuflen && current < end) { sized_buf cmp_key, val_buf; bufpos += read_kv(nodebuf + bufpos, &cmp_key, &val_buf); if (rq->cmp.compare(&cmp_key, rq->keys[current]) >= 0) { if (rq->fold) { rq->in_fold = 1; } uint64_t pointer = 0; int last_item = current; //Descend into the pointed to node. //with all keys < item key. do { last_item++; } while (last_item < end && rq->cmp.compare(&cmp_key, rq->keys[last_item]) >= 0); const raw_node_pointer *raw = (const raw_node_pointer*)val_buf.buf; if(rq->node_callback) { uint64_t subtreeSize = decode_raw48(raw->subtreesize); sized_buf reduce_value = {val_buf.buf + sizeof(raw_node_pointer), decode_raw16(raw->reduce_value_size)}; error_pass(rq->node_callback(rq, subtreeSize, &reduce_value)); } pointer = decode_raw48(raw->pointer); error_pass(btree_lookup_inner(rq, pointer, current, last_item)); if (!rq->in_fold) { current = last_item; } if(rq->node_callback) { error_pass(rq->node_callback(rq, 0, NULL)); } } } } else if (nodebuf[0] == 1) { //KV Node while (bufpos < nodebuflen && current < end) { sized_buf cmp_key, val_buf; bufpos += read_kv(nodebuf + bufpos, &cmp_key, &val_buf); int cmp_val = rq->cmp.compare(&cmp_key, rq->keys[current]); if (cmp_val >= 0 && rq->fold && !rq->in_fold) { rq->in_fold = 1; } else if (rq->in_fold && (current + 1) < end && (rq->cmp.compare(&cmp_key, rq->keys[current + 1])) > 0) { //We've hit a key past the end of our range. rq->in_fold = 0; rq->fold = 0; current = end; } if (cmp_val == 0 || (cmp_val > 0 && rq->in_fold)) { //Found error_pass(rq->fetch_callback(rq, &cmp_key, &val_buf)); if (!rq->in_fold) { current++; } } } } //Any remaining items are not found. while (current < end && !rq->fold) { error_pass(rq->fetch_callback(rq, rq->keys[current], NULL)); current++; } cleanup: free(nodebuf); return errcode; }