BadgerDB
 All Classes Namespaces Functions Variables Typedefs Friends Pages
file.cpp
1 
8 #include "file.h"
9 
10 #include <fstream>
11 #include <iostream>
12 #include <memory>
13 #include <string>
14 #include <cstdio>
15 #include <cassert>
16 
17 #include "exceptions/file_exists_exception.h"
18 #include "exceptions/file_not_found_exception.h"
19 #include "exceptions/file_open_exception.h"
20 #include "exceptions/invalid_page_exception.h"
21 #include "file_iterator.h"
22 #include "page.h"
23 
24 namespace badgerdb {
25 
26 File::StreamMap File::open_streams_;
27 File::CountMap File::open_counts_;
28 
29 File File::create(const std::string& filename) {
30  return File(filename, true /* create_new */);
31 }
32 
33 File File::open(const std::string& filename) {
34  return File(filename, false /* create_new */);
35 }
36 
37 void File::remove(const std::string& filename) {
38  if (!exists(filename)) {
39  throw FileNotFoundException(filename);
40  }
41  if (isOpen(filename)) {
42  throw FileOpenException(filename);
43  }
44  std::remove(filename.c_str());
45 }
46 
47 bool File::isOpen(const std::string& filename) {
48  if (!exists(filename)) {
49  return false;
50  }
51  return open_counts_.find(filename) != open_counts_.end();
52 }
53 
54 bool File::exists(const std::string& filename) {
55  std::fstream file(filename);
56  if(file)
57  {
58  file.close();
59  return true;
60  }
61 
62  return false;
63 }
64 
65 File::File(const File& other)
66  : filename_(other.filename_),
67  stream_(open_streams_[filename_]) {
68  ++open_counts_[filename_];
69 }
70 
71 File& File::operator=(const File& rhs) {
72  // This accounts for self-assignment and assignment of a File object for the
73  // same file.
74  close(); //close my file and associate me with the new one
75  filename_ = rhs.filename_;
76  openIfNeeded(false /* create_new */);
77  return *this;
78 }
79 
81  close();
82 }
83 
85  FileHeader header = readHeader();
86  Page new_page;
87  Page existing_page;
88  if (header.num_free_pages > 0) {
89  new_page = readPage(header.first_free_page, true /* allow_free */);
90  new_page.set_page_number(header.first_free_page);
91  header.first_free_page = new_page.next_page_number();
92  --header.num_free_pages;
93 
94  if (header.first_used_page == Page::INVALID_NUMBER ||
95  header.first_used_page > new_page.page_number()) {
96  // Either have no pages used or the head of the used list is a page later
97  // than the one we just allocated, so add the new page to the head.
98  if (header.first_used_page > new_page.page_number()) {
99  new_page.set_next_page_number(header.first_used_page);
100  }
101  header.first_used_page = new_page.page_number();
102  } else {
103  // New page is reused from somewhere after the beginning, so we need to
104  // find where in the used list to insert it.
105  PageId next_page_number = Page::INVALID_NUMBER;
106  for (FileIterator iter = begin(); iter != end(); ++iter) {
107  next_page_number = (*iter).next_page_number();
108  if (next_page_number > new_page.page_number() ||
109  next_page_number == Page::INVALID_NUMBER) {
110  existing_page = *iter;
111  break;
112  }
113  }
114  existing_page.set_next_page_number(new_page.page_number());
115  new_page.set_next_page_number(next_page_number);
116  }
117 
118  assert((header.num_free_pages == 0) ==
120  } else {
121  new_page.set_page_number(header.num_pages);
122  if (header.first_used_page == Page::INVALID_NUMBER) {
123  header.first_used_page = new_page.page_number();
124  } else {
125  // If we have pages allocated, we need to add the new page to the tail
126  // of the linked list.
127  for (FileIterator iter = begin(); iter != end(); ++iter) {
128  if ((*iter).next_page_number() == Page::INVALID_NUMBER) {
129  existing_page = *iter;
130  break;
131  }
132  }
133  assert(existing_page.isUsed());
134  existing_page.set_next_page_number(new_page.page_number());
135  }
136  ++header.num_pages;
137  }
138  writePage(new_page.page_number(), new_page);
139  if (existing_page.page_number() != Page::INVALID_NUMBER) {
140  // If we updated an existing page by inserting the new page into the
141  // used list, we need to write it out.
142  writePage(existing_page.page_number(), existing_page);
143  }
144  writeHeader(header);
145 
146  return new_page;
147 }
148 
149 Page File::readPage(const PageId page_number) const {
150  FileHeader header = readHeader();
151  if (page_number >= header.num_pages) {
152  throw InvalidPageException(page_number, filename_);
153  }
154  return readPage(page_number, false /* allow_free */);
155 }
156 
157 Page File::readPage(const PageId page_number, const bool allow_free) const {
158  Page page;
159  stream_->seekg(pagePosition(page_number), std::ios::beg);
160  stream_->read(reinterpret_cast<char*>(&page.header_), sizeof(page.header_));
161  stream_->read(reinterpret_cast<char*>(&page.data_[0]), Page::DATA_SIZE);
162  if (!allow_free && !page.isUsed()) {
163  throw InvalidPageException(page_number, filename_);
164  }
165 
166  return page;
167 }
168 
169 void File::writePage(const Page& new_page) {
170  PageHeader header = readPageHeader(new_page.page_number());
172  // Page has been deleted since it was read.
173  throw InvalidPageException(new_page.page_number(), filename_);
174  }
175  // Page on disk may have had its next page pointer updated since it was read;
176  // we don't modify that, but we do keep all the other modifications to the
177  // page header.
178  const PageId next_page_number = header.next_page_number;
179  header = new_page.header_;
180  header.next_page_number = next_page_number;
181  writePage(new_page.page_number(), header, new_page);
182 }
183 
184 void File::deletePage(const PageId page_number) {
185  FileHeader header = readHeader();
186  Page existing_page = readPage(page_number);
187  Page previous_page;
188  // If this page is the head of the used list, update the header to point to
189  // the next page in line.
190  if (page_number == header.first_used_page) {
191  header.first_used_page = existing_page.next_page_number();
192  } else {
193  // Walk the used list so we can update the page that points to this one.
194  for (FileIterator iter = begin(); iter != end(); ++iter) {
195  previous_page = *iter;
196  if (previous_page.next_page_number() == existing_page.page_number()) {
197  previous_page.set_next_page_number(existing_page.next_page_number());
198  break;
199  }
200  }
201  }
202  // Clear the page and add it to the head of the free list.
203  existing_page.initialize();
204  existing_page.set_next_page_number(header.first_free_page);
205  header.first_free_page = page_number;
206  ++header.num_free_pages;
207  if (previous_page.isUsed()) {
208  writePage(previous_page.page_number(), previous_page);
209  }
210  writePage(page_number, existing_page);
211  writeHeader(header);
212 }
213 
215  const FileHeader& header = readHeader();
216  return FileIterator(this, header.first_used_page);
217 }
218 
220  return FileIterator(this, Page::INVALID_NUMBER);
221 }
222 
223 File::File(const std::string& name, const bool create_new) : filename_(name) {
224  openIfNeeded(create_new);
225 
226  if (create_new) {
227  // File starts with 1 page (the header).
228  FileHeader header = {1 /* num_pages */, 0 /* first_used_page */,
229  0 /* num_free_pages */, 0 /* first_free_page */};
230  writeHeader(header);
231  }
232 }
233 
234 void File::openIfNeeded(const bool create_new) {
235  if (open_counts_.find(filename_) != open_counts_.end()) { //exists an entry already
236  ++open_counts_[filename_];
237  stream_ = open_streams_[filename_];
238  } else {
239  std::ios_base::openmode mode =
240  std::fstream::in | std::fstream::out | std::fstream::binary;
241  const bool already_exists = exists(filename_);
242  if (create_new) {
243  // Error if we try to overwrite an existing file.
244  if (already_exists) {
245  throw FileExistsException(filename_);
246  }
247  // New files have to be truncated on open.
248  mode = mode | std::fstream::trunc;
249  } else {
250  // Error if we try to open a file that doesn't exist.
251  if (!already_exists) {
252  throw FileNotFoundException(filename_);
253  }
254  }
255  stream_.reset(new std::fstream(filename_, mode));
256  open_streams_[filename_] = stream_;
257  open_counts_[filename_] = 1;
258  }
259 }
260 
261 void File::close() {
262  --open_counts_[filename_];
263  stream_.reset();
264  if (open_counts_[filename_] == 0) {
265  open_streams_.erase(filename_);
266  open_counts_.erase(filename_);
267  }
268 }
269 
270 void File::writePage(const PageId page_number, const Page& new_page) {
271  writePage(page_number, new_page.header_, new_page);
272 }
273 
274 void File::writePage(const PageId page_number, const PageHeader& header,
275  const Page& new_page) {
276  stream_->seekp(pagePosition(page_number), std::ios::beg);
277  stream_->write(reinterpret_cast<const char*>(&header), sizeof(header));
278  stream_->write(reinterpret_cast<const char*>(&new_page.data_[0]),
280  stream_->flush();
281 }
282 
283 FileHeader File::readHeader() const {
284  FileHeader header;
285  stream_->seekg(0 /* pos */, std::ios::beg);
286  stream_->read(reinterpret_cast<char*>(&header), sizeof(header));
287 
288  return header;
289 }
290 
291 void File::writeHeader(const FileHeader& header) {
292  stream_->seekp(0 /* pos */, std::ios::beg);
293  stream_->write(reinterpret_cast<const char*>(&header), sizeof(header));
294  stream_->flush();
295 }
296 
297 PageHeader File::readPageHeader(PageId page_number) const {
298  PageHeader header;
299  stream_->seekg(pagePosition(page_number), std::ios::beg);
300  stream_->read(reinterpret_cast<char*>(&header), sizeof(header));
301 
302  return header;
303 }
304 
305 }
static bool isOpen(const std::string &filename)
Definition: file.cpp:47
PageId first_free_page
Definition: file.h:43
FileIterator begin()
Definition: file.cpp:214
FileIterator end()
Definition: file.cpp:219
PageId next_page_number() const
Definition: page.h:200
Header metadata for files on disk which contain pages.
Definition: file.h:24
PageId next_page_number
Definition: page.h:58
static File open(const std::string &filename)
Definition: file.cpp:33
Class which represents a file in the filesystem containing database pages.
Definition: file.h:73
static const std::size_t DATA_SIZE
Definition: page.h:118
static File create(const std::string &filename)
Definition: file.cpp:29
std::uint32_t PageId
Identifier for a page in a file.
Definition: types.h:15
PageId num_pages
Definition: file.h:28
An exception that is thrown when a file operation is requested for a filename that doesn't exist...
An exception that is thrown when an attempt is made to access an invalid page in a file...
Class which represents a fixed-size database page containing records.
Definition: page.h:107
void writePage(const Page &new_page)
Definition: file.cpp:169
An exception that is thrown when a file deletion is requested for a filename that's currently open...
PageId page_number() const
Definition: page.h:193
Iterator for iterating over the pages in a file.
Definition: file_iterator.h:23
File(const File &other)
Definition: file.cpp:65
static void remove(const std::string &filename)
Definition: file.cpp:37
Page readPage(const PageId page_number) const
Definition: file.cpp:149
PageId num_free_pages
Definition: file.h:38
PageId first_used_page
Definition: file.h:33
Page allocatePage()
Definition: file.cpp:84
File & operator=(const File &rhs)
Definition: file.cpp:71
static const PageId INVALID_NUMBER
Definition: page.h:123
PageId current_page_number
Definition: page.h:53
Header metadata in a page.
Definition: page.h:25
static bool exists(const std::string &filename)
Definition: file.cpp:54
void deletePage(const PageId page_number)
Definition: file.cpp:184