xref: /leveldb-1.20/table/table_builder.cc (revision f122c6df)
1 // Copyright (c) 2011 The LevelDB Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. See the AUTHORS file for names of contributors.
4 
5 #include "leveldb/table_builder.h"
6 
7 #include <assert.h>
8 #include <stdio.h>
9 #include "leveldb/comparator.h"
10 #include "leveldb/env.h"
11 #include "table/block_builder.h"
12 #include "table/format.h"
13 #include "util/coding.h"
14 #include "util/crc32c.h"
15 #include "util/logging.h"
16 
17 namespace leveldb {
18 
19 struct TableBuilder::Rep {
20   Options options;
21   Options index_block_options;
22   WritableFile* file;
23   uint64_t offset;
24   Status status;
25   BlockBuilder data_block;
26   BlockBuilder index_block;
27   std::string last_key;
28   int64_t num_entries;
29   bool closed;          // Either Finish() or Abandon() has been called.
30 
31   // We do not emit the index entry for a block until we have seen the
32   // first key for the next data block.  This allows us to use shorter
33   // keys in the index block.  For example, consider a block boundary
34   // between the keys "the quick brown fox" and "the who".  We can use
35   // "the r" as the key for the index block entry since it is >= all
36   // entries in the first block and < all entries in subsequent
37   // blocks.
38   //
39   // Invariant: r->pending_index_entry is true only if data_block is empty.
40   bool pending_index_entry;
41   BlockHandle pending_handle;  // Handle to add to index block
42 
43   std::string compressed_output;
44 
45   Rep(const Options& opt, WritableFile* f)
46       : options(opt),
47         index_block_options(opt),
48         file(f),
49         offset(0),
50         data_block(&options),
51         index_block(&index_block_options),
52         num_entries(0),
53         closed(false),
54         pending_index_entry(false) {
55     index_block_options.block_restart_interval = 1;
56   }
57 };
58 
59 TableBuilder::TableBuilder(const Options& options, WritableFile* file)
60     : rep_(new Rep(options, file)) {
61 }
62 
63 TableBuilder::~TableBuilder() {
64   assert(rep_->closed);  // Catch errors where caller forgot to call Finish()
65   delete rep_;
66 }
67 
68 Status TableBuilder::ChangeOptions(const Options& options) {
69   // Note: if more fields are added to Options, update
70   // this function to catch changes that should not be allowed to
71   // change in the middle of building a Table.
72   if (options.comparator != rep_->options.comparator) {
73     return Status::InvalidArgument("changing comparator while building table");
74   }
75 
76   // Note that any live BlockBuilders point to rep_->options and therefore
77   // will automatically pick up the updated options.
78   rep_->options = options;
79   rep_->index_block_options = options;
80   rep_->index_block_options.block_restart_interval = 1;
81   return Status::OK();
82 }
83 
84 void TableBuilder::Add(const Slice& key, const Slice& value) {
85   Rep* r = rep_;
86   assert(!r->closed);
87   if (!ok()) return;
88   if (r->num_entries > 0) {
89     assert(r->options.comparator->Compare(key, Slice(r->last_key)) > 0);
90   }
91 
92   if (r->pending_index_entry) {
93     assert(r->data_block.empty());
94     r->options.comparator->FindShortestSeparator(&r->last_key, key);
95     std::string handle_encoding;
96     r->pending_handle.EncodeTo(&handle_encoding);
97     r->index_block.Add(r->last_key, Slice(handle_encoding));
98     r->pending_index_entry = false;
99   }
100 
101   r->last_key.assign(key.data(), key.size());
102   r->num_entries++;
103   r->data_block.Add(key, value);
104 
105   const size_t estimated_block_size = r->data_block.CurrentSizeEstimate();
106   if (estimated_block_size >= r->options.block_size) {
107     Flush();
108   }
109 }
110 
111 void TableBuilder::Flush() {
112   Rep* r = rep_;
113   assert(!r->closed);
114   if (!ok()) return;
115   if (r->data_block.empty()) return;
116   assert(!r->pending_index_entry);
117   WriteBlock(&r->data_block, &r->pending_handle);
118   if (ok()) {
119     r->pending_index_entry = true;
120     r->status = r->file->Flush();
121   }
122 }
123 
124 void TableBuilder::WriteBlock(BlockBuilder* block, BlockHandle* handle) {
125   // File format contains a sequence of blocks where each block has:
126   //    block_data: uint8[n]
127   //    type: uint8
128   //    crc: uint32
129   assert(ok());
130   Rep* r = rep_;
131   Slice raw = block->Finish();
132 
133   Slice block_contents;
134   CompressionType type = r->options.compression;
135   // TODO(postrelease): Support more compression options: zlib?
136   switch (type) {
137     case kNoCompression:
138       block_contents = raw;
139       break;
140 
141     case kSnappyCompression: {
142       std::string* compressed = &r->compressed_output;
143       if (port::Snappy_Compress(raw.data(), raw.size(), compressed) &&
144           compressed->size() < raw.size() - (raw.size() / 8u)) {
145         block_contents = *compressed;
146       } else {
147         // Snappy not supported, or compressed less than 12.5%, so just
148         // store uncompressed form
149         block_contents = raw;
150         type = kNoCompression;
151       }
152       break;
153     }
154   }
155   handle->set_offset(r->offset);
156   handle->set_size(block_contents.size());
157   r->status = r->file->Append(block_contents);
158   if (r->status.ok()) {
159     char trailer[kBlockTrailerSize];
160     trailer[0] = type;
161     uint32_t crc = crc32c::Value(block_contents.data(), block_contents.size());
162     crc = crc32c::Extend(crc, trailer, 1);  // Extend crc to cover block type
163     EncodeFixed32(trailer+1, crc32c::Mask(crc));
164     r->status = r->file->Append(Slice(trailer, kBlockTrailerSize));
165     if (r->status.ok()) {
166       r->offset += block_contents.size() + kBlockTrailerSize;
167     }
168   }
169   r->compressed_output.clear();
170   block->Reset();
171 }
172 
173 Status TableBuilder::status() const {
174   return rep_->status;
175 }
176 
177 Status TableBuilder::Finish() {
178   Rep* r = rep_;
179   Flush();
180   assert(!r->closed);
181   r->closed = true;
182   BlockHandle metaindex_block_handle;
183   BlockHandle index_block_handle;
184   if (ok()) {
185     BlockBuilder meta_index_block(&r->options);
186     // TODO(postrelease): Add stats and other meta blocks
187     WriteBlock(&meta_index_block, &metaindex_block_handle);
188   }
189   if (ok()) {
190     if (r->pending_index_entry) {
191       r->options.comparator->FindShortSuccessor(&r->last_key);
192       std::string handle_encoding;
193       r->pending_handle.EncodeTo(&handle_encoding);
194       r->index_block.Add(r->last_key, Slice(handle_encoding));
195       r->pending_index_entry = false;
196     }
197     WriteBlock(&r->index_block, &index_block_handle);
198   }
199   if (ok()) {
200     Footer footer;
201     footer.set_metaindex_handle(metaindex_block_handle);
202     footer.set_index_handle(index_block_handle);
203     std::string footer_encoding;
204     footer.EncodeTo(&footer_encoding);
205     r->status = r->file->Append(footer_encoding);
206     if (r->status.ok()) {
207       r->offset += footer_encoding.size();
208     }
209   }
210   return r->status;
211 }
212 
213 void TableBuilder::Abandon() {
214   Rep* r = rep_;
215   assert(!r->closed);
216   r->closed = true;
217 }
218 
219 uint64_t TableBuilder::NumEntries() const {
220   return rep_->num_entries;
221 }
222 
223 uint64_t TableBuilder::FileSize() const {
224   return rep_->offset;
225 }
226 
227 }
228